From b0a675eb97b2a3fbdd2b88835f39dba447fabed4 Mon Sep 17 00:00:00 2001 From: pasqualevitiello Date: Sat, 24 Oct 2026 22:52:37 +0200 Subject: [PATCH 01/15] feat(ui): add independent Plotter illustration lab and skill --- apps/ui/AGENTS.md | 12 + apps/ui/app/illustration-lab/README.md | 19 + .../illustration-lab/blueprint-drawing.tsx | 17 + apps/ui/app/illustration-lab/cal-studies.tsx | 490 +++++++++++++++ .../app/illustration-lab/flat-cal-studies.tsx | 260 ++++++++ .../illustration-lab/flat-object-studies.tsx | 249 ++++++++ .../illustration-lab/flat-system-studies.tsx | 269 +++++++++ .../app/illustration-lab/illustration-lab.tsx | 570 ++++++++++++++++++ .../interface-detail-studies.tsx | 230 +++++++ .../app/illustration-lab/network-studies.tsx | 497 +++++++++++++++ .../not-found-architecture-studies.tsx | 190 ++++++ .../illustration-lab/not-found-blueprint.tsx | 67 ++ .../not-found-geometry-studies.tsx | 157 +++++ .../app/illustration-lab/object-studies.tsx | 519 ++++++++++++++++ .../original-not-found-blueprint.tsx | 90 +++ .../original-particles-blueprint.tsx | 120 ++++ apps/ui/app/illustration-lab/page.tsx | 90 +++ .../illustration-lab/particles-blueprint.tsx | 117 ++++ .../popular-service-studies.tsx | 366 +++++++++++ .../illustration-lab/primitives-blueprint.tsx | 91 +++ .../app/illustration-lab/service-studies.tsx | 472 +++++++++++++++ .../ui/app/illustration-lab/state-studies.tsx | 129 ++++ .../app/illustration-lab/study-direction.ts | 104 ++++ apps/ui/app/illustration-lab/study-types.ts | 29 + .../app/illustration-lab/system-studies.tsx | 451 ++++++++++++++ apps/ui/next.config.ts | 5 + apps/ui/skills/plotter/README.md | 13 + apps/ui/skills/plotter/SKILL.md | 46 ++ apps/ui/skills/plotter/agents/openai.yaml | 4 + .../ui/skills/plotter/references/direction.md | 75 +++ .../plotter/references/implementation.md | 42 ++ apps/ui/skills/plotter/references/linear.png | Bin 0 -> 12018 bytes .../references/not-found-interrupted-path.png | Bin 0 -> 30585 bytes .../references/particles-recombination.png | Bin 0 -> 40822 bytes .../skills/plotter/references/particles.png | Bin 0 -> 43328 bytes .../skills/plotter/references/primitives.png | Bin 0 -> 57528 bytes 36 files changed, 5790 insertions(+) create mode 100644 apps/ui/app/illustration-lab/README.md create mode 100644 apps/ui/app/illustration-lab/blueprint-drawing.tsx create mode 100644 apps/ui/app/illustration-lab/cal-studies.tsx create mode 100644 apps/ui/app/illustration-lab/flat-cal-studies.tsx create mode 100644 apps/ui/app/illustration-lab/flat-object-studies.tsx create mode 100644 apps/ui/app/illustration-lab/flat-system-studies.tsx create mode 100644 apps/ui/app/illustration-lab/illustration-lab.tsx create mode 100644 apps/ui/app/illustration-lab/interface-detail-studies.tsx create mode 100644 apps/ui/app/illustration-lab/network-studies.tsx create mode 100644 apps/ui/app/illustration-lab/not-found-architecture-studies.tsx create mode 100644 apps/ui/app/illustration-lab/not-found-blueprint.tsx create mode 100644 apps/ui/app/illustration-lab/not-found-geometry-studies.tsx create mode 100644 apps/ui/app/illustration-lab/object-studies.tsx create mode 100644 apps/ui/app/illustration-lab/original-not-found-blueprint.tsx create mode 100644 apps/ui/app/illustration-lab/original-particles-blueprint.tsx create mode 100644 apps/ui/app/illustration-lab/page.tsx create mode 100644 apps/ui/app/illustration-lab/particles-blueprint.tsx create mode 100644 apps/ui/app/illustration-lab/popular-service-studies.tsx create mode 100644 apps/ui/app/illustration-lab/primitives-blueprint.tsx create mode 100644 apps/ui/app/illustration-lab/service-studies.tsx create mode 100644 apps/ui/app/illustration-lab/state-studies.tsx create mode 100644 apps/ui/app/illustration-lab/study-direction.ts create mode 100644 apps/ui/app/illustration-lab/study-types.ts create mode 100644 apps/ui/app/illustration-lab/system-studies.tsx create mode 100644 apps/ui/skills/plotter/README.md create mode 100644 apps/ui/skills/plotter/SKILL.md create mode 100644 apps/ui/skills/plotter/agents/openai.yaml create mode 100644 apps/ui/skills/plotter/references/direction.md create mode 100644 apps/ui/skills/plotter/references/implementation.md create mode 100644 apps/ui/skills/plotter/references/linear.png create mode 100644 apps/ui/skills/plotter/references/not-found-interrupted-path.png create mode 100644 apps/ui/skills/plotter/references/particles-recombination.png create mode 100644 apps/ui/skills/plotter/references/particles.png create mode 100644 apps/ui/skills/plotter/references/primitives.png diff --git a/apps/ui/AGENTS.md b/apps/ui/AGENTS.md index 7ccb236026..912a54e943 100644 --- a/apps/ui/AGENTS.md +++ b/apps/ui/AGENTS.md @@ -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/illustration-lab` page. + 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. --- @@ -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. + + + +## 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. + + diff --git a/apps/ui/app/illustration-lab/README.md b/apps/ui/app/illustration-lab/README.md new file mode 100644 index 0000000000..26b2187ece --- /dev/null +++ b/apps/ui/app/illustration-lab/README.md @@ -0,0 +1,19 @@ +# Plotter illustration lab + +Direct route: `/ui/illustration-lab`. This page is intentionally absent from site navigation and the sitemap, and its metadata disables indexing. The old prototype URL redirects here with its query parameters. + +The lab preserves all 54 remaining experiments, their stable codes, critique notes, and earlier rejected directions. E04 was removed at the user’s request. The current-direction filter hides earlier rejected studies; All experiments reveals them. Drawing/hero previews and the in-memory shortlist remain available. + +## Production selections + +| Study | Selected page | Lab source | +| --- | --- | --- | +| P00 | Main | `./primitives-blueprint.tsx` | +| C03 | Particles | `./particles-blueprint.tsx` | +| E02 | 404 | `./not-found-blueprint.tsx` | + +The lab 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 historical studies. When a selected production drawing changes, update its lab 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 experiment is unreviewed until the user responds. Add studies to their subject collection and preserve existing IDs when revising them. diff --git a/apps/ui/app/illustration-lab/blueprint-drawing.tsx b/apps/ui/app/illustration-lab/blueprint-drawing.tsx new file mode 100644 index 0000000000..1b1e0aebdf --- /dev/null +++ b/apps/ui/app/illustration-lab/blueprint-drawing.tsx @@ -0,0 +1,17 @@ +import type { ReactNode } from "react"; + +export function BlueprintDrawing({ children }: { children: ReactNode }) { + return ( + + ); +} diff --git a/apps/ui/app/illustration-lab/cal-studies.tsx b/apps/ui/app/illustration-lab/cal-studies.tsx new file mode 100644 index 0000000000..f4e44915ef --- /dev/null +++ b/apps/ui/app/illustration-lab/cal-studies.tsx @@ -0,0 +1,490 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; +import type { IllustrationStudy } from "./study-types"; + +function RegistrationMarks() { + return ( + + + + + ); +} + +function BoundCalendar() { + return ( + + + + + + + + + + + + + + + + + + + + + + + {Array.from({ length: 31 }, (_, index) => index + 1).map((day) => { + const position = day + 2; + return day === 24 ? null : ( + + ); + })} + + + + + October + + + 2026 + + + 24 + + + + + + + + + + + 7 days + + + Yours + + + + + + ); +} + +function SharedAvailability() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A + + + B + + + + + + + + + + + 13:00 + + + 15:00 + + + 2h + + + Shared availability + + + + + ); +} + +function ClockFace({ cx, hour }: { cx: number; hour: number }) { + const hourAngle = (hour / 12) * Math.PI * 2; + + return ( + + + + + {Array.from({ length: 12 }, (_, index) => index).map((hourTick) => ( + + ))} + + + + + + + + + ); +} + +function TwoTimeZones() { + return ( + + + + + + + + + + + + + + + + +6h + + + 09:30 + + + UTC + + + 15:30 + + + UTC+6 + + + + + ); +} + +function BookingTicket() { + return ( + + + + + + + + + + + + + + + + + October + + + 24 + + + + + + + + + + + + + + + + + + Booked + + + 14:30 + + + Confirmed + + + 30 min + + + + + ); +} + +function HostPortrait({ cy }: { cy: number }) { + return ( + + + + + + ); +} + +function RoutedBooking() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Request + + + Design + + + Support + + + Sales + + + One match + + + + + ); +} + +export const calStudies: IllustrationStudy[] = [ + { + id: "cal-calendar", + code: "K01", + title: "A day worth keeping", + description: + "A bound calendar isolates one date within a measured month. A lifted corner and fine page edges give the familiar object a quiet physical presence.", + category: "Cal.com", + illustration: , + }, + { + 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: "Cal.com", + illustration: , + }, + { + id: "cal-time-zones", + code: "K03", + title: "One moment, two places", + description: + "Two clock faces show the same instant six hours apart. Their shared minute hand and connecting datum make the time difference tangible.", + category: "Cal.com", + illustration: , + }, + { + id: "cal-booking", + code: "K04", + title: "Time, reserved", + description: + "A day on the calendar becomes a perforated confirmation ticket, turning available time into a definite appointment.", + category: "Cal.com", + illustration: , + }, + { + id: "cal-routing", + code: "K05", + title: "The right person", + description: + "One booking follows a clear route to its matching host. The other lanes recede, leaving the selected connection unmistakable.", + category: "Cal.com", + illustration: , + }, +]; diff --git a/apps/ui/app/illustration-lab/flat-cal-studies.tsx b/apps/ui/app/illustration-lab/flat-cal-studies.tsx new file mode 100644 index 0000000000..3b0537db23 --- /dev/null +++ b/apps/ui/app/illustration-lab/flat-cal-studies.tsx @@ -0,0 +1,260 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; +import type { IllustrationStudy } from "./study-types"; + +const weekDays = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; +const weekHours = ["09:00", "10:00", "11:00", "12:00", "13:00"]; + +function WeekConstruction() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {weekDays.map((day, index) => ( + + {day} + + ))} + + + {weekHours.map((hour, index) => ( + + {hour} + + ))} + + 7 days + + + 4 h + + + 1 day + + + Thu · 10:00–11:00 + + + + + + + ); +} + +function TimeSlotConstruction() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 15 min + + + 15 min + + + 30 min + + + + + u + + + u + + + u + + + R28 + + + + + + + ); +} + +function AvailabilityConstruction() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + u + + + 6u + + + 4u + + + + + Alex’s availability + + + Sam’s availability + + + Shared slot + + + + + 30 min meeting + + + + + + + ); +} + +export const flatCalStudies: IllustrationStudy[] = [ + { + id: "cal-week-grid", + code: "K06", + title: "A week, constructed", + description: + "Monday through Sunday on a measured hourly grid. One constructed circle locates a meeting on Thursday, from 10:00 to 11:00.", + category: "Cal.com", + illustration: , + revision: true, + }, + { + id: "cal-time-modules", + code: "K07", + title: "The shape of a time slot", + description: + "Two measured intervals combine into one reusable time slot. Duration becomes a rule of composition.", + category: "Cal.com", + illustration: , + revision: true, + }, + { + id: "cal-availability-plan", + code: "K08", + title: "Where schedules meet", + description: + "Alex’s and Sam’s availability meet in one shared slot. Named bands and a meeting callout make the overlap explicit.", + category: "Cal.com", + illustration: , + revision: true, + }, +]; diff --git a/apps/ui/app/illustration-lab/flat-object-studies.tsx b/apps/ui/app/illustration-lab/flat-object-studies.tsx new file mode 100644 index 0000000000..25bfb948ea --- /dev/null +++ b/apps/ui/app/illustration-lab/flat-object-studies.tsx @@ -0,0 +1,249 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; +import type { IllustrationStudy } from "./study-types"; + +const keyboardKeys = [ + { x: 136, y: 124, width: 68, label: "Tab" }, + { x: 212, y: 124, width: 44, label: "Q" }, + { x: 264, y: 124, width: 44, label: "W" }, + { x: 316, y: 124, width: 44, label: "E" }, + { x: 368, y: 124, width: 44, label: "R" }, + { x: 136, y: 176, width: 80, label: "Caps" }, + { x: 224, y: 176, width: 44, label: "A" }, + { x: 276, y: 176, width: 44, label: "S" }, + { x: 328, y: 176, width: 44, label: "D" }, + { x: 380, y: 176, width: 44, label: "F" }, + { x: 136, y: 228, width: 104, label: "Shift" }, + { x: 248, y: 228, width: 44, label: "Z" }, + { x: 300, y: 228, width: 44, label: "X" }, + { x: 352, y: 228, width: 44, label: "C" }, + { x: 136, y: 280, width: 44, label: "Ctrl" }, + { x: 188, y: 280, width: 44, label: "Fn" }, + { x: 240, y: 280, width: 44, label: "Alt" }, +]; + +function KeyboardPlanStudy() { + return ( + + + + + + + + + + + + {keyboardKeys.map((key) => ( + + ))} + + + + + + + + + + + {keyboardKeys.map((key) => ( + 1 ? "9" : "11"} + key={key.label} + x={key.x + key.width / 2} + y={key.y + 26} + > + {key.label} + + ))} + + V + + + Space + + + + + + + + + + + + Pitch / 52 + + + 44 + + + Keyboard / detail + + + + ); +} + +function LampElevationStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 184 + + + R92 + + + 220 + + + 116 + + + 160 + + + Elevation + + + + + + + ); +} + +const folderPositions = [ + { x: 104, y: 112 }, + { x: 312, y: 112 }, + { x: 208, y: 256 }, +]; + +function FolderPlanStudy() { + return ( + + + + + + + + + + + {folderPositions.map(({ x, y }) => ( + + ))} + + + + + + + + + + + + + + + + + + + + Tab / 56 + + + R8 + + + 48 + + + 104 + + + Module / 160 + + + + ); +} + +export const flatObjectStudies: IllustrationStudy[] = [ + { + id: "object-keyboard-plan", + code: "O03", + title: "A keyboard, in detail", + description: + "An enlarged portion of a keyboard, with staggered letter rows and wider modifiers. Open edges and dashed continuation reveal a detail within a larger system.", + category: "Objects", + illustration: , + revision: true, + }, + { + id: "object-lamp-elevation", + code: "O04", + title: "A lamp, drawn in elevation", + description: + "A semicircular shade, a centered stem, and a measured base. One radius establishes the profile, with every part held to the same vertical axis.", + category: "Objects", + illustration: , + }, + { + id: "object-folder-plan", + code: "O05", + title: "A family of folders", + description: + "Three independent folders share one tab, radius, and outline. Equal spacing and a common centerline turn a familiar shape into a small construction system.", + category: "Objects", + illustration: , + revision: true, + }, +]; diff --git a/apps/ui/app/illustration-lab/flat-system-studies.tsx b/apps/ui/app/illustration-lab/flat-system-studies.tsx new file mode 100644 index 0000000000..0624c0669b --- /dev/null +++ b/apps/ui/app/illustration-lab/flat-system-studies.tsx @@ -0,0 +1,269 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; +import type { IllustrationStudy } from "./study-types"; + +function SpacingPlanStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + R16 + + + 3u + + + + u + + + u + + + u + + + u + + + 12u + + + + + + + + ); +} + +const switchInputs = [148, 212, 276]; +const switchOutputs = [216, 280, 344]; + +function SwitchPlanStudy() { + return ( + + + + + + + + + + + + + + {switchInputs.map((y) => ( + + ))} + {switchOutputs.map((x) => ( + + ))} + + + + {switchOutputs.flatMap((x) => + switchInputs.map((y) => ( + + )), + )} + + + + {switchInputs.map((y) => ( + + ))} + {switchOutputs.map((x) => ( + + ))} + + + + + + + + + u + + + u + + + u + + + u + + + B / 02 + + + + A + + + B + + + C + + + + + 01 + + + 02 + + + 03 + + + + + + + + ); +} + +const matrixColumns = [152, 184, 216, 248, 280, 312, 344, 376, 408]; +const matrixRows = [104, 136, 168, 200, 232, 264, 296]; +const distributionPoints = [ + [248, 168], + [280, 168], + [312, 168], + [248, 200], + [280, 200], + [312, 200], + [280, 232], + [312, 232], +] as const; + +function CoordinatePlanStudy() { + return ( + + + + + + + {matrixColumns.flatMap((x) => + matrixRows.map((y) => ( + + )), + )} + + + + + + + + + + + + + + {distributionPoints.map(([x, y]) => ( + + ))} + + + + + (6u, 5u) + + + 6u + + + 5u + + + 0 + + + x + + + y + + + + ); +} + +export const flatSystemStudies: IllustrationStudy[] = [ + { + id: "interface-spacing-plan", + code: "I03", + title: "From parts to a whole", + description: + "Three separate elements project into one control. Equal gaps, a common centerline, and a shared radius make the assembly rule visible.", + category: "Interfaces", + illustration: , + revision: true, + }, + { + id: "workflow-switch-plan", + code: "W03", + title: "A connection, located", + description: + "Three inputs meet three outputs on a regular switching grid. A single closed junction connects B to 02, turning an abstract matrix into a precise route.", + category: "Workflows", + illustration: , + revision: true, + }, + { + id: "data-coordinate-plan", + code: "A03", + title: "A measured distribution", + description: + "A small cluster occupies a regular coordinate field. Two projected measurements locate one point and make the underlying rule visible.", + category: "Data", + illustration: , + revision: true, + }, +]; diff --git a/apps/ui/app/illustration-lab/illustration-lab.tsx b/apps/ui/app/illustration-lab/illustration-lab.tsx new file mode 100644 index 0000000000..cef963aece --- /dev/null +++ b/apps/ui/app/illustration-lab/illustration-lab.tsx @@ -0,0 +1,570 @@ +"use client"; + +import { RiArrowLeftLine } from "@remixicon/react"; +import { + ArrowLeftIcon, + ArrowRightIcon, + CheckIcon, + CopyIcon, + StarIcon, +} from "lucide-react"; +import { useSearchParams } from "next/navigation"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { cn } from "@/registry/default/lib/utils"; +import { Button, buttonVariants } from "@/registry/default/ui/button"; +import type { IllustrationStudy, StudyCategory } from "./study-types"; + +const categories: StudyCategory[] = [ + "Primitives", + "Particles", + "Cal.com", + "Interfaces", + "Services", + "Workflows", + "Data", + "Documents", + "Typography", + "States", + "404", + "Objects", +]; + +function studyLabel(study: IllustrationStudy) { + if (study.selected) return "Selected for coss"; + if (study.review === "reference") return "Style reference"; + if (study.review === "outside") return "Outside direction"; + if (study.review === "partial") { + return study.revision ? "Revised · awaiting review" : "Partly fits"; + } + if (study.revision) return "Revised 2D study"; + return study.baseline ? "Original study" : "Unreviewed"; +} + +const heroCopy: Record< + StudyCategory, + { title: string; description: string; action: string } +> = { + "Cal.com": { + title: "Make time for what matters.", + description: + "A clear path from finding the right time to meeting the right person.", + action: "Find a time", + }, + Interfaces: { + title: "Every detail has a place.", + description: + "Thoughtful parts, composed into interfaces that feel natural to use.", + action: "Explore components", + }, + Services: { + title: "Systems, made visible.", + description: + "From a request to a response, from an idea to a release. A precise drawing of how things connect.", + action: "Explore the system", + }, + Documents: { + title: "Ideas, given a home.", + description: + "Collect what you know. Give it structure. Make it easy to discover.", + action: "Open the library", + }, + Workflows: { + title: "Good connections move things forward.", + description: + "Bring the right steps together, with a clear route from start to finish.", + action: "Explore workflows", + }, + Objects: { + title: "Familiar forms. Precise construction.", + description: + "Familiar objects reveal the small decisions that make a design work.", + action: "Look closer", + }, + Data: { + title: "Find the shape in your information.", + description: + "Give complex information a clear structure, so the useful details emerge.", + action: "Explore your data", + }, + Typography: { + title: "Character, by construction.", + description: + "A curve, a counter, a carefully chosen proportion. Small decisions build an identity.", + action: "Explore the details", + }, + States: { + title: "Clarity at every step.", + description: + "A missing destination or a completed task, expressed in a single clear picture.", + action: "Find your next step", + }, + "404": { + title: "Page Not Found", + description: + "The page you’re looking for doesn’t exist or may have been moved.", + action: "Back to Home", + }, + Primitives: { + title: "A new, modern UI component library built on top of Base UI.", + description: "Built for developers and AI.", + action: "Get started", + }, + Particles: { + title: "Browse Particles", + description: + "Ready-to-use particles, the building blocks of your design system. Small pieces, endless possibilities.", + action: "Explore particles", + }, +}; + +export function IllustrationLab({ studies }: { studies: IllustrationStudy[] }) { + const params = useSearchParams(); + const firstStudy = studies.find((study) => study.review !== "outside"); + if (!firstStudy) throw new Error("The illustration lab needs a study."); + const active = + studies.find((study) => study.id === params.get("variant")) ?? firstStudy; + const showEarlier = + params.get("scope") === "all" || active.review === "outside"; + const workingStudies = studies.filter((study) => study.review !== "outside"); + const visibleStudies = showEarlier ? studies : workingStudies; + const visibleCategories = categories.filter((category) => + visibleStudies.some((study) => study.category === category), + ); + const view = params.get("view") === "hero" ? "hero" : "drawing"; + const categoryStudies = visibleStudies.filter( + (study) => study.category === active.category, + ); + const position = categoryStudies.findIndex((study) => study.id === active.id); + const [shortlist, setShortlist] = useState([]); + const [copyStatus, setCopyStatus] = useState(""); + const stage = useRef(null); + + const selectStudy = useCallback((id: string) => { + const next = new URLSearchParams(window.location.search); + next.set("variant", id); + window.history.replaceState(null, "", `?${next}`); + }, []); + + const cycle = useCallback( + (direction: number) => { + const group = studies.filter( + (study) => + study.category === active.category && + (showEarlier || study.review !== "outside"), + ); + const index = group.findIndex((study) => study.id === active.id); + const nextStudy = + group[(index + direction + group.length) % group.length]; + if (nextStudy) selectStudy(nextStudy.id); + }, + [active.category, active.id, selectStudy, showEarlier, studies], + ); + + useEffect(() => { + const handleKey = (event: KeyboardEvent) => { + if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) + return; + if ( + event.target instanceof Element && + event.target.closest( + "input, textarea, select, [contenteditable], [role=tablist], [role=radiogroup]", + ) + ) + return; + if (event.key === "ArrowLeft" || event.key === "ArrowRight") { + event.preventDefault(); + cycle(event.key === "ArrowLeft" ? -1 : 1); + } + }; + window.addEventListener("keydown", handleKey); + return () => window.removeEventListener("keydown", handleKey); + }, [cycle]); + + function setView(nextView: string) { + const next = new URLSearchParams(window.location.search); + next.set("view", nextView); + next.set("variant", active.id); + window.history.replaceState(null, "", `?${next}`); + } + + function setScope(includeEarlier: boolean) { + const next = new URLSearchParams(window.location.search); + if (includeEarlier) { + next.set("scope", "all"); + } else { + next.delete("scope"); + const selection = + active.review !== "outside" + ? active + : (workingStudies.find( + (study) => study.category === active.category, + ) ?? firstStudy); + if (selection) next.set("variant", selection.id); + } + window.history.replaceState(null, "", `?${next}`); + } + + function toggleShortlist(id: string) { + setShortlist((current) => + current.includes(id) + ? current.filter((item) => item !== id) + : [...current, id], + ); + setCopyStatus(""); + } + + async function copyShortlist() { + const selected = studies.filter((study) => shortlist.includes(study.id)); + const text = selected + .map((study) => `${study.code} · ${study.category} · ${study.title}`) + .join("\n"); + try { + await navigator.clipboard.writeText( + `My illustration shortlist:\n${text}`, + ); + setCopyStatus("Copied — paste your choices into the chat."); + } catch { + setCopyStatus( + "Copy unavailable. You can share the study codes below instead.", + ); + } + } + + const copy = heroCopy[active.category]; + + return ( +
+
+

+ Plotter / {workingStudies.length} current studies /{" "} + {studies.filter((study) => study.revision).length} revised +

+

+ Flat forms. Clear construction. +

+

+ Primitives and Particles set the reference: geometric forms and small + interface elements drawn as precise 2D constructions. Guides reveal + how the composition is built. +

+
+ +
+
+ + +
+
+ {visibleCategories.map((category) => ( + + ))} +
+
+

+ {active.category} / {categoryStudies.length}{" "} + {categoryStudies.length === 1 ? "study" : "studies"} +

+
+ + +
+
+
+ +
+
+
+

+ {active.code} / {active.category} + {" / "} + {studyLabel(active)} +

+

{active.title}

+ {active.reviewNote && ( +

+ {active.reviewNote} +

+ )} +
+ +
+ {view === "drawing" ? ( +
+ {active.illustration} +
+ ) : ( +
+
+

+ {copy.title} +

+

+ {copy.description} +

+ + {active.category === "404" && ( + +
+
+ {active.illustration} +
+
+ )} +

+ {active.description} +

+
+ +
+
+

All {active.category} studies

+

+ Select a drawing to inspect it above. +

+
+
+ {categoryStudies.map((study) => ( +
+ + +
+ ))} +
+
+ +
+
+

+ Your shortlist{" "} + / {shortlist.length} +

+ +
+ {shortlist.length ? ( +
+ {studies + .filter((study) => shortlist.includes(study.id)) + .map((study) => ( + + ))} +
+ ) : ( +

+ Star the studies you like, then copy your choices into the chat. +

+ )} +

+ {copyStatus || + "Your shortlist lasts for this visit. Copy it before refreshing or leaving."} +

+
+ +
+ + The direction we’re keeping + +

+ One flat drawing plane. Circles, rectangles, arcs, slots, and small + interface fragments. Fine outlines, quieter guides, sparse dimensions, + and generous empty space. Every guide explains a real relationship: + radius, spacing, alignment, connection, or assembly. No perspective, + volume, or tilted objects. The reference is a construction study, + rather than an object, screen mockup, or large pictogram decorated + with technical marks. Unreviewed studies remain candidates, not + approvals. +

+
+ + +
+ ); +} diff --git a/apps/ui/app/illustration-lab/interface-detail-studies.tsx b/apps/ui/app/illustration-lab/interface-detail-studies.tsx new file mode 100644 index 0000000000..6366d1325e --- /dev/null +++ b/apps/ui/app/illustration-lab/interface-detail-studies.tsx @@ -0,0 +1,230 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; +import type { IllustrationStudy } from "./study-types"; + +function BrowserPlanStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + coss.com + + + coss.com/ui + + + + + + + + + + + + 18 + + + R24 + + + 120 + + + Viewport + + + Browser / detail + + + + ); +} + +function PhonePlanStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + R24 + + + R16 + + + Safe area + + + 9u + + + 18u + + + 6u + + + 6u + + + + + + + + ); +} + +function ChatPlanStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Can we build it? + + + One part at a time. + + + + + 216 + + + R16 + + + 24 + + + 24 + + + Inset / 24 + + + 48 + + + + ); +} + +export const interfaceDetailStudies: IllustrationStudy[] = [ + { + id: "interface-browser-plan", + code: "I04", + title: "A browser, in detail", + description: + "An enlarged corner of browser chrome. A tab, navigation controls, and an open address field reveal the window’s construction while dashed edges continue beyond the drawing.", + category: "Interfaces", + illustration: , + revision: true, + }, + { + id: "interface-phone-plan", + code: "I05", + title: "The screen within the frame", + description: + "A smartphone in front elevation. Concentric corner radii and a measured safe area explain the relationship between the frame and its content.", + category: "Interfaces", + illustration: , + }, + { + id: "interface-chat-plan", + code: "I06", + title: "A conversation, constructed", + description: + "Two rounded message blocks pair with circular avatars. Shared radius, text inset, and avatar spacing construct the conversation without pointed tails.", + category: "Interfaces", + illustration: , + revision: true, + }, +]; diff --git a/apps/ui/app/illustration-lab/network-studies.tsx b/apps/ui/app/illustration-lab/network-studies.tsx new file mode 100644 index 0000000000..cbcf17e259 --- /dev/null +++ b/apps/ui/app/illustration-lab/network-studies.tsx @@ -0,0 +1,497 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; +import type { IllustrationStudy } from "./study-types"; + +function RegistrationMarks() { + return ( + + + + + ); +} + +function InterfaceLayersStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + u + + + u + + + assembly + + + + + ); +} + +function AdaptiveLayoutStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + wide + + + narrow + + + + + ); +} + +function ConnectionBridgeStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + span + + + joined + + + 01 + + + 02 + + + + + ); +} + +function BranchingWorkflowStudy() { + const routes = + "M132 224H216A24 24 0 0 0 240 200V152A24 24 0 0 1 264 128H420 M132 224H420 M132 224H216A24 24 0 0 1 240 248V296A24 24 0 0 0 264 320H420"; + + return ( + + + {[168, 192, 216, 240, 264, 288, 312, 336, 360, 384].map((x) => ( + + ))} + {[152, 176, 200, 248, 272, 296].map((y) => ( + + ))} + + + + + + + {/* Parallel outlines make the route itself the constructed form. */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + R24 + + + w = 12 + + + 96 + + + 96 + + + 288 + + + in + + + 01 + + + 02 + + + 03 + + + + + ); +} + +function InformationTerrainStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + summit + + + h + + + 02 + + + 01 + + + + + ); +} + +function OrderedCollectionStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + 01 + + + + + + + + + + retrieve + + + index + + + + + ); +} + +export const networkStudies: IllustrationStudy[] = [ + { + id: "interface-layers", + code: "I01", + title: "Layers of an interface", + description: + "A finished window lifts away from its layout and underlying grid, revealing the structure that holds it together.", + category: "Interfaces", + illustration: , + }, + { + id: "interface-layout", + code: "I02", + title: "A layout that adapts", + description: + "The same composition changes its proportions, carrying one clear idea from a wide canvas into a narrow one.", + category: "Interfaces", + illustration: , + }, + { + id: "workflow-bridge", + code: "W01", + title: "A connection, made", + description: + "A single slender bridge joins two endpoints, turning separate pieces into a continuous system.", + category: "Workflows", + illustration: , + }, + { + id: "workflow-branch", + code: "W02", + title: "A route, constructed", + description: + "One continuous route, three outlets. Parallel outlines reveal its width, repeated bend radius, and equal branch spacing.", + category: "Workflows", + illustration: , + revision: true, + }, + { + id: "data-terrain", + code: "A01", + title: "A field of information", + description: + "Measured contours give a field of information a recognizable landscape, revealing its shape and its highest point.", + category: "Data", + illustration: , + }, + { + id: "data-stack", + code: "A02", + title: "An ordered collection", + description: + "An indexed collection opens around one selected record, keeping its place in the larger structure visible.", + category: "Data", + illustration: , + }, +]; diff --git a/apps/ui/app/illustration-lab/not-found-architecture-studies.tsx b/apps/ui/app/illustration-lab/not-found-architecture-studies.tsx new file mode 100644 index 0000000000..d884f1b701 --- /dev/null +++ b/apps/ui/app/illustration-lab/not-found-architecture-studies.tsx @@ -0,0 +1,190 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; +import { NotFoundBlueprint } from "./not-found-blueprint"; +import type { IllustrationStudy } from "./study-types"; + +function UnopenedDoor() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4u + + + r = 4u + + + 90° + + + 01 + + + Threshold / plan + + + + + + + + + ); +} + +function VacantSheet() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + 404 + + + + + + + + + + + + + + + + + + No content + + + 01 + + + + + + + + + ); +} + +export const notFoundArchitectureStudies: IllustrationStudy[] = [ + { + id: "404-doorway", + code: "E01", + title: "An empty threshold", + description: + "A precise architectural threshold opens onto empty space, suggesting a destination that was never built.", + category: "404", + illustration: , + }, + { + id: "404-interrupted-path", + code: "E02", + title: "An interrupted path", + description: + "A measured route stops at a missing link. Its paired outlines and unresolved destination make the interruption the central gesture.", + category: "404", + illustration: , + revision: true, + }, + { + id: "404-vacant-sheet", + code: "E03", + title: "A vacant sheet", + description: + "A carefully measured document preserves its frame and registration marks while the page itself remains absent.", + category: "404", + illustration: , + }, +]; diff --git a/apps/ui/app/illustration-lab/not-found-blueprint.tsx b/apps/ui/app/illustration-lab/not-found-blueprint.tsx new file mode 100644 index 0000000000..db9281762c --- /dev/null +++ b/apps/ui/app/illustration-lab/not-found-blueprint.tsx @@ -0,0 +1,67 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; + +export function NotFoundBlueprint() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Missing link + + + Unresolved + + + Request + + + + + u + + + R60 + + + 16 + + + + ); +} diff --git a/apps/ui/app/illustration-lab/not-found-geometry-studies.tsx b/apps/ui/app/illustration-lab/not-found-geometry-studies.tsx new file mode 100644 index 0000000000..b2dc1b766e --- /dev/null +++ b/apps/ui/app/illustration-lab/not-found-geometry-studies.tsx @@ -0,0 +1,157 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; +import type { IllustrationStudy } from "./study-types"; + +function OffTheGrid() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + u + + + Δx + + + Δy + + + ∅ + + + Outside the system + + + 01 → 00 + + + + ); +} + +function UnbuiltArch() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Missing keystone + + + R126 + + + R90 + + + 7u + + + h + + + Unbuilt / Section A + + + 1 : 1 + + + + ); +} + +export const notFoundGeometryStudies: IllustrationStudy[] = [ + { + id: "404-off-grid", + code: "E05", + title: "Off the grid", + description: + "One displaced form leaves an empty address in the construction grid, suggesting a page outside the system.", + category: "404", + illustration: , + }, + { + id: "404-unbuilt-arch", + code: "E06", + title: "An unbuilt arch", + description: + "An arch awaits its central keystone, turning the missing page into an unfinished architectural connection.", + category: "404", + illustration: , + }, +]; diff --git a/apps/ui/app/illustration-lab/object-studies.tsx b/apps/ui/app/illustration-lab/object-studies.tsx new file mode 100644 index 0000000000..97426a6f90 --- /dev/null +++ b/apps/ui/app/illustration-lab/object-studies.tsx @@ -0,0 +1,519 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; +import type { IllustrationStudy } from "./study-types"; + +function OpenVolume() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 × folio + + + Index + + + 01 + + + The open volume + + + + ); +} + +function IndexedArchive() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A series + + + 3 leaves + + + Index + + + An indexed archive + + + + ); +} + +function AdjustableLight() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + θ + + + Ø + + + 01 + + + Working height + + + + ); +} + +function SheetToVolume() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Fold + + + h + + + u + + + 01 + + + From sheet to volume + + + + ); +} + +function LetterAnatomy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + x-height + + + Stem + + + r + + + Overshoot + + + Anatomy of a letter + + + + ); +} + +function MarkInMaking() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 90° + + + R + + + 1u + + + Terminal + + + A mark in the making + + + + ); +} + +export const objectStudies: IllustrationStudy[] = [ + { + id: "document-book", + code: "D01", + title: "The open volume", + description: + "A curved spread, a bound spine, and a ribbon index turn documentation into a tangible object.", + category: "Documents", + illustration: , + }, + { + id: "document-index", + code: "D02", + title: "An indexed archive", + description: + "Three indexed leaves emerge from an oblique folder: a small architecture for keeping knowledge in order.", + category: "Documents", + illustration: , + }, + { + id: "object-lamp", + code: "O01", + title: "An adjustable light", + description: + "An articulated desk lamp with measured joints and a quiet cone of light makes adaptability physical.", + category: "Objects", + illustration: , + }, + { + id: "object-fold", + code: "O02", + title: "From sheet to volume", + description: + "An open carton reveals the hinges and folds that transform one flat material into a useful volume.", + category: "Objects", + illustration: , + }, + { + id: "type-letter", + code: "T01", + title: "Anatomy of a letter", + description: + "A custom lowercase a exposes its circular bowl, counter, stem, and optical overshoot.", + category: "Typography", + illustration: , + }, + { + id: "type-monogram", + code: "T02", + title: "A mark in the making", + description: + "Two concentric arcs and a deliberate opening establish the logic of a simple geometric C.", + category: "Typography", + illustration: , + }, +]; diff --git a/apps/ui/app/illustration-lab/original-not-found-blueprint.tsx b/apps/ui/app/illustration-lab/original-not-found-blueprint.tsx new file mode 100644 index 0000000000..2cd5064c38 --- /dev/null +++ b/apps/ui/app/illustration-lab/original-not-found-blueprint.tsx @@ -0,0 +1,90 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; + +export function OriginalNotFoundBlueprint() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 6u + + + 6u + + + 3u + + + 03 + + + + + 404 + + + + + + + + ); +} diff --git a/apps/ui/app/illustration-lab/original-particles-blueprint.tsx b/apps/ui/app/illustration-lab/original-particles-blueprint.tsx new file mode 100644 index 0000000000..abf7f2b131 --- /dev/null +++ b/apps/ui/app/illustration-lab/original-particles-blueprint.tsx @@ -0,0 +1,120 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; + +export function OriginalParticlesBlueprint() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Workspace + + + Ready + + + Available + + + + + + 108 + + + 224 + + + 44 + + + R16 + + + 02 / Composition + + + 1 : 1 + + + + + + + + ); +} diff --git a/apps/ui/app/illustration-lab/page.tsx b/apps/ui/app/illustration-lab/page.tsx new file mode 100644 index 0000000000..0b4eb42ebf --- /dev/null +++ b/apps/ui/app/illustration-lab/page.tsx @@ -0,0 +1,90 @@ +import type { Metadata } from "next"; +import { Suspense } from "react"; +import { calStudies } from "./cal-studies"; +import { flatCalStudies } from "./flat-cal-studies"; +import { flatObjectStudies } from "./flat-object-studies"; +import { flatSystemStudies } from "./flat-system-studies"; +import { IllustrationLab } from "./illustration-lab"; +import { interfaceDetailStudies } from "./interface-detail-studies"; +import { networkStudies } from "./network-studies"; +import { notFoundArchitectureStudies } from "./not-found-architecture-studies"; +import { notFoundGeometryStudies } from "./not-found-geometry-studies"; +import { objectStudies } from "./object-studies"; +import { OriginalNotFoundBlueprint } from "./original-not-found-blueprint"; +import { OriginalParticlesBlueprint } from "./original-particles-blueprint"; +import { popularServiceStudies } from "./popular-service-studies"; +import { PrimitivesBlueprint } from "./primitives-blueprint"; +import { serviceStudies } from "./service-studies"; +import { stateStudies } from "./state-studies"; +import { applyStudyDirection } from "./study-direction"; +import type { IllustrationStudy } from "./study-types"; +import { systemStudies } from "./system-studies"; + +export const metadata: Metadata = { + title: "Plotter · Illustration lab", + description: + "Flat monochrome construction studies and the evolving Plotter illustration direction.", + robots: { index: false, follow: false }, +}; + +const references: IllustrationStudy[] = [ + { + id: "404-reference", + code: "E00", + title: "The missing corner", + description: + "A vacant module in an otherwise complete construction. The original 404 experiment, retained for comparison.", + category: "404", + illustration: , + baseline: true, + }, + { + id: "primitives-reference", + code: "P00", + title: "Circle, square, line", + description: + "Fundamental geometry and its construction rules. The current Primitives illustration.", + category: "Primitives", + illustration: , + baseline: true, + }, + { + id: "particles-reference", + code: "C00", + title: "The exploded interface", + description: + "A pill, row and toggle projected onto a shared frame. The original Particles illustration, retained as a style reference.", + category: "Particles", + illustration: , + baseline: true, + }, +]; + +// Direct-link review page. Intentionally absent from navigation and the sitemap. +export default function Page() { + return ( + Loading studies…} + > + study.category !== "404"), + ...systemStudies, + ...flatCalStudies, + ...interfaceDetailStudies, + ...flatSystemStudies, + ...flatObjectStudies, + ...serviceStudies, + ...popularServiceStudies, + ...calStudies, + ...networkStudies, + ...objectStudies, + ...stateStudies, + ...notFoundArchitectureStudies, + ...notFoundGeometryStudies, + ...references.filter((study) => study.category === "404"), + ].map(applyStudyDirection)} + /> + + ); +} diff --git a/apps/ui/app/illustration-lab/particles-blueprint.tsx b/apps/ui/app/illustration-lab/particles-blueprint.tsx new file mode 100644 index 0000000000..d2a62d606f --- /dev/null +++ b/apps/ui/app/illustration-lab/particles-blueprint.tsx @@ -0,0 +1,117 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; + +function Registration() { + return ( + + + + ); +} + +export function ParticlesBlueprint() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Studio + + + Public + + + Open + + + Studio + + + Public + + + Open + + + + + A / Identity + + + B / Visibility + + + C / Action + + + A + B + C + + + 3u + + + + + ); +} diff --git a/apps/ui/app/illustration-lab/popular-service-studies.tsx b/apps/ui/app/illustration-lab/popular-service-studies.tsx new file mode 100644 index 0000000000..64e46cc228 --- /dev/null +++ b/apps/ui/app/illustration-lab/popular-service-studies.tsx @@ -0,0 +1,366 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; +import type { IllustrationStudy } from "./study-types"; + +function GitHubMergeStudy() { + return ( + + + + + + + + + + + + + + + {[156, 260, 404].map((x) => ( + + ))} + {[204, 256, 308].map((x) => ( + + ))} + + + + + + + + + + + main + + + Proposed change + + + Review + + + Merge + + + + + Pull request + + + R48 + + + u + + + + ); +} + +function FigmaConstraintsStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Component + + + Instance · fill + + + Button + + + Button + + + Resize + + + + + 176 + + + 264 + + + 56 + + + R28 + + + + ); +} + +function NotionBlocksStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Heading + + + Text + + + Database + + + Page + + + + + 204 + + + Shared width + + + 28 + + + + ); +} + +function SupabaseRelationsStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Users + + + id + + + name + + + email + + + Projects + + + id + + + owner_id + + + title + + + Postgres + + + + + 132 + + + 132 + + + R24 + + + 32 + + + + ); +} + +export const popularServiceStudies: IllustrationStudy[] = [ + { + id: "service-github-merge", + code: "B06", + title: "GitHub · A change, brought together", + description: + "A proposed change branches from main, passes through review, and returns to one shared line. Circular bends and commit spacing make collaboration a measured construction.", + category: "Services", + illustration: , + }, + { + id: "service-figma-constraints", + code: "B07", + title: "Figma · Rules that resize", + description: + "A component grows into a wider instance while its height, radius, and end spacing remain fixed. A study of reusable design and layout constraints.", + category: "Services", + illustration: , + }, + { + id: "service-notion-blocks", + code: "B08", + title: "Notion · Knowledge, assembled", + description: + "Heading, text, and database blocks share one width and a repeatable gap. An offset block moves into the page’s common alignment frame.", + category: "Services", + illustration: , + }, + { + id: "service-supabase-relations", + code: "B09", + title: "Supabase · A shared relation", + description: + "Two Postgres table fragments connect a user’s primary key to project ownership. A single measured route expresses the relational foundation beneath an application.", + category: "Services", + illustration: , + revision: true, + }, +]; diff --git a/apps/ui/app/illustration-lab/primitives-blueprint.tsx b/apps/ui/app/illustration-lab/primitives-blueprint.tsx new file mode 100644 index 0000000000..c213db2e12 --- /dev/null +++ b/apps/ui/app/illustration-lab/primitives-blueprint.tsx @@ -0,0 +1,91 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; + +const gridCoordinates = [164, 204, 244, 284, 324, 364, 404]; + +export function PrimitivesBlueprint() { + return ( + + + {gridCoordinates.map((coordinate) => ( + + ))} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 6u + + + 6u + + + u + + + r = 3u + + + 45° + + + 01 + + + 02 + + + + + + + + + ); +} diff --git a/apps/ui/app/illustration-lab/service-studies.tsx b/apps/ui/app/illustration-lab/service-studies.tsx new file mode 100644 index 0000000000..404d2ffe0b --- /dev/null +++ b/apps/ui/app/illustration-lab/service-studies.tsx @@ -0,0 +1,472 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; +import type { IllustrationStudy } from "./study-types"; + +function CloudflareEdgeStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + Visitor + + + Cache + + + Origin + + + Cloudflare edge + + + Miss + + + Hit + + + + + 4u + + + 4u + + + R44 + + + + ); +} + +function VercelDeploymentStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Preview + + + Production + + + Promote + + + Production URL + + + + + 01 + + + 02 + + + 03 + + + 02 + + + 24 + + + 144 + + + R12 + + + + ); +} + +function StripeSettlementStudy() { + return ( + + + + + + + + + + + + + + {[128, 208, 288, 368].map((x) => ( + + ))} + + + + + + + + + + {[160, 240, 320, 400].map((x) => ( + + $25 + + ))} + + $100 · Gross balance + + + Payments + + + + + u + + + 4u + + + R8 + + + + ); +} + +function StripePaymentStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Card + + + Wallet + + + Bank + + + Pay + + + Checkout + + + $24 · Paid + + + Business + + + + + 2u + + + u + + + R16 + + + + ); +} + +function LinearCycleStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Issue 01 + + + Issue 02 + + + Issue 03 + + + Week 1 + + + Week 2 + + + Release + + + + + Cycle + + + u + + + R16 + + + + ); +} + +export const serviceStudies: IllustrationStudy[] = [ + { + id: "service-cloudflare-edge", + code: "B01", + title: "Cloudflare · Between visitor and origin", + description: + "An edge cache between visitor and origin: a hit returns locally, while a miss continues outward. A study of Cloudflare’s CDN role.", + category: "Services", + illustration: , + revision: true, + }, + { + id: "service-vercel-deploy", + code: "B02", + title: "Vercel · From preview to production", + description: + "Preview 02 is selected for production. Matching outlines identify the same source version; promotion rebuilds it for the production environment.", + category: "Services", + illustration: , + revision: true, + }, + { + id: "service-stripe-settlement", + code: "B03", + title: "Stripe · A balance, assembled", + description: + "Four equal payment units combine into one gross balance. Their conserved width makes collection and reconciliation a visible construction rule.", + category: "Services", + illustration: , + }, + { + id: "service-linear-cycle", + code: "B04", + title: "Linear · Work aligned to a cycle", + description: + "Issue modules share a two-week construction grid. Their measured spans align the work toward a common release milestone.", + category: "Services", + illustration: , + }, + { + id: "service-stripe-payment", + code: "B05", + title: "Stripe · Many ways to pay", + description: + "Card, wallet, and bank payments share one checkout aperture. A selected route carries a customer payment through to a paid purchase for the business.", + category: "Services", + illustration: , + }, +]; diff --git a/apps/ui/app/illustration-lab/state-studies.tsx b/apps/ui/app/illustration-lab/state-studies.tsx new file mode 100644 index 0000000000..f5c4f5f2ee --- /dev/null +++ b/apps/ui/app/illustration-lab/state-studies.tsx @@ -0,0 +1,129 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; +import type { IllustrationStudy } from "./study-types"; + +function MissingDestination() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Start + + + ∅ + + + 404 / Not found + + + An absent destination + + + + ); +} + +function CompleteAssembly() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Aligned + + + 1 : 1 + + + All in place + + + 90° + + + + ); +} + +export const stateStudies: IllustrationStudy[] = [ + { + id: "state-missing-page", + code: "S01", + title: "The missing destination", + description: + "A clear route reaches the outline of a page that isn’t there. The absence belongs to the destination.", + category: "States", + illustration: , + }, + { + id: "state-complete", + code: "S02", + title: "Everything in place", + description: + "Three fitted panels become one complete assembly, with a precisely constructed check marking the finished work.", + category: "States", + illustration: , + }, +]; diff --git a/apps/ui/app/illustration-lab/study-direction.ts b/apps/ui/app/illustration-lab/study-direction.ts new file mode 100644 index 0000000000..87823073e2 --- /dev/null +++ b/apps/ui/app/illustration-lab/study-direction.ts @@ -0,0 +1,104 @@ +import type { IllustrationStudy } from "./study-types"; + +// The rejected codes are explicit user feedback; the short diagnoses are ours. +// Keep original codes intact. +const outsideDirection: Record = { + K01: "A tilted calendar object with bindings and page depth.", + K04: "A pictorial calendar-and-ticket composition.", + K05: "A booking-flow illustration built from literal icons.", + I01: "Perspective and separated depth layers.", + I02: "Complete screen mockups rather than a construction relationship.", + D01: "A physical book with curved pages and depth.", + D02: "A physical archive shown in perspective.", + W01: "A physical bridge used as a metaphor.", + O01: "A literal object, even though it is drawn in elevation.", + O02: "A folded object with visible volume.", + A01: "A landscape built from stacked depth layers.", + A02: "A stack of records shown in perspective.", + S02: "A large success pictogram rather than a construction study.", +}; + +// Preserve the critique separately from our response to it. Revisions still +// need the user's review; they are not new style references by default. +const revisionFeedback: Record = { + I04: "The browser had the same problem as the earlier keyboard. Revised as a deliberate close-up with quiet construction and dashed continuation.", + I06: "Requested avatars and removal of the triangular bubble tails. Revised as rounded messages paired with circular avatars.", + O03: "The keyboard felt incomplete. Revised as a deliberate cropped detail with staggered rows and subtler dashed lines.", + O05: "Requested two or three folders instead of one. Revised as a family of three flat outlines.", + E02: "The idea worked but felt bland and weak. Revised with a measured route and a more prominent missing link.", + B01: "Questioned whether the metaphor accurately represented Cloudflare. Revised to keep the origin outside the edge and distinguish cache hits from misses.", + B09: "The connector at owner_id felt cramped. Replaced the branching endpoint with a circular port and a short straight approach.", + B02: "Requested a more conceptual Vercel study, then questioned its accuracy. Revised to show one selected preview promoted to production; the broadcast arcs are removed.", + K06: "The week was not recognizable. Revised with weekday and time labels, plus a named interval.", + K08: "The concept worked, but its meaning was unclear. Revised with named availability and meeting labels.", + I03: "A good direction, but not a strong example. Revised to make the construction relationship more prominent.", + W03: "The composition felt weak. Revised with a more substantial selection structure.", + W02: "Did not read as a blueprint. Revised around route width, bend radius, and branch spacing; awaiting review.", +}; + +export function applyStudyDirection( + study: IllustrationStudy, +): IllustrationStudy { + if (study.code === "C03") { + return { + ...study, + review: "partial", + revision: true, + selected: true, + reviewNote: + "Selected for the Particles page. The requested A + B + C revision adds an Open action to identity and visibility; awaiting review.", + }; + } + if (["P00", "E02"].includes(study.code)) { + return { + ...study, + review: "reference", + selected: true, + reviewNote: `Selected for the ${study.code === "P00" ? "main" : "404"} page. This preview uses the lab’s independent drawing snapshot.`, + }; + } + if (study.code === "B04") { + return { + ...study, + review: "reference", + reviewNote: + "Explicit user feedback: ‘love the Linear example.’ A service reference for aligned modules and meaningful time intervals.", + }; + } + if (study.code === "B03") { + return { + ...study, + reviewNote: + "The user said this Stripe example was okay and requested another focused more directly on what Stripe does.", + }; + } + const note = outsideDirection[study.code]; + if (note) return { ...study, review: "outside", reviewNote: note }; + + // These were not individually rejected; their tilt conflicts with the new rule. + if (study.code === "E03" || study.code === "E05") { + return { + ...study, + review: "outside", + reviewNote: + "Set aside because its tilted form conflicts with the clarified flat-view rule; not individually reviewed.", + }; + } + const revisionNote = revisionFeedback[study.code]; + if (revisionNote) { + return { + ...study, + review: "partial", + reviewNote: revisionNote, + }; + } + if (study.category === "Primitives" || study.category === "Particles") { + return { + ...study, + review: "reference", + reviewNote: + "Style reference: one plane, modular forms, and guides that explain their construction.", + }; + } + return study; +} diff --git a/apps/ui/app/illustration-lab/study-types.ts b/apps/ui/app/illustration-lab/study-types.ts new file mode 100644 index 0000000000..0f7471a348 --- /dev/null +++ b/apps/ui/app/illustration-lab/study-types.ts @@ -0,0 +1,29 @@ +import type { ReactNode } from "react"; + +export type StudyCategory = + | "Cal.com" + | "Interfaces" + | "Services" + | "Documents" + | "Workflows" + | "Objects" + | "Data" + | "Typography" + | "States" + | "404" + | "Primitives" + | "Particles"; + +export type IllustrationStudy = { + id: string; + code: string; + title: string; + description: string; + category: StudyCategory; + illustration: ReactNode; + baseline?: boolean; + selected?: boolean; + revision?: boolean; + review?: "reference" | "outside" | "partial"; + reviewNote?: string; +}; diff --git a/apps/ui/app/illustration-lab/system-studies.tsx b/apps/ui/app/illustration-lab/system-studies.tsx new file mode 100644 index 0000000000..549b3b7657 --- /dev/null +++ b/apps/ui/app/illustration-lab/system-studies.tsx @@ -0,0 +1,451 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; +import { ParticlesBlueprint } from "./particles-blueprint"; +import type { IllustrationStudy } from "./study-types"; + +function Registration() { + return ( + + + + + ); +} + +function TangencyStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2r + + + r + + + T₁ + + + T₂ + + + 90° + + + + + ); +} + +function CornerRuleStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + r + + + r + + + u + + + R104 + + + offset + + + + + ); +} + +function ProportionStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + u + + + 1.5u + + + 2.25u + + + 3.375u + + + × 1.5 + + + 2 + + + 1 + + + + + ); +} + +function AssemblyStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Workspace + + + Workspace + + + + + Ready + + + Ready + + + 01 + + + 02 + + + 03 + + + 3 parts / 1 composition + + + + + ); +} + +function ModularTrayStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Design system + + + Preview + + + Active + + + + + 12u + + + 5u + + + 6u + + + u + + + + + ); +} + +export const systemStudies: IllustrationStudy[] = [ + { + id: "primitives-tangency", + code: "P01", + title: "Tangency", + description: + "A circle meets two straightedges. The contact points make a simple construction rule the central gesture.", + category: "Primitives", + illustration: , + }, + { + id: "primitives-radius", + code: "P02", + title: "The corner rule", + description: + "One enlarged corner reveals the radius, offset, and tangent points behind a familiar interface shape.", + category: "Primitives", + illustration: , + }, + { + id: "primitives-proportion", + code: "P03", + title: "A system of proportions", + description: + "Four squares grow from one shared unit. A single diagonal connects the scale into a structural rhythm.", + category: "Primitives", + illustration: , + }, + { + id: "particles-assembly", + code: "C01", + title: "An assembly of parts", + description: + "An icon, a label, and a status pill align into one row, shown as an exploded technical drawing.", + category: "Particles", + illustration: , + }, + { + id: "particles-slots", + code: "C02", + title: "A modular tray", + description: + "Different interface fragments occupy a shared grid, like precisely fitted pieces in an architectural plan.", + category: "Particles", + illustration: , + }, + { + id: "particles-recombination", + code: "C03", + title: "Recombination", + description: + "Identity, visibility, and action combine into one compact panel. Each of the three fragments keeps its role in the assembled result.", + category: "Particles", + illustration: , + }, +]; diff --git a/apps/ui/next.config.ts b/apps/ui/next.config.ts index cdd60e02d5..4c63633e3c 100644 --- a/apps/ui/next.config.ts +++ b/apps/ui/next.config.ts @@ -7,6 +7,11 @@ const nextConfig: NextConfig = { basePath: process.env.NEXT_PUBLIC_BASE_PATH || "/ui", async redirects() { return [ + { + source: "/illustration-lab-prototype", + destination: "/illustration-lab", + permanent: false, + }, { destination: "/docs/radix-migration", permanent: true, diff --git a/apps/ui/skills/plotter/README.md b/apps/ui/skills/plotter/README.md new file mode 100644 index 0000000000..a552baf6bd --- /dev/null +++ b/apps/ui/skills/plotter/README.md @@ -0,0 +1,13 @@ +# Plotter + +Flat monochrome blueprint illustrations: precise geometry, quiet construction guides, and generous white space. Developed through the coss illustration lab. + +`SKILL.md` contains the workflow; `references/` contains the art direction, feedback, implementation guidance, and visual anchors. The current coss selections are P00 (main), C03 (Particles), and E02 (404). + +## Use + +Copy this complete directory into your agent’s skills directory, preserving its name and relative paths. Invoke `$plotter` with the subject, intended placement, and any reference study codes. + +Example: “Use $plotter to draw a flat scheduling illustration based on the Primitives and Particles references. Show two availability ranges meeting at one shared slot.” + +The source lab is at `/ui/illustration-lab`, or [`../../app/illustration-lab`](../../app/illustration-lab) in the repository. The page is available by direct URL and is not linked from site navigation. diff --git a/apps/ui/skills/plotter/SKILL.md b/apps/ui/skills/plotter/SKILL.md new file mode 100644 index 0000000000..5a5f33e7e2 --- /dev/null +++ b/apps/ui/skills/plotter/SKILL.md @@ -0,0 +1,46 @@ +--- +name: plotter +description: Create, critique, and refine flat monochrome blueprint illustrations in the established coss UI style, using Primitives and Particles as visual anchors. Use for technical hero illustrations, service concepts, interface construction studies, scheduling diagrams, and empty states in this style; not general architectural drafting or generic line art. +--- + +# Plotter + +Build a precise planar composition that communicates a construction relationship: radius, alignment, spacing, assembly, routing, or intersection. The result should feel like a refined architectural or electrical drawing, with one readable gesture and generous empty space. + +## Establish the reference + +Before drawing, inspect [Primitives](references/primitives.png) and [Particles](references/particles.png). These are the strongest user-named visual anchors. Match their composition, hierarchy, and restraint; the screenshots include page context, which is not a template to copy wholesale. For service concepts, also inspect [Linear](references/linear.png), which the user explicitly loved. + +The current coss selections are P00 for the main page, [C03 for Particles](references/particles-recombination.png), and [E02 for the 404](references/not-found-interrupted-path.png). The lab keeps independent snapshots of these drawings. Inspect the host page’s component when refining a production integration; the original Particles drawing remains a style anchor. + +Read [direction and feedback](references/direction.md) for the current evidence, relevant subject guidance, and unresolved choices. User feedback in the current conversation takes precedence. A study’s presence in the gallery does not make it an approved reference. + +## Compose + +- Use one flat plane. The user explicitly excludes perspective, isometric views, skewed or tilted objects, physical depth, glossy 3D, colorful gradients, and science-class atom diagrams. Diagonal radius lines and callout leaders can explain geometry without tilting the represented form. +- Keep the palette black, off-white, and soft gray. Adapt those values to the host’s theme while preserving the monochrome hierarchy. +- Make the main form read first, meaningful construction guides second, sparse dimensions and labels third. Hairline outlines should remain visible at the intended display size. +- Start from a small geometric vocabulary: circles, rectangles, lines, arcs, slots, and modest UI fragments. Choose one central relationship before adding detail. A complete screen mockup or a large pictogram with technical marks is a poor starting point for this direction. +- Give every guide a job. Show actual tangency, radius, equal intervals, alignment, a shared baseline, or a connection. Let grids fade where they stop explaining the form. Avoid filling empty areas with arbitrary drafting marks. +- Name what geometry alone cannot communicate. A week needs weekday or time cues; overlapping availability needs a clear association with people or schedules. Prefer short integrated labels over extra icons or a caption that must explain the whole image. +- For the coss main and Particles pages, preserve the split layout: left-aligned headline and description; a single composition on the right. The main page has CTAs beneath its copy; Particles has its search/filter directly beneath the copy, with its original filtering behavior. Treat the 404 separately: illustration above the centered heading, description, and CTA. Keep these layouts in their pages; do not introduce a shared page-hero component. Elsewhere, fit the host layout unless the user asks to restructure it. When revising a standalone study, keep the existing comparison surface. + +## Implement and inspect + +Use editable SVG or the host’s existing vector components for this geometric work. Read [SVG and coss integration](references/implementation.md) when implementing or checking geometry. Animation is optional future work; first make the static drawing succeed. + +Inspect the rendered result at its intended hero size and as a thumbnail, then check the narrow layout when embedding it in a page. At narrow sizes, preserve labels that identify the subject; simplify secondary guides or dimensions before shrinking all annotations into illegibility. Compare it with the relevant anchors. Resolve these questions before delivering: + +1. Does one central gesture survive at a glance? +2. Can the intended subject be identified from the form and restrained labels? +3. Do the guides explain how the form is constructed, and do their measurements agree with the geometry? +4. Is there enough quiet space, with clear separation between outlines, guides, and annotations? +5. Does the requested subject remain a flat construction, with no perspective, simulated depth, or ornamental clutter taking over? + +If the subject is unclear, clarify the structural cue or labeling before adding detail. If the drawing feels weak, strengthen the arrangement or relationship before adding more grid lines. A passing code check does not answer either design question. + +## Refine from feedback + +When the user critiques a study, preserve its identifier and revise it in place unless they want separate alternatives. Record the exact preference, the relevant study/revision, and the response in `references/direction.md` when that feedback changes future decisions. Separate explicit user feedback from your interpretation. Keep the record compact by consolidating superseded guidance. + +Treat broad praise as evidence of improvement, not approval of every example as a permanent rule. Promote a new visual anchor when the user identifies it as a reference. Keep unresolved concepts open. Change this skill narrowly as evidence accumulates; do not add a new skill for each subject unless its workflow genuinely diverges. diff --git a/apps/ui/skills/plotter/agents/openai.yaml b/apps/ui/skills/plotter/agents/openai.yaml new file mode 100644 index 0000000000..99ec3f0a2f --- /dev/null +++ b/apps/ui/skills/plotter/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Plotter" + short_description: "Refine flat monochrome construction illustrations" + default_prompt: "Use $plotter to create an illustration in our established flat construction style." diff --git a/apps/ui/skills/plotter/references/direction.md b/apps/ui/skills/plotter/references/direction.md new file mode 100644 index 0000000000..1364048a39 --- /dev/null +++ b/apps/ui/skills/plotter/references/direction.md @@ -0,0 +1,75 @@ +# Direction and feedback + +Evidence captured from the coss UI illustration reviews on 2026-10-24. This is a living art-direction record, not a claim that every study has been approved. + +## Anchors + +The user explicitly called the original Primitives and Particles illustrations the better work and asked that they guide further experiments. Their screenshots are bundled beside this file. Primitives centers a circle/square relationship with radius, tangency, alignment, and restrained hatching. Particles composes a pill, label row, and toggle fragment on a common planar construction frame. + +The user also explicitly said they “love the Linear example” (B04). Use [Linear](linear.png) as a service-concept reference: issue units aligned to a cycle and release. This endorses that example, not every service drawing. + +The gallery gives the entire Primitives/Particles categories reference status. That metadata is broader than the user’s clearest endorsement of the original two drawings. + +## Production selections + +The user explicitly selected **P00** for the main page, [**C03** for Particles](particles-recombination.png), and [**E02** for the 404](not-found-interrupted-path.png). These are current implementation decisions. The original Primitives/Particles references and loved Linear study still guide the broader style. The lab owns independent snapshots of the selected drawings, so it can ship separately from the production integration. Original C00 and E00 remain separate historical studies. + +## Review evidence + +| Evidence | Status and implication | +| --- | --- | +| C03: requests a third element so the composition becomes “A + B + C” | Revise C03 in place. Added an Open action alongside identity and visibility, repeated as the third row of the assembled panel; three equal rows measure 3u. This is a requested experiment, awaiting review. | +| User asks to hide the home and Particles illustrations on smaller screens | Show those two hero drawings only from the existing desktop split-layout breakpoint (`lg`, 1024px). This does not change the 404 or standalone lab studies. | +| User rejects a shared page-hero component and asks for the 404 heading to stay centered with the illustration above | Keep main and Particles split layouts in their pages. Use an illustration-first centered stack for the 404, including its lab hero previews. | +| User asks to restore the previous Particles page, then explicitly confirms “Keep C03; restore search beneath heading” and references PR #895 for headings | Keep C03 on the right, restore search beneath the left-aligned heading/description, and remove the added hero CTAs. Use PR #895’s fluid 36–64px heading scale, leading-none, and GEOM50 on the main and Particles pages. | +| User rejected K01, K04, K05, I01, I02, D01, D02, W01, O01, O02, A01, A02, S02; W02 partly | Explicit group feedback: these went beyond the desired set. The stated general boundary was flat 2D electrical/architectural blueprints, with no 3D or skewed views. | +| K06: not clear it is a week | Revised with Mon–Sun, hourly rows, and a labeled Thursday interval. A seven-column grid alone did not establish the subject. | +| K08: good concept, meaning unclear; more labeling suggested | Preserved crossing availability bands and added named people plus a shared-meeting callout. | +| I03: good, not an excellent example | Revised from a single generic row to separate elements projecting into a shared control. | +| W03: a bit weak | Replaced the isolated switch with a switching matrix and one selected junction. | +| W02: does not really seem a blueprint; user could not say why | Replaced rounded endpoint cards and checkmarks with a continuous route whose width, bend radius, and branch spacing are measured. | +| Response to those five revisions: “much better” | Collective positive feedback. Promising revised examples, not five individually designated canonical anchors. | +| E03/E05 set aside by the assistant | Inferred conflict with the no-tilt rule; not individually rejected by the user. | +| O03 keyboard: “doesn’t look good”; asks to show a portion and use more subtle/dashed lines | Revised as a deliberate keyboard detail with credible staggered rows and modifiers, quiet outlines, and dashed continuation. Revision unreviewed. | +| I04 browser: “same problem as keyboard before” | Interpreted as a request for a deliberate enlarged browser detail, with subdued strokes and dashed continuation. Latest revision unreviewed. | +| I06 chat: asks to add an avatar and remove the triangle on the bubble | Pair rounded, tail-free message blocks with circular avatars. Latest revision unreviewed. | +| O05 folder: asks for two or three instead of one | Revised as three separate flat outlines sharing tab geometry and alignment. Revision unreviewed. | +| E04: “remove” | Explicitly removed. This is a choice about E04, not a blanket rejection of all absence metaphors. | +| E02: good idea but bland/weak | Preserve the idea while strengthening its composition. Selected for the 404 page after revision. | +| B02 Vercel: not loved; wants more conceptual/metaphoric, then asks whether it is accurate and requests improvement | Replaced the broadcast-like aperture with separate preview frames and one selected preview promoted to an identical production frame. “Preview” / “Production” replaces “Draft” / “Live” because previews are already reachable deployments. Matching outlines identify the same source version, not the same build: preview promotion rebuilds for the production environment. User approved this direction of work; the resulting drawing is still unreviewed. | +| B03 Stripe: “ok”; wants an alternative focused on what it does | Keep B03 and add B05 as an alternative. “Ok” is not a request to replace it or make it a canonical anchor. | +| B01 Cloudflare: asks whether the representation is accurate | A request for factual review, not approval or rejection. Scope the concept clearly; latest revision unreviewed. | +| B09 Supabase: points to the crowded connector at owner_id and says it does not look good | Replaced the branching endpoint with a circular port and a short straight approach after the bend. This is a critique of that junction, not rejection of the study or all relationship notation. Revision unreviewed. | +| B04 Linear: “love the Linear example” | Explicit positive reference for service concepts; preserve it. | +| Request for more popular-service examples | New B05–B09 are exploration, awaiting user review. | + +The earlier K06/K08/I03/W02/W03 revision pass is captured in coss commit `285493c9` on `codex/blueprint-illustration-studies`. The preceding flat-direction pass is `e9077a93`. Preserve this evidence if gallery metadata later changes. + +## Expanded subject brief + +The user explicitly requested a keyboard, browser window, table lamp, folder, smartphone, chat, and service concepts for Cloudflare, Vercel, Stripe, and Linear, then asked for more popular-service examples. These are requested exploration areas; only the specific feedback above establishes preference or approval. The expansion does not rehabilitate earlier rejected object drawings. Render familiar objects as flat plans or elevations with meaningful construction relationships. Preserve the established no-perspective rule. + +## Working interpretations + +These diagnoses come from examining the drawings, not from more specific user quotations: + +- W02’s cards, checkmark, and arrows gave it the grammar of a flowchart. Constructing the route itself made the drafting language more coherent. +- Flatness alone is insufficient: I02’s complete screen and S02’s oversized success pictogram also fell outside the requested set. +- The main geometry should carry the idea; adding technical marks to a weak subject does not make it fit. +- Perspective belongs to the represented object, not every diagonal mark. Radius lines and annotation leaders remain useful. +- For O03, interpreting “show a portion” as an intentional detail crop resolves the earlier ambiguous miniature keyboard. The user asked for a portion and subdued/dashed lines; credible stagger and modifier widths are our implementation response. +- Service metaphors can be conceptually clear without illustrating every product. Accuracy concerns the relationship depicted and its stated scope. In the Vercel study, we diagnosed the expanding arcs as too suggestive of broadcasting; the user requested improvement after that explanation. The selected-preview construction is our response, not a newly approved anchor. + +## Subject guidance + +Use these as starting points, not approved concepts or mandatory motifs: + +- **Primitives:** fundamental shape relationships, tangency, radius, spacing, repeated units. +- **Particles/interfaces:** a small family of reusable fragments with visible composition rules. Keep the construction relationship legible without building an entire application screen. The current browser response uses an enlarged chrome detail instead of a miniature complete window; this is our interpretation of the keyboard comparison. For the chat study, the user specifically requested avatars and removal of triangular tails. +- **Cal.com:** a labeled weekly grid, interval composition, or named availability intersection. Distinguish abstract intersection from a literal time plot; only assign time values to an axis when the geometry supports that interpretation. +- **Workflows:** measured planar routes or switching/connection structures. The routing itself should be the visual subject. +- **Objects:** when showing a detail, preserve the object’s identifying geometry and make the crop deliberate with open edges or quiet dashed continuation. A keyboard detail should have credible key rows and modifiers. Use subdued construction lines; a crop should read as part of a larger system. Multiple folders can share a flat construction rule without implying stacked depth. +- **Services:** begin with one accurate product role or relationship, then translate it into a conceptual construction. Use B04 Linear as a reference for this balance. Check the chosen role against official product documentation and identify the scope when a service is broad. Depicting one real capability is sufficient; the drawing need not cover every company feature. Keep metaphoric geometry distinct from a literal architecture claim. +- **404/empty states:** an interrupted alignment or unresolved planar route can communicate absence. The revised E02 interrupted path is explicitly selected for the 404 page; E04 was removed. A currently open doorway URL is not approval evidence. + +Other categories remain open. Translate their subject into a flat construction relationship, then review the result against the anchors rather than assuming every object works in this style. diff --git a/apps/ui/skills/plotter/references/implementation.md b/apps/ui/skills/plotter/references/implementation.md new file mode 100644 index 0000000000..50704bb299 --- /dev/null +++ b/apps/ui/skills/plotter/references/implementation.md @@ -0,0 +1,42 @@ +# SVG and coss integration + +## Geometry and rendering + +The established examples use a `560 × 440` SVG viewBox. Typical main outlines are about `0.85–1.1` units, with `0.55–0.7` unit guides at lower opacity. Guides range from barely visible grid lines to readable dimensions; fills are sparse. These are starting proportions, not fixed requirements for every output size. + +- Use true circle/ellipse arc commands for radius-labeled bends. A quadratic curve across a square corner is not a circular fillet; an `R24` annotation must describe an actual radius-24 arc. Check arc direction and tangency as well as endpoints. +- Derive repeated spacing, centers, and dimensions from the same coordinates. A dimension should measure the indicated edges or centerlines, consistently. In a scheduling grid, verify that the selected interval matches the day and time labels. +- Keep annotations outside the principal silhouette where possible. Check collisions with guides, leader endpoints, and neighboring text at actual display size. +- Separate main outlines from grid, centerlines, dimensions, and text so later animation can target meaningful groups. Do not add animation machinery during a static drawing task. +- Repeated renderings in a gallery need unique SVG IDs if using defs, masks, or clips. Prefer simple geometry when those mechanisms add no benefit. +- Decorative website illustrations can inherit `currentColor` and be hidden from accessibility APIs when adjacent copy carries the meaning. A meaningful standalone diagram needs an accessible title/description appropriate to its use. + +## In the coss repository + +Find the checkout from the current workspace; the skill does not require a particular absolute checkout path. Read its applicable `AGENTS.md` before editing. + +| Purpose | Repository-relative source | +| --- | --- | +| Shared viewBox, typography, strokes | `apps/ui/app/illustration-lab/blueprint-drawing.tsx` | +| Primary geometry reference | `apps/ui/app/illustration-lab/primitives-blueprint.tsx` | +| Selected Particles composition (C03) | `apps/ui/app/illustration-lab/particles-blueprint.tsx` | +| Selected 404 composition (E02) | `apps/ui/app/illustration-lab/not-found-blueprint.tsx` | +| Original Particles style anchor (C00) | `apps/ui/app/illustration-lab/original-particles-blueprint.tsx` | +| Main integration target | `apps/ui/app/page.tsx` | +| Particles integration target | `apps/ui/app/particles/page.tsx` | +| 404 integration target | `apps/ui/app/not-found.tsx` | +| Review gallery and study IDs | `apps/ui/app/illustration-lab/` | +| Revision feedback metadata | `apps/ui/app/illustration-lab/study-direction.ts` | +| Typography rules | `packages/ui/src/fonts/font-guide.md` | + +The lab owns its drawings and wrapper independently of the production integration in PR #912. Use the lab’s `BlueprintDrawing` wrapper for compatible studies. It supplies monochrome foreground, Cal Sans, automatic optical sizing, and GEOM25. Preserve semantic surface masks such as `var(--color-sidebar)` and inspect them against the actual host background. Static illustration components do not need client-side state. + +Keep page layouts local, as explicitly requested by the user. Do not recreate a shared page-hero component. The lab’s hero mode should use the centered illustration-first arrangement for 404 studies and the split layout for other categories. + +The selected production integration for Main, Particles, and Chat uses the heading treatment from [PR #895](https://github.com/cosscom/ui/pull/895): `clamp(2.25rem, 1.5rem + 2.6vw, 4rem)`, line-height 1, and GEOM50, with normal letter spacing and automatic optical sizing. These settings are scoped to those pages. Particles keeps C03 on the right and its search/filter directly beneath the left-aligned description, rather than adding hero CTAs. + +Keep the project’s typography: no added letter-spacing, no synthesized bold/italic, one GEOM value per role, and typeset punctuation. Read the current guide for details instead of copying font configuration into every drawing. + +The unlinked comparison route is `/ui/illustration-lab?variant=&view=drawing` (or `view=hero`). Original IDs are `primitives-reference` and `particles-reference`; recent IDs include `cal-week-grid`, `cal-availability-plan`, `interface-spacing-plan`, `workflow-switch-plan`, and `workflow-branch`. Inspect current app configuration for the running address. Use bundled references when this checkout is unavailable; do not assume localhost is running. The lab has no navigation or sitemap link and opts out of indexing. + +Use focused repository formatting/type checks appropriate to the edited files, plus browser inspection. Keep unselected drawings on the lab page until the user chooses a production integration. When revising a production drawing, inspect its current source and update the corresponding lab snapshot explicitly when both should show the same revision. Keep lab imports local so either addition can ship independently. This skill does not authorize publishing, pushing, or creating a PR by itself. diff --git a/apps/ui/skills/plotter/references/linear.png b/apps/ui/skills/plotter/references/linear.png new file mode 100644 index 0000000000000000000000000000000000000000..f0445c3935b29933498b8ce5b99777a6f4093f22 GIT binary patch literal 12018 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 literal 0 HcmV?d00001 diff --git a/apps/ui/skills/plotter/references/particles-recombination.png b/apps/ui/skills/plotter/references/particles-recombination.png new file mode 100644 index 0000000000000000000000000000000000000000..22844f0d827cd2914d152bb46048cf4792e4f95e GIT binary patch literal 40822 zcmeFZ2UOF|);AhN0UH*iHx=myl-^MkkS<*aMWsm#y*E*ThZ-S)NC}Z%LV^Scy(rR~ z)Cd7W3lMtm<>PtJcg}gfwZ8X#&%Nt@>)v(OnXHxhXV1>e?BDE}Ju`bJXTQ%T0XNlE z)KmZz6aWCl`3rDH06YUwUA#nf=^_=?B`RuaDw-=duUxr&`N|#I8`o|!-eF>5yu-l2 z!p_af!pgYV ztPyaFhBEdNJ0-<^z=c~Bl(#6(egfG4?B*qkKl}Ulbcx~udqkmFw{iO^4lR65xMoDqrSIS!eMZjUq z-#hq!GQZGjZH%VyeYkRO<=*JC(I*yHWnTom@cz8}nejtUKSw`%wtV)b$r2OBlLr=m z>BoguCptpVDf^RJrlVI!r_ahE4YO2FtX8bn4rqrJ&H$g8DIkB+WX}tqSW_wd2W{oe zl5^loW(s9|W&U64`cED&q^6w#VAsJDY?CX-hOHkEzXz+7glmFHC-a}zU7#zV7%?_5 zZ})W9p}1znXe&10M|p!cA#|--hiVu#v`_5N8sh}o@m|&JFBwE*E6S5^k9dWM(k6!7 zbd}^JxyA_;h@8lnP2hEE3Fe7PPcS!&ic9ml@u2SLvo zo5T1?cJ)bS)!WB4G0aS($b@>?Uyn+5($K1EL-W5!RGwGBIF7*e0ikPzjH)eT=mgWC z)MntzQs0ldt<68XVUC!BGk^|C>j)mI!+Xe4(mJY;V^s3fWT#aHmfvcq0k#L8!jQ$4 zH*&s{kXdte`Hph@$ttLLxzz3B0xOi=uwK;a7`eoyd}CKjyWQIXJ?OJQt(6)-SD8d? z7nGH%a&N8svR&J&_#MTIMVKuGt6}nIyso7tH55MuDZF~C*6h-!5TX3NG%-y&Lhu2J zm$4?~CQsvkGRXgT$o962)xnp(6-!F8a~{ zu+S@EsND!0AtyP{v%0U1s@8;9`dtbwn0?(nO+LWc*Gj|lwWSU`AcG%11eVM#K$DIn zs7ol@H}>Irbu&}e4kzK1UDb&wK4tH3F>j|Ug6N#f8;*0e`SGP?3uGgGOf5p*m>=Y= zXp1S?a?0sARP#?Uf3ow$Dl;gDzFT52GkwMBDCYf}HYiA$1vhfVAu*}v5k}BYUB?<8 zZQG)Xx1>bOK=u@05l~wuX8_mRuVlhGGMF`}g<`(6W_i8Jg}05$rBYH$s`T^zls25P zr+;?gPK%UcPfyF}sQb7w`1OzJSQlRHp~nS1Y2Z9rhLqycgmylY29dx9}X64nCJ<%?37O&Ryik`IT-1FSBg^o%>TjB~o7)LmY zb`|d`f=5H(XjPTW+AA20`Ym;`Mnl73E#n^?2=gHmn?$@dx6H&qR6u1zNa*KHpp|KF zzehPijI0hmUiL@VvlaZHEiS662A-U9Kb+>cYszRZ_gm?Ut<)}}&b&O457!@}pRAZd ziI3Ue45-AihHnlBhkZJd7FC%*j=kC*V?hjCSw`++FQ(ugHqOXCj5$N6c|l`w}z8=a|0~5 z6F)bk`*;K#?(N;$H(oR~@FIQ=)<$C z#-Yd|I4Dw?8DpmPOrXn*P~76sZ008sgL8`lYuMJ1CNy2e83?lkNp=ULc^erZdPt(G z5Z-TACv)7mEeVe+EW`(?*OQmzaodGr-k2<{yh!*jwa^m&R?V;$9?usW24kpn5?81Q1w(TA9u=1A#PJB zG6L)j2szMY|4A#T+3l_E)l{0aiFV;081$=;4mK;Vz6Y0m6LC32WQKmlzaDx9Xyd3c z8ypC5E{Pq-XZv3ZCbOLZm_c50it%!G0MVdDYd@5RI>_#SwSEoRVD`wt^Hay-CeTbjXEu@$zfS z&aw<9(iKW6Tv!k22(WEQDo+WMV_ohfWL%-_30fNO^Ah)+aEoK$eOFa71vQ7~4CV6@ zE5eUhj+KE7f%0CkRt~)m^e%0Fo**a6x$cN!sJTE{Y)=au6>stwIyH!ONqRUWnC=D_ zj3)e?bL=SQRr_@hn#>($CsW^SOK9irNUMMCV?*l`QC!TWgfQ``!q=rGz;LiaJ$sGd z(>0S`O9J^&%!|z?AogHLJw=)TE`N})G(+4AU3mZN*JfU7u7138Vvh%?UKy*?v?|%i z>ySL_oji;8ajRZoq~)?9`go#_3zL}Q2B2Z zq%qKJ1FxdN&*91dvj#0Ze}TTOHet6NSOKrC2n$TIer>(Tf+@#XB7QO(%DhQQ>VXXt zniWkJU*BLVZs(~&BJ))L*iSb(1YfE{8&=~|gAbT1A?*>j7x9ZssA#98ScIKC%-mQk zvtm_eFo1@#r)-V4%0J{L8E&;?EKf&w{cR_eMh-&4`LKG`!`tuf@OD>Pp_G2qrdT(Z z09e08*99J;XIJ)Kt|{_3N#0VcpBI9*9w@f|{1T-LH__RPVH2KhP+(Ivs%nuTr3`~b z2+V5EEX0EJBEVE_imQAYJG+}n@-^fOl3Z9Pt%WuuueP+k_X6mL)ZvhkZ4+-lqx#?s zHHX$7d2LO$k^0tb#RsR!G@y30$357VNV<|+gs{+3C2Y7dcnqyA6OK&UaEJL>?~aRk zzNXb+4HGI{UUKeUaq7qz;#ZgV3Gh0O9KT9IPBCXnNGTb{&1pEd|9xcsAIYh3MpPVT zGjc_m0ePG#K|wvoscNAbI+o+$AIttuS<9r0Gn=CC#hnJ$mz!S={A^84DQmHBywthRKmy?#MQyZ z0c=!J&VbF!L@lQ&zC89?iO2eRQ+>4RZjYD3$tx`VAk6&UZT5&`;^$?>x#RbJ4L4Yh z?%C{bk60@d>%~v?~io0&a8<7#03Xj9ou~H z5wWbPbKU}(ISp|~%zQsvTM^LAsKP{uU*%I?-s#=7a|9h4oAT}(4@5r~h7V)Kb9ncX z3vvtZ&nUmd0IS4la%zIsGCrRlmZDq|4zrIcMoOLVX$I4SNeiv?K|%N4y6t!X8zCK2 zTAYGHCX8=+`FGOpjDA8!M+LB^2qhZDYr)?3^h$cTV+!Pl><_b6MYgPu3{P)$LsyLV zpVTj!5f*Y7Cz3 zcl58h_FdF5S1Rvl@p|Zs8TTH&ST{X*fEu{FdZzPEtc4TOq2Y&aZL+8LQ(;g4(7@T8 z#DT-dBk&~SOJ?4d0UN>>x{>w`sJH8Yh~aH12k)Uk5uxUGjL0YFg1zRw+?o{evL5EV z3o=Tvo2dg?)^A5Gbr3sF3r`q8X26eO-(n_@X1J?ug+-c|12`q}u*6_(<<5r2-+;FU zdbrWTL)F9}`aa*ls|dr&7NMJNaXtK)Xz)sOBLkksm>m`Kwu1N$9sM4l;>e;Ye!0&R zh+y_zsm4V369@6=fXGMGj5)5{cNPq^_AmSMM316Fs}WS{(RX4QoW!|7Im zjn`&Z{fwj8P|cyPK$68n_hN=ijTMQ$B^|Y|82`=kZ(RP*D}(OH9(MA7qoDsL{TqRQ zBk*qo{{MpjgLWIfLM~zK>i4|p#LK(2!@T9O!l9M_-xrWdK)`D^weBF7v?cQXyu07! z3g0ANv)2;LW8~&^ON(slFRBqTv2f!X&0~HlT$r$-MGR%pJXYKzVR|@-2%%h_^hqEpedC@+YKg}5c14VvU+dl`2E72=`%of zZ9pe?yn$`zGDK7>8);l}ahuf!>PhmnVO}eBDYn!nk69;u%1TkjdXS&kg`_~*?^AOgyO`}>=)D_A&~e@kk`K6( zWUgCL3N|}sD~hOiUadzA3}Dr>)KW-oufx~2ZG%7oiV|wkZ4Kp3ptft+hxi{tbyN8ZhZ$a(3NuV^AxtIIlmY z!Cr6CO?s}c(^Q)+MGTwgW?DM5av8*O9qU9?qSeg|3svMn9VNm)+s^=J01c^uZYwDJ z4IV>v0k!649J1<>!q|`Q=AHtRh*QcT>ij8#gGI0)OMJr)CF$MXjZt z$S9b}t_w5{*gB)Y&@%K6!kuu)XxuQEvi8IakVT%j16{QbeW~wyF4?bf7u=GozzwF& z0}?A}GOb%8_WDc?)GS9DWGSm9Urkr9B!x1VUZrbL1mPI0iz1&&sU~u-Q+}IP;P3MV z(|3Xf-~C+S_+_L~R8RAHK>V7P{*~G~#rw4}jtm>vDsb{vFPXKmRvGOkp-_UXxoXmrudCm9CR$_sxi;#%ZC)>ZSnq{Vsb%y-Gc+0fn=E9-; zw_?M=jYrEVb1~rZr{&WX+&ADM8j5?!7pG|94T|l0c){f2?+LeDn?AV8ab1q69%vsN z+thjnP(A~kA{3WI<=+EN<0nHid@jy0m+jQ!aL;Hu3|)&{?|bpN3qWVJ3v zFRLg7QL#bs?Ygfr9?G=UqwDHoFMsmx_dgn;Kq)wivIlO)7~H9kCSWFQA%`Q!w>}TW za`yBd5CY)aF+=z^;y_x1-zwD;zy*RrmJqOX@zH-wDk^6EZk{8Man*~wH)nwH^LEmh z+(2IS_R``!6}Er&?Y~KUN67~#nD%GUrM$dlR&XBT>CE<}Kd0OulPlA)N|h(}Hrj$A zf$tBOs5{O8muARc`bv!<;$A8uQ6F7Athxa$~VJ?Bw&3f$B$Hk74nYJ>M<+E%1Y;SFa1KoD!@LUP)@ zSuVlbBOOJH>*#82VY|x;H7=_Pu9ItgrajRZV`O51&8$+sRtpN~9+N$w;kBXgvA=Ro z&TH1rvJ>~XSSAJ}(Do^yg0z6ILW&(o_Z3dlO7(5mhSf)xI2X6kcDBzM)4M6c z(2Pbs+u=rv=_(%O1>SCPX8<7N=_Y{$=hf(~QJS)Eku=_R=`=U9S#I4=?<l&dp4N710~$-a)RcR) zyKFqzx9SjPaJ9{1nK05sq0a!)k%={0zD`Gwa?j;!=908Unk<5O9QpY_ZpO7g@GT}Q zH%S@0)|sw&{-_;u#b~kqj^`?MfKn{NuWmx=#zWCA6BT_S5ECb$sHY7u^+AhYF^{zdt4+0SM1I)P@j&c z8zj?a=w!#IJ~x;Uq`OGWQK@6=LSNO+4fw7*FQRrx8b9E0Ymwu!js8CN7_})@er0Ys z_%_k^^hr>`b?xK@LH(F%vhPVX90fM-jY86GFw`_-(zHM_!lJ@SH)FA*0{Y~E@8AGh zXp3A!mXTIM=?PEN2}kwLV!tJHY_dV6o4Wt>m)L?ZoV6XQOp`M9wMWyJ~mK0I2yRu+XX zz@13kI<=FSNN^cB1EfwrJ8<1>h2|_WEXWDJ4cpmn`@8C-HkmnQEFH6_BAcKw_o5-v za>0ho27@5ApZhuUDl5+Oc1D|4vIyMMx~jc^@=A7Eg~XOIU9<8l?}kbziIUE>DItD> zKREQHP@(0@5UBI6?qzN;htUaRvGY+f;q~d~;n!~sqa&Is%Vvh?nUv`4A3)?cGg=Kd zhHWLx$_ibvh0!s~7*Gu(D&#$czUU(Q;C!!akXGz(326*0W zO4xR`)F`R2kT1bCxF;;uzuRj+Q40j@Q~z-WAOfEqE}sEff@Z#kEcl0<(1e6>UUCDu zO{0x;1H==pbj|=C+(ej#NRpUa1B@l&ZD!`zya5-0Jm)qhJz0pWKZ&(q@k+r#FArg* znl!h!mq5&_uT#C|CpQfQ>8HkBXXK?{f>i~;TQg?HM1Pnmw~YP>=TT7J!huGh4uK^b zV9o6YAye@{y2e21CMkV)jFI+n5BpW&-t-#3|K3gOx7H7r(?F;ULxu_%d<8KyVCehAg?(}3gVYanaM8kf8^aDIYa@Qc zN1JPlVKKCP`7DBddPPs}?7#Pn0gk#8(-sz-V zXQy^K#$G3!KQ0(7-m6hCPHz?MLBO1D{SHwL(%=$pPc-pQGK6bO61ttS$MAIv%V7ep!Os{Rg*K{v3Z-|#V z9(3s4aOdR379V-c1Zj*c3Nl>?0u$^TY8Prhtj`JGK6uiar~9Flm4o&`$$Y@EO0o7x zsoJry+}!Jjcn&rS^x*x$)plMOtw_`KLvu;Go^l6n@vD!4VC~05l-7xPWN-iXDmR`O z5{JL=iQ8Ch(Al$o{mb?`~wmaP-a}MR4-j%1`w!z);6U7m- zGt7jsdeBuh4jU4vQyYa^gp-d&1J}1lXP1nIcM%ily`B3ypS%P;8~Tbt)sa#JS%g~g zK{oWbLeAymPN4Hu%ho}UM}mX*H0EL@Al(!L_Vs$ymR=%CVvxsC5&UGMzv%n&8=AfXRUI4p>C+f z4VO}cVch^6eKHvoNm7jym=dB%Dv4|l!soDA)IFH3Rtz84fxUsYq`%yeMKIVcN($%j z{m@A&I|Dp`=rm{yN$qV1^ry42A5@!TPn{J?Klzx;FUoSfnLPuX- zdzmzDG#}?`4Cmm$dujSQez~eM$hqY;E0%Mq0hLnoYH_mZ{e-D`zOe} zGk`t(;7R($8)E5s-7o7r{;I>!xuiP%^x&VQ z9B$}O4SRV>DmAYW8+qcn%lYD(z?>eT5Ck?;UN|iCqIU)->n1)U;i5VFqjwbjgC8Vq z?izPp4F&(1xd__?HdZpMoo6lo6*=0EYxLY9jU$A(PFQptq_E)f*k$r14KVesordB& ziIDN*C*yzHV9w7#f9gR~23)^pC-tT4j|X~3`ur1|i|cm-|A~+8UxfzQ4O(;llT*)l zgAU~XH{?PU{!>IQQ1?hh@BR%#4!56{rQ7H0p~(3XMb{Ymmm$*hW5E^Y;Ry~h=I$*J ze7gA9=7$Y8dvHC#^Hx;UsGzmk+vfV(H2<|+b*~Y6zIGomoBw8udtQ72a_%S{-5v@A zrAi0ABDFXC41Qlf7g$kr3K`Q)McLXIrsfE&;`E~up+ws=faeX>#!d2fnEn2c4wqnh zbrwV7jk*UAmQ6BA@M;|9duxBmayXA5tOjJ%pBlWT_W-^?ZU*i2^n0HH3KTaHoY#Zn zMB+3bP6JJE7Yh4mKeX&W1B@NjKY4aQKd{NaY1ryFtfIkj9NbfPlp15xu$L9S$$SPF zieos=m;9GoC;r-BhMn2ik*E5b@c1x||`zv}yf(Jg>)IGiEonsyLle>ZW zdtdwhmYhIhdAxL$Ak9vd&2E5o0wgL}lVDJgAV zaCidm*`Uziw=)b6jn0|W4-3__Z^;#S^vU=+M0#^(c3u$TDt3OrBS5slKnU4lRQtM@ zIo-}Ob;n_N2E0X1Bd?{K6jYe@LO8rnbs*hL%AzLlb5Wpg5KxGIgci{uXm`LC0S&pA z(i7TZ>RnJ|W4}B7wzteR*WKM+BQMan*os)5c4bG4SCncN;YY8Y*HGs~{hfX(`Q}tW z*>hs*2$T(3r(Aq|s(L=CnRB1YR-p^Vj!<94*i6li%jBCGWZ&G8D6nJ7Y_x{ILRg~B zS?UM|51DY6N`F$>vNR|Z$Nq>|L;{nAt0?Ri2-jjTlAS+3WtX_S3Snkzs?kYj0MQS4 z&rT`FXpu)=Y~T>_woyYJ&%8TlfU2yewFjqKu9ehOE$A(@`gY>^fNP9Itv}q%n5|Hs zDID5_r#O6$`_=x*XWUoK{}-F_)tLiUyjb;SIxRB7M`%|!Xdve6u|kD@>NLAgI|saY zX6nuQ3iI#Sm0&IX^k4g4i%%Rlv(5mFRz$uTxrZrxWGO=Dz+|8756-Om9B&)%4G9~m z{gQy{{J;VDd9dO^=xVS|8JPaZK+wJKzsn4xQ}2u?s_Ue>=#SUW6#jYLGT67>rNyb$ z+BiUtl^Rr5xf!*pGMlvFI7Z#b_5wP?uB=cnr*mvaV7YO1L0Gl1WMQ8c{6$jehwK`5 zkK+c)k77EoE<{kKb{uF3evVcfk>7PEjwmGcFC+=2MnkN$y$qQoYAQ5di&$CdMw=G1 zq%3|ojk%v7=eV6YMcC1%YaV*+=#hl9^5#PmA5=^3$wM3;TlX0m$-}IbB=oJ#>@EWb zbm}NJ89!U{{XVA8pPhCX>(uPB?{EX{x?9xM-pJTGE?T$cpzPS5kneP!F%PKH7%w5! z1|2Gxc-vG3awi%%rn>u9)K!IeTmB@8h4h^PqOcwr*Oh!LzYtDjr4Q04X5UX8=Wf`f z*)><07JXgH1u1<|*#GVW4oLsY-ZN%K@%*c@yn?iB;_)Ai*u_+;bdA$@x8%+pgc%S^|XYoO0PM zm!~+NWWQ@Q#LTaf+}>HZ*Ub5pV#}E9_I%j;V{mV&R}bsaz`4!iJ_BSGbFS~*aBZxYD%n3Djmv!uU>OdR=B)SjVr&7Oi@)l_gdxTR^U+WXyHxxp;PS`5LnQy-`O-DGZ zZ70UPbPl}z+A$a=ZD8l75}z3Pwt{1sZ^R7mnK?m5_&~KT1Zo{h&)dV$tJuyQ(+!wi z7rdKUF5_H1_v7lQq)bc$IjAoA;=G{=6T0T6qw=lUejHPB6W1m?$`INez_!@Z)I?zA z3UBq=iyH}n>25-*7d8+qVX*dH5id*qg|UE?eWdZ^fyHD|oxF{_LrQSmDvD0uVg)CC z9*(7;?0NdIy;QFs97k24P&)2nUOZ8!GvT{Z|D>{mv3lkje^ba#)GBhklB2K5!c5A? zbUHw+qC5I|b`}KcI4YqS5;}*>f$Bt-d8d)4(fp>Y3%V<%Q7tZo47f%feH ziZyUG?W^)?^;f1uy78oyy55sC-&QNbqk?0-RDUa|>_3zJu&+f<$*J6(SWQgrC&cwm z$9EpQ+~f*vW;R+h1^yb?VctnI3rQ{6udDgOvY%oZ?-Z*b9gk9v)t2j4Cl?5Nj}g+q zRCTiuuBf7cye3b49xltsG3~(mWGOg$!o$apazCQAH(X;>B7;xORD&ERdh#9JMN?0Jg-L! z#KfoaumK2RoJ965y-W%+Vbf1k_WkCd9mNXETXm`8B5P{;mppc8ZT%1?uB>#+{5(MB ziS2*CEBPD8-O6D-$c@n~UXB34Pq=jtwc>c0CvFX=AB7KxLN=dY9-U&DVtUHgB4`aS zU>JAyG!zuV(&OoCb0o2r4A#;Pe8rYS9wHv~=~VP}vEphc%xVuvk~74Lv5OeVoA%O@ zh3^L4@~i?>1e&+&IUZzOrdb$;Z9b|cvY5xfSVjfC`$JS+BEcVRzNCcP#(O1{=rR#B zXZOHWU|a}Q$_39`hv@|tmtaEz%Q!*>Da_twprJ0t+=`m#C?B(rDopq^B^0Zc-GBV+ zHej%Q(CzV8lGjeF?QoB|k44sXmU2Q@7osH5HVFS@(GI)=V& zxC`bkSurYrYqqtVt3yQ6lUn@JU8v@TUTGwY;|83C8jfDw27FgBWTs1dSIwZ#{vdPj zcMkyP9&?{0&DZRM+kJtu=OMaFBNS=blzWi{9wt%qd^0l@Wq0%m$;F@m3RyCn@P3!e zQOPOyj;)osf+?0it(Hq{;H|1zo;(qt`srv#)zQhhE*8wcwk2ad0KyccjD}MQW$4%>YXT$u^~`&l6<^-^?azbE zowKp6-k8kema2pKNvQdpv;eOkoR%XCzbi_@xMDTyDw}b$bp)IxEq(ut*t~^upN`j6 ztP_?qT<=}#CoZ!)_vo2&uxd`9Z~335f^#$-i@%My47b%yAHT&Lw5`pqD!Xu%)WZcI z7Vb!Y-}3mb?6s$$p9gS9ws-aP6G*{8xH4{& z)=#*P(c?i!h?kTaFPi*=kw&5^sUHH$aOM^#@+}MJWAMLdGPPpbU?o8#YH=nqFlF8B zm&`u~>Ebm-Vn9f~>akP$UE{tPKbL{jOU`SfE+K0deCHOX{N1(-2K1x463zhTK;m}A zs!QhTs>~B=fyw}JlB!T=39moS+r0xfwVjzn{G1oI{3&xnGsL*zp*JB(-^HbMVAAUe z4f=*ojShOqC$-~oR1CM71k^mZl>5nr1a)2eJ@Jz6^;L6!I|T?*Y9L4y{&v3a-0PQ% zB;s1t9c?G4=Z#0^tcC*Rc<(e059ep;^>03AUysJh@_PtD2HVWgjt0fIeK(t0F*_W_ ziLx`liw|mpr*#g5GlRQZkGY3jCt?i-r*dcbh2|#H8=tRR@a$;_vGuYPG63lkMt(yl z^a=N|=kWJ}jTU_?~NJxNPy;6x`NEw>5QC)Fr$A(rIGj7N`7HbuE5H zx|*W;x|X8u&p}7;H`d0U`XiO~bI$JpfJ>}u5{D10TG~(oUkDIuCyaSN#DkLWQf=Qn zY0iw`f;;)sdHA zWkv_fK2c+`?{2AI?LcM7O4Fwm?2v+bHz}=|+(qnLmyZItX#3S~5%Ii>Oe10>6&H@S z-x<7@#TOIS{ar=F1N`KB^FuYM5jAniPH<|yd_iNH$c1Y6% zNZ_4CtdL0bPOdG$12#}hE?It@B;+mTZ?sy1mnx>(wLgxB_EbW4ZK-!FPW1vQDCW&n zk3=bt&cpiowlG{L$)P9bP8S#w@Wc;)>$I!!bodO=A-8mXrEU8<#4&`#+sJmxB_60B{F6)Dko&=Iz|Ol`nj13HiX&e*&&yR2Zk)?qH&$GZv-dAh{C@{g zS?v6BakB4)?(^)+e@~A0pGXFK7PS4W=q@cGc!4HxK^qA>xf<`K7f5aoK%c&e9dUCU z6h$g4)+DZM3H=3w)W6;Q{0q`Rg#YL#_WcJS+V5KfXRhS%*JakdR{GB%yvgD9GonW07_r@_cwLZ7f!!HxorRTa58ewdYA8WS~;P zrP&V!F@wWq9y|q!L21ShveWy+JBQZBlAEWxWay(hMclchWDZ=)+xZ&wlppyzUZvwf zRc#7+p5L&?4p+4_e7L~HY2Mw@JWz7aPAG$*&8(#i&axaPuaURz-}~v`V(8x6ymio7 z=D_r=X`(<`9ydn_IypkrOGJjnqO2XnjfSICMPBZ#Lkfh>wwbgmC51)rvZu@?ZH(uhsAzd;4g#mIc&y(Gv$l#^gt_Yn`Bf~$ zOGbyPNLUzYsjH7AAyVt`%2@GobB0)z1#?-fJ@zqaE*H;^|9IhwbhY!oful^KKurnT z#>{j7=H4Krz)r=Y*qg6*wE`YS@`0y;TQ(p0x#<~FNbBQ1$7*Akn*$;Y_lfr##EGTL zD~_l&=tl5X2UeZt^|Hg z2-vo~DV707p*ra_sO;RSz-V=H8#!h=Udea$J&bphUtC57))2^q;P=}vk@yN6eOO?u10s-G( zRKG4!hIWy+?-b>|AqccF;UBmnXxWXg?;}oEpUehr+mYN>jPS_#=*yG42q;Fj*~1HB zcm|kVStu0KH#bCQ(BwD!K6vL~Dj^YU!>pfJZmm~kC6m`Nx*p6&>(f&nxO}bKl2bR9 zzE4(G4%{Oo^vF4g=WunGyJY9cIh@PX!f9yd2e|E-uWGUjaaAzn>TC0OrvvOgbL>3! zi|LR@@V2%w4W8!5WtCf?2X!xKbq1sz!**&b+)bE<%~?p;LsluPLN zA)^8JRaAy{mDb)?U3KvD9-qthRdDN`)_5lCdPj#aw31G%3!RY5^*5w;?o3+#_W}(KOBv%`EU`(72;|h_$AEp`FeLY$;YCUBTDxovHN>dOdfBn%u;mHN(Ezu5w+~K zI!9LJrs37W8Bg1<%Pq^(UuS@DRbqoa$P!;#;?mjiqqnVEYCvtkLTJ0$b3!D2{0!hH zp5=-G`o0nt>|rgu1#L}?#WUwDCvWa+XtyURVNI^2&d$qNIlgq?_)T*xhX5H(vmM&^ zZaK-^GlN-~nN`3n(U@sg)$KUs4@sL&UBnrHOnapY&j4NVcBx!@Bv$LhmKMFBXJnC? zS3_Jx$7dXWX(ZggVq6G@%M}_aCXIv}niUk`!~RS}#A2@WMy8lDT+53W z6!chGj1|>OA~*Yl__E=kD96WxC7I{73!w&#y!R1ljsc$b+0}ts>GXqMSG~YZ#@*Gm zRVsQ(>}C0_S!)=LI^o)s#;Gwg*~It;p`KTjN)l$XnaB^12bz2+ z9^)U%HjGSG7*(%VtCByc-_mD{c(k#Yo8NoiYrp}Q-_vrya;}OKlXuB>n3bMrgI|w!*nZoZt%hsfT?$L=ZfT0j zM~8c%-HyMjlekj^y9aAG8SIR^nUx-sl6n_c7Wb1>jfTLUx{=T;cENexvPe6Q&l-7J zH9a3NcN$~MOl*>k<@^eUM#p9|Dnd(jZcGk18!3s-72h{`d}uiU4;iDD2A3C*Zx6X* zMY!P3?9j}kyIB?QMh`(w)nZO|y|4N?c52h_K#~d`n4ghvCzO< zX4Id8aZQbmJ1sS36V5A=D}1Z4)%u=Ip~A*>yk~h2P&;z8&Pn{2UWozf@JEmcYSEzkY;NJH~Fx-x~aLXw4Swr z>a(tMNU)IjqfZN4Av^lH+9D8$r7zq39rNTY<7{H8Q#mVNds^nyC1y2ug;=5Rp#%S} zeX#WJ1NpjS1{L?Mcl(2lNG};V5 zIx3=eoKPOiDKV&t+ZawYDR9ye7)KoRQAy`Pst8#aDf6R5OufN}o*-asN=TpGjmkfi|qy&Ez002P3XwFDCr~0zsyJml>l#(rExOXxp zTY#;{Gv>bxJ5FQ1BtUsy!!2@ z{}MCw78(s7nzKN6_5P7Cbv&-qJ@I>3oh^+BvUi@Kesb>j4&mZ5OF~A63UZk_A}XY_ zGnj#=yLN*+fl=k$p}Et3!|D6su-*`fQ*w-q{Y3z)pjTY&f7!Fy;<&5)Q z#Oc&HAFjSIY%W9<=e=x+^-+qnyrfmwEY(`)x%%4cHR&G49PxFIWb;NvsxHEwS^slt z!mf|GzGy3`-n;3B+7CYoLrq>sL|Wk$4bO_5y7y#?wljd>vTs~-o(aK^nBpy@5$tkX zUu9^mv$y?w@6Ro#>bFHBIw^l1>@NP2uJH{&Sn`>C^G1aP;cICXGKXk1NoxU+FxQ4Scdd(K+MBV_}r>&_QfpQa=V9gHg9HpMb8t7#NWHs25&< zkyquo)!2kI@s9SN&T3Fz8l(Nimdnea?k5#3$WUFfIh$cN;|_xcjdc!H#q~Vwh*K~! zKM%SHi*_o+IW%|otyEillUer|4X%TNb7Er=?>92w)H1w1IrW`E$dA9j41(g23Ddg{ zRbu$Y>#_JhMOljA*zye%{hi!u6#s$_#n-% zhS*FoK^f6knWccB^-IBtnR<5==CCqsAw|^{)*|av=__YJkW`4dPa$uRItn&8XtV;Z z`NID4UfFehUZa6@^WS>Sg>C$X-C|=<^CSN_pC@>60Wtnrph8!g1FxOEQ<1tBzx;u5 zu{E_fZ2)ED2KHO|wd7}|;rI0yPit8wgx4g1!^0AxMp=o^Gi2}hz0>iNR$E~@eRuh@ zl9hvLR&1}Znkxz2!99+w(-!&XvwP&SMGoum)wgj8a= zD`Z48v#Z@Dc-KRS)7GVCy-7OB)8xJiiRvMEYq`4k+E~R7j#@?ET~vL@A}7}*UW-xSx#vQx z%3K>E$(p;Y;d$;@Rra4~>DrI#N{e#aj?s~+_H4Z$I4~Q-CA30}9trLH_+{!^=jNt( z74h=@X{jEyIQ5q{Fh;L*vD82ua9cZcN#XSGbj`L_dg0$*$*yC ziDbqU;%$6npw6lx*>#{VHZAr>o99lT0fPf}1a_VfR8vaVvF) z%J<*K0Sg~>g*!hEHxjcF>OtKZiqf$u-QT7xI5+17MMZMSz3PT)ddnE~+2PY0-;;f1 zWnT`8`3nu^$GXGlVg?VYPLBNFr~iv9>z`~CPO`|K<%Uci)mcm(K_EiiKK$8vIQILd z9&%RdJv2TbYQaWJEM+lSpT69qQe66J^V+th>INnMpC*7C<8I97u3%JOk&m)(cRG3f z*PrzN-6V+h;{x{YWAs+;_;CsmwkZ4{h!K!yd4s#N=S_5rJ2WkuSWy2*+a*K_>@8Y1 zRVQe7XFsuYQDpi&RQtl@zbpz%b>e>IzPFA-n{gce;n+l{ty>`1*H&50LA!YCd1dm< z+irB4y-t>8$R7K6`H$aHAetWGA?|9S7=yPkJvE;P)PfcfD4SZjAdoPN@2k=nybVL1 zBV=ffm?;$r#|lO+s~j$V(5Q3g zLGFDK*AjghhbFUgEOL1+o$n0Do9(=Pxi!fOfa#p?2hvib5TwodKNTpiw||03#@I0B zl5kr8ak z7TI<8H(?}(nirVVoV6WxLx0bypT-VCZp-s=1uu72bLwYddQs~9=SRnUwxp`2gTtiU zBJZgme~d7=h9H6!{Z8Z)gr&i(+EqBGeA6%X)(9DVj1;G zGN9fNH|c)ZaAV$qrA)}9DeN&$LS^AQD&x*6=A^3~+B9+=PN!=$0F>2*Z1p7sZoq{I ztwWPu09r}ashA?oTALtK{mh7J?41y4`I`D06E66#=IoTZjXuVsHXvCpV0ydS+TcRt z%@I>A+`PSHOB?s6?CkXO-+%b7{32k|P@bLqT?n1~ZZI>H4jgmD+DOyGIgpe4&i9Lv zIvq9z#+F$v@tiCt8BBlZG#}*t3TLgY#)~g=M%S|&01+S zSMhtel)*hzG-+8oH27zSa`pN91W;QJrUB)+m4;QBb*dzIS>c504>51{rp%Lqc z?#K@l{YI8X4ZO4@yR#Hp+SDZs#VSZJ3-YPTi6`DHM_}ob4R37bw_7b0QfgvNYtnxyf-|yw7aWwe_R6Str<*ShVtlO|w!%WQC{p zT6BrsHCb6rwMxkb<9^4c;`}PtH6BF1hEXw9)V#64mVl+0_fmI5z&?A1>W~*@H@LHN z$R=kBt$*XO>iw#GtEXF)%_sKzPH`XndC<{vT|vUaEKJh+iIDPW4|L~}bA^g$xs|SF zT48Rb+FoTdPgmm;@*&H>&O@iDWk~AG)B&z{Nh1B|Gt-wLhcOqMFN}ho)w^vq@FH`v zQ}wBx--L;w;Q)te29wZ9Chme{q*$l8mb@Xv+Mx~81~SX8FzXB0*}H}9FHiBX$OsE& zbKew<5phg`ess))S^5nExWukYnA+Z1!cE}s6S$pAHLPckb}cUIO?Be0_iC*yt#CsL z9?QFg|Iyxe2Q;;1dxKm>MX>_XuOdLI!mv_?T)uT)VAu=l4%5tx%?Ull6wW8Ykjv55-Ur4@v z-uG74v|V&}xzsflQk;kV2y+Ka!$NVHt6RgcCF3AKtb3-^HK+SjMZzeW^T%VC^=ZrllHvZ7*|9u;>WtKGH}tY~qqD5}si`ld z@CW4H+0gqj2HWh!>oqZ3d@DC9>exo)K?c-*O1@?Z3EF)z9Nx4Iex7iqYJAe%NhohZAH@?&f{q24MpV(#KMyT>v{+)${s|^a z^3i6gg>NDE>yIDY3QR+MDSSZWFH156h!+9*xcBO&+JAEgPK|hM=$pFew-Ko=itkFZ z4RRyopphz=dAyiz8u2uuH^C59*!gV*@`kO--Ac1Vi0ll8`DZ@;$>`!bgS99e&xHe_5Non z7CTRulH)Y1M~g@vj>!*M`dZ$(gS8YDO&{U4qH7cJl`j6p^+i~5>E_dkROR8!X(VBHw>&L+*yIDSEZDZh7}NE3DwBpMy)iK5i{#1ortu=^D#ie!L7*D z?tp~cnp5v89rCtKzDY;=(!n}9o$Lut&dO`@_P~O=Rt~M&>R%$vHx832_exOe-UE< zNWQe9y)yRx($fhJ2Vj8&#;(1LGHjrJlX4yeg+CcoEQoZ6LPdLns^BskMKPh^<+|45 z;2iMleES5W%LVmM-RgEA{7e1MC%vKGNDdhGT3PwTnn;MBp4>?NbVKJU{g$fuyCuL@ zYzMNvIzRu_`a>fKJ=Y4%HU>fZqZKD!C7<>6imrlxbuCdyt$M&LxKU^t77DFf1>=RjVeSB;K> zPio>Gt@LVHm!`2l%3r$CrJ^^ce={_G@LoJf)T~W3C3ArSA1H2MrY?fmEKU6~eE6{* zHL}pFk)mg9RGYap){iN1hpsM1DAYp|&&ciRxmcThChdNuxujsC9rPqUE|hJBx{h!$ zH2!ihA_?BII+3PQl=l>of{>R@kF*B5T5YGuNNQ#I>|9@~^53&VAMq9$<VT5<3+6 zycjqmQ?edqYX~BBS{gCS?g+60}VlpLrIh?My)>B zQ}mMdg#B=s#KD6R*;2btWz0q{r5f;Hz|5|2ZC?jdoW=cw@v^iA-1dFEb z3-mjSTwW6|ZV#{OYav$V4=n+|ad|_5r6FI4*G=)I2zVNUeKzqV!BCBNWo-T0WV4~w zxHZ9yCD9t=hN~akaY*CQQ8!~qmocdXX|VIL-#T_B4wq<%Thf7oAU&cFSv?I6%Z+;N zI%=4Q`S?||Qso4oHIvIXG<{x=<^*L;SeQSHotw_?Fh)8P3lmml&;QmA@z>*>IFqZ0 za}AFP)##_Nb{Z)yZu^suRo;tp>Xva@x^?x3L;Y1jda1nG8S8~hy_<$u--~g3;*{8& z5#(0K?v>&%2g^9d}4L%zZY+SE|`ZXHO|pcf$e_rdD97PFK6#&9xy(m?XII21Ca5R z$3+cqg(YAuI!z8nI4GNkPqfPnoo4oKnv|9n?{;{n?WC~Mww9W`3MU50C~pzy$M>lE zG3}Sef4zDC3Wa}=Y%}t>xh0b^70aAEV76>hx=gAgB>xgs!mjs&UB57{go{C>yU58$ z>KP3U9p{#z->v+)JUkG~Uq2J}?2P9oK2jOhcl>lgNSH-Uv}t=g&6P6S5@en$;#wuM;7U&VC%Bxd0f;g}g`E~8F4YAeN`&>3#H36S~AWMt&R}j~%MGS$P zRt=fF14{2Mpm7irndWU(#^B=CVA)t9mk>MeZISLpAc^j(w2?_Bqb;Ugx(Wp7k3ZTl zFvbbBl!GStwb%%E@KXHsxcyN!+;cvt5!oAqOS>JviC2=VS_Jx$F~Go$6CS6^uw%cX)HJzY@@ zY??RqH-?5OA`nO9)}~e5fdXQTwH0%{A2HL5q>7S>Hhq5kLCFdTBYqi6*h@<$CaYLu zQ)1S(k5A+0k~hO`Gp`g9FTNp9bCexLyjQ9@GhfIHJXo;-{d5dPBq5Tta;$>qa-8mJJtF z@>=moq>|n0J5S?**3L;Ff$O~vdGxJg%ZCkHLSJdv_}*|yex>nPqS~04?|#12_@@m2 z-Vjzd=~oi!@s@%57$Pmg0r13D=HjraMvW7 z3P(Zsb$$>|c^jzi5(b__`9RI?ImF5>BN&+7`L>Eew4$6EKx0&VlNe^LeT6i1`=O#& zq#zT?w6yp_QfxM|*B6_xeqB6(uy{Cl;-@iCM@irJ^mDo0NN@I`>nmx9r~!{!H6DC-p5GY5;!2%dal5YkGT|3a1btl|USlA2oOS zk)7sO34X-q-qm=KCSiwuXby+*cYu z+TLfsUd4Yw=^YUF9pkL5iCNYdGz9gf>**6N=(b0bPy?zX;Sg}Krnny|E1(vBEVlL$CuDJl00@U+n9E=TQ`zChE9t~FGa(656 z=2eUhah_K(wHKZMwx%uh{VwX}59Kv!45sO|bIDFHcsi-IVi9jlg{~ZC5FZM2Yr>17IT1_b5ohuqKR0bZX=|odX#!tLT zds`+p382958wB;{mFPI_6R6U{U%h_IkbCLVxsw5Oo5h3yf}#!cFmVv=@n#KB*45!QsorUvzrJ=)=*PL zm7G*~1~)5RHJ%o<$mUlOa5GnRYMAFdadvx5BW_ccw_(6Vhvd0QXZ0UT{jzJqgu^#j zTTi@PWQIsEYe6 zPs3&^_SYQYX-Vd?JbVLHIeqJZKapwW|Ks=k&F5o6R8=MW7U~emy>g=+jy69NbTevorQHP{({IO_u>=^Y_ODyk(PTx!d~1R6Net zX(~e~RTM_?j&Q=N?w3;gvm^W^D#S2((~pnkhYZY9OMYFM8W|3hwi6Wxyb5L3^R#kA zy|nU*x|<}V?SD~*ff%?}GrdHB20p+1KKGD>jCKPxg@ z65_0Pe40ORS1y zob16ZmZIc@1A5^;NLy#reuLAYI&~2p>}w<=P+3#MIjqujel0?zva0(t;@dfG>S7k#y{nts}eKJGT=Z~_Vnqn12WYJ4At;#Zfi6{ z8v8eKO2sUqhhZfM=fltjI!Sp9m(tq}ek!(#1I31WsAg4cJ~_Z*rLikg{B`8iPgJ&} z#I|jbgEQVg(QjFHrcdsj`$j#{@N*_yKhuZ4WpnQvmA2&POxphal7Cm%zczQ34>}*} zkBq(M0a6VqG}SA?1)K`QPKEV~6Iz|=6ZOg@c%C0;vSH1S07Rv}{e>;o(|qVTy3aO)`hVStYt6N z>LrZD#eD@eR;s`_yCy=HcBEzHKhpn|W?6JxDTcs}0xIo??sC%n89{sX=&r+88gf#a z`O(A?{oS7=0_T-F79)vCX@@Nb^y@x6+cVVR!#UvFmd#h1|7l9W?GAS}Z&zIcFQw&x zq2ZVBx7jKPfd1<=L#HQ8NZ}{gA`0gw0Wl>L5=>PE#idkR*{-uVZMHBm} z*0IdWP^eqkG!-}F#Ao3bNn@$%=EqAtf4wb#qMW`7@U!{u&_xntc!X>im3-D$uJ^G- zw96*OxPAU$^w2x;Dm+}O!l|SN-Y`&4Jiz&f3yGVkoKr|Kt{D(~gq1DyBQJ7HT24o9#-f)dw}Mk- z!#YhF3_7qVK3vwa^V0RBFkRo;f_{3PbBfLJ?^oqa)GIZ-N~P++0wO+KL}{foXaB^| zf@wn#E0>?9RI0~{G`4JN&B+f%SDmVSlbF=Lm2i!GD0&Pk&?zJ+t6~w09cfM># zc;v-m7^>-HzjbnGT0s4wo77m%{2HL61>4cHkzW?B=^d8NYol~4U&}wazT_?E@_u08 zVr5%NcgoaYTv4lb*a#=s!(*MQ>=HkoJJXc>&gLGUX9omw&_S5kvnK7p=n#LWqW>!S zTowJui6X5zo~t2g`Bu08#*ar8oMEpH-)VD?Ss6O}z_$w1(Sj2civghyTicTRwmG9` zKC3jt{!wb|fzD6i^9}Q`>9J}zItB6M0-Z8J0HyW<75JWd9cR<3{rd+%X~*$5mTy%a ztyS3n{0mUw3JE8PCg~PBFXOwEj75MsX~Czev;^ODc;rYV(J4F)*KcKN`C0kC3R!d2 z>T*G>HOt7vj&dU{#Gt94q#M=?kz#4kU-*VV$lzGUl(m>_0VhDWNxP>PgQ8}NwPQVq zo^_4C7`FXX+TY|Tj&na{gB|J$is^3oZwMZH|C>_L&;WrC(tu56?aQ-UKSqVMc5k@^3WK*)jBvf1su@n=VF2y-YJTg^Ou{8qiem0by$-X=s-^FUTXvw9{ z@MB_sG@=ZDrSTjF27t|1*l*L&*pVeQi}=JbqkZAG>`HVAZ{@|B z(aK3D()`}(nYs;i>3kulRi+*=$uFes5qyTKAk@ zlcy0(ZBkV**g9)(ah}~=LJRiX>O_1QspGMAB)NJS}%hXeh398LMr3N7lP$-Yi;ZDVa>1bvbaf2TEJ;2D_Ej z=ktgVn(W1*XF%6?Hq46(y>hJjJmT9R5U0MVCow+SPxu>7~ofLm0XR7UFVTFs- zt9hAfKpw0#S8|K)zEe?yX2i@{sfkQQJ^W+D-*L|r)P8P5b~v209XNyp)Iz99PZa0D zN-IVR(znR@I_8%n9Mry`D;0$3rIeJk%Wo`LI%adcW~z-pytw-u9|-?ki0RwvOyRyp z7Iz-7Kk#w%!HA97oKiA3n+yo4^xg!}-Fk@f82VV%0vO4nT(4i{y)$LZTqixpaR;F# zcjF*;Vs8q7tRCD+z-!NFWW*niA|O}{qniOylR51P=#fH-fwCo0W_ZM6OgJOiFre_9 z4n3TFp!!Kh*5*7B(R}G**s1M@NT_L6YR3wypmurFCJ`W;kf$DORkgyWdC>5V{r&H# zk3UAG-J#ik)|azzIHPCrlY9L3A4&g|`)Tp)E6o1iW=G-XgZTZ{&(s0JTu(FuQwVNq zuj=qg?3`FVstmj@_Q7qpnYj|H2WZxfwOjyg0FG33Xo5d)+Rm5N@2|ug_}?2PPf-mF zt9chR8cE%at^HT^#K{IKHB64wm7EZ6l7`Z(iglUA2z5Dd^rx+(|?PstPlzGfhOeT^kf+7 zbQWkewqx6)!}UJmK)ct3?)Gi)=1$q8YaL!$M=j>(S3~oQ+=u~Sr-A@F%iv90^00j) za`b^6fz^}~we)@JvcjR%9K966*(}}Woc+$ME=V?>*Wnsdc3*k)DEmI%btlsWC)-z= z{5XnzCO_OkkTRYEQ&&^S+8G;Lg_dZm?-VxGXuz!6E)(#FDfV zKBtz@KFMLiZ&6$5+c97z6uX|Oqu-WR!TpnQv1_x+B4{7kg;}r+ z|1vSa>r$bYe8a<}*I5gToce|b(~PdK`9ADLt5GO7dGjacdgSM(@p4NMFe=Jj>uZTc zHK)lhbjUvbGfTc{=tTjSqh z@V_Acl#urBJK33Gprp_7lUvtL&hnzsKK_uWCh6v(bue<4Jb`_NioIoTYRacCRcfxa@dZR$up z5L86Rsh@fvYv(GSSvTJ<(JOJlE2J&j#XxDGgYVSge5UIgX*lTfs(6{pGHCL18K*Ou z@5HdyirxDg&vDJ+5`2isCxo=q3w3_S`LXb^J_yAVZZrbQ{1G4dMAJ{~)qO9r^QcMB z+Wf-K^P49fi}`z;U4>d29wJx>$R5%XzQ$t&wezm5s^QKjY^tP6jp#=L0Xjm+HM(#Jr(SpZ%*&BbnO!FFaXK5gbgM<1XBu-Ely;{{aYo9O=u zYU_FbF*Y1uPeDI#pgbl1frloiPwyCHAGLFVBFZ^uj7ao{xx`8INuAmb&Z%Fba^4tp zieBfef{C=?IoeMP!xSaM74b}_Z|*mL68W;H!j@f7=5i`;E(dJ zgh$H%;I6^VMOCOU-Em2%YcQ-LOr90xG48R+u>=n_Uvm!H&>2=QI*K3tN~0}FvvF{A zyd!_HM8(F`+UQ**+jN{|JWk<9{8-<(IV0y_rNDx_eZeJeoJ9M$Asb@0TvIEtE}_EX zJtJcgk9ixmwcnw+5yks>FFe-Bby`iKe^SKUya=)rZ+jqCu(~*3eymo0&nP4pEu}VC zkZ{YVEt8_)UsBDvOV?dqWoeXR+x#9C4!M@6V{C;Yywu3$)aEUsZ{`>Cz&5%Q*R+_)iypnDwjoNVnBGLGFNMn= z{=OmlQ!_Yu$2J+HsyaP78TigLMy6^6Q zn5XOrz&}#0M%*3+>myJ2mWxfF3BHF4t44(qoS3U?D5NycQFtcSv=g{rzR~@5=u#BGanqWghGPHh1bN_<2P~ z-Oq@Lerb}m9RfzW-kP9T&8$h(84Bhel@%IEW5>4_K;-i&Nt#xIAe>&8vMA>pM)nB^ zTR8o(uoP7mHi~VKyFMi3Vpyz>9pbyu;DSl=mEuKfE8bHsM*1I~rns!yZQT$2h5{o} zI1OXx$#YhL<~=`Kv7?hysWmqRQrJtVh7QimvJ+CPyeSe%BO7JL8ORuW4iZNysRPhB z+T)hY>A;5FBo7Q2TII)RK(bOh$EasqWO?a)t|Uo%Nt*@%`=Y{6O=9WOY)#d{u1-t5 zs9F@zUPd+`tMUv-f`TfaKW1onGW=v)yg(DDcV$D%^95aO+bey~K6IsDluHoxxK%Ze zim)2QHVNvv9GE>^UU62{>|-bV1vhu_ynq#@P4@8#}z-fnqQ;h zxWqiA!7J`c-Zz)_A6dKn1q1vI@3F8vmE&pOhNDcwpLSuWaq%byX2OP1q~7$zI9&2% z&2FG-hD*?JDhD46roTunXZ*Y5t8C+$RGbBv8X#KGqNO8KB+ec90hvRR)_`GM%f&v3 zWkj%V+kdzeQEjL}qf$c%H<(U@|4c0!hVR1MalxLwmtBIojs zv(u;czfsa(C;bcjPIOvEt`Sj5Eb0PI_?o8@n?OyaEB23)bj>XC!xPU(4iwMq zCj>eanA35`8RhRP&esU$sZh)n8g!I!RE#!Gt~i8+-6PVgvts#mJ3byAyD0LLfj^jD zovRlIBj_V0$V^|n-FIj8Eh;~!y1(le1S6|G0#rm>m0?gm9Ofg6TS_0r4l2;~9JD{( zj(J#8t{dVG!4A|_BNf~%Ec3H<+Vb>8cKHEmBl3ORrEcy_?ppO6p*B0TQ?mYff}#Tl zV@8$p$OWLjI|Atv$}g(R74LMKHGFWO5Fc~A7ZQ8Bk=Mcay5bz(j8^ZdL7mdX#8 zCwsU2xLAi2A(60EWOQ`gg~)>bKCr~&FGpOhJ@f66cJgo+t8BOU>`f$?w+zg?eC$#O z(jV#wWb(JVb(35s7%D#@r3PC_&WQHY>~B+MvTTkHNjFh=r+od-vjT4F0pE|B&aeY%U1952oVU`NpTh{mckG*-yaW;ivgJp{qU|D~zkS zgqZXnt*mv{#YGX!1joe~m)K}0{0dzW2#B^TaaP0J42_-2NGPg{u(=c1yBjK{oILNji?}?u_(SWy{GoGt^sHG9R8VlpUgh+U+JT^r%q|=HVMQ}|wNp>S z*>Diun=QEQmxFMFPDPLbLByEsOhqjj2BSuYZ+kRupgq&52rZ?~64(#b?CU-Hvs8t_x+FT>CzGl51?%lXaMfpP{t=-;G zyf6rK1W*M-HKOPE&WSs>E?%V{R&AJfbs8u_7ezk!oo)U<&t0^T%GTi=(@;;4bmc@M zPQPh;QU&Jd3cPNwl^Z0}S^5WY;<6wq7ArSxSOAta_;)U%TkP?YQ9WFOv6K z-h^`1Uw(V$<$fwjyMiXT+NC-Y+@pb$8k}HlD{E$uVsLg>U*jSo#ciqV)hO3@?~cha z-0!<-s&(mflBvl-aw#z(F$YjDcWu{B`h0#!W~feau)4k~^sUR7d%avkVlB<Eyq{bL;vht zg7C1bTGoNP)TV;qfp6%>mWD%vc0*>IU9RDA(#f2KEx7SaTx5}5UsI>MU^YM2dm4Ma zk0f!#&&Z%M!I8p7(y0xC3zAb#Cp+#1$X)4LobDn=%^^sS_(YRf(lgN_XvB79cLTozEKxX%!7dya~IkdV7}@uAJ(t3y1$D> z3;L2i`viRV!?WMZdNltE%tyy;y4Ko)-a9lNpE;VY?wXi#mdp;0ObiD|altc+g52-w zBnT~_?3PI5^g1UM7z{hwyW0CIjjyVt-_<>G^4IurKb!Li%+8@!;LQ!%cr!pWm1)f{ z7=zk%EXI$k)1?$B8TF+<(QG&)Djp^S{}eVdai{^U9dj=5V6`+g5U!ZR=*GJ}^1w%# z6r`ARd9*C{5tiv6ojd~->2+~qdTol_!05abj?>X6@Q!9GAf3iL*~~d?y;HXEkx>=~ zcXf-g09G>)Qxh>Nn=&y1~ed-vL=#;-={3Ssq$GRU^$=?dtxKk0Epru`{ z$~w1=k2aZJ=TA7{@R?8;4p`D``Ka2YB3=$eyW35d6Rm{Xap7g|$OHy+4do=~H1y0b7 zo@ZQM>8oea?%7(lt4mF7Wd;M1i|)%iMA(=h(SOsi{&<-;-?{niX@mHbVIi9V8)q+Xm?nyq`a@8PDTz8D}?mfphBXl%reh=*bnQJTzvXUU$Ti} za#TRI##eHsV`i>|v#NXJ66Y3`>6N5Y)?zJ}6P{O_pSNTFac>bQFJmF#A{W;)t+P7j zzmYbuJOR;FZ(ytAN(RfYNED)P&G;0*H68Pj=~4;=YPgiyz#~S1lz2%)8^6-z>hCkM zyD-sXIZru}ayJtPJmMJO;yyn= zlw=@j;wB)|7bC>O)$N87(kcO=9ldXGx)fXtncd1f*>a#mck}l%2=$b~|M|s#W{3|o zhWLpJBu*q;qvr|TR2PYJ#U^L7l)Lp<;c;TNr^fB)(XT{uLOGJzGu_1JJJF5@Y<{}C ziDjY%Y{8@`E1`S_FHI+_gimLlJ^wVZbWXrz9v9KymTIzv<5q2f#*@EP%bjX{8aBQ| zJz44b!?TWsi;fzldF{xXv=GSP~_f22pcWQ0?1Mi|HInJsR#-Q{~V?w3mC-E^2%~=I&&^&8?(Fo}l5qh|7 zMMV&>>-kb72_!{wJ@6FB;YG(|;Q{{pDYOhRO=vDqdD+q}PMnh$kP}6nj79Xwv3oW@ ztWmQY-&U7lJU6F)cxbj+b2O>MJ!u;AzPKsR{B%h%(Jq8UFtM(UA8mLp^E@a#XHu2> zbjTb+;qwauJIzyhjUqc^{2BX4=m$1WwBQQW{C4;Om=iQ|L$57#{&?`OJM2zuvZ`Um1bdyw3Awnz*Nug_9ApspjGP_YL|j42{8KbyW8(yLE?JW z;nwOxOn9Q_YV?XuDc|8VVOfLz>c=G!99XF;xMx#AuE(>t{36^jMcXxbB5P`xv^-XO z3cVQgLATgB+1xdhBtFv%MwMNaK=T9^T3LMgN>f@T#CuNP)v4OoXIpflO0$bEPDU{0 z?NYVRzQWPli%hc?;BLZHkD^u)*21^p(qoB1a=whIgH~^5rZqse)_$G$-b1(S8dG_> zF5l!jL)F&$swoXXt)~$<`C1C3F3p{oJ=V|Ij!F(i4Cg|9-_2YT4XshM7&ufK(x zmz^SOx1*vk^)~n{Z^)YP#L4MN1rbbHYNi`?O9-q6JQ%6j^Vx4)GMPZUpXPE$X^$1R zXo@eav=6}YinNYHqFR+9RW1cag3qh!dTRN@eWT zJG9-4=i~IKrZPBgoEimPJiWX?Z@~DZ$Xuul?L`%R%?J+NKvH}Z3)biC@QJz8=25H* zq0ADKct@RLC3Q`?x*b#EE-ish_lNqCqFZbSZ*qy&_BtAEmX5!JW&a1piW5TILSc2< zKJX}eHHn=E1JNUmlhRTIoVHef&9kWQEUC6KkgHu$+r7s?MSWV;NlscNluO79+m=#g zH(jm#Dqc&f5`Q>5Vq(Aceo}|%Q3F{f?j$CGBnIcBk_p^DC#8ed3L97N%X>fW-a@_r zA5hn1Q#(&XAud~HCC8NDJWE+JWs1YMp@y%+^<9e5f?>Q{85z+57b`6+2d=c{3%v*> z@wWidg0sJsgM*su_fmX8q0%nhTzRS=KROz8KJXC?*UiW?O{SbdA(W^foa83imyEGT4MZz6OWmaO|#iSqCGPkwt;S_kDK z*3*tB_%2|qha69cX#f9qc@oHb{?c$g0nS zsW+m%EJn?#4Wmq5ht4p z7Zu^h7Hl}}wUN$|E)bsbFZDbbN8Am!2dDP0tUNh!mol_X!7n$^g_XMC)ar))=FX)n zDKBXbZV8|2Kqu=*MZQc>IRbOv$($YQ3D7_@==Ae3=MxNBCiwCBrIL5PFDQNRh`gqCEMg4G$=d{;}d zWkXLixYeAVtT+-4bXl{-<_*MwJPh_HrSteU=*BA}9nu7gO-9){Umx339N0~htW=Rt z56h}vP8t?7=QeMo!vuc*h%D4Y(AN$WtU6H9gElGDtIh=Lx;nZ!1MSUKn kYaDOR*s)}pVqP^~_~#0&|Ct~5@4WwA1OFN|@Rjg?00~ahI{*Lx literal 0 HcmV?d00001 diff --git a/apps/ui/skills/plotter/references/particles.png b/apps/ui/skills/plotter/references/particles.png new file mode 100644 index 0000000000000000000000000000000000000000..aaac9bc6ffb59df239e59923940a7012b830add3 GIT binary patch literal 43328 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 literal 0 HcmV?d00001 diff --git a/apps/ui/skills/plotter/references/primitives.png b/apps/ui/skills/plotter/references/primitives.png new file mode 100644 index 0000000000000000000000000000000000000000..ac4ef3d8752663f12d231887da75c184fbd758f7 GIT binary patch literal 57528 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 jv>)p|n!(cdu<_LNZv)%^-v74*{ Date: Sat, 24 Oct 2026 23:01:33 +0200 Subject: [PATCH 02/15] Refine the Particles lab alignment and construction guides --- .../illustration-lab/particles-blueprint.tsx | 210 ++++++++++-------- .../app/illustration-lab/study-direction.ts | 2 +- .../ui/skills/plotter/references/direction.md | 1 + 3 files changed, 115 insertions(+), 98 deletions(-) diff --git a/apps/ui/app/illustration-lab/particles-blueprint.tsx b/apps/ui/app/illustration-lab/particles-blueprint.tsx index d2a62d606f..9fd1964fa1 100644 --- a/apps/ui/app/illustration-lab/particles-blueprint.tsx +++ b/apps/ui/app/illustration-lab/particles-blueprint.tsx @@ -11,107 +11,123 @@ function Registration() { export function ParticlesBlueprint() { return ( - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Studio + + + Public + + + Open + + + Studio + + + Public + + + Open + + - - - - - - - - - - - - - - - - - - - - Studio - - - Public - - - Open - - - Studio - - - Public - - - Open - - - - - A / Identity - - - B / Visibility - - - C / Action - - - A + B + C - - - 3u - + + + A / Identity + + + B / Visibility + + + C / Action + + + A + B + C + + + 3u + + + - ); } diff --git a/apps/ui/app/illustration-lab/study-direction.ts b/apps/ui/app/illustration-lab/study-direction.ts index 87823073e2..975e5d2816 100644 --- a/apps/ui/app/illustration-lab/study-direction.ts +++ b/apps/ui/app/illustration-lab/study-direction.ts @@ -46,7 +46,7 @@ export function applyStudyDirection( revision: true, selected: true, reviewNote: - "Selected for the Particles page. The requested A + B + C revision adds an Open action to identity and visibility; awaiting review.", + "Selected for the Particles page. The A + B + C revision adds an Open action. After feedback on vertical alignment and a sparse grid, the composition is optically centered with continuous edge and row guides; awaiting review.", }; } if (["P00", "E02"].includes(study.code)) { diff --git a/apps/ui/skills/plotter/references/direction.md b/apps/ui/skills/plotter/references/direction.md index 1364048a39..9af9c27e44 100644 --- a/apps/ui/skills/plotter/references/direction.md +++ b/apps/ui/skills/plotter/references/direction.md @@ -19,6 +19,7 @@ The user explicitly selected **P00** for the main page, [**C03** for Particles]( | Evidence | Status and implication | | --- | --- | | C03: requests a third element so the composition becomes “A + B + C” | Revise C03 in place. Added an Open action alongside identity and visibility, repeated as the third row of the assembled panel; three equal rows measure 3u. This is a requested experiment, awaiting review. | +| C03 A + B + C: “it’s not vertical aligned” and the underlying grid “looks bad or it’s sparse” | Optically center the composition within its SVG and use continuous construction guides aligned to component edges and row boundaries. This is the implementation response to the critique, awaiting review. | | User asks to hide the home and Particles illustrations on smaller screens | Show those two hero drawings only from the existing desktop split-layout breakpoint (`lg`, 1024px). This does not change the 404 or standalone lab studies. | | User rejects a shared page-hero component and asks for the 404 heading to stay centered with the illustration above | Keep main and Particles split layouts in their pages. Use an illustration-first centered stack for the 404, including its lab hero previews. | | User asks to restore the previous Particles page, then explicitly confirms “Keep C03; restore search beneath heading” and references PR #895 for headings | Keep C03 on the right, restore search beneath the left-aligned heading/description, and remove the added hero CTAs. Use PR #895’s fluid 36–64px heading scale, leading-none, and GEOM50 on the main and Particles pages. | From 6b345420f5ff48f84040613775064dc7e8731243 Mon Sep 17 00:00:00 2001 From: pasqualevitiello Date: Sat, 24 Oct 2026 23:14:34 +0200 Subject: [PATCH 03/15] Curate Plotter illustrations into a showcase gallery --- apps/ui/AGENTS.md | 2 +- apps/ui/app/illustration-lab/README.md | 19 - apps/ui/app/illustration-lab/cal-studies.tsx | 490 --------------- .../app/illustration-lab/illustration-lab.tsx | 570 ------------------ .../app/illustration-lab/network-studies.tsx | 497 --------------- .../app/illustration-lab/object-studies.tsx | 519 ---------------- apps/ui/app/illustration-lab/page.tsx | 90 --- .../app/illustration-lab/study-direction.ts | 104 ---- apps/ui/app/plotter/README.md | 19 + .../blueprint-drawing.tsx | 0 apps/ui/app/plotter/cal-studies.tsx | 208 +++++++ .../flat-cal-studies.tsx | 9 +- .../flat-object-studies.tsx | 2 - .../flat-system-studies.tsx | 7 +- apps/ui/app/plotter/illustration-gallery.tsx | 215 +++++++ apps/ui/app/plotter/illustrations.tsx | 69 +++ .../interface-detail-studies.tsx | 2 - apps/ui/app/plotter/network-studies.tsx | 111 ++++ .../not-found-architecture-studies.tsx | 85 +-- .../not-found-blueprint.tsx | 0 .../not-found-geometry-studies.tsx | 81 +-- apps/ui/app/plotter/object-studies.tsx | 140 +++++ .../original-not-found-blueprint.tsx | 0 .../original-particles-blueprint.tsx | 0 apps/ui/app/plotter/page.tsx | 54 ++ .../particles-blueprint.tsx | 0 .../popular-service-studies.tsx | 1 - .../primitives-blueprint.tsx | 0 .../service-studies.tsx | 2 - .../state-studies.tsx | 67 +- .../study-types.ts | 18 +- .../system-studies.tsx | 12 +- apps/ui/next.config.ts | 7 +- apps/ui/skills/plotter/README.md | 4 +- apps/ui/skills/plotter/SKILL.md | 4 +- .../ui/skills/plotter/references/direction.md | 14 +- .../plotter/references/implementation.md | 22 +- 37 files changed, 867 insertions(+), 2577 deletions(-) delete mode 100644 apps/ui/app/illustration-lab/README.md delete mode 100644 apps/ui/app/illustration-lab/cal-studies.tsx delete mode 100644 apps/ui/app/illustration-lab/illustration-lab.tsx delete mode 100644 apps/ui/app/illustration-lab/network-studies.tsx delete mode 100644 apps/ui/app/illustration-lab/object-studies.tsx delete mode 100644 apps/ui/app/illustration-lab/page.tsx delete mode 100644 apps/ui/app/illustration-lab/study-direction.ts create mode 100644 apps/ui/app/plotter/README.md rename apps/ui/app/{illustration-lab => plotter}/blueprint-drawing.tsx (100%) create mode 100644 apps/ui/app/plotter/cal-studies.tsx rename apps/ui/app/{illustration-lab => plotter}/flat-cal-studies.tsx (98%) rename apps/ui/app/{illustration-lab => plotter}/flat-object-studies.tsx (99%) rename apps/ui/app/{illustration-lab => plotter}/flat-system-studies.tsx (98%) create mode 100644 apps/ui/app/plotter/illustration-gallery.tsx create mode 100644 apps/ui/app/plotter/illustrations.tsx rename apps/ui/app/{illustration-lab => plotter}/interface-detail-studies.tsx (99%) create mode 100644 apps/ui/app/plotter/network-studies.tsx rename apps/ui/app/{illustration-lab => plotter}/not-found-architecture-studies.tsx (55%) rename apps/ui/app/{illustration-lab => plotter}/not-found-blueprint.tsx (100%) rename apps/ui/app/{illustration-lab => plotter}/not-found-geometry-studies.tsx (51%) create mode 100644 apps/ui/app/plotter/object-studies.tsx rename apps/ui/app/{illustration-lab => plotter}/original-not-found-blueprint.tsx (100%) rename apps/ui/app/{illustration-lab => plotter}/original-particles-blueprint.tsx (100%) create mode 100644 apps/ui/app/plotter/page.tsx rename apps/ui/app/{illustration-lab => plotter}/particles-blueprint.tsx (100%) rename apps/ui/app/{illustration-lab => plotter}/popular-service-studies.tsx (99%) rename apps/ui/app/{illustration-lab => plotter}/primitives-blueprint.tsx (100%) rename apps/ui/app/{illustration-lab => plotter}/service-studies.tsx (99%) rename apps/ui/app/{illustration-lab => plotter}/state-studies.tsx (50%) rename apps/ui/app/{illustration-lab => plotter}/study-types.ts (50%) rename apps/ui/app/{illustration-lab => plotter}/system-studies.tsx (98%) diff --git a/apps/ui/AGENTS.md b/apps/ui/AGENTS.md index 912a54e943..ceba1a6552 100644 --- a/apps/ui/AGENTS.md +++ b/apps/ui/AGENTS.md @@ -1,6 +1,6 @@ # 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/illustration-lab` page. +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. diff --git a/apps/ui/app/illustration-lab/README.md b/apps/ui/app/illustration-lab/README.md deleted file mode 100644 index 26b2187ece..0000000000 --- a/apps/ui/app/illustration-lab/README.md +++ /dev/null @@ -1,19 +0,0 @@ -# Plotter illustration lab - -Direct route: `/ui/illustration-lab`. This page is intentionally absent from site navigation and the sitemap, and its metadata disables indexing. The old prototype URL redirects here with its query parameters. - -The lab preserves all 54 remaining experiments, their stable codes, critique notes, and earlier rejected directions. E04 was removed at the user’s request. The current-direction filter hides earlier rejected studies; All experiments reveals them. Drawing/hero previews and the in-memory shortlist remain available. - -## Production selections - -| Study | Selected page | Lab source | -| --- | --- | --- | -| P00 | Main | `./primitives-blueprint.tsx` | -| C03 | Particles | `./particles-blueprint.tsx` | -| E02 | 404 | `./not-found-blueprint.tsx` | - -The lab 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 historical studies. When a selected production drawing changes, update its lab 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 experiment is unreviewed until the user responds. Add studies to their subject collection and preserve existing IDs when revising them. diff --git a/apps/ui/app/illustration-lab/cal-studies.tsx b/apps/ui/app/illustration-lab/cal-studies.tsx deleted file mode 100644 index f4e44915ef..0000000000 --- a/apps/ui/app/illustration-lab/cal-studies.tsx +++ /dev/null @@ -1,490 +0,0 @@ -import { BlueprintDrawing } from "./blueprint-drawing"; -import type { IllustrationStudy } from "./study-types"; - -function RegistrationMarks() { - return ( - - - - - ); -} - -function BoundCalendar() { - return ( - - - - - - - - - - - - - - - - - - - - - - - {Array.from({ length: 31 }, (_, index) => index + 1).map((day) => { - const position = day + 2; - return day === 24 ? null : ( - - ); - })} - - - - - October - - - 2026 - - - 24 - - - - - - - - - - - 7 days - - - Yours - - - - - - ); -} - -function SharedAvailability() { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - A - - - B - - - - - - - - - - - 13:00 - - - 15:00 - - - 2h - - - Shared availability - - - - - ); -} - -function ClockFace({ cx, hour }: { cx: number; hour: number }) { - const hourAngle = (hour / 12) * Math.PI * 2; - - return ( - - - - - {Array.from({ length: 12 }, (_, index) => index).map((hourTick) => ( - - ))} - - - - - - - - - ); -} - -function TwoTimeZones() { - return ( - - - - - - - - - - - - - - - - +6h - - - 09:30 - - - UTC - - - 15:30 - - - UTC+6 - - - - - ); -} - -function BookingTicket() { - return ( - - - - - - - - - - - - - - - - - October - - - 24 - - - - - - - - - - - - - - - - - - Booked - - - 14:30 - - - Confirmed - - - 30 min - - - - - ); -} - -function HostPortrait({ cy }: { cy: number }) { - return ( - - - - - - ); -} - -function RoutedBooking() { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Request - - - Design - - - Support - - - Sales - - - One match - - - - - ); -} - -export const calStudies: IllustrationStudy[] = [ - { - id: "cal-calendar", - code: "K01", - title: "A day worth keeping", - description: - "A bound calendar isolates one date within a measured month. A lifted corner and fine page edges give the familiar object a quiet physical presence.", - category: "Cal.com", - illustration: , - }, - { - 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: "Cal.com", - illustration: , - }, - { - id: "cal-time-zones", - code: "K03", - title: "One moment, two places", - description: - "Two clock faces show the same instant six hours apart. Their shared minute hand and connecting datum make the time difference tangible.", - category: "Cal.com", - illustration: , - }, - { - id: "cal-booking", - code: "K04", - title: "Time, reserved", - description: - "A day on the calendar becomes a perforated confirmation ticket, turning available time into a definite appointment.", - category: "Cal.com", - illustration: , - }, - { - id: "cal-routing", - code: "K05", - title: "The right person", - description: - "One booking follows a clear route to its matching host. The other lanes recede, leaving the selected connection unmistakable.", - category: "Cal.com", - illustration: , - }, -]; diff --git a/apps/ui/app/illustration-lab/illustration-lab.tsx b/apps/ui/app/illustration-lab/illustration-lab.tsx deleted file mode 100644 index cef963aece..0000000000 --- a/apps/ui/app/illustration-lab/illustration-lab.tsx +++ /dev/null @@ -1,570 +0,0 @@ -"use client"; - -import { RiArrowLeftLine } from "@remixicon/react"; -import { - ArrowLeftIcon, - ArrowRightIcon, - CheckIcon, - CopyIcon, - StarIcon, -} from "lucide-react"; -import { useSearchParams } from "next/navigation"; -import { useCallback, useEffect, useRef, useState } from "react"; -import { cn } from "@/registry/default/lib/utils"; -import { Button, buttonVariants } from "@/registry/default/ui/button"; -import type { IllustrationStudy, StudyCategory } from "./study-types"; - -const categories: StudyCategory[] = [ - "Primitives", - "Particles", - "Cal.com", - "Interfaces", - "Services", - "Workflows", - "Data", - "Documents", - "Typography", - "States", - "404", - "Objects", -]; - -function studyLabel(study: IllustrationStudy) { - if (study.selected) return "Selected for coss"; - if (study.review === "reference") return "Style reference"; - if (study.review === "outside") return "Outside direction"; - if (study.review === "partial") { - return study.revision ? "Revised · awaiting review" : "Partly fits"; - } - if (study.revision) return "Revised 2D study"; - return study.baseline ? "Original study" : "Unreviewed"; -} - -const heroCopy: Record< - StudyCategory, - { title: string; description: string; action: string } -> = { - "Cal.com": { - title: "Make time for what matters.", - description: - "A clear path from finding the right time to meeting the right person.", - action: "Find a time", - }, - Interfaces: { - title: "Every detail has a place.", - description: - "Thoughtful parts, composed into interfaces that feel natural to use.", - action: "Explore components", - }, - Services: { - title: "Systems, made visible.", - description: - "From a request to a response, from an idea to a release. A precise drawing of how things connect.", - action: "Explore the system", - }, - Documents: { - title: "Ideas, given a home.", - description: - "Collect what you know. Give it structure. Make it easy to discover.", - action: "Open the library", - }, - Workflows: { - title: "Good connections move things forward.", - description: - "Bring the right steps together, with a clear route from start to finish.", - action: "Explore workflows", - }, - Objects: { - title: "Familiar forms. Precise construction.", - description: - "Familiar objects reveal the small decisions that make a design work.", - action: "Look closer", - }, - Data: { - title: "Find the shape in your information.", - description: - "Give complex information a clear structure, so the useful details emerge.", - action: "Explore your data", - }, - Typography: { - title: "Character, by construction.", - description: - "A curve, a counter, a carefully chosen proportion. Small decisions build an identity.", - action: "Explore the details", - }, - States: { - title: "Clarity at every step.", - description: - "A missing destination or a completed task, expressed in a single clear picture.", - action: "Find your next step", - }, - "404": { - title: "Page Not Found", - description: - "The page you’re looking for doesn’t exist or may have been moved.", - action: "Back to Home", - }, - Primitives: { - title: "A new, modern UI component library built on top of Base UI.", - description: "Built for developers and AI.", - action: "Get started", - }, - Particles: { - title: "Browse Particles", - description: - "Ready-to-use particles, the building blocks of your design system. Small pieces, endless possibilities.", - action: "Explore particles", - }, -}; - -export function IllustrationLab({ studies }: { studies: IllustrationStudy[] }) { - const params = useSearchParams(); - const firstStudy = studies.find((study) => study.review !== "outside"); - if (!firstStudy) throw new Error("The illustration lab needs a study."); - const active = - studies.find((study) => study.id === params.get("variant")) ?? firstStudy; - const showEarlier = - params.get("scope") === "all" || active.review === "outside"; - const workingStudies = studies.filter((study) => study.review !== "outside"); - const visibleStudies = showEarlier ? studies : workingStudies; - const visibleCategories = categories.filter((category) => - visibleStudies.some((study) => study.category === category), - ); - const view = params.get("view") === "hero" ? "hero" : "drawing"; - const categoryStudies = visibleStudies.filter( - (study) => study.category === active.category, - ); - const position = categoryStudies.findIndex((study) => study.id === active.id); - const [shortlist, setShortlist] = useState([]); - const [copyStatus, setCopyStatus] = useState(""); - const stage = useRef(null); - - const selectStudy = useCallback((id: string) => { - const next = new URLSearchParams(window.location.search); - next.set("variant", id); - window.history.replaceState(null, "", `?${next}`); - }, []); - - const cycle = useCallback( - (direction: number) => { - const group = studies.filter( - (study) => - study.category === active.category && - (showEarlier || study.review !== "outside"), - ); - const index = group.findIndex((study) => study.id === active.id); - const nextStudy = - group[(index + direction + group.length) % group.length]; - if (nextStudy) selectStudy(nextStudy.id); - }, - [active.category, active.id, selectStudy, showEarlier, studies], - ); - - useEffect(() => { - const handleKey = (event: KeyboardEvent) => { - if (event.altKey || event.ctrlKey || event.metaKey || event.shiftKey) - return; - if ( - event.target instanceof Element && - event.target.closest( - "input, textarea, select, [contenteditable], [role=tablist], [role=radiogroup]", - ) - ) - return; - if (event.key === "ArrowLeft" || event.key === "ArrowRight") { - event.preventDefault(); - cycle(event.key === "ArrowLeft" ? -1 : 1); - } - }; - window.addEventListener("keydown", handleKey); - return () => window.removeEventListener("keydown", handleKey); - }, [cycle]); - - function setView(nextView: string) { - const next = new URLSearchParams(window.location.search); - next.set("view", nextView); - next.set("variant", active.id); - window.history.replaceState(null, "", `?${next}`); - } - - function setScope(includeEarlier: boolean) { - const next = new URLSearchParams(window.location.search); - if (includeEarlier) { - next.set("scope", "all"); - } else { - next.delete("scope"); - const selection = - active.review !== "outside" - ? active - : (workingStudies.find( - (study) => study.category === active.category, - ) ?? firstStudy); - if (selection) next.set("variant", selection.id); - } - window.history.replaceState(null, "", `?${next}`); - } - - function toggleShortlist(id: string) { - setShortlist((current) => - current.includes(id) - ? current.filter((item) => item !== id) - : [...current, id], - ); - setCopyStatus(""); - } - - async function copyShortlist() { - const selected = studies.filter((study) => shortlist.includes(study.id)); - const text = selected - .map((study) => `${study.code} · ${study.category} · ${study.title}`) - .join("\n"); - try { - await navigator.clipboard.writeText( - `My illustration shortlist:\n${text}`, - ); - setCopyStatus("Copied — paste your choices into the chat."); - } catch { - setCopyStatus( - "Copy unavailable. You can share the study codes below instead.", - ); - } - } - - const copy = heroCopy[active.category]; - - return ( -
-
-

- Plotter / {workingStudies.length} current studies /{" "} - {studies.filter((study) => study.revision).length} revised -

-

- Flat forms. Clear construction. -

-

- Primitives and Particles set the reference: geometric forms and small - interface elements drawn as precise 2D constructions. Guides reveal - how the composition is built. -

-
- -
-
- - -
-
- {visibleCategories.map((category) => ( - - ))} -
-
-

- {active.category} / {categoryStudies.length}{" "} - {categoryStudies.length === 1 ? "study" : "studies"} -

-
- - -
-
-
- -
-
-
-

- {active.code} / {active.category} - {" / "} - {studyLabel(active)} -

-

{active.title}

- {active.reviewNote && ( -

- {active.reviewNote} -

- )} -
- -
- {view === "drawing" ? ( -
- {active.illustration} -
- ) : ( -
-
-

- {copy.title} -

-

- {copy.description} -

- - {active.category === "404" && ( - -
-
- {active.illustration} -
-
- )} -

- {active.description} -

-
- -
-
-

All {active.category} studies

-

- Select a drawing to inspect it above. -

-
-
- {categoryStudies.map((study) => ( -
- - -
- ))} -
-
- -
-
-

- Your shortlist{" "} - / {shortlist.length} -

- -
- {shortlist.length ? ( -
- {studies - .filter((study) => shortlist.includes(study.id)) - .map((study) => ( - - ))} -
- ) : ( -

- Star the studies you like, then copy your choices into the chat. -

- )} -

- {copyStatus || - "Your shortlist lasts for this visit. Copy it before refreshing or leaving."} -

-
- -
- - The direction we’re keeping - -

- One flat drawing plane. Circles, rectangles, arcs, slots, and small - interface fragments. Fine outlines, quieter guides, sparse dimensions, - and generous empty space. Every guide explains a real relationship: - radius, spacing, alignment, connection, or assembly. No perspective, - volume, or tilted objects. The reference is a construction study, - rather than an object, screen mockup, or large pictogram decorated - with technical marks. Unreviewed studies remain candidates, not - approvals. -

-
- - -
- ); -} diff --git a/apps/ui/app/illustration-lab/network-studies.tsx b/apps/ui/app/illustration-lab/network-studies.tsx deleted file mode 100644 index cbcf17e259..0000000000 --- a/apps/ui/app/illustration-lab/network-studies.tsx +++ /dev/null @@ -1,497 +0,0 @@ -import { BlueprintDrawing } from "./blueprint-drawing"; -import type { IllustrationStudy } from "./study-types"; - -function RegistrationMarks() { - return ( - - - - - ); -} - -function InterfaceLayersStudy() { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - u - - - u - - - assembly - - - - - ); -} - -function AdaptiveLayoutStudy() { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - wide - - - narrow - - - - - ); -} - -function ConnectionBridgeStudy() { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - span - - - joined - - - 01 - - - 02 - - - - - ); -} - -function BranchingWorkflowStudy() { - const routes = - "M132 224H216A24 24 0 0 0 240 200V152A24 24 0 0 1 264 128H420 M132 224H420 M132 224H216A24 24 0 0 1 240 248V296A24 24 0 0 0 264 320H420"; - - return ( - - - {[168, 192, 216, 240, 264, 288, 312, 336, 360, 384].map((x) => ( - - ))} - {[152, 176, 200, 248, 272, 296].map((y) => ( - - ))} - - - - - - - {/* Parallel outlines make the route itself the constructed form. */} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - R24 - - - w = 12 - - - 96 - - - 96 - - - 288 - - - in - - - 01 - - - 02 - - - 03 - - - - - ); -} - -function InformationTerrainStudy() { - return ( - - - - - - - - - - - - - - - - - - - - - - - summit - - - h - - - 02 - - - 01 - - - - - ); -} - -function OrderedCollectionStudy() { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - 01 - - - - - - - - - - retrieve - - - index - - - - - ); -} - -export const networkStudies: IllustrationStudy[] = [ - { - id: "interface-layers", - code: "I01", - title: "Layers of an interface", - description: - "A finished window lifts away from its layout and underlying grid, revealing the structure that holds it together.", - category: "Interfaces", - illustration: , - }, - { - id: "interface-layout", - code: "I02", - title: "A layout that adapts", - description: - "The same composition changes its proportions, carrying one clear idea from a wide canvas into a narrow one.", - category: "Interfaces", - illustration: , - }, - { - id: "workflow-bridge", - code: "W01", - title: "A connection, made", - description: - "A single slender bridge joins two endpoints, turning separate pieces into a continuous system.", - category: "Workflows", - illustration: , - }, - { - id: "workflow-branch", - code: "W02", - title: "A route, constructed", - description: - "One continuous route, three outlets. Parallel outlines reveal its width, repeated bend radius, and equal branch spacing.", - category: "Workflows", - illustration: , - revision: true, - }, - { - id: "data-terrain", - code: "A01", - title: "A field of information", - description: - "Measured contours give a field of information a recognizable landscape, revealing its shape and its highest point.", - category: "Data", - illustration: , - }, - { - id: "data-stack", - code: "A02", - title: "An ordered collection", - description: - "An indexed collection opens around one selected record, keeping its place in the larger structure visible.", - category: "Data", - illustration: , - }, -]; diff --git a/apps/ui/app/illustration-lab/object-studies.tsx b/apps/ui/app/illustration-lab/object-studies.tsx deleted file mode 100644 index 97426a6f90..0000000000 --- a/apps/ui/app/illustration-lab/object-studies.tsx +++ /dev/null @@ -1,519 +0,0 @@ -import { BlueprintDrawing } from "./blueprint-drawing"; -import type { IllustrationStudy } from "./study-types"; - -function OpenVolume() { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 2 × folio - - - Index - - - 01 - - - The open volume - - - - ); -} - -function IndexedArchive() { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - A series - - - 3 leaves - - - Index - - - An indexed archive - - - - ); -} - -function AdjustableLight() { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - θ - - - Ø - - - 01 - - - Working height - - - - ); -} - -function SheetToVolume() { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Fold - - - h - - - u - - - 01 - - - From sheet to volume - - - - ); -} - -function LetterAnatomy() { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - x-height - - - Stem - - - r - - - Overshoot - - - Anatomy of a letter - - - - ); -} - -function MarkInMaking() { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 90° - - - R - - - 1u - - - Terminal - - - A mark in the making - - - - ); -} - -export const objectStudies: IllustrationStudy[] = [ - { - id: "document-book", - code: "D01", - title: "The open volume", - description: - "A curved spread, a bound spine, and a ribbon index turn documentation into a tangible object.", - category: "Documents", - illustration: , - }, - { - id: "document-index", - code: "D02", - title: "An indexed archive", - description: - "Three indexed leaves emerge from an oblique folder: a small architecture for keeping knowledge in order.", - category: "Documents", - illustration: , - }, - { - id: "object-lamp", - code: "O01", - title: "An adjustable light", - description: - "An articulated desk lamp with measured joints and a quiet cone of light makes adaptability physical.", - category: "Objects", - illustration: , - }, - { - id: "object-fold", - code: "O02", - title: "From sheet to volume", - description: - "An open carton reveals the hinges and folds that transform one flat material into a useful volume.", - category: "Objects", - illustration: , - }, - { - id: "type-letter", - code: "T01", - title: "Anatomy of a letter", - description: - "A custom lowercase a exposes its circular bowl, counter, stem, and optical overshoot.", - category: "Typography", - illustration: , - }, - { - id: "type-monogram", - code: "T02", - title: "A mark in the making", - description: - "Two concentric arcs and a deliberate opening establish the logic of a simple geometric C.", - category: "Typography", - illustration: , - }, -]; diff --git a/apps/ui/app/illustration-lab/page.tsx b/apps/ui/app/illustration-lab/page.tsx deleted file mode 100644 index 0b4eb42ebf..0000000000 --- a/apps/ui/app/illustration-lab/page.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import type { Metadata } from "next"; -import { Suspense } from "react"; -import { calStudies } from "./cal-studies"; -import { flatCalStudies } from "./flat-cal-studies"; -import { flatObjectStudies } from "./flat-object-studies"; -import { flatSystemStudies } from "./flat-system-studies"; -import { IllustrationLab } from "./illustration-lab"; -import { interfaceDetailStudies } from "./interface-detail-studies"; -import { networkStudies } from "./network-studies"; -import { notFoundArchitectureStudies } from "./not-found-architecture-studies"; -import { notFoundGeometryStudies } from "./not-found-geometry-studies"; -import { objectStudies } from "./object-studies"; -import { OriginalNotFoundBlueprint } from "./original-not-found-blueprint"; -import { OriginalParticlesBlueprint } from "./original-particles-blueprint"; -import { popularServiceStudies } from "./popular-service-studies"; -import { PrimitivesBlueprint } from "./primitives-blueprint"; -import { serviceStudies } from "./service-studies"; -import { stateStudies } from "./state-studies"; -import { applyStudyDirection } from "./study-direction"; -import type { IllustrationStudy } from "./study-types"; -import { systemStudies } from "./system-studies"; - -export const metadata: Metadata = { - title: "Plotter · Illustration lab", - description: - "Flat monochrome construction studies and the evolving Plotter illustration direction.", - robots: { index: false, follow: false }, -}; - -const references: IllustrationStudy[] = [ - { - id: "404-reference", - code: "E00", - title: "The missing corner", - description: - "A vacant module in an otherwise complete construction. The original 404 experiment, retained for comparison.", - category: "404", - illustration: , - baseline: true, - }, - { - id: "primitives-reference", - code: "P00", - title: "Circle, square, line", - description: - "Fundamental geometry and its construction rules. The current Primitives illustration.", - category: "Primitives", - illustration: , - baseline: true, - }, - { - id: "particles-reference", - code: "C00", - title: "The exploded interface", - description: - "A pill, row and toggle projected onto a shared frame. The original Particles illustration, retained as a style reference.", - category: "Particles", - illustration: , - baseline: true, - }, -]; - -// Direct-link review page. Intentionally absent from navigation and the sitemap. -export default function Page() { - return ( - Loading studies…} - > - study.category !== "404"), - ...systemStudies, - ...flatCalStudies, - ...interfaceDetailStudies, - ...flatSystemStudies, - ...flatObjectStudies, - ...serviceStudies, - ...popularServiceStudies, - ...calStudies, - ...networkStudies, - ...objectStudies, - ...stateStudies, - ...notFoundArchitectureStudies, - ...notFoundGeometryStudies, - ...references.filter((study) => study.category === "404"), - ].map(applyStudyDirection)} - /> - - ); -} diff --git a/apps/ui/app/illustration-lab/study-direction.ts b/apps/ui/app/illustration-lab/study-direction.ts deleted file mode 100644 index 975e5d2816..0000000000 --- a/apps/ui/app/illustration-lab/study-direction.ts +++ /dev/null @@ -1,104 +0,0 @@ -import type { IllustrationStudy } from "./study-types"; - -// The rejected codes are explicit user feedback; the short diagnoses are ours. -// Keep original codes intact. -const outsideDirection: Record = { - K01: "A tilted calendar object with bindings and page depth.", - K04: "A pictorial calendar-and-ticket composition.", - K05: "A booking-flow illustration built from literal icons.", - I01: "Perspective and separated depth layers.", - I02: "Complete screen mockups rather than a construction relationship.", - D01: "A physical book with curved pages and depth.", - D02: "A physical archive shown in perspective.", - W01: "A physical bridge used as a metaphor.", - O01: "A literal object, even though it is drawn in elevation.", - O02: "A folded object with visible volume.", - A01: "A landscape built from stacked depth layers.", - A02: "A stack of records shown in perspective.", - S02: "A large success pictogram rather than a construction study.", -}; - -// Preserve the critique separately from our response to it. Revisions still -// need the user's review; they are not new style references by default. -const revisionFeedback: Record = { - I04: "The browser had the same problem as the earlier keyboard. Revised as a deliberate close-up with quiet construction and dashed continuation.", - I06: "Requested avatars and removal of the triangular bubble tails. Revised as rounded messages paired with circular avatars.", - O03: "The keyboard felt incomplete. Revised as a deliberate cropped detail with staggered rows and subtler dashed lines.", - O05: "Requested two or three folders instead of one. Revised as a family of three flat outlines.", - E02: "The idea worked but felt bland and weak. Revised with a measured route and a more prominent missing link.", - B01: "Questioned whether the metaphor accurately represented Cloudflare. Revised to keep the origin outside the edge and distinguish cache hits from misses.", - B09: "The connector at owner_id felt cramped. Replaced the branching endpoint with a circular port and a short straight approach.", - B02: "Requested a more conceptual Vercel study, then questioned its accuracy. Revised to show one selected preview promoted to production; the broadcast arcs are removed.", - K06: "The week was not recognizable. Revised with weekday and time labels, plus a named interval.", - K08: "The concept worked, but its meaning was unclear. Revised with named availability and meeting labels.", - I03: "A good direction, but not a strong example. Revised to make the construction relationship more prominent.", - W03: "The composition felt weak. Revised with a more substantial selection structure.", - W02: "Did not read as a blueprint. Revised around route width, bend radius, and branch spacing; awaiting review.", -}; - -export function applyStudyDirection( - study: IllustrationStudy, -): IllustrationStudy { - if (study.code === "C03") { - return { - ...study, - review: "partial", - revision: true, - selected: true, - reviewNote: - "Selected for the Particles page. The A + B + C revision adds an Open action. After feedback on vertical alignment and a sparse grid, the composition is optically centered with continuous edge and row guides; awaiting review.", - }; - } - if (["P00", "E02"].includes(study.code)) { - return { - ...study, - review: "reference", - selected: true, - reviewNote: `Selected for the ${study.code === "P00" ? "main" : "404"} page. This preview uses the lab’s independent drawing snapshot.`, - }; - } - if (study.code === "B04") { - return { - ...study, - review: "reference", - reviewNote: - "Explicit user feedback: ‘love the Linear example.’ A service reference for aligned modules and meaningful time intervals.", - }; - } - if (study.code === "B03") { - return { - ...study, - reviewNote: - "The user said this Stripe example was okay and requested another focused more directly on what Stripe does.", - }; - } - const note = outsideDirection[study.code]; - if (note) return { ...study, review: "outside", reviewNote: note }; - - // These were not individually rejected; their tilt conflicts with the new rule. - if (study.code === "E03" || study.code === "E05") { - return { - ...study, - review: "outside", - reviewNote: - "Set aside because its tilted form conflicts with the clarified flat-view rule; not individually reviewed.", - }; - } - const revisionNote = revisionFeedback[study.code]; - if (revisionNote) { - return { - ...study, - review: "partial", - reviewNote: revisionNote, - }; - } - if (study.category === "Primitives" || study.category === "Particles") { - return { - ...study, - review: "reference", - reviewNote: - "Style reference: one plane, modular forms, and guides that explain their construction.", - }; - } - return study; -} diff --git a/apps/ui/app/plotter/README.md b/apps/ui/app/plotter/README.md new file mode 100644 index 0000000000..eeb665d913 --- /dev/null +++ b/apps/ui/app/plotter/README.md @@ -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. diff --git a/apps/ui/app/illustration-lab/blueprint-drawing.tsx b/apps/ui/app/plotter/blueprint-drawing.tsx similarity index 100% rename from apps/ui/app/illustration-lab/blueprint-drawing.tsx rename to apps/ui/app/plotter/blueprint-drawing.tsx diff --git a/apps/ui/app/plotter/cal-studies.tsx b/apps/ui/app/plotter/cal-studies.tsx new file mode 100644 index 0000000000..9f2cc3bc5f --- /dev/null +++ b/apps/ui/app/plotter/cal-studies.tsx @@ -0,0 +1,208 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; +import type { IllustrationStudy } from "./study-types"; + +function RegistrationMarks() { + return ( + + + + + ); +} + +function SharedAvailability() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A + + + B + + + + + + + + + + + 13:00 + + + 15:00 + + + 2h + + + Shared availability + + + + + ); +} + +function ClockFace({ cx, hour }: { cx: number; hour: number }) { + const hourAngle = (hour / 12) * Math.PI * 2; + + return ( + + + + + {Array.from({ length: 12 }, (_, index) => index).map((hourTick) => ( + + ))} + + + + + + + + + ); +} + +function TwoTimeZones() { + return ( + + + + + + + + + + + + + + + + +6h + + + 09:30 + + + UTC + + + 15:30 + + + UTC+6 + + + + + ); +} + +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: , + }, + { + id: "cal-time-zones", + code: "K03", + title: "One moment, two places", + description: + "Two clock faces show the same instant six hours apart. Their shared minute hand and connecting datum make the time difference tangible.", + category: "Scheduling", + illustration: , + }, +]; diff --git a/apps/ui/app/illustration-lab/flat-cal-studies.tsx b/apps/ui/app/plotter/flat-cal-studies.tsx similarity index 98% rename from apps/ui/app/illustration-lab/flat-cal-studies.tsx rename to apps/ui/app/plotter/flat-cal-studies.tsx index 3b0537db23..c45bf4f85d 100644 --- a/apps/ui/app/illustration-lab/flat-cal-studies.tsx +++ b/apps/ui/app/plotter/flat-cal-studies.tsx @@ -233,9 +233,8 @@ export const flatCalStudies: IllustrationStudy[] = [ title: "A week, constructed", description: "Monday through Sunday on a measured hourly grid. One constructed circle locates a meeting on Thursday, from 10:00 to 11:00.", - category: "Cal.com", + category: "Scheduling", illustration: , - revision: true, }, { id: "cal-time-modules", @@ -243,9 +242,8 @@ export const flatCalStudies: IllustrationStudy[] = [ title: "The shape of a time slot", description: "Two measured intervals combine into one reusable time slot. Duration becomes a rule of composition.", - category: "Cal.com", + category: "Scheduling", illustration: , - revision: true, }, { id: "cal-availability-plan", @@ -253,8 +251,7 @@ export const flatCalStudies: IllustrationStudy[] = [ title: "Where schedules meet", description: "Alex’s and Sam’s availability meet in one shared slot. Named bands and a meeting callout make the overlap explicit.", - category: "Cal.com", + category: "Scheduling", illustration: , - revision: true, }, ]; diff --git a/apps/ui/app/illustration-lab/flat-object-studies.tsx b/apps/ui/app/plotter/flat-object-studies.tsx similarity index 99% rename from apps/ui/app/illustration-lab/flat-object-studies.tsx rename to apps/ui/app/plotter/flat-object-studies.tsx index 25bfb948ea..572e66c65f 100644 --- a/apps/ui/app/illustration-lab/flat-object-studies.tsx +++ b/apps/ui/app/plotter/flat-object-studies.tsx @@ -225,7 +225,6 @@ export const flatObjectStudies: IllustrationStudy[] = [ "An enlarged portion of a keyboard, with staggered letter rows and wider modifiers. Open edges and dashed continuation reveal a detail within a larger system.", category: "Objects", illustration: , - revision: true, }, { id: "object-lamp-elevation", @@ -244,6 +243,5 @@ export const flatObjectStudies: IllustrationStudy[] = [ "Three independent folders share one tab, radius, and outline. Equal spacing and a common centerline turn a familiar shape into a small construction system.", category: "Objects", illustration: , - revision: true, }, ]; diff --git a/apps/ui/app/illustration-lab/flat-system-studies.tsx b/apps/ui/app/plotter/flat-system-studies.tsx similarity index 98% rename from apps/ui/app/illustration-lab/flat-system-studies.tsx rename to apps/ui/app/plotter/flat-system-studies.tsx index 0624c0669b..8bebd8a7c1 100644 --- a/apps/ui/app/illustration-lab/flat-system-studies.tsx +++ b/apps/ui/app/plotter/flat-system-studies.tsx @@ -244,7 +244,6 @@ export const flatSystemStudies: IllustrationStudy[] = [ "Three separate elements project into one control. Equal gaps, a common centerline, and a shared radius make the assembly rule visible.", category: "Interfaces", illustration: , - revision: true, }, { id: "workflow-switch-plan", @@ -252,9 +251,8 @@ export const flatSystemStudies: IllustrationStudy[] = [ title: "A connection, located", description: "Three inputs meet three outputs on a regular switching grid. A single closed junction connects B to 02, turning an abstract matrix into a precise route.", - category: "Workflows", + category: "Systems", illustration: , - revision: true, }, { id: "data-coordinate-plan", @@ -262,8 +260,7 @@ export const flatSystemStudies: IllustrationStudy[] = [ title: "A measured distribution", description: "A small cluster occupies a regular coordinate field. Two projected measurements locate one point and make the underlying rule visible.", - category: "Data", + category: "Systems", illustration: , - revision: true, }, ]; diff --git a/apps/ui/app/plotter/illustration-gallery.tsx b/apps/ui/app/plotter/illustration-gallery.tsx new file mode 100644 index 0000000000..c3fc30f51f --- /dev/null +++ b/apps/ui/app/plotter/illustration-gallery.tsx @@ -0,0 +1,215 @@ +"use client"; + +import { ArrowLeftIcon, ArrowRightIcon, ArrowUpRightIcon } from "lucide-react"; +import { useSearchParams } from "next/navigation"; +import { useRef } from "react"; +import { Button } from "@/registry/default/ui/button"; +import { + Dialog, + DialogDescription, + DialogPanel, + DialogPopup, + DialogTitle, +} from "@/registry/default/ui/dialog"; +import type { IllustrationStudy, StudyCategory } from "./study-types"; + +const categories: StudyCategory[] = [ + "Foundations", + "Interfaces", + "Scheduling", + "Services", + "Systems", + "Objects", + "Empty states", +]; + +export function IllustrationGallery({ + illustrations, +}: { + illustrations: IllustrationStudy[]; +}) { + const params = useSearchParams(); + const category = categories.find((item) => item === params.get("category")); + const visible = category + ? illustrations.filter((item) => item.category === category) + : illustrations; + const active = illustrations.find( + (item) => item.id === params.get("variant"), + ); + const currentGroup = + active && !visible.includes(active) ? illustrations : visible; + const position = currentGroup.findIndex((item) => item.id === active?.id); + const lastTrigger = useRef(null); + + function updateParams(values: Record) { + const next = new URLSearchParams(window.location.search); + next.delete("view"); + next.delete("scope"); + for (const [key, value] of Object.entries(values)) { + if (value) next.set(key, value); + else next.delete(key); + } + const query = next.toString(); + window.history.replaceState( + null, + "", + `${window.location.pathname}${query ? `?${query}` : ""}`, + ); + } + + function cycle(direction: number) { + const next = + currentGroup[ + (position + direction + currentGroup.length) % currentGroup.length + ]; + if (next) updateParams({ variant: next.id }); + } + + return ( + <> +
+
+ {["All", ...categories].map((item) => ( + + ))} +
+

+ {visible.length} illustrations +

+
+ +
    + {visible.map((item) => ( +
  • + +
  • + ))} +
+ + { + if (!open) updateParams({ variant: null }); + }} + > + lastTrigger.current} + onKeyDown={(event) => { + if ( + event.altKey || + event.ctrlKey || + event.metaKey || + event.shiftKey + ) + return; + if (event.key === "ArrowLeft" || event.key === "ArrowRight") { + event.preventDefault(); + cycle(event.key === "ArrowLeft" ? -1 : 1); + } + }} + > + {active && ( + <> + +

+ {active.category} /{" "} + {active.code} +

+
+ {active.illustration} +
+
+ {active.title} + + {active.description} + +
+
+
+ +

+ {position + 1} / {currentGroup.length} +

+ +
+ + )} +
+
+ + ); +} diff --git a/apps/ui/app/plotter/illustrations.tsx b/apps/ui/app/plotter/illustrations.tsx new file mode 100644 index 0000000000..71a35daef9 --- /dev/null +++ b/apps/ui/app/plotter/illustrations.tsx @@ -0,0 +1,69 @@ +import { calStudies } from "./cal-studies"; +import { flatCalStudies } from "./flat-cal-studies"; +import { flatObjectStudies } from "./flat-object-studies"; +import { flatSystemStudies } from "./flat-system-studies"; +import { interfaceDetailStudies } from "./interface-detail-studies"; +import { networkStudies } from "./network-studies"; +import { notFoundArchitectureStudies } from "./not-found-architecture-studies"; +import { notFoundGeometryStudies } from "./not-found-geometry-studies"; +import { objectStudies } from "./object-studies"; +import { OriginalNotFoundBlueprint } from "./original-not-found-blueprint"; +import { OriginalParticlesBlueprint } from "./original-particles-blueprint"; +import { popularServiceStudies } from "./popular-service-studies"; +import { PrimitivesBlueprint } from "./primitives-blueprint"; +import { serviceStudies } from "./service-studies"; +import { stateStudies } from "./state-studies"; +import type { IllustrationStudy } from "./study-types"; +import { systemStudies } from "./system-studies"; + +const collection: IllustrationStudy[] = [ + { + id: "primitives-reference", + code: "P00", + title: "Circle, square, line", + description: + "Fundamental geometry, connected by radius, tangency, and a shared construction grid.", + category: "Foundations", + illustration: , + }, + { + id: "particles-reference", + code: "C00", + title: "The exploded interface", + description: + "A pill, row, and toggle projected onto a shared frame. Separate elements follow a common set of rules.", + category: "Interfaces", + illustration: , + }, + ...systemStudies, + ...flatCalStudies, + ...interfaceDetailStudies, + ...flatSystemStudies, + ...flatObjectStudies, + ...serviceStudies, + ...popularServiceStudies, + ...calStudies, + ...networkStudies, + ...objectStudies, + ...stateStudies, + ...notFoundArchitectureStudies, + ...notFoundGeometryStudies, + { + id: "404-reference", + code: "E00", + title: "The missing corner", + description: + "A vacant module in an otherwise complete construction. Absence, described by the edges around it.", + category: "Empty states", + illustration: , + }, +]; + +// Open the collection with a cross-section of its subjects. Codes and deep links +// stay stable as the drawings evolve. +const opening = ["P00", "C03", "B04", "K06", "I06", "E02"]; + +export const illustrations = [ + ...opening.flatMap((code) => collection.filter((item) => item.code === code)), + ...collection.filter((item) => !opening.includes(item.code)), +]; diff --git a/apps/ui/app/illustration-lab/interface-detail-studies.tsx b/apps/ui/app/plotter/interface-detail-studies.tsx similarity index 99% rename from apps/ui/app/illustration-lab/interface-detail-studies.tsx rename to apps/ui/app/plotter/interface-detail-studies.tsx index 6366d1325e..0e130e588f 100644 --- a/apps/ui/app/illustration-lab/interface-detail-studies.tsx +++ b/apps/ui/app/plotter/interface-detail-studies.tsx @@ -206,7 +206,6 @@ export const interfaceDetailStudies: IllustrationStudy[] = [ "An enlarged corner of browser chrome. A tab, navigation controls, and an open address field reveal the window’s construction while dashed edges continue beyond the drawing.", category: "Interfaces", illustration: , - revision: true, }, { id: "interface-phone-plan", @@ -225,6 +224,5 @@ export const interfaceDetailStudies: IllustrationStudy[] = [ "Two rounded message blocks pair with circular avatars. Shared radius, text inset, and avatar spacing construct the conversation without pointed tails.", category: "Interfaces", illustration: , - revision: true, }, ]; diff --git a/apps/ui/app/plotter/network-studies.tsx b/apps/ui/app/plotter/network-studies.tsx new file mode 100644 index 0000000000..4ce8a496cf --- /dev/null +++ b/apps/ui/app/plotter/network-studies.tsx @@ -0,0 +1,111 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; +import type { IllustrationStudy } from "./study-types"; + +function RegistrationMarks() { + return ( + + + + + ); +} + +function BranchingWorkflowStudy() { + const routes = + "M132 224H216A24 24 0 0 0 240 200V152A24 24 0 0 1 264 128H420 M132 224H420 M132 224H216A24 24 0 0 1 240 248V296A24 24 0 0 0 264 320H420"; + + return ( + + + {[168, 192, 216, 240, 264, 288, 312, 336, 360, 384].map((x) => ( + + ))} + {[152, 176, 200, 248, 272, 296].map((y) => ( + + ))} + + + + + + + {/* Parallel outlines make the route itself the constructed form. */} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + R24 + + + w = 12 + + + 96 + + + 96 + + + 288 + + + in + + + 01 + + + 02 + + + 03 + + + + + ); +} + +export const networkStudies: IllustrationStudy[] = [ + { + id: "workflow-branch", + code: "W02", + title: "A route, constructed", + description: + "One continuous route, three outlets. Parallel outlines reveal its width, repeated bend radius, and equal branch spacing.", + category: "Systems", + illustration: , + }, +]; diff --git a/apps/ui/app/illustration-lab/not-found-architecture-studies.tsx b/apps/ui/app/plotter/not-found-architecture-studies.tsx similarity index 55% rename from apps/ui/app/illustration-lab/not-found-architecture-studies.tsx rename to apps/ui/app/plotter/not-found-architecture-studies.tsx index d884f1b701..e34bce8d1c 100644 --- a/apps/ui/app/illustration-lab/not-found-architecture-studies.tsx +++ b/apps/ui/app/plotter/not-found-architecture-studies.tsx @@ -87,77 +87,6 @@ function UnopenedDoor() { ); } -function VacantSheet() { - return ( - - - - - - - - - - - - - - - - - - - - - - - - 404 - - - - - - - - - - - - - - - - - - No content - - - 01 - - - - - - - - - ); -} - export const notFoundArchitectureStudies: IllustrationStudy[] = [ { id: "404-doorway", @@ -165,7 +94,7 @@ export const notFoundArchitectureStudies: IllustrationStudy[] = [ title: "An empty threshold", description: "A precise architectural threshold opens onto empty space, suggesting a destination that was never built.", - category: "404", + category: "Empty states", illustration: , }, { @@ -174,17 +103,7 @@ export const notFoundArchitectureStudies: IllustrationStudy[] = [ title: "An interrupted path", description: "A measured route stops at a missing link. Its paired outlines and unresolved destination make the interruption the central gesture.", - category: "404", + category: "Empty states", illustration: , - revision: true, - }, - { - id: "404-vacant-sheet", - code: "E03", - title: "A vacant sheet", - description: - "A carefully measured document preserves its frame and registration marks while the page itself remains absent.", - category: "404", - illustration: , }, ]; diff --git a/apps/ui/app/illustration-lab/not-found-blueprint.tsx b/apps/ui/app/plotter/not-found-blueprint.tsx similarity index 100% rename from apps/ui/app/illustration-lab/not-found-blueprint.tsx rename to apps/ui/app/plotter/not-found-blueprint.tsx diff --git a/apps/ui/app/illustration-lab/not-found-geometry-studies.tsx b/apps/ui/app/plotter/not-found-geometry-studies.tsx similarity index 51% rename from apps/ui/app/illustration-lab/not-found-geometry-studies.tsx rename to apps/ui/app/plotter/not-found-geometry-studies.tsx index b2dc1b766e..bbbb1d70e5 100644 --- a/apps/ui/app/illustration-lab/not-found-geometry-studies.tsx +++ b/apps/ui/app/plotter/not-found-geometry-studies.tsx @@ -1,76 +1,6 @@ import { BlueprintDrawing } from "./blueprint-drawing"; import type { IllustrationStudy } from "./study-types"; -function OffTheGrid() { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - u - - - Δx - - - Δy - - - ∅ - - - Outside the system - - - 01 → 00 - - - - ); -} - function UnbuiltArch() { return ( @@ -136,22 +66,13 @@ function UnbuiltArch() { } export const notFoundGeometryStudies: IllustrationStudy[] = [ - { - id: "404-off-grid", - code: "E05", - title: "Off the grid", - description: - "One displaced form leaves an empty address in the construction grid, suggesting a page outside the system.", - category: "404", - illustration: , - }, { id: "404-unbuilt-arch", code: "E06", title: "An unbuilt arch", description: "An arch awaits its central keystone, turning the missing page into an unfinished architectural connection.", - category: "404", + category: "Empty states", illustration: , }, ]; diff --git a/apps/ui/app/plotter/object-studies.tsx b/apps/ui/app/plotter/object-studies.tsx new file mode 100644 index 0000000000..d5b1454e30 --- /dev/null +++ b/apps/ui/app/plotter/object-studies.tsx @@ -0,0 +1,140 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; +import type { IllustrationStudy } from "./study-types"; + +function LetterAnatomy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + x-height + + + Stem + + + r + + + Overshoot + + + Anatomy of a letter + + + + ); +} + +function MarkInMaking() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 90° + + + R + + + 1u + + + Terminal + + + A mark in the making + + + + ); +} + +export const objectStudies: IllustrationStudy[] = [ + { + id: "type-letter", + code: "T01", + title: "Anatomy of a letter", + description: + "A custom lowercase a exposes its circular bowl, counter, stem, and optical overshoot.", + category: "Foundations", + illustration: , + }, + { + id: "type-monogram", + code: "T02", + title: "A mark in the making", + description: + "Two concentric arcs and a deliberate opening establish the logic of a simple geometric C.", + category: "Foundations", + illustration: , + }, +]; diff --git a/apps/ui/app/illustration-lab/original-not-found-blueprint.tsx b/apps/ui/app/plotter/original-not-found-blueprint.tsx similarity index 100% rename from apps/ui/app/illustration-lab/original-not-found-blueprint.tsx rename to apps/ui/app/plotter/original-not-found-blueprint.tsx diff --git a/apps/ui/app/illustration-lab/original-particles-blueprint.tsx b/apps/ui/app/plotter/original-particles-blueprint.tsx similarity index 100% rename from apps/ui/app/illustration-lab/original-particles-blueprint.tsx rename to apps/ui/app/plotter/original-particles-blueprint.tsx diff --git a/apps/ui/app/plotter/page.tsx b/apps/ui/app/plotter/page.tsx new file mode 100644 index 0000000000..5ca7c4919d --- /dev/null +++ b/apps/ui/app/plotter/page.tsx @@ -0,0 +1,54 @@ +import { SiteFooter } from "@coss/ui/shared/site-footer"; +import type { Metadata } from "next"; +import { Suspense } from "react"; +import { IllustrationGallery } from "./illustration-gallery"; +import { illustrations } from "./illustrations"; + +export const metadata: Metadata = { + title: "Plotter illustrations – coss ui", + description: + "A collection of monochrome illustrations exploring geometry, interfaces, and the systems behind familiar things.", + robots: { index: false, follow: false }, +}; + +export default function Page() { + return ( + <> +
+
+

+ Plotter illustrations +

+

+ Simple forms. Fine lines. A collection of monochrome illustrations + exploring geometry, interfaces, and the systems behind familiar + things. +

+
+ Loading illustrations…

+ } + > + +
+
+
+

Draw in the same direction.

+

+ Plotter is also a reusable skill, with visual references and + guidance for creating new illustrations in this style. +

+
+ + Explore the Plotter skill + +
+
+ + + ); +} diff --git a/apps/ui/app/illustration-lab/particles-blueprint.tsx b/apps/ui/app/plotter/particles-blueprint.tsx similarity index 100% rename from apps/ui/app/illustration-lab/particles-blueprint.tsx rename to apps/ui/app/plotter/particles-blueprint.tsx diff --git a/apps/ui/app/illustration-lab/popular-service-studies.tsx b/apps/ui/app/plotter/popular-service-studies.tsx similarity index 99% rename from apps/ui/app/illustration-lab/popular-service-studies.tsx rename to apps/ui/app/plotter/popular-service-studies.tsx index 64e46cc228..514ce953fe 100644 --- a/apps/ui/app/illustration-lab/popular-service-studies.tsx +++ b/apps/ui/app/plotter/popular-service-studies.tsx @@ -361,6 +361,5 @@ export const popularServiceStudies: IllustrationStudy[] = [ "Two Postgres table fragments connect a user’s primary key to project ownership. A single measured route expresses the relational foundation beneath an application.", category: "Services", illustration: , - revision: true, }, ]; diff --git a/apps/ui/app/illustration-lab/primitives-blueprint.tsx b/apps/ui/app/plotter/primitives-blueprint.tsx similarity index 100% rename from apps/ui/app/illustration-lab/primitives-blueprint.tsx rename to apps/ui/app/plotter/primitives-blueprint.tsx diff --git a/apps/ui/app/illustration-lab/service-studies.tsx b/apps/ui/app/plotter/service-studies.tsx similarity index 99% rename from apps/ui/app/illustration-lab/service-studies.tsx rename to apps/ui/app/plotter/service-studies.tsx index 404d2ffe0b..8c89158d01 100644 --- a/apps/ui/app/illustration-lab/service-studies.tsx +++ b/apps/ui/app/plotter/service-studies.tsx @@ -430,7 +430,6 @@ export const serviceStudies: IllustrationStudy[] = [ "An edge cache between visitor and origin: a hit returns locally, while a miss continues outward. A study of Cloudflare’s CDN role.", category: "Services", illustration: , - revision: true, }, { id: "service-vercel-deploy", @@ -440,7 +439,6 @@ export const serviceStudies: IllustrationStudy[] = [ "Preview 02 is selected for production. Matching outlines identify the same source version; promotion rebuilds it for the production environment.", category: "Services", illustration: , - revision: true, }, { id: "service-stripe-settlement", diff --git a/apps/ui/app/illustration-lab/state-studies.tsx b/apps/ui/app/plotter/state-studies.tsx similarity index 50% rename from apps/ui/app/illustration-lab/state-studies.tsx rename to apps/ui/app/plotter/state-studies.tsx index f5c4f5f2ee..bbce7c8750 100644 --- a/apps/ui/app/illustration-lab/state-studies.tsx +++ b/apps/ui/app/plotter/state-studies.tsx @@ -51,62 +51,6 @@ function MissingDestination() { ); } -function CompleteAssembly() { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Aligned - - - 1 : 1 - - - All in place - - - 90° - - - - ); -} - export const stateStudies: IllustrationStudy[] = [ { id: "state-missing-page", @@ -114,16 +58,7 @@ export const stateStudies: IllustrationStudy[] = [ title: "The missing destination", description: "A clear route reaches the outline of a page that isn’t there. The absence belongs to the destination.", - category: "States", + category: "Empty states", illustration: , }, - { - id: "state-complete", - code: "S02", - title: "Everything in place", - description: - "Three fitted panels become one complete assembly, with a precisely constructed check marking the finished work.", - category: "States", - illustration: , - }, ]; diff --git a/apps/ui/app/illustration-lab/study-types.ts b/apps/ui/app/plotter/study-types.ts similarity index 50% rename from apps/ui/app/illustration-lab/study-types.ts rename to apps/ui/app/plotter/study-types.ts index 0f7471a348..15072cb929 100644 --- a/apps/ui/app/illustration-lab/study-types.ts +++ b/apps/ui/app/plotter/study-types.ts @@ -1,18 +1,13 @@ import type { ReactNode } from "react"; export type StudyCategory = - | "Cal.com" + | "Foundations" | "Interfaces" + | "Scheduling" | "Services" - | "Documents" - | "Workflows" + | "Systems" | "Objects" - | "Data" - | "Typography" - | "States" - | "404" - | "Primitives" - | "Particles"; + | "Empty states"; export type IllustrationStudy = { id: string; @@ -21,9 +16,4 @@ export type IllustrationStudy = { description: string; category: StudyCategory; illustration: ReactNode; - baseline?: boolean; - selected?: boolean; - revision?: boolean; - review?: "reference" | "outside" | "partial"; - reviewNote?: string; }; diff --git a/apps/ui/app/illustration-lab/system-studies.tsx b/apps/ui/app/plotter/system-studies.tsx similarity index 98% rename from apps/ui/app/illustration-lab/system-studies.tsx rename to apps/ui/app/plotter/system-studies.tsx index 549b3b7657..c1231b95c2 100644 --- a/apps/ui/app/illustration-lab/system-studies.tsx +++ b/apps/ui/app/plotter/system-studies.tsx @@ -400,7 +400,7 @@ export const systemStudies: IllustrationStudy[] = [ title: "Tangency", description: "A circle meets two straightedges. The contact points make a simple construction rule the central gesture.", - category: "Primitives", + category: "Foundations", illustration: , }, { @@ -409,7 +409,7 @@ export const systemStudies: IllustrationStudy[] = [ title: "The corner rule", description: "One enlarged corner reveals the radius, offset, and tangent points behind a familiar interface shape.", - category: "Primitives", + category: "Foundations", illustration: , }, { @@ -418,7 +418,7 @@ export const systemStudies: IllustrationStudy[] = [ title: "A system of proportions", description: "Four squares grow from one shared unit. A single diagonal connects the scale into a structural rhythm.", - category: "Primitives", + category: "Foundations", illustration: , }, { @@ -427,7 +427,7 @@ export const systemStudies: IllustrationStudy[] = [ title: "An assembly of parts", description: "An icon, a label, and a status pill align into one row, shown as an exploded technical drawing.", - category: "Particles", + category: "Interfaces", illustration: , }, { @@ -436,7 +436,7 @@ export const systemStudies: IllustrationStudy[] = [ title: "A modular tray", description: "Different interface fragments occupy a shared grid, like precisely fitted pieces in an architectural plan.", - category: "Particles", + category: "Interfaces", illustration: , }, { @@ -445,7 +445,7 @@ export const systemStudies: IllustrationStudy[] = [ title: "Recombination", description: "Identity, visibility, and action combine into one compact panel. Each of the three fragments keeps its role in the assembled result.", - category: "Particles", + category: "Interfaces", illustration: , }, ]; diff --git a/apps/ui/next.config.ts b/apps/ui/next.config.ts index 4c63633e3c..0699035781 100644 --- a/apps/ui/next.config.ts +++ b/apps/ui/next.config.ts @@ -7,9 +7,14 @@ const nextConfig: NextConfig = { basePath: process.env.NEXT_PUBLIC_BASE_PATH || "/ui", async redirects() { return [ + { + source: "/illustration-lab", + destination: "/plotter", + permanent: false, + }, { source: "/illustration-lab-prototype", - destination: "/illustration-lab", + destination: "/plotter", permanent: false, }, { diff --git a/apps/ui/skills/plotter/README.md b/apps/ui/skills/plotter/README.md index a552baf6bd..f6352765da 100644 --- a/apps/ui/skills/plotter/README.md +++ b/apps/ui/skills/plotter/README.md @@ -1,6 +1,6 @@ # Plotter -Flat monochrome blueprint illustrations: precise geometry, quiet construction guides, and generous white space. Developed through the coss illustration lab. +Flat monochrome blueprint illustrations: precise geometry, quiet construction guides, and generous white space. Developed for coss UI and collected in the Plotter illustrations showcase. `SKILL.md` contains the workflow; `references/` contains the art direction, feedback, implementation guidance, and visual anchors. The current coss selections are P00 (main), C03 (Particles), and E02 (404). @@ -10,4 +10,4 @@ Copy this complete directory into your agent’s skills directory, preserving it Example: “Use $plotter to draw a flat scheduling illustration based on the Primitives and Particles references. Show two availability ranges meeting at one shared slot.” -The source lab is at `/ui/illustration-lab`, or [`../../app/illustration-lab`](../../app/illustration-lab) in the repository. The page is available by direct URL and is not linked from site navigation. +The showcase is at `/ui/plotter`, or [`../../app/plotter`](../../app/plotter) in the repository. Browse by category and open an enlarged preview. The page is available by direct URL and is not linked from site navigation. diff --git a/apps/ui/skills/plotter/SKILL.md b/apps/ui/skills/plotter/SKILL.md index 5a5f33e7e2..c2559026a6 100644 --- a/apps/ui/skills/plotter/SKILL.md +++ b/apps/ui/skills/plotter/SKILL.md @@ -11,7 +11,7 @@ Build a precise planar composition that communicates a construction relationship Before drawing, inspect [Primitives](references/primitives.png) and [Particles](references/particles.png). These are the strongest user-named visual anchors. Match their composition, hierarchy, and restraint; the screenshots include page context, which is not a template to copy wholesale. For service concepts, also inspect [Linear](references/linear.png), which the user explicitly loved. -The current coss selections are P00 for the main page, [C03 for Particles](references/particles-recombination.png), and [E02 for the 404](references/not-found-interrupted-path.png). The lab keeps independent snapshots of these drawings. Inspect the host page’s component when refining a production integration; the original Particles drawing remains a style anchor. +The current coss selections are P00 for the main page, [C03 for Particles](references/particles-recombination.png), and [E02 for the 404](references/not-found-interrupted-path.png). The Plotter showcase keeps independent snapshots of these drawings. Inspect the host page’s component when refining a production integration; the original Particles drawing remains a style anchor. Read [direction and feedback](references/direction.md) for the current evidence, relevant subject guidance, and unresolved choices. User feedback in the current conversation takes precedence. A study’s presence in the gallery does not make it an approved reference. @@ -23,7 +23,7 @@ Read [direction and feedback](references/direction.md) for the current evidence, - Start from a small geometric vocabulary: circles, rectangles, lines, arcs, slots, and modest UI fragments. Choose one central relationship before adding detail. A complete screen mockup or a large pictogram with technical marks is a poor starting point for this direction. - Give every guide a job. Show actual tangency, radius, equal intervals, alignment, a shared baseline, or a connection. Let grids fade where they stop explaining the form. Avoid filling empty areas with arbitrary drafting marks. - Name what geometry alone cannot communicate. A week needs weekday or time cues; overlapping availability needs a clear association with people or schedules. Prefer short integrated labels over extra icons or a caption that must explain the whole image. -- For the coss main and Particles pages, preserve the split layout: left-aligned headline and description; a single composition on the right. The main page has CTAs beneath its copy; Particles has its search/filter directly beneath the copy, with its original filtering behavior. Treat the 404 separately: illustration above the centered heading, description, and CTA. Keep these layouts in their pages; do not introduce a shared page-hero component. Elsewhere, fit the host layout unless the user asks to restructure it. When revising a standalone study, keep the existing comparison surface. +- For the coss main and Particles pages, preserve the split layout: left-aligned headline and description; a single composition on the right. The main page has CTAs beneath its copy; Particles has its search/filter directly beneath the copy, with its original filtering behavior. Treat the 404 separately: illustration above the centered heading, description, and CTA. Keep these layouts in their pages; do not introduce a shared page-hero component. Elsewhere, fit the host layout unless the user asks to restructure it. When revising a standalone illustration, inspect it in the showcase grid and enlarged preview. ## Implement and inspect diff --git a/apps/ui/skills/plotter/references/direction.md b/apps/ui/skills/plotter/references/direction.md index 9af9c27e44..ac2eec1946 100644 --- a/apps/ui/skills/plotter/references/direction.md +++ b/apps/ui/skills/plotter/references/direction.md @@ -8,11 +8,11 @@ The user explicitly called the original Primitives and Particles illustrations t The user also explicitly said they “love the Linear example” (B04). Use [Linear](linear.png) as a service-concept reference: issue units aligned to a cycle and release. This endorses that example, not every service drawing. -The gallery gives the entire Primitives/Particles categories reference status. That metadata is broader than the user’s clearest endorsement of the original two drawings. +The former lab assigned reference status to the entire Primitives/Particles categories. That metadata exceeded the user’s clearest endorsement of the original two drawings and is not an approval source. ## Production selections -The user explicitly selected **P00** for the main page, [**C03** for Particles](particles-recombination.png), and [**E02** for the 404](not-found-interrupted-path.png). These are current implementation decisions. The original Primitives/Particles references and loved Linear study still guide the broader style. The lab owns independent snapshots of the selected drawings, so it can ship separately from the production integration. Original C00 and E00 remain separate historical studies. +The user explicitly selected **P00** for the main page, [**C03** for Particles](particles-recombination.png), and [**E02** for the 404](not-found-interrupted-path.png). These are current implementation decisions. The original Primitives/Particles references and loved Linear study still guide the broader style. The Plotter showcase owns independent snapshots of the selected drawings, so it can ship separately from the production integration. Original C00 and E00 remain separate examples. ## Review evidence @@ -20,8 +20,8 @@ The user explicitly selected **P00** for the main page, [**C03** for Particles]( | --- | --- | | C03: requests a third element so the composition becomes “A + B + C” | Revise C03 in place. Added an Open action alongside identity and visibility, repeated as the third row of the assembled panel; three equal rows measure 3u. This is a requested experiment, awaiting review. | | C03 A + B + C: “it’s not vertical aligned” and the underlying grid “looks bad or it’s sparse” | Optically center the composition within its SVG and use continuous construction guides aligned to component edges and row boundaries. This is the implementation response to the critique, awaiting review. | -| User asks to hide the home and Particles illustrations on smaller screens | Show those two hero drawings only from the existing desktop split-layout breakpoint (`lg`, 1024px). This does not change the 404 or standalone lab studies. | -| User rejects a shared page-hero component and asks for the 404 heading to stay centered with the illustration above | Keep main and Particles split layouts in their pages. Use an illustration-first centered stack for the 404, including its lab hero previews. | +| User asks to hide the home and Particles illustrations on smaller screens | Show those two hero drawings only from the existing desktop split-layout breakpoint (`lg`, 1024px). This does not change the 404 or standalone showcase illustrations. | +| User rejects a shared page-hero component and asks for the 404 heading to stay centered with the illustration above | Keep main and Particles split layouts in their pages. Use an illustration-first centered stack for the production 404 page. The showcase presents the drawing on its own. | | User asks to restore the previous Particles page, then explicitly confirms “Keep C03; restore search beneath heading” and references PR #895 for headings | Keep C03 on the right, restore search beneath the left-aligned heading/description, and remove the added hero CTAs. Use PR #895’s fluid 36–64px heading scale, leading-none, and GEOM50 on the main and Particles pages. | | User rejected K01, K04, K05, I01, I02, D01, D02, W01, O01, O02, A01, A02, S02; W02 partly | Explicit group feedback: these went beyond the desired set. The stated general boundary was flat 2D electrical/architectural blueprints, with no 3D or skewed views. | | K06: not clear it is a week | Revised with Mon–Sun, hourly rows, and a labeled Thursday interval. A seven-column grid alone did not establish the subject. | @@ -46,6 +46,12 @@ The user explicitly selected **P00** for the main page, [**C03** for Particles]( The earlier K06/K08/I03/W02/W03 revision pass is captured in coss commit `285493c9` on `codex/blueprint-illustration-studies`. The preceding flat-direction pass is `e9077a93`. Preserve this evidence if gallery metadata later changes. +## Showcase curation + +The user subsequently asked to remove unwanted and discarded examples and turn the lab into a showcase named **Plotter illustrations**. The collection now contains 39 illustrations at `/ui/plotter`, with category filters and enlarged previews. Shortlists, review controls, and page-hero mockups have been removed. + +The 13 explicitly rejected entries (K01, K04, K05, I01, I02, D01, D02, W01, O01, O02, A01, A02, S02) and the assistant-set-aside E03/E05 were removed from the drawing code. E04 was already absent. W02 stays because its revised planar route received the later collective “much better” response. Retained revisions of other critiqued examples remain subject to the evidence above: curation is not individual approval or promotion to a style anchor. + ## Expanded subject brief The user explicitly requested a keyboard, browser window, table lamp, folder, smartphone, chat, and service concepts for Cloudflare, Vercel, Stripe, and Linear, then asked for more popular-service examples. These are requested exploration areas; only the specific feedback above establishes preference or approval. The expansion does not rehabilitate earlier rejected object drawings. Render familiar objects as flat plans or elevations with meaningful construction relationships. Preserve the established no-perspective rule. diff --git a/apps/ui/skills/plotter/references/implementation.md b/apps/ui/skills/plotter/references/implementation.md index 50704bb299..9f1c94c89e 100644 --- a/apps/ui/skills/plotter/references/implementation.md +++ b/apps/ui/skills/plotter/references/implementation.md @@ -17,26 +17,26 @@ Find the checkout from the current workspace; the skill does not require a parti | Purpose | Repository-relative source | | --- | --- | -| Shared viewBox, typography, strokes | `apps/ui/app/illustration-lab/blueprint-drawing.tsx` | -| Primary geometry reference | `apps/ui/app/illustration-lab/primitives-blueprint.tsx` | -| Selected Particles composition (C03) | `apps/ui/app/illustration-lab/particles-blueprint.tsx` | -| Selected 404 composition (E02) | `apps/ui/app/illustration-lab/not-found-blueprint.tsx` | -| Original Particles style anchor (C00) | `apps/ui/app/illustration-lab/original-particles-blueprint.tsx` | +| Shared viewBox, typography, strokes | `apps/ui/app/plotter/blueprint-drawing.tsx` | +| Primary geometry reference | `apps/ui/app/plotter/primitives-blueprint.tsx` | +| Selected Particles composition (C03) | `apps/ui/app/plotter/particles-blueprint.tsx` | +| Selected 404 composition (E02) | `apps/ui/app/plotter/not-found-blueprint.tsx` | +| Original Particles style anchor (C00) | `apps/ui/app/plotter/original-particles-blueprint.tsx` | | Main integration target | `apps/ui/app/page.tsx` | | Particles integration target | `apps/ui/app/particles/page.tsx` | | 404 integration target | `apps/ui/app/not-found.tsx` | -| Review gallery and study IDs | `apps/ui/app/illustration-lab/` | -| Revision feedback metadata | `apps/ui/app/illustration-lab/study-direction.ts` | +| Showcase and illustration IDs | `apps/ui/app/plotter/` | +| Feedback evidence | `apps/ui/skills/plotter/references/direction.md` | | Typography rules | `packages/ui/src/fonts/font-guide.md` | -The lab owns its drawings and wrapper independently of the production integration in PR #912. Use the lab’s `BlueprintDrawing` wrapper for compatible studies. It supplies monochrome foreground, Cal Sans, automatic optical sizing, and GEOM25. Preserve semantic surface masks such as `var(--color-sidebar)` and inspect them against the actual host background. Static illustration components do not need client-side state. +The showcase owns its drawings and wrapper independently of the production integration in PR #912. Use its `BlueprintDrawing` wrapper for compatible illustrations. It supplies monochrome foreground, Cal Sans, automatic optical sizing, and GEOM25. Preserve semantic surface masks such as `var(--color-sidebar)` and inspect them against the actual host background. Static illustration components do not need client-side state. -Keep page layouts local, as explicitly requested by the user. Do not recreate a shared page-hero component. The lab’s hero mode should use the centered illustration-first arrangement for 404 studies and the split layout for other categories. +Keep production page layouts local, as explicitly requested by the user. Do not recreate a shared page-hero component. The showcase displays standalone drawings in a gallery with enlarged previews. The selected production integration for Main, Particles, and Chat uses the heading treatment from [PR #895](https://github.com/cosscom/ui/pull/895): `clamp(2.25rem, 1.5rem + 2.6vw, 4rem)`, line-height 1, and GEOM50, with normal letter spacing and automatic optical sizing. These settings are scoped to those pages. Particles keeps C03 on the right and its search/filter directly beneath the left-aligned description, rather than adding hero CTAs. Keep the project’s typography: no added letter-spacing, no synthesized bold/italic, one GEOM value per role, and typeset punctuation. Read the current guide for details instead of copying font configuration into every drawing. -The unlinked comparison route is `/ui/illustration-lab?variant=&view=drawing` (or `view=hero`). Original IDs are `primitives-reference` and `particles-reference`; recent IDs include `cal-week-grid`, `cal-availability-plan`, `interface-spacing-plan`, `workflow-switch-plan`, and `workflow-branch`. Inspect current app configuration for the running address. Use bundled references when this checkout is unavailable; do not assume localhost is running. The lab has no navigation or sitemap link and opts out of indexing. +The unlinked showcase route is `/ui/plotter`; `?variant=` opens an enlarged preview. Category filters organize the collection. Original IDs are `primitives-reference` and `particles-reference`; recent IDs include `cal-week-grid`, `cal-availability-plan`, `interface-spacing-plan`, `workflow-switch-plan`, and `workflow-branch`. The old lab and prototype routes redirect here with their query parameters. Inspect current app configuration for the running address. Use bundled references when this checkout is unavailable; do not assume localhost is running. The showcase has no navigation or sitemap link and opts out of indexing. -Use focused repository formatting/type checks appropriate to the edited files, plus browser inspection. Keep unselected drawings on the lab page until the user chooses a production integration. When revising a production drawing, inspect its current source and update the corresponding lab snapshot explicitly when both should show the same revision. Keep lab imports local so either addition can ship independently. This skill does not authorize publishing, pushing, or creating a PR by itself. +Use focused repository formatting/type checks appropriate to the edited files, plus browser inspection. Keep standalone illustrations in the showcase until the user chooses a production integration. Removed drawings remain documented in the feedback ledger rather than retained as hidden gallery entries. When revising a production drawing, inspect its current source and update the corresponding showcase snapshot explicitly when both should show the same revision. Keep showcase imports local so either addition can ship independently. This skill does not authorize publishing, pushing, or creating a PR by itself. From 65c7269fb8bf2ae2663dfa8db036817459fc07cc Mon Sep 17 00:00:00 2001 From: pasqualevitiello Date: Sun, 25 Oct 2026 08:09:25 +0100 Subject: [PATCH 04/15] feat(ui): showcase continuous Plotter construction animation --- apps/ui/app/plotter/illustration-gallery.tsx | 2 +- apps/ui/app/plotter/page.tsx | 66 ++++- .../primitives-construction-motion.tsx | 67 +++++ .../app/plotter/primitives-morph-details.tsx | 276 ++++++++++++++++++ .../plotter/primitives-morph-transitions.tsx | 140 +++++++++ apps/ui/app/plotter/primitives-morph.tsx | 264 +++++++++++++++++ .../app/plotter/primitives-motion-preview.tsx | 71 +++++ .../app/plotter/primitives-motion.module.css | 13 + .../ui/skills/plotter/references/direction.md | 14 +- 9 files changed, 902 insertions(+), 11 deletions(-) create mode 100644 apps/ui/app/plotter/primitives-construction-motion.tsx create mode 100644 apps/ui/app/plotter/primitives-morph-details.tsx create mode 100644 apps/ui/app/plotter/primitives-morph-transitions.tsx create mode 100644 apps/ui/app/plotter/primitives-morph.tsx create mode 100644 apps/ui/app/plotter/primitives-motion-preview.tsx create mode 100644 apps/ui/app/plotter/primitives-motion.module.css diff --git a/apps/ui/app/plotter/illustration-gallery.tsx b/apps/ui/app/plotter/illustration-gallery.tsx index c3fc30f51f..570e66545a 100644 --- a/apps/ui/app/plotter/illustration-gallery.tsx +++ b/apps/ui/app/plotter/illustration-gallery.tsx @@ -127,7 +127,7 @@ export function IllustrationGallery({
-

{item.title}

+

{item.title}

{item.category}

diff --git a/apps/ui/app/plotter/page.tsx b/apps/ui/app/plotter/page.tsx index 5ca7c4919d..1f58c9192e 100644 --- a/apps/ui/app/plotter/page.tsx +++ b/apps/ui/app/plotter/page.tsx @@ -3,11 +3,13 @@ import type { Metadata } from "next"; import { Suspense } from "react"; import { IllustrationGallery } from "./illustration-gallery"; import { illustrations } from "./illustrations"; +import { PrimitivesBlueprint } from "./primitives-blueprint"; +import { PrimitivesMotionPreview } from "./primitives-motion-preview"; export const metadata: Metadata = { title: "Plotter illustrations – coss ui", description: - "A collection of monochrome illustrations exploring geometry, interfaces, and the systems behind familiar things.", + "A collection of monochrome illustrations and animations exploring geometry, interfaces, and the systems behind familiar things.", robots: { index: false, follow: false }, }; @@ -21,17 +23,63 @@ export default function Page() {

Simple forms. Fine lines. A collection of monochrome illustrations - exploring geometry, interfaces, and the systems behind familiar - things. + and animations exploring geometry, interfaces, and the systems + behind familiar things.

- Loading illustrations…

- } +
- - +
+

+ Animations +

+

1 example

+
+
+
+

Foundations / M01

+

+ Changing geometry +

+

+ Four forms, one continuous loop. Each shape reveals its own + construction, with measurements that follow the geometry. +

+
+
+ + + +
+
+
+
+

+ Illustrations +

+ + Loading illustrations… +

+ } + > + +
+

Draw in the same direction.

diff --git a/apps/ui/app/plotter/primitives-construction-motion.tsx b/apps/ui/app/plotter/primitives-construction-motion.tsx new file mode 100644 index 0000000000..2069282545 --- /dev/null +++ b/apps/ui/app/plotter/primitives-construction-motion.tsx @@ -0,0 +1,67 @@ +import type { ReactNode } from "react"; + +// Construction finishes during the first second of each 2.4s hold. Reset only +// after its parent is hidden, so subsequent cycles never flash the final state. +export function ConstructionMotion({ + attributeName, + delay = 0.12, + duration = 0.68, + from, + stage, + to, + type, +}: { + attributeName: string; + delay?: number; + duration?: number; + from: string | number; + stage: number; + to: string | number; + type?: "rotate"; +}) { + const timing = { + attributeName, + begin: `${stage * 3.2}s`, + calcMode: "spline" as const, + dur: "12.8s", + keySplines: "0 0 1 1;0.22 0 0.12 1;0 0 1 1;0 0 1 1;0 0 1 1", + keyTimes: [ + 0, + delay / 12.8, + (delay + duration) / 12.8, + 2.45 / 12.8, + 2.5 / 12.8, + 1, + ].join(";"), + repeatCount: "indefinite", + values: [from, from, to, to, from, from].join(";"), + }; + + return type ? ( + + ) : ( + + ); +} + +export function ConstructionAnnotation({ + children, + stage, +}: { + children: ReactNode; + stage: number; +}) { + return ( + + + {children} + + ); +} diff --git a/apps/ui/app/plotter/primitives-morph-details.tsx b/apps/ui/app/plotter/primitives-morph-details.tsx new file mode 100644 index 0000000000..2f93d96851 --- /dev/null +++ b/apps/ui/app/plotter/primitives-morph-details.tsx @@ -0,0 +1,276 @@ +import { + ConstructionAnnotation, + ConstructionMotion, +} from "./primitives-construction-motion"; + +export const morphDetailIds = [ + "circle", + "capsule", + "rounded", + "chamfer", +] as const; + +export function MorphDetails({ + hatchId, + stage, +}: { + hatchId: string; + stage: (typeof morphDetailIds)[number]; +}) { + if (stage === "circle") { + const clipId = `${hatchId}-sector`; + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + r = 3u + + + 45° + + + 01 / Sector + + + + + ); + } + + if (stage === "capsule") { + return ( + + + {[204, 364].map((center) => ( + + + + + ))} + + + + {[204, 364].map((center) => ( + + ))} + + + + + + 2u + + + + + + + + + 4u + + + 02 / Tangency + + + + + ); + } + + if (stage === "rounded") { + return ( + + {/* A 1u inset of the R1u outline produces a sharp 4u square. */} + + + + + + + + + + + + + + + + + + + + + + 1u + + + 4u + + + 03 / Offset + + + + + ); + } + + return ( + + {[ + { area: "M164 144V84H224Z", cut: "M152 156L236 72" }, + { area: "M344 84H404V144Z", cut: "M332 72L416 156" }, + { area: "M404 264V324H344Z", cut: "M416 252L332 336" }, + { area: "M224 324H164V264Z", cut: "M236 336L152 252" }, + ].map(({ area, cut }, index) => ( + + + + + + + + + ))} + + + + + + + + + + + + + 1.5u + + + 1.5u + + + 3u + + + 04 / Chamfer + + + + + ); +} diff --git a/apps/ui/app/plotter/primitives-morph-transitions.tsx b/apps/ui/app/plotter/primitives-morph-transitions.tsx new file mode 100644 index 0000000000..293e511089 --- /dev/null +++ b/apps/ui/app/plotter/primitives-morph-transitions.tsx @@ -0,0 +1,140 @@ +type Timing = { start: number; end: number }; + +const duration = 12.8; +const circleToCapsule = { start: 2.4, end: 3.2 }; +const capsuleToRounded = { start: 5.6, end: 6.4 }; + +function TransitionVisibility({ start, end }: Timing) { + return ( + time / duration) + .join(";")} + repeatCount="indefinite" + values="0;0;1;1;0;0" + /> + ); +} + +function TransitionMotion({ + attributeName, + from, + to, + start, + end, +}: Timing & { + attributeName: string; + from: string | number; + to: string | number; +}) { + return ( + time / duration) + .join(";")} + repeatCount="indefinite" + values={[from, from, to, to, from, from].join(";")} + /> + ); +} + +const corners = [ + { id: "top-right", x: 1, y: -1 }, + { id: "bottom-right", x: 1, y: 1 }, + { id: "bottom-left", x: -1, y: 1 }, + { id: "top-left", x: -1, y: -1 }, +] as const; + +export function PrimitivesMorphTransitions() { + return ( + + + + + {[204, 364].map((center) => ( + + + + + ))} + + + {[204, 364].map((center) => ( + + + + ))} + + + + + + {corners.map(({ id, x, y }) => ( + + + + + ))} + + + {corners.map(({ id, x, y }) => ( + + + + ))} + + + + ); +} diff --git a/apps/ui/app/plotter/primitives-morph.tsx b/apps/ui/app/plotter/primitives-morph.tsx new file mode 100644 index 0000000000..11e95c57cd --- /dev/null +++ b/apps/ui/app/plotter/primitives-morph.tsx @@ -0,0 +1,264 @@ +"use client"; + +import { useEffect, useId, useRef } from "react"; +import { BlueprintDrawing } from "./blueprint-drawing"; +import { MorphDetails, morphDetailIds } from "./primitives-morph-details"; +import { PrimitivesMorphTransitions } from "./primitives-morph-transitions"; +import styles from "./primitives-motion.module.css"; + +type Shape = { width: number; height: number; corner: number; bevel?: boolean }; + +// All stages share four straight edges and four cubic corners, including the +// circle’s zero-length edges. Matching topology keeps every point continuous. +function construct({ width, height, corner: r, bevel }: Shape) { + const left = 284 - width / 2; + const right = 284 + width / 2; + const top = 204 - height / 2; + const bottom = 204 + height / 2; + const a = bevel ? r / 3 : r * 0.55228475; + const b = bevel ? r / 3 : 0; + const points = { + "top-left": [left + r, top], + "top-right": [right - r, top], + "right-top": [right, top + r], + "right-bottom": [right, bottom - r], + "bottom-right": [right - r, bottom], + "bottom-left": [left + r, bottom], + "left-bottom": [left, bottom - r], + "left-top": [left, top + r], + } as const; + return { + points, + outline: `M${left + r} ${top} L${right - r} ${top} + C${right - r + a} ${top + b} ${right - b} ${top + r - a} ${right} ${top + r} + L${right} ${bottom - r} + C${right - b} ${bottom - r + a} ${right - r + a} ${bottom - b} ${right - r} ${bottom} + L${left + r} ${bottom} + C${left + r - a} ${bottom - b} ${left + b} ${bottom - r + a} ${left} ${bottom - r} + L${left} ${top + r} + C${left + b} ${top + r - a} ${left + r - a} ${top + b} ${left + r} ${top} Z`, + // One continuous guide owns each extension; measurement marks never retrace it. + guides: `M${left} 38V348 M${right} 38V348 + M${left - 24} ${top}H490 M${left - 24} ${bottom}H490 + M164 348V372 M204 348V372`, + dimensions: `M${left} 46H264 M304 46H${right} + M${left - 4} 50l8-8 M${right - 4} 50l8-8 + M482 ${top}V190 M482 218V${bottom} + M478 ${top + 4}l8-8 M478 ${bottom + 4}l8-8`, + }; +} + +const circle = construct({ width: 240, height: 240, corner: 120 }); +const stages = [ + circle, + construct({ width: 320, height: 160, corner: 80 }), + construct({ width: 240, height: 240, corner: 40 }), + construct({ width: 240, height: 240, corner: 60, bevel: true }), + circle, +]; + +// Every 3.2s: hold the finished form for 2.4s, then morph over 0.8s. +// Repeated frames share the same SVG clock with their construction details. +function Morph({ + attributeName, + values, +}: { + attributeName: string; + values: (string | number)[]; +}) { + return ( + + index === values.length - 1 ? [value] : [value, value], + ) + .join(";")} + /> + ); +} + +const tangentIds = [ + "top-left", + "top-right", + "right-top", + "right-bottom", + "bottom-right", + "bottom-left", + "left-bottom", + "left-top", +] as const; + +const gridX = [124, 164, 204, 244, 284, 324, 364, 404, 444]; +const gridY = [84, 124, 164, 204, 244, 284, 324]; + +export function PrimitivesMorph({ paused }: { paused: boolean }) { + const root = useRef(null); + const widthLabel = useRef(null); + const heightLabel = useRef(null); + const hatchId = useId(); + const guideId = useId(); + const gridMaskId = useId(); + + useEffect(() => { + const svg = root.current?.querySelector("svg"); + if (!svg) return; + if (paused) svg.pauseAnimations(); + else svg.unpauseAnimations(); + + const left = svg.querySelector( + '[data-anchor="left-bottom"]', + ); + const right = svg.querySelector( + '[data-anchor="right-top"]', + ); + const top = svg.querySelector('[data-anchor="top-left"]'); + const bottom = svg.querySelector( + '[data-anchor="bottom-right"]', + ); + let frame = 0; + + function updateMeasurements() { + if (left && right && top && bottom) { + // Read the animated anchor coordinates, so numerical labels use exactly + // the same eased geometry as the SVG, including after pause/resume. + const width = `${Number(((right.cx.animVal.value - left.cx.animVal.value) / 40).toFixed(1))}u`; + const height = `${Number(((bottom.cy.animVal.value - top.cy.animVal.value) / 40).toFixed(1))}u`; + if (widthLabel.current && widthLabel.current.textContent !== width) + widthLabel.current.textContent = width; + if (heightLabel.current && heightLabel.current.textContent !== height) + heightLabel.current.textContent = height; + } + if (!paused) frame = requestAnimationFrame(updateMeasurements); + } + + updateMeasurements(); + return () => cancelAnimationFrame(frame); + }, [paused]); + + return ( +
+ + + + stage.guides)} + /> + + {/* Keep coincident grid lines from darkening the continuous guides. */} + + + + + {/* Origin at the circle center aligns a hatch line with its 45° radius. */} + + + + + + + {gridX.map((x) => ( + + ))} + {gridY.map((y) => ( + + ))} + + + + stage.dimensions)} + /> + + + stage.outline)} + /> + + + {tangentIds.map((id) => ( + + stage.points[id][0])} + /> + stage.points[id][1])} + /> + + ))} + + + {morphDetailIds.map((stage, index) => ( + + + + + ))} + + + 6u + + + 6u + + + u + + + + + + + + +
+ ); +} diff --git a/apps/ui/app/plotter/primitives-motion-preview.tsx b/apps/ui/app/plotter/primitives-motion-preview.tsx new file mode 100644 index 0000000000..586fdb601c --- /dev/null +++ b/apps/ui/app/plotter/primitives-motion-preview.tsx @@ -0,0 +1,71 @@ +"use client"; + +import { PauseIcon, PlayIcon } from "lucide-react"; +import { type ReactNode, useEffect, useRef, useState } from "react"; +import { useMediaQuery } from "@/registry/default/hooks/use-media-query"; +import { Button } from "@/registry/default/ui/button"; +import { PrimitivesMorph } from "./primitives-morph"; +import styles from "./primitives-motion.module.css"; + +export function PrimitivesMotionPreview({ children }: { children: ReactNode }) { + const drawing = useRef(null); + const [live, setLive] = useState(true); + const [paused, setPaused] = useState(false); + const [inView, setInView] = useState(false); + const reducedMotion = useMediaQuery("(prefers-reduced-motion: reduce)"); + + useEffect(() => { + if (!drawing.current) return; + const observer = new IntersectionObserver(([entry]) => { + if (entry) setInView(entry.isIntersecting); + }); + observer.observe(drawing.current); + return () => observer.disconnect(); + }, []); + + return ( +
+
+
{children}
+ {live && !reducedMotion && ( + + )} +
+
+

Continuous loop

+
+ + {live && ( + + )} +
+

+ Reduced motion · Static drawing +

+
+
+ ); +} diff --git a/apps/ui/app/plotter/primitives-motion.module.css b/apps/ui/app/plotter/primitives-motion.module.css new file mode 100644 index 0000000000..65202934bd --- /dev/null +++ b/apps/ui/app/plotter/primitives-motion.module.css @@ -0,0 +1,13 @@ +.construction { + display: none; +} + +@media (prefers-reduced-motion: no-preference) { + .drawing[data-live] .construction { + display: block; + } + + .drawing[data-live] .original { + visibility: hidden; + } +} diff --git a/apps/ui/skills/plotter/references/direction.md b/apps/ui/skills/plotter/references/direction.md index ac2eec1946..a3f84bb062 100644 --- a/apps/ui/skills/plotter/references/direction.md +++ b/apps/ui/skills/plotter/references/direction.md @@ -1,6 +1,6 @@ # Direction and feedback -Evidence captured from the coss UI illustration reviews on 2026-10-24. This is a living art-direction record, not a claim that every study has been approved. +Evidence captured from the coss UI illustration reviews on 2026-10-24–25. This is a living art-direction record, not a claim that every study has been approved. ## Anchors @@ -52,6 +52,18 @@ The user subsequently asked to remove unwanted and discarded examples and turn t The 13 explicitly rejected entries (K01, K04, K05, I01, I02, D01, D02, W01, O01, O02, A01, A02, S02) and the assistant-set-aside E03/E05 were removed from the drawing code. E04 was already absent. W02 stays because its revised planar route received the later collective “much better” response. Retained revisions of other critiqued examples remain subject to the evidence above: curation is not individual approval or promotion to a style anchor. +## Motion studies + +For the P00 experiment, the user requested continuous shape changes, changing measurements, faster morphs, and longer holds. They then clarified that “every shape has its own characteristics”: carrying the same diagonal and bottom-left shaded area through every form felt repetitive. Give each stage a construction relationship specific to its geometry; let its guides and shaded regions enter and leave with that relationship. The user subsequently said “love” about the sector, tangency, inset, and chamfer sequence and requested more visual impact. This endorses that sequence, not mandatory motifs for future drawings. The next experiment animates each construction and carries generating geometry between stages, then rests; that motion refinement is awaiting review. + +The user later rejected both dotted-guide animation experiments (marching dashes and progressive drawing); both were removed. They also disliked the rounded square’s inset breaking apart into chamfer cuts. That bridge was removed while retaining the inset construction within its own stage. The circle’s requested 45° radius must coincide exactly with the first hatch line, rather than run beside it. + +The user flagged gaps and overlapping strokes where measurement extensions meet construction guides, allowing caps or continuous lines. The response uses continuous shared guides, joins the unit-scale extensions, and excludes coincident grid strokes. Give each shared segment one stroke; avoid opacity changes caused by retracing it in separate layers. + +For the capsule, the user requested a 45° upper-left 2u radius and removal of the continuous line connecting the generating-circle centers. Keep that shared axis dashed, including during the circle-to-capsule transition. + +The user then said “love it” and requested a separate animation section. The refined loop is now the first example, M01 / Changing geometry, under Animations on `/ui/plotter`. P00 remains a static illustration in the gallery. Preserve manual pause and reduced-motion behavior; inline examples also pause while offscreen. + ## Expanded subject brief The user explicitly requested a keyboard, browser window, table lamp, folder, smartphone, chat, and service concepts for Cloudflare, Vercel, Stripe, and Linear, then asked for more popular-service examples. These are requested exploration areas; only the specific feedback above establishes preference or approval. The expansion does not rehabilitate earlier rejected object drawings. Render familiar objects as flat plans or elevations with meaningful construction relationships. Preserve the established no-perspective rule. From 7154cc2fda8dbfc1f107c05d2e7302cd93aa339a Mon Sep 17 00:00:00 2001 From: pasqualevitiello Date: Sun, 25 Oct 2026 08:46:12 +0100 Subject: [PATCH 05/15] feat(ui): animate familiar UI forms in Plotter --- apps/ui/app/plotter/page.tsx | 4 +- apps/ui/app/plotter/primitives-button.tsx | 46 +++++++ .../primitives-construction-motion.tsx | 23 ++-- .../app/plotter/primitives-morph-details.tsx | 125 ++++-------------- apps/ui/app/plotter/primitives-morph-style.ts | 7 + .../ui/app/plotter/primitives-morph-timing.ts | 7 + .../plotter/primitives-morph-transitions.tsx | 56 ++++---- apps/ui/app/plotter/primitives-morph.tsx | 80 ++++++----- apps/ui/app/plotter/primitives-switch.tsx | 112 ++++++++++++++++ .../ui/skills/plotter/references/direction.md | 6 + 10 files changed, 300 insertions(+), 166 deletions(-) create mode 100644 apps/ui/app/plotter/primitives-button.tsx create mode 100644 apps/ui/app/plotter/primitives-morph-style.ts create mode 100644 apps/ui/app/plotter/primitives-morph-timing.ts create mode 100644 apps/ui/app/plotter/primitives-switch.tsx diff --git a/apps/ui/app/plotter/page.tsx b/apps/ui/app/plotter/page.tsx index 1f58c9192e..ac66cecf6f 100644 --- a/apps/ui/app/plotter/page.tsx +++ b/apps/ui/app/plotter/page.tsx @@ -48,8 +48,8 @@ export default function Page() { Changing geometry

- Four forms, one continuous loop. Each shape reveals its own - construction, with measurements that follow the geometry. + Avatar, pill, switch, button, card. Five familiar UI elements, + one continuous loop, with measurements that follow the geometry.

diff --git a/apps/ui/app/plotter/primitives-button.tsx b/apps/ui/app/plotter/primitives-button.tsx new file mode 100644 index 0000000000..2a51c30d80 --- /dev/null +++ b/apps/ui/app/plotter/primitives-button.tsx @@ -0,0 +1,46 @@ +import { + ConstructionAnnotation, + ConstructionMotion, +} from "./primitives-construction-motion"; +import { secondaryLine } from "./primitives-morph-style"; + +export function ButtonConstruction() { + return ( + + + + + + + + + + + + + + 1.5u + + + 04 / Button + + + + + ); +} diff --git a/apps/ui/app/plotter/primitives-construction-motion.tsx b/apps/ui/app/plotter/primitives-construction-motion.tsx index 2069282545..f0f8a69754 100644 --- a/apps/ui/app/plotter/primitives-construction-motion.tsx +++ b/apps/ui/app/plotter/primitives-construction-motion.tsx @@ -1,4 +1,9 @@ import type { ReactNode } from "react"; +import { + morphCycleDuration, + morphKeyTimes, + morphStageDuration, +} from "./primitives-morph-timing"; // Construction finishes during the first second of each 2.4s hold. Reset only // after its parent is hidden, so subsequent cycles never flash the final state. @@ -21,18 +26,18 @@ export function ConstructionMotion({ }) { const timing = { attributeName, - begin: `${stage * 3.2}s`, + begin: `${stage * morphStageDuration}s`, calcMode: "spline" as const, - dur: "12.8s", + dur: `${morphCycleDuration}s`, keySplines: "0 0 1 1;0.22 0 0.12 1;0 0 1 1;0 0 1 1;0 0 1 1", - keyTimes: [ + keyTimes: morphKeyTimes( 0, - delay / 12.8, - (delay + duration) / 12.8, - 2.45 / 12.8, - 2.5 / 12.8, - 1, - ].join(";"), + delay, + delay + duration, + 2.45, + 2.5, + morphCycleDuration, + ), repeatCount: "indefinite", values: [from, from, to, to, from, from].join(";"), }; diff --git a/apps/ui/app/plotter/primitives-morph-details.tsx b/apps/ui/app/plotter/primitives-morph-details.tsx index 2f93d96851..6328b30b32 100644 --- a/apps/ui/app/plotter/primitives-morph-details.tsx +++ b/apps/ui/app/plotter/primitives-morph-details.tsx @@ -1,13 +1,17 @@ +import { ButtonConstruction } from "./primitives-button"; import { ConstructionAnnotation, ConstructionMotion, } from "./primitives-construction-motion"; +import { secondaryLine } from "./primitives-morph-style"; +import { SwitchConstruction } from "./primitives-switch"; export const morphDetailIds = [ "circle", "capsule", + "switch", + "button", "rounded", - "chamfer", ] as const; export function MorphDetails({ @@ -43,7 +47,7 @@ export function MorphDetails({ - + - + @@ -84,7 +88,7 @@ export function MorphDetails({ 45° - 01 / Sector + 01 / Avatar @@ -104,24 +108,14 @@ export function MorphDetails({ ))} - + {[204, 364].map((center) => ( - + ))} - + - + @@ -143,7 +137,7 @@ export function MorphDetails({ 4u - 02 / Tangency + 02 / Pill @@ -151,46 +145,49 @@ export function MorphDetails({ ); } + if (stage === "switch") return ; + + if (stage === "button") return ; + if (stage === "rounded") { return ( {/* A 1u inset of the R1u outline produces a sharp 4u square. */} - - + + - + - + - + - - + + @@ -200,7 +197,7 @@ export function MorphDetails({ 4u - 03 / Offset + 05 / Card @@ -208,69 +205,5 @@ export function MorphDetails({ ); } - return ( - - {[ - { area: "M164 144V84H224Z", cut: "M152 156L236 72" }, - { area: "M344 84H404V144Z", cut: "M332 72L416 156" }, - { area: "M404 264V324H344Z", cut: "M416 252L332 336" }, - { area: "M224 324H164V264Z", cut: "M236 336L152 252" }, - ].map(({ area, cut }, index) => ( - - - - - - - - - ))} - - - - - - - - - - - - - 1.5u - - - 1.5u - - - 3u - - - 04 / Chamfer - - - - - ); + return null; } diff --git a/apps/ui/app/plotter/primitives-morph-style.ts b/apps/ui/app/plotter/primitives-morph-style.ts new file mode 100644 index 0000000000..4e375694fb --- /dev/null +++ b/apps/ui/app/plotter/primitives-morph-style.ts @@ -0,0 +1,7 @@ +// The pill’s diagonal radius is the reference for continuous secondary lines. +export const secondaryLine = { + opacity: 0.5, + strokeWidth: 0.65, + strokeLinecap: "butt", + strokeLinejoin: "miter", +} as const; diff --git a/apps/ui/app/plotter/primitives-morph-timing.ts b/apps/ui/app/plotter/primitives-morph-timing.ts new file mode 100644 index 0000000000..cef4d36771 --- /dev/null +++ b/apps/ui/app/plotter/primitives-morph-timing.ts @@ -0,0 +1,7 @@ +export const morphStageDuration = 3.2; +export const morphHoldDuration = 2.4; +export const morphCycleDuration = 5 * morphStageDuration; + +export function morphKeyTimes(...seconds: number[]) { + return seconds.map((second) => second / morphCycleDuration).join(";"); +} diff --git a/apps/ui/app/plotter/primitives-morph-transitions.tsx b/apps/ui/app/plotter/primitives-morph-transitions.tsx index 293e511089..f2c6527e88 100644 --- a/apps/ui/app/plotter/primitives-morph-transitions.tsx +++ b/apps/ui/app/plotter/primitives-morph-transitions.tsx @@ -1,8 +1,10 @@ +import { secondaryLine } from "./primitives-morph-style"; +import { morphCycleDuration as duration } from "./primitives-morph-timing"; + type Timing = { start: number; end: number }; -const duration = 12.8; const circleToCapsule = { start: 2.4, end: 3.2 }; -const capsuleToRounded = { start: 5.6, end: 6.4 }; +const buttonToCard = { start: 12, end: 12.8 }; function TransitionVisibility({ start, end }: Timing) { return ( @@ -77,15 +79,9 @@ export function PrimitivesMorphTransitions() { ))} - + {[204, 364].map((center) => ( - + - - + + {corners.map(({ id, x, y }) => ( - + + ))} - + {corners.map(({ id, x, y }) => ( - + + diff --git a/apps/ui/app/plotter/primitives-morph.tsx b/apps/ui/app/plotter/primitives-morph.tsx index 11e95c57cd..86abe2f424 100644 --- a/apps/ui/app/plotter/primitives-morph.tsx +++ b/apps/ui/app/plotter/primitives-morph.tsx @@ -3,20 +3,27 @@ import { useEffect, useId, useRef } from "react"; import { BlueprintDrawing } from "./blueprint-drawing"; import { MorphDetails, morphDetailIds } from "./primitives-morph-details"; +import { secondaryLine } from "./primitives-morph-style"; +import { + morphCycleDuration, + morphHoldDuration, + morphKeyTimes, + morphStageDuration, +} from "./primitives-morph-timing"; import { PrimitivesMorphTransitions } from "./primitives-morph-transitions"; import styles from "./primitives-motion.module.css"; +import { SwitchThumb } from "./primitives-switch"; -type Shape = { width: number; height: number; corner: number; bevel?: boolean }; +type Shape = { width: number; height: number; corner: number }; // All stages share four straight edges and four cubic corners, including the // circle’s zero-length edges. Matching topology keeps every point continuous. -function construct({ width, height, corner: r, bevel }: Shape) { +function construct({ width, height, corner: r }: Shape) { const left = 284 - width / 2; const right = 284 + width / 2; const top = 204 - height / 2; const bottom = 204 + height / 2; - const a = bevel ? r / 3 : r * 0.55228475; - const b = bevel ? r / 3 : 0; + const a = r * 0.55228475; const points = { "top-left": [left + r, top], "top-right": [right - r, top], @@ -30,13 +37,13 @@ function construct({ width, height, corner: r, bevel }: Shape) { return { points, outline: `M${left + r} ${top} L${right - r} ${top} - C${right - r + a} ${top + b} ${right - b} ${top + r - a} ${right} ${top + r} + C${right - r + a} ${top} ${right} ${top + r - a} ${right} ${top + r} L${right} ${bottom - r} - C${right - b} ${bottom - r + a} ${right - r + a} ${bottom - b} ${right - r} ${bottom} + C${right} ${bottom - r + a} ${right - r + a} ${bottom} ${right - r} ${bottom} L${left + r} ${bottom} - C${left + r - a} ${bottom - b} ${left + b} ${bottom - r + a} ${left} ${bottom - r} + C${left + r - a} ${bottom} ${left} ${bottom - r + a} ${left} ${bottom - r} L${left} ${top + r} - C${left + b} ${top + r - a} ${left + r - a} ${top + b} ${left + r} ${top} Z`, + C${left} ${top + r - a} ${left + r - a} ${top} ${left + r} ${top} Z`, // One continuous guide owns each extension; measurement marks never retrace it. guides: `M${left} 38V348 M${right} 38V348 M${left - 24} ${top}H490 M${left - 24} ${bottom}H490 @@ -49,11 +56,13 @@ function construct({ width, height, corner: r, bevel }: Shape) { } const circle = construct({ width: 240, height: 240, corner: 120 }); +const pill = construct({ width: 320, height: 160, corner: 80 }); const stages = [ circle, - construct({ width: 320, height: 160, corner: 80 }), + pill, + pill, + construct({ width: 320, height: 160, corner: 24 }), construct({ width: 240, height: 240, corner: 40 }), - construct({ width: 240, height: 240, corner: 60, bevel: true }), circle, ]; @@ -70,9 +79,21 @@ function Morph({ ["0 0 1 1", "0.4 0 0.2 1"]) + .join(";")} + keyTimes={morphKeyTimes( + ...values.flatMap((_, index) => + index === values.length - 1 + ? [morphCycleDuration] + : [ + index * morphStageDuration, + index * morphStageDuration + morphHoldDuration, + ], + ), + )} repeatCount="indefinite" values={values .flatMap((value, index) => @@ -172,13 +193,6 @@ export function PrimitivesMorph({ paused }: { paused: boolean }) { patternUnits="userSpaceOnUse" width="10" > - @@ -191,24 +205,19 @@ export function PrimitivesMorph({ paused }: { paused: boolean }) { ))} - + stage.dimensions)} /> - + stage.outline)} /> - + {tangentIds.map((id) => ( @@ -253,11 +268,12 @@ export function PrimitivesMorph({ paused }: { paused: boolean }) { u - + +
); diff --git a/apps/ui/app/plotter/primitives-switch.tsx b/apps/ui/app/plotter/primitives-switch.tsx new file mode 100644 index 0000000000..7918526b0c --- /dev/null +++ b/apps/ui/app/plotter/primitives-switch.tsx @@ -0,0 +1,112 @@ +import { ConstructionAnnotation } from "./primitives-construction-motion"; +import { secondaryLine } from "./primitives-morph-style"; +import { + morphCycleDuration as duration, + morphKeyTimes as keyTimes, +} from "./primitives-morph-timing"; + +const radiusEndpoint = -60 / Math.sqrt(2); + +export function SwitchConstruction() { + return ( + + + + + + + + + + + 4u + + + 03 / Switch + + + + + ); +} + +export function SwitchThumb() { + return ( + + + + {/* Geometry resets after the whole thumb is hidden. */} + + + + + + + + + 1.5u + + + + + ); +} diff --git a/apps/ui/skills/plotter/references/direction.md b/apps/ui/skills/plotter/references/direction.md index a3f84bb062..0a2a99a4f3 100644 --- a/apps/ui/skills/plotter/references/direction.md +++ b/apps/ui/skills/plotter/references/direction.md @@ -64,6 +64,12 @@ For the capsule, the user requested a 45° upper-left 2u radius and removal of t The user then said “love it” and requested a separate animation section. The refined loop is now the first example, M01 / Changing geometry, under Animations on `/ui/plotter`. P00 remains a static illustration in the gallery. Preserve manual pause and reduced-motion behavior; inline examples also pause while offscreen. +When relating the forms to UI elements, the user questioned the chamfer’s role, was unconvinced by a tag metaphor, and then explicitly discarded a play-triangle trial. They requested a switch derived from the pill, or placed before it, then a rounded rectangle between the switch and square. The current sequence is Avatar → Pill → Switch → Button → Card, using those UI names in the drawing. One of the pill’s generating circles becomes the sliding switch thumb; its track then becomes the rounded rectangular button before turning into the square card. This revision is awaiting review. + +The user explicitly said “never a solid bg” after seeing the filled switch thumb. Keep illustrated forms transparent: no opaque fills to hide guides, no solid background patches, and no area tint. Construction lines remain visible through the thumb; line hatching can still describe a measured region. + +The user asked for consistent continuous secondary lines, using the pill’s diagonal radius as the reference after noticing that the card’s inner square looked different. In M01, use the shared secondary-line style (0.65 SVG units, 50% opacity, butt caps and miter joins) for continuous construction geometry, inset frames, dimensions, and their marks. Keep primary outlines and the faint grid/reference scaffolding in their separate visual roles. + ## Expanded subject brief The user explicitly requested a keyboard, browser window, table lamp, folder, smartphone, chat, and service concepts for Cloudflare, Vercel, Stripe, and Linear, then asked for more popular-service examples. These are requested exploration areas; only the specific feedback above establishes preference or approval. The expansion does not rehabilitate earlier rejected object drawings. Render familiar objects as flat plans or elevations with meaningful construction relationships. Preserve the established no-perspective rule. From 30e30dfde947a66927c13973c51d778f9e9573e9 Mon Sep 17 00:00:00 2001 From: pasqualevitiello Date: Sun, 25 Oct 2026 08:59:40 +0100 Subject: [PATCH 06/15] feat(ui): add measured slider to Plotter animation --- apps/ui/app/plotter/page.tsx | 5 +- .../app/plotter/primitives-morph-details.tsx | 4 + .../ui/app/plotter/primitives-morph-timing.ts | 2 +- apps/ui/app/plotter/primitives-morph.tsx | 36 ++++++-- apps/ui/app/plotter/primitives-slider.tsx | 87 +++++++++++++++++++ .../ui/skills/plotter/references/direction.md | 6 ++ 6 files changed, 132 insertions(+), 8 deletions(-) create mode 100644 apps/ui/app/plotter/primitives-slider.tsx diff --git a/apps/ui/app/plotter/page.tsx b/apps/ui/app/plotter/page.tsx index ac66cecf6f..35b0876220 100644 --- a/apps/ui/app/plotter/page.tsx +++ b/apps/ui/app/plotter/page.tsx @@ -48,8 +48,9 @@ export default function Page() { Changing geometry

- Avatar, pill, switch, button, card. Five familiar UI elements, - one continuous loop, with measurements that follow the geometry. + Avatar, pill, switch, button, card, slider. Six familiar UI + elements, one continuous loop, with measurements that follow the + geometry.

diff --git a/apps/ui/app/plotter/primitives-morph-details.tsx b/apps/ui/app/plotter/primitives-morph-details.tsx index 6328b30b32..61c7c9f2dd 100644 --- a/apps/ui/app/plotter/primitives-morph-details.tsx +++ b/apps/ui/app/plotter/primitives-morph-details.tsx @@ -4,6 +4,7 @@ import { ConstructionMotion, } from "./primitives-construction-motion"; import { secondaryLine } from "./primitives-morph-style"; +import { SliderConstruction } from "./primitives-slider"; import { SwitchConstruction } from "./primitives-switch"; export const morphDetailIds = [ @@ -12,6 +13,7 @@ export const morphDetailIds = [ "switch", "button", "rounded", + "slider", ] as const; export function MorphDetails({ @@ -149,6 +151,8 @@ export function MorphDetails({ if (stage === "button") return ; + if (stage === "slider") return ; + if (stage === "rounded") { return ( diff --git a/apps/ui/app/plotter/primitives-morph-timing.ts b/apps/ui/app/plotter/primitives-morph-timing.ts index cef4d36771..afb6704a58 100644 --- a/apps/ui/app/plotter/primitives-morph-timing.ts +++ b/apps/ui/app/plotter/primitives-morph-timing.ts @@ -1,6 +1,6 @@ export const morphStageDuration = 3.2; export const morphHoldDuration = 2.4; -export const morphCycleDuration = 5 * morphStageDuration; +export const morphCycleDuration = 6 * morphStageDuration; export function morphKeyTimes(...seconds: number[]) { return seconds.map((second) => second / morphCycleDuration).join(";"); diff --git a/apps/ui/app/plotter/primitives-morph.tsx b/apps/ui/app/plotter/primitives-morph.tsx index 86abe2f424..181a64a95b 100644 --- a/apps/ui/app/plotter/primitives-morph.tsx +++ b/apps/ui/app/plotter/primitives-morph.tsx @@ -12,6 +12,7 @@ import { } from "./primitives-morph-timing"; import { PrimitivesMorphTransitions } from "./primitives-morph-transitions"; import styles from "./primitives-motion.module.css"; +import { SliderGridMask } from "./primitives-slider"; import { SwitchThumb } from "./primitives-switch"; type Shape = { width: number; height: number; corner: number }; @@ -36,6 +37,7 @@ function construct({ width, height, corner: r }: Shape) { } as const; return { points, + showHeight: height >= 40 ? 1 : 0, outline: `M${left + r} ${top} L${right - r} ${top} C${right - r + a} ${top} ${right} ${top + r - a} ${right} ${top + r} L${right} ${bottom - r} @@ -49,8 +51,8 @@ function construct({ width, height, corner: r }: Shape) { M${left - 24} ${top}H490 M${left - 24} ${bottom}H490 M164 348V372 M204 348V372`, dimensions: `M${left} 46H264 M304 46H${right} - M${left - 4} 50l8-8 M${right - 4} 50l8-8 - M482 ${top}V190 M482 218V${bottom} + M${left - 4} 50l8-8 M${right - 4} 50l8-8`, + heightDimension: `M482 ${top}V190 M482 218V${bottom} M478 ${top + 4}l8-8 M478 ${bottom + 4}l8-8`, }; } @@ -63,6 +65,7 @@ const stages = [ pill, construct({ width: 320, height: 160, corner: 24 }), construct({ width: 240, height: 240, corner: 40 }), + construct({ width: 320, height: 12, corner: 6 }), circle, ]; @@ -184,6 +187,7 @@ export function PrimitivesMorph({ paused }: { paused: boolean }) { > + {/* Origin at the circle center aligns a hatch line with its 45° radius. */} stage.dimensions)} /> + + stage.showHeight)} + /> + + stage.heightDimension)} + /> + + + stage.showHeight)} + /> {tangentIds.map((id) => ( 6u - - 6u - + + stage.showHeight)} + /> + + 6u + + u diff --git a/apps/ui/app/plotter/primitives-slider.tsx b/apps/ui/app/plotter/primitives-slider.tsx new file mode 100644 index 0000000000..a3d9006c54 --- /dev/null +++ b/apps/ui/app/plotter/primitives-slider.tsx @@ -0,0 +1,87 @@ +import { + ConstructionAnnotation, + ConstructionMotion, +} from "./primitives-construction-motion"; +import { secondaryLine } from "./primitives-morph-style"; +import { + morphCycleDuration, + morphHoldDuration, + morphKeyTimes, + morphStageDuration, +} from "./primitives-morph-timing"; + +const travelGuides = "M204 116V292 M364 116V292 M204 164H364 M204 244H364"; +const scalePositions = [124, 164, 204, 244, 284, 324, 364, 404, 444]; + +// Remove only coincident grid strokes, so the construction dashes retain gaps. +export function SliderGridMask() { + return ( + + + + + ); +} + +export function SliderConstruction() { + return ( + + + + + + + + + + + {scalePositions.map((x, index) => { + const halfTick = index % 2 === 0 ? 4 : 2; + return ; + })} + + + + + + + + 4u + + + 0% + + + 50% + + + 100% + + + 06 / Slider + + + + + ); +} diff --git a/apps/ui/skills/plotter/references/direction.md b/apps/ui/skills/plotter/references/direction.md index 0a2a99a4f3..3c55b9613b 100644 --- a/apps/ui/skills/plotter/references/direction.md +++ b/apps/ui/skills/plotter/references/direction.md @@ -70,6 +70,12 @@ The user explicitly said “never a solid bg” after seeing the filled switch t The user asked for consistent continuous secondary lines, using the pill’s diagonal radius as the reference after noticing that the card’s inner square looked different. In M01, use the shared secondary-line style (0.65 SVG units, 50% opacity, butt caps and miter joins) for continuous construction geometry, inset frames, dimensions, and their marks. Keep primary outlines and the faint grid/reference scaffolding in their separate visual roles. +After committing the five-stage version, the user requested a slider at the end of the sequence. The new experiment compresses Card into a thin track, then moves a transparent thumb from 25% to 75%, before returning to Avatar. The six-stage sequence and slider treatment are awaiting review. + +The first Slider trial was weak: the user could barely see its dotted line and was not happy with the overall result. The revision makes the displacement the subject: a larger transparent thumb, a dashed starting circle, tangent guides between the start/end positions, a 4u travel dimension, and a measured percentage scale. Dashed guides use 38% opacity; coincident grid segments are excluded along their strokes so the gaps remain visible. This revised treatment is awaiting review. + +The user questioned whether the sector animation suited an avatar but said they liked it. After a suggestion to rename the stage Circle, they explicitly chose to leave it as is and commit. Preserve both the Avatar label and its sector construction. + ## Expanded subject brief The user explicitly requested a keyboard, browser window, table lamp, folder, smartphone, chat, and service concepts for Cloudflare, Vercel, Stripe, and Linear, then asked for more popular-service examples. These are requested exploration areas; only the specific feedback above establishes preference or approval. The expansion does not rehabilitate earlier rejected object drawings. Render familiar objects as flat plans or elevations with meaningful construction relationships. Preserve the established no-perspective rule. From 3bfd6b9d5549911b7e02b57567d7ea9a10ee5dd6 Mon Sep 17 00:00:00 2001 From: pasqualevitiello Date: Sun, 25 Oct 2026 14:08:21 +0100 Subject: [PATCH 07/15] feat(ui): refine Plotter studies and animate particle assemblies --- apps/ui/app/plotter/blueprint-drawing.tsx | 27 + apps/ui/app/plotter/cal-studies.tsx | 111 ++- apps/ui/app/plotter/content-state-studies.tsx | 551 +++++++++++++++ apps/ui/app/plotter/dimensions.tsx | 61 ++ apps/ui/app/plotter/flat-object-studies.tsx | 292 ++++---- apps/ui/app/plotter/illustration-gallery.tsx | 22 + apps/ui/app/plotter/illustrations.tsx | 4 + .../app/plotter/interface-detail-studies.tsx | 191 ++--- apps/ui/app/plotter/network-studies.tsx | 88 +-- .../plotter/original-particles-blueprint.tsx | 144 ++-- apps/ui/app/plotter/page.tsx | 29 +- .../plotter/particles-assembly-content.tsx | 109 +++ .../plotter/particles-assembly-drawing.tsx | 513 ++++++++++++++ .../plotter/particles-assembly-geometry.ts | 178 +++++ apps/ui/app/plotter/particles-blueprint.tsx | 218 +++--- .../app/plotter/particles-motion.module.css | 13 + apps/ui/app/plotter/particles-motion.tsx | 168 +++++ .../app/plotter/popular-service-studies.tsx | 101 +-- apps/ui/app/plotter/state-studies.tsx | 114 ++- apps/ui/app/plotter/study-types.ts | 5 + apps/ui/app/plotter/system-state-studies.tsx | 650 ++++++++++++++++++ apps/ui/app/plotter/system-studies.tsx | 62 +- apps/ui/skills/plotter/SKILL.md | 8 +- .../plotter/references/approved-references.md | 55 ++ .../ui/skills/plotter/references/direction.md | 111 ++- .../plotter/references/implementation.md | 4 +- 26 files changed, 3079 insertions(+), 750 deletions(-) create mode 100644 apps/ui/app/plotter/content-state-studies.tsx create mode 100644 apps/ui/app/plotter/dimensions.tsx create mode 100644 apps/ui/app/plotter/particles-assembly-content.tsx create mode 100644 apps/ui/app/plotter/particles-assembly-drawing.tsx create mode 100644 apps/ui/app/plotter/particles-assembly-geometry.ts create mode 100644 apps/ui/app/plotter/particles-motion.module.css create mode 100644 apps/ui/app/plotter/particles-motion.tsx create mode 100644 apps/ui/app/plotter/system-state-studies.tsx create mode 100644 apps/ui/skills/plotter/references/approved-references.md diff --git a/apps/ui/app/plotter/blueprint-drawing.tsx b/apps/ui/app/plotter/blueprint-drawing.tsx index 1b1e0aebdf..36070a82be 100644 --- a/apps/ui/app/plotter/blueprint-drawing.tsx +++ b/apps/ui/app/plotter/blueprint-drawing.tsx @@ -15,3 +15,30 @@ export function BlueprintDrawing({ children }: { children: ReactNode }) { ); } + +// 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 ( + + ); +} diff --git a/apps/ui/app/plotter/cal-studies.tsx b/apps/ui/app/plotter/cal-studies.tsx index 9f2cc3bc5f..b92795a4e8 100644 --- a/apps/ui/app/plotter/cal-studies.tsx +++ b/apps/ui/app/plotter/cal-studies.tsx @@ -104,81 +104,60 @@ function SharedAvailability() { ); } -function ClockFace({ cx, hour }: { cx: number; hour: number }) { - const hourAngle = (hour / 12) * Math.PI * 2; - - return ( - - - - - {Array.from({ length: 12 }, (_, index) => index).map((hourTick) => ( - - ))} - - - - - - - - - ); -} - function TwoTimeZones() { return ( - - - + + + + + + - - - - + + {[104, 176, 248, 320, 392, 464].map((x) => ( + + ))} + + + + + + + + - - - - - +6h + + + UTC - - 09:30 + + UTC+03 - - UTC + + 09:00 + + + 12:00 - - 15:30 + + + {["07", "08", "09", "10", "11", "12"].map((hour, index) => ( + + {hour} + + ))} + {["10", "11", "12", "13", "14", "15"].map((hour, index) => ( + + {hour} + + ))} + + 1h - - UTC+6 + + Same booking @@ -201,7 +180,7 @@ export const calStudies: IllustrationStudy[] = [ code: "K03", title: "One moment, two places", description: - "Two clock faces show the same instant six hours apart. Their shared minute hand and connecting datum make the time difference tangible.", + "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: , }, diff --git a/apps/ui/app/plotter/content-state-studies.tsx b/apps/ui/app/plotter/content-state-studies.tsx new file mode 100644 index 0000000000..e90acb3075 --- /dev/null +++ b/apps/ui/app/plotter/content-state-studies.tsx @@ -0,0 +1,551 @@ +import { BlueprintDrawing, CompactBlueprintDrawing } from "./blueprint-drawing"; +import { HorizontalDimension, VerticalDimension } from "./dimensions"; +import type { IllustrationStudy } from "./study-types"; + +const secondaryLine = { + opacity: 0.5, + strokeWidth: 0.65, + strokeLinecap: "butt", + strokeLinejoin: "miter", +} as const; + +function Registration() { + return ( + + + + ); +} + +const indexRecords = [ + { x: 108, label: "A" }, + { x: 180, label: "B" }, + { x: 324, label: "D" }, + { x: 396, label: "E" }, +]; + +function SearchForm() { + return ( + <> + + + + {indexRecords.map(({ x }) => ( + + ))} + + ); +} + +function SearchGap({ dash = "4 5" }: { dash?: string }) { + return ( + + + + + + ); +} + +function SearchStudy() { + return ( + + + + + + + + + + + + + + + + + C + + {indexRecords.map(({ x, label }) => ( + + {label} + + ))} + + + + Index + + + 0 matches + + + + + ); +} + +function SearchCompact() { + return ( + + + + {indexRecords.map(({ x }) => ( + + ))} + + + ); +} + +const readNotifications = [132, 196, 260]; + +function NotificationStream({ dash = "3 5" }: { dash?: string }) { + return ( + <> + + {readNotifications.map((y) => ( + + + + + + + + ))} + + + + + + + ); +} + +function NotificationsStudy() { + return ( + + + + + + + + + + + + + + Read + + + 0 unread + + + + + The stream is caught up. + + + + + ); +} + +function NotificationsCompact() { + return ( + + + + ); +} + +const pageHeight = 200 * Math.sqrt(2); +const pageBottom = 80 + pageHeight; +const writingTop = 80 + pageHeight * 0.2; +const writingBottom = 80 + pageHeight * 0.8; + +function PageForm() { + return ( + <> + + + + + ); +} + +function WritingArea({ dash = "3 5" }: { dash?: string }) { + return ( + + ); +} + +function FilesStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + Begin here + + + + + 1 : √2 + + + + + ); +} + +function FilesCompact() { + return ( + + + + + + ); +} + +const plotColumns = [196, 244, 292, 340, 388, 436]; +const plotRows = [132, 180, 228, 276]; + +function EmptyPlot({ compact = false }: { compact?: boolean }) { + return ( + <> + + {plotRows.map((y) => ( + + ))} + {plotColumns.map((x) => ( + + ))} + + + + + ); +} + +function DataStudy() { + return ( + + + + + + + + + + + + + + No observations + + + + + 0 + + + x + + + y + + + An unoccupied coordinate field + + + + + ); +} + +function DataCompact() { + return ( + + + + ); +} + +const weekdays = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]; + +function EmptyWeek() { + return ( + <> + + + + + + + ); +} + +function BookingsStudy() { + return ( + + + + + + + + + + + + + + + + + {weekdays.map((day, index) => ( + + {day} + + ))} + + + {["09:00", "10:00", "11:00", "12:00"].map((time, index) => ( + + {time} + + ))} + + + 0 bookings + + + + ); +} + +function BookingsCompact() { + return ( + + + {[160, 200, 280, 320, 360, 400].map((x) => ( + + ))} + + ); +} + +function ImageBounds({ dash = "4 6" }: { dash?: string }) { + return ( + <> + + + + + + ); +} + +function ImageStudy() { + return ( + + + + + + + + + + + + + + + + + + + Image / 4 : 3 + + + + + ); +} + +function ImageCompact() { + return ( + + + + ); +} + +export const contentStateStudies: IllustrationStudy[] = [ + { + id: "state-search", + code: "S03", + title: "A search, unanswered", + description: + "A query projects into the missing position of an ordered index. The surrounding records exist; the requested one does not.", + category: "Empty states", + illustration: , + emptyState: { + title: "No results found", + description: "Try a different search term or remove a filter.", + illustration: , + }, + }, + { + id: "state-notifications", + code: "S05", + title: "A stream, caught up", + description: + "Read entries occupy an evenly spaced notification stream. Its next position remains open, with no unread entry to continue it.", + category: "Empty states", + illustration: , + emptyState: { + title: "No notifications", + description: "You’re all caught up. New activity will appear here.", + illustration: , + }, + }, + { + id: "state-files", + code: "S06", + title: "A page, unwritten", + description: + "A blank writing area repeats the page’s 1 : √2 proportion. Equal side margins locate the cursor before the first line exists.", + category: "Empty states", + illustration: , + emptyState: { + title: "No documents yet", + description: "Create a document to start writing.", + illustration: , + }, + }, + { + id: "state-data", + code: "S07", + title: "A field without observations", + description: + "Calibrated axes establish a coordinate field. Its units are defined, but no observation occupies a coordinate.", + category: "Empty states", + illustration: , + emptyState: { + title: "No data yet", + description: "Data will appear once activity is recorded.", + illustration: , + }, + }, + { + id: "state-bookings", + code: "S13", + title: "A week, unbooked", + description: + "Seven days share a measured hour grid. Today has its place in the week; the time field contains no bookings.", + category: "Empty states", + illustration: , + emptyState: { + title: "No upcoming bookings", + description: "Share your booking link to start receiving meetings.", + illustration: , + }, + }, + { + id: "state-image", + code: "S14", + title: "An image, not yet placed", + description: + "Crop corners establish a 4 : 3 image area. The diagonal and unit measurements define its proportions while the image itself is absent.", + category: "Empty states", + illustration: , + emptyState: { + title: "No image", + description: "Drop an image here or browse your files.", + illustration: , + }, + }, +]; diff --git a/apps/ui/app/plotter/dimensions.tsx b/apps/ui/app/plotter/dimensions.tsx new file mode 100644 index 0000000000..062e57d5e6 --- /dev/null +++ b/apps/ui/app/plotter/dimensions.tsx @@ -0,0 +1,61 @@ +export function HorizontalDimension({ + x1, + x2, + y, + label, +}: { + x1: number; + x2: number; + y: number; + label: string; +}) { + return ( + <> + + + {label} + + + ); +} + +export function VerticalDimension({ + x, + y1, + y2, + label, + side = "end", +}: { + x: number; + y1: number; + y2: number; + label: string; + side?: "start" | "end"; +}) { + return ( + <> + + + {label} + + + ); +} diff --git a/apps/ui/app/plotter/flat-object-studies.tsx b/apps/ui/app/plotter/flat-object-studies.tsx index 572e66c65f..b1549daab7 100644 --- a/apps/ui/app/plotter/flat-object-studies.tsx +++ b/apps/ui/app/plotter/flat-object-studies.tsx @@ -2,92 +2,70 @@ import { BlueprintDrawing } from "./blueprint-drawing"; import type { IllustrationStudy } from "./study-types"; const keyboardKeys = [ - { x: 136, y: 124, width: 68, label: "Tab" }, - { x: 212, y: 124, width: 44, label: "Q" }, - { x: 264, y: 124, width: 44, label: "W" }, - { x: 316, y: 124, width: 44, label: "E" }, - { x: 368, y: 124, width: 44, label: "R" }, - { x: 136, y: 176, width: 80, label: "Caps" }, - { x: 224, y: 176, width: 44, label: "A" }, - { x: 276, y: 176, width: 44, label: "S" }, - { x: 328, y: 176, width: 44, label: "D" }, - { x: 380, y: 176, width: 44, label: "F" }, - { x: 136, y: 228, width: 104, label: "Shift" }, - { x: 248, y: 228, width: 44, label: "Z" }, - { x: 300, y: 228, width: 44, label: "X" }, - { x: 352, y: 228, width: 44, label: "C" }, - { x: 136, y: 280, width: 44, label: "Ctrl" }, - { x: 188, y: 280, width: 44, label: "Fn" }, - { x: 240, y: 280, width: 44, label: "Alt" }, + { x: 144, y: 140, label: "Q" }, + { x: 216, y: 140, label: "W" }, + { x: 288, y: 140, label: "E" }, + { x: 162, y: 212, label: "A" }, + { x: 234, y: 212, label: "S" }, + { x: 306, y: 212, label: "D" }, ]; function KeyboardPlanStudy() { return ( - - - - - - - - - - - {keyboardKeys.map((key) => ( - + + + + + + + + + + + + + {keyboardKeys.map(({ x, y, label }) => ( + ))} - - - - - - - - - - - {keyboardKeys.map((key) => ( - 1 ? "9" : "11"} - key={key.label} - x={key.x + key.width / 2} - y={key.y + 26} - > - {key.label} + + + + + + + + + + {keyboardKeys.map(({ x, y, label }) => ( + + {label} ))} - - V - - - Space - - - - - - - + + + + - - - Pitch / 52 + + + Pitch / u - - 44 + + 64 - - Keyboard / detail + + ¼u + + + Stagger / ¼u @@ -97,119 +75,109 @@ function KeyboardPlanStudy() { function LampElevationStudy() { return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + - - 184 - - - R92 + + 2r - - 220 + + r - - 116 + + a - - 160 + + a - - Elevation + + Generating circle - - - ); } -const folderPositions = [ - { x: 104, y: 112 }, - { x: 312, y: 112 }, - { x: 208, y: 256 }, +const folders = [ + { x: 104, width: 216, height: 176, opacity: 1 }, + { x: 352, width: 136, height: 104, opacity: 0.62 }, ]; function FolderPlanStudy() { return ( - - - + + + + + - - - - - - {folderPositions.map(({ x, y }) => ( + + {folders.map(({ x, width, height, opacity }) => ( ))} - - - - - - - - - - - - - - - - - - - Tab / 56 + + + + + + + + + + + + + + + + + Tab / 64 + + + Tab / 64 - + R8 - - 48 + + w₁ - - 104 + + w₂ - - Module / 160 + + One tab / two widths @@ -222,7 +190,7 @@ export const flatObjectStudies: IllustrationStudy[] = [ code: "O03", title: "A keyboard, in detail", description: - "An enlarged portion of a keyboard, with staggered letter rows and wider modifiers. Open edges and dashed continuation reveal a detail within a larger system.", + "Two cropped rows reveal the keyboard’s construction: one key pitch and a quarter-unit stagger. Dashed continuations keep the focus on the spacing rule.", category: "Objects", illustration: , }, @@ -231,7 +199,7 @@ export const flatObjectStudies: IllustrationStudy[] = [ code: "O04", title: "A lamp, drawn in elevation", description: - "A semicircular shade, a centered stem, and a measured base. One radius establishes the profile, with every part held to the same vertical axis.", + "Half a circle becomes the shade. Its center establishes the support axis and divides the base into equal halves.", category: "Objects", illustration: , }, @@ -240,7 +208,7 @@ export const flatObjectStudies: IllustrationStudy[] = [ code: "O05", title: "A family of folders", description: - "Three independent folders share one tab, radius, and outline. Equal spacing and a common centerline turn a familiar shape into a small construction system.", + "One tab construction belongs to two folder widths. Paired corner radii and a shared tab dimension hold the family together.", category: "Objects", illustration: , }, diff --git a/apps/ui/app/plotter/illustration-gallery.tsx b/apps/ui/app/plotter/illustration-gallery.tsx index 570e66545a..e20953a515 100644 --- a/apps/ui/app/plotter/illustration-gallery.tsx +++ b/apps/ui/app/plotter/illustration-gallery.tsx @@ -11,6 +11,13 @@ import { DialogPopup, DialogTitle, } from "@/registry/default/ui/dialog"; +import { + Empty, + EmptyDescription, + EmptyHeader, + EmptyMedia, + EmptyTitle, +} from "@/registry/default/ui/empty"; import type { IllustrationStudy, StudyCategory } from "./study-types"; const categories: StudyCategory[] = [ @@ -181,6 +188,21 @@ export function IllustrationGallery({ {active.description}
+ {active.emptyState && ( +
+ + + + {active.emptyState.illustration} + + {active.emptyState.title} + + {active.emptyState.description} + + + +
+ )}
@@ -59,6 +61,31 @@ export default function Page() {
+
+
+

Particles / C03

+

+ Three assemblies +

+

+ Workspace, booking, activity. Three sets of independent elements + assemble into three different interfaces in a repeating + sequence. +

+
+
+ + + +
+
+ + {scene === "workspace" && part === 0 && ( + + )} + {scene === "workspace" && part === 1 && ( + + {trailingMotion} + + + {thumbMotion} + + + )} + {scene === "booking" && part === 0 && ( + + )} + {scene === "booking" && part === 1 && } + {scene === "booking" && part === 2 && ( + <> + + + + )} + {scene === "activity" && part === 0 && ( + + )} + + + {scene === "workspace" && part === 0 && } + {scene === "workspace" && part === 2 && ( + + {trailingMotion} + + + )} + {scene === "booking" && part === 0 && ( + + )} + {scene === "booking" && part === 1 && } + {scene === "activity" && part === 0 && ( + <> + + + + )} + {scene === "activity" && part === 2 && } + + {scene === "activity" ? ( + + {part === 1 && ( + <> + + New reply + + + Maya · just now + + + )} + {part === 2 && ( + + Ready + + )} + + ) : ( + + {label} + + )} + + ); +} diff --git a/apps/ui/app/plotter/particles-assembly-drawing.tsx b/apps/ui/app/plotter/particles-assembly-drawing.tsx new file mode 100644 index 0000000000..53cfbeacb6 --- /dev/null +++ b/apps/ui/app/plotter/particles-assembly-drawing.tsx @@ -0,0 +1,513 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; +import { + AssemblyContent, + type AssemblyScene, +} from "./particles-assembly-content"; +import { + type AssemblyAxis, + type AssemblyPart, + type Corner, + type Edge, + jointDuration, + jointNeighbor, + jointTime, + type ModuleGeometry, + outlineAt, + outlineTimes, + travel, +} from "./particles-assembly-geometry"; +import { secondaryLine } from "./primitives-morph-style"; + +export const particlesSequenceDuration = 16.2; +const chapterDuration = particlesSequenceDuration / 3; +const parts = [0, 1, 2] as const; +type Part = AssemblyPart; +type Frame = readonly [number, string | number]; +const edges: Edge[] = ["top", "right", "bottom", "left"]; +const corners: Corner[] = ["topLeft", "topRight", "bottomRight", "bottomLeft"]; +type Assembly = { + scene: AssemblyScene; + axis: AssemblyAxis; + unit: number; + labels: readonly [string, string, string]; + modules: readonly [ModuleGeometry, ModuleGeometry, ModuleGeometry]; + frame: { + x: number; + y: number; + width: number; + height: number; + radius: number; + }; +}; +const assemblies: Assembly[] = [ + { + scene: "workspace", + axis: "vertical", + unit: 40, + labels: ["Identity", "Visibility", "Action"], + modules: [ + { width: 120, targetWidth: 240, height: 40, radius: 8, x: 160, y: 224 }, + { width: 120, targetWidth: 240, height: 40, radius: 8, x: 160, y: 264 }, + { width: 120, targetWidth: 240, height: 40, radius: 8, x: 160, y: 304 }, + ], + frame: { x: 160, y: 224, width: 240, height: 120, radius: 8 }, + }, + { + scene: "booking", + axis: "horizontal", + unit: 40, + labels: ["Date", "Time", "Meeting"], + modules: [ + { width: 120, height: 40, radius: 8, x: 100, y: 264 }, + { width: 120, height: 40, radius: 8, x: 220, y: 264 }, + { width: 120, height: 40, radius: 8, x: 340, y: 264 }, + ], + frame: { x: 100, y: 264, width: 360, height: 40, radius: 8 }, + }, + { + scene: "activity", + axis: "horizontal", + unit: 24, + labels: ["Avatar", "Message", "Status"], + modules: [ + { width: 48, height: 48, radius: 8, x: 148, y: 260 }, + { width: 144, height: 48, radius: 8, x: 196, y: 260 }, + { width: 72, height: 32, targetHeight: 48, radius: 16, x: 340, y: 260 }, + ], + frame: { x: 148, y: 260, width: 264, height: 48, radius: 8 }, + }, +]; + +function Motion({ + index, + attributeName, + frames, + mode = "spline", +}: { + index: number; + attributeName: string; + frames: readonly Frame[]; + mode?: "spline" | "linear" | "discrete"; +}) { + const timeline = frames.map( + ([time, value]) => [time + index * chapterDuration, value] as const, + ); + const first = timeline[0]; + const last = timeline[timeline.length - 1]; + if (!first || !last) return null; + if (first[0] > 0) timeline.unshift([0, first[1]]); + if (last[0] < particlesSequenceDuration) + timeline.push([particlesSequenceDuration, last[1]]); + const timing = { + attributeName, + begin: "0s", + dur: `${particlesSequenceDuration}s`, + repeatCount: "indefinite", + calcMode: mode, + keyTimes: timeline + .map(([time]) => time / particlesSequenceDuration) + .join(";"), + keySplines: + mode === "spline" + ? timeline + .slice(1) + .map(() => "0.4 0 0.2 1") + .join(";") + : undefined, + values: timeline.map(([, value]) => value).join(";"), + }; + return attributeName === "transform" ? ( + + ) : ( + + ); +} + +function FragmentOutline({ + assembly, + index, + part, +}: { + assembly: Assembly; + index: number; + part: Part; +}) { + const times = outlineTimes(assembly.modules, assembly.axis, part); + const geometry = times.map((time) => + outlineAt(assembly.modules, assembly.axis, part, time), + ); + const initial = outlineAt(assembly.modules, assembly.axis, part, 0); + return ( + + {edges.map((edge) => { + const neighbor = jointNeighbor(assembly.axis, part, edge); + // Only the facing strokes merge. The exterior corner arcs keep their weight. + const ownsDivider = edge === "bottom" || edge === "right"; + const contact = + neighbor === undefined ? undefined : jointTime(part, neighbor); + return ( + + [ + time, + geometry[i]?.[edge] ?? initial[edge], + ])} + /> + {contact !== undefined && ( + <> + + + + )} + + ); + })} + {corners.map((corner) => ( + + [ + time, + geometry[i]?.[corner] ?? initial[corner], + ])} + /> + + ))} + + ); +} + +function Fragment({ + assembly, + index, + part, +}: { + assembly: Assembly; + index: number; + part: Part; +}) { + const { scene } = assembly; + const module = assembly.modules[part]; + const sourceX = 140 + part * 140 - module.width / 2; + const sourceY = 124 - module.height / 2; + const { start, end } = travel(part); + const grows = module.targetWidth !== undefined; + return ( + <> + + + + + + + + {module.targetHeight !== undefined && ( + + )} + + ) : undefined + } + thumbMotion={ + scene === "workspace" ? ( + + ) : undefined + } + /> + + + + ); +} + +function Receiver({ assembly, index }: { assembly: Assembly; index: number }) { + return ( + + {parts.map((part) => { + const module = assembly.modules[part]; + const final = outlineAt( + assembly.modules, + assembly.axis, + part, + chapterDuration, + ); + const { end } = travel(part); + return ( + + + {edges + .filter( + (edge) => + jointNeighbor(assembly.axis, part, edge) === undefined, + ) + .map((edge) => ( + + ))} + {corners.map((corner) => ( + + ))} + + ); + })} + + ); +} + +function widthDimension(left: number, right: number, y: number) { + const center = (left + right) / 2; + return `M${left} ${y - 8}V${y + 8} M${right} ${y - 8}V${y + 8} M${left} ${y}H${center - 16} M${center + 16} ${y}H${right} M${left - 4} ${y + 4}l8-8 M${right - 4} ${y + 4}l8-8`; +} + +function Measurements({ + assembly, + index, +}: { + assembly: Assembly; + index: number; +}) { + const { frame } = assembly; + const right = frame.x + frame.width; + const bottom = frame.y + frame.height; + const cx = frame.x + frame.width / 2; + const cy = frame.y + frame.height / 2; + const dy = bottom + 32; + return ( + + + + + + + {frame.height / assembly.unit}u + + + {frame.width / assembly.unit}u + + + A + B + C + + + + ); +} + +function Construction({ assembly }: { assembly: Assembly }) { + const { frame, scene, modules } = assembly; + const left = scene === "workspace" ? 140 : frame.x - 20; + const right = scene === "workspace" ? 420 : frame.x + frame.width + 20; + const top = frame.y - 20; + const bottom = frame.y + frame.height + 20; + return ( + <> + + + {modules.flatMap((module, part) => { + const x = 140 + part * 140 - module.width / 2; + return [x, x + module.width / 2, x + module.width].map((column) => ( + + )); + })} + + {/* Each field is fixed; all guides stop outside the assembled content. */} + + + {Array.from( + { length: Math.floor(frame.width / assembly.unit) + 1 }, + (_, i) => frame.x + i * assembly.unit, + ).map((x) => ( + + ))} + + + + {[140, 280, 420].map((x) => ( + + + + + ))} + + + + {assembly.labels.map((label, part) => ( + + {String.fromCharCode(65 + part)} / {label} + + ))} + + + ); +} + +export function ParticlesAssemblyDrawing() { + return ( + + {assemblies.map((assembly, index) => ( + + + + + {parts.map((part) => ( + + ))} + + + ))} + + ); +} diff --git a/apps/ui/app/plotter/particles-assembly-geometry.ts b/apps/ui/app/plotter/particles-assembly-geometry.ts new file mode 100644 index 0000000000..51a08f4f12 --- /dev/null +++ b/apps/ui/app/plotter/particles-assembly-geometry.ts @@ -0,0 +1,178 @@ +export type AssemblyPart = 0 | 1 | 2; +export type Edge = "top" | "right" | "bottom" | "left"; +export type Corner = "topLeft" | "topRight" | "bottomRight" | "bottomLeft"; +export type AssemblyAxis = "vertical" | "horizontal"; +export type ModuleGeometry = { + width: number; + height: number; + radius: number; + x: number; + y: number; + targetWidth?: number; + targetHeight?: number; +}; + +const circleControl = 0.5522847498; +const samplingRate = 60; +export const jointDuration = 0.22; + +export function travel(part: AssemblyPart) { + const start = 0.45 + (2 - part) * 0.32; + return { start, end: start + 0.9 }; +} + +export function easing(progress: number) { + if (progress <= 0) return 0; + if (progress >= 1) return 1; + + let low = 0; + let high = 1; + for (let iteration = 0; iteration < 32; iteration++) { + const position = (low + high) / 2; + const inverse = 1 - position; + const x = + 3 * inverse * inverse * position * 0.4 + + 3 * inverse * position * position * 0.2 + + position * position * position; + if (x < progress) low = position; + else high = position; + } + + const position = (low + high) / 2; + return 3 * (1 - position) * position * position + position ** 3; +} + +export function jointNeighbor( + axis: AssemblyAxis, + part: AssemblyPart, + edge: Edge, +): AssemblyPart | undefined { + const previous = axis === "vertical" ? "top" : "left"; + const next = axis === "vertical" ? "bottom" : "right"; + if (edge === previous && part > 0) return (part - 1) as AssemblyPart; + if (edge === next && part < 2) return (part + 1) as AssemblyPart; + return undefined; +} + +export function jointTime(part: AssemblyPart, neighbor: AssemblyPart) { + return Math.max(travel(part).end, travel(neighbor).end); +} + +function flightProgress(part: AssemblyPart, time: number) { + const { start, end } = travel(part); + return easing((time - start) / (end - start)); +} + +export function sizeAt( + module: ModuleGeometry, + part: AssemblyPart, + time: number, +) { + const progress = flightProgress(part, time); + return { + width: + module.width + + ((module.targetWidth ?? module.width) - module.width) * progress, + height: + module.height + + ((module.targetHeight ?? module.height) - module.height) * progress, + radius: module.radius + (8 - module.radius) * progress, + progress, + }; +} + +export function positionAt( + modules: readonly ModuleGeometry[], + _axis: AssemblyAxis, + part: AssemblyPart, + time: number, +) { + const module = modules[part]; + if (!module) throw new Error(`Missing assembly module ${part}`); + const progress = flightProgress(part, time); + const sourceX = 140 + part * 140 - module.width / 2; + const sourceY = 124 - module.height / 2; + return { + x: sourceX + (module.x - sourceX) * progress, + y: sourceY + (module.y - sourceY) * progress, + }; +} + +function coordinate(value: number) { + return Number(value.toFixed(4)); +} + +export function outlineAt( + modules: readonly ModuleGeometry[], + axis: AssemblyAxis, + part: AssemblyPart, + time: number, +): Record { + const module = modules[part]; + if (!module) throw new Error(`Missing assembly module ${part}`); + const { width, height, radius } = sizeAt(module, part, time); + + function corner(edge: Edge) { + const neighbor = jointNeighbor(axis, part, edge); + if (neighbor === undefined) return radius; + const contact = jointTime(part, neighbor); + return radius * (1 - easing((time - contact) / jointDuration)); + } + + const topLeft = corner(axis === "vertical" ? "top" : "left"); + const topRight = corner(axis === "vertical" ? "top" : "right"); + const bottomRight = corner(axis === "vertical" ? "bottom" : "right"); + const bottomLeft = corner(axis === "vertical" ? "bottom" : "left"); + const point = (x: number, y: number) => `${coordinate(x)} ${coordinate(y)}`; + + // Separate arcs stay primary while only the straight mating edges become dividers. + return { + top: `M${point(topLeft, 0)} L${point(width - topRight, 0)}`, + right: `M${point(width, topRight)} L${point(width, height - bottomRight)}`, + bottom: `M${point(width - bottomRight, height)} L${point(bottomLeft, height)}`, + left: `M${point(0, height - bottomLeft)} L${point(0, topLeft)}`, + topLeft: `M${point(0, topLeft)} C${point(0, topLeft * (1 - circleControl))} ${point(topLeft * (1 - circleControl), 0)} ${point(topLeft, 0)}`, + topRight: `M${point(width - topRight, 0)} C${point(width - topRight * (1 - circleControl), 0)} ${point(width, topRight * (1 - circleControl))} ${point(width, topRight)}`, + bottomRight: `M${point(width, height - bottomRight)} C${point(width, height - bottomRight * (1 - circleControl))} ${point(width - bottomRight * (1 - circleControl), height)} ${point(width - bottomRight, height)}`, + bottomLeft: `M${point(bottomLeft, height)} C${point(bottomLeft * (1 - circleControl), height)} ${point(0, height - bottomLeft * (1 - circleControl))} ${point(0, height - bottomLeft)}`, + }; +} + +function addSamples(times: Set, start: number, end: number) { + times.add(Number(start.toFixed(9))); + for (let sample = 1; start + sample / samplingRate < end; sample++) { + times.add(Number((start + sample / samplingRate).toFixed(9))); + } + times.add(Number(end.toFixed(9))); +} + +export function sampleTimes(parts: readonly AssemblyPart[]) { + const times = new Set([0, 5.4]); + for (const part of parts) { + const { start, end } = travel(part); + addSamples(times, start, end); + } + return [...times].sort((a, b) => a - b); +} + +export function outlineTimes( + _modules: readonly ModuleGeometry[], + axis: AssemblyAxis, + part: AssemblyPart, +): number[] { + const participating: AssemblyPart[] = [part]; + const edges: Edge[] = + axis === "vertical" ? ["top", "bottom"] : ["left", "right"]; + for (const edge of edges) { + const neighbor = jointNeighbor(axis, part, edge); + if (neighbor !== undefined) participating.push(neighbor); + } + const times = new Set(sampleTimes(participating)); + for (const edge of edges) { + const neighbor = jointNeighbor(axis, part, edge); + if (neighbor === undefined) continue; + const contact = jointTime(part, neighbor); + addSamples(times, contact, contact + jointDuration); + } + return [...times].sort((a, b) => a - b); +} diff --git a/apps/ui/app/plotter/particles-blueprint.tsx b/apps/ui/app/plotter/particles-blueprint.tsx index 9fd1964fa1..ffe2c4818d 100644 --- a/apps/ui/app/plotter/particles-blueprint.tsx +++ b/apps/ui/app/plotter/particles-blueprint.tsx @@ -1,9 +1,40 @@ import { BlueprintDrawing } from "./blueprint-drawing"; -function Registration() { +function ParticleContent({ + kind, + x, + y, +}: { + kind: "identity" | "visibility" | "action"; + x: number; + y: number; +}) { return ( - - + + + {kind === "identity" && ( + <> + + + + )} + {kind === "action" && } + + {kind === "visibility" && ( + + + + + )} + + + {kind === "identity" + ? "Studio" + : kind === "visibility" + ? "Public" + : "Open"} + + ); } @@ -11,122 +42,71 @@ function Registration() { export function ParticlesBlueprint() { return ( - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Studio - - - Public - - - Open - - - Studio - - - Public - - - Open - - - - - - A / Identity - - - B / Visibility - - - C / Action - - - A + B + C - - - 3u - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + A + + + B + + + C + + + Parts + + + Composition + + + 3u + + + 3u + + + u + + + A + B + C + + + + ); diff --git a/apps/ui/app/plotter/particles-motion.module.css b/apps/ui/app/plotter/particles-motion.module.css new file mode 100644 index 0000000000..3a33eeaf27 --- /dev/null +++ b/apps/ui/app/plotter/particles-motion.module.css @@ -0,0 +1,13 @@ +.animation { + display: none; +} + +@media (prefers-reduced-motion: no-preference) { + .drawing[data-live] .animation { + display: block; + } + + .drawing[data-live] .original { + visibility: hidden; + } +} diff --git a/apps/ui/app/plotter/particles-motion.tsx b/apps/ui/app/plotter/particles-motion.tsx new file mode 100644 index 0000000000..fb4f53caaa --- /dev/null +++ b/apps/ui/app/plotter/particles-motion.tsx @@ -0,0 +1,168 @@ +"use client"; + +import { PauseIcon, PlayIcon, RotateCcwIcon } from "lucide-react"; +import { type ReactNode, useEffect, useRef, useState } from "react"; +import { useMediaQuery } from "@/registry/default/hooks/use-media-query"; +import { Button } from "@/registry/default/ui/button"; +import { + ParticlesAssemblyDrawing, + particlesSequenceDuration, +} from "./particles-assembly-drawing"; +import styles from "./particles-motion.module.css"; + +const chapters = ["Workspace", "Booking", "Activity"]; +const chapterDuration = particlesSequenceDuration / chapters.length; + +export function ParticlesMotion({ + children, + controls = "hero", +}: { + children: ReactNode; + controls?: "hero" | "preview"; +}) { + const drawing = useRef(null); + const animation = useRef(null); + const [live, setLive] = useState(true); + const [paused, setPaused] = useState(false); + const [inView, setInView] = useState(false); + const [stage, setStage] = useState(0); + const reducedMotion = useMediaQuery("(prefers-reduced-motion: reduce)"); + + useEffect(() => { + if (!drawing.current) return; + const observer = new IntersectionObserver(([entry]) => { + if (entry) setInView(entry.isIntersecting); + }); + observer.observe(drawing.current); + return () => observer.disconnect(); + }, []); + + useEffect(() => { + const svg = animation.current?.querySelector("svg"); + if (!svg) return; + if (!live || reducedMotion || paused || !inView) { + svg.pauseAnimations(); + return; + } + + svg.unpauseAnimations(); + const updateStage = () => { + const time = svg.getCurrentTime() % particlesSequenceDuration; + // SVG clock precision can place a chapter seek just before its boundary. + setStage( + Math.max( + 0, + Math.min( + chapters.length - 1, + Math.floor((time + 0.0001) / chapterDuration), + ), + ), + ); + }; + updateStage(); + const timer = window.setInterval(updateStage, 100); + return () => window.clearInterval(timer); + }, [paused, inView, live, reducedMotion]); + + const seek = (chapter: number) => { + const svg = animation.current?.querySelector("svg"); + svg?.setCurrentTime(chapter * chapterDuration); + if (inView && !reducedMotion) svg?.unpauseAnimations(); + setStage(chapter); + setLive(true); + setPaused(false); + }; + + const togglePlayback = () => { + if (!live) { + setLive(true); + setPaused(false); + } else setPaused((value) => !value); + }; + + return ( +
+
+
+ {children} +
+
+ +
+ {controls === "hero" && !reducedMotion && ( + + )} +
+ {controls === "preview" && ( +
+
+ {chapters.map((chapter, index) => ( + + ))} +
+
+ {live && ( + + )} + + {live && ( + + )} +
+

+ Reduced motion · Static drawing +

+
+ )} +
+ ); +} diff --git a/apps/ui/app/plotter/popular-service-studies.tsx b/apps/ui/app/plotter/popular-service-studies.tsx index 514ce953fe..4cf10a624f 100644 --- a/apps/ui/app/plotter/popular-service-studies.tsx +++ b/apps/ui/app/plotter/popular-service-studies.tsx @@ -148,81 +148,56 @@ function NotionBlocksStudy() { return ( - + + - - - - - - + + + - - - - - - + + + + + + + + + + + + + + - - - - - - - - - - Heading - - + + Text - - Database + + Title - - Page + + Tasks - - - 204 + + + 5u + + + Insert block - - Shared width + + Page - - 28 + + Inset @@ -349,7 +324,7 @@ export const popularServiceStudies: IllustrationStudy[] = [ code: "B08", title: "Notion · Knowledge, assembled", description: - "Heading, text, and database blocks share one width and a repeatable gap. An offset block moves into the page’s common alignment frame.", + "A reusable text block fits an open position in a page. Shared widths, repeated gaps, and one insertion route explain the assembly.", category: "Services", illustration: , }, diff --git a/apps/ui/app/plotter/state-studies.tsx b/apps/ui/app/plotter/state-studies.tsx index bbce7c8750..b9b64715b6 100644 --- a/apps/ui/app/plotter/state-studies.tsx +++ b/apps/ui/app/plotter/state-studies.tsx @@ -1,64 +1,106 @@ -import { BlueprintDrawing } from "./blueprint-drawing"; +import { BlueprintDrawing, CompactBlueprintDrawing } from "./blueprint-drawing"; +import { HorizontalDimension } from "./dimensions"; import type { IllustrationStudy } from "./study-types"; +function RequestRoute() { + return ( + <> + + + + ); +} + +function AbsentPage({ dash = "4 5" }: { dash?: string }) { + return ( + + + + + + ); +} + function MissingDestination() { return ( - - - + + + - - - - - + + + + + - - - - - - - + + + - - + + + + + + - - - - - - - - Start + + + Request - - ∅ + + 404 - - 404 / Not found + + + + R40 - - An absent destination + + No page at this address + + + ); } +function MissingDestinationCompact() { + return ( + + + + + ); +} + export const stateStudies: IllustrationStudy[] = [ { id: "state-missing-page", code: "S01", title: "The missing destination", description: - "A clear route reaches the outline of a page that isn’t there. The absence belongs to the destination.", + "A paired route reaches a closed end one unit before an unbuilt page. The address remains projected, but the connection is absent.", category: "Empty states", illustration: , + emptyState: { + title: "Page not found", + description: "The page may have moved or no longer exists.", + illustration: , + }, }, ]; diff --git a/apps/ui/app/plotter/study-types.ts b/apps/ui/app/plotter/study-types.ts index 15072cb929..8d4c40cb7b 100644 --- a/apps/ui/app/plotter/study-types.ts +++ b/apps/ui/app/plotter/study-types.ts @@ -16,4 +16,9 @@ export type IllustrationStudy = { description: string; category: StudyCategory; illustration: ReactNode; + emptyState?: { + title: string; + description: string; + illustration: ReactNode; + }; }; diff --git a/apps/ui/app/plotter/system-state-studies.tsx b/apps/ui/app/plotter/system-state-studies.tsx new file mode 100644 index 0000000000..69c66191a2 --- /dev/null +++ b/apps/ui/app/plotter/system-state-studies.tsx @@ -0,0 +1,650 @@ +import { BlueprintDrawing, CompactBlueprintDrawing } from "./blueprint-drawing"; +import { HorizontalDimension, VerticalDimension } from "./dimensions"; +import type { IllustrationStudy } from "./study-types"; + +// The cloud’s three generating circles share the y = 200 baseline. The route +// has a 40-unit centerline radius and stops 32 units before its continuation. +function CloudForm() { + return ( + <> + + + + ); +} + +function CloudGap({ dash }: { dash: string }) { + return ( + <> + + + + ); +} + +function OfflineStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + This device + + + No connection + + + + + R40 + + + + ); +} + +function OfflineCompact() { + return ( + + + + + ); +} + +function LockForm() { + return ( + <> + + + + + ); +} + +function AccessRoute({ dash }: { dash: string }) { + return ( + <> + + + + ); +} + +function LockInset() { + return ; +} + +function AccessStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Request + + + Protected + + + + + R40 + + + R16 + + + 16 + + + Ø20 + + + 8 + + + Closed boundary + + + + + ); +} + +function AccessCompact() { + return ( + + + + + + + + ); +} + +function EnvelopeForm() { + return ( + <> + + + + ); +} + +function EnvelopeFolds({ dash }: { dash: string }) { + return ( + + + + + ); +} + +function EmailStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + Fold + + + One crease / two positions + + + + ); +} + +function EmailCompact() { + return ( + + + + + ); +} + +function polar(cx: number, cy: number, radius: number, degrees: number) { + const angle = (degrees * Math.PI) / 180; + const x = cx + radius * Math.cos(angle); + const y = cy + radius * Math.sin(angle); + return `${Math.round(x * 100) / 100} ${Math.round(y * 100) / 100}`; +} + +function gearOutline({ + cx, + cy, + teeth, + root, + tip, + phase, +}: { + cx: number; + cy: number; + teeth: number; + root: number; + tip: number; + phase: number; +}) { + const step = 360 / teeth; + const tipHalf = step * 0.14; + const rootHalf = step * 0.34; + const at = (radius: number, degrees: number) => + polar(cx, cy, radius, degrees); + const segments = Array.from({ length: teeth }, (_, index) => { + const angle = phase + index * step; + return `L${at(tip, angle - tipHalf)}A${tip} ${tip} 0 0 1 ${at(tip, angle + tipHalf)}L${at(root, angle + rootHalf)}A${root} ${root} 0 0 1 ${at(root, angle + step - rootHalf)}`; + }); + return `M${at(root, phase - rootHalf)}${segments.join("")}Z`; +} + +// A shared module gives pitch radii 72, 48 and 36 for 12, 8 and 6 teeth. +// The small pair’s pitch circles are tangent at (372, 172). The drive sits +// 40 units outside mesh; the schematic teeth have 2 units of tip clearance. +const gears = [ + { + cx: 212, + cy: 220, + pitch: 72, + teeth: 12, + root: 64, + tip: 78, + phase: 0, + hub: 18, + }, + { + cx: 372, + cy: 220, + pitch: 48, + teeth: 8, + root: 40, + tip: 54, + phase: 22.5, + hub: 12, + }, + { + cx: 372, + cy: 136, + pitch: 36, + teeth: 6, + root: 28, + tip: 42, + phase: 30, + hub: 9, + }, +]; + +function GearsForm() { + return ( + <> + {gears.map((gear) => ( + + + + + ))} + + ); +} + +function MaintenanceStudy() { + return ( + + + {gears.map((gear) => ( + + ))} + + + + + + + + + + + + + + Drive disengaged + + + + + R72 + + + R48 + + + 6 teeth + + + One pitch / 12 : 8 : 6 + + + + ); +} + +function MaintenanceCompact() { + return ( + + + + + ); +} + +const wallHatch = Array.from( + { length: 11 }, + (_, index) => `M432 ${144 + index * 16}l16-16`, +).join(" "); + +function PlugForm() { + return ( + <> + + + + + + ); +} + +function SocketRecesses({ dash }: { dash: string }) { + return ( + + ); +} + +function DisconnectedStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + Disconnected + + + + ); +} + +function DisconnectedCompact() { + return ( + + + + + + + ); +} + +// R40 sides meet a tip 76 below the head center. The tangent points sit +// 21.0526 below that center and 34.0116 to either side. +function PinForm() { + return ( + <> + + + + ); +} + +function CoordinateField({ + dash, + arrowheads = false, +}: { + dash: string; + arrowheads?: boolean; +}) { + return ( + + + + + {arrowheads && ( + + )} + + ); +} + +function LocationStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + Not set + + + + + R40 + + + x + + + y + + + 0 + + + + ); +} + +function LocationCompact() { + return ( + + + + + + ); +} + +export const systemStateStudies: IllustrationStudy[] = [ + { + id: "state-offline", + code: "S04", + title: "A cloud, out of reach", + description: + "A curved connection stops before the cloud. Its separated continuation and measured gap make the missing route visible.", + category: "Empty states", + illustration: , + emptyState: { + title: "You’re offline", + description: "Changes will sync when your connection returns.", + illustration: , + }, + }, + { + id: "state-access", + code: "S08", + title: "A boundary, locked", + description: + "A closed lock interrupts the access route. Its corner radius sets the inset, while a separate construction field locates the keyhole without crossing it.", + category: "Empty states", + illustration: , + emptyState: { + title: "Access restricted", + description: "Ask a workspace owner to grant you access.", + illustration: , + }, + }, + { + id: "state-email", + code: "S09", + title: "An envelope, unfolded", + description: + "An open flap mirrors its folded position across one crease. Equal offsets and the pocket’s diagonals explain the envelope as a flat construction.", + category: "Empty states", + illustration: , + emptyState: { + title: "Check your email", + description: "We sent a sign-in link to your inbox.", + illustration: , + }, + }, + { + id: "state-maintenance", + code: "S10", + title: "A gear train, interrupted", + description: + "Three gears share one tooth pitch. The two smaller gears engage, while a 40-unit pitch-circle gap keeps the main drive disconnected.", + category: "Empty states", + illustration: , + emptyState: { + title: "Under maintenance", + description: "Settings are temporarily unavailable. Check back soon.", + illustration: , + }, + }, + { + id: "state-disconnected", + code: "S11", + title: "A connection, apart", + description: + "A mobile plug aligns with a fixed socket in a hatched wall section. Two projected contact axes cross the measured gap between them.", + category: "Empty states", + illustration: , + emptyState: { + title: "Integration disconnected", + description: "Reconnect your account to resume syncing.", + illustration: , + }, + }, + { + id: "state-location", + code: "S12", + title: "A pin, unplaced", + description: + "A compact pin sits just above an unresolved point in a broad coordinate field. The measured horizontal offset locates the proposed position.", + category: "Empty states", + illustration: , + emptyState: { + title: "No location set", + description: "Add an address so attendees know where to meet.", + illustration: , + }, + }, +]; diff --git a/apps/ui/app/plotter/system-studies.tsx b/apps/ui/app/plotter/system-studies.tsx index c1231b95c2..7190f7cc7b 100644 --- a/apps/ui/app/plotter/system-studies.tsx +++ b/apps/ui/app/plotter/system-studies.tsx @@ -14,53 +14,39 @@ function Registration() { function TangencyStudy() { return ( - - - + + + - - - - - + + + - - - - - - - - - + + + + - - - - + + + + + + + - + 2r - + r - - T₁ - - - T₂ + + d - - 90° + + Common tangent @@ -399,7 +385,7 @@ export const systemStudies: IllustrationStudy[] = [ code: "P01", title: "Tangency", description: - "A circle meets two straightedges. The contact points make a simple construction rule the central gesture.", + "Two radii share a baseline and an inclined tangent. The contact geometry connects unequal circles through one construction rule.", category: "Foundations", illustration: , }, diff --git a/apps/ui/skills/plotter/SKILL.md b/apps/ui/skills/plotter/SKILL.md index c2559026a6..76ff04bf1b 100644 --- a/apps/ui/skills/plotter/SKILL.md +++ b/apps/ui/skills/plotter/SKILL.md @@ -1,6 +1,6 @@ --- name: plotter -description: Create, critique, and refine flat monochrome blueprint illustrations in the established coss UI style, using Primitives and Particles as visual anchors. Use for technical hero illustrations, service concepts, interface construction studies, scheduling diagrams, and empty states in this style; not general architectural drafting or generic line art. +description: Create, critique, and refine flat monochrome blueprint illustrations in the established coss UI style, using the user-approved construction studies as visual references. Use for technical hero illustrations, service concepts, interface construction studies, scheduling diagrams, and empty states in this style; not general architectural drafting or generic line art. --- # Plotter @@ -9,11 +9,11 @@ Build a precise planar composition that communicates a construction relationship ## Establish the reference -Before drawing, inspect [Primitives](references/primitives.png) and [Particles](references/particles.png). These are the strongest user-named visual anchors. Match their composition, hierarchy, and restraint; the screenshots include page context, which is not a template to copy wholesale. For service concepts, also inspect [Linear](references/linear.png), which the user explicitly loved. +Before drawing or judging style, read the [approved reference set](references/approved-references.md). It records the user’s exact 27 selections and supersedes earlier broad endorsements. Choose a reference by its construction relationship, then inspect that drawing. [Primitives/P00](references/primitives.png) is the general geometric anchor and [Linear/B04](references/linear.png) is a strong service example; use the approved subject-specific studies for other compositions. -The current coss selections are P00 for the main page, [C03 for Particles](references/particles-recombination.png), and [E02 for the 404](references/not-found-interrupted-path.png). The Plotter showcase keeps independent snapshots of these drawings. Inspect the host page’s component when refining a production integration; the original Particles drawing remains a style anchor. +The current production selections are P00 for the main page, C03 for Particles, and E02 for the 404. Production placement is distinct from reference approval: C03 and the original Particles/C00 are outside the current approved reference set. Their prior feedback remains useful history, not a reason to copy them as the default style. -Read [direction and feedback](references/direction.md) for the current evidence, relevant subject guidance, and unresolved choices. User feedback in the current conversation takes precedence. A study’s presence in the gallery does not make it an approved reference. +Read [direction and feedback](references/direction.md) for implementation choices and earlier review evidence. The current approved reference set takes precedence over historical “unreviewed” labels. A drawing’s presence in the gallery does not establish approval. ## Compose diff --git a/apps/ui/skills/plotter/references/approved-references.md b/apps/ui/skills/plotter/references/approved-references.md new file mode 100644 index 0000000000..2b246774d6 --- /dev/null +++ b/apps/ui/skills/plotter/references/approved-references.md @@ -0,0 +1,55 @@ +# Approved references + +On 2026-10-25, the user explicitly identified these 27 drawings as the best illustrations and asked that they be used as references. This list supersedes earlier broad endorsements and pending-review labels. Match the current approved revisions, not an earlier iteration with the same code. + +## Reference set + +Choose the nearest construction relationship from this set before drawing. Inspect it in the Plotter gallery at `/ui/plotter?variant=`; locate its editable SVG by searching its code in `apps/ui/app/plotter/`. A familiar subject alone is not a sufficient reference match. + +| Code | Drawing | Study ID | +| --- | --- | --- | +| P00 | Circle, square, line | `primitives-reference` | +| B04 | Linear · Work aligned to a cycle | `service-linear-cycle` | +| K06 | A week, constructed | `cal-week-grid` | +| I06 | A conversation, constructed | `interface-chat-plan` | +| E02 | An interrupted path | `404-interrupted-path` | +| P02 | The corner rule | `primitives-radius` | +| P03 | A system of proportions | `primitives-proportion` | +| C01 | An assembly of parts | `particles-assembly` | +| C02 | A modular tray | `particles-slots` | +| K07 | The shape of a time slot | `cal-time-modules` | +| K08 | Where schedules meet | `cal-availability-plan` | +| I03 | From parts to a whole | `interface-spacing-plan` | +| W03 | A connection, located | `workflow-switch-plan` | +| A03 | A measured distribution | `data-coordinate-plan` | +| B01 | Cloudflare · Between visitor and origin | `service-cloudflare-edge` | +| B02 | Vercel · From preview to production | `service-vercel-deploy` | +| B03 | Stripe · A balance, assembled | `service-stripe-settlement` | +| B05 | Stripe · Many ways to pay | `service-stripe-payment` | +| B06 | GitHub · A change, brought together | `service-github-merge` | +| B07 | Figma · Rules that resize | `service-figma-constraints` | +| B09 | Supabase · A shared relation | `service-supabase-relations` | +| K02 | Common ground | `cal-availability` | +| T01 | Anatomy of a letter | `type-letter` | +| T02 | A mark in the making | `type-monogram` | +| E01 | An empty threshold | `404-doorway` | +| E06 | An unbuilt arch | `404-unbuilt-arch` | +| E00 | The missing corner | `404-reference` | + +## Scope of the decision + +The user said the remaining illustrations do not look perfect or do not fit the style for different reasons, and could not yet identify those reasons. Treat them as revision candidates, not style references. This feedback does not request their deletion or establish a specific diagnosis for each drawing. + +The omitted current static drawings are C00, C03, P01, K03, I04, I05, W02, O03, O04, O05, B08, S01, and S03–S14. In particular, original Particles/C00 is now a historical reference; C03’s production placement is a separate implementation decision. Preserve the user’s specific earlier feedback on details without promoting the whole drawing. This static selection does not revoke the separately reviewed animation work. + +## Working interpretation + +These are hypotheses from comparing the selected drawings with the remaining studies, not additional user-stated rules: + +- **Make a construction relationship the subject.** P00 relates circle and square; C01/I03 assemble parts; B04 aligns work to a cycle; B07 demonstrates constraints. Recognizability and technical annotations alone have been less reliable, as in the lamp, phone, and browser studies. +- **Give the composition a focal point.** K06 selects an interval, W03 selects a junction, and E00 leaves one quadrant unresolved. O05’s equally weighted folders and B08’s repeated blocks offer a less distinct central event. +- **Use measurements to explain the main idea.** P03’s progression and K07’s interval lengths support their composition. Multiple unrelated product dimensions can scatter attention, as in O04’s shade, stem, base, and overall height. +- **Spend detail on the organizing rule.** I06 reduces conversation to avatars, tail-free message blocks, and spacing. O03/I04 devote more attention to keyboard legends and browser chrome. A keyboard or browser remains a possible subject; simplify the representation around one useful relationship. +- **Make absence affect the structure.** E00’s missing corner, E02’s interrupted route, and E06’s absent keystone visibly change the construction. This is a more dependable starting point than selecting an empty-state icon and surrounding it with measurements. + +Do not force every omission into one explanation. P01 is close to the established geometric language; its weaker fit may concern composition or distinctiveness. Some newer empty-state drawings already contain structural ideas. T01/T02 and E01 show that recognizable forms can be successful. Evaluate the specific drawing against the closest approved reference instead of banning a subject category. diff --git a/apps/ui/skills/plotter/references/direction.md b/apps/ui/skills/plotter/references/direction.md index 3c55b9613b..ad4f372fa3 100644 --- a/apps/ui/skills/plotter/references/direction.md +++ b/apps/ui/skills/plotter/references/direction.md @@ -2,19 +2,49 @@ Evidence captured from the coss UI illustration reviews on 2026-10-24–25. This is a living art-direction record, not a claim that every study has been approved. -## Anchors +## Current references -The user explicitly called the original Primitives and Particles illustrations the better work and asked that they guide further experiments. Their screenshots are bundled beside this file. Primitives centers a circle/square relationship with radius, tangency, alignment, and restrained hatching. Particles composes a pill, label row, and toggle fragment on a common planar construction frame. +The user explicitly selected the [27 approved references](approved-references.md) on 2026-10-25. Use that exact set for future drawing and critique. It supersedes the earlier blanket emphasis on original Primitives/Particles and the historical pending-review labels below. Other current examples need refinement or a clearer fit; the user did not yet specify a reason for each and did not ask to remove them. -The user also explicitly said they “love the Linear example” (B04). Use [Linear](linear.png) as a service-concept reference: issue units aligned to a cycle and release. This endorses that example, not every service drawing. +Original Primitives/P00 and Linear/B04 remain approved. Original Particles/C00 is now a historical reference. Earlier endorsements of a particular detail, such as C00’s arrow caps, remain evidence about that detail rather than approval of the whole composition. -The former lab assigned reference status to the entire Primitives/Particles categories. That metadata exceeded the user’s clearest endorsement of the original two drawings and is not an approval source. +## Reference-led refinement pass + +The user next asked to improve the remaining drawings using the selected references. All 24 candidates were revised in place: C00, C03, P01, K03, I04, I05, W02, O03, O04, O05, B08, S01, and S03–S14. Their IDs and subjects remain stable; the 27 approved SVGs are unchanged. These new revisions await user review and are not additional style references. + +The implementation response concentrates each drawing on one relationship: + +- P01 shares a tangent between two radii; K03 aligns one booking across two hour scales; W02 selects one route through a measured junction. +- C00 repeats an inset across fragments; C03 recombines equal-width parts into three equal rows; B08 inserts a reusable block into a matching page slot. +- O03 shows quarter-pitch stagger, I04 an enlarged browser corner, I05 a concentric frame and safe area, O04 a semicircle on its support axis, and O05 a tab shared by two folder widths. +- The empty states give absence a structural role: a missing index entry, an ended notification stream, an unwritten page, an empty coordinate field or week, an unoccupied image frame, an interrupted route, a locked boundary, a pending message, separated pitch circles, disconnected contacts, or an unresolved location. + +The revised forms are transparent. Grid paths are omitted or split where they would add noise or cross labels; no background patches hide them. Hero and compact empty-state versions were checked separately. This pass changes the showcase’s static studies, not the production heroes or the animation sequences. + +### Follow-up on the revised set + +The user reviewed ten studies and gave these corrections. This is specific feedback on the reference-led pass, not approval or rejection of the other revisions: + +- **C00/C03:** the user clarified that C02 is the switch reference. Use its 30 × 18 track, R9 corners, R5.5 thumb, 3.5 inset, and primary 0.95 stroke at full opacity. Matching C00 to C03’s muted secondary stroke did not resolve the inconsistency; geometry and stroke treatment must both match C02. The animated switch already follows this treatment. +- **I04:** the browser’s upper-left radius is excessive and its three chrome circles are undersized. Reduce the corner and enlarge/re-align the controls within the same detail crop. +- **I05:** the external lines feel bland. Use exterior dimensions and extensions to explain the frame and safe-area insets; extra lines need a geometric purpose. +- **B08:** the insertion route and arrow should be dashed. Treat it as movement, with a solid open cap meeting the first dash at its tip. +- **S13:** the circle around Wednesday feels inconsistent. Use a restrained weekday underline while retaining the column alignment. +- **S08:** the lock body lacks construction geometry. Explain the rounded body, keyhole axis, and their relationship, not just the shackle. +- **S09:** the separated letter/envelope result is not liked. The response uses one open envelope with its folded flap projected across the crease. +- **S10:** the two-gear result is not liked; the user suggests a third, smaller gear. The response uses a 12/8/6-tooth train: the smaller pair engages while the main drive remains separated. Keep tooth pitch and construction circles coherent across the mechanism. +- **S11:** the symmetric plug/socket revision is worse; the user preferred the former wall section on the right. Restore the fixed wall/socket relationship and its section hatching. +- **S12:** the composition is too vertical. Broaden the coordinate field and shorten the pin-to-target relationship without rotating the represented object. + +These responses remain subject to review. Preserve the other studies, the approved reference set, and the animation work. ## Production selections -The user explicitly selected **P00** for the main page, [**C03** for Particles](particles-recombination.png), and [**E02** for the 404](not-found-interrupted-path.png). These are current implementation decisions. The original Primitives/Particles references and loved Linear study still guide the broader style. The Plotter showcase owns independent snapshots of the selected drawings, so it can ship separately from the production integration. Original C00 and E00 remain separate examples. +The user explicitly selected **P00** for the main page, [**C03** for Particles](particles-recombination.png), and [**E02** for the 404](not-found-interrupted-path.png). These are current implementation decisions. Use the current approved reference set for the broader style; C03’s production selection does not make it a reference. The Plotter showcase owns independent snapshots of the selected drawings, so it can ship separately from the production integration. Original C00 and E00 remain separate examples. + +## Historical review evidence -## Review evidence +These entries record earlier feedback and its implementation response. Current reference status comes from the explicit selection above. | Evidence | Status and implication | | --- | --- | @@ -29,26 +59,26 @@ The user explicitly selected **P00** for the main page, [**C03** for Particles]( | I03: good, not an excellent example | Revised from a single generic row to separate elements projecting into a shared control. | | W03: a bit weak | Replaced the isolated switch with a switching matrix and one selected junction. | | W02: does not really seem a blueprint; user could not say why | Replaced rounded endpoint cards and checkmarks with a continuous route whose width, bend radius, and branch spacing are measured. | -| Response to those five revisions: “much better” | Collective positive feedback. Promising revised examples, not five individually designated canonical anchors. | +| Response to those five revisions: “much better” | Initially collective positive feedback. K06, K08, I03, and W03 were later explicitly selected as references; W02 was omitted. | | E03/E05 set aside by the assistant | Inferred conflict with the no-tilt rule; not individually rejected by the user. | -| O03 keyboard: “doesn’t look good”; asks to show a portion and use more subtle/dashed lines | Revised as a deliberate keyboard detail with credible staggered rows and modifiers, quiet outlines, and dashed continuation. Revision unreviewed. | -| I04 browser: “same problem as keyboard before” | Interpreted as a request for a deliberate enlarged browser detail, with subdued strokes and dashed continuation. Latest revision unreviewed. | -| I06 chat: asks to add an avatar and remove the triangle on the bubble | Pair rounded, tail-free message blocks with circular avatars. Latest revision unreviewed. | -| O05 folder: asks for two or three instead of one | Revised as three separate flat outlines sharing tab geometry and alignment. Revision unreviewed. | +| O03 keyboard: “doesn’t look good”; asks to show a portion and use more subtle/dashed lines | Revised as a deliberate keyboard detail with credible staggered rows and modifiers, quiet outlines, and dashed continuation. O03 is outside the current approved reference set. | +| I04 browser: “same problem as keyboard before” | Interpreted as a request for a deliberate enlarged browser detail, with subdued strokes and dashed continuation. I04 is outside the current approved reference set. | +| I06 chat: asks to add an avatar and remove the triangle on the bubble | Pair rounded, tail-free message blocks with circular avatars. I06 was later explicitly selected as a reference. | +| O05 folder: asks for two or three instead of one | Revised as three separate flat outlines sharing tab geometry and alignment. O05 is outside the current approved reference set. | | E04: “remove” | Explicitly removed. This is a choice about E04, not a blanket rejection of all absence metaphors. | | E02: good idea but bland/weak | Preserve the idea while strengthening its composition. Selected for the 404 page after revision. | -| B02 Vercel: not loved; wants more conceptual/metaphoric, then asks whether it is accurate and requests improvement | Replaced the broadcast-like aperture with separate preview frames and one selected preview promoted to an identical production frame. “Preview” / “Production” replaces “Draft” / “Live” because previews are already reachable deployments. Matching outlines identify the same source version, not the same build: preview promotion rebuilds for the production environment. User approved this direction of work; the resulting drawing is still unreviewed. | -| B03 Stripe: “ok”; wants an alternative focused on what it does | Keep B03 and add B05 as an alternative. “Ok” is not a request to replace it or make it a canonical anchor. | -| B01 Cloudflare: asks whether the representation is accurate | A request for factual review, not approval or rejection. Scope the concept clearly; latest revision unreviewed. | -| B09 Supabase: points to the crowded connector at owner_id and says it does not look good | Replaced the branching endpoint with a circular port and a short straight approach after the bend. This is a critique of that junction, not rejection of the study or all relationship notation. Revision unreviewed. | +| B02 Vercel: not loved; wants more conceptual/metaphoric, then asks whether it is accurate and requests improvement | Replaced the broadcast-like aperture with separate preview frames and one selected preview promoted to an identical production frame. “Preview” / “Production” replaces “Draft” / “Live” because previews are already reachable deployments. Matching outlines identify the same source version, not the same build: preview promotion rebuilds for the production environment. The revised B02 drawing was later explicitly selected as a reference. | +| B03 Stripe: “ok”; wants an alternative focused on what it does | Kept B03 and added B05 as an alternative. Both were later explicitly selected as references. | +| B01 Cloudflare: asks whether the representation is accurate | Initially a request for factual review. Scope the concept clearly; B01 was later explicitly selected as a reference. | +| B09 Supabase: points to the crowded connector at owner_id and says it does not look good | Replaced the branching endpoint with a circular port and a short straight approach after the bend. This critiques that junction, not all relationship notation. Revised B09 was later explicitly selected as a reference. | | B04 Linear: “love the Linear example” | Explicit positive reference for service concepts; preserve it. | -| Request for more popular-service examples | New B05–B09 are exploration, awaiting user review. | +| Request for more popular-service examples | B05, B06, B07, and B09 were later explicitly selected as references; B08 was omitted. | The earlier K06/K08/I03/W02/W03 revision pass is captured in coss commit `285493c9` on `codex/blueprint-illustration-studies`. The preceding flat-direction pass is `e9077a93`. Preserve this evidence if gallery metadata later changes. ## Showcase curation -The user subsequently asked to remove unwanted and discarded examples and turn the lab into a showcase named **Plotter illustrations**. The collection now contains 39 illustrations at `/ui/plotter`, with category filters and enlarged previews. Shortlists, review controls, and page-hero mockups have been removed. +The user subsequently asked to remove unwanted and discarded examples and turn the lab into a showcase named **Plotter illustrations**. The gallery at `/ui/plotter` has category filters and enlarged previews. Shortlists, review controls, and page-hero mockups have been removed. The 13 explicitly rejected entries (K01, K04, K05, I01, I02, D01, D02, W01, O01, O02, A01, A02, S02) and the assistant-set-aside E03/E05 were removed from the drawing code. E04 was already absent. W02 stays because its revised planar route received the later collective “much better” response. Retained revisions of other critiqued examples remain subject to the evidence above: curation is not individual approval or promotion to a style anchor. @@ -76,10 +106,55 @@ The first Slider trial was weak: the user could barely see its dotted line and w The user questioned whether the sector animation suited an avatar but said they liked it. After a suggestion to rename the stage Circle, they explicitly chose to leave it as is and commit. Preserve both the Avatar label and its sector construction. +The user next requested an animation for the Particles page, specifically C03. They liked the top-to-bottom assembly concept but criticized the first trial’s unstable grid, vacant destination, and stretching elements. They found the subsequent centered exploded stack boring and explicitly asked to restore the original concept, suggesting two isolated grids like B03 and a lower block that starts with only a dashed border. Build bottom-up and separate top-down using clear side lanes, with one owner for each joined seam. Both grids remain stationary. + +The next feedback explicitly requests fully rounded A, B, and C in their initial positions and dashed outlines left behind when they depart. Keep fragments rigid at 3u × 1u during travel; flatten their shared corners only at contact. Reveal the 3u × 3u dimensions and A + B + C only after assembly, then widen the completed block, and toggle the switch later. This post-assembly enlargement is explicitly requested; it does not endorse stretching separate fragments in transit. The current trial expands to 6u × 3u around its center, preserving stroke widths, corner radii, text, and control sizes. Update the width annotation with the geometry; shrink before separation. Preserve pause, offscreen pause, and reduced-motion behavior in the Particles hero and Plotter preview. This revision is awaiting review. + +The user then asked to improve C03’s grid by avoiding lines that cut through the elements. They explicitly rejected masking or covering the existing lines: rework the grid geometry itself. The lower field’s half-cell vertical offset put horizontal strokes through row centers, making it look misplaced. Use fixed, intentional edge and row-alignment guides, aligned to the actual row boundaries and the composition’s common center. Leave unnecessary interior strokes out of the paths entirely; no animated grid masks or background patches. This grid revision is awaiting review. + +For C03’s three-assembly animation, the user wants the parts to become a new common shape. They rejected withdrawing whole borders and retaining independent boxes inside a new container. Exterior paths must form the silhouette themselves, with one owner for each shared divider. They then asked to improve the colliding-edge morph and explicitly rejected changing the movement: “i just wanted the colliding edge morph be better.” Preserve the original straight eased trajectories, travel-time resizing, stagger, receiver guides, and chapter pacing. Do not add curved docking paths or early enlargement to solve this detail. The current response separates straight edges from exterior corner arcs: paired corners retain their radius until contact, then flatten together over 0.22 seconds while the surviving edge becomes the secondary divider. Only the duplicate facing line feathers out over the last 0.08 seconds before contact; no whole fragment outline disappears. Keep the 0.9-second flights, 5.4-second chapters, 16.2-second loop, source ghosts, fixed grids, and later switch toggle. This edge-only revision awaits review and stays separate from the production hero. + ## Expanded subject brief The user explicitly requested a keyboard, browser window, table lamp, folder, smartphone, chat, and service concepts for Cloudflare, Vercel, Stripe, and Linear, then asked for more popular-service examples. These are requested exploration areas; only the specific feedback above establishes preference or approval. The expansion does not rehabilitate earlier rejected object drawings. Render familiar objects as flat plans or elevations with meaningful construction relationships. Preserve the established no-perspective rule. +## Empty-state set + +The user asked for concepts, states, or objects that fit this style and are in high demand, and wanted advice. They chose all twelve suggested object-for-state pairings, static, at both hero and compact empty-state sizes: + +- search (no results) +- offline +- notifications +- files +- data +- access +- email +- maintenance +- disconnected integration +- location +- bookings +- image + +These are S03–S14 under Empty states. Each one adds a 96px version, shown in the coss `Empty` component in its preview dialog. The compact version reuses the hero geometry with non-scaling 1px strokes and no labels. All twelve remain outside the current approved reference set. + +When the user asked for an honest assessment, the assistant judged most of the set to be annotated pictograms. In those, the state lived in a caption rather than the geometry, and the compact versions read as generic outline icons. The user replied “ok improve them”. The four strongest were reworked using E02’s grammar: + +- **S04:** a route from this device, cut by break slashes, with a dashed ghost reaching a cloud of tangent circles. +- **S06:** an empty text area scaled on the page diagonal, holding only a cursor. +- **S11:** the measured gap is labeled as the state. +- **S12:** the pin is held above an empty dashed target. + +The reworked studies use two label tiers: semantic labels at 11 / 72% and measurements at 10 / 50%. Each compact version keeps one construction cue that survives at 96px: the break, the empty text area, the gap, or the target. Asked whether to cut, rework, or keep the remaining eight, the user chose to rework them one or two at a time for review. Two more have been reworked so far: + +- **S03:** a typed query above a dashed four-row results list, in the style of C03’s interface fragments. +- **S10:** two gears with matching teeth, their pitch circles 40 apart instead of tangent. + +At that point S05, S07–S09, S13, and S14 remained to be reworked; the reference-led refinement pass above now covers them. + +On S11, the user noted that drawings like these often use tiny arrows to show direction. They expected one pointing right along the dashed prong lines. Use small open arrowheads in the secondary-line style on a path or axis that implies movement. Place them within the gap or route, pointing toward the destination. Hero only: they vanish at 96px. + +The user then pointed to E02, C00, and P02 as well-executed arrow caps, and to C03 and S11 as inconsistent. Draw every cap the way those three do: an open 90° chevron, each leg about 6 long at 45° to its shaft. Put it in the same group and opacity as the shaft, with the tip exactly on the end of a drawn line. On a 45° shaft the legs are axis-aligned (`m-6 0h6v-6`). On an axis-aligned shaft they offset ±4.24 on both axes. On other angles, rotate the reversed shaft direction by ±45°. On a dashed shaft, start the path at the tip so a dash, not a gap, meets the cap, or place the tip at the end of a dash. + ## Working interpretations These diagnoses come from examining the drawings, not from more specific user quotations: @@ -89,7 +164,7 @@ These diagnoses come from examining the drawings, not from more specific user qu - The main geometry should carry the idea; adding technical marks to a weak subject does not make it fit. - Perspective belongs to the represented object, not every diagonal mark. Radius lines and annotation leaders remain useful. - For O03, interpreting “show a portion” as an intentional detail crop resolves the earlier ambiguous miniature keyboard. The user asked for a portion and subdued/dashed lines; credible stagger and modifier widths are our implementation response. -- Service metaphors can be conceptually clear without illustrating every product. Accuracy concerns the relationship depicted and its stated scope. In the Vercel study, we diagnosed the expanding arcs as too suggestive of broadcasting; the user requested improvement after that explanation. The selected-preview construction is our response, not a newly approved anchor. +- Service metaphors can be conceptually clear without illustrating every product. Accuracy concerns the relationship depicted and its stated scope. In the Vercel study, we diagnosed the expanding arcs as too suggestive of broadcasting; the user requested improvement after that explanation. The selected-preview construction was our response and was later explicitly included in the approved reference set. ## Subject guidance diff --git a/apps/ui/skills/plotter/references/implementation.md b/apps/ui/skills/plotter/references/implementation.md index 9f1c94c89e..06609093c9 100644 --- a/apps/ui/skills/plotter/references/implementation.md +++ b/apps/ui/skills/plotter/references/implementation.md @@ -29,7 +29,9 @@ Find the checkout from the current workspace; the skill does not require a parti | Feedback evidence | `apps/ui/skills/plotter/references/direction.md` | | Typography rules | `packages/ui/src/fonts/font-guide.md` | -The showcase owns its drawings and wrapper independently of the production integration in PR #912. Use its `BlueprintDrawing` wrapper for compatible illustrations. It supplies monochrome foreground, Cal Sans, automatic optical sizing, and GEOM25. Preserve semantic surface masks such as `var(--color-sidebar)` and inspect them against the actual host background. Static illustration components do not need client-side state. +The showcase owns its drawings and wrapper independently of the production integration in PR #912. Use its `BlueprintDrawing` wrapper for compatible illustrations. It supplies monochrome foreground, Cal Sans, automatic optical sizing, and GEOM25. Follow the current transparency and grid-construction feedback in [direction.md](direction.md) when revising these drawings. Static illustration components do not need client-side state. + +Choose style references from [the approved set](approved-references.md); production placement and bundled historical screenshots are separate from reference approval. Keep production page layouts local, as explicitly requested by the user. Do not recreate a shared page-hero component. The showcase displays standalone drawings in a gallery with enlarged previews. From a6f43527d84f1a32127ec84e54d4e6720a07e194 Mon Sep 17 00:00:00 2001 From: pasqualevitiello Date: Sun, 25 Oct 2026 15:41:37 +0100 Subject: [PATCH 08/15] feat(ui): expand Plotter studies and refine motion previews --- .../coss-concepts/common-interface-study.tsx | 184 +++++++ apps/ui/app/plotter/coss-concepts/index.tsx | 65 +++ .../coss-concepts/shared-system-study.tsx | 100 ++++ apps/ui/app/plotter/disconnected-plug.tsx | 92 ++++ .../engineering-motion-preview.module.css | 13 + .../plotter/engineering-motion-preview.tsx | 104 ++++ apps/ui/app/plotter/engineering-studies.tsx | 448 ++++++++++++++++++ apps/ui/app/plotter/illustration-gallery.tsx | 174 ++----- apps/ui/app/plotter/illustrations.tsx | 2 + apps/ui/app/plotter/missing-destination.tsx | 104 ++++ .../plotter/newtons-cradle-motion-drawing.tsx | 183 +++++++ .../not-found-architecture-studies.tsx | 4 +- .../plotter/not-found-geometry-studies.tsx | 2 +- .../app/plotter/not-found-motion.module.css | 13 + apps/ui/app/plotter/not-found-motion.tsx | 117 +++++ apps/ui/app/plotter/page.tsx | 114 ++++- apps/ui/app/plotter/primitives-blueprint.tsx | 13 +- .../production-chain-motion-drawing.tsx | 261 ++++++++++ apps/ui/app/plotter/service-studies.tsx | 10 +- apps/ui/app/plotter/state-studies.tsx | 94 +--- apps/ui/app/plotter/system-state-studies.tsx | 248 ++++------ apps/ui/skills/plotter/SKILL.md | 6 +- .../references/coss-product-concepts.md | 46 ++ .../ui/skills/plotter/references/direction.md | 40 +- .../plotter/references/drafting-standard.md | 98 ++++ .../plotter/references/implementation.md | 12 +- .../references/remaining-source-audit.md | 89 ++++ .../skills/plotter/references/source-audit.md | 163 +++++++ .../plotter/references/verification-prompt.md | 112 +++++ 29 files changed, 2507 insertions(+), 404 deletions(-) create mode 100644 apps/ui/app/plotter/coss-concepts/common-interface-study.tsx create mode 100644 apps/ui/app/plotter/coss-concepts/index.tsx create mode 100644 apps/ui/app/plotter/coss-concepts/shared-system-study.tsx create mode 100644 apps/ui/app/plotter/disconnected-plug.tsx create mode 100644 apps/ui/app/plotter/engineering-motion-preview.module.css create mode 100644 apps/ui/app/plotter/engineering-motion-preview.tsx create mode 100644 apps/ui/app/plotter/engineering-studies.tsx create mode 100644 apps/ui/app/plotter/missing-destination.tsx create mode 100644 apps/ui/app/plotter/newtons-cradle-motion-drawing.tsx create mode 100644 apps/ui/app/plotter/not-found-motion.module.css create mode 100644 apps/ui/app/plotter/not-found-motion.tsx create mode 100644 apps/ui/app/plotter/production-chain-motion-drawing.tsx create mode 100644 apps/ui/skills/plotter/references/coss-product-concepts.md create mode 100644 apps/ui/skills/plotter/references/drafting-standard.md create mode 100644 apps/ui/skills/plotter/references/remaining-source-audit.md create mode 100644 apps/ui/skills/plotter/references/source-audit.md create mode 100644 apps/ui/skills/plotter/references/verification-prompt.md diff --git a/apps/ui/app/plotter/coss-concepts/common-interface-study.tsx b/apps/ui/app/plotter/coss-concepts/common-interface-study.tsx new file mode 100644 index 0000000000..0996931170 --- /dev/null +++ b/apps/ui/app/plotter/coss-concepts/common-interface-study.tsx @@ -0,0 +1,184 @@ +import { BlueprintDrawing } from "../blueprint-drawing"; + +// Conceptual assembly, not a physical part or infrastructure diagram. +// Four outer profiles stand for the catalog services. Their common circular +// opening stands for the shared SDK contract. The lifted quadrant is a planar +// translation (u, −u), never a rotation or a perspective/extrusion operation. +const unit = 32; +const center = { x: 264, y: 224 }; +const radius = 2 * unit; +const displaced = { x: center.x + unit, y: center.y - unit }; +const arrow = 6 / Math.SQRT2; + +type Interval = readonly [number, number]; + +function remaining(start: number, end: number, cuts: Interval[]) { + const segments: Interval[] = []; + let cursor = start; + for (const [from, to] of cuts.sort((a, b) => a[0] - b[0])) { + if (from > cursor) segments.push([cursor, Math.min(from, end)]); + cursor = Math.max(cursor, to); + if (cursor >= end) break; + } + if (cursor < end) segments.push([cursor, end]); + return segments.filter(([from, to]) => to > from); +} + +const labelBounds = [ + [162, 130, 216, 151], + [348, 89, 390, 110], + [164, 308, 237, 331], + [300, 308, 347, 331], +] as const; + +function ConstructionGrid() { + const paths: string[] = []; + // The grid measures the 32-unit profile steps. Clear the common opening, + // selected interface strip, text, and segments already owned by an outline. + for (const x of [168, 200, 232, 296, 328, 360]) { + const cuts: Interval[] = []; + const dx = x - center.x; + if (Math.abs(dx) < radius) { + const dy = Math.sqrt(radius ** 2 - dx ** 2); + cuts.push([center.y - dy, center.y + dy]); + } + if (dx <= 0 && dx > -80) { + cuts.push([center.y, center.y + Math.sqrt(80 ** 2 - dx ** 2)]); + } + const displacedX = x - displaced.x; + if (displacedX >= 0 && displacedX < radius) { + cuts.push([ + displaced.y - Math.sqrt(radius ** 2 - displacedX ** 2), + displaced.y, + ]); + } + for (const [left, top, right, bottom] of labelBounds) { + if (x >= left && x <= right) cuts.push([top, bottom]); + } + if (x === 168) cuts.push([352, 368]); + if (x === 296) cuts.push([64, 128]); + if (x === 360) cuts.push([288, 352]); + paths.push(...remaining(80, 368, cuts).map(([a, b]) => `M${x} ${a}V${b}`)); + } + for (const y of [128, 160, 192, 256, 288, 320]) { + const cuts: Interval[] = []; + const dy = y - center.y; + if (Math.abs(dy) < radius) { + const dx = Math.sqrt(radius ** 2 - dy ** 2); + cuts.push([center.x - dx, center.x + dx]); + } + if (dy >= 0 && dy < 80) { + cuts.push([center.x - Math.sqrt(80 ** 2 - dy ** 2), center.x]); + } + const displacedY = y - displaced.y; + if (displacedY <= 0 && displacedY > -radius) { + cuts.push([ + displaced.x, + displaced.x + Math.sqrt(radius ** 2 - displacedY ** 2), + ]); + } + for (const [left, top, right, bottom] of labelBounds) { + if (y >= top && y <= bottom) cuts.push([left, right]); + } + if (y === 192) cuts.push([360, 424]); + if (y === 288) cuts.push([360, 392]); + paths.push(...remaining(120, 440, cuts).map(([a, b]) => `M${a} ${y}H${b}`)); + } + return ; +} + +function InterfaceHatch() { + const paths: string[] = []; + // Selected common-profile strip: SW quadrant, 64 ≤ r ≤ 80. For x+y=k, + // circle intersections are solved analytically and the inner disk removed. + // This highlights the matching edge; it does not assert a cut material. + for (let sum = 408; sum <= 564; sum += 12) { + const offset = sum - center.x - center.y; + const outerRoot = Math.sqrt(2 * 80 ** 2 - offset ** 2); + const low = (offset - outerRoot) / 2; + const high = Math.min((offset + outerRoot) / 2, 0, offset); + const cuts: Interval[] = []; + if (Math.abs(offset) < radius * Math.SQRT2) { + const innerRoot = Math.sqrt(2 * radius ** 2 - offset ** 2); + cuts.push([(offset - innerRoot) / 2, (offset + innerRoot) / 2]); + } + for (const [a, b] of remaining(low, high, cuts)) { + paths.push( + `M${center.x + a} ${sum - center.x - a}L${center.x + b} ${sum - center.x - b}`, + ); + } + } + return ; +} + +export function CommonInterfaceStudy() { + return ( + + + + + + + + + {/* The unoccupied position is projected exactly, not a second part. */} + + + + + + + {/* One owner for every shared seam in the assembled three parts. */} + + + {/* The same NE quadrant translated by (32, −32). */} + + + + + + + + + + + + + + Email + + + SMS + + + Scheduling + + + Video + + + One interface + + + + + r + + + r + + + u + + + One SDK · One key per environment + + + + ); +} diff --git a/apps/ui/app/plotter/coss-concepts/index.tsx b/apps/ui/app/plotter/coss-concepts/index.tsx new file mode 100644 index 0000000000..e17415cba5 --- /dev/null +++ b/apps/ui/app/plotter/coss-concepts/index.tsx @@ -0,0 +1,65 @@ +import { CommonInterfaceStudy } from "./common-interface-study"; +import { SharedSystemStudy } from "./shared-system-study"; + +const concepts = [ + { + code: "X01", + title: "One interface, many services", + description: + "Different parts, one shared opening. Email, SMS, video, and scheduling meet a common SDK contract.", + Drawing: CommonInterfaceStudy, + }, + { + code: "X07", + title: "A common boundary", + description: + "Two complementary profiles form one system. A construction study of the interface between an app and coss.", + Drawing: SharedSystemStudy, + }, +] as const; + +export function CossConcepts() { + return ( +
+
+

+ coss.com concepts +

+

+ Two studies of a shared interface: different services, designed to fit + together. +

+
+
+ {concepts.map(({ code, title, description, Drawing }) => ( +
+
+ +
+
+
+

{title}

+

+ {description} +

+
+ + {code} + +
+
+ ))} +
+
+ ); +} diff --git a/apps/ui/app/plotter/coss-concepts/shared-system-study.tsx b/apps/ui/app/plotter/coss-concepts/shared-system-study.tsx new file mode 100644 index 0000000000..03a6a64553 --- /dev/null +++ b/apps/ui/app/plotter/coss-concepts/shared-system-study.tsx @@ -0,0 +1,100 @@ +import { BlueprintDrawing } from "../blueprint-drawing"; + +const unit = 60; +const frame = { left: 160, top: 88, size: 4 * unit }; +const seamX = frame.left + 2 * unit; +const upperCenterY = frame.top + unit; +const lowerCenterY = frame.top + 3 * unit; +const middleY = frame.top + 2 * unit; +const bottomY = frame.top + frame.size; +const rightX = frame.left + frame.size; +const radialOffset = unit / Math.SQRT2; +const radiusTip = { + x: seamX + radialOffset, + y: upperCenterY - radialOffset, +}; + +// A conceptual compatibility diagram, not a physical joint or API topology. +// The two regions are complementary: their common interface is owned once. +// Two equal tangent generating circles give a C1-continuous pair of arcs. +const interfaceProfile = `M${seamX} ${frame.top}A${unit} ${unit} 0 0 1 ${seamX} ${middleY}A${unit} ${unit} 0 0 0 ${seamX} ${bottomY}`; +const complementaryLoci = `M${seamX} ${frame.top}A${unit} ${unit} 0 0 0 ${seamX} ${middleY} M${seamX} ${middleY}A${unit} ${unit} 0 0 1 ${seamX} ${bottomY}`; + +// The half-unit field locates the circle centers, radii, and square edges. +// Shared axes/boundaries own their strokes; text zones are omitted in geometry. +const grid = [ + "M160 330V352 M400 330V352", + "M190 64V198 M190 218V352", + "M220 64V198 M220 218V352", + "M250 64V352", + "M310 64V102 M310 134V352", + "M340 64V198 M340 218V352", + "M370 64V198 M370 218V352", + "M136 88H158 M402 88H424", + "M136 118H284 M318 118H424", + "M136 178H424", + "M136 208H178 M230 208H330 M382 208H424", + "M136 238H424", + "M136 298H424", + "M136 328H158 M402 328H424", +].join(" "); + +export function SharedSystemStudy() { + return ( + + + + + + + + + + + + + + + + + + + 4u + + + 2u + + + r = u + + + App + + + coss + + + One shared interface + + + + ); +} diff --git a/apps/ui/app/plotter/disconnected-plug.tsx b/apps/ui/app/plotter/disconnected-plug.tsx new file mode 100644 index 0000000000..9da10c1821 --- /dev/null +++ b/apps/ui/app/plotter/disconnected-plug.tsx @@ -0,0 +1,92 @@ +import { BlueprintDrawing, CompactBlueprintDrawing } from "./blueprint-drawing"; +import { HorizontalDimension, VerticalDimension } from "./dimensions"; + +function PlugBody() { + return ( + <> + + + + ); +} + +// Conceptual section: the fixed support lies behind x356. A separate socket +// has a 12-unit flange in front of that plane and a body recessed to x400. +// Contact pockets enter at x344 and stop at x388, inside the socket body. +// The one-sided hatch denotes a support; it is not a material specification. +function SocketSection({ compact = false }: { compact?: boolean }) { + return ( + <> + + + + + + ); +} + +export function DisconnectedBlueprint() { + return ( + + + + + + + + + + + + + + {/* Gap witnesses: the pin ends at x264 and socket mouth at x344. */} + + + {/* Both pin and pocket centerlines are y196 and y244. */} + + + + + + + Disconnected + + + + + Socket + + + + ); +} + +export function DisconnectedCompact() { + return ( + + + + + + + ); +} diff --git a/apps/ui/app/plotter/engineering-motion-preview.module.css b/apps/ui/app/plotter/engineering-motion-preview.module.css new file mode 100644 index 0000000000..3a33eeaf27 --- /dev/null +++ b/apps/ui/app/plotter/engineering-motion-preview.module.css @@ -0,0 +1,13 @@ +.animation { + display: none; +} + +@media (prefers-reduced-motion: no-preference) { + .drawing[data-live] .animation { + display: block; + } + + .drawing[data-live] .original { + visibility: hidden; + } +} diff --git a/apps/ui/app/plotter/engineering-motion-preview.tsx b/apps/ui/app/plotter/engineering-motion-preview.tsx new file mode 100644 index 0000000000..11730b1e12 --- /dev/null +++ b/apps/ui/app/plotter/engineering-motion-preview.tsx @@ -0,0 +1,104 @@ +"use client"; + +import { PauseIcon, PlayIcon, RotateCcwIcon } from "lucide-react"; +import { type ReactNode, useEffect, useRef, useState } from "react"; +import { useMediaQuery } from "@/registry/default/hooks/use-media-query"; +import { Button } from "@/registry/default/ui/button"; +import styles from "./engineering-motion-preview.module.css"; + +export function EngineeringMotionPreview({ + animated, + caption, + children, +}: { + animated: ReactNode; + caption: string; + children: ReactNode; +}) { + const drawing = useRef(null); + const animation = useRef(null); + const [live, setLive] = useState(true); + const [paused, setPaused] = useState(false); + const [inView, setInView] = useState(false); + const reducedMotion = useMediaQuery("(prefers-reduced-motion: reduce)"); + + useEffect(() => { + if (!drawing.current) return; + const observer = new IntersectionObserver(([entry]) => { + if (entry) setInView(entry.isIntersecting); + }); + observer.observe(drawing.current); + return () => observer.disconnect(); + }, []); + + useEffect(() => { + const svg = animation.current?.querySelector("svg"); + if (!svg) return; + if (!live || paused || reducedMotion || !inView) svg.pauseAnimations(); + else svg.unpauseAnimations(); + }, [live, paused, reducedMotion, inView]); + + const togglePlayback = () => { + if (live) setPaused((value) => !value); + else { + setLive(true); + setPaused(false); + } + }; + + const replay = () => { + animation.current?.querySelector("svg")?.setCurrentTime(0); + setLive(true); + setPaused(false); + }; + + return ( +
+
+
{children}
+
+ {animated} +
+
+
+

{caption}

+
+ + + {live && ( + + )} +
+

+ Reduced motion · Static drawing +

+
+
+ ); +} diff --git a/apps/ui/app/plotter/engineering-studies.tsx b/apps/ui/app/plotter/engineering-studies.tsx new file mode 100644 index 0000000000..94393c7ec0 --- /dev/null +++ b/apps/ui/app/plotter/engineering-studies.tsx @@ -0,0 +1,448 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; +import type { IllustrationStudy } from "./study-types"; + +type GridBoundary = { + left: number; + right: number; + top: number; + bottom: number; +}; + +// Trim the actual guide geometry around forms and labels. No cover or mask. +function gridSegments( + start: number, + end: number, + omissions: [number, number][], +) { + const segments: [number, number][] = []; + let cursor = start; + for (const [from, to] of omissions.sort((a, b) => a[0] - b[0])) { + if (from > cursor) segments.push([cursor, Math.min(from, end)]); + cursor = Math.max(cursor, to); + if (cursor >= end) break; + } + if (cursor < end) segments.push([cursor, end]); + return segments; +} + +function constructionGrid( + boundary: GridBoundary, + columns: readonly number[], + rows: readonly number[], + omissions: GridBoundary[], +) { + return [ + ...columns.flatMap((x) => + gridSegments( + boundary.top, + boundary.bottom, + omissions + .filter(({ left, right }) => x >= left && x <= right) + .map(({ top, bottom }) => [top, bottom]), + ).map(([from, to]) => `M${x} ${from}V${to}`), + ), + ...rows.flatMap((y) => + gridSegments( + boundary.left, + boundary.right, + omissions + .filter(({ top, bottom }) => y >= top && y <= bottom) + .map(({ left, right }) => [left, right]), + ).map(([from, to]) => `M${from} ${y}H${to}`), + ), + ].join(" "); +} + +const arrowOffset = 6 / Math.SQRT2; +const cpu = { x: 220, y: 144, size: 120, pinPitch: 12, pinLength: 12 }; +const cpuPinOffsets = Array.from( + { length: 8 }, + (_, i) => 18 + i * cpu.pinPitch, +); +const processorRoutes = [174, 234]; +const boardGrid = constructionGrid( + { left: 88, right: 472, top: 96, bottom: 336 }, + Array.from({ length: 15 }, (_, i) => 112 + i * 24).filter((x) => x !== 280), + Array.from({ length: 9 }, (_, i) => 120 + i * 24), + [ + { left: 206, right: 354, top: 130, bottom: 278 }, + { left: 400, right: 460, top: 150, bottom: 258 }, + ...processorRoutes.flatMap((y) => [ + { left: 118, right: 210, top: y - 6, bottom: y + 6 }, + { left: 350, right: 418, top: y - 6, bottom: y + 6 }, + ]), + { left: 112, right: 136, top: 139, bottom: 157 }, + { left: 236, right: 324, top: 108, bottom: 130 }, + ], +); + +// A hypothetical processor-board plan. Pads and trace endpoints agree, but +// the routes do not assert a functional CPU/RAM circuit or a vendor pinout. +function ProcessorBoardStudy() { + return ( + + + + {cpuPinOffsets.map((offset) => ( + + + + + ))} + {processorRoutes.map((y) => ( + + ))} + + + + + + {processorRoutes.map((y) => ( + + ))} + + + + {processorRoutes.map((y) => ( + + ))} + + + + {/* Center axes stop at the package envelope and never cross the die. */} + + + + + + 7p + + + CPU + + + RAM + + + I/O + + + Processor · planar construction + + + + ); +} + +// The 24-unit field locates routing and terminal centers, not physical sizes. +const rcGrid = constructionGrid( + { left: 88, right: 472, top: 112, bottom: 352 }, + Array.from({ length: 15 }, (_, i) => 112 + i * 24), + Array.from({ length: 10 }, (_, i) => 112 + i * 24), + [ + { left: 104, right: 456, top: 178, bottom: 190 }, + { left: 181, right: 259, top: 169, bottom: 199 }, + { left: 323, right: 333, top: 178, bottom: 346 }, + { left: 301, right: 355, top: 253, bottom: 271 }, + { left: 301, right: 355, top: 325, bottom: 346 }, + { left: 97, right: 128, top: 147, bottom: 165 }, + { left: 429, right: 468, top: 147, bottom: 165 }, + { left: 191, right: 249, top: 133, bottom: 150 }, + { left: 340, right: 360, top: 158, bottom: 176 }, + { left: 365, right: 414, top: 247, bottom: 283 }, + { left: 142, right: 260, top: 312, bottom: 330 }, + ], +); + +function RcFilterStudy() { + return ( + + + + + + + + + + + + + Vin + + + Vout + + + R1 · 1 kΩ + + + n₁ + + + C1 + + + 100 nF + + + τ = RC = 100 μs + + + RC low-pass · one shared node + + + + ); +} + +const assemblyStations = [144, 280, 416]; +const assemblyGrid = constructionGrid( + { left: 88, right: 472, top: 184, bottom: 320 }, + assemblyStations.flatMap((x) => [x - 40, x - 32, x, x + 32, x + 40]), + [200, 248, 264, 272, 304], + [ + ...assemblyStations.map((x) => ({ + left: x - 42, + right: x + 42, + top: 198, + bottom: 274, + })), + { left: 86, right: 474, top: 270, bottom: 306 }, + { left: 250, right: 310, top: 184, bottom: 196 }, + ...assemblyStations.map((x) => ({ + left: x - 2, + right: x + 2, + top: 312, + bottom: 320, + })), + ], +); + +export function ProductionChainStudy() { + return ( + + + + + + + + {assemblyStations.map((x) => ( + + {/* An open frame. The core shares its eventual side and bottom edges. */} + + + + ))} + + + + + + + + + + + + + + + + + Core + + + 3u + + + Pitch + + + Load + + + Assembled + + + One line · equal carrier pitch + + + + ); +} + +const cradle = { + centers: [216, 260, 304, 348, 392], + pivotY: 120, + length: 176, + radius: 22, + pivotRadius: 2.4, + angle: Math.PI / 6, +} as const; +const cradleRestY = cradle.pivotY + cradle.length; +const cradlePivotX = cradle.centers[0]; +const displacedBob = { + x: cradlePivotX - cradle.length * Math.sin(cradle.angle), + y: cradle.pivotY + cradle.length * Math.cos(cradle.angle), +}; +const displacedStringStart = { + x: cradlePivotX - cradle.pivotRadius * Math.sin(cradle.angle), + y: cradle.pivotY + cradle.pivotRadius * Math.cos(cradle.angle), +}; +const displacedStringEnd = { + x: cradlePivotX - (cradle.length - cradle.radius) * Math.sin(cradle.angle), + y: cradle.pivotY + (cradle.length - cradle.radius) * Math.cos(cradle.angle), +}; +// Trim the center trajectory exactly where it meets the two bob outlines. +const cradleArcInset = 2 * Math.asin(cradle.radius / (2 * cradle.length)); +const cradleArcStart = { + x: cradlePivotX - cradle.length * Math.sin(cradle.angle - cradleArcInset), + y: cradle.pivotY + cradle.length * Math.cos(cradle.angle - cradleArcInset), +}; +const cradleArcEnd = { + x: cradlePivotX - cradle.length * Math.sin(cradleArcInset), + y: cradle.pivotY + cradle.length * Math.cos(cradleArcInset), +}; +const cradleArcArrow = [-Math.PI / 4, Math.PI / 4] + .map( + (angle) => + `${cradleArcEnd.x - 6 * Math.cos(cradleArcInset + angle)} ${cradleArcEnd.y - 6 * Math.sin(cradleArcInset + angle)}`, + ) + .join(` ${cradleArcEnd.x} ${cradleArcEnd.y} `); +const cradleAngleRadius = 48; +const cradleRadiusOffset = cradle.radius / Math.SQRT2; + +export function NewtonsCradleStudy() { + return ( + + {/* S1 marks identify the fixed support, not a material cross-section. */} + + {Array.from({ length: 15 }, (_, i) => 176 + i * 16).map((x) => ( + + ))} + + + + + + + {cradle.centers.map((x) => ( + + + + + ))} + + + + + + + + + + + + {cradle.centers.slice(1).map((x) => ( + + + + + ))} + + + + + + + + + + + + + 30° + + + 8u + + + 2u + + + r = u + + + Equal lengths · tangent at rest + + + + ); +} + +export const engineeringStudies: IllustrationStudy[] = [ + { + id: "object-gpu-plan", + code: "O06", + title: "A processor, constructed", + description: + "Regular contacts connect a central CPU to I/O and memory. Dashed centerlines locate the package on an open construction grid. The layout is conceptual.", + category: "Objects", + illustration: , + }, + { + id: "workflow-rc-filter", + code: "W04", + title: "One node, two paths", + description: + "A first-order RC low-pass joins a series resistor and a shunt capacitor at one explicit junction.", + category: "Systems", + illustration: , + }, + { + id: "workflow-production-chain", + code: "W05", + title: "A part, made in sequence", + description: + "Equal carriers advance along one assembly line while a matching core enters an open frame at the central station.", + category: "Systems", + illustration: , + }, + { + id: "object-newtons-cradle", + code: "O07", + title: "Newton’s cradle, at release", + description: + "Five equal pendulums share a tangent rest position; one follows a true arc from a 30° release.", + category: "Objects", + illustration: , + }, +]; diff --git a/apps/ui/app/plotter/illustration-gallery.tsx b/apps/ui/app/plotter/illustration-gallery.tsx index e20953a515..bd31ab94c4 100644 --- a/apps/ui/app/plotter/illustration-gallery.tsx +++ b/apps/ui/app/plotter/illustration-gallery.tsx @@ -1,23 +1,8 @@ "use client"; -import { ArrowLeftIcon, ArrowRightIcon, ArrowUpRightIcon } from "lucide-react"; import { useSearchParams } from "next/navigation"; -import { useRef } from "react"; +import { useEffect } from "react"; import { Button } from "@/registry/default/ui/button"; -import { - Dialog, - DialogDescription, - DialogPanel, - DialogPopup, - DialogTitle, -} from "@/registry/default/ui/dialog"; -import { - Empty, - EmptyDescription, - EmptyHeader, - EmptyMedia, - EmptyTitle, -} from "@/registry/default/ui/empty"; import type { IllustrationStudy, StudyCategory } from "./study-types"; const categories: StudyCategory[] = [ @@ -36,17 +21,21 @@ export function IllustrationGallery({ illustrations: IllustrationStudy[]; }) { const params = useSearchParams(); - const category = categories.find((item) => item === params.get("category")); + const linkedIllustration = illustrations.find( + (item) => item.id === params.get("variant"), + ); + const category = + linkedIllustration?.category ?? + categories.find((item) => item === params.get("category")); const visible = category ? illustrations.filter((item) => item.category === category) : illustrations; - const active = illustrations.find( - (item) => item.id === params.get("variant"), - ); - const currentGroup = - active && !visible.includes(active) ? illustrations : visible; - const position = currentGroup.findIndex((item) => item.id === active?.id); - const lastTrigger = useRef(null); + const linkedId = linkedIllustration?.id; + + // Existing preview URLs now locate the drawing directly in the gallery. + useEffect(() => { + if (linkedId) document.getElementById(linkedId)?.scrollIntoView(); + }, [linkedId]); function updateParams(values: Record) { const next = new URLSearchParams(window.location.search); @@ -60,18 +49,10 @@ export function IllustrationGallery({ window.history.replaceState( null, "", - `${window.location.pathname}${query ? `?${query}` : ""}`, + `${window.location.pathname}${query ? `?${query}` : ""}#illustrations`, ); } - function cycle(direction: number) { - const next = - currentGroup[ - (position + direction + currentGroup.length) % currentGroup.length - ]; - if (next) updateParams({ variant: next.id }); - } - return ( <>
@@ -109,129 +90,40 @@ export function IllustrationGallery({ aria-label={ category ? `${category} illustrations` : "All illustrations" } - className="grid grid-cols-1 gap-x-6 gap-y-10 py-8 sm:grid-cols-2 lg:grid-cols-3 lg:py-10" + className="grid grid-cols-[repeat(auto-fill,minmax(min(100%,600px),1fr))] gap-x-6 gap-y-12 py-8 lg:py-10" > {visible.map((item) => ( -
  • - + +
  • ))} - - { - if (!open) updateParams({ variant: null }); - }} - > - lastTrigger.current} - onKeyDown={(event) => { - if ( - event.altKey || - event.ctrlKey || - event.metaKey || - event.shiftKey - ) - return; - if (event.key === "ArrowLeft" || event.key === "ArrowRight") { - event.preventDefault(); - cycle(event.key === "ArrowLeft" ? -1 : 1); - } - }} - > - {active && ( - <> - -

    - {active.category} /{" "} - {active.code} -

    -
    - {active.illustration} -
    -
    - {active.title} - - {active.description} - -
    - {active.emptyState && ( -
    - - - - {active.emptyState.illustration} - - {active.emptyState.title} - - {active.emptyState.description} - - - -
    - )} -
    -
    - -

    - {position + 1} / {currentGroup.length} -

    - -
    - - )} -
    -
    ); } diff --git a/apps/ui/app/plotter/illustrations.tsx b/apps/ui/app/plotter/illustrations.tsx index bb8e8b3ebc..2d888e6470 100644 --- a/apps/ui/app/plotter/illustrations.tsx +++ b/apps/ui/app/plotter/illustrations.tsx @@ -1,5 +1,6 @@ import { calStudies } from "./cal-studies"; import { contentStateStudies } from "./content-state-studies"; +import { engineeringStudies } from "./engineering-studies"; import { flatCalStudies } from "./flat-cal-studies"; import { flatObjectStudies } from "./flat-object-studies"; import { flatSystemStudies } from "./flat-system-studies"; @@ -50,6 +51,7 @@ const collection: IllustrationStudy[] = [ ...stateStudies, ...contentStateStudies, ...systemStateStudies, + ...engineeringStudies, ...notFoundArchitectureStudies, ...notFoundGeometryStudies, { diff --git a/apps/ui/app/plotter/missing-destination.tsx b/apps/ui/app/plotter/missing-destination.tsx new file mode 100644 index 0000000000..685fe45df3 --- /dev/null +++ b/apps/ui/app/plotter/missing-destination.tsx @@ -0,0 +1,104 @@ +import { BlueprintDrawing, CompactBlueprintDrawing } from "./blueprint-drawing"; +import { HorizontalDimension } from "./dimensions"; + +function RequestRoute() { + return ( + <> + + + + ); +} + +function AbsentPage({ dash = "3 5" }: { dash?: string }) { + return ( + + + + + + ); +} + +// S01 is shared by the gallery and M03. The marker follows the true R32 +// centerline inside the 16-unit route; its 3.5-unit radius stays clear of the +// closed end at x272. No part of the request crosses the 32-unit address gap. +export function MissingDestinationBlueprint({ + animated = false, +}: { + animated?: boolean; +}) { + return ( + + + + + + + + + + + + + + {animated && ( + + + + )} + + + + + + + + + + + Request + + + 404 + + + + + R40 + + + No page at this address + + + + + + + ); +} + +export function MissingDestinationCompact() { + return ( + + + + + ); +} diff --git a/apps/ui/app/plotter/newtons-cradle-motion-drawing.tsx b/apps/ui/app/plotter/newtons-cradle-motion-drawing.tsx new file mode 100644 index 0000000000..4a89aeb79e --- /dev/null +++ b/apps/ui/app/plotter/newtons-cradle-motion-drawing.tsx @@ -0,0 +1,183 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; + +const cradle = { + pivots: [216, 260, 304, 348, 392], + pivotY: 120, + length: 176, + radius: 22, + pivotRadius: 2.4, + amplitude: 30, + duration: 2, +} as const; +const restY = cradle.pivotY + cradle.length; + +// Dimensionless pendulum flight: θ″ + sin(θ) = 0. Substituting +// θ = 2 asin(k cos φ), k = sin(θmax/2), gives dt/dφ below. Simpson sampling +// supplies one descent; reflection supplies the remaining three quarters. +// The user’s video reference suggests a roughly 2.4 s cycle and 30° release. +// The requested speed refinements use 2 s with the same pendulum curve. +// Contacts at ¼ and ¾ of each cycle transfer velocity without dwell. +// The complete bob sweep remains inside x = 106…502, clear of the witnesses. +// This idealization omits collision compliance, damping, and interior motion. +function swingSamples() { + const count = 96; + const step = Math.PI / (2 * count); + const k = Math.sin((cradle.amplitude * Math.PI) / 360); + const rate = (phase: number) => 1 / Math.sqrt(1 - (k * Math.cos(phase)) ** 2); + let elapsed = 0; + const descent = Array.from({ length: count + 1 }, (_, i) => { + const phase = i * step; + if (i) { + elapsed += + (step * + (rate(phase - step) + 4 * rate(phase - step / 2) + rate(phase))) / + 6; + } + return { + time: elapsed, + angle: i === count ? 0 : (360 * Math.asin(k * Math.cos(phase))) / Math.PI, + speed: Math.sin(phase), + }; + }); + const samples = [0, 1, 2, 3].flatMap((quarter) => { + const reverse = quarter % 2 === 1; + return (reverse ? [...descent].reverse() : descent) + .slice(quarter ? 1 : 0) + .map(({ time, angle, speed }) => ({ + time: (quarter + (reverse ? 1 - time / elapsed : time / elapsed)) / 4, + angle: (quarter === 1 || quarter === 2 ? -1 : 1) * angle, + // Angular velocity per normalized cycle, from the same energy integral. + velocity: + ((quarter < 2 ? -1 : 1) * 1440 * elapsed * k * speed) / Math.PI, + })); + }); + return { + samples, + duration: cradle.duration, + }; +} + +const { samples, duration } = swingSamples(); +const keyTimes = samples.map(({ time }) => time).join(";"); + +function swingMotion(side: "left" | "right", pivotX: number) { + const frames = samples.map((sample) => ({ + ...sample, + angle: + side === "left" ? Math.max(0, sample.angle) : Math.min(0, sample.angle), + })); + return { + values: frames + .map(({ angle }) => `${angle.toFixed(9)} ${pivotX} ${cradle.pivotY}`) + .join(";"), + // Cubic Hermite segments preserve the calculated endpoint velocities: + // zero at each turnaround, continuous in flight, handed off at contact. + // Fixed x controls make spline time linear; only the angle is interpolated. + keySplines: frames + .flatMap((current, i) => { + const next = frames[i + 1]; + if (!next) return []; + const angle = next.angle - current.angle; + if (!angle) return ["0 0 1 1"]; + const scale = (next.time - current.time) / (3 * angle); + return [ + `0.333333333 ${(current.velocity * scale).toFixed(9)} 0.666666667 ${(1 - next.velocity * scale).toFixed(9)}`, + ]; + }) + .join(";"), + }; +} + +function Pendulum({ x }: { x: number }) { + return ( + <> + + + + ); +} + +function SwingingPendulum({ side, x }: { side: "left" | "right"; x: number }) { + const motion = swingMotion(side, x); + return ( + + + + + ); +} + +const radiusOffset = cradle.radius / Math.SQRT2; +const radiusPoint = { x: 304 - radiusOffset, y: restY + radiusOffset }; + +export function NewtonsCradleMotionDrawing() { + return ( + + {/* S1 support marks stay fixed. No moving guide or ghost bob is needed. */} + + {Array.from({ length: 15 }, (_, i) => 176 + i * 16).map((x) => ( + + ))} + + + + + + + {cradle.pivots.map((x) => ( + + + + + ))} + + + + {cradle.pivots.slice(1, -1).map((x) => ( + + ))} + + + + + {/* Both length witnesses lie beyond the complete swept bob envelope. */} + + + {/* Radius leader belongs to the stationary middle bob, below the sweep. */} + + + + + + 8u + + + 2u + + + r = u + + + Equal lengths · Tangent at rest + + + + ); +} diff --git a/apps/ui/app/plotter/not-found-architecture-studies.tsx b/apps/ui/app/plotter/not-found-architecture-studies.tsx index e34bce8d1c..3ed154d6fb 100644 --- a/apps/ui/app/plotter/not-found-architecture-studies.tsx +++ b/apps/ui/app/plotter/not-found-architecture-studies.tsx @@ -40,7 +40,9 @@ function UnopenedDoor() { - + diff --git a/apps/ui/app/plotter/not-found-geometry-studies.tsx b/apps/ui/app/plotter/not-found-geometry-studies.tsx index bbbb1d70e5..8dab4e62a5 100644 --- a/apps/ui/app/plotter/not-found-geometry-studies.tsx +++ b/apps/ui/app/plotter/not-found-geometry-studies.tsx @@ -34,7 +34,7 @@ function UnbuiltArch() { - + diff --git a/apps/ui/app/plotter/not-found-motion.module.css b/apps/ui/app/plotter/not-found-motion.module.css new file mode 100644 index 0000000000..3a33eeaf27 --- /dev/null +++ b/apps/ui/app/plotter/not-found-motion.module.css @@ -0,0 +1,13 @@ +.animation { + display: none; +} + +@media (prefers-reduced-motion: no-preference) { + .drawing[data-live] .animation { + display: block; + } + + .drawing[data-live] .original { + visibility: hidden; + } +} diff --git a/apps/ui/app/plotter/not-found-motion.tsx b/apps/ui/app/plotter/not-found-motion.tsx new file mode 100644 index 0000000000..fcd9d76a2c --- /dev/null +++ b/apps/ui/app/plotter/not-found-motion.tsx @@ -0,0 +1,117 @@ +"use client"; + +import { PauseIcon, PlayIcon, RotateCcwIcon } from "lucide-react"; +import { useEffect, useRef, useState } from "react"; +import { useMediaQuery } from "@/registry/default/hooks/use-media-query"; +import { Button } from "@/registry/default/ui/button"; +import { MissingDestinationBlueprint } from "./missing-destination"; +import styles from "./not-found-motion.module.css"; + +export function NotFoundMotion({ + controls = "preview", +}: { + controls?: "hero" | "preview"; +}) { + const drawing = useRef(null); + const animation = useRef(null); + const [live, setLive] = useState(true); + const [paused, setPaused] = useState(false); + const [inView, setInView] = useState(false); + const reducedMotion = useMediaQuery("(prefers-reduced-motion: reduce)"); + + useEffect(() => { + if (!drawing.current) return; + const observer = new IntersectionObserver(([entry]) => { + if (entry) setInView(entry.isIntersecting); + }); + observer.observe(drawing.current); + return () => observer.disconnect(); + }, []); + + useEffect(() => { + const svg = animation.current?.querySelector("svg"); + if (!svg) return; + if (!live || reducedMotion || paused || !inView) svg.pauseAnimations(); + else svg.unpauseAnimations(); + }, [live, reducedMotion, paused, inView]); + + const togglePlayback = () => { + if (live) setPaused((value) => !value); + else { + setLive(true); + setPaused(false); + } + }; + + const replay = () => { + const svg = animation.current?.querySelector("svg"); + svg?.setCurrentTime(0); + if (inView && !reducedMotion) svg?.unpauseAnimations(); + setLive(true); + setPaused(false); + }; + + return ( +
    +
    +
    + +
    +
    + +
    + {controls === "hero" && !reducedMotion && ( + + )} +
    + {controls === "preview" && ( +
    +

    + A request approaches · The gap remains +

    +
    + + + {live && ( + + )} +
    +

    + Reduced motion · Static drawing +

    +
    + )} +
    + ); +} diff --git a/apps/ui/app/plotter/page.tsx b/apps/ui/app/plotter/page.tsx index 7dd44f9c03..28780aed96 100644 --- a/apps/ui/app/plotter/page.tsx +++ b/apps/ui/app/plotter/page.tsx @@ -1,12 +1,21 @@ import { SiteFooter } from "@coss/ui/shared/site-footer"; import type { Metadata } from "next"; import { Suspense } from "react"; +import { CossConcepts } from "./coss-concepts"; +import { EngineeringMotionPreview } from "./engineering-motion-preview"; +import { + NewtonsCradleStudy, + ProductionChainStudy, +} from "./engineering-studies"; import { IllustrationGallery } from "./illustration-gallery"; import { illustrations } from "./illustrations"; +import { NewtonsCradleMotionDrawing } from "./newtons-cradle-motion-drawing"; +import { NotFoundMotion } from "./not-found-motion"; import { ParticlesBlueprint } from "./particles-blueprint"; import { ParticlesMotion } from "./particles-motion"; import { PrimitivesBlueprint } from "./primitives-blueprint"; import { PrimitivesMotionPreview } from "./primitives-motion-preview"; +import { ProductionChainMotionDrawing } from "./production-chain-motion-drawing"; export const metadata: Metadata = { title: "Plotter illustrations – coss ui", @@ -28,6 +37,29 @@ export default function Page() { and animations exploring geometry, interfaces, and the systems behind familiar things.

    +
    Animations -

    2 examples

    +

    5 examples

    @@ -86,7 +118,87 @@ export default function Page() {
    +
    +
    +

    + Empty states / M03 +

    +

    + An unreachable destination +

    +

    + A request follows a curved route, reaches the closed end, and + returns. The destination stays absent, one unit beyond its + reach. +

    +
    +
    + +
    +
    +
    +
    +

    Systems / W05

    +

    + An assembly line in motion +

    +

    + A core lowers into its carrier. The line advances, the completed + part moves on, and the next assembly begins. +

    +
    +
    + } + caption="Load · Seat · Advance" + > + + +
    +
    +
    +
    +

    Objects / O07

    +

    + One swing, passed along +

    +

    + One end swings in and the other swings out. Equal lengths and + fixed pivots carry the motion through a continuous rhythm. +

    +
    +
    + } + caption="One swing · Alternating ends" + > + + +
    +
    +
    - - + + - + diff --git a/apps/ui/app/plotter/production-chain-motion-drawing.tsx b/apps/ui/app/plotter/production-chain-motion-drawing.tsx new file mode 100644 index 0000000000..f10a181aef --- /dev/null +++ b/apps/ui/app/plotter/production-chain-motion-drawing.tsx @@ -0,0 +1,261 @@ +"use client"; + +import { useId } from "react"; +import { BlueprintDrawing } from "./blueprint-drawing"; + +const cycle = 1.8; +const duration = `${cycle}s`; +const pitch = 136; +const carrierCenters = [8, 144, 280, 416]; +const stations = [144, 280, 416]; +const arrowOffset = 6 / Math.SQRT2; + +// Quarter-pitch references sit below every swept part. Station witnesses +// own their verticals; the field ends above the dimension and its text. +const grid = Array.from({ length: 11 }, (_, i) => 110 + (i * pitch) / 4) + .filter((x) => !stations.includes(x)) + .map((x) => `M${x} 312V336`) + .join(" "); + +const dropStart = 0.04; +const dropTime = 0.38; +const dropDistance = 80; +const impact = dropStart + dropTime; +const gravity = (2 * dropDistance) / dropTime ** 2; +const reboundHeight = 0.35; +const reboundSpeed = Math.sqrt(2 * gravity * reboundHeight); +const settled = impact + (2 * reboundSpeed) / gravity; + +function coreDisplacement(time: number) { + if (time <= dropStart) return 0; + if (time < impact) return (gravity * (time - dropStart) ** 2) / 2; + if (time >= settled) return dropDistance; + const elapsed = time - impact; + return dropDistance - reboundSpeed * elapsed + (gravity * elapsed ** 2) / 2; +} + +const number = (value: number) => Number(value.toFixed(9)); +// Insert wall-crossing times exactly. Linear samples between them approximate +// constant acceleration within 0.01 SVG unit; every edge uses this same curve. +const coreTimes = [ + ...new Set([ + 0, + dropStart, + dropStart + Math.sqrt((2 * 24) / gravity), + dropStart + Math.sqrt((2 * 72) / gravity), + impact, + impact + 0.000001, + impact + reboundSpeed / gravity, + settled, + cycle, + ...Array.from( + { length: Math.ceil((settled - dropStart) * 120) }, + (_, i) => dropStart + i / 120, + ).filter((time) => time < settled), + ]), +].sort((a, b) => a - b); +const coreFrames = coreTimes.map((time) => { + const displacement = coreDisplacement(time); + const top = number(128 + displacement); + const exposedSideEnd = number(Math.max(top, Math.min(top + 48, 200))); + return { + bottom: `0 ${number(displacement)}`, + outline: `M256 ${top}H304 M256 ${top}V${exposedSideEnd} M304 ${top}V${exposedSideEnd}`, + visibility: displacement === dropDistance ? "hidden" : "visible", + }; +}); +const coreKeyTimes = coreTimes.map((time) => number(time / cycle)).join(";"); + +// A motor index: jerk ramps acceleration up/down, cruises, then brakes. +// Each ramp is an exact cubic; position, speed, and acceleration join smoothly. +const indexSplines = + "0 0 1 1;0.333333333333 0 0.666666666667 0;0.333333333333 0.2 0.666666666667 0.6;0 0 1 1;0.333333333333 0.4 0.666666666667 0.8;0.333333333333 1 0.666666666667 1;0 0 1 1"; + +function indexMotion( + start: number, + travelTime: number, + accelerate: number, + brake: number, + distance: number, + origin = 0, +) { + const speed = distance / (travelTime - (accelerate + brake) / 2); + return { + keyTimes: [ + 0, + start, + start + accelerate / 2, + start + accelerate, + start + travelTime - brake, + start + travelTime - brake / 2, + start + travelTime, + cycle, + ] + .map((time) => number(time / cycle)) + .join(";"), + values: [ + origin, + origin, + origin + (speed * accelerate) / 12, + origin + (speed * accelerate) / 2, + origin + distance - (speed * brake) / 2, + origin + distance - (speed * brake) / 12, + origin + distance, + origin + distance, + ] + .map((position) => `${number(position)} 0`) + .join(";"), + }; +} + +const belt = indexMotion(settled + 0.02, 1.1, 0.16, 0.2, pitch); +const feedTravelTime = 0.7; +const feedToDrop = 0.14; +const feedArrival = cycle + dropStart - feedToDrop; +// Feed overlaps indexing; its arrival is tied to the next drop, not the reset. +const feed = indexMotion( + feedArrival - feedTravelTime, + feedTravelTime, + 0.1, + 0.12, + pitch * 2, + -pitch * 2, +); + +export function ProductionChainMotionDrawing() { + const id = useId(); + const carrierClip = `${id}-carrier-entry-exit`; + const feedClip = `${id}-core-feed`; + + return ( + + + {/* Only moving objects are clipped, at their entry/exit apertures. */} + + + + + + + + + + + + + + {/* Feed guides clear the moving core by 4 units and end before the drop. */} + + + + + + + + {carrierCenters.map((x) => ( + + + + + ))} + + + {/* Core sides transfer to the carrier walls at y=200. Each + remaining segment stays on the rigid 48×48 core boundary. */} + + frame.outline).join(";")} + /> + + + frame.bottom).join(";")} + /> + {/* Contact transfers the bottom to the carrier. The rebound + restores it after 1µs, within 0.00005 unit of the seat. */} + frame.visibility).join(";")} + /> + + + + + + + + + + + + + + + + + + + + + + Core + + + 3u + + + Pitch + + + Load + + + Assembled + + + One line · equal carrier pitch + + + + ); +} diff --git a/apps/ui/app/plotter/service-studies.tsx b/apps/ui/app/plotter/service-studies.tsx index 8c89158d01..4a14025d68 100644 --- a/apps/ui/app/plotter/service-studies.tsx +++ b/apps/ui/app/plotter/service-studies.tsx @@ -226,7 +226,7 @@ function StripeSettlementStudy() { ))} - $100 · Gross balance + $100 · Gross payments Payments @@ -403,7 +403,7 @@ function LinearCycleStudy() { Week 2 - Release + Cycle end @@ -443,9 +443,9 @@ export const serviceStudies: IllustrationStudy[] = [ { id: "service-stripe-settlement", code: "B03", - title: "Stripe · A balance, assembled", + title: "Stripe · Payments, combined", description: - "Four equal payment units combine into one gross balance. Their conserved width makes collection and reconciliation a visible construction rule.", + "Four equal payments combine into one gross payment total, before fees, refunds, or availability. Their conserved width makes aggregation a visible construction rule.", category: "Services", illustration: , }, @@ -454,7 +454,7 @@ export const serviceStudies: IllustrationStudy[] = [ code: "B04", title: "Linear · Work aligned to a cycle", description: - "Issue modules share a two-week construction grid. Their measured spans align the work toward a common release milestone.", + "Issue modules share a two-week construction grid. Their measured spans align the work within a common cycle boundary.", category: "Services", illustration: , }, diff --git a/apps/ui/app/plotter/state-studies.tsx b/apps/ui/app/plotter/state-studies.tsx index b9b64715b6..effa4058c8 100644 --- a/apps/ui/app/plotter/state-studies.tsx +++ b/apps/ui/app/plotter/state-studies.tsx @@ -1,93 +1,9 @@ -import { BlueprintDrawing, CompactBlueprintDrawing } from "./blueprint-drawing"; -import { HorizontalDimension } from "./dimensions"; +import { + MissingDestinationBlueprint, + MissingDestinationCompact, +} from "./missing-destination"; import type { IllustrationStudy } from "./study-types"; -function RequestRoute() { - return ( - <> - - - - ); -} - -function AbsentPage({ dash = "4 5" }: { dash?: string }) { - return ( - - - - - - ); -} - -function MissingDestination() { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - Request - - - 404 - - - - - R40 - - - No page at this address - - - - - - - ); -} - -function MissingDestinationCompact() { - return ( - - - - - ); -} - export const stateStudies: IllustrationStudy[] = [ { id: "state-missing-page", @@ -96,7 +12,7 @@ export const stateStudies: IllustrationStudy[] = [ description: "A paired route reaches a closed end one unit before an unbuilt page. The address remains projected, but the connection is absent.", category: "Empty states", - illustration: , + illustration: , emptyState: { title: "Page not found", description: "The page may have moved or no longer exists.", diff --git a/apps/ui/app/plotter/system-state-studies.tsx b/apps/ui/app/plotter/system-state-studies.tsx index 69c66191a2..eab53be469 100644 --- a/apps/ui/app/plotter/system-state-studies.tsx +++ b/apps/ui/app/plotter/system-state-studies.tsx @@ -1,5 +1,9 @@ import { BlueprintDrawing, CompactBlueprintDrawing } from "./blueprint-drawing"; import { HorizontalDimension, VerticalDimension } from "./dimensions"; +import { + DisconnectedBlueprint, + DisconnectedCompact, +} from "./disconnected-plug"; import type { IllustrationStudy } from "./study-types"; // The cloud’s three generating circles share the y = 200 baseline. The route @@ -254,73 +258,76 @@ function EmailCompact() { ); } -function polar(cx: number, cy: number, radius: number, degrees: number) { - const angle = (degrees * Math.PI) / 180; - const x = cx + radius * Math.cos(angle); - const y = cy + radius * Math.sin(angle); - return `${Math.round(x * 100) / 100} ${Math.round(y * 100) / 100}`; -} +const gearModule = 14; +const meshAngle = Math.atan2(78.4, 58.8); -function gearOutline({ - cx, - cy, - teeth, - root, - tip, - phase, -}: { +type Gear = { cx: number; cy: number; teeth: number; - root: number; - tip: number; phase: number; -}) { - const step = 360 / teeth; - const tipHalf = step * 0.14; - const rootHalf = step * 0.34; - const at = (radius: number, degrees: number) => - polar(cx, cy, radius, degrees); - const segments = Array.from({ length: teeth }, (_, index) => { + hub: number; +}; + +function gearOutline({ cx, cy, teeth, phase }: Gear) { + const pitch = (teeth * gearModule) / 2; + const root = pitch - 0.65 * gearModule; + const tip = pitch + 0.45 * gearModule; + const step = (2 * Math.PI) / teeth; + const point = (radius: number, angle: number) => ({ + x: cx + radius * Math.cos(angle), + y: cy + radius * Math.sin(angle), + }); + const corners = Array.from({ length: teeth }, (_, index) => { const angle = phase + index * step; - return `L${at(tip, angle - tipHalf)}A${tip} ${tip} 0 0 1 ${at(tip, angle + tipHalf)}L${at(root, angle + rootHalf)}A${root} ${root} 0 0 1 ${at(root, angle + step - rootHalf)}`; + return [ + point(root, angle - 0.27 * step), + point(tip, angle - 0.17 * step), + point(tip, angle + 0.17 * step), + point(root, angle + 0.27 * step), + ]; + }).flat(); + const serialize = (p: { x: number; y: number }) => + `${p.x.toFixed(3)} ${p.y.toFixed(3)}`; + // Short quadratic joins soften the four corners of each broad tooth. + // This is a schematic profile, not a generated involute or a cutting plan. + const joins = corners.map((corner, index) => { + const previous = corners[(index + corners.length - 1) % corners.length]; + const next = corners[(index + 1) % corners.length]; + if (!previous || !next) return { before: corner, corner, after: corner }; + const inset = (neighbor: { x: number; y: number }) => { + const length = Math.hypot(neighbor.x - corner.x, neighbor.y - corner.y); + return { + x: corner.x + ((neighbor.x - corner.x) * 1.4) / length, + y: corner.y + ((neighbor.y - corner.y) * 1.4) / length, + }; + }; + return { before: inset(previous), corner, after: inset(next) }; }); - return `M${at(root, phase - rootHalf)}${segments.join("")}Z`; + const first = joins[0]; + if (!first) return ""; + return `M${serialize(first.before)}${joins + .map( + ({ before, corner, after }, index) => + `${index ? `L${serialize(before)}` : ""}Q${serialize(corner)} ${serialize(after)}`, + ) + .join("")}Z`; } -// A shared module gives pitch radii 72, 48 and 36 for 12, 8 and 6 teeth. -// The small pair’s pitch circles are tangent at (372, 172). The drive sits -// 40 units outside mesh; the schematic teeth have 2 units of tip clearance. -const gears = [ - { - cx: 212, - cy: 220, - pitch: 72, - teeth: 12, - root: 64, - tip: 78, - phase: 0, - hub: 18, - }, +// Conceptual maintenance diagram. All pitch radii derive from module 14. +// The small pair's pitch circles touch at (383.6, 236.8); a tooth faces a +// valley there. The main drive is separated by 48 between pitch circles. +// The shallow, rounded profiles indicate engagement in this static pose; +// they do not assert conjugate motion or manufacturability. +const gears: Gear[] = [ + { cx: 176, cy: 192, teeth: 10, phase: 0, hub: 18 }, + { cx: 350, cy: 192, teeth: 8, phase: meshAngle, hub: 14 }, { - cx: 372, - cy: 220, - pitch: 48, - teeth: 8, - root: 40, - tip: 54, - phase: 22.5, - hub: 12, - }, - { - cx: 372, - cy: 136, - pitch: 36, + cx: 408.8, + cy: 270.4, teeth: 6, - root: 28, - tip: 42, - phase: 30, - hub: 9, + phase: meshAngle + Math.PI - Math.PI / 6, + hub: 10.5, }, ]; @@ -340,47 +347,40 @@ function GearsForm() { function MaintenanceStudy() { return ( - + {gears.map((gear) => ( - + + + + ))} - - - - + + - + Drive disengaged - - R72 - - - R48 - - - 6 teeth - - - One pitch / 12 : 8 : 6 + + Common pitch · 10 : 8 : 6 @@ -389,81 +389,13 @@ function MaintenanceStudy() { function MaintenanceCompact() { return ( - + - - - ); -} - -const wallHatch = Array.from( - { length: 11 }, - (_, index) => `M432 ${144 + index * 16}l16-16`, -).join(" "); - -function PlugForm() { - return ( - <> - - - - - - ); -} - -function SocketRecesses({ dash }: { dash: string }) { - return ( - - ); -} - -function DisconnectedStudy() { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - Disconnected - - - - ); -} - -function DisconnectedCompact() { - return ( - - - - ); @@ -610,7 +542,7 @@ export const systemStateStudies: IllustrationStudy[] = [ code: "S10", title: "A gear train, interrupted", description: - "Three gears share one tooth pitch. The two smaller gears engage, while a 40-unit pitch-circle gap keeps the main drive disconnected.", + "Three gears share a common pitch. A separated drive interrupts the mechanism.", category: "Empty states", illustration: , emptyState: { @@ -624,9 +556,9 @@ export const systemStateStudies: IllustrationStudy[] = [ code: "S11", title: "A connection, apart", description: - "A mobile plug aligns with a fixed socket in a hatched wall section. Two projected contact axes cross the measured gap between them.", + "A mobile plug aligns with a separate socket recessed behind a thin mounting flange. The hatched support, matching contact spacing, and open gap describe the interrupted connection.", category: "Empty states", - illustration: , + illustration: , emptyState: { title: "Integration disconnected", description: "Reconnect your account to resume syncing.", diff --git a/apps/ui/skills/plotter/SKILL.md b/apps/ui/skills/plotter/SKILL.md index 76ff04bf1b..58e17ecc9b 100644 --- a/apps/ui/skills/plotter/SKILL.md +++ b/apps/ui/skills/plotter/SKILL.md @@ -23,12 +23,16 @@ Read [direction and feedback](references/direction.md) for implementation choice - Start from a small geometric vocabulary: circles, rectangles, lines, arcs, slots, and modest UI fragments. Choose one central relationship before adding detail. A complete screen mockup or a large pictogram with technical marks is a poor starting point for this direction. - Give every guide a job. Show actual tangency, radius, equal intervals, alignment, a shared baseline, or a connection. Let grids fade where they stop explaining the form. Avoid filling empty areas with arbitrary drafting marks. - Name what geometry alone cannot communicate. A week needs weekday or time cues; overlapping availability needs a clear association with people or schedules. Prefer short integrated labels over extra icons or a caption that must explain the whole image. -- For the coss main and Particles pages, preserve the split layout: left-aligned headline and description; a single composition on the right. The main page has CTAs beneath its copy; Particles has its search/filter directly beneath the copy, with its original filtering behavior. Treat the 404 separately: illustration above the centered heading, description, and CTA. Keep these layouts in their pages; do not introduce a shared page-hero component. Elsewhere, fit the host layout unless the user asks to restructure it. When revising a standalone illustration, inspect it in the showcase grid and enlarged preview. +- For the coss main and Particles pages, preserve the split layout: left-aligned headline and description; a single composition on the right. The main page has CTAs beneath its copy; Particles has its search/filter directly beneath the copy, with its original filtering behavior. Treat the 404 separately: illustration above the centered heading, description, and CTA. Keep these layouts in their pages; do not introduce a shared page-hero component. Elsewhere, fit the host layout unless the user asks to restructure it. When revising a standalone illustration, inspect its native-size drawing in the showcase gallery. ## Implement and inspect Use editable SVG or the host’s existing vector components for this geometric work. Read [SVG and coss integration](references/implementation.md) when implementing or checking geometry. Animation is optional future work; first make the static drawing succeed. +Read the [drafting standard](references/drafting-standard.md) when creating or revising linework. It defines this style as **monochrome orthographic construction diagrams**, assigns line and hatch roles, and separates geometric constructions, functional schematics, and service metaphors. Existing approved compositions establish art direction; they do not certify technical accuracy. Preserve explicit user choices when an older reference and a proposed normalization differ. + +For a technical audit, use the [verification prompt](references/verification-prompt.md) with the actual source and rendered revision. Calculate geometric claims and verify product behavior independently. The [source audit](references/source-audit.md) records inspected examples, corrections, and remaining limits; it is evidence for those revisions, not a gallery-wide certification. + Inspect the rendered result at its intended hero size and as a thumbnail, then check the narrow layout when embedding it in a page. At narrow sizes, preserve labels that identify the subject; simplify secondary guides or dimensions before shrinking all annotations into illegibility. Compare it with the relevant anchors. Resolve these questions before delivering: 1. Does one central gesture survive at a glance? diff --git a/apps/ui/skills/plotter/references/coss-product-concepts.md b/apps/ui/skills/plotter/references/coss-product-concepts.md new file mode 100644 index 0000000000..cc335f2bde --- /dev/null +++ b/apps/ui/skills/plotter/references/coss-product-concepts.md @@ -0,0 +1,46 @@ +# coss.com product concepts + +Source review: 25 October 2026. Scope: two replacement local concepts in `/ui/plotter#coss-concepts`. These are proposals for review; do not publish, push, or deploy them to coss.com. +These are conceptual product diagrams. They do not claim to be infrastructure topology, standardized +electrical symbols, physical parts, performance charts or implementation availability badges. + +## Evidence and claim boundary + +- The [live homepage](https://coss.com/) presents Email, SMS, Video and Scheduling behind one SDK and + one key, with a common operational contract. Its catalog is retained without new API or availability + claims. The cosscom/coss repository’s `apps/site/src/components/home/parts.ts` supplies the same four entries. +- [SDK overview](https://coss.com/docs/sdk) and + `content/docs/(tools)/sdk/index.mdx`: one client is created with an API key and reused across products. +- [Sending email](https://coss.com/docs/email/sending), + [delivery events](https://coss.com/docs/email/delivery), and the corresponding local MDX explain an + accepted send followed by delivery reporting for individual recipients. The return line is an event, + not a promise that every recipient receives or opens the email. +- [SMS](https://coss.com/docs/sms) and `content/docs/sms/index.mdx` describe sending to a carrier and + following its reported outcome through the same key, webhooks and logs. The drawing does not promise + delivery, depict a guaranteed latency or claim the return is an SMS sent to the recipient. +- [The public documentation index](https://coss.com/llms.txt) covers implemented Email, SMS, Hooks and + Risk; it is not identical to the homepage's four-product presentation. Video and Scheduling therefore + remain conceptual room/availability illustrations of the existing marketing descriptions. No method, + status, quota, recording pipeline or synchronization behavior is invented for them. + +Live text and the checked-out primary MDX were read before implementation. Some live `.md` fetches +were unavailable in the research tool; their checked-out source supplied the detailed behavior evidence. + +## Visual direction and rejection + +The user rejected the first X01–X07 set for poor quality relative to the established illustrations. The bus, service pictograms, and separate/shared-container comparison are superseded. Their scoped product research remains useful; their geometry checks do not establish visual acceptance. + +The new pair starts from complementary geometry rather than a catalog of icons: + +| Drawing | Construction → product meaning | What it leaves out | +| --- | --- | --- | +| X01 · One interface, many services | Four planar pieces register to one circular opening → different catalog services meet one SDK contract | Physical parts, actual network topology, API availability or authorization guarantees | +| X07 · A common boundary | Two complementary regions share one continuous curved boundary → an app and coss meet through a common interface | Transport, request flow, timings, deployment topology or provider implementation | + +P00 supplies the hierarchy of silhouette, generators, grid, and measurements. B03/B04 establish how a service idea can live in a construction relationship. Neither proposal has been approved as a reference. Components live under `app/plotter/coss-concepts` and are shown only in the local Plotter showcase. + +## Geometry and review limits + +The X07 square is 240 units wide; its two generating circles have equal radius 60 and centers 120 apart. Their complementary semicircles form a shared S-profile with one path owner. The labels `4u`, `2u`, and `r = u` use `u = 60`. These are drawing units, not product dimensions. + +Geometry and browser review are recorded against specific revisions in the [source audit](source-audit.md). Product meaning, geometric consistency, and visual acceptance remain separate. Do not promote the new concepts into the approved reference set without explicit user selection. diff --git a/apps/ui/skills/plotter/references/direction.md b/apps/ui/skills/plotter/references/direction.md index ad4f372fa3..5bc8bd1fd4 100644 --- a/apps/ui/skills/plotter/references/direction.md +++ b/apps/ui/skills/plotter/references/direction.md @@ -135,7 +135,7 @@ The user asked for concepts, states, or objects that fit this style and are in h - bookings - image -These are S03–S14 under Empty states. Each one adds a 96px version, shown in the coss `Empty` component in its preview dialog. The compact version reuses the hero geometry with non-scaling 1px strokes and no labels. All twelve remain outside the current approved reference set. +These are S03–S14 under Empty states. Each one originally added a 96px version shown in the coss `Empty` component in its preview dialog; the later native-size gallery feedback removes those application cards from display. The compact version reuses the hero geometry with non-scaling 1px strokes and no labels. All twelve remain outside the current approved reference set. When the user asked for an honest assessment, the assistant judged most of the set to be annotated pictograms. In those, the state lived in a caption rather than the geometry, and the compact versions read as generic outline icons. The user replied “ok improve them”. The four strongest were reworked using E02’s grammar: @@ -155,6 +155,44 @@ On S11, the user noted that drawings like these often use tiny arrows to show di The user then pointed to E02, C00, and P02 as well-executed arrow caps, and to C03 and S11 as inconsistent. Draw every cap the way those three do: an open 90° chevron, each leg about 6 long at 45° to its shaft. Put it in the same group and opacity as the shaft, with the tip exactly on the end of a drawn line. On a 45° shaft the legs are axis-aligned (`m-6 0h6v-6`). On an axis-aligned shaft they offset ±4.24 on both axes. On other angles, rotate the reversed shaft direction by ±45°. On a dashed shaft, start the path at the tip so a dash, not a gap, meets the cap, or place the tip at the end of a dash. +## Technical rigor and the October 25 expansion + +The user requested a less projecting female socket in S11, improved gears in S10, a third 404 animation, and GPU, circuit, production-chain, and Newton’s cradle studies. Their review of the first expansion is explicit: + +- **W05:** wanted a metaphor for an assembly line, not isolated fabrication steps. +- **W04:** needs a meaningful underlying grid. +- **O06:** too much cardboard-like detail, no grid, and weak fit with the established style. The construction relationship needs to carry the subject. +- **O07:** nice, but not fully convincing; refine without treating this as approval. +- **S11:** the female connector is a distinct component, not the wall itself. Keep a restrained mounted receptacle, avoiding the earlier oversized protruding housing. +- **S10:** the tooth shapes look bad and there are too many teeth. Mathematical gear detail alone did not satisfy the visual direction; use fewer, better-shaped teeth and clearer placement. +- **M03:** replace the disconnected-plug animation with **S01**, the missing destination. This replaces the earlier animation subject; it does not change the production 404. + +These revisions are work for review and do not expand the approved reference set. + +The user also asked for an exact style definition, standardized line and hatch meanings, and a prompt for technical verification. The resulting [drafting standard](drafting-standard.md) names the style monochrome orthographic construction diagrams. The [verification prompt](verification-prompt.md) separates mathematical, semantic, conventional, and visual evidence. Calculations support bounded claims; conceptual service drawings do not become certified engineering drawings through appearance alone. Preserve the earlier explicit arrow-cap geometry when normalizing line roles. + +**coss.com scope:** The user explicitly said not to push anything to coss.com and wants to see illustrations here. Show the proposals in the local Plotter showcase at `#coss-concepts`; do not publish, deploy, or push the website changes. Product research uses the actual `cosscom/coss` source, not this UI repository’s older `apps/www`. The subject is a shared SDK, API key, and operational contract, documented in [coss product concepts](coss-product-concepts.md). This is a brief for proposals, not permission to rewrite product claims or an approved visual anchor. + +## Rejected coss concepts and the next O06 revision + +The user explicitly rejected the first seven coss.com proposals as poor quality and below the other illustrations. Their presence in a technically checked source audit did not establish visual quality. Those seven compositions are superseded by a smaller, new exploration; neither new alternative is an approved reference. + +Our diagnosis: the product icons and shared bus explained the catalog but lacked the central geometric construction of P00, B03, B04, and C01. The response tests a common aperture assembled from different service pieces and a complementary curved interface between two regions. These are metaphors for a common product contract, not representations of infrastructure or manufactured parts. Review the composition before expanding the set again. + +The user called the nine-box **O06** “bland”, then rejected the selected-group/5× compute detail too: “still don’t like o06 i want something that reminds me to a cardboard with a cpu”. We interpret “cardboard” here as a circuit board carrying a CPU. The next CPU-board version supplied a recognizable central processor, regular pins, and routed traces, but the user then asked to remove the outer card and mounting holes, add dashed lines, and reduce crowding. The current response keeps the CPU recognition cues in an open construction field: no board enclosure, no lower contact bank, two I/O routes and two memory routes, more space before the memory package, and dashed center axes with a defined alignment role. This supersedes our inference that an abstract compute-group relationship was the right subject. Keep the blueprint hierarchy and meaningful grid, but do not remove the requested physical recognition cues in pursuit of abstraction. This open processor composition is our response, not an approved reference or a functional circuit. Do not reintroduce an enclosing card or dense peripheral detail into O06 without a new request. + +## W05 and O07 motion experiments + +For W05, the user subsequently requested faster box delivery before the drop, then asked to keep improving it. The feed retains the faster 0.70s travel, but now approaches while the conveyor is still indexing: feed 1.00–1.70s, indexing ends at 1.590269s, and the next drop begins at 1.84s. The 1.8s cycle removes the earlier nearly sequential delivery wait while retaining the fall/contact behavior. Faint fixed feed guides explain the horizontal approach; they clear the core by 4 units and end 8 units before the drop window. These are implementation decisions awaiting user review, not new approved motion references. + +The user separately said O07’s easing still did not look realistic. An initial 0.30 m timing calibration shortened the cycle to 1.113268s, but the user then supplied [this cradle video](https://www.youtube.com/watch?v=09vzFodz4SI). Inspection of the opening swings and stepped frames around 15–17.64s showed a slower, broader motion with crisp contact. The 30 cm assumption was superseded: that trial used an approximately reference-matched 2.4s cycle and 30° release, retaining the nonlinear pendulum curve and instantaneous transfer. These are visual estimates from the video, not measured physical specifications. The reference supplies motion direction while the established flat drawing style remains. The user then asked to speed it up just a little without changing the easing: the cycle first shortened to 2.2s, then to 2.0s after a further speed request, with identical normalized key times, spline controls, and 30° swing geometry. + +The user requested animations for W05 (assembly line) and O07 (Newton’s cradle), then said both “feel slow and lack physics” and do not feel natural. They remain separate examples in the Animations section, with the static gallery drawings retained. The revised direction is a brisk assembly cycle with an accelerating drop, restrained contact response, and conveyor acceleration/deceleration; the cradle should accelerate into contact, transfer motion immediately, and slow naturally at its turning points. The user liked the faster revisions but said they were still not fully natural and W05 remained a bit slow. The next pass specifically reduced idle handoffs, restrained the insertion rebound, smoothed motor acceleration, and tightened the cradle’s arc and turnarounds. Shortening a loop alone is insufficient if every movement still uses generic easing. Existing direction still applies: transparent forms, stable construction guides, rigid moving parts, and careful shared-edge ownership. These are requested experiments, not approved motion references or full physical simulations. + +## Native-size gallery + +The user asked that the gallery SVGs not be scaled down and said enlargement dialogs were unnecessary. Main drawings now render at 560×440 CSS pixels in the gallery, with enough room per column and local horizontal scrolling on narrow screens. Descriptions stay visible inline. The user subsequently asked to remove the extra “Page not found”, “No results found”, “No notifications”, and similar application cards, so the compact 96×96 previews are no longer rendered in the gallery. Existing variant URLs locate the appropriate card instead of opening a dialog. This presentation change does not alter the drawing geometry or promote new style references. + ## Working interpretations These diagnoses come from examining the drawings, not from more specific user quotations: diff --git a/apps/ui/skills/plotter/references/drafting-standard.md b/apps/ui/skills/plotter/references/drafting-standard.md new file mode 100644 index 0000000000..c03ff40464 --- /dev/null +++ b/apps/ui/skills/plotter/references/drafting-standard.md @@ -0,0 +1,98 @@ +# Plotter drafting standard + +Research and house specification, 2026-10-25. Use for new drawings and deliberate revisions. Read the [approved references](approved-references.md) for composition and the [verification prompt](verification-prompt.md) before review. The [source audit](source-audit.md) records known exceptions in the inspected revisions; approval of an illustration’s appearance does not prove every annotation. + +## Name and scope + +**Monochrome orthographic construction diagrams** is the precise name for this house illustration language. “Blueprint illustrations” remains useful informal shorthand. The drawings present one flat geometric relationship through visible outlines, restrained construction lines, sparse dimensions, and occasional line hatching. P00 supplies the circle/square relationship, C02 the modular assembly and switch geometry, and B04 the alignment of work within a time grid. + +“Blueprint” historically identifies a reproduction process: cyanotypes commonly reproduce designs as white lines on a blue ground. Black or theme-colored SVG linework is not a cyanotype reproduction. This distinction concerns medium, not drawing accuracy. [Te Papa collection terminology](https://collections.tepapa.govt.nz/category/421176) + +Declare one mode in the drawing’s source note or review: + +| Mode | What the drawing asserts | Required evidence | +| --- | --- | --- | +| Geometric construction | Coordinates, radii, tangencies, equal intervals, or proportions | Calculations from the actual SVG geometry | +| Physical technical view | The shape of a real or explicitly hypothetical object in a stated plan, elevation, or section | Defined projection, scale/units, cut plane where relevant, source/model, dimensional consistency | +| Functional schematic | Connectivity and behavior of a named system | Defined nodes, ports, edge meaning, state, authoritative symbol/behavior references | +| Conceptual diagram | A limited analogy explaining a service or UI relationship | Explicit mapping and limitations, current primary sources for product claims, correct depicted geometry | + +Most Plotter drawings are geometric constructions or conceptual diagrams. A plug outline is not automatically an electrical schematic; a circle marked “Cache” is a conceptual node, not an IEC electrical component. Orthographic here describes the flat presentation, not a claim that every diagram is a projection of a buildable object. + +## What the external standards establish + +These are verified source facts, separate from the house choices below: + +- **ISO 128-2:2022** concerns technical line types, configurations, leaders, and references, including discipline-specific applications. Its public abstract supports treating line roles consistently; it does not establish Plotter’s pixel values. [ISO catalogue](https://www.iso.org/standard/83355.html) +- **ISO 128-3:2022** concerns views, sections, and cuts using orthographic projection methods. A cut or section therefore needs a meaningful relationship to an object and view. [ISO catalogue](https://www.iso.org/standard/83356.html) +- **ISO 129-1:2018** concerns presentation of dimensions and associated tolerances. Its scope distinguishes presentation from the meaning/application of dimensional tolerancing. Decorative dimensions cannot establish fit or manufacturability. [ISO catalogue](https://www.iso.org/standard/64007.html) +- **IEC 60617 DB** is IEC’s maintained collection of graphical symbols for diagrams. **IEC 60417** addresses symbols on equipment, a different use. A claimed IEC symbol needs the actual symbol entry and application context. [IEC TC 3](https://tc3.iec.ch/standard-as-database/) +- **NPS HABS drawing guidelines** distinguish line weights by the represented feature and require scale-aware adjustment. They differentiate hidden/missing features and overhead features. Their hatch rules depend on the view and material: plan poché and section treatment differ, and standard CAD surface patterns must not substitute for measured conditions. This demonstrates why “architectural hatch” is not one universal texture. [HABS guidelines, §§4.2–4.7](https://www.nps.gov/subjects/heritagedocumentation/upload/HABS-Guidelines-Measured-Drawings_508.pdf) + +Research used the public ISO catalogue abstracts and the cited IEC/NPS material. It did not review the full paid ISO standards or validate individual IEC symbol entries. This document defines an internally consistent illustration standard; it does not certify ISO/IEC/HABS compliance. + +## House stroke palette + +All lengths below are SVG user units in a `560 × 440` viewBox. Width and opacity are independent: width expresses drawing role, opacity establishes visual emphasis. The values intentionally stay close to the approved anchors; they are house choices, not physical pen widths or quotations from ISO. + +| Role | Width | Opacity | Pattern | Use | +| --- | ---: | ---: | --- | --- | +| Primary form | 0.95 | 1 | Continuous | Main silhouette or selected functional route | +| Secondary form | 0.65 | 0.50 | Continuous | Subordinate real detail | +| Dimension / leader | 0.65 | 0.50 | Continuous | Measurement, extension, explanatory leader | +| Reference guides | 0.60 | 0.24 | Continuous | Subordinate alignment projections and reference scaffold | +| Grid | 0.60 | 0.10 | Continuous | A useful repeated coordinate/module field | +| Hatch | 0.65 | 0.27 | Continuous | One declared bounded region | +| Section cut, when needed | 1.30 | 1 | Continuous | Material actually intersected by a stated cut plane | + +P00’s existing `1.0` primary width is an accepted anchor variation. Continuous secondary lines share the approved diagonal’s `0.65` width and `0.50` opacity, including dimensions and leaders; do not create per-drawing variants. C02’s switch is the reusable component reference: track `30 × 18`, corner radius `9`, thumb radius `5.5`, edge inset `3.5`, primary width `0.95` at full opacity. Preserve those proportions when scaling the component as a whole. + +Assign patterned lines by meaning: + +| Role | `stroke-dasharray` | Width / opacity | Meaning | +| --- | --- | --- | --- | +| Axis / centerline / pitch locus | `8 3 1 3` | 0.60 / 0.24 | A center, symmetry axis, or theoretical generating locus | +| Hidden or absent outline | `3 5` | 0.65 / 0.45 | Geometry not presently visible or present; explain which in context | +| Motion or transfer | `4 6` | 0.65 / 0.50 | Proposed movement or conceptual transfer, with solid open arrowhead | + +These patterns are deliberately different. A continuous construction that directly explains the principal form, such as a generating radius or inset, uses the shared secondary style; the lighter reference-guide role is reserved for subordinate scaffold. Do not choose a dash merely because it looks technical. If a drawing includes both hidden and absent features, distinguish them with labels or remove one ambiguous role. A physical hidden edge and a hypothetical motion path must never be the same unlabeled mark. + +Use butt caps on dashed shafts to retain predictable dash lengths; use consistent joins. The user-approved open arrowhead has two 6-unit legs, each at 45° to the shaft: axial offset and half-width are both `6 / √2 ≈ 4.242641`. Its two arms are solid, even when the shaft is dashed, and meet the shaft’s terminal dash at the tip. Set dash phase or start the dashed path at the tip to avoid a detached arrow. Dimension endpoints use the existing 8 × 8 diagonal slash; use a radius arrow/leader for a radius rather than a slash pretending to be a point. This explicit preference in [direction.md](direction.md) takes precedence over inferred house defaults. + +Draw a coincident segment once. Do not stack a grid stroke, extension stroke, and object stroke on the same segment. Split subordinate geometry at the required boundary; preserve one owner for each shared segment. Plan label clearance by shortening/splitting lines, with at least 3 units around the actual text bounds. All forms are transparent; remove area tints and background-colored patches that erase guides. Text glyph fills are allowed. A filled junction marker belongs only to a declared symbol convention, never to a background patch. + +## Hatch grammar + +Hatching describes a region, not a lighting effect. For every hatch, record the region’s boundary and its meaning: for example “square minus circle in this quadrant,” “selected interval,” or “unspecified material cut by this section.” Only claim a material such as concrete or steel when its representation is sourced and appropriate to the stated discipline/view. + +- **H1:** parallel lines at −45° in SVG screen coordinates, width `0.65`, opacity `0.27`, perpendicular pitch `12 / √2 ≈ 8.485`. This is P00’s 12-unit spacing measured along horizontal/vertical edges. +- **H2:** the same pitch and weight at +45°, reserved for distinguishing an adjacent region. Use one angle and one pitch throughout a single region. +- Clip each line analytically or with a unique SVG clip path to the declared region. Boundaries remain continuous; hatch endpoints terminate at them without extending beyond them. Do not invent a boundary to accommodate a convenient hatch tile. +- For a cut region, exclude voids and openings. The same object keeps a consistent hatch in the same view. A generic 45° hatch does not identify material or prove a compliant section. +- **S1, schematic support:** −45° marks with horizontal/vertical extent `16`, hence length `16√2`, at a `16`-unit boundary intercept pitch; width `0.65`, opacity `0.27`. Omit strokes intersecting openings. Compact versions may retain every fourth mark. This one-sided boundary convention identifies a fixed support or schematic wall only when named; it is not a fully specified material section or H1 region. + +The anchor source may contain older opacity, spacing, or fill choices. Preserve its composition; apply this normalized palette when a revision is authorized. Do not silently reinterpret old graphics as standards-compliant. + +## Geometry and dimensions + +Each drawing has one coordinate system and one declared dimensional scale. A local unit `u` may differ between drawings, but all `u` labels within a drawing must resolve to one value. Raw numeric dimensions describe SVG construction units unless a real-world unit and scale are explicitly supplied. A `144` label on a conceptual deployment card measures its geometry, not a Vercel product property. + +Derive points, paths, and labels from the same named parameters. Preserve full precision in calculations; round serialization, not the underlying construction. A circle of center `C` and radius `r` has point `P(θ) = C + r(cos θ, sin θ)`. In SVG, positive `y` points down. A labeled circular fillet uses a true circular arc with tangent straight segments. A quadratic Bézier that merely resembles a corner does not establish its radius. + +Every dimension identifies its two witnesses: edges, centers, tangent points, or datums. State which. Dimension line and extension lines must agree with those witnesses. A radius leader terminates on the declared arc and is aligned with its generating radius when drawn as a radial dimension. An angle arc must be concentric with the angle’s vertex and meet the two rays. Use `R` for radius and `⌀` for diameter consistently; do not attach either to an arbitrary curve. + +House numerical tolerances for generated SVG: intended equalities and incidences must be derived exactly; serialized coordinate checks allow `0.05` SVG unit and angle checks allow `0.1°`. These thresholds accommodate decimal serialization, not inaccurate construction. Report the residual, even when it passes. Tolerances for physical manufacturing require a separate engineering specification. + +## Symbols, topology, and analogy + +Before drawing a connector, specify its start, end, direction, and meaning. For functional schematics, construct an explicit node/edge list and identify each port. Choose a documented junction/crossing convention; a crossing must never accidentally create a connection. Check that each physical contact, pipe, or conductor actually reaches the intended port, and that a deliberately disconnected contact remains separated. + +For literal electrical schematics, cite the precise IEC 60617 symbol entry or another explicitly chosen authoritative standard, including name, identifier, and relevant application notes. Keep terminal count and orientation consistent with that source. If the entry is unavailable, mark the symbol unverified; a resemblance is not evidence. A custom symbol is allowed in a conceptual diagram when defined in a local legend and not attributed to IEC. + +For service illustrations, maintain a short mapping: depicted object → product concept; connecting line → relationship; omitted processes → limits of the claim. Verify product behavior against current official documentation. Geometric equality may encode “same source version” or “equal amounts,” but must not imply identical builds, zero fees, guaranteed availability, or timing that the service does not provide. + +Mechanical symbols require the same discipline. Pitch-circle tangency and shared module are necessary geometric checks for a gear illustration, but do not prove conjugate tooth profiles, interference-free rotation, load capacity, or manufacturability. Call simplified profiles schematic and qualify engagement claims unless the relevant contact/kinematic checks have been performed. + +## Completion gate + +For an explicit technical verification request, complete the [verification report](verification-prompt.md) for the current source and rendered revision. For an ordinary refinement, check only affected claims and their dependencies, record the relevant calculation or visual check concisely, and disclose unverified behavior. Geometry, semantics, and visual fit remain separate verdicts. A beautiful reference can contain a factual defect; a mathematically correct drawing can still fail art direction. There is no “unquestionable” guarantee: there is a bounded claim, evidence, residual error, and a reviewable conclusion. diff --git a/apps/ui/skills/plotter/references/implementation.md b/apps/ui/skills/plotter/references/implementation.md index 06609093c9..cecb72de12 100644 --- a/apps/ui/skills/plotter/references/implementation.md +++ b/apps/ui/skills/plotter/references/implementation.md @@ -2,7 +2,7 @@ ## Geometry and rendering -The established examples use a `560 × 440` SVG viewBox. Typical main outlines are about `0.85–1.1` units, with `0.55–0.7` unit guides at lower opacity. Guides range from barely visible grid lines to readable dimensions; fills are sparse. These are starting proportions, not fixed requirements for every output size. +The established examples use a `560 × 440` SVG viewBox. Use the [drafting standard](drafting-standard.md) for current line roles, dash patterns, hatch spacing, and geometry tolerances. Preserve transparent forms and adapt the complete composition to its intended display size; historical fills and per-study stroke variations are not new defaults. - Use true circle/ellipse arc commands for radius-labeled bends. A quadratic curve across a square corner is not a circular fillet; an `R24` annotation must describe an actual radius-24 arc. Check arc direction and tangency as well as endpoints. - Derive repeated spacing, centers, and dimensions from the same coordinates. A dimension should measure the indicated edges or centerlines, consistently. In a scheduling grid, verify that the selected interval matches the day and time labels. @@ -33,12 +33,18 @@ The showcase owns its drawings and wrapper independently of the production integ Choose style references from [the approved set](approved-references.md); production placement and bundled historical screenshots are separate from reference approval. -Keep production page layouts local, as explicitly requested by the user. Do not recreate a shared page-hero component. The showcase displays standalone drawings in a gallery with enlarged previews. +Keep production page layouts local, as explicitly requested by the user. Do not recreate a shared page-hero component. The showcase displays each standalone drawing at its native 560×440 size in the gallery, with no enlargement dialog. Grid columns accommodate the full canvas; narrow screens scroll within the drawing rather than scaling it down. Keep the illustration captions inline. Do not add the extra compact empty-state application cards (such as “Page not found” or “No results found”); the user explicitly removed those from the gallery. The selected production integration for Main, Particles, and Chat uses the heading treatment from [PR #895](https://github.com/cosscom/ui/pull/895): `clamp(2.25rem, 1.5rem + 2.6vw, 4rem)`, line-height 1, and GEOM50, with normal letter spacing and automatic optical sizing. These settings are scoped to those pages. Particles keeps C03 on the right and its search/filter directly beneath the left-aligned description, rather than adding hero CTAs. Keep the project’s typography: no added letter-spacing, no synthesized bold/italic, one GEOM value per role, and typeset punctuation. Read the current guide for details instead of copying font configuration into every drawing. -The unlinked showcase route is `/ui/plotter`; `?variant=` opens an enlarged preview. Category filters organize the collection. Original IDs are `primitives-reference` and `particles-reference`; recent IDs include `cal-week-grid`, `cal-availability-plan`, `interface-spacing-plan`, `workflow-switch-plan`, and `workflow-branch`. The old lab and prototype routes redirect here with their query parameters. Inspect current app configuration for the running address. Use bundled references when this checkout is unavailable; do not assume localhost is running. The showcase has no navigation or sitemap link and opts out of indexing. +The unlinked showcase route is `/ui/plotter`; `?variant=` selects the drawing’s category and scrolls to its inline card for compatibility with existing preview links. Category filters organize the collection. Original IDs are `primitives-reference` and `particles-reference`; recent IDs include `cal-week-grid`, `cal-availability-plan`, `interface-spacing-plan`, `workflow-switch-plan`, and `workflow-branch`. The old lab and prototype routes redirect here with their query parameters. Inspect current app configuration for the running address. Use bundled references when this checkout is unavailable; do not assume localhost is running. The showcase has no navigation or sitemap link and opts out of indexing. Use focused repository formatting/type checks appropriate to the edited files, plus browser inspection. Keep standalone illustrations in the showcase until the user chooses a production integration. Removed drawings remain documented in the feedback ledger rather than retained as hidden gallery entries. When revising a production drawing, inspect its current source and update the corresponding showcase snapshot explicitly when both should show the same revision. Keep showcase imports local so either addition can ship independently. This skill does not authorize publishing, pushing, or creating a PR by itself. + +## Engineering animation previews + +W05 and O07 keep their static gallery drawings in `engineering-studies.tsx`. Their motion experiments live separately in `production-chain-motion-drawing.tsx` and `newtons-cradle-motion-drawing.tsx`, inside the showcase’s Animations section. `engineering-motion-preview.tsx` owns playback, replay, static fallback, reduced-motion handling, and pausing when the drawing leaves view; its child SVG owns the timeline. This separation is a showcase choice, not a shared production page hero. + +Keep construction guides fixed and outside swept regions when they would otherwise cut through moving parts. Do not hide guides with opaque object fills. W05’s rigid carriers preserve their pitch and hand coincident edge ownership over at seating; O07’s bobs rotate about fixed pivots with constant string lengths. Animation does not turn either illustration into a manufacturing design or a complete physics simulation. Review the full loop, contacts, restart boundary, and controls separately from the static source. diff --git a/apps/ui/skills/plotter/references/remaining-source-audit.md b/apps/ui/skills/plotter/references/remaining-source-audit.md new file mode 100644 index 0000000000..ddd8423b9c --- /dev/null +++ b/apps/ui/skills/plotter/references/remaining-source-audit.md @@ -0,0 +1,89 @@ +# Remaining Plotter source audit + +Bounded source pass on 2026-10-25. This supplements [source-audit.md](source-audit.md); it is not rendered acceptance, a mechanical certification, or approval of every inspected drawing. The 43 studies below received a source reading focused on their principal dimensions, circle/arc relationships, interval alignment, and annotation meaning. Together with the separate audit’s 12 studies, this covered the principal static construction of all 55 registered studies at that snapshot. Later revisions require fresh checks of affected claims. The revised shared S01/M03 component has since received fresh source, geometry, marker-clearance, and limited browser checks in [source-audit.md](source-audit.md). Those named checks supersede the earlier S01 source snapshot; the broader static inventory does not verify other animations. Individual successful checks do not certify every path in a drawing. + +## Concrete findings and corrections + +| Study | Evidence | Outcome | +| --- | --- | --- | +| **E01 / `404-doorway`** | In `app/plotter/not-found-architecture-studies.tsx`, the radius leader formerly ran from `(208,248)` to `(296,160)`: length `124.45079349`, despite the swing arc having `R128` and the label `r = 4u`. It missed the arc by `3.54920651`. | **Corrected only the leader endpoint.** It now derives `(208 + 128/√2, 248 − 128/√2) = (298.50966799,157.49033201)`. The represented doorway, swing arc, label, and cap geometry are unchanged. | +| **E06 / `404-unbuilt-arch`** | In `app/plotter/not-found-geometry-studies.tsx`, the R126 leader started at `(208,176)` and the R90 leader at `(349,216)`. Their nominal-center radius residuals were `+0.49110641` and `+4.11163584`. Because the approved arch endpoints are rounded, its serialized SVG arcs have centers `(279.99998991,280.05043350)` and `(280.00000072,279.98863831)`, rather than precisely `(280,280)`. Residuals against those actual arcs were `+0.53256995` and `+4.10390924`. | **Corrected only the two leader attachment points.** They are now `(208.303044749,176.437943637)` and `(345.990882349,218.790570189)`, projected onto the existing SVG arcs. Incidence error is below `10⁻⁹` units. The approved arch paths are unchanged; these corrections do not claim that their rounded endpoints have become an exact concentric construction. | +| **T01 / `type-letter`** | In `app/plotter/object-studies.tsx`, the counter is drawn with cubic Béziers. Its upper-left quarter meets the 45° ray at `(222.25,176.25)` with radius `61.87184335` about `(266,220)`. The `r` leader ends at `(222,176)`, missing the actual outline by `0.35355339`. The counter’s cardinal radius is `62`, so it is also an approximation to a circle. | **Unchanged; needs a deliberate choice.** Preserve the letterform and call the annotation a construction radius/approximation, or replace the intended circular counter with true `R62` arcs and derive the leader endpoint. Do not silently rewrite an approved type drawing. | + +The E01/E06 edits are narrow incidence corrections, not a restyle. Browser inspection of those annotations remains part of the parent review. + +For T01, the smallest proposed wording change is `r ≈ 62`, which acknowledges an approximate nominal radius while preserving the approved cubic letterform. Re-anchoring its leader to the actual curve at `(222.25,176.25)` would be a separate small incidence correction. Neither change was applied as part of this audit. + +## Selected checks that passed + +- **P01:** the common tangent is within `0.00183` units of the R80 circle and `0.000007` units of the R40 circle. Both circles share the baseline at `y=324`. +- **P02/P03:** the corner’s offset is `104 − 80 = 24`; the proportion study uses widths `36,54,81,121.5`, each multiplied by `1.5`, on a shared baseline. Its indicated slope is `1/2`. +- **K02/K03/K06/K07/K08:** the selected overlap, one-hour UTC/UTC+03 booking, Thursday 10:00–11:00 cell, 15 + 15 = 30 minute construction, and `6u × 4u` availability extents agree with the displayed geometry. K07’s R28 leader rounding residual is approximately `0.00143`. +- **I03/I04/I05/I06:** the inspected insets, browser R24/R12 fillets, phone’s concentric R32/R24 corners and eight-unit offset, safe-area margins, and conversation spacing agree with the dimensions. The phone has `u=16`, total width `10u`, and horizontal margins `1.5u + 7u + 1.5u`. +- **W02/W03/A03:** branch bends are R24 with outlets separated by `4r`; the switch matrix uses 64-unit pitch; the coordinate datum at `(120,328)` and selected point `(312,168)` give `(6u,5u)` with `u=32`. +- **O03/O04/O05:** keyboard pitch is 72 and stagger is 18 (`¼u`); the lamp’s generating circle is R96; both folder tabs measure 64 while the bodies measure 216 and 136. +- **B06/B07/B09:** the inspected branch fillets, 176-to-264 resize with constant 56 height, and 132-wide relation tables with 32-unit rows agree with their labels. This checks diagram geometry, not every claim about the external products. +- **S01/S03/S05/S06/S07/S08/S09/S12/S13/S14:** checked relationships include 16-wide paired routes, the missing 56-wide index record, 64-pitch notification positions, a `200 × 200√2` page and `u + 3u + u` writing margins, 48-unit plot intervals, concentric R40/R24 lock arcs, the flap reflected from `y=88` to `y=248` about `y=168`, the pin’s near-tangent serialized sides, seven 40-wide day columns, and the 240:180 image frame ratio. +- **E00/E02/T02:** the missing-corner dimensions use `u=40`; the interrupted route uses concentric R52/R60/R68 bends with a 16-unit band; the C mark uses R114/R78 arcs and a 90° opening. These checks retain the qualified rounding tolerance; they are not symbolic proofs for every serialized coordinate. + +## Final five static studies + +This follow-up closes the five source-reading gaps from the first addendum. It does not change the C03 animation or other motion sources. + +| Study and source | Principal checks | Qualification / outcome | +| --- | --- | --- | +| **C00 / `particles-reference`**, `app/plotter/original-particles-blueprint.tsx` | Shared inset `188 − 164 = 24 = u`; row width `404 − 164 = 240 = 10u`; R16 leader residual `−0.005245`; switch track `30 × 18`, R9, thumb R5.5, right/top/bottom insets `3.5`. | Checked geometry passes. Historical line opacity/dash choices remain outside the newer normalized house roles; this pass does not restyle them. | +| **C03 / `particles-recombination`**, static `app/plotter/particles-blueprint.tsx` | Three fragments are `144 × 48`; the assembled panel is `144 × 144 = 3u × 3u` with `u=48`. Dividers at `y=208,256` split `y=160…304` into three equal rows. Source and target content share relative coordinates. R16 transfer bends are tangent to their straight segments. Arrow tips `(320,184/232/280)` meet the starting dash; 4.24-offset shoulders produce approximately 6-unit, 45° legs. Switch geometry matches C00/C02. | Static construction passes the inspected relationships. The distinct animation and its sampled cubic transitions were not audited or edited here. The static arrows retain legacy `4 5` dashes and `0.36` opacity; that is a future semantic-style normalization item, not a dimensional failure. | +| **B05 / `service-stripe-payment`**, `app/plotter/service-studies.tsx` | Input pills are 112 wide (`2u`); checkout pill is 56 wide (`u`); all have 32 height/R16 ends. Selected route’s two R16 bends meet straight segments tangentially. Radius leader residual `−0.019387`. Wallet/bank alternatives join the shared route at its final horizontal segment. | Geometry passes. Keep the illustrated Card → `$24 · Paid` outcome scoped to a completed successful payment. Do not imply all bank methods settle immediately or that checkout completion itself proves payment success. Stripe distinguishes session `complete` from `payment_status=paid`, and states that processing may continue after completion. [Checkout Session reference](https://docs.stripe.com/api/checkout/sessions/object). | +| **B08 / `service-notion-blocks`**, `app/plotter/popular-service-studies.tsx` | Incoming block and absent slot both measure `200 × 40`; width `5u` gives `u=40`. Page has 20-unit horizontal margins, 16-unit top/bottom margins, and 8-unit gaps between rows. Both R24 route bends are tangent; arrow tip `(386,300)` meets the first dash and points toward the matching slot. | Checked geometry passes. This is a block-composition analogy, not a claim about an exact Notion screen, fixed block size, or API move operation. Notion identifies headings, paragraphs, and to-do content as block types, which supports this scoped analogy. [Notion block reference](https://developers.notion.com/reference/block). | +| **S04 / `state-offline`**, `CloudForm`, `CloudGap`, and `OfflineStudy` in `app/plotter/system-state-studies.tsx` | Paired route uses concentric R32/R48 bends around `(240,296)`, giving a 16-unit band and R40 centerline. Gap `268 − 236 = 32`. R40 leader residual `−0.006040`. Cloud lobe seam residuals against the intended R30/R48/R36 generating circles are all below `0.006607`; their nominal bottom tangencies share `y=200`. | Checked construction passes the serialization tolerance. Adjacent cloud lobes intentionally intersect; they are not mutually tangent fillets. Offline/sync wording is illustrative empty-state copy, not verification of application connectivity or storage behavior. | + +## Coverage inventory + +These 43 codes and IDs were included in this bounded source reading: + +| Codes | Study IDs | +| --- | --- | +| P01, P02, P03 | `primitives-tangency`, `primitives-radius`, `primitives-proportion` | +| C00, C01, C03 | `particles-reference`, `particles-assembly`, `particles-recombination` | +| K02, K03, K06, K07, K08 | `cal-availability`, `cal-time-zones`, `cal-week-grid`, `cal-time-modules`, `cal-availability-plan` | +| I03, I04, I05, I06 | `interface-spacing-plan`, `interface-browser-plan`, `interface-phone-plan`, `interface-chat-plan` | +| W02, W03, A03 | `workflow-branch`, `workflow-switch-plan`, `data-coordinate-plan` | +| O03, O04, O05 | `object-keyboard-plan`, `object-lamp-elevation`, `object-folder-plan` | +| B05, B06, B07, B08, B09 | `service-stripe-payment`, `service-github-merge`, `service-figma-constraints`, `service-notion-blocks`, `service-supabase-relations` | +| T01, T02 | `type-letter`, `type-monogram` | +| S01, S03, S04, S05, S06, S07 | `state-missing-page`, `state-search`, `state-offline`, `state-notifications`, `state-files`, `state-data` | +| S08, S09, S12, S13, S14 | `state-access`, `state-email`, `state-location`, `state-bookings`, `state-image` | +| E00, E01, E02, E06 | `404-reference`, `404-doorway`, `404-interrupted-path`, `404-unbuilt-arch` | + +The separate audit covers **P00, C02, B01, B02, B03, B04, S10, S11, O06, W04, W05, O07**. Its own findings and limitations still apply. + +No registered static study remains outside the combined source-reading inventory. This means coverage of each principal construction, not exhaustive proof of all serialized paths, a pass on every legacy fill/line role, or visual approval. Compact variants were read when they shared the inspected geometry, but were not independently rendered here. Motion sequences remain outside this static addendum; the main audit separately records the bounded M03 checks. + +## Transparency scan across all 55 studies + +At the original source snapshot, all 55 registered hero drawings successfully rendered to SVG for an automated inherited-fill scan. The later S01/S10/S11/engineering revisions were source-read for fills; they add no area patch or background fill (the RC junction retains its semantic foreground dot). The full 55-drawing automated scan was not repeated after those revisions. The scan excluded text and did not flag ordinary `currentColor` semantic dots. It identified background-token fills or translucent area tints in **26 studies**: + +`P00, P02, P03, C01, C02, K02, K06, K07, K08, I06, W03, A03, B01, B02, B03, B04, B05, B06, B07, B09, T01, T02, E00, E01, E02, E06`. + +Those surviving fills conflict with the newer transparent-form direction. The count is a mechanical inventory, not permission for a blanket deletion: some currently conceal continuous guides or wires, so each affected composition needs its paths inspected when the fill is removed. This pass did not replace fills with another patch, normalize historical line roles, or change approved composition geometry. + +## Checked source snapshots + +SHA-256 after the narrow E01/E06 corrections; the S10-containing source and engineering source hashes below were refreshed after their separately documented re-review. The new shared S01 and M03 hashes are in the main audit. + +| File | Hash | +| --- | --- | +| `app/plotter/not-found-architecture-studies.tsx` | `cddb10767627b6d07aab10548b1b51cdbd1b0540bb5cb33d697a8df013f51fe7` | +| `app/plotter/not-found-geometry-studies.tsx` | `f9760542d6d79088bd33aa64af1d14ac60abc1a7b735dd4bae1f8fea87207e71` | +| `app/plotter/object-studies.tsx` | `c59c5a103b7c1e399d5c1c94003acd6e3a29b56ebe5ee5a8989ce0bb200a7e30` | +| `app/plotter/engineering-studies.tsx` | `0c2ef29222c874e1777fb62fc0dd3059b683deebbbf2caf1e50420ba30b922b2` | +| `app/plotter/original-particles-blueprint.tsx` | `085026c3b3843bb2ab144e1fe147533e5af48587673e1f94cc4e8d1c0bd31cdc` | +| `app/plotter/particles-blueprint.tsx` | `3f6da32aac516cbf8ac987277ec3f69b242a8453cf16de663b7b71b62297ec51` | +| `app/plotter/service-studies.tsx` | `fb16277ad79c6aacb3734d9e34b09816d29cc7ec8615260329a3cd8541d5f912` | +| `app/plotter/popular-service-studies.tsx` | `1331a3236bca39518600580d0aaf696dbd452ec79505fc1ea66fdcc6c3d5c12e` | +| `app/plotter/system-state-studies.tsx` | `0ba231cd9b1069e28c6a5bea028758c39812fcd5c45f313f1eab2f0d86008f30` | + +Re-run source checks and update these hashes after further edits. No source reading or attribute scan establishes thumbnail readability, narrow-layout quality, or user approval. + +Local review captures are not bundled with this skill. Regenerate the named views from Plotter when reviewing another checkout; source hashes identify the inspected revisions. diff --git a/apps/ui/skills/plotter/references/source-audit.md b/apps/ui/skills/plotter/references/source-audit.md new file mode 100644 index 0000000000..3b3b4b0362 --- /dev/null +++ b/apps/ui/skills/plotter/references/source-audit.md @@ -0,0 +1,163 @@ +# Plotter source audit + +Recorded 2026-10-25 against the working tree based on HEAD `f6da7e24`. **Revision status:** O06, W04, W05, O07, S10, S11, and the new S01-based M03 were re-read and recalculated after user feedback. Their older board, bore/insert sequence, involute-gear, wall-socket, and plug-animation findings are superseded. The file hashes below identify the inspected revisions; subsequent edits invalidate affected checks. This bounded source, geometry, and meaning review does not certify the gallery. + +## Evidence and verdict scope + +- **Source checks performed:** read TSX geometry, inherited stroke/fill roles, SVG path commands, labels, descriptions, and the drafting standard for P00, C02, B01–B04, S10, S11, O06, W04, W05, O07, and revised S01/M03. Two replacement coss concepts have separate coverage below; the former seven were rejected for visual quality. +- **Independent calculations performed:** recomputed distances, units, incidence, tangency, angles, RC arithmetic, gear pitch geometry, cradle trajectory intersections, and marker clearance with Python’s `math` functions. The new gear profiles also received a sampled static nonintersection check. +- **Implementation evidence:** this reviewer ran Biome and focused strict TypeScript checks on the earlier corrected sources. The authors report full UI TypeScript and scoped Biome passes after the new revisions. These are attributed build results, not independent re-runs by this reviewer. Showcase corrections are separate from production integration. +- **Render review performed:** independently viewed author-rendered PNGs of all four revised engineering studies and rasterized current S10/S11/S01 SVGs. The latter rasterizer used fallback fonts; it establishes shape visibility, not browser typography. Parent browser evidence is attributed below. Final guide-only cleanup was source-reviewed; saved images may precede it. +- **Mechanism limitation:** static profile separation does not prove conjugate motion, actual tooth contact, load capacity, or manufacturability. No older involute/collision-phase result applies to the new schematic gear profiles. + +PASS below means the named claim was checked. NEEDS QUALIFICATION means a wider claim lacks evidence or a convention remains ambiguous. Legacy style findings are not silently converted into passes by a geometry result. + +The [remaining-source audit](remaining-source-audit.md) covers the other 43 principal static constructions, including E01/E06 callout corrections and the gallery-wide fill inventory. Revised S01 is rechecked here without adding another registered study. Together these reports cover all 55 principal constructions; they do not establish exhaustive path correctness or complete style normalization. M03 motion and the coss concepts are separate from that count. + +### Current integration render evidence + +The parent confirmed inspecting revised S10/S11 in hero and 96-pixel compact presentations, and exercising new M03 movement, Pause/Resume, and Show static/Play animation. Saved parent evidence: revised gears (`plotter-gears-revised.png`, local capture), revised socket (`plotter-socket-revised.png`, local capture), S01-based M03 (`plotter-s01-animation.png`, local capture). The M03 capture precedes the final guide/leader cleanup. These observations do not constitute user approval. + +The parent previously reviewed the nine-box O06 revision with contacts in light/dark themes, plus O06/W04/W05/O07 as dark gallery thumbnails beside approved references. The user subsequently called that O06 revision “bland” and also rejected its 5× compute-detail replacement. Those two render snapshots are historical; neither establishes approval of the current CPU-board direction. Saved evidence includes final GPU (`plotter-gpu-revised.png`, local capture) and dark object gallery (`plotter-objects-dark.png`, local capture). The parent reports final full reloads without the development-issue badge; transient edit-time hydration messages remain only in historical logs. This is attributed observation, not this reviewer’s independent console inspection. A complete theme/mobile pass across the whole collection was not reported. + +### User-feedback criteria checked + +| Study | Revised evidence | +| --- | --- | +| W05 | One conveyor carries identical open frames at constant pitch; a matching core descends at the center station and is seated in the right frame. | +| W04 | A 24-unit grid locates routing/terminals and is trimmed around wires, symbols, and labels. Topology and RC arithmetic remain consistent. | +| O06 | The user rejected the compute-array/detail studies and requested a recognizable board with a CPU. Latest feedback removes the enclosing card and mounting holes, asks for dashed lines, and calls the composition crowded. The revised open drawing retains the CPU and pins, reduces ten routes to four, removes the lower contact bank, spaces memory farther away, and adds dashed center axes. | +| O07 | A restrained fixed support replaces the surrounding frame; exact strings, tangent rest positions, release arc, and dimensions remain legible. | +| S11 | The female flange, recessed body, and contact pockets are separate from the hatched support; mouth gap and mating axes agree. | +| S10 | 10/8/6 broad, rounded schematic teeth replace 24/18/12 finely sampled teeth. Static geometry and pitch relationships agree. | +| M03 | The S01 route/page construction is shared by static and animated views. Its request follows the route centerline and leaves the missing address gap intact. | + +## Corrected reference drawings + +| Study / source function | Calculation and evidence | Current result | +| --- | --- | --- | +| P00 / `primitives-blueprint.tsx: PrimitivesBlueprint` | Square `240×240`; `u=40`; circle `(284,204), r=120=3u`; four side tangencies and the 45° diagonal agree. Radius endpoint is now `(284+120/√2,204−120/√2)`; angle arc endpoint is `(284+24/√2,204−24/√2)`. Incidence residuals are floating-point roundoff. Cap tip shares the radial endpoint. | **PASS geometry.** Original radial residual `0.20815` is corrected. Legacy style exceptions remain below. | +| C02 / `system-studies.tsx: ModularTrayStudy` | `u=22`; `412−148=264=12u`; widths `110=5u`, `132=6u`; gap `280−258=22`. Switch `30×18`, `R9`; thumb `R5.5`, right/top/bottom inset `3.5`. | **PASS geometry. NEEDS QUALIFICATION for convention:** the dashed layout-allocation box is not a physical hidden edge; document its conceptual role. | +| B01 / `service-studies.tsx: CloudflareEdgeStudy` | Edge `224×224=4u×4u`, `u=56`; cache `R44`; radius-leader residual `0.01796<0.05`. Hit path returns toward visitor; miss path goes toward origin. | **PASS scoped example:** a cache-eligible request with simple hit/miss outcomes. Other statuses, tiered cache, and the whole Cloudflare service are outside its scope. [Cloudflare cache responses](https://developers.cloudflare.com/cache/concepts/cache-responses/) | +| B02 / `service-studies.tsx: VercelDeploymentStudy` | Frames `144×72`, `R12`; spacing `192−168=24`; index `02` identifies the same source version. Description allows a rebuilt production artifact. | **PASS scoped explanation.** Preview promotion rebuilds; staged production promotion is a different flow. [Vercel promotion](https://vercel.com/docs/deployments/promoting-a-deployment) | +| B03 / `service-studies.tsx: StripeSettlementStudy` | `4×64=256`; `4×$25=$100`; `u=64`. Label now says “Gross payments”; description excludes fees, refunds, and availability. Stable ID retains `settlement`; visible title is “Payments, combined.” | **PASS arithmetic and bounded meaning.** Gross aggregation does not depict actual net/available balance or full settlement/reconciliation. [Stripe balance transactions](https://docs.stripe.com/api/balance_transactions/object) | +| B04 / `service-studies.tsx: LinearCycleStudy` | `u=68`; `408−136=272=4u`; illustrative weeks each span `2u`; pills `R16`; rounded radius-leader residual `0.01939<0.05`. Marker now says “Cycle end”; release equivalence removed. | **PASS geometry and corrected scope.** Two weeks is this example, not a universal duration or release boundary. [Linear cycles](https://linear.app/docs/use-cycles) | + +## Revised mechanical and missing-destination studies + +### S10: `system-state-studies.tsx`, `gearOutline`, `MaintenanceStudy` + +The new model uses common module `m=14` and `10/8/6` teeth. Pitch radii `70/56/42` satisfy `r=mz/2`; chosen schematic root radii are `60.9/46.9/32.9` and tips `76.3/62.3/48.3`. Those root/tip proportions and rounded trapezoidal profiles are house illustration choices, not standard involute geometry. Common module/pitch is a meaningful reference relationship; it does not make arbitrary profiles conjugate. [KHK gear-mesh explanation](https://www.khkgears.us/media/1256/gear-mesh-and-tooth-modifications.pdf) + +Centers are `(176,192)`, `(350,192)`, `(408.8,270.4)`. The smaller pair’s distance is `hypot(58.8,78.4)=98=56+42`, with pitch contact `(383.6,236.8)`. A tooth faces a valley along that centerline. Main-drive pitch gap is `174−70−56=48`, matching the dimension witnesses at `x=246/294`. This is a pitch-circle gap, not the shortest outline clearance. + +The reviewer independently rebuilt the same rounded outline equations and sampled each quadratic join at 20 subdivisions. In the static pose, segment intersections were zero; sampled minimum outline clearances were approximately `36.011` for the separated drive and `2.329` for the smaller pair. This bounded numerical check is not a proof for every point or other phases, and the visible clearance does not establish physical tooth contact. + +**PASS:** tooth counts, pitch radii, center spacing, phase relation, labeled gap, and sampled static nonintersection. **NEEDS QUALIFICATION:** engagement is schematic. No pressure-angle, conjugate-motion, continuous-contact, strength, or manufacturing claim is supported. The old 25° involute and 25-phase checks are superseded. + +### S11: `disconnected-plug.tsx` + +The female socket is now a distinct section: flange `x=344…356`, recessed body to `x=400`, and two contact pockets ending at `x=388`. The support plane is `x=356`; it is separate from the mouth at `x=344`. Male pins end at `x=264`, so the depicted mouth gap is `344−264=80`. Pins/pockets share centers `y=196/244`, spacing `48`, and height `12`. Pocket depth `388−344=44` exceeds pin protrusion `40`. + +S1 support hatch uses 16-unit strokes at −45° and `.65/.27`, only outside the body opening. It is a fixed-support convention, not material identification. Movement arrows use rounded `4.243` shoulder offsets, producing approximately `6.00051`-unit legs at 45°, within the house serialization threshold. Dashed shafts start at their tips. Duplicate horizontal-dimension witness segments found during review were removed; longer witnesses now meet the helper’s terminal segments at `y=146`. + +**PASS:** separate socket/support geometry, pin/pocket alignment, gap and spacing labels, and the checked line-role corrections. **NEEDS QUALIFICATION:** nominal geometry does not establish electrical type, safe electrical construction, contact force, or a manufactured fit. S11 is static; M03 no longer animates this drawing. + +### S01 and M03: `missing-destination.tsx`, `not-found-motion.tsx` + +Static S01 and animated M03 share the same route/page component. The paired route remains `16` wide, with concentric `R24/R40` bends and an `R32` centerline. It ends at `x=272`; the absent-page reference plane at `x=304` gives `u=32`. The source preserves those primary shapes while splitting guides around them. The external R40 leader meets the outline at `(211.716,215.716)`; radial residual is approximately `−0.000384`, below the `0.05` house threshold. Its two open-cap legs are exactly six units and 45° to the diagonal shaft. + +The hollow request marker has radius `3.5` and follows `M122,296 … H266`, length `54 + 32π + 20 + 26 = 200.530965`. Its maximum center is `x=266`; including its `.95` stroke, its right edge is `269.975`, leaving `2.025` units before the route end and the full 32-unit address gap unbridged. Both bends follow the true centerline; the moving outline stays within the route. + +The `8.4s` loop has monotonic keyTimes and six matching keyPoints. It pauses at each end and returns on the same path, so the loop closes without a jump. Source retains Pause/Resume, Replay, static fallback, reduced-motion hiding/pausing, and offscreen pausing. Parent browser review observed movement, Pause/Resume, and Show static/Play animation. Replay, reduced-motion behavior, and offscreen behavior were source-read but not independently exercised here. + +**PASS:** shared geometry, radius incidence, marker clearance, and source-level loop/control wiring. **NEEDS QUALIFICATION:** this is a request/absence metaphor, not a timing or network-behavior model. Browser evidence is scoped to the controls actually exercised; the last guide cleanup followed the saved M03 capture. + +## Revised engineering studies + +| Study / function in `engineering-studies.tsx` | Independent verification | Scope and verdict | +| --- | --- | --- | +| O06 / `ProcessorBoardStudy` | CPU package remains120 square with eight pins per side, pitch `p=12`, pin width4 and length12; first-to-last center span84=`7p`. The die56 square and RAM40×96 share center height204. Two routes per side at174/234 are symmetric about that center: left from I/O circle edges128 to CPU pin ends208; right from CPU pin ends352 to RAM pin ends404. The board enclosure, holes, lower bank, and six surplus routes are removed. Dashed axes use `8 3 1 3` at `.6/.24`; the grid omits its coincident x280 column. | **PASS named geometry and contact incidence.** This is a hypothetical processor layout, not a functional CPU/RAM circuit, vendor pinout, manufacturing layout, or electrical safety claim. | +| W04 / `RcFilterStudy` | Grid pitch `24`; terminal centers `112/448`, resistor endpoints `184/256`, and junction `328` have the same x phase. Wire `y=184` shares the row phase. Vin reaches R1, whose output and Vout share junction `(328,184)`; C1 connects that node to the lower reference. Plates at `y=256/268` stay separated; grid gaps avoid false electrical continuation. `R=1000Ω`, `C=100×10⁻⁹F`: `τ=100μs`, ideal `fc=1591.549Hz`. | **PASS topology, grid purpose, arithmetic** for an ideal unloaded low-pass referenced to the lower node. Individual IEC 60617 entries were not authenticated; no compliance claim. [TI passive-components lesson, p.13](https://www.ti.com/content/dam/videos/external-videos/zh-tw/4/3816841626001/6119035031001.mp4/subassets/passive_components_and_circuits_2.pdf) supports this topology. | +| W05 / `ProductionChainStudy` | Carrier centers `144/280/416` have pitch `136`. Each frame is `64×64`, with 8-unit walls and a `48×56` opening. The incoming `48×48` core seats at `y=208…256`, leaving 8-unit headroom; final core edges share the frame’s existing side/bottom segments rather than retracing them. Conveyor has `R16` ends and `R8` rollers. Arrow tips `(304,288)` and `(280,200)` meet starting dashes; cap legs are 6 at 45°. | **PASS assembly-line relationship and nominal geometry.** Carrier locations, loading direction, and seated state agree. Equal nominal core/opening widths do not prove a toleranced fit or a production-machine design. Guide/terminal overlaps identified in review were removed. | +| O07 / `NewtonsCradleStudy` | `u=r=22`, pivot-to-center `L=176=8u`, center spacing `44=2u`; rest centers at `y=296`. Released center `(128,272.420471)` is exactly 176 from `(216,120)` at 30°. Strings are trimmed to anchor radius 2.4 and bob radius 22, leaving exposed length 151.6. With `δ=2asin(22/(2·176))`, trajectory endpoints lie exactly 22 from the corresponding bob centers. Independent residuals are about `10⁻¹⁴`; tangent-cap legs are 6. Radius and 30° angle callouts derive from the same constants. | **PASS ideal construction and the checked line-role cleanup.** S1 hatching identifies a fixed support. This planar release diagram does not simulate collisions, damping, restitution, or subsequent motion. Refined support and dimensions were visually readable in the saved raster and parent enlarged preview. | + +For the preceding revision, the reviewer inspected four author PNGs, including the now-superseded GPU contact-bank raster (`plotter-gpu-contacts-source-review.png`, local capture); parent independently reviewed W04/W05/O07 enlarged in the light theme. Later line-ownership cleanup changed only guide extents. Saved parent evidence: circuit (`plotter-circuit-revised.png`, local capture), assembly line (`plotter-assembly-line-revised.png`, local capture), cradle (`plotter-cradle-revised.png`, local capture). These are review observations, not approval as permanent style references. + +The prior 5× O06 detail was checked mathematically and inspected in the browser, then explicitly rejected by the user in favor of a recognizable board with a CPU. The old `plotter-gpu-focal-detail.png` capture is a rejected revision, not current review evidence. The shared-coordinate scaling check does not apply to the replacement board. + +The enclosed CPU-board capture `plotter-cpu-board.png` is superseded by the open processor revision. The new drawing was inspected in the enlarged light-theme preview and Objects gallery; current capture: `plotter-cpu-open.png`. Biome and the full UI typecheck pass. A separate read-only source review confirmed the four pad incidences, concentric CPU/die, shared CPU/RAM center, exact84-unit pin span, and nonduplicated axes; its suggested isolated-axis tick cleanup was applied. Its visual acceptance remains open; it has not joined the approved reference set. W04/W05/O07 source was verified byte-for-byte unchanged from the version immediately before this O06 edit. + +## Remaining cross-cutting findings + +1. **Legacy transparency:** P00 retains a `.035` area tint and background-colored point fills; C02 and B01–B04 retain background-colored shape fills. These conflict with newer transparent-form guidance. Only requested math/wording corrections were made to those approved compositions; no blanket restyling was performed. +2. **Legacy line roles:** approved sources retain historic dash sequences and opacities. Assign meaning before normalization; text replacement cannot identify centers, hidden features, allocation boxes, and hypothetical transfer. +3. **Coincident segments:** some older grids retrace construction/object segments. P00’s `x=164` and `x=404` occur in both grid and construction layers. These remain future cleanup, not a verified one-owner result. +4. **Symbols and behavior:** RC topology and gear pitch math pass their named checks. Individual IEC database symbols and manufacturing validity were not authenticated. Geometric consistency and physical completeness are separate claims. +5. **Rendering:** source results do not prove label clearance, optical hierarchy, responsive appearance, or user acceptance. Edits invalidate earlier render snapshots of affected drawings until rechecked. +6. **Separate production follow-up:** `apps/ui/components/primitives-blueprint.tsx` still has P00’s original rounded radius/angle endpoints. Its production integration belongs to a separate worktree/PR; synchronize the exact showcase correction there as a separate change. This audit does not claim that production duplicate is corrected. + +## Replacement coss concepts: bounded review + +The user rejected the original X01–X07 set for poor quality relative to the established drawings. The prior bus/catalog/comparison geometry checks are historical and do not apply to the replacements. That code was removed from the local showcase. New X01 and X07 remain proposals, outside the 55-study inventory and the approved reference set. + +- **X01 / `common-interface-study.tsx`:** one central opening has radius `64 = 2u`, with `u = 32`. The upper-right piece is translated by exactly `(32, −32)`; its corresponding opening quadrant and projection witnesses use the same radius. The assembled three-quarter opening and the two internal seams each have one primary path owner. The selected lower-left profile band is analytically bounded by radii 64 and 80. Hatching identifies the common profile, not a physical material. +- **X07 / `shared-system-study.tsx`:** the `240×240` square uses `u = 60`. Two circle centers at `(280,148)` and `(280,268)` are `120 = 2u` apart; equal R60 arcs meet tangentially at `(280,208)`. Their complementary semicircles generate the shared S-profile. The outline/seam are separate single paths; the opposite half-circles and axes are construction references. The radial arrow endpoint is computed with `60/√2` offsets. +- **Meaning:** these are compatibility metaphors for a common SDK contract. They do not assert physical construction, infrastructure topology, or implemented Video/Scheduling API availability. The [product claim mapping](coss-product-concepts.md) retains primary-source evidence and those limits. + +Parent browser review inspected the pair in the local two-column section in light and dark themes. Saved capture: `plotter-coss-interface-studies.png` (local capture). Source geometry and visible hierarchy are separate from user acceptance; no reference approval or coss.com publication is implied. Narrow-screen layouts were not independently exercised. + +Checked SHA-256 prefixes under `app/plotter/coss-concepts/`: + +| File | SHA-256 prefix | +| --- | --- | +| `common-interface-study.tsx` | `ecec2d1d7d62` | +| `index.tsx` | `9782beedc977` | +| `shared-system-study.tsx` | `0af177359ce1` | + +## W05 and O07 motion experiments + +These separate showcase animations retain the original static gallery drawings. They have not been selected as motion references. + +- **W05, preceding revision:** the 2.25-second refinement shortened the former 3.0-second cycle. The rigid 48×48 core drops 80 units from 0.04–0.42s; a restrained 0.35-unit rebound settles at 0.470269s. Indexing starts 20ms later and finishes at 1.590269s. Feed finished at 2.16s, leaving 0.13s until the next drop. All core edges share one sampled displacement function and exact wall/floor ownership. Motor/feed interpolation uses cubic constant-jerk ramps with continuous position, velocity, and acceleration at joins. The author checked 9,001 serialized phases: rigid size, exact 136-unit pitch and 272-unit feed travel, no penetration, and gravity-curve approximation error below 0.01 unit. Calculated start/end rasters were identical. The current timing is recorded below. This remains an illustrative gravity/contact model with a driven conveyor, not a machine simulation. +- **O07, preceding revision:** the 1.8-second cadence used a restrained 26° release. Energy-derived cubic segments replaced linear angle chords, giving exactly zero endpoint speed at each turnaround and continuous flight velocity. Contact occurred at 0.45s/1.35s with matched speed magnitude 91.152327°/s and no dwell. The author checked 36,001 poses: no bob overlap, fixed 176-unit strings (residual below 6×10⁻¹⁴), complete disk bounds x116.847…491.153/y256.188…318, and a closed seam. Serialized velocity-continuity error was below 4.6×10⁻⁷°/s away from impacts. The current calibration is recorded below. Damping, compliance, finite contact duration, and interior dynamics remain omitted. A visually delayed wave through tangent rigid disks was rejected because it would require unmodeled deformation or gaps. +- **Presentation:** parent browser review confirmed active 2.25s/1.8s spline timelines and observed moving phases. The fixed support/grid, transparent forms, static gallery drawings, and annotations remain unchanged. Replay and Pause/Resume passed the preceding revision and their shared controls are unchanged. Offscreen pausing and reduced-motion static fallback remain source-reviewed rather than independently emulated. Capture from that revision: `plotter-w05-o07-rhythm.png` (local capture). The former `plotter-w05-o07-physics.png` and `plotter-w05-o07-animations.png` record superseded motion. +- **Checks:** scoped Biome and full UI TypeScript checks pass. Existing W05/O07 static functions were only exported for preview fallbacks; their geometry is unchanged. + +**Current W05 cadence:** the fast 0.70s delivery is retained, now at 1.00–1.70s within a 1.8s cycle. Visible feed starts at 1.171471s, overlapping the last 0.418799s of indexing. The next drop starts at 1.84s, giving a 0.14s handoff. Drop, rebound, and belt trajectories are unchanged in absolute time. The author rechecked 9,001 serialized phases for rigid size, contact ownership, and penetration, and compared pixel-identical loop endpoints. A separate read-only review found no source-level handoff or loop defect. Parent added fixed reference guides at y=124/180, x=80…248: 4 units clear of the incoming core's y=128…176 bounds and 8 units short of its x=256 drop edge. They do not cross moving parts or cover lines. Biome and diff checks pass; browser review confirms the 1.8s timelines and revised guides in moving phases. Full TypeScript was not rerun for these timing constants and SVG path changes. Capture: `plotter-w05-overlapping-feed.png`. The prior `plotter-w05-faster-feed.png` records the superseded sequential feed. + +**Superseded O07 timing calibration:** the same nonlinear quarter-flight integral K=1.591254382 now produces the duration as `4K√(0.30/9.80665) = 1.1132684299524582s`, rather than normalizing to a hardcoded 1.8s. The chosen 0.30 m length calibrates the motion; it is not a measurement of the drawn object. The earlier timing implied approximately 0.78427 m under the same model. Contacts occur at 0.2783171075/0.8349513225s, with matching incoming/outgoing speed magnitude 147.3806178°/s. Two source reviews confirm 385 serialized values/keyTimes, 384 valid spline segments, strictly increasing times, zero-speed turnarounds, and matching loop endpoints. The author rechecked 36,001 poses and agreement with the independent energy relation within 0.000006°/s. Geometry, bounds, annotations, and ideal collision assumptions are unchanged. Browser review confirms both 1.113268s timelines and moving phases; capture `plotter-o07-calibrated-swing.png`. Scoped Biome and full UI TypeScript checks pass. The governing nonlinear pendulum equation is described in [OpenStax, Pendulums](https://openstax.org/books/university-physics-volume-1/pages/15-4-pendulums); the 30 cm calibration is a house choice, not a value from that source. + +**O07 video reference:** the user then supplied [this video](https://www.youtube.com/watch?v=09vzFodz4SI). Parent inspected opening playback and paused/stepped frames around 15–17.64s in the browser; this was not a full-hour viewing. The observed visual rhythm suggests roughly a 2.4s cycle and 30° release, superseding the arbitrary 30 cm calibration. These are approximate visual estimates, not motion-capture data. O07 uses those values with the same nonlinear pendulum flight and instantaneous contact model. Full swept disk bounds are x106…502, y250.420…318; the outer guide fragments retain at least 6/8 units of horizontal clearance. Browser confirms active 2.4s timelines and a 30° starting release. Read-only review confirms valid serialized splines, zero-speed turnarounds, matched contact speeds of 78.99743219°/s at 0.6/1.8s, and exact loop endpoints. Across 36,001 poses, no disk overlap or string-length drift above 5.7×10⁻¹⁴ was found. Biome passes. Capture: `plotter-o07-video-rhythm.png`. The previous `plotter-o07-calibrated-swing.png` records the superseded faster trial. + +**Current O07 speed adjustment:** the user requested a slight speed-up with unchanged easing. The duration shortened from 2.4s to 2.2s, then to 2.0s after a further speed request (10% faster than the 2.2s trial). Browser comparison confirms both animations retain byte-identical `values`, `keyTimes`, and `keySplines`; the 30° geometry, normalized motion, contact ordering, and loop are unchanged. Contact times scale to 0.5/1.5s. Biome passes. Capture: `plotter-o07-two-second-cycle.png`. + +Checked source hashes under `apps/ui/app/plotter/`: + +| File | SHA-256 prefix | +| --- | --- | +| `production-chain-motion-drawing.tsx` | `62af5a7164d8` | +| `newtons-cradle-motion-drawing.tsx` | `329191db4253` | +| `engineering-motion-preview.tsx` | `7e194f9de588` | +| `engineering-motion-preview.module.css` | `47531e974a1a` | + +## Reproduction notes + +Recompute from current source after edits. Preserve exact relationships and report residuals against house serialization thresholds (`0.05` SVG unit, `0.1°`), never as manufacturing tolerances. File hashes recorded below identify this source snapshot; unrelated working-tree edits may continue. + +SHA-256 prefixes, all under `apps/ui/app/plotter/`: + +| File | SHA-256 prefix | +| --- | --- | +| `primitives-blueprint.tsx` | `be989e00c285` | +| `service-studies.tsx` | `fb16277ad79c` | +| `system-studies.tsx` | `04de74c464c9` | +| `system-state-studies.tsx` | `0ba231cd9b10` | +| `disconnected-plug.tsx` | `7bd9b1e03e5d` | +| `engineering-studies.tsx` | `343bd2dc51c5` | +| `missing-destination.tsx` | `e3ba6a8f8bed` | +| `state-studies.tsx` | `c7190028b3fc` | +| `not-found-motion.tsx` | `01d467710cbd` | +| `not-found-motion.module.css` | `47531e974a1a` | + +Local review captures are not bundled with this skill. Regenerate the named views from Plotter when reviewing another checkout; source hashes identify the inspected revisions. diff --git a/apps/ui/skills/plotter/references/verification-prompt.md b/apps/ui/skills/plotter/references/verification-prompt.md new file mode 100644 index 0000000000..0366ab2601 --- /dev/null +++ b/apps/ui/skills/plotter/references/verification-prompt.md @@ -0,0 +1,112 @@ +# Plotter verification prompt + +Copy the prompt below when a technical verification or a full review is requested. Ordinary visual refinements need only concise checks of changed claims and their dependencies; they do not require the full report each time. Supply the actual SVG/TSX source and a rendered image; a screenshot alone cannot establish exact geometry. The reviewer reports findings before editing unless corrections are already authorized. + +```text +Review this Plotter illustration as an independent technical and visual reviewer. + +Inputs +- Study ID and intended subject: +- Current SVG/TSX source, dependencies, and revision: +- Rendered image at intended size; compact/thumbnail and theme variants where used: +- Intended mode: geometric construction / physical technical view / functional schematic / conceptual diagram: +- Nearest approved references and which relationship each supplies: +- Claimed units, scale, equations, behavior, and external sources: + +Read apps/ui/skills/plotter/SKILL.md and its references/drafting-standard.md, +approved-references.md, and current direction.md. Treat appearance approval +as art-direction evidence, not as proof of mathematical or product accuracy. + +1. Establish the claim boundary. + State the one relationship this drawing communicates. List every visible + numeric label, radius/diameter/angle claim, repeated spacing, connection, + hatch region, technical symbol, and product-behavior claim. Classify each + as geometry, technical convention, functional behavior, or analogy. + Identify what the drawing intentionally does not claim. + Done when every meaningful mark has a role and every claim has a row. + +2. Recover the actual construction. + Read source parameters, transforms, clipping, inherited styles, and path + commands. Resolve all coordinates into one frame. Record centers, radii, + endpoints, relevant bounds, and the value of u. Derive label values from + those coordinates independently. Source comments are claims to test. + Done when each claim can be traced to exact source locations and data. + +3. Calculate geometry; do not judge it by resemblance. + - Distance: d = hypot(x2-x1, y2-y1); verify which witnesses are measured. + - Circle incidence: residual = abs(hypot(P-C)-r). + - Point on a segment: cross-product residual plus parameter 0 <= t <= 1. + - Tangency: radius perpendicular to tangent, and point on both objects. + - Circle tangency: center distance equals r1+r2 (external) or |r1-r2| + (internal), with the intended contact point checked. + - Angles: atan2 or normalized dot product; check the actual rays, center + and endpoints of any angle arc, SVG arc flags, and sweep direction. + - Circular fillets: correct radius, center, endpoint incidence, tangent + joins, and continuity; do not treat an arbitrary Bézier as circular. + - Repetition: list successive differences and compare with declared u. + - Hatches: angle, perpendicular pitch, clipping region, exclusions, and + boundary intersections; check duplicate coincident strokes. + - Layout labels: compute rendered text bounds where available; inspect + clearance, collisions, clipping, and missing essential labels. + Use exact parameter relationships and the house serialization thresholds + (0.05 SVG unit and 0.1°). Report expected, actual, and residual values. + A physical fit claim needs its own engineering tolerances. + Done when all applicable geometric claims have calculations, or an + explicit reason they could not be verified. + +4. Verify topology and behavior separately. + For a schematic, list nodes, ports, and directed/undirected edges. Check + every intended junction and crossing. Verify terminal count, states, + direction, and continuity. Trace at least one complete claimed path. + For a mechanism, distinguish static placement from kinematics. Shared + module and tangent pitch circles do not prove a valid gear mesh; actual + tooth engagement requires profile, phase, and motion/contact evidence. + For service diagrams, verify each claim against current first-party docs. + State which dimensions represent only the illustration’s construction. + Done when claimed behavior is supported or marked needs qualification. + +5. Verify symbols and drafting roles. + Inventory stroke widths, opacities, dash patterns, arrows, and hatch roles. + Compare them with drafting-standard.md. Identify a precise source entry + for any claimed standardized symbol. Separate sourced facts from house + conventions. Check transparent geometry, one owner per shared segment, + and solid arrow caps meeting dashed shafts. Never assert ISO/IEC + compliance from a catalogue abstract or from visual similarity. + Done when each role is consistent or has a specific reported exception. + +6. Inspect the render against approved anchors. + Check the actual hero size and thumbnail; inspect compact/mobile use and + both themes when applicable. Assess flatness, one focal relationship, + subject recognition, quiet space, line hierarchy, and readable labels. + Name what was actually viewed. If rendering is unavailable, mark visual + verification unperformed; source checks cannot substitute for it. + Done when each used presentation has evidence or an explicit limitation. + +Output +- Scope: study, revision/file hash, inspected modes/sizes, primary sources. +- Verdicts: geometry / behavior / conventions / visual fit, each PASS, + FAIL, or NEEDS QUALIFICATION. “Not applicable” is allowed with a reason. +- Evidence table: + Claim | Class | Source location | Expected | Actual/calculation | + Residual/tolerance | Evidence URL or render | Verdict +- Findings, highest impact first: observable defect, why it matters, + smallest adequate correction, and the exact check that would close it. +- Unverified items and limitations; do not silently promote them to passes. +- Overall result: FAIL if a required claim is contradicted; NEEDS + QUALIFICATION if no contradiction is found but a required claim lacks + evidence; PASS only for the explicitly inspected scope. + +Use concise explanations. Produce reproducible evidence rather than +assurances. Do not call the drawing unquestionable, physically correct, +manufacturable, or standards-compliant without the corresponding proof. +``` + +## Minimal report example + +| Claim | Calculation | Verdict | +| --- | --- | --- | +| P00 square is `6u`, with `u = 40` | `404 − 164 = 240 = 6 × 40` | PASS | +| P00 original radial endpoint lies on `R120` | `hypot(369 − 284, 119 − 204) = 120.20815`; residual `0.20815 > 0.05` | FAIL | +| A simplified gear pair is a functional mechanism | Shared pitch and center distance checked; tooth contact over rotation not established | NEEDS QUALIFICATION | + +This example describes the original inspected P00 endpoint. Recalculate from the current source after correction. A report is evidence for a revision, not a permanent badge attached to a study ID. From aeebda6ba387149db564dc774c1909eef0e06c9e Mon Sep 17 00:00:00 2001 From: pasqualevitiello Date: Sun, 25 Oct 2026 15:52:57 +0100 Subject: [PATCH 09/15] refactor(ui): refine cradle sway and turnaround timing --- .../plotter/newtons-cradle-motion-drawing.tsx | 90 ++++++++++++++----- .../ui/skills/plotter/references/direction.md | 4 + .../skills/plotter/references/source-audit.md | 8 +- 3 files changed, 77 insertions(+), 25 deletions(-) diff --git a/apps/ui/app/plotter/newtons-cradle-motion-drawing.tsx b/apps/ui/app/plotter/newtons-cradle-motion-drawing.tsx index 4a89aeb79e..54fbd5ab7a 100644 --- a/apps/ui/app/plotter/newtons-cradle-motion-drawing.tsx +++ b/apps/ui/app/plotter/newtons-cradle-motion-drawing.tsx @@ -7,7 +7,9 @@ const cradle = { radius: 22, pivotRadius: 2.4, amplitude: 30, + interiorAmplitude: 0.75, duration: 2, + turnaroundEmphasis: 0.25, } as const; const restY = cradle.pivotY + cradle.length; @@ -15,10 +17,13 @@ const restY = cradle.pivotY + cradle.length; // θ = 2 asin(k cos φ), k = sin(θmax/2), gives dt/dφ below. Simpson sampling // supplies one descent; reflection supplies the remaining three quarters. // The user’s video reference suggests a roughly 2.4 s cycle and 30° release. -// The requested speed refinements use 2 s with the same pendulum curve. +// A smooth quarter-flight time remap gives the turn more breathing room while +// retaining the requested 2 s cycle. This is an illustration timing accent, +// not an unmodified gravity trajectory. Velocities include its chain rule. // Contacts at ¼ and ¾ of each cycle transfer velocity without dwell. // The complete bob sweep remains inside x = 106…502, clear of the witnesses. -// This idealization omits collision compliance, damping, and interior motion. +// A small phase-locked sway gives the touching group a shared response. This +// remains a motion study, not a simulation of collision compliance or damping. function swingSamples() { const count = 96; const step = Math.PI / (2 * count); @@ -43,13 +48,21 @@ function swingSamples() { const reverse = quarter % 2 === 1; return (reverse ? [...descent].reverse() : descent) .slice(quarter ? 1 : 0) - .map(({ time, angle, speed }) => ({ - time: (quarter + (reverse ? 1 - time / elapsed : time / elapsed)) / 4, - angle: (quarter === 1 || quarter === 2 ? -1 : 1) * angle, - // Angular velocity per normalized cycle, from the same energy integral. - velocity: - ((quarter < 2 ? -1 : 1) * 1440 * elapsed * k * speed) / Math.PI, - })); + .map(({ time, angle, speed }) => { + const progress = time / elapsed; + const flightTime = + progress + + (cradle.turnaroundEmphasis * Math.sin(Math.PI * progress)) / Math.PI; + const timeRate = + 1 + cradle.turnaroundEmphasis * Math.cos(Math.PI * progress); + return { + time: (quarter + (reverse ? 1 - flightTime : flightTime)) / 4, + angle: (quarter === 1 || quarter === 2 ? -1 : 1) * angle, + velocity: + ((quarter < 2 ? -1 : 1) * 1440 * elapsed * k * speed) / + (Math.PI * timeRate), + }; + }); }); return { samples, @@ -60,11 +73,18 @@ function swingSamples() { const { samples, duration } = swingSamples(); const keyTimes = samples.map(({ time }) => time).join(";"); -function swingMotion(side: "left" | "right", pivotX: number) { +type PendulumRole = "left" | "middle" | "right"; + +function swingScale(side: PendulumRole, angle: number) { + const active = + (side === "left" && angle > 0) || (side === "right" && angle < 0); + return active ? 1 : cradle.interiorAmplitude / cradle.amplitude; +} + +function swingMotion(side: PendulumRole, pivotX: number) { const frames = samples.map((sample) => ({ ...sample, - angle: - side === "left" ? Math.max(0, sample.angle) : Math.min(0, sample.angle), + angle: sample.angle * swingScale(side, sample.angle), })); return { values: frames @@ -80,8 +100,11 @@ function swingMotion(side: "left" | "right", pivotX: number) { const angle = next.angle - current.angle; if (!angle) return ["0 0 1 1"]; const scale = (next.time - current.time) / (3 * angle); + // Pick the segment's side of contact: the end bob exchanges the main + // and shared velocities there, while the middle three stay continuous. + const response = swingScale(side, current.angle + next.angle); return [ - `0.333333333 ${(current.velocity * scale).toFixed(9)} 0.666666667 ${(1 - next.velocity * scale).toFixed(9)}`, + `0.333333333 ${(current.velocity * response * scale).toFixed(9)} 0.666666667 ${(1 - next.velocity * response * scale).toFixed(9)}`, ]; }) .join(";"), @@ -95,16 +118,20 @@ function Pendulum({ x }: { x: number }) { d={`M${x} ${cradle.pivotY + cradle.pivotRadius}V${restY - cradle.radius}`} /> + {x === 304 && ( + + )} ); } -function SwingingPendulum({ side, x }: { side: "left" | "right"; x: number }) { +function SwingingPendulum({ side, x }: { side: PendulumRole; x: number }) { const motion = swingMotion(side, x); return ( radiusLeader(angle * swingScale("middle", angle))) + .join(";"); export function NewtonsCradleMotionDrawing() { return ( @@ -149,7 +187,7 @@ export function NewtonsCradleMotionDrawing() { {cradle.pivots.slice(1, -1).map((x) => ( - + ))} @@ -158,11 +196,17 @@ export function NewtonsCradleMotionDrawing() { {/* Both length witnesses lie beyond the complete swept bob envelope. */} - {/* Radius leader belongs to the stationary middle bob, below the sweep. */} - - + + + diff --git a/apps/ui/skills/plotter/references/direction.md b/apps/ui/skills/plotter/references/direction.md index 5bc8bd1fd4..7ca30cb820 100644 --- a/apps/ui/skills/plotter/references/direction.md +++ b/apps/ui/skills/plotter/references/direction.md @@ -187,6 +187,10 @@ For W05, the user subsequently requested faster box delivery before the drop, th The user separately said O07’s easing still did not look realistic. An initial 0.30 m timing calibration shortened the cycle to 1.113268s, but the user then supplied [this cradle video](https://www.youtube.com/watch?v=09vzFodz4SI). Inspection of the opening swings and stepped frames around 15–17.64s showed a slower, broader motion with crisp contact. The 30 cm assumption was superseded: that trial used an approximately reference-matched 2.4s cycle and 30° release, retaining the nonlinear pendulum curve and instantaneous transfer. These are visual estimates from the video, not measured physical specifications. The reference supplies motion direction while the established flat drawing style remains. The user then asked to speed it up just a little without changing the easing: the cycle first shortened to 2.2s, then to 2.0s after a further speed request, with identical normalized key times, spline controls, and 30° swing geometry. +The next O07 request adds subtle movement to the three middle balls, like the video. The response is a shared ±0.75° sway (about 2.3 SVG units horizontally), following the same signed pendulum phase. The resting end follows that group to preserve tangency; the active end retains its existing swing, easing, and 2.0s timing. Keep the middle balls synchronized rather than introducing a delayed wave, and keep the radius leader and center mark attached to the moving center ball. This bounded motion approximation awaits user review; it is not a measured reconstruction of the video or a contact simulation. + +The user then requested stronger slowdown near the outer ball’s highest point, with immediate departure from impact and a fast return. The next revision preserves the 2.0s cadence and both swing amplitudes but smoothly redistributes time within each quarter-flight: more time near the turn, less near contact. It adds no stationary hold. The timing accent intentionally departs from the unmodified pendulum curve; keep that distinction explicit in technical claims. The shared middle sway follows the revised phase so the contact geometry remains coherent. + The user requested animations for W05 (assembly line) and O07 (Newton’s cradle), then said both “feel slow and lack physics” and do not feel natural. They remain separate examples in the Animations section, with the static gallery drawings retained. The revised direction is a brisk assembly cycle with an accelerating drop, restrained contact response, and conveyor acceleration/deceleration; the cradle should accelerate into contact, transfer motion immediately, and slow naturally at its turning points. The user liked the faster revisions but said they were still not fully natural and W05 remained a bit slow. The next pass specifically reduced idle handoffs, restrained the insertion rebound, smoothed motor acceleration, and tightened the cradle’s arc and turnarounds. Shortening a loop alone is insufficient if every movement still uses generic easing. Existing direction still applies: transparent forms, stable construction guides, rigid moving parts, and careful shared-edge ownership. These are requested experiments, not approved motion references or full physical simulations. ## Native-size gallery diff --git a/apps/ui/skills/plotter/references/source-audit.md b/apps/ui/skills/plotter/references/source-audit.md index 3b3b4b0362..25aa6754ee 100644 --- a/apps/ui/skills/plotter/references/source-audit.md +++ b/apps/ui/skills/plotter/references/source-audit.md @@ -130,14 +130,18 @@ These separate showcase animations retain the original static gallery drawings. **O07 video reference:** the user then supplied [this video](https://www.youtube.com/watch?v=09vzFodz4SI). Parent inspected opening playback and paused/stepped frames around 15–17.64s in the browser; this was not a full-hour viewing. The observed visual rhythm suggests roughly a 2.4s cycle and 30° release, superseding the arbitrary 30 cm calibration. These are approximate visual estimates, not motion-capture data. O07 uses those values with the same nonlinear pendulum flight and instantaneous contact model. Full swept disk bounds are x106…502, y250.420…318; the outer guide fragments retain at least 6/8 units of horizontal clearance. Browser confirms active 2.4s timelines and a 30° starting release. Read-only review confirms valid serialized splines, zero-speed turnarounds, matched contact speeds of 78.99743219°/s at 0.6/1.8s, and exact loop endpoints. Across 36,001 poses, no disk overlap or string-length drift above 5.7×10⁻¹⁴ was found. Biome passes. Capture: `plotter-o07-video-rhythm.png`. The previous `plotter-o07-calibrated-swing.png` records the superseded faster trial. -**Current O07 speed adjustment:** the user requested a slight speed-up with unchanged easing. The duration shortened from 2.4s to 2.2s, then to 2.0s after a further speed request (10% faster than the 2.2s trial). Browser comparison confirms both animations retain byte-identical `values`, `keyTimes`, and `keySplines`; the 30° geometry, normalized motion, contact ordering, and loop are unchanged. Contact times scale to 0.5/1.5s. Biome passes. Capture: `plotter-o07-two-second-cycle.png`. +**O07 speed adjustment, before interior movement:** the user requested a slight speed-up with unchanged easing. The duration shortened from 2.4s to 2.2s, then to 2.0s after a further speed request (10% faster than the 2.2s trial). Browser comparison confirmed both animations retained byte-identical `values`, `keyTimes`, and `keySplines`; the 30° geometry, normalized motion, contact ordering, and loop were unchanged. Contact times scale to 0.5/1.5s. Biome passed. Capture: `plotter-o07-two-second-cycle.png`. + +**O07 interior movement, before turnaround emphasis:** the user next requested subtle movement of the three middle balls, as in the reference. A shared secondary angle `β = 0.025θ` gives them a ±0.75° sway; the resting outer follows the same angle. Each bob still rotates about its own fixed pivot. Read-only review sampled 60,001 signed outer angles: the touching four-ball group keeps 44-unit center spacing within 2.9×10⁻¹⁴, and the active outer never overlaps it. Maximum interior drift is 2.303769 units horizontally and 0.015078 vertically; complete bounds and guide clearances remain unchanged. The central mark and radius-leader tip track the middle bob while the label stays fixed. Browser comparison confirms the active outer angles and spline strings remain byte-identical to the preceding version; all five pendulums share its 385 key times and 2s duration. Per-segment endpoint velocity scaling preserves the intended end-ball handoff without a delayed interior wave. This is a phase-locked motion approximation, not a solved five-body contact model. Scoped Biome and full UI TypeScript checks pass; the moving preview and Replay were inspected. Capture: `plotter-o07-middle-sway.png`. + +**Current O07 turnaround emphasis:** the next request asks for more slowdown at the highest point and a faster departure/return at impact. Quarter-flight time is remapped with `F(q) = q + 0.25 sin(πq)/π`; its derivative stays in `[0.75, 1.25]`, with velocities adjusted by the chain rule. The 2s loop, 30° outer swing, and ±0.75° shared sway remain. Read-only review confirms 385 values, 384 valid monotone spline segments, strictly increasing key times, no overshoot or hold, and unchanged contacts at 0.5/1.5s. Main/contact speed is 126.3958915°/s, one-third above the previous curve; the shared speed is 3.1598974°/s. Turnaround speed remains zero, and the loop has no position/velocity seam. This deliberately accentuates the illustration’s timing rather than preserving the unmodified gravity trajectory. Browser comparison confirms unchanged angle values and updated timing for all five pendulums; the moving preview and Replay were inspected. Biome and full UI TypeScript checks pass. Capture: `plotter-o07-turnaround-emphasis.png`. Checked source hashes under `apps/ui/app/plotter/`: | File | SHA-256 prefix | | --- | --- | | `production-chain-motion-drawing.tsx` | `62af5a7164d8` | -| `newtons-cradle-motion-drawing.tsx` | `329191db4253` | +| `newtons-cradle-motion-drawing.tsx` | `3930716d38c6` | | `engineering-motion-preview.tsx` | `7e194f9de588` | | `engineering-motion-preview.module.css` | `47531e974a1a` | From f9482a320d2afaf502b2646ac3addf8c3cf75abb Mon Sep 17 00:00:00 2001 From: pasqualevitiello Date: Sat, 31 Oct 2026 09:53:46 +0100 Subject: [PATCH 10/15] feat(ui): package Plotter skill and add evaluated illustrations --- apps/ui/app/plotter/README.md | 14 ++- apps/ui/app/plotter/appointment-buffers.tsx | 97 +++++++++++++++++++ apps/ui/app/plotter/content-state-studies.tsx | 10 ++ apps/ui/app/plotter/flat-cal-studies.tsx | 10 ++ apps/ui/app/plotter/no-matching-results.tsx | 90 +++++++++++++++++ apps/ui/app/plotter/shared-frame.tsx | 58 +++++++++++ apps/ui/app/plotter/system-studies.tsx | 10 ++ apps/ui/package.json | 1 + apps/ui/plotter-evals/2026-10-31/README.md | 19 ++++ apps/ui/plotter-evals/2026-10-31/buffers.svg | 58 +++++++++++ apps/ui/plotter-evals/2026-10-31/cases.json | 61 ++++++++++++ .../2026-10-31/clean-install/agent-notes.json | 92 ++++++++++++++++++ .../clean-install/inscribed-circle.svg | 36 +++++++ .../clean-install/package-hashes.json | 25 +++++ .../2026-10-31/clean-install/result.json | 18 ++++ apps/ui/plotter-evals/2026-10-31/modules.svg | 43 ++++++++ apps/ui/plotter-evals/2026-10-31/search.svg | 55 +++++++++++ apps/ui/scripts/check-plotter.tsx | 76 +++++++++++++++ apps/ui/skills/plotter/README.md | 20 +++- apps/ui/skills/plotter/SKILL.md | 47 ++++----- .../plotter/references/approved-references.md | 23 ++++- .../ui/skills/plotter/references/direction.md | 16 ++- .../plotter/references/drawings/B04.svg | 1 + .../plotter/references/drawings/C02.svg | 1 + .../plotter/references/drawings/E02.svg | 1 + .../plotter/references/drawings/I06.svg | 1 + .../plotter/references/drawings/K07.svg | 1 + .../plotter/references/drawings/P00.svg | 1 + .../plotter/references/drawings/manifest.json | 55 +++++++++++ .../plotter/references/implementation.md | 8 +- .../plotter/references/verification-prompt.md | 8 +- 31 files changed, 917 insertions(+), 39 deletions(-) create mode 100644 apps/ui/app/plotter/appointment-buffers.tsx create mode 100644 apps/ui/app/plotter/no-matching-results.tsx create mode 100644 apps/ui/app/plotter/shared-frame.tsx create mode 100644 apps/ui/plotter-evals/2026-10-31/README.md create mode 100644 apps/ui/plotter-evals/2026-10-31/buffers.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/cases.json create mode 100644 apps/ui/plotter-evals/2026-10-31/clean-install/agent-notes.json create mode 100644 apps/ui/plotter-evals/2026-10-31/clean-install/inscribed-circle.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/clean-install/package-hashes.json create mode 100644 apps/ui/plotter-evals/2026-10-31/clean-install/result.json create mode 100644 apps/ui/plotter-evals/2026-10-31/modules.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/search.svg create mode 100644 apps/ui/scripts/check-plotter.tsx create mode 100644 apps/ui/skills/plotter/references/drawings/B04.svg create mode 100644 apps/ui/skills/plotter/references/drawings/C02.svg create mode 100644 apps/ui/skills/plotter/references/drawings/E02.svg create mode 100644 apps/ui/skills/plotter/references/drawings/I06.svg create mode 100644 apps/ui/skills/plotter/references/drawings/K07.svg create mode 100644 apps/ui/skills/plotter/references/drawings/P00.svg create mode 100644 apps/ui/skills/plotter/references/drawings/manifest.json diff --git a/apps/ui/app/plotter/README.md b/apps/ui/app/plotter/README.md index eeb665d913..36798becb1 100644 --- a/apps/ui/app/plotter/README.md +++ b/apps/ui/app/plotter/README.md @@ -2,7 +2,19 @@ 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. +The gallery presents flat monochrome illustrations, organized by subject with category filters and native-size inline drawings. Stable illustration IDs preserve links and feedback history; `?variant=` selects the category and scrolls to the drawing. 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. + +## Fresh-session examples + +The user selected the original versions without added background grids from the 2026-10-31 evaluation: + +| Code | Illustration | Study ID | +| --- | --- | --- | +| C04 | Shared frame | `particles-shared-frame` | +| K09 | Appointment buffers | `cal-appointment-buffers` | +| S15 | No matching results | `state-no-matching-results` | + +The original SVGs, prompts, and feedback are preserved in [`../../plotter-evals/2026-10-31`](../../plotter-evals/2026-10-31). These catalog additions retain their own review history; they do not change the skill’s earlier approved reference set. ## Production selections diff --git a/apps/ui/app/plotter/appointment-buffers.tsx b/apps/ui/app/plotter/appointment-buffers.tsx new file mode 100644 index 0000000000..9b8d3d8875 --- /dev/null +++ b/apps/ui/app/plotter/appointment-buffers.tsx @@ -0,0 +1,97 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; + +export function AppointmentBuffersStudy() { + return ( + + + + + + + + + + + + 10 min + + + 30 min + + + 10 min + + + + + + + + + + + + + + Buffer before + + + Appointment + + + Buffer after + + + + + + + + 09:50 + + + 10:00 + + + 10:30 + + + 10:40 + + + + + + + + 50 min reserved + + + ); +} diff --git a/apps/ui/app/plotter/content-state-studies.tsx b/apps/ui/app/plotter/content-state-studies.tsx index e90acb3075..bb24c913fa 100644 --- a/apps/ui/app/plotter/content-state-studies.tsx +++ b/apps/ui/app/plotter/content-state-studies.tsx @@ -1,5 +1,6 @@ import { BlueprintDrawing, CompactBlueprintDrawing } from "./blueprint-drawing"; import { HorizontalDimension, VerticalDimension } from "./dimensions"; +import { NoMatchingResultsStudy } from "./no-matching-results"; import type { IllustrationStudy } from "./study-types"; const secondaryLine = { @@ -464,6 +465,15 @@ function ImageCompact() { } export const contentStateStudies: IllustrationStudy[] = [ + { + id: "state-no-matching-results", + code: "S15", + title: "No matching results", + description: + "A lookup for key 03 finds no entry among 01, 02, and 04. A missing position interrupts the repeated index.", + category: "Empty states", + illustration: , + }, { id: "state-search", code: "S03", diff --git a/apps/ui/app/plotter/flat-cal-studies.tsx b/apps/ui/app/plotter/flat-cal-studies.tsx index c45bf4f85d..a03828f1b6 100644 --- a/apps/ui/app/plotter/flat-cal-studies.tsx +++ b/apps/ui/app/plotter/flat-cal-studies.tsx @@ -1,3 +1,4 @@ +import { AppointmentBuffersStudy } from "./appointment-buffers"; import { BlueprintDrawing } from "./blueprint-drawing"; import type { IllustrationStudy } from "./study-types"; @@ -254,4 +255,13 @@ export const flatCalStudies: IllustrationStudy[] = [ category: "Scheduling", illustration: , }, + { + id: "cal-appointment-buffers", + code: "K09", + title: "Appointment buffers", + description: + "A 30-minute appointment sits between two 10-minute buffers. The proportional intervals reserve 50 minutes in total.", + category: "Scheduling", + illustration: , + }, ]; diff --git a/apps/ui/app/plotter/no-matching-results.tsx b/apps/ui/app/plotter/no-matching-results.tsx new file mode 100644 index 0000000000..d46fc34cd3 --- /dev/null +++ b/apps/ui/app/plotter/no-matching-results.tsx @@ -0,0 +1,90 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; + +export function NoMatchingResultsStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index + + + Query + + + 03 + + + 01 + + + 02 + + + 04 + + + 0 matches + + + + u + + + ); +} diff --git a/apps/ui/app/plotter/shared-frame.tsx b/apps/ui/app/plotter/shared-frame.tsx new file mode 100644 index 0000000000..4e1ff8c4a7 --- /dev/null +++ b/apps/ui/app/plotter/shared-frame.tsx @@ -0,0 +1,58 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; + +export function SharedFrameStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + Shared frame + + + A + + + B + + + + + 14u + + + u + + + 4u + + + u + + + 7u + + + u + + + + + ); +} diff --git a/apps/ui/app/plotter/system-studies.tsx b/apps/ui/app/plotter/system-studies.tsx index 7190f7cc7b..5954956281 100644 --- a/apps/ui/app/plotter/system-studies.tsx +++ b/apps/ui/app/plotter/system-studies.tsx @@ -1,5 +1,6 @@ import { BlueprintDrawing } from "./blueprint-drawing"; import { ParticlesBlueprint } from "./particles-blueprint"; +import { SharedFrameStudy } from "./shared-frame"; import type { IllustrationStudy } from "./study-types"; function Registration() { @@ -434,4 +435,13 @@ export const systemStudies: IllustrationStudy[] = [ category: "Interfaces", illustration: , }, + { + id: "particles-shared-frame", + code: "C04", + title: "Shared frame", + description: + "Two unequal modules fit one frame. A shared unit defines the edge clearances and the space between them.", + category: "Interfaces", + illustration: , + }, ]; diff --git a/apps/ui/package.json b/apps/ui/package.json index c5163683f5..4640850a25 100644 --- a/apps/ui/package.json +++ b/apps/ui/package.json @@ -49,6 +49,7 @@ "format": "biome format --write .", "format:all": "biome check --write --unsafe --no-errors-on-unmatched", "lint": "biome lint .", + "plotter:check": "bun scripts/check-plotter.tsx", "postinstall": "fumadocs-mdx", "registry:build": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/build-registry.mts", "registry:validate-deps": "tsx --tsconfig ./tsconfig.scripts.json ./scripts/validate-registry-deps.mts", diff --git a/apps/ui/plotter-evals/2026-10-31/README.md b/apps/ui/plotter-evals/2026-10-31/README.md new file mode 100644 index 0000000000..ab0c742320 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/README.md @@ -0,0 +1,19 @@ +# Plotter evaluation · 2026-10-31 + +`cases.json` preserves the three original briefs, SVG hashes, catalog IDs, and user feedback. `modules.svg`, `buffers.svg`, and `search.svg` are the original self-contained outputs, including their embedded font. The user liked all three and preferred them without the additional background grids tried afterward. + +Their catalog equivalents are C04, K09, and S15. Those components inherit the website’s font and theme colors; the shared-frame hatch is analytically clipped instead of using an SVG clip ID. The original geometry, measurements, and labels are retained. + +These were three separate fresh sessions using the skill at `aeebda6b`. There was no without-skill control or repeat trial. The results demonstrate those particular outcomes and preferences, not a measured reliability rate or proof that the skill improved performance. + +## Repeating an exercise + +Copy the complete skill directory into a temporary location. Start a fresh agent session with only that copy, the brief, and an empty output directory. Preserve the resulting SVG, the exact prompt, the package’s file hashes, and the review outcome. Keep generated candidates outside the catalog until selected. + +Review recognition, construction relationship, hierarchy, geometry, and readability at the intended size separately. A metadata or rendering check cannot decide visual quality. When an instruction changes, repeat the affected brief and an unrelated brief before drawing conclusions. + +Run `bun run plotter:check` from `apps/ui` for package links, bundled reference hashes, catalog identifiers, and SVG rendering. The skill README describes the current experimental scope. + +## Clean-install result + +The consolidated package completed one fresh-session inscribed-circle exercise. `clean-install/` preserves the brief, exact package hashes, output, agent notes, and independent geometry residuals. No checkout or conversation context was supplied. This passed a bounded portability check; the new drawing has not been selected for the catalog or approved as a style reference. diff --git a/apps/ui/plotter-evals/2026-10-31/buffers.svg b/apps/ui/plotter-evals/2026-10-31/buffers.svg new file mode 100644 index 0000000000..bb2cb2e23e --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/buffers.svg @@ -0,0 +1,58 @@ + + A 30-minute appointment with 10-minute buffers + A proportional time interval: a 10-minute buffer before, a 30-minute appointment, and a 10-minute buffer after. The appointment runs from 10:00 to 10:30 in this example; the full reserved interval runs from 09:50 to 10:40 and measures 50 minutes. Diagonal hatching identifies the two buffer intervals. + Mode: conceptual diagram. Scale: 7.2 SVG units per minute. Horizontal widths 72, 216, 72 represent durations 10, 30, 10 minutes. H1 hatching means buffer time, not material. Example times only; no product behavior is asserted. References: Plotter K07 interval composition and P00 line hierarchy. All forms are transparent. + + + + + + + + + + + + + + + + + 10 min + 30 min + 10 min + + + + + + + + + + + + + + Buffer before + Appointment + Buffer after + + + + + + + 09:50 + 10:00 + 10:30 + 10:40 + + + + + + + + 50 min reserved + diff --git a/apps/ui/plotter-evals/2026-10-31/cases.json b/apps/ui/plotter-evals/2026-10-31/cases.json new file mode 100644 index 0000000000..7b62148936 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/cases.json @@ -0,0 +1,61 @@ +{ + "date": "2026-10-31", + "skill_commit": "aeebda6ba387149db564dc774c1909eef0e06c9e", + "purpose": "Preserved first-pass outputs and prompts. Catalog implementations remove embedded fonts and global styles while retaining the drawings.", + "cases": [ + { + "id": "modules", + "request": "Create a standalone static blueprint illustration showing two differently sized rounded modules fitting into one shared frame, with a consistent gap. Intended display is 560 × 440.", + "output": "modules.svg", + "sha256": "ada7356d43edbc252ec895ea330451780a0a68fe55b0da46a7893d27472733f4", + "catalog_code": "C04", + "catalog_id": "particles-shared-frame", + "condition": "Fresh agent session with Plotter; no without-skill control", + "user_verdict": "Liked; original version preferred over added background-grid variant" + }, + { + "id": "buffers", + "request": "Create a standalone static blueprint illustration explaining a 30-minute appointment with a 10-minute buffer before and after. Intended display is 560 × 440.", + "output": "buffers.svg", + "sha256": "0ab4cc7f653d7ff68dc596f52d37bab8c432233ecd45e88de5084a3fef334733", + "catalog_code": "K09", + "catalog_id": "cal-appointment-buffers", + "condition": "Fresh agent session with Plotter; no without-skill control", + "user_verdict": "Liked; original version preferred over added background-grid variant" + }, + { + "id": "search", + "request": "Create a standalone static blueprint illustration for No matching results: a query has no matching entry in a small indexed collection. Intended display is 560 × 440.", + "output": "search.svg", + "sha256": "40ce15fc9113808a87be7518e1847c751cdc78f6951f150747af6461a4f1cc05", + "catalog_code": "S15", + "catalog_id": "state-no-matching-results", + "condition": "Fresh agent session with Plotter; no without-skill control", + "user_verdict": "Liked; original version preferred over added background-grid variant" + } + ], + "feedback": [ + { + "speaker": "user", + "text": "I like them all." + }, + { + "speaker": "user", + "text": "i honestly prefer these examples to be without grids i'd add them only if necessary" + }, + { + "speaker": "user", + "text": "i think the rule of thumb should be: if it's pure geometric then a grid would work eg M01 or P00." + } + ], + "interpretation": "Consider a grid when geometric construction is the subject; retain it when it reveals the construction. The three original examples remain preferred.", + "limitations": [ + "Three single runs with the skill; no causal improvement claim or reliability estimate.", + "User approval of these examples does not automatically add them to the earlier 27-reference set." + ], + "original_geometry_checks": [ + "Shared-frame horizontal clearances are 24 units.", + "Appointment intervals 72:216:72 represent 10:30:10 minutes.", + "Indexed record centers follow a 60-unit pitch." + ] +} diff --git a/apps/ui/plotter-evals/2026-10-31/clean-install/agent-notes.json b/apps/ui/plotter-evals/2026-10-31/clean-install/agent-notes.json new file mode 100644 index 0000000000..79719168c6 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/clean-install/agent-notes.json @@ -0,0 +1,92 @@ +{ + "subject": "Circle inscribed in an equilateral triangle", + "mode": "geometric construction", + "candidate_count": 1, + "output": "output/inscribed-circle.svg", + "size": { + "width": 560, + "height": 440, + "viewBox": "0 0 560 440" + }, + "references": [ + { + "path": "plotter/references/drawings/P00.svg", + "reason": "Closest bundled relationship: circle, straight boundaries, radii, and tangency. Borrowed hierarchy and restrained geometry, not historical background fills.", + "inspection": "Source read and rendered on a light surface via html_preview." + } + ], + "decisions": [ + "Triangle and incircle share the primary 0.95-unit stroke. Radii, perpendicular markers, and equality ticks use the standard 0.65-unit stroke at 0.50 opacity.", + "Solid point markers are explicitly declared geometric-point symbols. T₁, T₂, T₃ identify tangencies; O identifies the center. Three right-angle markers and the equal-radius equation explain the relationship.", + "Matching side ticks identify the equilateral triangle. No physical dimension or standards-compliance claim is made.", + "No grid, hatch, or area fill: the three radii already explain the entire construction. Enclosed forms and canvas remain transparent.", + "Cal Sans, Arial, sans-serif fallback stack; automatic optical sizing, no letter-spacing, no synthesized styles, GEOM 25 for the single text role. No font files required.", + "Accessible title and description included. No external resources or scripts." + ], + "construction": { + "center": [280.0, 246.0], + "radius": 90.0, + "vertices": [[280.0, 66.0], [124.115427, 336.0], [435.884573, 336.0]], + "tangent_points": [ + [202.057714, 201.0], + [357.942286, 201.0], + [280.0, 336.0] + ], + "equations": [ + "altitude = 3r", + "side = 2√3r", + "tangent points are the midpoints of the three sides" + ] + }, + "checks": { + "xml_parse": "PASS", + "geometry_source": "Actual serialized SVG coordinates recovered with Python XML parser.", + "side_lengths_svg_units": [ + 311.7691455217984, 311.7691455217984, 311.769146 + ], + "equal_side_max_difference_svg_units": 4.78201570786041e-7, + "tangency": [ + { + "circle_incidence_residual_svg_units": 2.949678048480564e-7, + "side_incidence_residual_svg_units": 4.3301269656642006e-7, + "radius_side_angle_degrees": 90.00000005767743, + "right_angle_residual_degrees": 5.767742550233379e-8, + "side_parameter": 0.49999999919812466 + }, + { + "circle_incidence_residual_svg_units": 2.9496777642634697e-7, + "side_incidence_residual_svg_units": 4.330126732287751e-7, + "radius_side_angle_degrees": 90.00000005767741, + "right_angle_residual_degrees": 5.7677411291479075e-8, + "side_parameter": 0.4999999991981247 + }, + { + "circle_incidence_residual_svg_units": 0.0, + "side_incidence_residual_svg_units": 0.0, + "radius_side_angle_degrees": 90.0, + "right_angle_residual_degrees": 0.0, + "side_parameter": 0.5 + } + ], + "tolerances": { + "position_svg_units": 0.05, + "angle_degrees": 0.1 + }, + "geometry_verdict": "PASS", + "visual": { + "tool": "html_preview", + "sizes": ["560 × 440", "280 × 220 thumbnail"], + "surface": "white", + "console_messages": [], + "result": "The circle-in-triangle gesture remains clear at both sizes. At intended size, labels, radius lines, right-angle markers, and equality ticks are separated and visible; thumbnail preserves the form and contact relationship, while the equation is secondary small text.", + "iterations": 1 + }, + "html_render_called": false + }, + "missing_dependencies": [], + "blockers": [], + "limitations": [ + "Font fallback is allowed and no Cal Sans file was supplied; the exact browser-resolved font was not measured.", + "This is a standalone light-surface geometric illustration, not a responsive host integration or a physical engineering drawing." + ] +} diff --git a/apps/ui/plotter-evals/2026-10-31/clean-install/inscribed-circle.svg b/apps/ui/plotter-evals/2026-10-31/clean-install/inscribed-circle.svg new file mode 100644 index 0000000000..f3aac57385 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/clean-install/inscribed-circle.svg @@ -0,0 +1,36 @@ + + A circle inscribed in an equilateral triangle + A circle touches the three equal sides of an equilateral triangle at T₁, T₂, and T₃. Three radii extend from the center O to those tangent points. Small squares show that each radius is perpendicular to its side. Solid point markers identify the center and tangencies; matching side ticks indicate equal side lengths. + + + + + + + + + + + + + + + + + + + T₁ + T₂ + T₃ + O + r + OT₁ = OT₂ = OT₃ = r + + + diff --git a/apps/ui/plotter-evals/2026-10-31/clean-install/package-hashes.json b/apps/ui/plotter-evals/2026-10-31/clean-install/package-hashes.json new file mode 100644 index 0000000000..50a2659705 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/clean-install/package-hashes.json @@ -0,0 +1,25 @@ +{ + "README.md": "fc5015c1dda14fa30cfd35b2ed7eca7fbf7c4342adfcd2a03b4ccc577510b504", + "SKILL.md": "27d953450e19af4a50b3f58baf4dd350b3c7d8315b57dfd0a559f3916a1e4473", + "agents/openai.yaml": "60cd55400869654c90fd25f6d8b92c75f1cfc32cc902e2aa2051b0ddee93914a", + "references/approved-references.md": "e17a1ab5f56eddb55fa64ee88e6af110a78e5ef19db290bc809998eb685ea158", + "references/coss-product-concepts.md": "f77ae6e1af2f8e0a79d0ad05ccf41075554651d0d32e4126755f4f649104e377", + "references/direction.md": "637b08c6d16d94299e37266028c61642b9e1686caa874008352a93dd9095e4f1", + "references/drafting-standard.md": "a65fe5b1f88f44196bc340acedd294fb61e0150de6a69711cf158bd1c17b0f3b", + "references/drawings/B04.svg": "3635d28fa90efa36cbb4c9374274df930eafa65bbd50c938508c243ab24ca294", + "references/drawings/C02.svg": "21763dddf898e52c29f616aa897792307336914d3f034ddd3a18aafc8fe9f89c", + "references/drawings/E02.svg": "595d5174194ac7cfef182eb86488a8dda7ee53a20b0b0ed0b3197f60b04b0f66", + "references/drawings/I06.svg": "47118d15b7f0787dcf8c4a88abe3348ce1f5ce3b8cffe47b38694cd6dc710a02", + "references/drawings/K07.svg": "4f1c3e771ff163a2de9bfcf44800892dc549fa0a1347c4caac78cf6fbb7fd057", + "references/drawings/P00.svg": "4082ec9a1000107cb1580fcd6826cff15cc15c2c3d919a79291d1d7070cce1d4", + "references/drawings/manifest.json": "5e4773509da7aeb974bd400470e9969e474d66c1791dad7068ca29e8b5492c9d", + "references/implementation.md": "1098415d3d68c993171fb1b80318bdd794a0f71fae17bbaf036e9daed25131b5", + "references/linear.png": "8bdb6dd8bf343bb92ee3cb7fc34bd7fb71f712d8f9c9ca37b2be026112fd9d4f", + "references/not-found-interrupted-path.png": "ee3f7c33506fda5fb4cbb86600dc9b65b7dc6e3ea40c6c4c3a911f44c33fdfd4", + "references/particles-recombination.png": "df932415d657cc7710b70770f97b28ae5db15abe6cf3d9b2fe59316b549b7442", + "references/particles.png": "fe1a662f99f25f95b992ac634ba73dd50637e56ffa2428bfb0fd30fd833e3ebc", + "references/primitives.png": "72c0331f1861d687639f72ee46861e8e1e526151a6c6f0b717d1ce07143db0ec", + "references/remaining-source-audit.md": "2ff697dc69f1e29a212bbb95a2ae2fb892f12ab041d5927e68a321038b27e030", + "references/source-audit.md": "97d9479c541cf4f5479469534b87d1d1ae9d5d57ffd4691fa75ededb68707c7b", + "references/verification-prompt.md": "1e4d13f5ebcddd8648c3af3fe9095e57ac0d07065e3965c7f4a41feaefef4d9a" +} diff --git a/apps/ui/plotter-evals/2026-10-31/clean-install/result.json b/apps/ui/plotter-evals/2026-10-31/clean-install/result.json new file mode 100644 index 0000000000..d94cd2d860 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/clean-install/result.json @@ -0,0 +1,18 @@ +{ + "brief": "Create a standalone 560 \u00d7 440 SVG illustration of a circle inscribed in an equilateral triangle. Make the relationship between the circle, the sides, and their tangent points understandable.", + "condition": "Fresh agent session, copied skill package only; repository and prior conversation excluded by task instructions. This is a scoped agent exercise, not an OS-enforced isolation boundary.", + "package_revision": "Exact packaged files identified by package-hashes.json; based on this change set before its commit.", + "result": "Passed one bounded standalone-use smoke test. Agent found bundled P00, produced editable SVG with no external dependencies, inspected native/thumbnail output, and explained its grid decision.", + "independent_geometry_residuals_svg_units": { + "equal_side_lengths": 4.78201570786041e-7, + "center_to_sides_radius": 1.3804489640278916e-7, + "tangent_points_on_circle": 2.949678048480564e-7, + "tangent_points_on_sides": 4.3301269656642006e-7 + }, + "parent_render_review": "Native-size rendering inspected with fallback font. Clear tangencies and labels; no console errors. Aesthetic approval remains with the user.", + "limitations": [ + "One task and one run; no reliability estimate or without-skill comparison.", + "Geometric subject does not test service-product research, every subject category, or animation.", + "Candidate is evaluation evidence, not an approved reference or catalog addition." + ] +} diff --git a/apps/ui/plotter-evals/2026-10-31/modules.svg b/apps/ui/plotter-evals/2026-10-31/modules.svg new file mode 100644 index 0000000000..8450a1d0ae --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/modules.svg @@ -0,0 +1,43 @@ + + Two modules, one shared frame + Two rounded modules of widths 4u and 7u fit inside a frame 14u wide. All straight edge clearances are 1u, including the central gap. The frame has radius 36 and modules have radius 12, with shared exterior corner centers. One unit equals 24 SVG construction units. + Mode: geometric construction. u = 24 SVG units. Frame: (112,120), 336 × 216, R36. A: (136,144), 96 × 168, R12. B: (256,144), 168 × 168, R12. Hatch: selected central clearance strip x232–256, y156–300. No physical manufacturing claim. + + + + + + + + + + + + + + + + + + + + + + + + + + Shared frame + A + B + + + 14u + u + 4u + u + 7u + u + + + \ No newline at end of file diff --git a/apps/ui/plotter-evals/2026-10-31/search.svg b/apps/ui/plotter-evals/2026-10-31/search.svg new file mode 100644 index 0000000000..a03d4c8b9c --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/search.svg @@ -0,0 +1,55 @@ + + No matching results + Query 03 approaches an empty dashed record between entries 02 and 04. Three indexed records are present, and the query has zero matches. + + Conceptual diagram: exact lookup of key 03 in the set {01, 02, 04}. Rectangles represent records, the dashed footprint represents absence, and the dashed arrow represents a lookup request. Slot pitch u = 60 SVG units; no physical scale or implementation architecture is asserted. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Index + Query + 03 + 01 + 02 + 04 + 0 matches + + u + diff --git a/apps/ui/scripts/check-plotter.tsx b/apps/ui/scripts/check-plotter.tsx new file mode 100644 index 0000000000..bdd93b0d67 --- /dev/null +++ b/apps/ui/scripts/check-plotter.tsx @@ -0,0 +1,76 @@ +/// + +import { createHash } from "node:crypto"; +import { readdirSync, readFileSync, statSync } from "node:fs"; +import { dirname, resolve } from "node:path"; +import { YAML } from "bun"; +import { renderToStaticMarkup } from "react-dom/server"; +import { illustrations } from "../app/plotter/illustrations"; + +const skillRoot = resolve(import.meta.dir, "../skills/plotter"); +const skill = readFileSync(resolve(skillRoot, "SKILL.md"), "utf8"); +const frontmatter = skill.match(/^---\n([\s\S]*?)\n---/); +if (!frontmatter?.[1]) throw new Error("Plotter is missing YAML frontmatter."); +const metadata = YAML.parse(frontmatter[1]) as Record; +if (metadata.name !== "plotter" || typeof metadata.description !== "string") { + throw new Error("Plotter needs a name and description for skill discovery."); +} + +function checkLinks(directory: string) { + for (const entry of readdirSync(directory, { withFileTypes: true })) { + const file = resolve(directory, entry.name); + if (entry.isDirectory()) checkLinks(file); + if (!entry.isFile() || !entry.name.endsWith(".md")) continue; + const text = readFileSync(file, "utf8"); + for (const match of text.matchAll(/\]\(([^)]+)\)/g)) { + const target = match[1]; + if (!target) continue; + if (/^[a-z][a-z\d+.-]*:/i.test(target) || target.startsWith("#")) + continue; + statSync(resolve(dirname(file), target.replace(/#.*/, ""))); + } + } +} +checkLinks(skillRoot); + +const referenceRoot = resolve(skillRoot, "references/drawings"); +const manifest = JSON.parse( + readFileSync(resolve(referenceRoot, "manifest.json"), "utf8"), +) as { references: { code: string; file: string; sha256: string }[] }; +for (const reference of manifest.references) { + const contents = readFileSync(resolve(referenceRoot, reference.file)); + const hash = createHash("sha256").update(contents).digest("hex"); + if (hash !== reference.sha256) { + throw new Error( + `${reference.code}: bundled snapshot differs from its manifest.`, + ); + } +} + +const codes = new Set(); +const ids = new Set(); +const svgIds = new Set(); +for (const study of illustrations) { + if (codes.has(study.code) || ids.has(study.id)) { + throw new Error(`${study.code}: duplicate catalog code or deep-link ID.`); + } + codes.add(study.code); + ids.add(study.id); + const svg = renderToStaticMarkup(study.illustration); + if (!svg.startsWith("` links locate native-size inline drawings. Narrow screens scroll within each drawing. The page is available by direct URL and is not linked from site navigation. + +The three initial fresh-session experiments, prompts, and feedback are preserved in the repository at `apps/ui/plotter-evals/2026-10-31/`. They are evaluation evidence and catalog additions, separate from the 27 approved style references. A later instruction change should identify the observed failure and compare actual outputs on the affected brief and an unrelated one. Clean-install results establish only the tasks and revisions actually tested. + +Maintainers can run `bun run plotter:check` from `apps/ui` to check skill metadata and links, bundled snapshot hashes, catalog IDs, and SVG rendering. This checks package integrity and rendering, not visual quality. Review the actual drawings and record a fresh-session exercise before declaring a candidate ready; ordinary installed users do not need the repository check command. diff --git a/apps/ui/skills/plotter/SKILL.md b/apps/ui/skills/plotter/SKILL.md index 58e17ecc9b..0f57841358 100644 --- a/apps/ui/skills/plotter/SKILL.md +++ b/apps/ui/skills/plotter/SKILL.md @@ -5,46 +5,47 @@ description: Create, critique, and refine flat monochrome blueprint illustration # Plotter -Build a precise planar composition that communicates a construction relationship: radius, alignment, spacing, assembly, routing, or intersection. The result should feel like a refined architectural or electrical drawing, with one readable gesture and generous empty space. +Build an editable, flat monochrome illustration around one construction relationship: radius, alignment, spacing, assembly, routing, or intersection. The result should have one readable gesture and generous empty space. Establish the static drawing before considering motion. ## Establish the reference -Before drawing or judging style, read the [approved reference set](references/approved-references.md). It records the user’s exact 27 selections and supersedes earlier broad endorsements. Choose a reference by its construction relationship, then inspect that drawing. [Primitives/P00](references/primitives.png) is the general geometric anchor and [Linear/B04](references/linear.png) is a strong service example; use the approved subject-specific studies for other compositions. +Read the [approved reference index](references/approved-references.md), choose the nearest construction relationship, and inspect its bundled SVG. Six snapshots cover geometric construction, modular assembly, time intervals, service concepts, conversation, and absence. They work without the coss checkout; the wider 27-drawing reference set is available when the repository is present. A familiar subject alone is not a sufficient match. -The current production selections are P00 for the main page, C03 for Particles, and E02 for the 404. Production placement is distinct from reference approval: C03 and the original Particles/C00 are outside the current approved reference set. Their prior feedback remains useful history, not a reason to copy them as the default style. - -Read [direction and feedback](references/direction.md) for implementation choices and earlier review evidence. The current approved reference set takes precedence over historical “unreviewed” labels. A drawing’s presence in the gallery does not establish approval. +The snapshots establish composition and hierarchy. Some retain historical fills or line treatments; use the [drafting standard](references/drafting-standard.md) for new linework, transparent forms, hatching, and geometric tolerances. Source revisions are recorded in the snapshot manifest. Appearance approval does not certify technical accuracy or every subsequent revision. ## Compose -- Use one flat plane. The user explicitly excludes perspective, isometric views, skewed or tilted objects, physical depth, glossy 3D, colorful gradients, and science-class atom diagrams. Diagonal radius lines and callout leaders can explain geometry without tilting the represented form. +- Use one flat plane. Exclude perspective, isometric views, skewed or tilted objects, physical depth, glossy 3D, colorful gradients, and science-class atom diagrams. Diagonal radius lines and callout leaders can explain geometry without tilting the form. - Keep the palette black, off-white, and soft gray. Adapt those values to the host’s theme while preserving the monochrome hierarchy. -- Make the main form read first, meaningful construction guides second, sparse dimensions and labels third. Hairline outlines should remain visible at the intended display size. -- Start from a small geometric vocabulary: circles, rectangles, lines, arcs, slots, and modest UI fragments. Choose one central relationship before adding detail. A complete screen mockup or a large pictogram with technical marks is a poor starting point for this direction. -- Give every guide a job. Show actual tangency, radius, equal intervals, alignment, a shared baseline, or a connection. Let grids fade where they stop explaining the form. Avoid filling empty areas with arbitrary drafting marks. -- Name what geometry alone cannot communicate. A week needs weekday or time cues; overlapping availability needs a clear association with people or schedules. Prefer short integrated labels over extra icons or a caption that must explain the whole image. -- For the coss main and Particles pages, preserve the split layout: left-aligned headline and description; a single composition on the right. The main page has CTAs beneath its copy; Particles has its search/filter directly beneath the copy, with its original filtering behavior. Treat the 404 separately: illustration above the centered heading, description, and CTA. Keep these layouts in their pages; do not introduce a shared page-hero component. Elsewhere, fit the host layout unless the user asks to restructure it. When revising a standalone illustration, inspect its native-size drawing in the showcase gallery. +- Make the main form read first, meaningful construction guides second, sparse dimensions and labels third. Hairline outlines must remain visible at the intended display size. +- Start from circles, rectangles, lines, arcs, slots, or modest UI fragments. Choose the central relationship before adding detail. A complete screen mockup or a large pictogram surrounded by technical marks is a poor starting point. +- Give every guide a job: tangency, radius, equal intervals, alignment, a shared baseline, or a connection. Consider a background grid when geometric construction is the subject; keep it when it helps reveal that construction. If removing it leaves the relationships equally clear, omit it. Preserve grids that carry the subject itself, such as calendar cells, or were explicitly requested for a particular drawing. +- Name what geometry alone cannot communicate. A week needs weekday or time cues; overlapping availability needs an association with people or schedules. Prefer short integrated labels over extra icons or a caption that must explain the entire image. ## Implement and inspect -Use editable SVG or the host’s existing vector components for this geometric work. Read [SVG and coss integration](references/implementation.md) when implementing or checking geometry. Animation is optional future work; first make the static drawing succeed. - -Read the [drafting standard](references/drafting-standard.md) when creating or revising linework. It defines this style as **monochrome orthographic construction diagrams**, assigns line and hatch roles, and separates geometric constructions, functional schematics, and service metaphors. Existing approved compositions establish art direction; they do not certify technical accuracy. Preserve explicit user choices when an older reference and a proposed normalization differ. +Deliver editable SVG or the host’s existing vector components. The references use a `560 × 440` viewBox; fit the requested placement. Use unique IDs for repeated SVG defs, masks, or clips. A meaningful standalone diagram needs an accessible title and description; a decorative illustration can be hidden from accessibility APIs when adjacent copy supplies its meaning. -For a technical audit, use the [verification prompt](references/verification-prompt.md) with the actual source and rendered revision. Calculate geometric claims and verify product behavior independently. The [source audit](references/source-audit.md) records inspected examples, corrections, and remaining limits; it is evidence for those revisions, not a gallery-wide certification. +Inherit the host font, or use `Cal Sans, sans-serif` with a sans-serif fallback when Cal Sans is unavailable. Keep automatic optical sizing, no added letter-spacing, no synthesized styles, and one GEOM value per text role. Use typeset punctuation. A standalone task does not require the coss checkout or its font files. -Inspect the rendered result at its intended hero size and as a thumbnail, then check the narrow layout when embedding it in a page. At narrow sizes, preserve labels that identify the subject; simplify secondary guides or dimensions before shrinking all annotations into illegibility. Compare it with the relevant anchors. Resolve these questions before delivering: +Inspect the rendered drawing at its intended size and as a thumbnail; check narrow layouts when embedding it. Preserve identifying labels before secondary dimensions. Resolve these questions: 1. Does one central gesture survive at a glance? -2. Can the intended subject be identified from the form and restrained labels? -3. Do the guides explain how the form is constructed, and do their measurements agree with the geometry? -4. Is there enough quiet space, with clear separation between outlines, guides, and annotations? -5. Does the requested subject remain a flat construction, with no perspective, simulated depth, or ornamental clutter taking over? +2. Can the subject be identified from the form and restrained labels? +3. Do the guides explain construction, and do their measurements agree with the geometry? +4. Is there enough quiet space and separation between outlines, guides, and annotations? +5. Does the subject remain flat, without simulated depth or ornamental clutter? + +If meaning is unclear, improve the structural cue or labeling before adding detail. If the composition is weak, improve its arrangement before adding guides. A passing code check does not answer either design question. + +## Task-specific guidance -If the subject is unclear, clarify the structural cue or labeling before adding detail. If the drawing feels weak, strengthen the arrangement or relationship before adding more grid lines. A passing code check does not answer either design question. +- **Integrating into coss or revising its motion examples:** read [SVG and coss integration](references/implementation.md), then the applicable repository instructions. Preserve the host page’s layout unless a change was requested. +- **Revising a particular existing study:** read its entries in [direction and feedback](references/direction.md). Current explicit user feedback takes precedence; gallery inclusion and production placement are separate from reference approval. +- **Technical verification:** use the [verification prompt](references/verification-prompt.md) with current source and rendered output. The [source audit](references/source-audit.md) is historical evidence for named revisions, not a requirement for every drawing or a gallery-wide certification. ## Refine from feedback -When the user critiques a study, preserve its identifier and revise it in place unless they want separate alternatives. Record the exact preference, the relevant study/revision, and the response in `references/direction.md` when that feedback changes future decisions. Separate explicit user feedback from your interpretation. Keep the record compact by consolidating superseded guidance. +Preserve a study’s identifier when revising it unless separate alternatives were requested. Record feedback that changes future decisions in `references/direction.md`, naming the study/revision and separating the user’s preference from your interpretation. Keep this history compact. -Treat broad praise as evidence of improvement, not approval of every example as a permanent rule. Promote a new visual anchor when the user identifies it as a reference. Keep unresolved concepts open. Change this skill narrowly as evidence accumulates; do not add a new skill for each subject unless its workflow genuinely diverges. +Broad praise does not promote every example to a permanent reference. Add a visual anchor when the user identifies it as one. Improve instructions narrowly from observed failures; keep unresolved concepts open. diff --git a/apps/ui/skills/plotter/references/approved-references.md b/apps/ui/skills/plotter/references/approved-references.md index 2b246774d6..4baea6c6aa 100644 --- a/apps/ui/skills/plotter/references/approved-references.md +++ b/apps/ui/skills/plotter/references/approved-references.md @@ -1,10 +1,25 @@ # Approved references -On 2026-10-25, the user explicitly identified these 27 drawings as the best illustrations and asked that they be used as references. This list supersedes earlier broad endorsements and pending-review labels. Match the current approved revisions, not an earlier iteration with the same code. +On 2026-10-25, the user explicitly identified these 27 drawings as the best illustrations and asked that they be used as references. This list supersedes earlier broad endorsements and pending-review labels. Keep reference selection separate from revision identity: a later edit to a drawing does not silently inherit approval. -## Reference set +## Bundled starting points -Choose the nearest construction relationship from this set before drawing. Inspect it in the Plotter gallery at `/ui/plotter?variant=`; locate its editable SVG by searching its code in `apps/ui/app/plotter/`. A familiar subject alone is not a sufficient reference match. +Choose by construction relationship, then render and inspect the linked SVG. These six snapshots are available in a standalone installation: + +| Relationship | Reference | What to borrow | +| --- | --- | --- | +| Circle, square, radius, and tangency | [P00](drawings/P00.svg) | A shared geometric construction with one focal relationship | +| Modules following one allocation rule | [C02](drawings/C02.svg) | Repeated units, spacing, and a coherent component family | +| Time intervals combining into a whole | [K07](drawings/K07.svg) | Proportional lengths and restrained duration labels | +| Work aligned to a cycle | [B04](drawings/B04.svg) | A scoped service concept organized by a shared time field | +| Conversation expressed with a few UI fragments | [I06](drawings/I06.svg) | Recognition through avatars, tail-free messages, and spacing | +| Absence interrupting a structure | [E02](drawings/E02.svg) | A missing connection that affects the construction itself | + +[The manifest](drawings/manifest.json) records source revision and snapshot hashes. These are source snapshots of selected compositions, not newly normalized or technically certified drawings. Historical background fills and line-role variations may remain; use the [drafting standard](drafting-standard.md) for new work. Render the snapshots on a light surface to inspect their original presentation. + +## Full reference set + +The following selection remains unchanged. When the coss checkout is available, inspect these in `/ui/plotter?variant=` and find source by code in `apps/ui/app/plotter/`. Without it, choose the nearest bundled relationship above instead of assuming the gallery or other source files are accessible. A familiar subject alone is not a sufficient reference match. | Code | Drawing | Study ID | | --- | --- | --- | @@ -40,7 +55,7 @@ Choose the nearest construction relationship from this set before drawing. Inspe The user said the remaining illustrations do not look perfect or do not fit the style for different reasons, and could not yet identify those reasons. Treat them as revision candidates, not style references. This feedback does not request their deletion or establish a specific diagnosis for each drawing. -The omitted current static drawings are C00, C03, P01, K03, I04, I05, W02, O03, O04, O05, B08, S01, and S03–S14. In particular, original Particles/C00 is now a historical reference; C03’s production placement is a separate implementation decision. Preserve the user’s specific earlier feedback on details without promoting the whole drawing. This static selection does not revoke the separately reviewed animation work. +At the time of the selection, the omitted static drawings were C00, C03, P01, K03, I04, I05, W02, O03, O04, O05, B08, S01, and S03–S14. In particular, original Particles/C00 is now a historical reference; C03’s production placement is a separate implementation decision. Preserve the user’s specific earlier feedback on details without promoting the whole drawing. This static selection does not revoke the separately reviewed animation work. The later C04, K09, and S15 experiments were liked and requested for the catalog; they are not additions to this 27-reference selection. ## Working interpretation diff --git a/apps/ui/skills/plotter/references/direction.md b/apps/ui/skills/plotter/references/direction.md index 7ca30cb820..ce0600b6e0 100644 --- a/apps/ui/skills/plotter/references/direction.md +++ b/apps/ui/skills/plotter/references/direction.md @@ -1,6 +1,20 @@ # Direction and feedback -Evidence captured from the coss UI illustration reviews on 2026-10-24–25. This is a living art-direction record, not a claim that every study has been approved. +Evidence captured from the coss UI illustration reviews on 2026-10-24–25 and the fresh-session evaluation on 2026-10-31. This is a living art-direction record, not a claim that every study has been approved. + +## Fresh-session experiments and grid comparison — 2026-10-31 + +The user liked all three fresh-session experiments and later requested their original versions in the catalog: + +| Experiment | Catalog code and ID | Subject | +| --- | --- | --- | +| A | C04 · `particles-shared-frame` | Two unequal modules in a shared frame | +| B | K09 · `cal-appointment-buffers` | An appointment with equal buffers | +| C | S15 · `state-no-matching-results` | An indexed collection with no matching result | + +After comparing versions with and without added background grids, the user said: “i honestly prefer these examples to be without grids i'd add them only if necessary”. Keep the originals without added grids. This supersedes the assistant’s recommendation to keep the grid variants; it does not revoke explicit grid requests for existing studies or promote these experiments into the approved reference set. Prompts, originals, and feedback are preserved in the repository at `apps/ui/plotter-evals/2026-10-31/`. + +The user then proposed geometric studies such as M01 and P00 as good candidates for grids. They accepted the qualification that geometric content alone is insufficient: A is also geometric and was preferred without the grid. The resulting [Compose](../SKILL.md#compose) rule is to consider a grid when geometric construction is the subject and keep it when it helps reveal that construction. The assistant’s interpretation is that the extra grids were redundant in these three examples, not that grids are incompatible with the style. ## Current references diff --git a/apps/ui/skills/plotter/references/drawings/B04.svg b/apps/ui/skills/plotter/references/drawings/B04.svg new file mode 100644 index 0000000000..9f21beb38b --- /dev/null +++ b/apps/ui/skills/plotter/references/drawings/B04.svg @@ -0,0 +1 @@ + diff --git a/apps/ui/skills/plotter/references/drawings/C02.svg b/apps/ui/skills/plotter/references/drawings/C02.svg new file mode 100644 index 0000000000..53369c093a --- /dev/null +++ b/apps/ui/skills/plotter/references/drawings/C02.svg @@ -0,0 +1 @@ + diff --git a/apps/ui/skills/plotter/references/drawings/E02.svg b/apps/ui/skills/plotter/references/drawings/E02.svg new file mode 100644 index 0000000000..f801453ae7 --- /dev/null +++ b/apps/ui/skills/plotter/references/drawings/E02.svg @@ -0,0 +1 @@ + diff --git a/apps/ui/skills/plotter/references/drawings/I06.svg b/apps/ui/skills/plotter/references/drawings/I06.svg new file mode 100644 index 0000000000..c4b260accc --- /dev/null +++ b/apps/ui/skills/plotter/references/drawings/I06.svg @@ -0,0 +1 @@ + diff --git a/apps/ui/skills/plotter/references/drawings/K07.svg b/apps/ui/skills/plotter/references/drawings/K07.svg new file mode 100644 index 0000000000..5e7c2577be --- /dev/null +++ b/apps/ui/skills/plotter/references/drawings/K07.svg @@ -0,0 +1 @@ + diff --git a/apps/ui/skills/plotter/references/drawings/P00.svg b/apps/ui/skills/plotter/references/drawings/P00.svg new file mode 100644 index 0000000000..215c7e2804 --- /dev/null +++ b/apps/ui/skills/plotter/references/drawings/P00.svg @@ -0,0 +1 @@ + diff --git a/apps/ui/skills/plotter/references/drawings/manifest.json b/apps/ui/skills/plotter/references/drawings/manifest.json new file mode 100644 index 0000000000..a94cacbffd --- /dev/null +++ b/apps/ui/skills/plotter/references/drawings/manifest.json @@ -0,0 +1,55 @@ +{ + "source_commit": "aeebda6ba387149db564dc774c1909eef0e06c9e", + "exported": "2026-10-31", + "scope": "Source snapshots of approved compositions. Legacy fill/line choices remain; these are not newly normalized or certified drawings. Use the drafting standard for new work. Font falls back to sans-serif outside a Cal Sans host.", + "references": [ + { + "code": "P00", + "id": "primitives-reference", + "title": "Circle, square, line", + "file": "P00.svg", + "sha256": "4082ec9a1000107cb1580fcd6826cff15cc15c2c3d919a79291d1d7070cce1d4", + "source": "apps/ui/app/plotter/primitives-blueprint.tsx" + }, + { + "code": "C02", + "id": "particles-slots", + "title": "A modular tray", + "file": "C02.svg", + "sha256": "21763dddf898e52c29f616aa897792307336914d3f034ddd3a18aafc8fe9f89c", + "source": "apps/ui/app/plotter/system-studies.tsx" + }, + { + "code": "K07", + "id": "cal-time-modules", + "title": "The shape of a time slot", + "file": "K07.svg", + "sha256": "4f1c3e771ff163a2de9bfcf44800892dc549fa0a1347c4caac78cf6fbb7fd057", + "source": "apps/ui/app/plotter/flat-cal-studies.tsx" + }, + { + "code": "B04", + "id": "service-linear-cycle", + "title": "Linear · Work aligned to a cycle", + "file": "B04.svg", + "sha256": "3635d28fa90efa36cbb4c9374274df930eafa65bbd50c938508c243ab24ca294", + "source": "apps/ui/app/plotter/service-studies.tsx" + }, + { + "code": "I06", + "id": "interface-chat-plan", + "title": "A conversation, constructed", + "file": "I06.svg", + "sha256": "47118d15b7f0787dcf8c4a88abe3348ce1f5ce3b8cffe47b38694cd6dc710a02", + "source": "apps/ui/app/plotter/interface-detail-studies.tsx" + }, + { + "code": "E02", + "id": "404-interrupted-path", + "title": "An interrupted path", + "file": "E02.svg", + "sha256": "595d5174194ac7cfef182eb86488a8dda7ee53a20b0b0ed0b3197f60b04b0f66", + "source": "apps/ui/app/plotter/not-found-blueprint.tsx" + } + ] +} diff --git a/apps/ui/skills/plotter/references/implementation.md b/apps/ui/skills/plotter/references/implementation.md index cecb72de12..3c11f85e03 100644 --- a/apps/ui/skills/plotter/references/implementation.md +++ b/apps/ui/skills/plotter/references/implementation.md @@ -1,5 +1,7 @@ # SVG and coss integration +Read this file when integrating into coss, adapting an existing host component, or working on the showcase’s motion examples. Standalone drawing uses the bundled references and drafting standard without a repository checkout. + ## Geometry and rendering The established examples use a `560 × 440` SVG viewBox. Use the [drafting standard](drafting-standard.md) for current line roles, dash patterns, hatch spacing, and geometry tolerances. Preserve transparent forms and adapt the complete composition to its intended display size; historical fills and per-study stroke variations are not new defaults. @@ -21,7 +23,7 @@ Find the checkout from the current workspace; the skill does not require a parti | Primary geometry reference | `apps/ui/app/plotter/primitives-blueprint.tsx` | | Selected Particles composition (C03) | `apps/ui/app/plotter/particles-blueprint.tsx` | | Selected 404 composition (E02) | `apps/ui/app/plotter/not-found-blueprint.tsx` | -| Original Particles style anchor (C00) | `apps/ui/app/plotter/original-particles-blueprint.tsx` | +| Historical original Particles drawing (C00) | `apps/ui/app/plotter/original-particles-blueprint.tsx` | | Main integration target | `apps/ui/app/page.tsx` | | Particles integration target | `apps/ui/app/particles/page.tsx` | | 404 integration target | `apps/ui/app/not-found.tsx` | @@ -33,7 +35,9 @@ The showcase owns its drawings and wrapper independently of the production integ Choose style references from [the approved set](approved-references.md); production placement and bundled historical screenshots are separate from reference approval. -Keep production page layouts local, as explicitly requested by the user. Do not recreate a shared page-hero component. The showcase displays each standalone drawing at its native 560×440 size in the gallery, with no enlargement dialog. Grid columns accommodate the full canvas; narrow screens scroll within the drawing rather than scaling it down. Keep the illustration captions inline. Do not add the extra compact empty-state application cards (such as “Page not found” or “No results found”); the user explicitly removed those from the gallery. +Keep production page layouts local, as explicitly requested by the user. Main and Particles use left-aligned headline/description with one drawing on the right; main has CTAs and Particles has its search/filter beneath the copy, retaining existing filtering behavior. The 404 uses a centered stack with the illustration above its heading, description, and CTA. Do not recreate a shared page-hero component. The showcase displays each standalone drawing at its native 560×440 size in the gallery, with no enlargement dialog. Grid columns accommodate the full canvas; narrow screens scroll within the drawing rather than scaling it down. Keep the illustration captions inline. Do not add the extra compact empty-state application cards (such as “Page not found” or “No results found”); the user explicitly removed those from the gallery. + +The selected production drawings are P00 for Main, C03 for Particles, and E02 for the 404. Placement is separate from style-reference approval; C03 and historical C00 are outside the approved 27. The selected production integration for Main, Particles, and Chat uses the heading treatment from [PR #895](https://github.com/cosscom/ui/pull/895): `clamp(2.25rem, 1.5rem + 2.6vw, 4rem)`, line-height 1, and GEOM50, with normal letter spacing and automatic optical sizing. These settings are scoped to those pages. Particles keeps C03 on the right and its search/filter directly beneath the left-aligned description, rather than adding hero CTAs. diff --git a/apps/ui/skills/plotter/references/verification-prompt.md b/apps/ui/skills/plotter/references/verification-prompt.md index 0366ab2601..ec015c1856 100644 --- a/apps/ui/skills/plotter/references/verification-prompt.md +++ b/apps/ui/skills/plotter/references/verification-prompt.md @@ -13,9 +13,11 @@ Inputs - Nearest approved references and which relationship each supplies: - Claimed units, scale, equations, behavior, and external sources: -Read apps/ui/skills/plotter/SKILL.md and its references/drafting-standard.md, -approved-references.md, and current direction.md. Treat appearance approval -as art-direction evidence, not as proof of mathematical or product accuracy. +Resolve the installed Plotter skill directory, then read its SKILL.md and +references/drafting-standard.md and references/approved-references.md. For an +existing study, also read its relevant entries in references/direction.md. +Treat appearance approval as art-direction evidence, not as proof of +mathematical or product accuracy. 1. Establish the claim boundary. State the one relationship this drawing communicates. List every visible From 21ecf5c31338ecc49fc270572b53e229a860ba98 Mon Sep 17 00:00:00 2001 From: pasqualevitiello Date: Sat, 31 Oct 2026 15:32:08 +0100 Subject: [PATCH 11/15] feat(ui): expand Plotter catalogue and add illustrated visual grammar --- apps/ui/app/plotter/README.md | 16 + .../app/plotter/drawings/agent-checkpoint.svg | 1 + .../app/plotter/drawings/agent-delegation.svg | 46 + .../ui/app/plotter/drawings/agent-handoff.svg | 1 + apps/ui/app/plotter/drawings/agent-memory.svg | 1 + .../app/plotter/drawings/agent-parallel.svg | 1 + .../plotter/drawings/agent-permissions.svg | 1 + .../plotter/drawings/agent-registration.svg | 71 + apps/ui/app/plotter/drawings/agent-tools.svg | 1 + .../drawings/arch-missing-keystone.svg | 1 + .../app/plotter/drawings/arch-staircase.svg | 1 + .../app/plotter/drawings/craft-dovetail.svg | 1 + .../app/plotter/drawings/craft-paper-fold.svg | 1 + apps/ui/app/plotter/drawings/craft-stitch.svg | 1 + apps/ui/app/plotter/drawings/craft-tiles.svg | 1 + apps/ui/app/plotter/drawings/craft-weave.svg | 1 + .../plotter/drawings/geo-circle-rectangle.svg | 1 + .../plotter/drawings/geo-compound-radii.svg | 1 + .../drawings/geo-interrupted-pattern.svg | 1 + .../plotter/drawings/geo-negative-space.svg | 1 + .../drawings/interface-corner-rule.svg | 1 + .../app/plotter/drawings/interface-reflow.svg | 1 + .../drawings/interface-slider-value.svg | 1 + apps/ui/app/plotter/drawings/manifest.json | 416 + .../plotter/drawings/map-converging-paths.svg | 1 + .../plotter/drawings/map-summit-contours.svg | 1 + .../plotter/drawings/mech-cam-follower.svg | 1 + .../plotter/drawings/mech-iris-aperture.svg | 35 + .../plotter/drawings/mech-latch-receiver.svg | 1 + .../plotter/drawings/mech-pulley-ratio.svg | 1 + apps/ui/app/plotter/drawings/music-chord.svg | 1 + .../app/plotter/drawings/nature-honeycomb.svg | 1 + .../plotter/drawings/nature-leaf-veins.svg | 1 + apps/ui/app/plotter/drawings/play-balance.svg | 1 + .../ui/app/plotter/drawings/play-dominoes.svg | 1 + .../drawings/software-branch-history.svg | 1 + .../plotter/drawings/software-cache-route.svg | 1 + .../drawings/time-appointment-buffers.svg | 1 + .../drawings/time-common-recurrence.svg | 1 + .../plotter/drawings/type-letter-strokes.svg | 1 + .../ui/app/plotter/drawings/type-ligature.svg | 1 + .../drawings/type-negative-monogram.svg | 1 + .../drawings/type-optical-alignment.svg | 1 + .../plotter/drawings/type-shared-glyphs.svg | 1 + apps/ui/app/plotter/engineering-studies.tsx | 3 + apps/ui/app/plotter/expanded-studies.tsx | 7459 +++++++++++++++++ .../app/plotter/grammar-comparison.module.css | 93 + apps/ui/app/plotter/grammar-comparison.tsx | 59 + apps/ui/app/plotter/illustration-gallery.tsx | 9 + apps/ui/app/plotter/illustrations.tsx | 2 + apps/ui/app/plotter/page.tsx | 8 + .../app/plotter/production-chain-hatching.tsx | 16 + .../production-chain-motion-drawing.tsx | 23 +- apps/ui/app/plotter/study-types.ts | 11 +- apps/ui/app/plotter/visual-grammar-content.ts | 68 + .../app/plotter/visual-grammar-drawings.tsx | 1579 ++++ apps/ui/app/plotter/visual-grammar.tsx | 69 + .../2026-10-31/catalog-expansion/notes.md | 25 + .../removed/absence-connectors-gap.svg | 1 + .../removed/absence-empty-position.svg | 1 + .../removed/absence-matching-slot.svg | 1 + .../removed/absence-missing-piece.svg | 1 + .../removed/absence-unbuilt-destination.svg | 1 + .../removed/arch-door-clearance.svg | 1 + .../removed/arch-proportional-window.svg | 1 + .../removed/arch-sliding-partitions.svg | 1 + .../removed/geo-shared-tangent.svg | 1 + .../removed/interface-nested-spacing.svg | 1 + .../removed/interface-shared-selection.svg | 1 + .../catalog-expansion/removed/manifest.json | 398 + .../removed/map-missing-bridge.svg | 1 + .../removed/map-road-radii.svg | 1 + .../removed/map-transit-platform.svg | 1 + .../removed/mech-scissor-linkage.svg | 1 + .../removed/music-meeting-rhythms.svg | 1 + .../removed/music-missing-beat.svg | 1 + .../removed/music-rhythm-intervals.svg | 1 + .../removed/music-tuning-fork.svg | 1 + .../removed/nature-offset-rings.svg | 1 + .../removed/nature-seed-spiral.svg | 1 + .../removed/nature-tributaries.svg | 1 + .../removed/object-camera-lens.svg | 1 + .../removed/object-hinge-panels.svg | 1 + .../removed/object-key-lock.svg | 1 + .../removed/object-umbrella-ribs.svg | 1 + .../removed/object-zipper.svg | 1 + .../removed/play-almost-fit.svg | 1 + .../catalog-expansion/removed/play-maze.svg | 1 + .../removed/play-tangram.svg | 1 + .../removed/software-common-key.svg | 1 + .../removed/software-queue-slot.svg | 1 + .../removed/software-shape-filter.svg | 1 + .../removed/time-availability-overlap.svg | 1 + .../removed/time-schedule-handoff.svg | 1 + .../removed/time-unfinished-deadline.svg | 1 + .../2026-10-31/follow-up/agent-delegation.svg | 46 + .../follow-up/before/agent-delegation.svg | 23 + .../follow-up/before/iris-aperture.svg | 1 + .../2026-10-31/follow-up/iris-aperture.svg | 35 + .../2026-10-31/follow-up/notes.md | 23 + apps/ui/scripts/build-plotter-drawings.py | 46 + apps/ui/scripts/build-plotter-grammar.py | 43 + apps/ui/scripts/plotter_svg.py | 70 + apps/ui/skills/plotter/README.md | 4 +- apps/ui/skills/plotter/SKILL.md | 10 +- .../plotter/references/approved-references.md | 5 +- .../ui/skills/plotter/references/direction.md | 35 + .../plotter/references/drawings/C01.svg | 1 + .../plotter/references/drawings/manifest.json | 9 + .../examples/agent-registration-before.svg | 43 + .../references/examples/agent-registration.md | 43 + .../examples/agent-registration.svg | 71 + .../plotter/references/grammar/arrows.svg | 4 + .../references/grammar/centerlines.svg | 1 + .../plotter/references/grammar/dimensions.svg | 1 + .../plotter/references/grammar/grids.svg | 1 + .../plotter/references/grammar/hatching.svg | 29 + .../plotter/references/grammar/outlines.svg | 71 + .../references/grammar/projections.svg | 46 + .../plotter/references/grammar/states.svg | 4 + .../plotter/references/visual-grammar.md | 81 + 121 files changed, 11216 insertions(+), 11 deletions(-) create mode 100644 apps/ui/app/plotter/drawings/agent-checkpoint.svg create mode 100644 apps/ui/app/plotter/drawings/agent-delegation.svg create mode 100644 apps/ui/app/plotter/drawings/agent-handoff.svg create mode 100644 apps/ui/app/plotter/drawings/agent-memory.svg create mode 100644 apps/ui/app/plotter/drawings/agent-parallel.svg create mode 100644 apps/ui/app/plotter/drawings/agent-permissions.svg create mode 100644 apps/ui/app/plotter/drawings/agent-registration.svg create mode 100644 apps/ui/app/plotter/drawings/agent-tools.svg create mode 100644 apps/ui/app/plotter/drawings/arch-missing-keystone.svg create mode 100644 apps/ui/app/plotter/drawings/arch-staircase.svg create mode 100644 apps/ui/app/plotter/drawings/craft-dovetail.svg create mode 100644 apps/ui/app/plotter/drawings/craft-paper-fold.svg create mode 100644 apps/ui/app/plotter/drawings/craft-stitch.svg create mode 100644 apps/ui/app/plotter/drawings/craft-tiles.svg create mode 100644 apps/ui/app/plotter/drawings/craft-weave.svg create mode 100644 apps/ui/app/plotter/drawings/geo-circle-rectangle.svg create mode 100644 apps/ui/app/plotter/drawings/geo-compound-radii.svg create mode 100644 apps/ui/app/plotter/drawings/geo-interrupted-pattern.svg create mode 100644 apps/ui/app/plotter/drawings/geo-negative-space.svg create mode 100644 apps/ui/app/plotter/drawings/interface-corner-rule.svg create mode 100644 apps/ui/app/plotter/drawings/interface-reflow.svg create mode 100644 apps/ui/app/plotter/drawings/interface-slider-value.svg create mode 100644 apps/ui/app/plotter/drawings/manifest.json create mode 100644 apps/ui/app/plotter/drawings/map-converging-paths.svg create mode 100644 apps/ui/app/plotter/drawings/map-summit-contours.svg create mode 100644 apps/ui/app/plotter/drawings/mech-cam-follower.svg create mode 100644 apps/ui/app/plotter/drawings/mech-iris-aperture.svg create mode 100644 apps/ui/app/plotter/drawings/mech-latch-receiver.svg create mode 100644 apps/ui/app/plotter/drawings/mech-pulley-ratio.svg create mode 100644 apps/ui/app/plotter/drawings/music-chord.svg create mode 100644 apps/ui/app/plotter/drawings/nature-honeycomb.svg create mode 100644 apps/ui/app/plotter/drawings/nature-leaf-veins.svg create mode 100644 apps/ui/app/plotter/drawings/play-balance.svg create mode 100644 apps/ui/app/plotter/drawings/play-dominoes.svg create mode 100644 apps/ui/app/plotter/drawings/software-branch-history.svg create mode 100644 apps/ui/app/plotter/drawings/software-cache-route.svg create mode 100644 apps/ui/app/plotter/drawings/time-appointment-buffers.svg create mode 100644 apps/ui/app/plotter/drawings/time-common-recurrence.svg create mode 100644 apps/ui/app/plotter/drawings/type-letter-strokes.svg create mode 100644 apps/ui/app/plotter/drawings/type-ligature.svg create mode 100644 apps/ui/app/plotter/drawings/type-negative-monogram.svg create mode 100644 apps/ui/app/plotter/drawings/type-optical-alignment.svg create mode 100644 apps/ui/app/plotter/drawings/type-shared-glyphs.svg create mode 100644 apps/ui/app/plotter/expanded-studies.tsx create mode 100644 apps/ui/app/plotter/grammar-comparison.module.css create mode 100644 apps/ui/app/plotter/grammar-comparison.tsx create mode 100644 apps/ui/app/plotter/production-chain-hatching.tsx create mode 100644 apps/ui/app/plotter/visual-grammar-content.ts create mode 100644 apps/ui/app/plotter/visual-grammar-drawings.tsx create mode 100644 apps/ui/app/plotter/visual-grammar.tsx create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/notes.md create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-connectors-gap.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-empty-position.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-matching-slot.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-missing-piece.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-unbuilt-destination.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/arch-door-clearance.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/arch-proportional-window.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/arch-sliding-partitions.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/geo-shared-tangent.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/interface-nested-spacing.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/interface-shared-selection.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/manifest.json create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/map-missing-bridge.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/map-road-radii.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/map-transit-platform.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/mech-scissor-linkage.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/music-meeting-rhythms.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/music-missing-beat.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/music-rhythm-intervals.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/music-tuning-fork.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/nature-offset-rings.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/nature-seed-spiral.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/nature-tributaries.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-camera-lens.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-hinge-panels.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-key-lock.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-umbrella-ribs.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-zipper.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/play-almost-fit.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/play-maze.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/play-tangram.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/software-common-key.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/software-queue-slot.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/software-shape-filter.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/time-availability-overlap.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/time-schedule-handoff.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/time-unfinished-deadline.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/follow-up/agent-delegation.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/follow-up/before/agent-delegation.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/follow-up/before/iris-aperture.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/follow-up/iris-aperture.svg create mode 100644 apps/ui/plotter-evals/2026-10-31/follow-up/notes.md create mode 100644 apps/ui/scripts/build-plotter-drawings.py create mode 100644 apps/ui/scripts/build-plotter-grammar.py create mode 100644 apps/ui/scripts/plotter_svg.py create mode 100644 apps/ui/skills/plotter/references/drawings/C01.svg create mode 100644 apps/ui/skills/plotter/references/examples/agent-registration-before.svg create mode 100644 apps/ui/skills/plotter/references/examples/agent-registration.md create mode 100644 apps/ui/skills/plotter/references/examples/agent-registration.svg create mode 100644 apps/ui/skills/plotter/references/grammar/arrows.svg create mode 100644 apps/ui/skills/plotter/references/grammar/centerlines.svg create mode 100644 apps/ui/skills/plotter/references/grammar/dimensions.svg create mode 100644 apps/ui/skills/plotter/references/grammar/grids.svg create mode 100644 apps/ui/skills/plotter/references/grammar/hatching.svg create mode 100644 apps/ui/skills/plotter/references/grammar/outlines.svg create mode 100644 apps/ui/skills/plotter/references/grammar/projections.svg create mode 100644 apps/ui/skills/plotter/references/grammar/states.svg create mode 100644 apps/ui/skills/plotter/references/visual-grammar.md diff --git a/apps/ui/app/plotter/README.md b/apps/ui/app/plotter/README.md index 36798becb1..77920e3293 100644 --- a/apps/ui/app/plotter/README.md +++ b/apps/ui/app/plotter/README.md @@ -26,6 +26,22 @@ The original SVGs, prompts, and feedback are preserved in [`../../plotter-evals/ 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. +## Expanded collection + +The initial expansion added 78 studies. After the user removed 36 entries during visual review, 42 additions remain alongside the earlier 58: **100 illustrations** in total. The user explicitly liked the revised registration, delegation, and iris drawings. Retaining the other additions does not approve them individually or change the 27 approved visual references. Existing codes and IDs are preserved without renumbering; removed sources remain in the [review archive](../../plotter-evals/2026-10-31/catalog-expansion/removed/). + +Editable sources and metadata live in [`drawings/`](drawings/). Preserve the IDs in [`drawings/manifest.json`](drawings/manifest.json); they also drive category filters and `?variant=` links. Construction and reference choices are recorded in the manifest and SVG metadata. Edit the SVG source, then regenerate the React components from the repository root: + +```sh +python3 apps/ui/scripts/build-plotter-drawings.py +``` + +The generated `expanded-studies.tsx` uses the existing drawing wrapper and gives every rendered instance unique definition IDs. Format the generated file with the repository formatter. Do not edit it directly. The [expansion notes](../../plotter-evals/2026-10-31/catalog-expansion/notes.md) record the feedback and visual review scope. + +The visual grammar uses the skill’s editable SVGs as its source. Hover, keyboard focus, or the touch toggle isolates the topic’s marks; `data-grammar-focus` identifies those elements in the source. After editing `skills/plotter/references/grammar/*.svg`, run `python3 apps/ui/scripts/build-plotter-grammar.py` from the repository root and format `visual-grammar-drawings.tsx`. The page explanations live in `visual-grammar-content.ts`. + ## Plotter skill +The visible Visual grammar section precedes the illustrations and is reachable from the top navigation at `/ui/plotter#visual-grammar`. + 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. diff --git a/apps/ui/app/plotter/drawings/agent-checkpoint.svg b/apps/ui/app/plotter/drawings/agent-checkpoint.svg new file mode 100644 index 0000000000..68333dc53d --- /dev/null +++ b/apps/ui/app/plotter/drawings/agent-checkpoint.svg @@ -0,0 +1 @@ +A state worth keepingOne selected state is preserved as a checkpoint while the wider work continues.Revision 2, catalog candidate. Reference: C01, K07. Conceptual checkpoint. Unit u = 24. A 96-unit, 4u partition of a 336-unit, 14u record is repeated unchanged below. Hatching identifies the same saved state in both positions. This illustrates capture, not a claim of fault tolerance or storage durability.WorkState 02ContinueState 02Checkpoint14u4u diff --git a/apps/ui/app/plotter/drawings/agent-delegation.svg b/apps/ui/app/plotter/drawings/agent-delegation.svg new file mode 100644 index 0000000000..cc0aeaeb85 --- /dev/null +++ b/apps/ui/app/plotter/drawings/agent-delegation.svg @@ -0,0 +1,46 @@ + + One task, two agents + A task is divided into research and drafting scopes. The two scopes retain their widths and profiles when separated and assigned to Agent 01 and Agent 02. + Study: agent-delegation, revision 2, explicitly liked by the user on 2026-10-31. Conceptual diagram. References: K07 part/whole proportions, C01 preserved fragments, C02 allocation. Unit u = 24 SVG units. The task is 14u wide; research and drafting allocations are 6u and 8u. Widths describe the illustrative partition, not effort, duration, cost, or an execution schedule. The two lower forms retain the source partition geometry, translated by (-12,156) and (12,156). The guide pairs connect the source's lower width witnesses to the assigned forms' upper width witnesses; the arrows indicate conceptual assignment, not measured trajectories. No background grid is needed. + + + + + + + + + + + + + + + + + + + + + One task + Research + Draft + Research + Draft + Agent 01 + Agent 02 + + + + + + + + + + + 14u + 6u + 8u + + diff --git a/apps/ui/app/plotter/drawings/agent-handoff.svg b/apps/ui/app/plotter/drawings/agent-handoff.svg new file mode 100644 index 0000000000..a6d03de4f1 --- /dev/null +++ b/apps/ui/app/plotter/drawings/agent-handoff.svg @@ -0,0 +1 @@ +Context, carried forwardOne continuous context passes between two agents without changing its form.Revision 2, catalog candidate. Reference: E02, I06. Conceptual handoff. A 40-unit band has paired bend radii 20 and 60. The same 64 × 24 context capsule has 8-unit side clearances in both horizontal portions. Hatching marks the handoff section, not a physical material. No serialization, timing or persistence guarantee is asserted.ContextContextAgent 01Agent 02HandoffR2040336 diff --git a/apps/ui/app/plotter/drawings/agent-memory.svg b/apps/ui/app/plotter/drawings/agent-memory.svg new file mode 100644 index 0000000000..c7e076e5ef --- /dev/null +++ b/apps/ui/app/plotter/drawings/agent-memory.svg @@ -0,0 +1 @@ +One context, two readersTwo agents address the same findings row within one shared memory.Revision 2, catalog candidate. Reference: B09, C02. Conceptual shared access. The 192-unit record is 8u wide with 48-unit, 2u rows. Both connections terminate at the same row, not duplicated stores. Route returns are R24. Hatching identifies the selected row. No concurrent consistency guarantee is depicted.Shared memoryGoalFindingsNext stepAgent 01Agent 028u2uR24 diff --git a/apps/ui/app/plotter/drawings/agent-parallel.svg b/apps/ui/app/plotter/drawings/agent-parallel.svg new file mode 100644 index 0000000000..0511f587dc --- /dev/null +++ b/apps/ui/app/plotter/drawings/agent-parallel.svg @@ -0,0 +1 @@ +Separate work, one joinThree agents finish at different points and wait for the longest task before joining.Revision 2, catalog candidate. Reference: B04, K07. Conceptual synchronization. One illustrative time unit u = 72 SVG units. Work durations are 3u, 4u and 2u; the shared join is at 4u. Hatching denotes waiting intervals only. The shared join line owns all right-edge segments. This is not a scheduler performance claim.Agent 01Agent 02Agent 03StartJoinWait4u2u diff --git a/apps/ui/app/plotter/drawings/agent-permissions.svg b/apps/ui/app/plotter/drawings/agent-permissions.svg new file mode 100644 index 0000000000..5691036d9b --- /dev/null +++ b/apps/ui/app/plotter/drawings/agent-permissions.svg @@ -0,0 +1 @@ +Only the shared scopeThe granted region is the intersection of what is requested and what is allowed.Revision 2, catalog candidate. Reference: K08, P00. Conceptual permission scope. Two 240 × 144 rounded fields overlap; the selected intersection is bounded by their actual edges, with no duplicate outlines. Unit u = 24; overlap width is 6u. Hatching means granted scope. This is an illustrative set relationship, not a policy engine or numerical permission measure.Requested scopeAllowed scopeGranted10u6u diff --git a/apps/ui/app/plotter/drawings/agent-registration.svg b/apps/ui/app/plotter/drawings/agent-registration.svg new file mode 100644 index 0000000000..0d0264275a --- /dev/null +++ b/apps/ui/app/plotter/drawings/agent-registration.svg @@ -0,0 +1,71 @@ + + An agent, registered + An identity, name, and two tools align into one registry record. The identity and tool shapes retain their sizes and horizontal positions. The record receives the example identifier agent_07. + Study: agent-registration, revision 2, explicitly liked by the user on 2026-10-31. Mode: conceptual diagram. Registration is represented as the assembly of one discoverable record from an agent definition; this does not depict authentication, permission grants, or execution. References: C01 assembly, C02 allocation, I06 identity fragments. Local unit u = 24 SVG units. Record width 14u = 336; height 4u = 96; horizontal allocation 1u inset + 2u identity + 1u gap + 5u name + 1u gap + 3u tools + 1u inset. Source and assembled fragments differ only by a vertical translation of 148 units. The assigned identifier is new text within the name allocation. No background grid or hatch is needed. + + + + + + + + + + + + + + + + + + + + + + Research agent + Read + Search + + + Identity + Name + Tools + + + + + + + + Registered agent + + + + + + + + + + + + + Research agent + Read + Search + + agent_07 + + + + + + + + + 14u + u + u + + diff --git a/apps/ui/app/plotter/drawings/agent-tools.svg b/apps/ui/app/plotter/drawings/agent-tools.svg new file mode 100644 index 0000000000..e720a31d07 --- /dev/null +++ b/apps/ui/app/plotter/drawings/agent-tools.svg @@ -0,0 +1 @@ +One matching interfaceA selected tool’s rounded end matches the agent’s common interface.Revision 2, catalog candidate. Reference: C02, P02. Conceptual tool selection. All tools have 144 × 48 footprints with R24 ends. The receiving semicircle has the same radius. The selected end reaches the receiving profile after a horizontal translation of 96 units. The drawing does not describe an actual API connector.ReadSearchWriteR24Agent 07Tool interface14432 diff --git a/apps/ui/app/plotter/drawings/arch-missing-keystone.svg b/apps/ui/app/plotter/drawings/arch-missing-keystone.svg new file mode 100644 index 0000000000..def98b48bf --- /dev/null +++ b/apps/ui/app/plotter/drawings/arch-missing-keystone.svg @@ -0,0 +1 @@ +The last piece of the archA lifted keystone preserves the geometry of the opening it will complete.Revision 2. Reference E06. Geometric assembly study. Concentric arch radii120 and84; missing20° sector spans-100° to-80°. The keystone is the exact annular sector translated upward52, with H1 identifying that part. Missing arcs show its destination; shared radial seams are not duplicated. No structural-stability assertion.R84R120Keystone240 diff --git a/apps/ui/app/plotter/drawings/arch-staircase.svg b/apps/ui/app/plotter/drawings/arch-staircase.svg new file mode 100644 index 0000000000..b3afe91950 --- /dev/null +++ b/apps/ui/app/plotter/drawings/arch-staircase.svg @@ -0,0 +1 @@ +One rise, one runSix equal rise-and-run pairs construct a staircase along one repeated slope.Revision 2. Reference C02. Geometric staircase study. u=16; every run3u=48, rise2u=32, six runs span18u=288. The diagonal joins the repeated pitch vertices; H1 identifies one triangular unit cell, not a material section. The outline is a construction profile, not a staircase specification.3u2u18uOne step diff --git a/apps/ui/app/plotter/drawings/craft-dovetail.svg b/apps/ui/app/plotter/drawings/craft-dovetail.svg new file mode 100644 index 0000000000..f8355dcca5 --- /dev/null +++ b/apps/ui/app/plotter/drawings/craft-dovetail.svg @@ -0,0 +1 @@ +A joint that holdsA wider tail and narrower neck create one complementary boundary between two pieces.Revision 2, catalog candidate. Reference: C01, P00. Illustrative dovetail in plan. Shared seam drawn once. Neck width 128, tail width 160, depth 48 SVG units; symmetric flank offset 16. Hatching identifies the projecting tongue region, not a material. No manufacturing tolerance or load claim is implied.Part APart B12816048 diff --git a/apps/ui/app/plotter/drawings/craft-paper-fold.svg b/apps/ui/app/plotter/drawings/craft-paper-fold.svg new file mode 100644 index 0000000000..eca9741a38 --- /dev/null +++ b/apps/ui/app/plotter/drawings/craft-paper-fold.svg @@ -0,0 +1 @@ +Across the creaseOne corner reflects across its diagonal crease and becomes a triangular flap.Revision 2, catalog candidate. Reference: P00, E00. Flat paper-fold study. Square side 240; u=40. Crease x+y=380 joins (160,220) and (280,100); reflection maps the original corner (160,100) to (280,220). The crease is drawn only once as the visible boundary. Hatching identifies the folded flap, and dashed edges mark the original absent corner. Arrow indicates folding direction, not a physical 3D trajectory.45°3u6u diff --git a/apps/ui/app/plotter/drawings/craft-stitch.svg b/apps/ui/app/plotter/drawings/craft-stitch.svg new file mode 100644 index 0000000000..8280b8f6c4 --- /dev/null +++ b/apps/ui/app/plotter/drawings/craft-stitch.svg @@ -0,0 +1 @@ +A seam, joinedRepeated crossed stitches bridge two edges on one shared hole pitch.Revision 2, catalog candidate. Reference: K07, C02. Conceptual front view of a stitched seam. Hole centers repeat every 48 units and sit 16 units from the joined edge. Thread segments end on the hole circumferences. One diagonal is interrupted at each crossing to preserve over/under meaning. The construction is illustrative, not a sewing specification.Edge AEdge B11248 diff --git a/apps/ui/app/plotter/drawings/craft-tiles.svg b/apps/ui/app/plotter/drawings/craft-tiles.svg new file mode 100644 index 0000000000..3828a5c775 --- /dev/null +++ b/apps/ui/app/plotter/drawings/craft-tiles.svg @@ -0,0 +1 @@ +One shared cornerFour tiles meet around a common circular opening and a constant joint width.Revision 2, catalog candidate. Reference: P00, C02. Flat tiling construction. Four 128-unit square allocations surround a 32-unit cross joint. A shared R48 circle cuts their inner corners; its remaining arcs appear only within the joint, so no locus segment retraces a tile edge. Overall extent 288 × 288. The four quarter turns place tiles in one plane and do not introduce perspective.R4832288 diff --git a/apps/ui/app/plotter/drawings/craft-weave.svg b/apps/ui/app/plotter/drawings/craft-weave.svg new file mode 100644 index 0000000000..9692c75a8c --- /dev/null +++ b/apps/ui/app/plotter/drawings/craft-weave.svg @@ -0,0 +1 @@ +Over and underOne warp and one weft reveal the alternating rule that holds a weave together.Revision 2, catalog candidate. Reference: C02, P00. Flat weave construction: 24-unit strands on 64-unit centers. Under-strand outlines stop 4 units before the crossing strand; the interruptions alternate by row and column. Hatching marks one selected crossing. This is an ordering diagram, not a fabric or strength specification.WarpWeft6424 diff --git a/apps/ui/app/plotter/drawings/geo-circle-rectangle.svg b/apps/ui/app/plotter/drawings/geo-circle-rectangle.svg new file mode 100644 index 0000000000..7fc7462778 --- /dev/null +++ b/apps/ui/app/plotter/drawings/geo-circle-rectangle.svg @@ -0,0 +1 @@ +A circle, extendedA circle and its wider rounded-rectangle state share one height while their corner radii change.Revision 2. Reference P00. Geometric construction. u=44. Initial circle radius2u=88; final rectangle6u×4u with corner radiusu. Dashed circle means previous form. The two arrows describe horizontal change, not scaling the height.R = u6u4uCircle / beforeA shared height diff --git a/apps/ui/app/plotter/drawings/geo-compound-radii.svg b/apps/ui/app/plotter/drawings/geo-compound-radii.svg new file mode 100644 index 0000000000..0c2c8e329b --- /dev/null +++ b/apps/ui/app/plotter/drawings/geo-compound-radii.svg @@ -0,0 +1 @@ +A curve from three radiiThree circular arcs share tangent directions at their joins, producing one uninterrupted curve.Revision 2. Reference P00. Geometric construction. Arc centers(104,204),(280,204),(280,284); radii112,64,144. First arc90→0°, second180→270°, third270→360°. Only selected generating radii are drawn, avoiding coincident radius segments.R112R64R144TangentThree radii · one continuous curve diff --git a/apps/ui/app/plotter/drawings/geo-interrupted-pattern.svg b/apps/ui/app/plotter/drawings/geo-interrupted-pattern.svg new file mode 100644 index 0000000000..b7f3439448 --- /dev/null +++ b/apps/ui/app/plotter/drawings/geo-interrupted-pattern.svg @@ -0,0 +1 @@ +One interval left openA field of tangent circles keeps its repeated pitch while one missing member interrupts the lower row.Revision 2. Reference P00. Geometric construction. u=48. Circle radiusu; pitch2u=96 on both axes. The absent lower middle circle uses the absent-outline convention. Outer width6u=288. No full background grid; tangent contacts carry the pattern.6u2uInterruption diff --git a/apps/ui/app/plotter/drawings/geo-negative-space.svg b/apps/ui/app/plotter/drawings/geo-negative-space.svg new file mode 100644 index 0000000000..dd0fbdffb3 --- /dev/null +++ b/apps/ui/app/plotter/drawings/geo-negative-space.svg @@ -0,0 +1 @@ +The shape between circlesTwo equal circular regions reveal an unhatched lens where their boundaries overlap.Revision 2. Reference P00. Geometric construction. u=100; two radius-u circles have centers separated byu. H1 occupies the symmetric difference of the two discs; the shared lens is clear. A single even-odd clipping path defines the two bounded regions.R = uuShared void diff --git a/apps/ui/app/plotter/drawings/interface-corner-rule.svg b/apps/ui/app/plotter/drawings/interface-corner-rule.svg new file mode 100644 index 0000000000..f49764992e --- /dev/null +++ b/apps/ui/app/plotter/drawings/interface-corner-rule.svg @@ -0,0 +1 @@ +One corner ruleThree differently proportioned components retain a common corner radius.Revision 2. Reference: P00. Geometric construction. u=24; true circular R24 corners. Widths 4u,6u,3u; heights6u,4u,2u. Generating radii meet each top-left arc.R244uR246uR243uOne radius · different proportions diff --git a/apps/ui/app/plotter/drawings/interface-reflow.svg b/apps/ui/app/plotter/drawings/interface-reflow.svg new file mode 100644 index 0000000000..3ac912336b --- /dev/null +++ b/apps/ui/app/plotter/drawings/interface-reflow.svg @@ -0,0 +1 @@ +The same modules, reflowedThree equal modules preserve their size when the available width wraps the last one onto another row.Revision 2. Reference: C02. Geometric UI construction. Each module96x56; all gaps and insets12. Outer frames336x80 and228x148. C is emphasized in both arrangements; no resizing of the modules.ABCABC336228WideNarrow diff --git a/apps/ui/app/plotter/drawings/interface-slider-value.svg b/apps/ui/app/plotter/drawings/interface-slider-value.svg new file mode 100644 index 0000000000..22a70db153 --- /dev/null +++ b/apps/ui/app/plotter/drawings/interface-slider-value.svg @@ -0,0 +1 @@ +Position becomes valueThe thumb locates sixty percent of a shared travel interval; the track clears its circular outline.Revision 2. Reference: P00. Geometric UI construction. Travel from x120 to440 is320; thumb x312 is192/320=60%. Thumb R24. Rail y220/236 intersects it at x312±sqrt(24²−8²), and rail paths stop there.60%0100R2460 diff --git a/apps/ui/app/plotter/drawings/manifest.json b/apps/ui/app/plotter/drawings/manifest.json new file mode 100644 index 0000000000..502e3ef0f2 --- /dev/null +++ b/apps/ui/app/plotter/drawings/manifest.json @@ -0,0 +1,416 @@ +[ + { + "id": "agent-registration", + "code": "AG01", + "title": "An agent, registered", + "description": "Identity, name, and tools keep their alignment as they become one registry record.", + "category": "Agents", + "file": "agent-registration.svg" + }, + { + "id": "agent-delegation", + "code": "AG02", + "title": "One task, two agents", + "description": "Research and drafting separate into complementary scopes, assigned to two agents.", + "category": "Agents", + "file": "agent-delegation.svg" + }, + { + "id": "agent-handoff", + "code": "AG03", + "title": "Context, carried forward", + "description": "One continuous context passes between two agents without changing its form.", + "category": "Agents", + "file": "agent-handoff.svg", + "reference": "E02, I06", + "construction": "Conceptual handoff. A 40-unit band has paired bend radii 20 and 60. The same 64 × 24 context capsule has 8-unit side clearances in both horizontal portions. Hatching marks the handoff section, not a physical material. No serialization, timing or persistence guarantee is asserted." + }, + { + "id": "agent-tools", + "code": "AG04", + "title": "One matching interface", + "description": "A selected tool’s rounded end matches the agent’s common interface.", + "category": "Agents", + "file": "agent-tools.svg", + "reference": "C02, P02", + "construction": "Conceptual tool selection. All tools have 144 × 48 footprints with R24 ends. The receiving semicircle has the same radius. The selected end reaches the receiving profile after a horizontal translation of 96 units. The drawing does not describe an actual API connector." + }, + { + "id": "agent-permissions", + "code": "AG05", + "title": "Only the shared scope", + "description": "The granted region is the intersection of what is requested and what is allowed.", + "category": "Agents", + "file": "agent-permissions.svg", + "reference": "K08, P00", + "construction": "Conceptual permission scope. Two 240 × 144 rounded fields overlap; the selected intersection is bounded by their actual edges, with no duplicate outlines. Unit u = 24; overlap width is 6u. Hatching means granted scope. This is an illustrative set relationship, not a policy engine or numerical permission measure." + }, + { + "id": "agent-memory", + "code": "AG06", + "title": "One context, two readers", + "description": "Two agents address the same findings row within one shared memory.", + "category": "Agents", + "file": "agent-memory.svg", + "reference": "B09, C02", + "construction": "Conceptual shared access. The 192-unit record is 8u wide with 48-unit, 2u rows. Both connections terminate at the same row, not duplicated stores. Route returns are R24. Hatching identifies the selected row. No concurrent consistency guarantee is depicted." + }, + { + "id": "agent-checkpoint", + "code": "AG07", + "title": "A state worth keeping", + "description": "One selected state is preserved as a checkpoint while the wider work continues.", + "category": "Agents", + "file": "agent-checkpoint.svg", + "reference": "C01, K07", + "construction": "Conceptual checkpoint. Unit u = 24. A 96-unit, 4u partition of a 336-unit, 14u record is repeated unchanged below. Hatching identifies the same saved state in both positions. This illustrates capture, not a claim of fault tolerance or storage durability." + }, + { + "id": "agent-parallel", + "code": "AG08", + "title": "Separate work, one join", + "description": "Three agents finish at different points and wait for the longest task before joining.", + "category": "Agents", + "file": "agent-parallel.svg", + "reference": "B04, K07", + "construction": "Conceptual synchronization. One illustrative time unit u = 72 SVG units. Work durations are 3u, 4u and 2u; the shared join is at 4u. Hatching denotes waiting intervals only. The shared join line owns all right-edge segments. This is not a scheduler performance claim." + }, + { + "id": "geo-circle-rectangle", + "code": "GE01", + "title": "A circle, extended", + "description": "A circle and its wider rounded-rectangle state share one height while their corner radii change.", + "category": "Foundations", + "file": "geo-circle-rectangle.svg", + "reference": "P00", + "construction": "Geometric construction. u=44. Initial circle radius2u=88; final rectangle6u×4u with corner radiusu. Dashed circle means previous form. The two arrows describe horizontal change, not scaling the height." + }, + { + "id": "geo-compound-radii", + "code": "GE03", + "title": "A curve from three radii", + "description": "Three circular arcs share tangent directions at their joins, producing one uninterrupted curve.", + "category": "Foundations", + "file": "geo-compound-radii.svg", + "reference": "P00", + "construction": "Geometric construction. Arc centers(104,204),(280,204),(280,284); radii112,64,144. First arc90→0°, second180→270°, third270→360°. Only selected generating radii are drawn, avoiding coincident radius segments." + }, + { + "id": "geo-negative-space", + "code": "GE04", + "title": "The shape between circles", + "description": "Two equal circular regions reveal an unhatched lens where their boundaries overlap.", + "category": "Foundations", + "file": "geo-negative-space.svg", + "reference": "P00", + "construction": "Geometric construction. u=100; two radius-u circles have centers separated byu. H1 occupies the symmetric difference of the two discs; the shared lens is clear. A single even-odd clipping path defines the two bounded regions." + }, + { + "id": "geo-interrupted-pattern", + "code": "GE05", + "title": "One interval left open", + "description": "A field of tangent circles keeps its repeated pitch while one missing member interrupts the lower row.", + "category": "Foundations", + "file": "geo-interrupted-pattern.svg", + "reference": "P00", + "construction": "Geometric construction. u=48. Circle radiusu; pitch2u=96 on both axes. The absent lower middle circle uses the absent-outline convention. Outer width6u=288. No full background grid; tangent contacts carry the pattern." + }, + { + "id": "mech-iris-aperture", + "code": "ME01", + "title": "Six blades, one aperture", + "description": "Six repeated edges shape a single opening. Hatching identifies one visible blade.", + "category": "Mechanisms", + "file": "mech-iris-aperture.svg" + }, + { + "id": "mech-pulley-ratio", + "code": "ME02", + "title": "One belt, two rotations", + "description": "A shared belt joins two pulley radii in a 2:1 relation.", + "category": "Mechanisms", + "file": "mech-pulley-ratio.svg", + "reference": "P00", + "construction": "Conceptual mechanism study. Contact radii72 and36, center distance208. Contact normals derive from(R-r)/distance. The belt boundary owns its circular contact arcs; no pulley outline duplicates them. Inner circles are subordinate details. Ideal no-slip ratio only; no hardware or load specification." + }, + { + "id": "mech-cam-follower", + "code": "ME04", + "title": "An offset becomes a lift", + "description": "An eccentric circular cam meets a guided roller, relating rotation to a vertical displacement.", + "category": "Mechanisms", + "file": "mech-cam-follower.svg", + "reference": "P00", + "construction": "Conceptual mechanism, one pose. Cam center(260,244),radius76; shaft(236,244), eccentricity24. Roller radius12 atx280; y derived from center distance88, so contact is tangent. Rotation is about the shaft, lift is guided vertically. No dynamic, contact-force, or manufacturability claim." + }, + { + "id": "mech-latch-receiver", + "code": "ME05", + "title": "A profile finds its receiver", + "description": "A rounded bolt approaches the matching receiver, with its destination and clearance visible.", + "category": "Mechanisms", + "file": "mech-latch-receiver.svg", + "reference": "P00", + "construction": "Conceptual fit study. Bolt24-radius nose; receiver28-radius cavity gives4 radial clearance. Source bolt nose center(304,220); proposed destination center(392,220), translation88. Absent lines show destination only. H1 identifies the receiver body, not a material section. No hardware specification." + }, + { + "id": "arch-staircase", + "code": "AR02", + "title": "One rise, one run", + "description": "Six equal rise-and-run pairs construct a staircase along one repeated slope.", + "category": "Architecture", + "file": "arch-staircase.svg", + "reference": "C02", + "construction": "Geometric staircase study. u=16; every run3u=48, rise2u=32, six runs span18u=288. The diagonal joins the repeated pitch vertices; H1 identifies one triangular unit cell, not a material section. The outline is a construction profile, not a staircase specification." + }, + { + "id": "arch-missing-keystone", + "code": "AR03", + "title": "The last piece of the arch", + "description": "A lifted keystone preserves the geometry of the opening it will complete.", + "category": "Architecture", + "file": "arch-missing-keystone.svg", + "reference": "E06", + "construction": "Geometric assembly study. Concentric arch radii120 and84; missing20° sector spans-100° to-80°. The keystone is the exact annular sector translated upward52, with H1 identifying that part. Missing arcs show its destination; shared radial seams are not duplicated. No structural-stability assertion." + }, + { + "id": "nature-leaf-veins", + "code": "NA01", + "title": "A branching leaf", + "description": "Paired veins follow a repeated pitch within a leaf formed by two intersecting arcs.", + "category": "Nature", + "file": "nature-leaf-veins.svg", + "reference": "P00", + "construction": "u=40. Lens centers (330,220) and (230,220), radii130, tips(280,100),(280,340); maximum width160=4u. Vein endpoints have vertical pitch40. The emphasized pair joins the same midrib. Stylized leaf, not a botanical specimen." + }, + { + "id": "nature-honeycomb", + "code": "NA02", + "title": "One edge, two cells", + "description": "A selected hexagonal cell shares each of its walls with the surrounding six cells.", + "category": "Nature", + "file": "nature-honeycomb.svg", + "reference": "P00", + "construction": "Seven regular hexagons of circumradius48. Neighbor centers lie at sqrt(3)*48. Shared edges are serialized once; central-cell edges own primary role. H1 denotes the selected cell. r=48, total width288=6r." + }, + { + "id": "music-chord", + "code": "MU02", + "title": "Three notes, one onset", + "description": "Three separately placed pitches align vertically on one shared onset to form a chord.", + "category": "Music", + "file": "music-chord.svg", + "reference": "C01", + "construction": "The source heads at y240,208,176 retain those staff positions in the final chord atx424. Staff spacing16. Every staff segment clears notehead interiors; the chord stem is drawn once. This is a pitch/alignment illustration, not a complete notated score or named harmony." + }, + { + "id": "map-summit-contours", + "code": "MP04", + "title": "The summit in contours", + "description": "Nested elevation lines tighten around one marked summit, with a consistent interval between levels.", + "category": "Maps", + "file": "map-summit-contours.svg", + "reference": "P00", + "construction": "Five illustrative polar contours carry levels100,125,150,175,200. Radii28,50,74,98,122 with deterministic cosine/sine modulation and small center shifts. A plus marks the summit inside the200 contour. Each path deliberately stops around its text label; no masking patch hides linework. These are invented elevation units, not a surveyed location." + }, + { + "id": "map-converging-paths", + "code": "MP05", + "title": "Different paths, one place", + "description": "Three routes join one final stretch before reaching the same destination.", + "category": "Maps", + "file": "map-converging-paths.svg", + "reference": "E02", + "construction": "The central route is primary; two secondary approaches use R48 outside bends and R24 merging bends. Shared final segment x288..408 at y220 is drawn once. The destination ring is reached at its left tangent x408. Conceptual map topology, not a surveyed road network." + }, + { + "id": "type-letter-strokes", + "code": "TY01", + "title": "A from three strokes", + "description": "Two parallel-sided diagonal strokes and one horizontal stroke make an outlined capital A; the crossbar is identified by hatching.", + "category": "Typography", + "file": "type-letter-strokes.svg", + "reference": "T01", + "construction": "Borrow T01’s outlined glyph, counter and named construction. Local u = 40 SVG units; cap height = 6u; each sloping stem has 40 units of horizontal width and constant normal width 40 / sqrt(1 + 0.45²). The crossbar spans y252–280. This is a constructed glyph, not a production font." + }, + { + "id": "type-shared-glyphs", + "code": "TY02", + "title": "One arch, three glyphs", + "description": "A shared circular shoulder and a constant stem width generate n, h and m, with the h selected to show the construction.", + "category": "Typography", + "file": "type-shared-glyphs.svg", + "reference": "T01", + "construction": "Borrow T01’s circular counter and stem relationship. Local u = 16 SVG units; outer shoulders R40 and inner shoulders R24 differ by u; ascender extension is 4u. The overlapping outer m shoulder arcs meet at their calculated intersection (400,212). The letters are conceptual geometric glyphs." + }, + { + "id": "type-ligature", + "code": "TY03", + "title": "The joining stroke", + "description": "The f and i share one continuous crossbar, joining the two glyphs into a single constructed ligature.", + "category": "Typography", + "file": "type-ligature.svg", + "reference": "T01", + "construction": "Borrow T01’s outlined letter and circular counter construction. Local u = 28 SVG units; the hook uses concentric R56 and R28 arcs, both vertical stems and the common crossbar are u wide, and the i dot has diameter u. Hatching identifies the bar that unites the forms." + }, + { + "id": "type-negative-monogram", + "code": "TY04", + "title": "A monogram in the void", + "description": "An M is defined by the transparent space within a hatched field; its straight stems and surrounding margin follow the same unit.", + "category": "Typography", + "file": "type-negative-monogram.svg", + "reference": "T02", + "construction": "Borrow T02’s mark, negative space and local construction unit. Local u = 32 SVG units; the outer field is 8u square; each straight stem and the nearest field inset are u. The parallel diagonal strokes are approximately one unit wide measured normally. Hatching belongs only to the surrounding field, never the letter." + }, + { + "id": "type-optical-alignment", + "code": "TY05", + "title": "The center you see", + "description": "A rightward shift brings a triangle’s area centroid to the center of its frame, providing a geometric starting point for optical alignment.", + "category": "Typography", + "file": "type-optical-alignment.svg", + "reference": "T02", + "construction": "Borrow T02’s bounded mark and annotated construction. Both frames are 144 units square; triangles are 90 units wide and 108 high. The first uses bounding-box alignment; the second moves right by 90 / 6 = 15 units so its area centroid is centered. Hatching denotes the measured area. Final optical balance remains a visual judgement." + }, + { + "id": "interface-corner-rule", + "code": "UI01", + "title": "One corner rule", + "description": "Three differently proportioned components retain a common corner radius.", + "category": "Interfaces", + "file": "interface-corner-rule.svg", + "reference": "P00", + "construction": "Geometric construction. u=24; true circular R24 corners. Widths 4u,6u,3u; heights6u,4u,2u. Generating radii meet each top-left arc." + }, + { + "id": "interface-reflow", + "code": "UI02", + "title": "The same modules, reflowed", + "description": "Three equal modules preserve their size when the available width wraps the last one onto another row.", + "category": "Interfaces", + "file": "interface-reflow.svg", + "reference": "C02", + "construction": "Geometric UI construction. Each module96x56; all gaps and insets12. Outer frames336x80 and228x148. C is emphasized in both arrangements; no resizing of the modules." + }, + { + "id": "interface-slider-value", + "code": "UI03", + "title": "Position becomes value", + "description": "The thumb locates sixty percent of a shared travel interval; the track clears its circular outline.", + "category": "Interfaces", + "file": "interface-slider-value.svg", + "reference": "P00", + "construction": "Geometric UI construction. Travel from x120 to440 is320; thumb x312 is192/320=60%. Thumb R24. Rail y220/236 intersects it at x312±sqrt(24²−8²), and rail paths stop there." + }, + { + "id": "software-cache-route", + "code": "SW02", + "title": "A shorter return", + "description": "A cached response turns back at the near bend while the longer path continues to the origin.", + "category": "Systems", + "file": "software-cache-route.svg", + "reference": "E02", + "construction": "Conceptual route, not network architecture. u=84; cache x280 is2u from x112, origin x448 is4u. All corners R24. Shared request/return segments are drawn once. Distances show the analogy only, not latency or network hops." + }, + { + "id": "software-branch-history", + "code": "SW03", + "title": "A history comes together", + "description": "One branch departs from a shared history, carries its own revision, and joins the main line again.", + "category": "Systems", + "file": "software-branch-history.svg", + "reference": "B04", + "construction": "Conceptual version history. u=24; routing corners R24, fork-to-merge span240. Circles are revision points; paths are ancestry. Main and branch intersect only at declared fork/merge. Spatial distances are illustrative, not time or commit count." + }, + { + "id": "time-appointment-buffers", + "code": "TM02", + "title": "Room around an appointment", + "description": "An hour-long appointment reserves fifteen minutes of buffer time on either side.", + "category": "Scheduling", + "file": "time-appointment-buffers.svg", + "reference": "K07", + "construction": "Conceptual schedule. Borrowed the user-liked K09/buffers hierarchy: primary appointment, subordinate buffers, boundary witnesses and total reserved interval. Scale4 SVG units/minute; widths60,240,60 correspond15,60,15 minutes. Example10:00–11:00 appointment; reserved09:45–11:15. Hatch means buffer time." + }, + { + "id": "time-common-recurrence", + "code": "TM03", + "title": "The same interval, again", + "description": "Four occurrences align to one repeated seven-day interval.", + "category": "Scheduling", + "file": "time-common-recurrence.svg", + "reference": "B04", + "construction": "Conceptual recurrence. Event centers136,232,328,424 share a96-unit pitch representing seven days. Tiles64x64. The common baseline is the time axis; stems locate each occurrence. No month or real calendar date is asserted." + }, + { + "id": "craft-weave", + "code": "CR01", + "title": "Over and under", + "description": "One warp and one weft reveal the alternating rule that holds a weave together.", + "category": "Craft", + "file": "craft-weave.svg", + "reference": "C02, P00", + "construction": "Flat weave construction: 24-unit strands on 64-unit centers. Under-strand outlines stop 4 units before the crossing strand; the interruptions alternate by row and column. Hatching marks one selected crossing. This is an ordering diagram, not a fabric or strength specification." + }, + { + "id": "craft-stitch", + "code": "CR02", + "title": "A seam, joined", + "description": "Repeated crossed stitches bridge two edges on one shared hole pitch.", + "category": "Craft", + "file": "craft-stitch.svg", + "reference": "K07, C02", + "construction": "Conceptual front view of a stitched seam. Hole centers repeat every 48 units and sit 16 units from the joined edge. Thread segments end on the hole circumferences. One diagonal is interrupted at each crossing to preserve over/under meaning. The construction is illustrative, not a sewing specification." + }, + { + "id": "craft-dovetail", + "code": "CR03", + "title": "A joint that holds", + "description": "A wider tail and narrower neck create one complementary boundary between two pieces.", + "category": "Craft", + "file": "craft-dovetail.svg", + "reference": "C01, P00", + "construction": "Illustrative dovetail in plan. Shared seam drawn once. Neck width 128, tail width 160, depth 48 SVG units; symmetric flank offset 16. Hatching identifies the projecting tongue region, not a material. No manufacturing tolerance or load claim is implied." + }, + { + "id": "craft-paper-fold", + "code": "CR04", + "title": "Across the crease", + "description": "One corner reflects across its diagonal crease and becomes a triangular flap.", + "category": "Craft", + "file": "craft-paper-fold.svg", + "reference": "P00, E00", + "construction": "Flat paper-fold study. Square side 240; u=40. Crease x+y=380 joins (160,220) and (280,100); reflection maps the original corner (160,100) to (280,220). The crease is drawn only once as the visible boundary. Hatching identifies the folded flap, and dashed edges mark the original absent corner. Arrow indicates folding direction, not a physical 3D trajectory." + }, + { + "id": "craft-tiles", + "code": "CR05", + "title": "One shared corner", + "description": "Four tiles meet around a common circular opening and a constant joint width.", + "category": "Craft", + "file": "craft-tiles.svg", + "reference": "P00, C02", + "construction": "Flat tiling construction. Four 128-unit square allocations surround a 32-unit cross joint. A shared R48 circle cuts their inner corners; its remaining arcs appear only within the joint, so no locus segment retraces a tile edge. Overall extent 288 × 288. The four quarter turns place tiles in one plane and do not introduce perspective." + }, + { + "id": "play-dominoes", + "code": "PL03", + "title": "One piece short", + "description": "The pip sequence continues across one absent domino, leaving its position unmistakable.", + "category": "Play", + "file": "play-dominoes.svg", + "reference": "E00, K07", + "construction": "Flat domino sequence. Each piece is48×96 with72-unit pitch; u=24. Filled circles are the declared domino-pip convention. Visible pairs are1/2,2/3,4/5,5/6; the missing3/4 piece is absent rather than hidden. No falling motion is depicted." + }, + { + "id": "play-balance", + "code": "PL04", + "title": "Unequal arms, equal moments", + "description": "Three mass units on the shorter arm balance two on the longer arm.", + "category": "Play", + "file": "play-balance.svg", + "reference": "P03, P00", + "construction": "Conceptual ideal lever. Fulcrum x248; weight centersx152 and392 give arms96 and144, withu=48. Identical72×24 blocks represent one mass unitm. Hatching identifies one equal unit on each side. Geometry illustrates equal moments only, not structural capacity or real-world calibration." + } +] diff --git a/apps/ui/app/plotter/drawings/map-converging-paths.svg b/apps/ui/app/plotter/drawings/map-converging-paths.svg new file mode 100644 index 0000000000..a74a4a9405 --- /dev/null +++ b/apps/ui/app/plotter/drawings/map-converging-paths.svg @@ -0,0 +1 @@ +Different paths, one placeThree routes join one final stretch before reaching the same destination.Mode: conceptual construction. Reference: E02. The central route is primary; two secondary approaches use R48 outside bends and R24 merging bends. Shared final segment x288..408 at y220 is drawn once. The destination ring is reached at its left tangent x408. Conceptual map topology, not a surveyed road network.DestinationR48Shared routeABC diff --git a/apps/ui/app/plotter/drawings/map-summit-contours.svg b/apps/ui/app/plotter/drawings/map-summit-contours.svg new file mode 100644 index 0000000000..4f52b54e75 --- /dev/null +++ b/apps/ui/app/plotter/drawings/map-summit-contours.svg @@ -0,0 +1 @@ +The summit in contoursNested elevation lines tighten around one marked summit, with a consistent interval between levels.Mode: conceptual construction. Reference: P00. Five illustrative polar contours carry levels100,125,150,175,200. Radii28,50,74,98,122 with deterministic cosine/sine modulation and small center shifts. A plus marks the summit inside the200 contour. Each path deliberately stops around its text label; no masking patch hides linework. These are invented elevation units, not a surveyed location.200175150125100SummitContour interval / 25 diff --git a/apps/ui/app/plotter/drawings/mech-cam-follower.svg b/apps/ui/app/plotter/drawings/mech-cam-follower.svg new file mode 100644 index 0000000000..7862730b3f --- /dev/null +++ b/apps/ui/app/plotter/drawings/mech-cam-follower.svg @@ -0,0 +1 @@ +An offset becomes a liftAn eccentric circular cam meets a guided roller, relating rotation to a vertical displacement.Revision 2. Reference P00. Conceptual mechanism, one pose. Cam center(260,244),radius76; shaft(236,244), eccentricity24. Roller radius12 atx280; y derived from center distance88, so contact is tangent. Rotation is about the shaft, lift is guided vertically. No dynamic, contact-force, or manufacturability claim.e = 24R76RotationLiftFollower diff --git a/apps/ui/app/plotter/drawings/mech-iris-aperture.svg b/apps/ui/app/plotter/drawings/mech-iris-aperture.svg new file mode 100644 index 0000000000..0b778483a2 --- /dev/null +++ b/apps/ui/app/plotter/drawings/mech-iris-aperture.svg @@ -0,0 +1,35 @@ + + Six blades, one aperture + Six repeated blade edges are tangent to one generating circle, leaving a hexagonal aperture. Hatching identifies the visible region of one blade. + Study: mech-iris-aperture, revision 2, explicitly liked by the user on 2026-10-31. Mode: geometric construction inspired by an iris aperture. Reference: P00 circle, tangency, hierarchy, bounded hatch. C=(280,220); R=120; r=40; u=40 SVG units. For normals n at multiples of 60 degrees and tangent t=(-n.y,n.x), blade edges lie on n dot (P-C)=r. Their outer endpoints are C+r*n+sqrt(R*R-r*r)*t. The six opening vertices lie on radius r/cos(30 degrees), at angles 30+60i degrees. Each visible edge runs from the previous opening vertex to its outer endpoint. H1 hatching denotes one visible blade region, bounded by adjacent edges and the outer circle; it is not material or shading. This illustrative layout does not assert pivot positions, blade thickness, hidden overlap geometry, or verified mechanical motion. The axes and generating circle explain the repetition without a background grid. + + + + + + + + + + + + + + + + + + + + + + + + u + 6u + + + Aperture + Blade 01 + + diff --git a/apps/ui/app/plotter/drawings/mech-latch-receiver.svg b/apps/ui/app/plotter/drawings/mech-latch-receiver.svg new file mode 100644 index 0000000000..4f80607915 --- /dev/null +++ b/apps/ui/app/plotter/drawings/mech-latch-receiver.svg @@ -0,0 +1 @@ +A profile finds its receiverA rounded bolt approaches the matching receiver, with its destination and clearance visible.Revision 2. Reference P00. Conceptual fit study. Bolt24-radius nose; receiver28-radius cavity gives4 radial clearance. Source bolt nose center(304,220); proposed destination center(392,220), translation88. Absent lines show destination only. H1 identifies the receiver body, not a material section. No hardware specification.R244 clearanceBoltReceiver216 diff --git a/apps/ui/app/plotter/drawings/mech-pulley-ratio.svg b/apps/ui/app/plotter/drawings/mech-pulley-ratio.svg new file mode 100644 index 0000000000..2085857250 --- /dev/null +++ b/apps/ui/app/plotter/drawings/mech-pulley-ratio.svg @@ -0,0 +1 @@ +One belt, two rotationsA shared belt joins two pulley radii in a 2:1 relation.Revision 2. Reference P00. Conceptual mechanism study. Contact radii72 and36, center distance208. Contact normals derive from(R-r)/distance. The belt boundary owns its circular contact arcs; no pulley outline duplicates them. Inner circles are subordinate details. Ideal no-slip ratio only; no hardware or load specification.1 turn2 turns2rr208 diff --git a/apps/ui/app/plotter/drawings/music-chord.svg b/apps/ui/app/plotter/drawings/music-chord.svg new file mode 100644 index 0000000000..48d61be005 --- /dev/null +++ b/apps/ui/app/plotter/drawings/music-chord.svg @@ -0,0 +1 @@ +Three notes, one onsetThree separately placed pitches align vertically on one shared onset to form a chord.Mode: conceptual construction. Reference: C01. The source heads at y240,208,176 retain those staff positions in the final chord atx424. Staff spacing16. Every staff segment clears notehead interiors; the chord stem is drawn once. This is a pitch/alignment illustration, not a complete notated score or named harmony.SeparateTogetherThree pitches diff --git a/apps/ui/app/plotter/drawings/nature-honeycomb.svg b/apps/ui/app/plotter/drawings/nature-honeycomb.svg new file mode 100644 index 0000000000..15aa73b1ef --- /dev/null +++ b/apps/ui/app/plotter/drawings/nature-honeycomb.svg @@ -0,0 +1 @@ +One edge, two cellsA selected hexagonal cell shares each of its walls with the surrounding six cells.Mode: conceptual construction. Reference: P00. Seven regular hexagons of circumradius48. Neighbor centers lie at sqrt(3)*48. Shared edges are serialized once; central-cell edges own primary role. H1 denotes the selected cell. r=48, total width288=6r.Shared wall6rr diff --git a/apps/ui/app/plotter/drawings/nature-leaf-veins.svg b/apps/ui/app/plotter/drawings/nature-leaf-veins.svg new file mode 100644 index 0000000000..e26da7c2f0 --- /dev/null +++ b/apps/ui/app/plotter/drawings/nature-leaf-veins.svg @@ -0,0 +1 @@ +A branching leafPaired veins follow a repeated pitch within a leaf formed by two intersecting arcs.Mode: conceptual construction. Reference: P00. u=40. Lens centers (330,220) and (230,220), radii130, tips(280,100),(280,340); maximum width160=4u. Vein endpoints have vertical pitch40. The emphasized pair joins the same midrib. Stylized leaf, not a botanical specimen.Midribu4u diff --git a/apps/ui/app/plotter/drawings/play-balance.svg b/apps/ui/app/plotter/drawings/play-balance.svg new file mode 100644 index 0000000000..f280aca154 --- /dev/null +++ b/apps/ui/app/plotter/drawings/play-balance.svg @@ -0,0 +1 @@ +Unequal arms, equal momentsThree mass units on the shorter arm balance two on the longer arm.Revision 2, catalog candidate. Reference: P03, P00. Conceptual ideal lever. Fulcrum x248; weight centersx152 and392 give arms96 and144, withu=48. Identical72×24 blocks represent one mass unitm. Hatching identifies one equal unit on each side. Geometry illustrates equal moments only, not structural capacity or real-world calibration.2u3u3m2m3m × 2u = 2m × 3u diff --git a/apps/ui/app/plotter/drawings/play-dominoes.svg b/apps/ui/app/plotter/drawings/play-dominoes.svg new file mode 100644 index 0000000000..faba03c807 --- /dev/null +++ b/apps/ui/app/plotter/drawings/play-dominoes.svg @@ -0,0 +1 @@ +One piece shortThe pip sequence continues across one absent domino, leaving its position unmistakable.Revision 2, catalog candidate. Reference: E00, K07. Flat domino sequence. Each piece is48×96 with72-unit pitch; u=24. Filled circles are the declared domino-pip convention. Visible pairs are1/2,2/3,4/5,5/6; the missing3/4 piece is absent rather than hidden. No falling motion is depicted.Missing2u3u diff --git a/apps/ui/app/plotter/drawings/software-branch-history.svg b/apps/ui/app/plotter/drawings/software-branch-history.svg new file mode 100644 index 0000000000..2bbf8b9c16 --- /dev/null +++ b/apps/ui/app/plotter/drawings/software-branch-history.svg @@ -0,0 +1 @@ +A history comes togetherOne branch departs from a shared history, carries its own revision, and joins the main line again.Revision 2. Reference: B04. Conceptual version history. u=24; routing corners R24, fork-to-merge span240. Circles are revision points; paths are ancestry. Main and branch intersect only at declared fork/merge. Spatial distances are illustrative, not time or commit count.R24BranchMainForkMerge10u diff --git a/apps/ui/app/plotter/drawings/software-cache-route.svg b/apps/ui/app/plotter/drawings/software-cache-route.svg new file mode 100644 index 0000000000..ffa07875e5 --- /dev/null +++ b/apps/ui/app/plotter/drawings/software-cache-route.svg @@ -0,0 +1 @@ +A shorter returnA cached response turns back at the near bend while the longer path continues to the origin.Revision 2. Reference: E02. Conceptual route, not network architecture. u=84; cache x280 is2u from x112, origin x448 is4u. All corners R24. Shared request/return segments are drawn once. Distances show the analogy only, not latency or network hops.CacheOriginR24RequestResponse2u4u diff --git a/apps/ui/app/plotter/drawings/time-appointment-buffers.svg b/apps/ui/app/plotter/drawings/time-appointment-buffers.svg new file mode 100644 index 0000000000..a02e3f6e11 --- /dev/null +++ b/apps/ui/app/plotter/drawings/time-appointment-buffers.svg @@ -0,0 +1 @@ +Room around an appointmentAn hour-long appointment reserves fifteen minutes of buffer time on either side.Revision 2. Reference: K07. Conceptual schedule. Borrowed the user-liked K09/buffers hierarchy: primary appointment, subordinate buffers, boundary witnesses and total reserved interval. Scale4 SVG units/minute; widths60,240,60 correspond15,60,15 minutes. Example10:00–11:00 appointment; reserved09:45–11:15. Hatch means buffer time.15 min60 min15 minBuffer beforeAppointmentBuffer after09:4510:0011:0011:1590 min reserved diff --git a/apps/ui/app/plotter/drawings/time-common-recurrence.svg b/apps/ui/app/plotter/drawings/time-common-recurrence.svg new file mode 100644 index 0000000000..9909a2a4c6 --- /dev/null +++ b/apps/ui/app/plotter/drawings/time-common-recurrence.svg @@ -0,0 +1 @@ +The same interval, againFour occurrences align to one repeated seven-day interval.Revision 2. Reference: B04. Conceptual recurrence. Event centers136,232,328,424 share a96-unit pitch representing seven days. Tiles64x64. The common baseline is the time axis; stems locate each occurrence. No month or real calendar date is asserted.Event01Event02Event03Event047 days7 days7 daysOne recurring interval diff --git a/apps/ui/app/plotter/drawings/type-letter-strokes.svg b/apps/ui/app/plotter/drawings/type-letter-strokes.svg new file mode 100644 index 0000000000..d2d3a9a084 --- /dev/null +++ b/apps/ui/app/plotter/drawings/type-letter-strokes.svg @@ -0,0 +1 @@ +A from three strokesTwo parallel-sided diagonal strokes and one horizontal stroke make an outlined capital A; the crossbar is identified by hatching.Mode: conceptual construction. Reference: T01. Borrow T01’s outlined glyph, counter and named construction. Local u = 40 SVG units; cap height = 6u; each sloping stem has 40 units of horizontal width and constant normal width 40 / sqrt(1 + 0.45²). The crossbar spans y252–280. This is a constructed glyph, not a production font.6uuCrossbar diff --git a/apps/ui/app/plotter/drawings/type-ligature.svg b/apps/ui/app/plotter/drawings/type-ligature.svg new file mode 100644 index 0000000000..db5daccd32 --- /dev/null +++ b/apps/ui/app/plotter/drawings/type-ligature.svg @@ -0,0 +1 @@ +The joining strokeThe f and i share one continuous crossbar, joining the two glyphs into a single constructed ligature.Mode: conceptual construction. Reference: T01. Borrow T01’s outlined letter and circular counter construction. Local u = 28 SVG units; the hook uses concentric R56 and R28 arcs, both vertical stems and the common crossbar are u wide, and the i dot has diameter u. Hatching identifies the bar that unites the forms.R56Shared strokeu diff --git a/apps/ui/app/plotter/drawings/type-negative-monogram.svg b/apps/ui/app/plotter/drawings/type-negative-monogram.svg new file mode 100644 index 0000000000..ba762da40a --- /dev/null +++ b/apps/ui/app/plotter/drawings/type-negative-monogram.svg @@ -0,0 +1 @@ +A monogram in the voidAn M is defined by the transparent space within a hatched field; its straight stems and surrounding margin follow the same unit.Mode: conceptual construction. Reference: T02. Borrow T02’s mark, negative space and local construction unit. Local u = 32 SVG units; the outer field is 8u square; each straight stem and the nearest field inset are u. The parallel diagonal strokes are approximately one unit wide measured normally. Hatching belongs only to the surrounding field, never the letter.8uuu diff --git a/apps/ui/app/plotter/drawings/type-optical-alignment.svg b/apps/ui/app/plotter/drawings/type-optical-alignment.svg new file mode 100644 index 0000000000..d078d7e93d --- /dev/null +++ b/apps/ui/app/plotter/drawings/type-optical-alignment.svg @@ -0,0 +1 @@ +The center you seeA rightward shift brings a triangle’s area centroid to the center of its frame, providing a geometric starting point for optical alignment.Mode: conceptual construction. Reference: T02. Borrow T02’s bounded mark and annotated construction. Both frames are 144 units square; triangles are 90 units wide and 108 high. The first uses bounding-box alignment; the second moves right by 90 / 6 = 15 units so its area centroid is centered. Hatching denotes the measured area. Final optical balance remains a visual judgement.BoundsAreaδδ = width / 6 diff --git a/apps/ui/app/plotter/drawings/type-shared-glyphs.svg b/apps/ui/app/plotter/drawings/type-shared-glyphs.svg new file mode 100644 index 0000000000..e52a084c7a --- /dev/null +++ b/apps/ui/app/plotter/drawings/type-shared-glyphs.svg @@ -0,0 +1 @@ +One arch, three glyphsA shared circular shoulder and a constant stem width generate n, h and m, with the h selected to show the construction.Mode: conceptual construction. Reference: T01. Borrow T01’s circular counter and stem relationship. Local u = 16 SVG units; outer shoulders R40 and inner shoulders R24 differ by u; ascender extension is 4u. The overlapping outer m shoulder arcs meet at their calculated intersection (400,212). The letters are conceptual geometric glyphs.R40u4u diff --git a/apps/ui/app/plotter/engineering-studies.tsx b/apps/ui/app/plotter/engineering-studies.tsx index 94393c7ec0..8c9fb7a0ff 100644 --- a/apps/ui/app/plotter/engineering-studies.tsx +++ b/apps/ui/app/plotter/engineering-studies.tsx @@ -1,4 +1,5 @@ import { BlueprintDrawing } from "./blueprint-drawing"; +import { ProductionChainHatching } from "./production-chain-hatching"; import type { IllustrationStudy } from "./study-types"; type GridBoundary = { @@ -239,6 +240,8 @@ export function ProductionChainStudy() { ))} + + diff --git a/apps/ui/app/plotter/expanded-studies.tsx b/apps/ui/app/plotter/expanded-studies.tsx new file mode 100644 index 0000000000..a8fca38581 --- /dev/null +++ b/apps/ui/app/plotter/expanded-studies.tsx @@ -0,0 +1,7459 @@ +// Generated from drawings/*.svg by scripts/build-plotter-drawings.py. Edit the SVG sources. +import { useId } from "react"; +import { BlueprintDrawing } from "./blueprint-drawing"; +import type { IllustrationStudy } from "./study-types"; + +function DrawingAgentRegistration() { + return ( + + + + + + + + + + + + + + + + + + + + + {"Research agent"} + + + {"Read"} + + + {"Search"} + + + + + {"Identity"} + + + {"Name"} + + + {"Tools"} + + + + + + + + {"Registered agent"} + + + + + + + + + + + + + + {"Research agent"} + + + {"Read"} + + + {"Search"} + + + + {"agent_07"} + + + + + + + + + {"14u"} + + + {"u"} + + + {"u"} + + + + + ); +} + +function DrawingAgentDelegation() { + return ( + + + + + + + + + + + + + + + + + + + {"One task"} + + + {"Research"} + + + {"Draft"} + + + {"Research"} + + + {"Draft"} + + + {"Agent 01"} + + + {"Agent 02"} + + + + + + + + + + + {"14u"} + + + {"6u"} + + + {"8u"} + + + + + ); +} + +function DrawingAgentHandoff() { + const id = useId(); + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {"Context"} + + + + {"Context"} + + + {"Agent 01"} + + + {"Agent 02"} + + + + {"Handoff"} + + + + + {"R20"} + + + + {"40"} + + + + + {"336"} + + + + + + + ); +} + +function DrawingAgentTools() { + return ( + + + + + + {"Read"} + + + + {"Search"} + + + + {"Write"} + + + + + + + + {"R24"} + + + {"Agent 07"} + + + {"Tool interface"} + + + + + {"144"} + + + + {"32"} + + + + ); +} + +function DrawingAgentPermissions() { + const id = useId(); + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {"Requested scope"} + + + {"Allowed scope"} + + + + {"Granted"} + + + + + {"10u"} + + + + {"6u"} + + + + + ); +} + +function DrawingAgentMemory() { + const id = useId(); + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {"Shared memory"} + + + {"Goal"} + + + {"Findings"} + + + {"Next step"} + + + + + {"Agent 01"} + + + + {"Agent 02"} + + + + + + + + {"8u"} + + + + {"2u"} + + + + {"R24"} + + + + + ); +} + +function DrawingAgentCheckpoint() { + const id = useId(); + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {"Work"} + + + {"State 02"} + + + {"Continue"} + + + {"State 02"} + + + {"Checkpoint"} + + + + + + + + {"14u"} + + + + {"4u"} + + + + ); +} + +function DrawingAgentParallel() { + const id = useId(); + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {"Agent 01"} + + + + {"Agent 02"} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {"Agent 03"} + + + + + {"Start"} + + + {"Join"} + + + + {"Wait"} + + + + + {"4u"} + + + + + {"2u"} + + + + ); +} + +function DrawingGeoCircleRectangle() { + return ( + + + + + + + + + {"R = u"} + + + + + + + + + + + + + + {"6u"} + + + + + + + + + {"4u"} + + + + {"Circle / before"} + + + {"A shared height"} + + + + ); +} + +function DrawingGeoCompoundRadii() { + return ( + + + + + + + + + + {"R112"} + + + + + + + + {"R64"} + + + + + + + + {"R144"} + + + + + + {"Tangent"} + + + {"Three radii · one continuous curve"} + + + + ); +} + +function DrawingGeoNegativeSpace() { + const id = useId(); + return ( + + + + + + + + + + + + + + + + + {"R = u"} + + + + + + + + + {"u"} + + + + {"Shared void"} + + + + ); +} + +function DrawingGeoInterruptedPattern() { + return ( + + + + + + + + + + + + + + + + + {"6u"} + + + + + + + + + {"2u"} + + + + {"Interruption"} + + + + ); +} + +function DrawingMechIrisAperture() { + const id = useId(); + return ( + + + + + + + + + + + + + + + + + + + + + + + + + {"u"} + + + {"6u"} + + + + + {"Aperture"} + + + {"Blade 01"} + + + + + ); +} + +function DrawingMechPulleyRatio() { + return ( + + + + + + + + + + + + + + {"1 turn"} + + + {"2 turns"} + + + + + {"2r"} + + + + + {"r"} + + + + + + + + + {"208"} + + + + ); +} + +function DrawingMechCamFollower() { + return ( + + + + + + + + + + + + {"e = 24"} + + + + + {"R76"} + + + + + + {"Rotation"} + + + + + {"Lift"} + + + + {"Follower"} + + + + ); +} + +function DrawingMechLatchReceiver() { + const id = useId(); + return ( + + + + + + + + + + + + + + + + + + + + {"R24"} + + + + {"4 clearance"} + + + {"Bolt"} + + + {"Receiver"} + + + + + + + + + {"216"} + + + + ); +} + +function DrawingArchStaircase() { + const id = useId(); + return ( + + + + + + + + + + + + + + + + + + {"3u"} + + + + + + + + + {"2u"} + + + + + + + + + {"18u"} + + + + {"One step"} + + + + ); +} + +function DrawingArchMissingKeystone() { + const id = useId(); + return ( + + + + + + + + + + + + + + + + + + + + + + {"R84"} + + + + + {"R120"} + + + + + {"Keystone"} + + + + + + + + + {"240"} + + + + ); +} + +function DrawingNatureLeafVeins() { + return ( + + + + + + + + + + + + + {"Midrib"} + + + + {"u"} + + + + + + {"4u"} + + + + ); +} + +function DrawingNatureHoneycomb() { + const id = useId(); + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {"Shared wall"} + + + + + + {"6r"} + + + + + + {"r"} + + + + ); +} + +function DrawingMusicChord() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + {"Separate"} + + + {"Together"} + + + + + {"Three pitches"} + + + + + ); +} + +function DrawingMapSummitContours() { + return ( + + + + + {"200"} + + + + {"175"} + + + + {"150"} + + + + {"125"} + + + + {"100"} + + + + + {"Summit"} + + + {"Contour interval / 25"} + + + + ); +} + +function DrawingMapConvergingPaths() { + return ( + + + + + + + + {"Destination"} + + + + + + {"R48"} + + + + + + {"Shared route"} + + + {"A"} + + + {"B"} + + + {"C"} + + + + ); +} + +function DrawingTypeLetterStrokes() { + const id = useId(); + return ( + + + + + + + + + + + + + + {"6u"} + + + + + {"u"} + + + + {"Crossbar"} + + + + + ); +} + +function DrawingTypeSharedGlyphs() { + return ( + + + + + + + + + + + + + + + + + + {"R40"} + + + + + {"u"} + + + + {"4u"} + + + + ); +} + +function DrawingTypeLigature() { + const id = useId(); + return ( + + + + + + + + + + + + + + + {"R56"} + + + + {"Shared stroke"} + + + + + {"u"} + + + + + ); +} + +function DrawingTypeNegativeMonogram() { + const id = useId(); + return ( + + + + + + + + + + + + + + + {"8u"} + + + + + {"u"} + + + + {"u"} + + + + + ); +} + +function DrawingTypeOpticalAlignment() { + const id = useId(); + return ( + + + + + + + + + + + + + + + {"Bounds"} + + + + + + + + + + + + + + + + {"Area"} + + + + {"δ"} + + + {"δ = width / 6"} + + + + ); +} + +function DrawingInterfaceCornerRule() { + return ( + + + + + + + + {"R24"} + + + + + {"4u"} + + + + + + + {"R24"} + + + + + {"6u"} + + + + + + + {"R24"} + + + + + {"3u"} + + + + {"One radius · different proportions"} + + + + ); +} + +function DrawingInterfaceReflow() { + return ( + + + + + + + {"A"} + + + + {"B"} + + + + {"C"} + + + + {"A"} + + + + {"B"} + + + + {"C"} + + + + {"336"} + + + + + {"228"} + + + + + {"Wide"} + + + {"Narrow"} + + + + ); +} + +function DrawingInterfaceSliderValue() { + return ( + + + + + + + + + {"60%"} + + + + {"0"} + + + {"100"} + + + + + {"R24"} + + + {"60"} + + + + ); +} + +function DrawingSoftwareCacheRoute() { + return ( + + + + + + + + {"Cache"} + + + {"Origin"} + + + + + + {"R24"} + + + {"Request"} + + + {"Response"} + + + + {"2u"} + + + + + {"4u"} + + + + ); +} + +function DrawingSoftwareBranchHistory() { + return ( + + + + + + + + + + + + {"R24"} + + + + {"Branch"} + + + {"Main"} + + + {"Fork"} + + + {"Merge"} + + + + {"10u"} + + + + + ); +} + +function DrawingTimeAppointmentBuffers() { + const id = useId(); + return ( + + + + + + + + + + + + + + + + {"15 min"} + + + {"60 min"} + + + {"15 min"} + + + {"Buffer before"} + + + {"Appointment"} + + + {"Buffer after"} + + + + {"09:45"} + + + + {"10:00"} + + + + {"11:00"} + + + + {"11:15"} + + + + + {"90 min reserved"} + + + + ); +} + +function DrawingTimeCommonRecurrence() { + return ( + + + + + + {"Event"} + + + {"01"} + + + + + {"Event"} + + + {"02"} + + + + + {"Event"} + + + {"03"} + + + + + {"Event"} + + + {"04"} + + + + + + + + + {"7 days"} + + + + {"7 days"} + + + + {"7 days"} + + + {"One recurring interval"} + + + + ); +} + +function DrawingCraftWeave() { + const id = useId(); + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {"Warp"} + + + {"Weft"} + + + + + {"64"} + + + + {"24"} + + + + ); +} + +function DrawingCraftStitch() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + {"Edge A"} + + + {"Edge B"} + + + + + {"112"} + + + + + {"48"} + + + + ); +} + +function DrawingCraftDovetail() { + const id = useId(); + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {"Part A"} + + + {"Part B"} + + + + + {"128"} + + + + {"160"} + + + + + {"48"} + + + + ); +} + +function DrawingCraftPaperFold() { + const id = useId(); + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {"45°"} + + + + + {"3u"} + + + + {"6u"} + + + + ); +} + +function DrawingCraftTiles() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + {"R48"} + + + + {"32"} + + + + + {"288"} + + + + ); +} + +function DrawingPlayDominoes() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {"Missing"} + + + + + + {"2u"} + + + + {"3u"} + + + + ); +} + +function DrawingPlayBalance() { + const id = useId(); + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {"2u"} + + + {"3u"} + + + {"3m"} + + + {"2m"} + + + {"3m × 2u = 2m × 3u"} + + + + ); +} + +export const expandedStudies: IllustrationStudy[] = [ + { + id: "agent-registration", + code: "AG01", + title: "An agent, registered", + description: + "Identity, name, and tools keep their alignment as they become one registry record.", + category: "Agents", + illustration: , + }, + { + id: "agent-delegation", + code: "AG02", + title: "One task, two agents", + description: + "Research and drafting separate into complementary scopes, assigned to two agents.", + category: "Agents", + illustration: , + }, + { + id: "agent-handoff", + code: "AG03", + title: "Context, carried forward", + description: + "One continuous context passes between two agents without changing its form.", + category: "Agents", + illustration: , + }, + { + id: "agent-tools", + code: "AG04", + title: "One matching interface", + description: + "A selected tool’s rounded end matches the agent’s common interface.", + category: "Agents", + illustration: , + }, + { + id: "agent-permissions", + code: "AG05", + title: "Only the shared scope", + description: + "The granted region is the intersection of what is requested and what is allowed.", + category: "Agents", + illustration: , + }, + { + id: "agent-memory", + code: "AG06", + title: "One context, two readers", + description: + "Two agents address the same findings row within one shared memory.", + category: "Agents", + illustration: , + }, + { + id: "agent-checkpoint", + code: "AG07", + title: "A state worth keeping", + description: + "One selected state is preserved as a checkpoint while the wider work continues.", + category: "Agents", + illustration: , + }, + { + id: "agent-parallel", + code: "AG08", + title: "Separate work, one join", + description: + "Three agents finish at different points and wait for the longest task before joining.", + category: "Agents", + illustration: , + }, + { + id: "geo-circle-rectangle", + code: "GE01", + title: "A circle, extended", + description: + "A circle and its wider rounded-rectangle state share one height while their corner radii change.", + category: "Foundations", + illustration: , + }, + { + id: "geo-compound-radii", + code: "GE03", + title: "A curve from three radii", + description: + "Three circular arcs share tangent directions at their joins, producing one uninterrupted curve.", + category: "Foundations", + illustration: , + }, + { + id: "geo-negative-space", + code: "GE04", + title: "The shape between circles", + description: + "Two equal circular regions reveal an unhatched lens where their boundaries overlap.", + category: "Foundations", + illustration: , + }, + { + id: "geo-interrupted-pattern", + code: "GE05", + title: "One interval left open", + description: + "A field of tangent circles keeps its repeated pitch while one missing member interrupts the lower row.", + category: "Foundations", + illustration: , + }, + { + id: "mech-iris-aperture", + code: "ME01", + title: "Six blades, one aperture", + description: + "Six repeated edges shape a single opening. Hatching identifies one visible blade.", + category: "Mechanisms", + illustration: , + }, + { + id: "mech-pulley-ratio", + code: "ME02", + title: "One belt, two rotations", + description: "A shared belt joins two pulley radii in a 2:1 relation.", + category: "Mechanisms", + illustration: , + }, + { + id: "mech-cam-follower", + code: "ME04", + title: "An offset becomes a lift", + description: + "An eccentric circular cam meets a guided roller, relating rotation to a vertical displacement.", + category: "Mechanisms", + illustration: , + }, + { + id: "mech-latch-receiver", + code: "ME05", + title: "A profile finds its receiver", + description: + "A rounded bolt approaches the matching receiver, with its destination and clearance visible.", + category: "Mechanisms", + illustration: , + }, + { + id: "arch-staircase", + code: "AR02", + title: "One rise, one run", + description: + "Six equal rise-and-run pairs construct a staircase along one repeated slope.", + category: "Architecture", + illustration: , + }, + { + id: "arch-missing-keystone", + code: "AR03", + title: "The last piece of the arch", + description: + "A lifted keystone preserves the geometry of the opening it will complete.", + category: "Architecture", + illustration: , + }, + { + id: "nature-leaf-veins", + code: "NA01", + title: "A branching leaf", + description: + "Paired veins follow a repeated pitch within a leaf formed by two intersecting arcs.", + category: "Nature", + illustration: , + }, + { + id: "nature-honeycomb", + code: "NA02", + title: "One edge, two cells", + description: + "A selected hexagonal cell shares each of its walls with the surrounding six cells.", + category: "Nature", + illustration: , + }, + { + id: "music-chord", + code: "MU02", + title: "Three notes, one onset", + description: + "Three separately placed pitches align vertically on one shared onset to form a chord.", + category: "Music", + illustration: , + }, + { + id: "map-summit-contours", + code: "MP04", + title: "The summit in contours", + description: + "Nested elevation lines tighten around one marked summit, with a consistent interval between levels.", + category: "Maps", + illustration: , + }, + { + id: "map-converging-paths", + code: "MP05", + title: "Different paths, one place", + description: + "Three routes join one final stretch before reaching the same destination.", + category: "Maps", + illustration: , + }, + { + id: "type-letter-strokes", + code: "TY01", + title: "A from three strokes", + description: + "Two parallel-sided diagonal strokes and one horizontal stroke make an outlined capital A; the crossbar is identified by hatching.", + category: "Typography", + illustration: , + }, + { + id: "type-shared-glyphs", + code: "TY02", + title: "One arch, three glyphs", + description: + "A shared circular shoulder and a constant stem width generate n, h and m, with the h selected to show the construction.", + category: "Typography", + illustration: , + }, + { + id: "type-ligature", + code: "TY03", + title: "The joining stroke", + description: + "The f and i share one continuous crossbar, joining the two glyphs into a single constructed ligature.", + category: "Typography", + illustration: , + }, + { + id: "type-negative-monogram", + code: "TY04", + title: "A monogram in the void", + description: + "An M is defined by the transparent space within a hatched field; its straight stems and surrounding margin follow the same unit.", + category: "Typography", + illustration: , + }, + { + id: "type-optical-alignment", + code: "TY05", + title: "The center you see", + description: + "A rightward shift brings a triangle’s area centroid to the center of its frame, providing a geometric starting point for optical alignment.", + category: "Typography", + illustration: , + }, + { + id: "interface-corner-rule", + code: "UI01", + title: "One corner rule", + description: + "Three differently proportioned components retain a common corner radius.", + category: "Interfaces", + illustration: , + }, + { + id: "interface-reflow", + code: "UI02", + title: "The same modules, reflowed", + description: + "Three equal modules preserve their size when the available width wraps the last one onto another row.", + category: "Interfaces", + illustration: , + }, + { + id: "interface-slider-value", + code: "UI03", + title: "Position becomes value", + description: + "The thumb locates sixty percent of a shared travel interval; the track clears its circular outline.", + category: "Interfaces", + illustration: , + }, + { + id: "software-cache-route", + code: "SW02", + title: "A shorter return", + description: + "A cached response turns back at the near bend while the longer path continues to the origin.", + category: "Systems", + illustration: , + }, + { + id: "software-branch-history", + code: "SW03", + title: "A history comes together", + description: + "One branch departs from a shared history, carries its own revision, and joins the main line again.", + category: "Systems", + illustration: , + }, + { + id: "time-appointment-buffers", + code: "TM02", + title: "Room around an appointment", + description: + "An hour-long appointment reserves fifteen minutes of buffer time on either side.", + category: "Scheduling", + illustration: , + }, + { + id: "time-common-recurrence", + code: "TM03", + title: "The same interval, again", + description: "Four occurrences align to one repeated seven-day interval.", + category: "Scheduling", + illustration: , + }, + { + id: "craft-weave", + code: "CR01", + title: "Over and under", + description: + "One warp and one weft reveal the alternating rule that holds a weave together.", + category: "Craft", + illustration: , + }, + { + id: "craft-stitch", + code: "CR02", + title: "A seam, joined", + description: + "Repeated crossed stitches bridge two edges on one shared hole pitch.", + category: "Craft", + illustration: , + }, + { + id: "craft-dovetail", + code: "CR03", + title: "A joint that holds", + description: + "A wider tail and narrower neck create one complementary boundary between two pieces.", + category: "Craft", + illustration: , + }, + { + id: "craft-paper-fold", + code: "CR04", + title: "Across the crease", + description: + "One corner reflects across its diagonal crease and becomes a triangular flap.", + category: "Craft", + illustration: , + }, + { + id: "craft-tiles", + code: "CR05", + title: "One shared corner", + description: + "Four tiles meet around a common circular opening and a constant joint width.", + category: "Craft", + illustration: , + }, + { + id: "play-dominoes", + code: "PL03", + title: "One piece short", + description: + "The pip sequence continues across one absent domino, leaving its position unmistakable.", + category: "Play", + illustration: , + }, + { + id: "play-balance", + code: "PL04", + title: "Unequal arms, equal moments", + description: + "Three mass units on the shorter arm balance two on the longer arm.", + category: "Play", + illustration: , + }, +]; diff --git a/apps/ui/app/plotter/grammar-comparison.module.css b/apps/ui/app/plotter/grammar-comparison.module.css new file mode 100644 index 0000000000..1f5ed7e746 --- /dev/null +++ b/apps/ui/app/plotter/grammar-comparison.module.css @@ -0,0 +1,93 @@ +.drawing:is([data-isolated], :focus-visible) + svg:not([data-grammar-preserve-contrast="true"]) + g:not(defs *, clipPath *, mask *, pattern *, marker *, symbol *) { + opacity: 1; +} + +.drawing:is([data-isolated], :focus-visible) + svg + :is( + path, + rect, + circle, + ellipse, + line, + polyline, + polygon, + text, + use, + image + ):not( + defs *, + clipPath *, + mask *, + pattern *, + marker *, + symbol *, + [data-grammar-focus="true"], + [data-grammar-focus="true"] * + ) { + opacity: 0; +} + +.drawing:is([data-isolated], :focus-visible) + svg:not([data-grammar-preserve-contrast="true"]) + :is([data-grammar-focus="true"], [data-grammar-focus="true"] *):not( + defs *, + clipPath *, + mask *, + pattern *, + marker *, + symbol * + ) { + opacity: 1; + stroke-opacity: 1; +} + +@media (hover: hover) and (pointer: fine) { + .drawing:hover + svg:not([data-grammar-preserve-contrast="true"]) + g:not(defs *, clipPath *, mask *, pattern *, marker *, symbol *) { + opacity: 1; + } + + .drawing:hover + svg + :is( + path, + rect, + circle, + ellipse, + line, + polyline, + polygon, + text, + use, + image + ):not( + defs *, + clipPath *, + mask *, + pattern *, + marker *, + symbol *, + [data-grammar-focus="true"], + [data-grammar-focus="true"] * + ) { + opacity: 0; + } + + .drawing:hover + svg:not([data-grammar-preserve-contrast="true"]) + :is([data-grammar-focus="true"], [data-grammar-focus="true"] *):not( + defs *, + clipPath *, + mask *, + pattern *, + marker *, + symbol * + ) { + opacity: 1; + stroke-opacity: 1; + } +} diff --git a/apps/ui/app/plotter/grammar-comparison.tsx b/apps/ui/app/plotter/grammar-comparison.tsx new file mode 100644 index 0000000000..e1781c7834 --- /dev/null +++ b/apps/ui/app/plotter/grammar-comparison.tsx @@ -0,0 +1,59 @@ +"use client"; + +import { type ReactNode, useId, useState } from "react"; +import { Button } from "@/registry/default/ui/button"; +import styles from "./grammar-comparison.module.css"; + +export function GrammarComparison({ + title, + children, + caption, +}: { + title: string; + children: ReactNode; + caption: string; +}) { + const [isolated, setIsolated] = useState(false); + const id = useId(); + const drawingId = `${id}-drawing`; + const instructionId = `${id}-instruction`; + + return ( +
    +
    + +

    + Hover or focus to reveal the element. Tap to keep it isolated. +

    +
    + +
    + {caption} +
    +

    + Scroll sideways to see the full drawing. +

    +
    + ); +} diff --git a/apps/ui/app/plotter/illustration-gallery.tsx b/apps/ui/app/plotter/illustration-gallery.tsx index bd31ab94c4..7162c7f6c7 100644 --- a/apps/ui/app/plotter/illustration-gallery.tsx +++ b/apps/ui/app/plotter/illustration-gallery.tsx @@ -13,6 +13,15 @@ const categories: StudyCategory[] = [ "Systems", "Objects", "Empty states", + "Agents", + "Mechanisms", + "Architecture", + "Nature", + "Music", + "Maps", + "Typography", + "Craft", + "Play", ]; export function IllustrationGallery({ diff --git a/apps/ui/app/plotter/illustrations.tsx b/apps/ui/app/plotter/illustrations.tsx index 2d888e6470..4f8f7c04a6 100644 --- a/apps/ui/app/plotter/illustrations.tsx +++ b/apps/ui/app/plotter/illustrations.tsx @@ -1,6 +1,7 @@ import { calStudies } from "./cal-studies"; import { contentStateStudies } from "./content-state-studies"; import { engineeringStudies } from "./engineering-studies"; +import { expandedStudies } from "./expanded-studies"; import { flatCalStudies } from "./flat-cal-studies"; import { flatObjectStudies } from "./flat-object-studies"; import { flatSystemStudies } from "./flat-system-studies"; @@ -54,6 +55,7 @@ const collection: IllustrationStudy[] = [ ...engineeringStudies, ...notFoundArchitectureStudies, ...notFoundGeometryStudies, + ...expandedStudies, { id: "404-reference", code: "E00", diff --git a/apps/ui/app/plotter/page.tsx b/apps/ui/app/plotter/page.tsx index 28780aed96..2c2cbb8dd9 100644 --- a/apps/ui/app/plotter/page.tsx +++ b/apps/ui/app/plotter/page.tsx @@ -16,6 +16,7 @@ import { ParticlesMotion } from "./particles-motion"; import { PrimitivesBlueprint } from "./primitives-blueprint"; import { PrimitivesMotionPreview } from "./primitives-motion-preview"; import { ProductionChainMotionDrawing } from "./production-chain-motion-drawing"; +import { VisualGrammar } from "./visual-grammar"; export const metadata: Metadata = { title: "Plotter illustrations – coss ui", @@ -53,6 +54,12 @@ export default function Page() { > coss.com concepts + + Visual grammar +
    +
    + ); +} diff --git a/apps/ui/app/plotter/production-chain-motion-drawing.tsx b/apps/ui/app/plotter/production-chain-motion-drawing.tsx index f10a181aef..752d721ed2 100644 --- a/apps/ui/app/plotter/production-chain-motion-drawing.tsx +++ b/apps/ui/app/plotter/production-chain-motion-drawing.tsx @@ -2,6 +2,7 @@ import { useId } from "react"; import { BlueprintDrawing } from "./blueprint-drawing"; +import { ProductionChainHatching } from "./production-chain-hatching"; const cycle = 1.8; const duration = `${cycle}s`; @@ -167,8 +168,21 @@ export function ProductionChainMotionDrawing() { ))} + + + frame.bottom).join(";")} + /> + + + {/* Core sides transfer to the carrier walls at y=200. Each remaining segment stays on the rigid 48×48 core boundary. */} @@ -205,10 +219,7 @@ export function ProductionChainMotionDrawing() { - + - + + +
    + +
    + {visualGrammarEntries.map((entry) => ( +
    +
    +

    + {entry.title} +

    +

    + {entry.description} +

    +

    + {entry.omit} +

    +
    + + {visualGrammarDrawings[entry.id]} + +
    + ))} +
    +
    + ); +} diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/notes.md b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/notes.md new file mode 100644 index 0000000000..c109a7fb3d --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/notes.md @@ -0,0 +1,25 @@ +# Reference-led catalogue expansion + +After rejecting the initial 78-drawing batch, the user explicitly liked the rebuilt registration drawing, then the delegation and iris follow-up. Their next instruction was: “love them, continue with all the remaining examples, and also want them to be added to the catalogue”. + +The initial expansion included the three liked revisions unchanged in appearance, plus 75 revised subjects, bringing the catalogue to 136. The user subsequently removed 36 entries, leaving **100 illustrations**, including 42 from this expansion. Active editable SVGs live in [`../../../app/plotter/drawings`](../../../app/plotter/drawings); the [manifest](../../../app/plotter/drawings/manifest.json) records stable IDs, codes, descriptions, reference choices, and construction details. SVG metadata supplies the same construction context. AG01, AG02, and ME01 retain the positive feedback above; the other 39 retained revisions await individual user feedback. None changes the approved 27-reference selection. + +## Subsequent curation + +The user found 33 of the additions “not visually interesting”, considered AR01 and GE02 too similar to existing studies, and then explicitly requested OB04’s removal too. The complete code list and reasons are recorded in [direction and feedback](../../../skills/plotter/references/direction.md#catalogue-removals-after-expansion--2026-10-31). Removed SVGs and metadata are preserved in [`removed/`](removed/) as review evidence, outside the active catalogue and its generated components. + +Repeated mentions of OB03 and AB05 were counted once. Retained codes and IDs have not been renumbered. These decisions apply to the named drawings, not whole subject categories, and retaining a drawing does not establish approval. + +## Drawing process + +The revision pass proceeded by subject group after the three liked examples established the direction. Each new drawing was compared at native 560 × 440 against a relevant existing reference. The comparisons considered main-form proportions, the relationship carrying the idea, useful guides, and label clearance. Refinements included shared-edge ownership, actual tangent connections, corrected clipping for negative space, measured intervals, and clearer labels. The integrated React drawings were also rendered together to inspect their appearance after conversion. + +The initial collection contained eight agent concepts and five subjects each in geometry, mechanisms, objects, architecture, nature, music, maps, typography, interfaces, software, time, absence, craft, and play. Conceptual metaphors are identified in source metadata; dimensions describe the drawn geometry rather than asserting physical performance. Background grids were omitted where local geometry already explained the relationship. + +This is an authored illustration revision, not a clean-install skill evaluation. No test suites, type checks, or automated audit campaigns were run, following the user’s request. Rendering and visual comparison informed the drawings; they do not establish engineering accuracy or broad skill reliability. + +## Related requests + +W05 now has H1 hatching only on its square cores, following the user’s correction. Static and animated views preserve the existing outlines and timing; carriers remain unhatched. + +The skill’s [illustrated visual grammar](../../../skills/plotter/references/visual-grammar.md) explains mark selection with paired examples. It supplements the drafting standard without changing the approved style references. diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-connectors-gap.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-connectors-gap.svg new file mode 100644 index 0000000000..766c28c27c --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-connectors-gap.svg @@ -0,0 +1 @@ +Almost connectedA matching tongue and receiver share an axis but remain separated by one visible gap.Revision 2. Reference: E02. Conceptual fit study. u=56 is the gap from tongue endpoint x272 to receiver mouth x328. Tongue and recess48 high and48 deep. H1 identifies the receiver body, bounded by its actual profile and excluding its open recess. No electrical function or working hardware claim.ConnectorReceiveru diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-empty-position.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-empty-position.svg new file mode 100644 index 0000000000..e4760f311f --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-empty-position.svg @@ -0,0 +1 @@ +A place in the collectionOne empty position retains the dimensions and spacing of the surrounding records.Revision 2. Reference: C02. Conceptual allocation. u=24; six positions form3 columns and2 rows. Cards96x64; all gaps and frame insets24. The missing lower-middle item is dashed. The existing frame is a shared collection boundary, not a full application screen.Item 01Item 02Item 03Item 04OpenItem 0616u4uuu diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-matching-slot.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-matching-slot.svg new file mode 100644 index 0000000000..a63426b85c --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-matching-slot.svg @@ -0,0 +1 @@ +An opening with a matchA separate element and an open receiver share the same profile, width, and generating radius.Revision 2. Reference: P00. Geometric fit metaphor. u=48; both D profiles are96wide and96high, made from a48-wide rectangle and a right semicircle R48. Profile translation is216 units horizontally. The right outline is an existing vacant opening, not an absent part; its support frame is secondary. No tolerance or hardware claim.OpenR48ElementReceiver2u2u diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-missing-piece.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-missing-piece.svg new file mode 100644 index 0000000000..1bc9eb0c0c --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-missing-piece.svg @@ -0,0 +1 @@ +The unfilled quarterThree interlocking quarters leave one necessary part absent, while its outer edges remain implied.Revision 2. Reference: P00. Geometric assembly metaphor, with E00 absence informing the composition. u=40. Square240x240; missing upper-right quadrant120x120. R16 circular tabs along mating seams. Existing outer boundary and shared interior seams are drawn once; dashed edges are absent, not hidden. No puzzle manufacturability claim.Missing0102036u3u diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-unbuilt-destination.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-unbuilt-destination.svg new file mode 100644 index 0000000000..8765e8319e --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/absence-unbuilt-destination.svg @@ -0,0 +1 @@ +A route to what comes nextA complete route reaches the opening reserved for a destination that has not yet been built.Revision 2. Reference: E02. Conceptual absence. Route width16; centerline bends R64 and R48; corresponding boundary radii56/72 and40/56. The destination is absent and dashed. Route centerline and generating radius are guides, not additional route edges. No network architecture claim.R64UnbuiltDestinationRoute diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/arch-door-clearance.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/arch-door-clearance.svg new file mode 100644 index 0000000000..7646955cd7 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/arch-door-clearance.svg @@ -0,0 +1 @@ +The space a door needsThe door and its swing envelope share one pivot and one radius.Revision 2. Reference E01. Conceptual architectural plan. u=24; opening and leaf length6u=144. Quarter-circle clearance radius144 is centered at hinge(224,276). Dashed horizontal line denotes the closed leaf edge. H1 identifies wall regions. Illustrative dimensions, not building-code or construction guidance.R14490°6uClearance diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/arch-proportional-window.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/arch-proportional-window.svg new file mode 100644 index 0000000000..e9363536a7 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/arch-proportional-window.svg @@ -0,0 +1 @@ +A window in shared proportionsOne module divides a window into four panes with related widths and heights.Revision 2. Reference C02. Geometric elevation. u=48. Clear opening5u×6u, divided horizontally into2u+3u and vertically into2u+4u. Thus the upper-left pane is2u square. Outer frame inset12. Each mullion and shared boundary has one owner; no ornamental grid or material claim.2u3u2u4uOne module, four panes diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/arch-sliding-partitions.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/arch-sliding-partitions.svg new file mode 100644 index 0000000000..04945efcd2 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/arch-sliding-partitions.svg @@ -0,0 +1 @@ +A room opens between panelsTwo equal partitions slide apart while a faint former seam locates the closed position.Revision 2. Reference E01. Conceptual elevation of sliding partitions. Each panel128×160. Closed leading edges meet atx280; panels translate by-28 and+28, giving56 opening. Missing outlines indicate former meeting edges only. H1 identifies the fixed frame, not a material. This is a spatial-change illustration, not hardware documentation.Slide12856 diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/geo-shared-tangent.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/geo-shared-tangent.svg new file mode 100644 index 0000000000..f951279f68 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/geo-shared-tangent.svg @@ -0,0 +1 @@ +Two radii, one tangentUnequal circles meet the same baseline and inclined tangent; their radii explain both contacts.Revision 2. Reference P00. Geometric construction. Radii80 and48, centers(196,244),(380,276), baseliney324. The upper tangent normal is derived from the center vector and radius difference; lower normals are vertical. Dimension184 measures horizontal center separation.R80R48184Tangent diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/interface-nested-spacing.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/interface-nested-spacing.svg new file mode 100644 index 0000000000..fb1067a421 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/interface-nested-spacing.svg @@ -0,0 +1 @@ +Space within spaceEqual insets repeat through three nested boundaries; one hatched strip makes the spacing visible.Revision 2. Reference: C02. Geometric construction. u=24. Frames288x240,240x192,192x144 share center280,220 and constant24 insets. H1 region is the middle-to-content inset along the left edge, not material.ContentContaineruuuu diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/interface-shared-selection.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/interface-shared-selection.svg new file mode 100644 index 0000000000..396013acd7 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/interface-shared-selection.svg @@ -0,0 +1 @@ +One selection, three formsOne bounding selection encloses three independent forms, with handles and a shared inset.Revision 2. Reference: C02. Conceptual UI selection. Circle diameter64, pill112x64, square64x64; bounds x128..432,y196..260. Selection x104..456,y172..284 gives24 inset. Continuous selection edges stop at their corner handles. Dimensions use SVG units.Label3 selected35224 diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/manifest.json b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/manifest.json new file mode 100644 index 0000000000..93a76a8ff8 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/manifest.json @@ -0,0 +1,398 @@ +[ + { + "id": "geo-shared-tangent", + "code": "GE02", + "title": "Two radii, one tangent", + "description": "Unequal circles meet the same baseline and inclined tangent; their radii explain both contacts.", + "category": "Foundations", + "file": "geo-shared-tangent.svg", + "reference": "P00", + "construction": "Geometric construction. Radii80 and48, centers(196,244),(380,276), baseliney324. The upper tangent normal is derived from the center vector and radius difference; lower normals are vertical. Dimension184 measures horizontal center separation.", + "removal_reason": "Redundant with existing catalogue studies." + }, + { + "id": "mech-scissor-linkage", + "code": "ME03", + "title": "Equal links, changing span", + "description": "Three crossing link pairs share pivots and repeat one geometry across an expanding linkage.", + "category": "Mechanisms", + "file": "mech-scissor-linkage.svg", + "reference": "C02", + "construction": "Conceptual planar linkage. u=48. Each bay spans96 horizontally and160 vertically. All diagonals have equal length. Link edges are offset4 from their centerline and terminate analytically on radius5 end pivots and radius7 center pivots. The arrow suggests extension, not a verified travel range.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "object-camera-lens", + "code": "OB01", + "title": "One optical center", + "description": "Concentric lens and retaining-ring boundaries share one optical center.", + "category": "Objects", + "file": "object-camera-lens.svg", + "reference": "P00", + "construction": "Geometric front-view concept. Radii120,108,80,56 share center(280,220). H1 identifies the retaining annulus108≤r≤120, not a material or shading. Axis lines are split around the center and its identifying label. No specific lens construction or optical performance asserted.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "object-umbrella-ribs", + "code": "OB02", + "title": "Eight ribs, one center", + "description": "Eight equal radial ribs hold a scalloped canopy around one shared hub.", + "category": "Objects", + "file": "object-umbrella-ribs.svg", + "reference": "P00", + "construction": "Geometric umbrella plan. Ribs radius120 on45° pitch; neighboring canopy tips joined by concave radius100 arcs. Hub radius6. H1 identifies one canopy panel bounded by two ribs, the scalloped edge and hub. Flat illustrative construction, not a fabrication plan.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "object-hinge-panels", + "code": "OB03", + "title": "Two panels, one axis", + "description": "Alternating hinge leaves connect two panels around a common axis.", + "category": "Objects", + "file": "object-hinge-panels.svg", + "reference": "C02", + "construction": "Conceptual front elevation. Panels144×168; edge gap64; barrel16×128. Open leaf paths share the barrel boundary without retracing it. The centerline denotes the hinge axis, not an electrical connection. No load, clearances or actual hardware standard asserted.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "object-key-lock", + "code": "OB05", + "title": "A key meets its profile", + "description": "A stepped key approaches a receiver with the same profile and alignment.", + "category": "Objects", + "file": "object-key-lock.svg", + "reference": "C01", + "construction": "Conceptual fit diagram. Key bow radius36 with15-radius opening; shank profile translated128 units forms the receiver cavity. Tooth starts220,256,292 share pitchu=36. This illustrates a matching profile and does not claim a working lock or authentication system.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "arch-door-clearance", + "code": "AR01", + "title": "The space a door needs", + "description": "The door and its swing envelope share one pivot and one radius.", + "category": "Architecture", + "file": "arch-door-clearance.svg", + "reference": "E01", + "construction": "Conceptual architectural plan. u=24; opening and leaf length6u=144. Quarter-circle clearance radius144 is centered at hinge(224,276). Dashed horizontal line denotes the closed leaf edge. H1 identifies wall regions. Illustrative dimensions, not building-code or construction guidance.", + "removal_reason": "Redundant with existing catalogue studies." + }, + { + "id": "arch-sliding-partitions", + "code": "AR04", + "title": "A room opens between panels", + "description": "Two equal partitions slide apart while a faint former seam locates the closed position.", + "category": "Architecture", + "file": "arch-sliding-partitions.svg", + "reference": "E01", + "construction": "Conceptual elevation of sliding partitions. Each panel128×160. Closed leading edges meet atx280; panels translate by-28 and+28, giving56 opening. Missing outlines indicate former meeting edges only. H1 identifies the fixed frame, not a material. This is a spatial-change illustration, not hardware documentation.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "arch-proportional-window", + "code": "AR05", + "title": "A window in shared proportions", + "description": "One module divides a window into four panes with related widths and heights.", + "category": "Architecture", + "file": "arch-proportional-window.svg", + "reference": "C02", + "construction": "Geometric elevation. u=48. Clear opening5u×6u, divided horizontally into2u+3u and vertically into2u+4u. Thus the upper-left pane is2u square. Outer frame inset12. Each mullion and shared boundary has one owner; no ornamental grid or material claim.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "nature-offset-rings", + "code": "NA03", + "title": "Growth shifts the center", + "description": "Successive rings expand while their centers move, leaving the earliest core visibly off-center.", + "category": "Nature", + "file": "nature-offset-rings.svg", + "reference": "P00", + "construction": "Six circular growth contours: center=(250+5i,220), radius=20i, i=1..6. Center drift25=5u with u5. H1 selects the southwest sector of the newest annular band, between the two actual circular boundaries. This is a geometric growth analogy, not a dated tree section.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "nature-tributaries", + "code": "NA04", + "title": "Small streams, one river", + "description": "Two bounded channels merge at one confluence and continue as a single downstream course.", + "category": "Nature", + "file": "nature-tributaries.svg", + "reference": "E02", + "construction": "Continuous banks and a single inner junction define a Y-shaped channel. Arrows mean conceptual downstream movement. The width w of the lower straight reach spans56 SVG units; no discharge, scale, or hydraulics claim. Cubic banks are intentionally organic and carry no radius labels.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "nature-seed-spiral", + "code": "NA05", + "title": "An angle becomes a field", + "description": "A constant turn and a growing radius organize eighty seeds around one center.", + "category": "Nature", + "file": "nature-seed-spiral.svg", + "reference": "P00", + "construction": "Golden angle = pi*(3-sqrt(5)). Plotted seed indices20..99 have radius12sqrt(n); each next angle advances137.507764 degrees. The omitted inner indices leave space to explain the generating turn. Circlesr3.8 represent seeds; outer128 circle is a reference envelope, not a flower edge. The drawn central arc uses the exact angle; display label rounds to137.5 degrees.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "music-rhythm-intervals", + "code": "MU01", + "title": "Time between notes", + "description": "A four-beat measure makes one half-beat onset visible between two regular beats.", + "category": "Music", + "file": "music-rhythm-intervals.svg", + "reference": "K07", + "construction": "Beat pitch72 SVG units. Onsets at positions0,72,108,144,216 relative to136. The measure ends at424. Outlined noteheads are onset markers; the drawing does not assert note durations or a complete score. Beat and half-beat dimensions share the same72-unit scale.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "music-tuning-fork", + "code": "MU03", + "title": "A symmetric tuning fork", + "description": "Equal-width prongs and a centered handle grow from two concentric circular bends.", + "category": "Music", + "file": "music-tuning-fork.svg", + "reference": "P00", + "construction": "u=24 prong width. Inner and outer U bends have radii40 and64 about(280,232). Outer arc-to-handle intersections are derived at x268 and292. The central axis is a construction reference. This stylized silhouette makes no resonance-frequency or manufacturability claim.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "music-missing-beat", + "code": "MU04", + "title": "The absent beat", + "description": "A repeated pulse sequence holds one step at the baseline, leaving a deliberate rest.", + "category": "Music", + "file": "music-missing-beat.svg", + "reference": "E02", + "construction": "Eight40-unit step intervals. Each present pulse is16 wide and80 high. Step5 is absent; its expected outline uses3 5, while the real signal remains at the baseline. u=40 step period, not a physical time unit. On/off are conceptual sequencer states.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "music-meeting-rhythms", + "code": "MU05", + "title": "Three against two", + "description": "Two repeating rhythms meet at the same cycle boundary after three pulses in one and two in the other.", + "category": "Music", + "file": "music-meeting-rhythms.svg", + "reference": "B04", + "construction": "Common cycle192=6u with u32. Upper pulses every64=2u, lower every96=3u. Two cycles span88..472; the common boundary at280 owns one continuous primary stroke. Baselines and cycle witnesses are distinct from pulse marks.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "map-transit-platform", + "code": "MP01", + "title": "A shared platform", + "description": "Two routes align their stops on opposite sides of one platform.", + "category": "Maps", + "file": "map-transit-platform.svg", + "reference": "E02", + "construction": "u24; track spacing72=3u, platformheight24. Two R24 tangent bends lift routeB onto the shared alignment. Stop-circle interiors are clear of track strokes. The generating circle excludes the actual curved route segment. Conceptual transit interchange, not a real station plan.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "map-road-radii", + "code": "MP02", + "title": "The radius of a turn", + "description": "Four equal circular curb returns organize the meeting of two equal-width roads.", + "category": "Maps", + "file": "map-road-radii.svg", + "reference": "P00", + "construction": "Road width64=2u withu32. Four R40 tangent arcs join horizontal and vertical straight curbs. The northwest circle guide shows only its unused270-degree portion, so no guide retraces the actual curb. This is illustrative junction geometry, not a road safety or vehicle-turning assessment.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "map-missing-bridge", + "code": "MP03", + "title": "A route without its span", + "description": "A road reaches both riverbanks, but its bridge remains an absent part of the route.", + "category": "Maps", + "file": "map-missing-bridge.svg", + "reference": "E02", + "construction": "Road width32; missing span88 between x236 and324. Road endcaps own the riverbank coincidence across y204..236; bank paths stop and restart at those boundaries. Dashed edges indicate absent bridge geometry. This conceptual plan does not assert real topography or bridge dimensions.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "interface-nested-spacing", + "code": "UI04", + "title": "Space within space", + "description": "Equal insets repeat through three nested boundaries; one hatched strip makes the spacing visible.", + "category": "Interfaces", + "file": "interface-nested-spacing.svg", + "reference": "C02", + "construction": "Geometric construction. u=24. Frames288x240,240x192,192x144 share center280,220 and constant24 insets. H1 region is the middle-to-content inset along the left edge, not material.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "interface-shared-selection", + "code": "UI05", + "title": "One selection, three forms", + "description": "One bounding selection encloses three independent forms, with handles and a shared inset.", + "category": "Interfaces", + "file": "interface-shared-selection.svg", + "reference": "C02", + "construction": "Conceptual UI selection. Circle diameter64, pill112x64, square64x64; bounds x128..432,y196..260. Selection x104..456,y172..284 gives24 inset. Continuous selection edges stop at their corner handles. Dimensions use SVG units.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "software-queue-slot", + "code": "SW01", + "title": "A queue meets a slot", + "description": "Equal work units wait for one matching processing opening. The leading unit and the available slot share one size.", + "category": "Systems", + "file": "software-queue-slot.svg", + "reference": "C02", + "construction": "Conceptual allocation. u=48. Queue units48x48 at72 pitch; receiver opening48 high and48 deep. Shapes indicate queue order and capacity, not processor hardware. No inference about throughput.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "software-common-key", + "code": "SW04", + "title": "Two records, one key", + "description": "A shared identifier relates two rows at different positions, carried by a single measured connection.", + "category": "Systems", + "file": "software-common-key.svg", + "reference": "C02", + "construction": "Conceptual relation, with approved B09 informing the offset-table layout. u=24; both tables144 wide, rows32 high. Key42 links the selected rows. Shared row boundaries have one stroke owner; ports interrupt outside borders. The curved relation usesR24 and carries no transfer or causal claim.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "software-shape-filter", + "code": "SW05", + "title": "Only what fits", + "description": "A measured opening admits the smaller circles while the larger profiles remain outside.", + "category": "Systems", + "file": "software-shape-filter.svg", + "reference": "P00", + "construction": "Geometric filter analogy. Opening48 high between y208/256; circular items diameter40 leave4 vertical clearance each side. Held square64 and triangleheight56 exceed opening height. Profiles remain unrotated. This is a size-filter metaphor, not a physical sorting machine.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "time-availability-overlap", + "code": "TM01", + "title": "Where availability meets", + "description": "Two named availability windows project onto their common two-hour interval.", + "category": "Scheduling", + "file": "time-availability-overlap.svg", + "reference": "K07", + "construction": "Conceptual schedule using96 SVG units/hour. Ana09:00–12:00 spans120..408; Lee10:00–13:00 spans216..504. Their10:00–12:00 intersection216..408 is hatched. Hatching means shared availability. Example schedules only.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "time-schedule-handoff", + "code": "TM04", + "title": "One schedule takes over", + "description": "Two schedules meet at one time boundary; the handoff aligns the end of one with the start of the other.", + "category": "Scheduling", + "file": "time-schedule-handoff.svg", + "reference": "K07", + "construction": "Conceptual schedule at84 SVG units/hour. TeamA12:00–14:00 spans112..280; TeamB14:00–16:00 spans280..448. Both intervals168wide. Hatch marks the matching handoff edges, not a duration claim. The shared boundary is drawn once within each row.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "time-unfinished-deadline", + "code": "TM05", + "title": "The deadline arrives first", + "description": "A fixed deadline interrupts a sequence with completed work, an active step, and unfinished steps still ahead.", + "category": "Scheduling", + "file": "time-unfinished-deadline.svg", + "reference": "B04", + "construction": "Conceptual sequence. u=56 is the step pitch, each step40x40. Four completed steps are hatched, the active step partially hatched, two absent/unfinished steps dashed. Deadline x372 lies between active and unfinished steps. No time estimate or progress percentage is claimed.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "absence-missing-piece", + "code": "AB01", + "title": "The unfilled quarter", + "description": "Three interlocking quarters leave one necessary part absent, while its outer edges remain implied.", + "category": "Empty states", + "file": "absence-missing-piece.svg", + "reference": "P00", + "construction": "Geometric assembly metaphor, with E00 absence informing the composition. u=40. Square240x240; missing upper-right quadrant120x120. R16 circular tabs along mating seams. Existing outer boundary and shared interior seams are drawn once; dashed edges are absent, not hidden. No puzzle manufacturability claim.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "absence-empty-position", + "code": "AB02", + "title": "A place in the collection", + "description": "One empty position retains the dimensions and spacing of the surrounding records.", + "category": "Empty states", + "file": "absence-empty-position.svg", + "reference": "C02", + "construction": "Conceptual allocation. u=24; six positions form3 columns and2 rows. Cards96x64; all gaps and frame insets24. The missing lower-middle item is dashed. The existing frame is a shared collection boundary, not a full application screen.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "absence-connectors-gap", + "code": "AB03", + "title": "Almost connected", + "description": "A matching tongue and receiver share an axis but remain separated by one visible gap.", + "category": "Empty states", + "file": "absence-connectors-gap.svg", + "reference": "E02", + "construction": "Conceptual fit study. u=56 is the gap from tongue endpoint x272 to receiver mouth x328. Tongue and recess48 high and48 deep. H1 identifies the receiver body, bounded by its actual profile and excluding its open recess. No electrical function or working hardware claim.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "absence-unbuilt-destination", + "code": "AB04", + "title": "A route to what comes next", + "description": "A complete route reaches the opening reserved for a destination that has not yet been built.", + "category": "Empty states", + "file": "absence-unbuilt-destination.svg", + "reference": "E02", + "construction": "Conceptual absence. Route width16; centerline bends R64 and R48; corresponding boundary radii56/72 and40/56. The destination is absent and dashed. Route centerline and generating radius are guides, not additional route edges. No network architecture claim.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "absence-matching-slot", + "code": "AB05", + "title": "An opening with a match", + "description": "A separate element and an open receiver share the same profile, width, and generating radius.", + "category": "Empty states", + "file": "absence-matching-slot.svg", + "reference": "P00", + "construction": "Geometric fit metaphor. u=48; both D profiles are96wide and96high, made from a48-wide rectangle and a right semicircle R48. Profile translation is216 units horizontally. The right outline is an existing vacant opening, not an absent part; its support frame is secondary. No tolerance or hardware claim.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "play-tangram", + "code": "PL01", + "title": "Seven parts, one square", + "description": "Seven complementary pieces retain a single square silhouette; one square piece is identified within it.", + "category": "Play", + "file": "play-tangram.svg", + "reference": "P00, C01", + "construction": "Planar tangram partition of a 240-unit square; u=40. Two large triangles, one medium triangle, two small triangles, one square and one parallelogram share their seams. Hatching identifies the square piece, whose horizontal diagonal is 3u. The dashed center projection explains the half-width relationship.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "play-maze", + "code": "PL02", + "title": "The interrupted route", + "description": "One closed passage stops a route whose continuation remains visible.", + "category": "Play", + "file": "play-maze.svg", + "reference": "E02, W03", + "construction": "Illustrative planar maze. Wall pitch 56, outer extent224, highlighted route bendsR8. The route enters and exits through actual gaps. The vertical closure at(336,196) interrupts the intended path. Dashed continuation is a hypothetical route, not a hidden wall. No claim of a unique maze solution is made.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "play-almost-fit", + "code": "PL05", + "title": "Almost aligned", + "description": "The matching circular profiles remain separated by one deliberate vertical offset.", + "category": "Play", + "file": "play-almost-fit.svg", + "reference": "P00, P02", + "construction": "Planar fit study. Both interface profiles are semicirclesR32. Their centers are(248,212) and(312,236), giving a24-unit vertical mismatch. Both body widths128 are shown. The source tab and receiving recess would match after translation(64,24); no clearance or manufacturing claim is implied.", + "removal_reason": "The user found this revision not visually interesting." + }, + { + "id": "object-zipper", + "code": "OB04", + "title": "Two edges become one", + "description": "Two tooth rows meet at a slider and continue as one alternating seam.", + "category": "Objects", + "file": "object-zipper.svg", + "reference": "C01", + "construction": "Conceptual zipper front view. Incoming tape edges route to slider boundary. Incoming tooth roots belong to tape edges, so roots are not redrawn. Joined tooth pitchu=12; the shown span3u is36. Slider upper boundary is split at pull tab. The geometry conveys joining, not a textile or tooth manufacturing specification.", + "removal_reason": "The user explicitly requested removal after the first curation pass." + } +] diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/map-missing-bridge.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/map-missing-bridge.svg new file mode 100644 index 0000000000..7f6c6ddfbc --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/map-missing-bridge.svg @@ -0,0 +1 @@ +A route without its spanA road reaches both riverbanks, but its bridge remains an absent part of the route.Mode: conceptual construction. Reference: E02. Road width32; missing span88 between x236 and324. Road endcaps own the riverbank coincidence across y204..236; bank paths stop and restart at those boundaries. Dashed edges indicate absent bridge geometry. This conceptual plan does not assert real topography or bridge dimensions.RiverRouteDestinationMissing span diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/map-road-radii.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/map-road-radii.svg new file mode 100644 index 0000000000..44523e2046 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/map-road-radii.svg @@ -0,0 +1 @@ +The radius of a turnFour equal circular curb returns organize the meeting of two equal-width roads.Mode: conceptual construction. Reference: P00. Road width64=2u withu32. Four R40 tangent arcs join horizontal and vertical straight curbs. The northwest circle guide shows only its unused270-degree portion, so no guide retraces the actual curb. This is illustrative junction geometry, not a road safety or vehicle-turning assessment.R402u diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/map-transit-platform.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/map-transit-platform.svg new file mode 100644 index 0000000000..275ce13a76 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/map-transit-platform.svg @@ -0,0 +1 @@ +A shared platformTwo routes align their stops on opposite sides of one platform.Mode: conceptual construction. Reference: E02. u24; track spacing72=3u, platformheight24. Two R24 tangent bends lift routeB onto the shared alignment. Stop-circle interiors are clear of track strokes. The generating circle excludes the actual curved route segment. Conceptual transit interchange, not a real station plan.Shared platform3uR24AB diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/mech-scissor-linkage.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/mech-scissor-linkage.svg new file mode 100644 index 0000000000..ac6c6dabae --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/mech-scissor-linkage.svg @@ -0,0 +1 @@ +Equal links, changing spanThree crossing link pairs share pivots and repeat one geometry across an expanding linkage.Revision 2. Reference C02. Conceptual planar linkage. u=48. Each bay spans96 horizontally and160 vertically. All diagonals have equal length. Link edges are offset4 from their centerline and terminate analytically on radius5 end pivots and radius7 center pivots. The arrow suggests extension, not a verified travel range.2u2u2uExtend diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/music-meeting-rhythms.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/music-meeting-rhythms.svg new file mode 100644 index 0000000000..2d8fcbb98f --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/music-meeting-rhythms.svg @@ -0,0 +1 @@ +Three against twoTwo repeating rhythms meet at the same cycle boundary after three pulses in one and two in the other.Mode: conceptual construction. Reference: B04. Common cycle192=6u with u32. Upper pulses every64=2u, lower every96=3u. Two cycles span88..472; the common boundary at280 owns one continuous primary stroke. Baselines and cycle witnesses are distinct from pulse marks.32Together2u3uOne cycle diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/music-missing-beat.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/music-missing-beat.svg new file mode 100644 index 0000000000..26b9fb5c6a --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/music-missing-beat.svg @@ -0,0 +1 @@ +The absent beatA repeated pulse sequence holds one step at the baseline, leaving a deliberate rest.Mode: conceptual construction. Reference: E02. Eight40-unit step intervals. Each present pulse is16 wide and80 high. Step5 is absent; its expected outline uses3 5, while the real signal remains at the baseline. u=40 step period, not a physical time unit. On/off are conceptual sequencer states.Rest12345678OnOffuEight steps diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/music-rhythm-intervals.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/music-rhythm-intervals.svg new file mode 100644 index 0000000000..150367acf0 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/music-rhythm-intervals.svg @@ -0,0 +1 @@ +Time between notesA four-beat measure makes one half-beat onset visible between two regular beats.Mode: conceptual construction. Reference: K07. Beat pitch72 SVG units. Onsets at positions0,72,108,144,216 relative to136. The measure ends at424. Outlined noteheads are onset markers; the drawing does not assert note durations or a complete score. Beat and half-beat dimensions share the same72-unit scale.4 beats1 beat½12&34 diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/music-tuning-fork.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/music-tuning-fork.svg new file mode 100644 index 0000000000..0927744cc0 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/music-tuning-fork.svg @@ -0,0 +1 @@ +A symmetric tuning forkEqual-width prongs and a centered handle grow from two concentric circular bends.Mode: conceptual construction. Reference: P00. u=24 prong width. Inner and outer U bends have radii40 and64 about(280,232). Outer arc-to-handle intersections are derived at x268 and292. The central axis is a construction reference. This stylized silhouette makes no resonance-frequency or manufacturability claim.uR64One symmetry axis diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/nature-offset-rings.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/nature-offset-rings.svg new file mode 100644 index 0000000000..95cf635a95 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/nature-offset-rings.svg @@ -0,0 +1 @@ +Growth shifts the centerSuccessive rings expand while their centers move, leaving the earliest core visibly off-center.Mode: conceptual construction. Reference: P00. Six circular growth contours: center=(250+5i,220), radius=20i, i=1..6. Center drift25=5u with u5. H1 selects the southwest sector of the newest annular band, between the two actual circular boundaries. This is a geometric growth analogy, not a dated tree section.5uNew growthEarlier center diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/nature-seed-spiral.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/nature-seed-spiral.svg new file mode 100644 index 0000000000..534bbc620c --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/nature-seed-spiral.svg @@ -0,0 +1 @@ +An angle becomes a fieldA constant turn and a growing radius organize eighty seeds around one center.Mode: conceptual construction. Reference: P00. Golden angle = pi*(3-sqrt(5)). Plotted seed indices20..99 have radius12sqrt(n); each next angle advances137.507764 degrees. The omitted inner indices leave space to explain the generating turn. Circlesr3.8 represent seeds; outer128 circle is a reference envelope, not a flower edge. The drawn central arc uses the exact angle; display label rounds to137.5 degrees.137.5°One turn, repeated diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/nature-tributaries.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/nature-tributaries.svg new file mode 100644 index 0000000000..841ce3fd01 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/nature-tributaries.svg @@ -0,0 +1 @@ +Small streams, one riverTwo bounded channels merge at one confluence and continue as a single downstream course.Mode: conceptual construction. Reference: E02. Continuous banks and a single inner junction define a Y-shaped channel. Arrows mean conceptual downstream movement. The width w of the lower straight reach spans56 SVG units; no discharge, scale, or hydraulics claim. Cubic banks are intentionally organic and carry no radius labels.ConfluencewTributaryTributary diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-camera-lens.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-camera-lens.svg new file mode 100644 index 0000000000..7fbb09d083 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-camera-lens.svg @@ -0,0 +1 @@ +One optical centerConcentric lens and retaining-ring boundaries share one optical center.Revision 2. Reference P00. Geometric front-view concept. Radii120,108,80,56 share center(280,220). H1 identifies the retaining annulus108≤r≤120, not a material or shading. Axis lines are split around the center and its identifying label. No specific lens construction or optical performance asserted.R56Optical axisRetaining ring⌀240 diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-hinge-panels.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-hinge-panels.svg new file mode 100644 index 0000000000..bc8ed02d22 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-hinge-panels.svg @@ -0,0 +1 @@ +Two panels, one axisAlternating hinge leaves connect two panels around a common axis.Revision 2. Reference C02. Conceptual front elevation. Panels144×168; edge gap64; barrel16×128. Open leaf paths share the barrel boundary without retracing it. The centerline denotes the hinge axis, not an electrical connection. No load, clearances or actual hardware standard asserted.Panel APanel BShared axis64 diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-key-lock.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-key-lock.svg new file mode 100644 index 0000000000..7653fa116e --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-key-lock.svg @@ -0,0 +1 @@ +A key meets its profileA stepped key approaches a receiver with the same profile and alignment.Revision 2. Reference C01. Conceptual fit diagram. Key bow radius36 with15-radius opening; shank profile translated128 units forms the receiver cavity. Tooth starts220,256,292 share pitchu=36. This illustrates a matching profile and does not claim a working lock or authentication system.KeyMatching profile2u diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-umbrella-ribs.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-umbrella-ribs.svg new file mode 100644 index 0000000000..5a21d39260 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-umbrella-ribs.svg @@ -0,0 +1 @@ +Eight ribs, one centerEight equal radial ribs hold a scalloped canopy around one shared hub.Revision 2. Reference P00. Geometric umbrella plan. Ribs radius120 on45° pitch; neighboring canopy tips joined by concave radius100 arcs. Hub radius6. H1 identifies one canopy panel bounded by two ribs, the scalloped edge and hub. Flat illustrative construction, not a fabrication plan.45°⌀240 diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-zipper.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-zipper.svg new file mode 100644 index 0000000000..1c105b28ff --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/object-zipper.svg @@ -0,0 +1 @@ +Two edges become oneTwo tooth rows meet at a slider and continue as one alternating seam.Revision 2. Reference C01. Conceptual zipper front view. Incoming tape edges route to slider boundary. Incoming tooth roots belong to tape edges, so roots are not redrawn. Joined tooth pitchu=12; the shown span3u is36. Slider upper boundary is split at pull tab. The geometry conveys joining, not a textile or tooth manufacturing specification.Two edgesOne seam3u diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/play-almost-fit.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/play-almost-fit.svg new file mode 100644 index 0000000000..99cefec8cc --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/play-almost-fit.svg @@ -0,0 +1 @@ +Almost alignedThe matching circular profiles remain separated by one deliberate vertical offset.Revision 2, catalog candidate. Reference: P00, P02. Planar fit study. Both interface profiles are semicirclesR32. Their centers are(248,212) and(312,236), giving a24-unit vertical mismatch. Both body widths128 are shown. The source tab and receiving recess would match after translation(64,24); no clearance or manufacturing claim is implied.Part APart BR3224128128 diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/play-maze.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/play-maze.svg new file mode 100644 index 0000000000..f5f4fd431e --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/play-maze.svg @@ -0,0 +1 @@ +The interrupted routeOne closed passage stops a route whose continuation remains visible.Revision 2, catalog candidate. Reference: E02, W03. Illustrative planar maze. Wall pitch 56, outer extent224, highlighted route bendsR8. The route enters and exits through actual gaps. The vertical closure at(336,196) interrupts the intended path. Dashed continuation is a hypothetical route, not a hidden wall. No claim of a unique maze solution is made.Blocked22456 diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/play-tangram.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/play-tangram.svg new file mode 100644 index 0000000000..b232210c02 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/play-tangram.svg @@ -0,0 +1 @@ +Seven parts, one squareSeven complementary pieces retain a single square silhouette; one square piece is identified within it.Revision 2, catalog candidate. Reference: P00, C01. Planar tangram partition of a 240-unit square; u=40. Two large triangles, one medium triangle, two small triangles, one square and one parallelogram share their seams. Hatching identifies the square piece, whose horizontal diagonal is 3u. The dashed center projection explains the half-width relationship.6u3uSquare diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/software-common-key.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/software-common-key.svg new file mode 100644 index 0000000000..4bba198c90 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/software-common-key.svg @@ -0,0 +1 @@ +Two records, one keyA shared identifier relates two rows at different positions, carried by a single measured connection.Revision 2. Reference: C02. Conceptual relation, with approved B09 informing the offset-table layout. u=24; both tables144 wide, rows32 high. Key42 links the selected rows. Shared row boundaries have one stroke owner; ports interrupt outside borders. The curved relation usesR24 and carries no transfer or causal claim.R24OrdersPeople17426576172842656u6u diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/software-queue-slot.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/software-queue-slot.svg new file mode 100644 index 0000000000..214c32e7cf --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/software-queue-slot.svg @@ -0,0 +1 @@ +A queue meets a slotEqual work units wait for one matching processing opening. The leading unit and the available slot share one size.Revision 2. Reference: C02. Conceptual allocation. u=48. Queue units48x48 at72 pitch; receiver opening48 high and48 deep. Shapes indicate queue order and capacity, not processor hardware. No inference about throughput.030201uuProcessing slotWaitingNext diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/software-shape-filter.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/software-shape-filter.svg new file mode 100644 index 0000000000..788510c89f --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/software-shape-filter.svg @@ -0,0 +1 @@ +Only what fitsA measured opening admits the smaller circles while the larger profiles remain outside.Revision 2. Reference: P00. Geometric filter analogy. Opening48 high between y208/256; circular items diameter40 leave4 vertical clearance each side. Held square64 and triangleheight56 exceed opening height. Profiles remain unrotated. This is a size-filter metaphor, not a physical sorting machine.48HeldAdmittedOne opening diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/time-availability-overlap.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/time-availability-overlap.svg new file mode 100644 index 0000000000..56cbec084e --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/time-availability-overlap.svg @@ -0,0 +1 @@ +Where availability meetsTwo named availability windows project onto their common two-hour interval.Revision 2. Reference: K07. Conceptual schedule using96 SVG units/hour. Ana09:00–12:00 spans120..408; Lee10:00–13:00 spans216..504. Their10:00–12:00 intersection216..408 is hatched. Hatching means shared availability. Example schedules only.AnaLee09:0012:0010:0013:00Shared2 hours diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/time-schedule-handoff.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/time-schedule-handoff.svg new file mode 100644 index 0000000000..0e49621a72 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/time-schedule-handoff.svg @@ -0,0 +1 @@ +One schedule takes overTwo schedules meet at one time boundary; the handoff aligns the end of one with the start of the other.Revision 2. Reference: K07. Conceptual schedule at84 SVG units/hour. TeamA12:00–14:00 spans112..280; TeamB14:00–16:00 spans280..448. Both intervals168wide. Hatch marks the matching handoff edges, not a duration claim. The shared boundary is drawn once within each row.12:0014:0016:00Team ATeam BHandoff diff --git a/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/time-unfinished-deadline.svg b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/time-unfinished-deadline.svg new file mode 100644 index 0000000000..1c5623c9e2 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/time-unfinished-deadline.svg @@ -0,0 +1 @@ +The deadline arrives firstA fixed deadline interrupts a sequence with completed work, an active step, and unfinished steps still ahead.Revision 2. Reference: B04. Conceptual sequence. u=56 is the step pitch, each step40x40. Four completed steps are hatched, the active step partially hatched, two absent/unfinished steps dashed. Deadline x372 lies between active and unfinished steps. No time estimate or progress percentage is claimed.DeadlineCompleteActiveRemainingu diff --git a/apps/ui/plotter-evals/2026-10-31/follow-up/agent-delegation.svg b/apps/ui/plotter-evals/2026-10-31/follow-up/agent-delegation.svg new file mode 100644 index 0000000000..607550d513 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/follow-up/agent-delegation.svg @@ -0,0 +1,46 @@ + + One task, two agents + A task is divided into research and drafting scopes. The two scopes retain their widths and profiles when separated and assigned to Agent 01 and Agent 02. + Study: agent-delegation, revision 2, awaiting user review. Conceptual diagram. References: K07 part/whole proportions, C01 preserved fragments, C02 allocation. Unit u = 24 SVG units. The task is 14u wide; research and drafting allocations are 6u and 8u. Widths describe the illustrative partition, not effort, duration, cost, or an execution schedule. The two lower forms retain the source partition geometry, translated by (-12,156) and (12,156). The guide pairs connect the source's lower width witnesses to the assigned forms' upper width witnesses; the arrows indicate conceptual assignment, not measured trajectories. No background grid is needed. + + + + + + + + + + + + + + + + + + + + + One task + Research + Draft + Research + Draft + Agent 01 + Agent 02 + + + + + + + + + + + 14u + 6u + 8u + + diff --git a/apps/ui/plotter-evals/2026-10-31/follow-up/before/agent-delegation.svg b/apps/ui/plotter-evals/2026-10-31/follow-up/before/agent-delegation.svg new file mode 100644 index 0000000000..841d262562 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/follow-up/before/agent-delegation.svg @@ -0,0 +1,23 @@ + + A task, divided + One task is partitioned into two complementary pieces assigned to Agent 01 and Agent 02. Their matching stepped edges retain the shape of the whole. + + + + + + + + + + + + + + + + Task + Agent 01 + Agent 02 + + diff --git a/apps/ui/plotter-evals/2026-10-31/follow-up/before/iris-aperture.svg b/apps/ui/plotter-evals/2026-10-31/follow-up/before/iris-aperture.svg new file mode 100644 index 0000000000..ab3c152312 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/follow-up/before/iris-aperture.svg @@ -0,0 +1 @@ +An iris apertureSix conceptual iris blades divide the annular region around a hexagonal opening; one blade is hatched to identify the repeated part. This is an abstract mechanism, not a manufacturing drawing.OpenSix blades · one aperture diff --git a/apps/ui/plotter-evals/2026-10-31/follow-up/iris-aperture.svg b/apps/ui/plotter-evals/2026-10-31/follow-up/iris-aperture.svg new file mode 100644 index 0000000000..e9a1aee139 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/follow-up/iris-aperture.svg @@ -0,0 +1,35 @@ + + Six blades, one aperture + Six repeated blade edges are tangent to one generating circle, leaving a hexagonal aperture. Hatching identifies the visible region of one blade. + Study: mech-iris-aperture, revision 2, awaiting user review. Mode: geometric construction inspired by an iris aperture. Reference: P00 circle, tangency, hierarchy, bounded hatch. C=(280,220); R=120; r=40; u=40 SVG units. For normals n at multiples of 60 degrees and tangent t=(-n.y,n.x), blade edges lie on n dot (P-C)=r. Their outer endpoints are C+r*n+sqrt(R*R-r*r)*t. The six opening vertices lie on radius r/cos(30 degrees), at angles 30+60i degrees. Each visible edge runs from the previous opening vertex to its outer endpoint. H1 hatching denotes one visible blade region, bounded by adjacent edges and the outer circle; it is not material or shading. This illustrative layout does not assert pivot positions, blade thickness, hidden overlap geometry, or verified mechanical motion. The axes and generating circle explain the repetition without a background grid. + + + + + + + + + + + + + + + + + + + + + + + + u + 6u + + + Aperture + Blade 01 + + diff --git a/apps/ui/plotter-evals/2026-10-31/follow-up/notes.md b/apps/ui/plotter-evals/2026-10-31/follow-up/notes.md new file mode 100644 index 0000000000..9ba02d2ab8 --- /dev/null +++ b/apps/ui/plotter-evals/2026-10-31/follow-up/notes.md @@ -0,0 +1,23 @@ +# Delegation and iris follow-up + +The user explicitly liked agent-registration revision 2, then accepted the proposal to work on agent delegation followed by the iris aperture, one at a time. After seeing both drawings, the user said “love them, continue with all the remaining examples, and also want them to be added to the catalogue”. These exact revisions are preserved here and included as AG02 and ME01; registration is AG01. This feedback does not add them to the earlier 27-reference set. + +## Agent delegation + +[Editable SVG](agent-delegation.svg) · [rejected batch version](before/agent-delegation.svg) · study `agent-delegation`, revision 2. + +Primary reference: K07’s proportional relationship between a whole and its parts. C01 supplies the idea of preserving fragment geometry; C02 supplies allocation. The drawing reverses assembly: one task becomes two separately assigned scopes. Unlike the earlier stepped-piece draft, the task and the roles are visible together, and the retained widths connect the two states. + +The source is 336 units wide, partitioned into 144 and 192. The lower forms retain those profiles, separated by 24 units. Dimension labels use `u = 24`. Widths encode an illustrative scope allocation, not time, effort, cost, or dependencies. The projections connect corresponding width witnesses; the arrows mean assignment. No background grid is needed. + +## Iris aperture + +[Editable SVG](iris-aperture.svg) · [rejected batch version](before/iris-aperture.svg) · study `mech-iris-aperture`, revision 2. + +Primary reference: P00’s circle/tangency relationship, focal geometry, and bounded hatch. Six equally spaced tangent edges create a central hexagonal opening within a 240-unit disc. A faint incircle explains the edges, while H1 hatching identifies one visible blade region. Axes and the incircle provide the useful construction; no background grid is added. + +This is an illustrative geometric iris study. Its visible T-junctions suggest overlapping blades, but it makes no claim about hidden blade profiles, pivots, or verified mechanical movement. The SVG metadata records the actual generating geometry. + +## Review scope + +Each candidate was rendered beside its approved reference at native 560 × 440 size. Visual review covers hierarchy, construction, labels, and visible intersections. This is neither a fresh-session skill experiment nor a technical certification. No test suite was run. Positive user feedback applies to these exact two revisions; the liked registration version remains unchanged. diff --git a/apps/ui/scripts/build-plotter-drawings.py b/apps/ui/scripts/build-plotter-drawings.py new file mode 100644 index 0000000000..59ef7b0b60 --- /dev/null +++ b/apps/ui/scripts/build-plotter-drawings.py @@ -0,0 +1,46 @@ +"""Compile the editable catalog SVGs into local React drawing components.""" + +import json +import xml.etree.ElementTree as ET +from pathlib import Path + +from plotter_svg import element + +ROOT = Path(__file__).resolve().parents[1] / "app" / "plotter" +SOURCE = ROOT / "drawings" +manifest = json.loads((SOURCE / "manifest.json").read_text()) + + +components = [] +entries = [] +uses_ids = False +for item in manifest: + svg = ET.parse(SOURCE / item["file"]).getroot() + ids = [n.attrib["id"] for n in svg.iter() if "id" in n.attrib and n.tag.rsplit("}", 1)[-1] not in {"title", "desc"}] + uses_ids |= bool(ids) + name = "Drawing" + "".join(part.title() for part in item["id"].split("-")) + drawing = "\n".join(filter(None, (element(n, ids, 4) for n in svg))) + join = json.dumps(svg.attrib.get("stroke-linejoin", "round")) + cap = json.dumps(svg.attrib.get("stroke-linecap", "butt")) + width = json.dumps(svg.attrib.get("stroke-width", "0.95")) + declaration = " const id = useId();\n" if ids else "" + components.append(f'''function {name}() {{ +{declaration} return ( + + +{drawing} + + + ); +}} +''') + fields = "\n".join(f" {key}: {json.dumps(item[key], ensure_ascii=False)}," for key in ("id", "code", "title", "description", "category")) + entries.append(" {\n" + fields + f"\n illustration: <{name} />,\n }}") + +imports = 'import { useId } from "react";\n' if uses_ids else "" +output = '// Generated from drawings/*.svg by scripts/build-plotter-drawings.py. Edit the SVG sources.\n' +output += imports + 'import { BlueprintDrawing } from "./blueprint-drawing";\nimport type { IllustrationStudy } from "./study-types";\n\n' +output += "\n".join(components) +output += "\nexport const expandedStudies: IllustrationStudy[] = [\n" + ",\n".join(entries) + "\n];\n" +(ROOT / "expanded-studies.tsx").write_text(output) +print(f"Compiled {len(manifest)} editable SVG drawings.") diff --git a/apps/ui/scripts/build-plotter-grammar.py b/apps/ui/scripts/build-plotter-grammar.py new file mode 100644 index 0000000000..9376eec227 --- /dev/null +++ b/apps/ui/scripts/build-plotter-grammar.py @@ -0,0 +1,43 @@ +"""Compile the skill's illustrated grammar into the Plotter page's SVG components.""" + +import xml.etree.ElementTree as ET +from pathlib import Path + +from plotter_svg import element + +ROOT = Path(__file__).resolve().parents[1] +SOURCE = ROOT / "skills" / "plotter" / "references" / "grammar" +components = [] +entries = [] + +for source in sorted(SOURCE.glob("*.svg")): + svg = ET.parse(source).getroot() + svg.attrib.pop("role", None) + svg.attrib.pop("aria-labelledby", None) + svg.set("aria-hidden", "true") + svg.set("focusable", "false") + ids = [ + node.attrib["id"] + for node in svg.iter() + if "id" in node.attrib + and node.tag.rsplit("}", 1)[-1] not in {"title", "desc"} + ] + name = "Grammar" + "".join(part.title() for part in source.stem.split("-")) + components.append( + f"function {name}() {{\n" + + (" const id = useId();\n" if ids else "") + + " return (\n" + + element(svg, ids, 2) + + "\n );\n}\n" + ) + entries.append(f" {source.stem}: <{name} />,") + +output = "// Generated from skills/plotter/references/grammar/*.svg by scripts/build-plotter-grammar.py.\n" +output += 'import { type ReactNode, useId } from "react";\n' +output += 'import type { visualGrammarEntries } from "./visual-grammar-content";\n\n' +output += "\n".join(components) +output += "\nexport const visualGrammarDrawings: Record<\n" +output += ' (typeof visualGrammarEntries)[number]["id"],\n ReactNode\n> = {\n' +output += "\n".join(entries) + "\n};\n" +(ROOT / "app" / "plotter" / "visual-grammar-drawings.tsx").write_text(output) +print(f"Compiled {len(entries)} illustrated grammar topics.") diff --git a/apps/ui/scripts/plotter_svg.py b/apps/ui/scripts/plotter_svg.py new file mode 100644 index 0000000000..5c6c2175d1 --- /dev/null +++ b/apps/ui/scripts/plotter_svg.py @@ -0,0 +1,70 @@ +"""Shared SVG-to-JSX serialization for Plotter catalogue and grammar drawings.""" + +import json +import re + + +def jsx_name(name): + name = name.rsplit("}", 1)[-1] + if name.startswith(("aria-", "data-")): + return name + if name == "class": + return "className" + return re.sub(r"[-:]([a-z])", lambda m: m[1].upper(), name) + + +def js_string(value, ids): + # Each component instance owns its defs, including repeated gallery renders. + dynamic = value + for svg_id in ids: + dynamic = dynamic.replace(f"url(#{svg_id})", f"url(#${{id}}-{svg_id})") + if dynamic == f"#{svg_id}": + dynamic = f"#${{id}}-{svg_id}" + if dynamic != value: + return "`" + dynamic.replace("`", "\\`") + "`" + return json.dumps(value, ensure_ascii=False) + + +def element(node, ids, depth=3): + tag = node.tag.rsplit("}", 1)[-1] + if tag in {"metadata", "title", "desc"}: + return "" + attributes = [] + for key, value in node.attrib.items(): + key = jsx_name(key) + if key == "id": + attributes.append(f"id={{`${{id}}-{value}`}}") + elif key == "style": + styles = [] + for declaration in value.split(";"): + if ":" in declaration: + prop, val = declaration.split(":", 1) + styles.append(f"{json.dumps(jsx_name(prop.strip()))}: {js_string(val.strip(), ids)}") + attributes.append("style={{" + ", ".join(styles) + "}}") + else: + attributes.append(f"{key}={{{js_string(value, ids)}}}") + start = " " * depth * 2 + "<" + tag + if attributes: + start += " " + " ".join(attributes) + children = [] + if node.text and node.text.strip(): + content = json.dumps(node.text.strip(), ensure_ascii=False) + if tag == "style": + # Scope the source stylesheet to this rendered SVG instance. + css = re.sub( + r"#([\w-]+)", + lambda m: f'[id="${{id}}-{m[1]}"]' if m[1] in ids else m[0], + node.text.strip(), + ) + content = "`" + css.replace("`", "\\`") + "`" + children.append(" " * (depth + 1) * 2 + "{" + content + "}") + for child in node: + rendered = element(child, ids, depth + 1) + if rendered: + children.append(rendered) + if child.tail and child.tail.strip(): + children.append(" " * (depth + 1) * 2 + "{" + json.dumps(child.tail.strip(), ensure_ascii=False) + "}") + if not children: + return start + " />" + return start + ">\n" + "\n".join(children) + "\n" + " " * depth * 2 + f"" + diff --git a/apps/ui/skills/plotter/README.md b/apps/ui/skills/plotter/README.md index 65db329e7a..3d49955d75 100644 --- a/apps/ui/skills/plotter/README.md +++ b/apps/ui/skills/plotter/README.md @@ -10,7 +10,9 @@ Copy this complete `plotter` directory into your agent’s skills directory, pre Example: “Use $plotter to show a 20-minute appointment between two 5-minute buffers, as a standalone 560 × 440 SVG.” -`SKILL.md` contains the workflow. The [reference index](references/approved-references.md) maps six bundled SVG snapshots to construction relationships and preserves the wider approved set. The [drafting standard](references/drafting-standard.md) defines current linework; historical visual examples may contain documented exceptions. Outside coss, use the host font or the documented sans-serif fallback. +`SKILL.md` contains the workflow. The [reference index](references/approved-references.md) maps seven bundled SVG snapshots to construction relationships and preserves the wider approved set. The [illustrated visual grammar](references/visual-grammar.md) explains when each kind of mark helps, with full-size SVG studies. The [drafting standard](references/drafting-standard.md) defines current linework; historical visual examples may contain documented exceptions. Outside coss, use the host font or the documented sans-serif fallback. + +The [registration case](references/examples/agent-registration.md) preserves a drawing from a rejected batch and a reference-led revision the user explicitly liked. It explains the generation failure and the focused workflow correction; it is not an additional approved style reference or evidence of broader reliability. ## Source and maintenance diff --git a/apps/ui/skills/plotter/SKILL.md b/apps/ui/skills/plotter/SKILL.md index 0f57841358..318381aca8 100644 --- a/apps/ui/skills/plotter/SKILL.md +++ b/apps/ui/skills/plotter/SKILL.md @@ -9,12 +9,16 @@ Build an editable, flat monochrome illustration around one construction relation ## Establish the reference -Read the [approved reference index](references/approved-references.md), choose the nearest construction relationship, and inspect its bundled SVG. Six snapshots cover geometric construction, modular assembly, time intervals, service concepts, conversation, and absence. They work without the coss checkout; the wider 27-drawing reference set is available when the repository is present. A familiar subject alone is not a sufficient match. +Read the [approved reference index](references/approved-references.md), choose the nearest construction relationship, and inspect its bundled SVG. Seven snapshots cover geometric construction, assembly, allocation, time intervals, service concepts, conversation, and absence. They work without the coss checkout; the wider 27-drawing reference set is available when the repository is present. A familiar subject alone is not a sufficient match. + +Before implementing, identify what the reference contributes: its focal relationship, the size and arrangement of its main forms, and what its guides explain. Preserve those decisions while adapting the subject. Matching stroke values and shape vocabulary alone has produced weak, generic diagrams. For new service or agent concepts, read the [registration failure and revision](references/examples/agent-registration.md) before choosing a composition. It records a rejected version, a user-liked revision, and the reasoning behind the change. The record layout addresses that brief; choose a relevant construction for other subjects. The snapshots establish composition and hierarchy. Some retain historical fills or line treatments; use the [drafting standard](references/drafting-standard.md) for new linework, transparent forms, hatching, and geometric tolerances. Source revisions are recorded in the snapshot manifest. Appearance approval does not certify technical accuracy or every subsequent revision. ## Compose +Read the [illustrated visual grammar](references/visual-grammar.md) before choosing supporting marks. It explains outlines, hatching, grids, arrows, projections, dimensions, axes, and absent forms through full-size construction studies. Choose a mark for the relationship it explains; use the drafting standard for its numerical treatment. + - Use one flat plane. Exclude perspective, isometric views, skewed or tilted objects, physical depth, glossy 3D, colorful gradients, and science-class atom diagrams. Diagonal radius lines and callout leaders can explain geometry without tilting the form. - Keep the palette black, off-white, and soft gray. Adapt those values to the host’s theme while preserving the monochrome hierarchy. - Make the main form read first, meaningful construction guides second, sparse dimensions and labels third. Hairline outlines must remain visible at the intended display size. @@ -28,7 +32,7 @@ Deliver editable SVG or the host’s existing vector components. The references Inherit the host font, or use `Cal Sans, sans-serif` with a sans-serif fallback when Cal Sans is unavailable. Keep automatic optical sizing, no added letter-spacing, no synthesized styles, and one GEOM value per text role. Use typeset punctuation. A standalone task does not require the coss checkout or its font files. -Inspect the rendered drawing at its intended size and as a thumbnail; check narrow layouts when embedding it. Preserve identifying labels before secondary dimensions. Resolve these questions: +Place the rendered candidate beside its chosen reference at the same intended size, then inspect both as thumbnails; check narrow layouts when embedding it. Compare the prominence and proportions of the main form, the spatial relationship carrying the idea, and the useful construction detail. A successful render is not a successful composition. Preserve identifying labels before secondary dimensions. Resolve these questions: 1. Does one central gesture survive at a glance? 2. Can the subject be identified from the form and restrained labels? @@ -38,6 +42,8 @@ Inspect the rendered drawing at its intended size and as a thumbnail; check narr If meaning is unclear, improve the structural cue or labeling before adding detail. If the composition is weak, improve its arrangement before adding guides. A passing code check does not answer either design question. +For a collection, establish this comparison with one representative drawing before generating the rest. Work through the remaining subjects in manageable groups, retaining reference comparison and revision. Large uncalibrated batches have repeated the same compositional failure across otherwise different subjects. This is an internal refinement step, not a requirement to seek permission or reduce the user's requested scope; follow an explicit request to review a pilot before expanding. + ## Task-specific guidance - **Integrating into coss or revising its motion examples:** read [SVG and coss integration](references/implementation.md), then the applicable repository instructions. Preserve the host page’s layout unless a change was requested. diff --git a/apps/ui/skills/plotter/references/approved-references.md b/apps/ui/skills/plotter/references/approved-references.md index 4baea6c6aa..fd2915015f 100644 --- a/apps/ui/skills/plotter/references/approved-references.md +++ b/apps/ui/skills/plotter/references/approved-references.md @@ -4,18 +4,19 @@ On 2026-10-25, the user explicitly identified these 27 drawings as the best illu ## Bundled starting points -Choose by construction relationship, then render and inspect the linked SVG. These six snapshots are available in a standalone installation: +Choose by construction relationship, then render and inspect the linked SVG. These seven snapshots are available in a standalone installation: | Relationship | Reference | What to borrow | | --- | --- | --- | | Circle, square, radius, and tangency | [P00](drawings/P00.svg) | A shared geometric construction with one focal relationship | +| Separate fragments becoming one record or control | [C01](drawings/C01.svg) | Preserved dimensions and positions, projection, and one assembled focal form | | Modules following one allocation rule | [C02](drawings/C02.svg) | Repeated units, spacing, and a coherent component family | | Time intervals combining into a whole | [K07](drawings/K07.svg) | Proportional lengths and restrained duration labels | | Work aligned to a cycle | [B04](drawings/B04.svg) | A scoped service concept organized by a shared time field | | Conversation expressed with a few UI fragments | [I06](drawings/I06.svg) | Recognition through avatars, tail-free messages, and spacing | | Absence interrupting a structure | [E02](drawings/E02.svg) | A missing connection that affects the construction itself | -[The manifest](drawings/manifest.json) records source revision and snapshot hashes. These are source snapshots of selected compositions, not newly normalized or technically certified drawings. Historical background fills and line-role variations may remain; use the [drafting standard](drafting-standard.md) for new work. Render the snapshots on a light surface to inspect their original presentation. +[The manifest](drawings/manifest.json) records source revision and snapshot hashes; a reference-level `source_commit` overrides the manifest's shared revision. These are source snapshots of selected compositions, not newly normalized or technically certified drawings. Historical background fills and line-role variations may remain; use the [drafting standard](drafting-standard.md) for new work. Render the snapshots on a light surface to inspect their original presentation. Bundling C01 adds access to an already approved composition; the 27-reference selection remains unchanged. ## Full reference set diff --git a/apps/ui/skills/plotter/references/direction.md b/apps/ui/skills/plotter/references/direction.md index ce0600b6e0..df771258b3 100644 --- a/apps/ui/skills/plotter/references/direction.md +++ b/apps/ui/skills/plotter/references/direction.md @@ -2,6 +2,41 @@ Evidence captured from the coss UI illustration reviews on 2026-10-24–25 and the fresh-session evaluation on 2026-10-31. This is a living art-direction record, not a claim that every study has been approved. +## Visual grammar on the page — 2026-10-31 + +The user expected the grammar on the Plotter page and found the initial small paired illustrations “poor”. The response adds a visible navigation link and a section at `/ui/plotter#visual-grammar`, then replaces the worksheet-sized pairs with full `560 × 440` studies and a useful/counterexample toggle. Registration, delegation, and iris reuse the liked construction arrangements; the other topics use fuller geometric constructions. Counterexamples change the supporting marks while retaining the main geometry. This is the implementation response to the critique, not new approval of these teaching examples. + + +The user then said they “love the new grammar examples”, asked to remove the counterexamples, and proposed hiding unrelated elements on hover. Keep these eight full-size drawings and their original appearance. The page isolates the topic’s marked elements on hover, keyboard focus, or a touch toggle; leaving the interaction restores the full drawing. Counterexample assets and controls were removed. This is positive feedback on the teaching drawings, without changing the earlier 27-reference selection. + +## Catalogue removals after expansion — 2026-10-31 + +The user requested removal of 36 distinct studies: + +- **“Not visually interesting”:** OB01, OB02, OB03, OB05, ME03, AR04, AR05, NA03, NA04, NA05, MU01, MU03, MU04, MU05, MP01, MP02, MP03, UI04, UI05, SW01, SW04, SW05, TM01, TM04, TM05, AB01, AB02, AB03, AB04, AB05, PL01, PL02, PL05. +- **Too similar to existing catalogue entries:** AR01 and GE02. +- **Explicit follow-up:** “remove OB04 too”. + +OB03 and AB05 were each repeated in the request; each is removed once. The active catalogue now contains 100 illustrations: 58 earlier drawings and 42 expansion entries. Preserve their codes and IDs without closing numbering gaps. Removed SVGs and metadata are retained only as review evidence in `apps/ui/plotter-evals/2026-10-31/catalog-expansion/removed/`. + +This feedback rejects these particular drawings, not their entire subject categories. It does not promote the remaining entries to approved references. The stated visual-interest critique should not be replaced with an invented technical diagnosis. + +## Liked revisions, catalogue expansion, and mark selection — 2026-10-31 + +After liking registration revision 2, the user also liked the reference-led delegation and iris revisions: “love them, continue with all the remaining examples, and also want them to be added to the catalogue”. Preserve those three appearances as AG01, AG02, and ME01. The initial implementation added all 78 requested subjects to the existing 58 drawings, with editable SVGs and stable IDs. The subsequent removals are recorded above; the 39 other retained revisions still await individual feedback. Catalogue inclusion does not expand the 27-reference set. The repository’s `apps/ui/plotter-evals/2026-10-31/catalog-expansion/notes.md` records this pass. + +The user asked for diagonal lines on W05’s cubes, then clarified: “W05: only cubes should have diagonal lines”. Hatch only the square core faces in both the static drawing and its animation. Leave carrier frames and surrounding machinery unhatched; keep the hatch attached to the moving core. This is a W05-specific preference, not a requirement to hatch every object. + +The user also requested a short illustrated explanation of drawing elements. The [visual grammar](visual-grammar.md) pairs useful and unnecessary applications of each mark. It explains selection and meaning while the drafting standard remains authoritative for line values. Do not treat the available marks as a checklist to include in every drawing. + +## Rejected broad batch and single-subject revision — 2026-10-31 + +The user described the subsequent 78-drawing batch as “low quality” and “not consistent with our rules and the other examples.” They authorized the proposed response: pause expansion, rebuild one agent-registration illustration beside approved references, and use the comparison to improve the skill. This is collective rejection of the batch, not individual feedback on every concept. + +Our diagnosis is that generating large groups before establishing visual fit reproduced surface ingredients without sufficiently developing the construction relationships. The final inspection caught obvious defects but did not reject weak compositions. This repeats the earlier rejected service concepts below. The user's request to skip test suites did not remove the need for design judgment. + +The [registration case](examples/agent-registration.md) preserves the rejected version, the chosen C01/C02/I06 references, and the proposed revision. The main skill now requires a concrete reference mapping and a same-size visual comparison before expanding a collection. After viewing the revision and being asked whether it felt closer to the desired style, the user replied “i love it”. Preserve revision 2’s appearance and record this as positive feedback on that exact drawing. The subsequent catalogue request is recorded above; praise for registration alone is not evidence of reliability across other subjects. The existing 27-reference selection and the three previously liked experiments remain intact. + ## Fresh-session experiments and grid comparison — 2026-10-31 The user liked all three fresh-session experiments and later requested their original versions in the catalog: diff --git a/apps/ui/skills/plotter/references/drawings/C01.svg b/apps/ui/skills/plotter/references/drawings/C01.svg new file mode 100644 index 0000000000..831bfe5d5c --- /dev/null +++ b/apps/ui/skills/plotter/references/drawings/C01.svg @@ -0,0 +1 @@ + diff --git a/apps/ui/skills/plotter/references/drawings/manifest.json b/apps/ui/skills/plotter/references/drawings/manifest.json index a94cacbffd..67e11390d7 100644 --- a/apps/ui/skills/plotter/references/drawings/manifest.json +++ b/apps/ui/skills/plotter/references/drawings/manifest.json @@ -11,6 +11,15 @@ "sha256": "4082ec9a1000107cb1580fcd6826cff15cc15c2c3d919a79291d1d7070cce1d4", "source": "apps/ui/app/plotter/primitives-blueprint.tsx" }, + { + "code": "C01", + "id": "particles-assembly", + "title": "An assembly of parts", + "file": "C01.svg", + "sha256": "4b203778a9d05394ca1f436f09962330d712b998f9996fd195e61cdb24cc99f0", + "source": "apps/ui/app/plotter/system-studies.tsx", + "source_commit": "f9482a320d2afaf502b2646ac3addf8c3cf75abb" + }, { "code": "C02", "id": "particles-slots", diff --git a/apps/ui/skills/plotter/references/examples/agent-registration-before.svg b/apps/ui/skills/plotter/references/examples/agent-registration-before.svg new file mode 100644 index 0000000000..3a0150e54b --- /dev/null +++ b/apps/ui/skills/plotter/references/examples/agent-registration-before.svg @@ -0,0 +1,43 @@ + + A place in the registry + An independent agent moves into a matching registry cell. Its circular identity mark and three capability marks stay the same; its new row is identified as agent_07. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Agent + Registry + agent_07 + + + agent_06 + agent_08 + Unlisted + Register + + diff --git a/apps/ui/skills/plotter/references/examples/agent-registration.md b/apps/ui/skills/plotter/references/examples/agent-registration.md new file mode 100644 index 0000000000..96bed6c68c --- /dev/null +++ b/apps/ui/skills/plotter/references/examples/agent-registration.md @@ -0,0 +1,43 @@ +# Agent registration: failure and revision + +## Status and brief + +On 2026-10-31 the user rejected a 78-drawing batch as low quality and inconsistent with the established examples and rules. Agent registration was included in that collective rejection; it did not receive a separate verdict. The user authorized rebuilding this one subject against approved references and using the comparison to improve the skill before further expansion. + +- [Before](agent-registration-before.svg): preserved from the rejected batch. +- [Revision 2](agent-registration.svg): explicitly liked by the user on 2026-10-31: “i love it”. Preserve this visual revision. +- [C01](../drawings/C01.svg): the approved assembly reference used for this revision. [C02](../drawings/C02.svg) supplies allocation and spacing; [I06](../drawings/I06.svg) supplies restrained identity fragments. + +The user’s response is positive evidence for this exact revision. The existing 27-reference selection remains unchanged. Use this case to understand the comparison and refinement process; assembly is not necessarily the right metaphor for every agent concept. + +## What failed + +The earlier drawing placed an 80-unit agent tile beside a three-row registry table, duplicated its small identity/capability marks in a row, and joined them with a labeled transfer arrow. Our diagnosis is that the marks and labels explained the subject more than the composition did. The table occupied more attention than the registration relationship. Correct stroke values did not make that arrangement fit the established work. + +The production process amplified the weakness: large batches were generated before a close reference comparison, and the final contact-sheet pass largely looked for conspicuous defects. The same generic node-and-connector tendency had already been rejected in earlier service proposals. That lesson needs to influence new concepts, not remain available only when revisiting an old study. + +## What the revision borrows + +| Decision | Approved evidence | Registration response | +| --- | --- | --- | +| Make assembly visible | C01 repeats the same fragments in separate and assembled states | Identity, name, and tools retain their geometry and horizontal positions in one registry record | +| Give the result enough presence | C01 has a substantial enclosing record; C02 organizes fragments within a shared allocation | A 336 × 96 record is the main form, with secondary guides outside and within its insets | +| Make spacing explain a relationship | C02's gaps and allocations relate real boundaries | A 24-unit inset/gap system allocates space to the identity, name, and tools | +| Keep recognition economical | I06 uses a small avatar and modest text fragments | One avatar, “Research agent,” and “Read”/“Search” labels establish the subject; an assigned example ID distinguishes the record | +| Preserve line roles | The drafting standard separates forms, detail, projection, and movement | Primary outer forms remain full-strength; face details, field baseline, projections, and assembly arrow use their respective subordinate styles | + +The same-size comparison prompted two refinements before presentation: reducing the initial 384-unit record to 336 units removed unused name-field space, and restoring primary strokes to the loose forms prevented them from reading as absent or disabled. A separate visual review prompted a clearer “Registered agent” label, stronger identifier text, and replacing the overall height dimension with the second equal allocation gap. These explanations are the assistants’ interpretation of the changes. The user subsequently liked the finished revision as a whole; they did not separately endorse each explanation as a general rule. + +## Meaning and bounds + +This is a conceptual illustration of assembling an indexed registry record from an agent definition. It does not specify an API, authentication, permission grants, persistence guarantees, or execution. The tools are descriptive capabilities in this example. A generic avatar supplies an identity cue, not a claim that the agent is a human. + +The local unit is `u = 24` SVG units. The horizontal allocation is `1u + 2u + 1u + 5u + 1u + 3u + 1u = 14u`, comprising inset, identity, gap, name, gap, tools, inset. The record is `14u × 4u`. The repeated fragments translate vertically by 148 units. The new identifier occupies the existing name allocation. Metadata in the editable SVG records the same mapping. + +No background grid is needed: the fragment projections already explain alignment. There is no hatch because no region needs an additional meaning. Do not add either merely to increase visual density. + +## How to use this evidence + +Render the approved reference, candidate, and rejected drawing at `560 × 440`, with a common font and surface. Compare the main form's presence, the relationship visible without the explanatory caption, and the purpose of each guide. Also inspect the thumbnail for loss of the central gesture. Return to the composition when those relationships are weak; do not repair them by adding decorative drafting marks. + +The revision has received a visual comparison, not a fresh-session skill evaluation or a technical certification. No test suite was requested for this pass. Keep the rejected originals separate from the catalogue’s later reference-led revisions. The positive response supports this one revision; explore other construction relationships in small groups before drawing conclusions about broader reliability. The subsequent request to add all revised subjects is recorded in [direction and feedback](../direction.md#liked-revisions-catalogue-expansion-and-mark-selection--2026-10-31). diff --git a/apps/ui/skills/plotter/references/examples/agent-registration.svg b/apps/ui/skills/plotter/references/examples/agent-registration.svg new file mode 100644 index 0000000000..0d0264275a --- /dev/null +++ b/apps/ui/skills/plotter/references/examples/agent-registration.svg @@ -0,0 +1,71 @@ + + An agent, registered + An identity, name, and two tools align into one registry record. The identity and tool shapes retain their sizes and horizontal positions. The record receives the example identifier agent_07. + Study: agent-registration, revision 2, explicitly liked by the user on 2026-10-31. Mode: conceptual diagram. Registration is represented as the assembly of one discoverable record from an agent definition; this does not depict authentication, permission grants, or execution. References: C01 assembly, C02 allocation, I06 identity fragments. Local unit u = 24 SVG units. Record width 14u = 336; height 4u = 96; horizontal allocation 1u inset + 2u identity + 1u gap + 5u name + 1u gap + 3u tools + 1u inset. Source and assembled fragments differ only by a vertical translation of 148 units. The assigned identifier is new text within the name allocation. No background grid or hatch is needed. + + + + + + + + + + + + + + + + + + + + + + Research agent + Read + Search + + + Identity + Name + Tools + + + + + + + + Registered agent + + + + + + + + + + + + + Research agent + Read + Search + + agent_07 + + + + + + + + + 14u + u + u + + diff --git a/apps/ui/skills/plotter/references/grammar/arrows.svg b/apps/ui/skills/plotter/references/grammar/arrows.svg new file mode 100644 index 0000000000..ad5d296603 --- /dev/null +++ b/apps/ui/skills/plotter/references/grammar/arrows.svg @@ -0,0 +1,4 @@ + +A quarter turn, directedOne rigid capsule rotates from a solid horizontal pose into its dashed vertical proposal. The arrow follows a true quarter circle and points toward the proposed position.Plotter teaching construction, native560×440. References: P00 radius and orthogonal geometry; E02 curved path; C01 preserved fragment geometry. u=24 SVG units. Capsule end centers are(200,284),(392,284), separation192=8u; radius24, width48=2u. Proposed pose is a90° counterclockwise rotation about(200,284), taking the right center to(200,92). The R216 transfer arc is outside the moving part. R64 angle witness shares the pivot. Motion dash4 6 and solid open6-unit45°legs. This is a geometric movement illustration, not a load-bearing mechanism specification. +90°Quarter turnProposedCurrent8u2uOne pivot · one rigid part + diff --git a/apps/ui/skills/plotter/references/grammar/centerlines.svg b/apps/ui/skills/plotter/references/grammar/centerlines.svg new file mode 100644 index 0000000000..bf35a446e1 --- /dev/null +++ b/apps/ui/skills/plotter/references/grammar/centerlines.svg @@ -0,0 +1 @@ +Six slots, one centerSix curved slots share a pitch circle and one center. Three centerlines locate their radial midpoints, with a sixty-degree angle establishing the repeating interval.Geometric teaching construction after P00 circular construction. Native560×440. Ring center(280,220), outerR132 and innerR40. Six curved slots at angular centers -90,-30,30,90,150,210 degrees; centerline radius92, inner84, outer100, roundcapR8. Each spans30°; centers repeat60°. This is a planar repetition study, not a manufactured part specification. Centerlines and theoretical pitch circle use house8 3 1 3 dash; angle witness centered on(280,220) and attached to two true radial midlines.60°Pitch⌀2646 equal intervals diff --git a/apps/ui/skills/plotter/references/grammar/dimensions.svg b/apps/ui/skills/plotter/references/grammar/dimensions.svg new file mode 100644 index 0000000000..3ba48c6e91 --- /dev/null +++ b/apps/ui/skills/plotter/references/grammar/dimensions.svg @@ -0,0 +1 @@ +The dimensions of an offsetA quarter-turn band is generated by concentric radii 120 and 80. Its width is seven units, its offset is one unit, and its radius leader meets the actual outer arc.Geometric teaching construction after approved P02. Native 560 × 440; u=40. Center(280,248); outer radius120=3u, inner radius80=2u; band offset40=u. Horizontal left x120 and right x400 give total width280=7u. No physical material or load claim. Radius leader ends at(280+120/sqrt(2),248-120/sqrt(2)). Principal arcs and generating-circle remainders have separate stroke ownership.7uuR120Offsetu = 40 diff --git a/apps/ui/skills/plotter/references/grammar/grids.svg b/apps/ui/skills/plotter/references/grammar/grids.svg new file mode 100644 index 0000000000..26fad04f5d --- /dev/null +++ b/apps/ui/skills/plotter/references/grammar/grids.svg @@ -0,0 +1 @@ +A letter on its moduleA modular letter P is constructed with a 32-unit stem, a 96-unit outer bowl radius, and a 64-unit counter radius. The 32-unit grid locates their shared steps.Plotter visual grammar: grids. Approved references: T01 for letter anatomy, P00 for a visible generating field. Native 560 × 440. Local unit u=32. Stem x184..216, y96..352; outside bowl center(280,192), R96=3u; counter R64=2u. The primary letter has no solid fill. The useful grid begins(152,64), pitch 32, and is split around coincident outline segments. A grid is used because the stem, counter, heights and radii share the actual unit field, not merely because the subject is geometric.3u2u8uuu = 32 diff --git a/apps/ui/skills/plotter/references/grammar/hatching.svg b/apps/ui/skills/plotter/references/grammar/hatching.svg new file mode 100644 index 0000000000..2d26b0a32f --- /dev/null +++ b/apps/ui/skills/plotter/references/grammar/hatching.svg @@ -0,0 +1,29 @@ + + One blade, one bounded region + The selected blade keeps a consistent diagonal hatch inside its visible boundaries. The central aperture stays open. + Plotter visual grammar: hatching. This teaching composition reuses the user-liked ME01 iris arrangement, whose approved construction reference is P00. Center(280,220), outside radius120, generating radius40. H1 at -45 degrees and 12-unit edge-intercept pitch identifies one visible blade region. No material or verified mechanism is asserted. The clipping path is bounded by two visible blade edges and the outer circle; the opening is excluded. + + + + + + + + + + + + + + + + + + u + 6u + + + Aperture + Blade 01 + + diff --git a/apps/ui/skills/plotter/references/grammar/outlines.svg b/apps/ui/skills/plotter/references/grammar/outlines.svg new file mode 100644 index 0000000000..7133e2c822 --- /dev/null +++ b/apps/ui/skills/plotter/references/grammar/outlines.svg @@ -0,0 +1,71 @@ + + Hierarchy within an assembled record + Identity, name, and tools assemble into one record. Main silhouettes remain primary; face details, dimensions, projections, and the transfer arrow remain subordinate. + Plotter grammar teaching example. Adapted from the user-liked registration revision, with C01 assembly, C02 allocation, and I06 identity as approved composition references. The drawing preserves the source line hierarchy. Local unit u=24; record336x96; source fragments translate148 vertically. No background grid or decorative hatch. + + + + + + + + + + + + + + + + + + + + + + Research agent + Read + Search + + + Identity + Name + Tools + + + + + + + + Registered agent + + + + + + + + + + + + + Research agent + Read + Search + + agent_07 + + + + + + + + + 14u + u + u + + diff --git a/apps/ui/skills/plotter/references/grammar/projections.svg b/apps/ui/skills/plotter/references/grammar/projections.svg new file mode 100644 index 0000000000..6f923eb6f2 --- /dev/null +++ b/apps/ui/skills/plotter/references/grammar/projections.svg @@ -0,0 +1,46 @@ + + A whole, projected into its parts + The Research and Draft allocations retain their widths after separation. Four quiet projections connect the exact width witnesses of each source allocation with its assigned form. + Plotter grammar teaching example, adapted from the user-liked delegation revision. Approved references K07 part/whole proportion, C01 preserved fragments, C02 allocation. Unit u=24. Whole width336 partitions into144 and192; lower forms translate(-12,156) and(12,156). Projection lines extend the corresponding allocation edges, with8-unit clearances from forms. No background grid or extra hatch. + + + + + + + + + + + + + + + + + + + + + One task + Research + Draft + Research + Draft + Agent 01 + Agent 02 + + + + + + + + + + + 14u + 6u + 8u + + diff --git a/apps/ui/skills/plotter/references/grammar/states.svg b/apps/ui/skills/plotter/references/grammar/states.svg new file mode 100644 index 0000000000..84352f0155 --- /dev/null +++ b/apps/ui/skills/plotter/references/grammar/states.svg @@ -0,0 +1,4 @@ + +The channel behind the faceA solid front plate and circular opening stay visible while dashed boundaries describe a rear channel. The hidden state is named in the callout and view label.Plotter teaching drawing, native560×440. References:P00 focal circular construction; C02 commonframe and sparse measuredallocation; E02 geometry distinguished by state. Simplified front view of a hypothetical plate with a rearchannel; no manufacturing,pressure,orflow-performance claim. Visibleface336×248 withR16corners, centralopeningR52, mount holesR6. The hidden channel is16wide: outerbendR96, innerbendR80, centered(280,220), terminated byR8caps at(184,132)and(184,308). VisibleportopeningsR3.5. The304lowerdimension spans actualcorner tangenciesx128..432. Hiddenedges use3 5; centeraxes use8 3 1 3. No background grid or area tint. +Port APort BHiddenrear channel336R52304Front view · channel concealed behind the face + diff --git a/apps/ui/skills/plotter/references/visual-grammar.md b/apps/ui/skills/plotter/references/visual-grammar.md new file mode 100644 index 0000000000..d02dd160dc --- /dev/null +++ b/apps/ui/skills/plotter/references/visual-grammar.md @@ -0,0 +1,81 @@ +# Visual grammar + +Start with the main form. For each supporting mark, name the relationship it explains. If removing the mark leaves that relationship equally clear, omit it. A drawing does not need every element below. + +The user liked these eight **560 × 440** teaching constructions. On the Plotter page at `/ui/plotter#visual-grammar`, hovering, keyboard focus, or the touch toggle hides unrelated elements and isolates the marks named by each topic. The SVG’s `data-grammar-focus` attributes identify those marks; the complete drawing stays intact. Counterexamples were removed at the user’s request. + +Use the [drafting standard](drafting-standard.md#house-stroke-palette) for numerical treatment and the [approved references](approved-references.md) for composition. Registration, delegation, and iris provide additional user-liked evidence for these teaching adaptations; the approved reference selection remains unchanged. + +## Primary and secondary outlines + +![The fragments and completed record read first.](grammar/outlines.svg) + +Identity, name, and tools assemble into one record. Primary outlines hold the loose fragments and finished form together; quieter face details and construction lines support them. Keep supporting marks subordinate. Giving every stroke equal weight makes the construction compete with the subject. + +An important loose fragment can remain primary even when small. The [registration revision](examples/agent-registration.md) preserves that hierarchy. + + +## Diagonal hatching + +![One blade is selected; its boundaries and the central opening stay clear.](grammar/hatching.svg) + +Hatching singles out one bounded region. Here it identifies a visible iris blade while the aperture remains clear. Its direction and spacing stay consistent across that region. Omit hatching when no region needs distinguishing. It is not a lighting effect or a material label. + +A hatch describes a selected region or a stated cut, not generic lighting. A diagonal pattern alone does not identify a material. See [hatch grammar](drafting-standard.md#hatch-grammar). + + +## Background grids + +![The grid reveals one module across the stem, counter, and bowl.](grammar/grids.svg) + +A grid makes a shared construction module visible. This letter’s stem, counter, and bowl radii all follow the same 32-unit field. Omit a background grid when local guides already explain the relationship. Geometric subject matter alone is not a reason to add one. + +Geometric content alone is insufficient. Keep a grid when its coordinate or module system explains the drawing; calendar cells can also be the subject itself. + + +## Movement and transfer arrows + +![The arrow carries the quarter turn toward the proposed position.](grammar/arrows.svg) + +One rigid part turns around a shared pivot. The movement arrow names the direction from its current position to the proposed one. Use an arrow only when a change or direction matters; alignment and equal size need different marks. + +Movement and conceptual transfer use the house **`4 6` shaft with a solid open head**. Alignment and size use projections or dimensions instead. A functional connector needs its own stated connectivity meaning. + + +## Projection guides + +![Four quiet guides preserve the same two allocations.](grammar/projections.svg) + +A whole task separates into two assigned scopes. Each projection connects the same width witness before and after separation, so the parts retain a visible relationship to the whole. Omit guides that cannot name both corresponding features. Nearby endpoints are not enough: the alignment must be exact. + +Keep projections subordinate, stop them before labels and contours, and give each coincident segment one stroke owner. + + +## Dimensions and radius leaders + +![One unit system connects the width, offset, and actual circular radius.](grammar/dimensions.svg) + +Dimensions reveal the proportions that construct a form. Here a concentric bend keeps a constant offset, while its width and radius use the same unit. Omit measurements that do not explain the construction. Every witness and radius leader must reach the feature it measures. + +Derive the label and its witnesses from the same geometry. A radius leader reaches the actual arc along its generating radius. See [geometry and dimensions](drafting-standard.md#geometry-and-dimensions). + + +## Centerlines and loci + +![One center and one pitch circle locate all six repeated slots.](grammar/centerlines.svg) + +Centerlines and a pitch circle reveal how repeated features share one center. Six curved slots follow the same circle at equal angular intervals. Omit axes and loci that do not explain symmetry, rotation, or placement. A dashed guide must pass through the geometry it claims to organize. + +The house **`8 3 1 3`** pattern identifies axes, centers, and theoretical pitch loci. A direct generating radius can instead use continuous secondary linework. + + +## Absent, hidden, and proposed outlines + +![The view and callout make the hidden channel unambiguous.](grammar/states.svg) + +The dashed channel lies behind a visible front face. Its label explains why the geometry is not shown as a solid outline. The same dashed outline may mean hidden, absent, or proposed geometry. Name the state when the form alone cannot distinguish it. + +The house **`3 5`** outline means hidden or absent geometry. A proposed future form uses that absent outline with a state label and, when useful, a separate movement arrow. If hidden and absent features coexist, label their states or separate the views. + + +The main form should still read without its supporting marks. If it does not, revisit the composition before adding drafting detail. From fff9dea6c1546e2de21758511424a707a0dbda1b Mon Sep 17 00:00:00 2001 From: pasqualevitiello Date: Sat, 31 Oct 2026 15:48:20 +0100 Subject: [PATCH 12/15] fix(ui): preserve grammar marks during isolation --- apps/ui/app/plotter/README.md | 2 + .../app/plotter/grammar-comparison.module.css | 40 +++---------------- apps/ui/app/plotter/visual-grammar-content.ts | 16 ++++---- .../app/plotter/visual-grammar-drawings.tsx | 20 +++++----- .../ui/skills/plotter/references/direction.md | 2 + .../plotter/references/grammar/arrows.svg | 2 +- .../references/grammar/centerlines.svg | 2 +- .../plotter/references/grammar/dimensions.svg | 2 +- .../plotter/references/grammar/grids.svg | 2 +- .../plotter/references/grammar/outlines.svg | 2 +- .../plotter/references/grammar/states.svg | 2 +- .../plotter/references/visual-grammar.md | 14 ++++--- 12 files changed, 43 insertions(+), 63 deletions(-) diff --git a/apps/ui/app/plotter/README.md b/apps/ui/app/plotter/README.md index 77920e3293..6901b79d9b 100644 --- a/apps/ui/app/plotter/README.md +++ b/apps/ui/app/plotter/README.md @@ -40,6 +40,8 @@ The generated `expanded-studies.tsx` uses the existing drawing wrapper and gives The visual grammar uses the skill’s editable SVGs as its source. Hover, keyboard focus, or the touch toggle isolates the topic’s marks; `data-grammar-focus` identifies those elements in the source. After editing `skills/plotter/references/grammar/*.svg`, run `python3 apps/ui/scripts/build-plotter-grammar.py` from the repository root and format `visual-grammar-drawings.tsx`. The page explanations live in `visual-grammar-content.ts`. +Isolation preserves the original stroke treatment. The grid’s missing shared segments use `data-grammar-isolation-only` and are hidden in the complete drawing, then revealed at the same grid weight when the surrounding geometry disappears. Callouts are not part of a focused element unless that topic explains callouts. + ## Plotter skill The visible Visual grammar section precedes the illustrations and is reachable from the top navigation at `/ui/plotter#visual-grammar`. diff --git a/apps/ui/app/plotter/grammar-comparison.module.css b/apps/ui/app/plotter/grammar-comparison.module.css index 1f5ed7e746..2eadcbd2b3 100644 --- a/apps/ui/app/plotter/grammar-comparison.module.css +++ b/apps/ui/app/plotter/grammar-comparison.module.css @@ -1,9 +1,3 @@ -.drawing:is([data-isolated], :focus-visible) - svg:not([data-grammar-preserve-contrast="true"]) - g:not(defs *, clipPath *, mask *, pattern *, marker *, symbol *) { - opacity: 1; -} - .drawing:is([data-isolated], :focus-visible) svg :is( @@ -31,26 +25,12 @@ } .drawing:is([data-isolated], :focus-visible) - svg:not([data-grammar-preserve-contrast="true"]) - :is([data-grammar-focus="true"], [data-grammar-focus="true"] *):not( - defs *, - clipPath *, - mask *, - pattern *, - marker *, - symbol * - ) { - opacity: 1; - stroke-opacity: 1; + svg + [data-grammar-isolation-only="true"] { + visibility: visible; } @media (hover: hover) and (pointer: fine) { - .drawing:hover - svg:not([data-grammar-preserve-contrast="true"]) - g:not(defs *, clipPath *, mask *, pattern *, marker *, symbol *) { - opacity: 1; - } - .drawing:hover svg :is( @@ -77,17 +57,7 @@ opacity: 0; } - .drawing:hover - svg:not([data-grammar-preserve-contrast="true"]) - :is([data-grammar-focus="true"], [data-grammar-focus="true"] *):not( - defs *, - clipPath *, - mask *, - pattern *, - marker *, - symbol * - ) { - opacity: 1; - stroke-opacity: 1; + .drawing:hover svg [data-grammar-isolation-only="true"] { + visibility: visible; } } diff --git a/apps/ui/app/plotter/visual-grammar-content.ts b/apps/ui/app/plotter/visual-grammar-content.ts index b29a0c43a3..d9a5361474 100644 --- a/apps/ui/app/plotter/visual-grammar-content.ts +++ b/apps/ui/app/plotter/visual-grammar-content.ts @@ -28,9 +28,9 @@ export const visualGrammarEntries = [ id: "arrows", title: "Movement and transfer arrows", description: - "One rigid part turns around a shared pivot. The movement arrow names the direction from its current position to the proposed one.", + "The curved dashed line and its solid open arrowhead show a rigid part turning from its current position to the proposed one. The nearby “Quarter turn” text is a callout, not part of the arrow.", omit: "Use an arrow only when a change or direction matters; alignment and equal size need different marks.", - caption: "The arrow carries the quarter turn toward the proposed position.", + caption: "On hover, only the dashed shaft and solid arrowhead remain.", }, { id: "projections", @@ -55,14 +55,16 @@ export const visualGrammarEntries = [ description: "Centerlines and a pitch circle reveal how repeated features share one center. Six curved slots follow the same circle at equal angular intervals.", omit: "Omit axes and loci that do not explain symmetry, rotation, or placement. A dashed guide must pass through the geometry it claims to organize.", - caption: "One center and one pitch circle locate all six repeated slots.", + caption: + "On hover, the center mark, radial axes, and pitch circle remain; the slots and callout disappear.", }, { id: "states", - title: "Absent, hidden, and proposed outlines", + title: "Hidden, absent, and proposed outlines", description: - "The dashed channel lies behind a visible front face. Its label explains why the geometry is not shown as a solid outline.", - omit: "The same dashed outline may mean hidden, absent, or proposed geometry. Name the state when the form alone cannot distinguish it.", - caption: "The view and callout make the hidden channel unambiguous.", + "The dashed C-shaped boundary represents a channel behind the plate’s visible front face. The channel exists, but this view cannot see it directly. Only that hidden boundary remains on hover.", + omit: "Hidden means present but concealed; absent means missing; proposed means a possible future form. Label the intended state. The solid line pointing to “Hidden rear channel” is a callout leader, a separate annotation that disappears on hover.", + caption: + "This example shows hidden geometry: the channel’s boundary behind the face.", }, ] as const; diff --git a/apps/ui/app/plotter/visual-grammar-drawings.tsx b/apps/ui/app/plotter/visual-grammar-drawings.tsx index 609fb8b43d..36e4b4fe0a 100644 --- a/apps/ui/app/plotter/visual-grammar-drawings.tsx +++ b/apps/ui/app/plotter/visual-grammar-drawings.tsx @@ -103,7 +103,6 @@ function GrammarArrows() { d={"M352.735065 131.264935L376 112H400"} strokeWidth={"0.65"} opacity={"0.5"} - data-grammar-focus={"true"} /> {"Quarter turn"} @@ -359,7 +357,6 @@ function GrammarCenterlines() { stroke={"currentColor"} strokeWidth={"0.65"} strokeOpacity={"0.5"} - data-grammar-focus={"true"} /> {"Pitch"} @@ -626,7 +621,6 @@ function GrammarDimensions() { fill={"currentColor"} stroke={"none"} opacity={"0.5"} - data-grammar-focus={"true"} > {"u = 40"} @@ -852,6 +846,16 @@ function GrammarGrids() { opacity={"0.1"} data-grammar-focus={"true"} /> + {"Hidden"} @@ -1486,7 +1487,6 @@ function GrammarStates() { stroke={"none"} fontSize={"11"} opacity={"0.72"} - data-grammar-focus={"true"} > {"rear channel"} diff --git a/apps/ui/skills/plotter/references/direction.md b/apps/ui/skills/plotter/references/direction.md index df771258b3..02ec279a0b 100644 --- a/apps/ui/skills/plotter/references/direction.md +++ b/apps/ui/skills/plotter/references/direction.md @@ -9,6 +9,8 @@ The user expected the grammar on the Plotter page and found the initial small pa The user then said they “love the new grammar examples”, asked to remove the counterexamples, and proposed hiding unrelated elements on hover. Keep these eight full-size drawings and their original appearance. The page isolates the topic’s marked elements on hover, keyboard focus, or a touch toggle; leaving the interaction restores the full drawing. Counterexample assets and controls were removed. This is positive feedback on the teaching drawings, without changing the earlier 27-reference selection. +The user then clarified that isolation must not change the selected element’s color or original stroke treatment, and that unrelated labels and callout leaders must disappear. The isolated grid also exposed gaps where full-view strokes had shared ownership. Preserve original opacity and widths; isolate only the named marks. Restore the grid’s missing shared segments only during isolation. The page now defines the hidden C-shaped channel and distinguishes its dashed boundary from the separate callout leader. + ## Catalogue removals after expansion — 2026-10-31 The user requested removal of 36 distinct studies: diff --git a/apps/ui/skills/plotter/references/grammar/arrows.svg b/apps/ui/skills/plotter/references/grammar/arrows.svg index ad5d296603..e6bcc219ce 100644 --- a/apps/ui/skills/plotter/references/grammar/arrows.svg +++ b/apps/ui/skills/plotter/references/grammar/arrows.svg @@ -1,4 +1,4 @@ A quarter turn, directedOne rigid capsule rotates from a solid horizontal pose into its dashed vertical proposal. The arrow follows a true quarter circle and points toward the proposed position.Plotter teaching construction, native560×440. References: P00 radius and orthogonal geometry; E02 curved path; C01 preserved fragment geometry. u=24 SVG units. Capsule end centers are(200,284),(392,284), separation192=8u; radius24, width48=2u. Proposed pose is a90° counterclockwise rotation about(200,284), taking the right center to(200,92). The R216 transfer arc is outside the moving part. R64 angle witness shares the pivot. Motion dash4 6 and solid open6-unit45°legs. This is a geometric movement illustration, not a load-bearing mechanism specification. -90°Quarter turnProposedCurrent8u2uOne pivot · one rigid part +90°Quarter turnProposedCurrent8u2uOne pivot · one rigid part diff --git a/apps/ui/skills/plotter/references/grammar/centerlines.svg b/apps/ui/skills/plotter/references/grammar/centerlines.svg index bf35a446e1..9771ff59e8 100644 --- a/apps/ui/skills/plotter/references/grammar/centerlines.svg +++ b/apps/ui/skills/plotter/references/grammar/centerlines.svg @@ -1 +1 @@ -Six slots, one centerSix curved slots share a pitch circle and one center. Three centerlines locate their radial midpoints, with a sixty-degree angle establishing the repeating interval.Geometric teaching construction after P00 circular construction. Native560×440. Ring center(280,220), outerR132 and innerR40. Six curved slots at angular centers -90,-30,30,90,150,210 degrees; centerline radius92, inner84, outer100, roundcapR8. Each spans30°; centers repeat60°. This is a planar repetition study, not a manufactured part specification. Centerlines and theoretical pitch circle use house8 3 1 3 dash; angle witness centered on(280,220) and attached to two true radial midlines.60°Pitch⌀2646 equal intervals +Six slots, one centerSix curved slots share a pitch circle and one center. Three centerlines locate their radial midpoints, with a sixty-degree angle establishing the repeating interval.Geometric teaching construction after P00 circular construction. Native560×440. Ring center(280,220), outerR132 and innerR40. Six curved slots at angular centers -90,-30,30,90,150,210 degrees; centerline radius92, inner84, outer100, roundcapR8. Each spans30°; centers repeat60°. This is a planar repetition study, not a manufactured part specification. Centerlines and theoretical pitch circle use house8 3 1 3 dash; angle witness centered on(280,220) and attached to two true radial midlines.60°Pitch⌀2646 equal intervals diff --git a/apps/ui/skills/plotter/references/grammar/dimensions.svg b/apps/ui/skills/plotter/references/grammar/dimensions.svg index 3ba48c6e91..840868f26f 100644 --- a/apps/ui/skills/plotter/references/grammar/dimensions.svg +++ b/apps/ui/skills/plotter/references/grammar/dimensions.svg @@ -1 +1 @@ -The dimensions of an offsetA quarter-turn band is generated by concentric radii 120 and 80. Its width is seven units, its offset is one unit, and its radius leader meets the actual outer arc.Geometric teaching construction after approved P02. Native 560 × 440; u=40. Center(280,248); outer radius120=3u, inner radius80=2u; band offset40=u. Horizontal left x120 and right x400 give total width280=7u. No physical material or load claim. Radius leader ends at(280+120/sqrt(2),248-120/sqrt(2)). Principal arcs and generating-circle remainders have separate stroke ownership.7uuR120Offsetu = 40 +The dimensions of an offsetA quarter-turn band is generated by concentric radii 120 and 80. Its width is seven units, its offset is one unit, and its radius leader meets the actual outer arc.Geometric teaching construction after approved P02. Native 560 × 440; u=40. Center(280,248); outer radius120=3u, inner radius80=2u; band offset40=u. Horizontal left x120 and right x400 give total width280=7u. No physical material or load claim. Radius leader ends at(280+120/sqrt(2),248-120/sqrt(2)). Principal arcs and generating-circle remainders have separate stroke ownership.7uuR120Offsetu = 40 diff --git a/apps/ui/skills/plotter/references/grammar/grids.svg b/apps/ui/skills/plotter/references/grammar/grids.svg index 26fad04f5d..34570a1e22 100644 --- a/apps/ui/skills/plotter/references/grammar/grids.svg +++ b/apps/ui/skills/plotter/references/grammar/grids.svg @@ -1 +1 @@ -A letter on its moduleA modular letter P is constructed with a 32-unit stem, a 96-unit outer bowl radius, and a 64-unit counter radius. The 32-unit grid locates their shared steps.Plotter visual grammar: grids. Approved references: T01 for letter anatomy, P00 for a visible generating field. Native 560 × 440. Local unit u=32. Stem x184..216, y96..352; outside bowl center(280,192), R96=3u; counter R64=2u. The primary letter has no solid fill. The useful grid begins(152,64), pitch 32, and is split around coincident outline segments. A grid is used because the stem, counter, heights and radii share the actual unit field, not merely because the subject is geometric.3u2u8uuu = 32 +A letter on its moduleA modular letter P is constructed with a 32-unit stem, a 96-unit outer bowl radius, and a 64-unit counter radius. The 32-unit grid locates their shared steps. In isolation, the grid becomes continuous across the positions normally occupied by the letter, annotations, and witnesses.Plotter visual grammar: grids. Approved references: T01 for letter anatomy, P00 for a visible generating field. Native 560 × 440. Local unit u=32. Stem x184..216, y96..352; outside bowl center(280,192), R96=3u; counter R64=2u. The primary letter has no solid fill. The useful grid begins(152,64), pitch 32, and is split around coincident outline segments. Fifteen hidden complementary segments restore the same lattice only during isolation, when the other geometry disappears. These additions retain the grid’s 0.6 width and 0.1 opacity, and do not overlap its existing segments. The complete field has verticals at x152..408 in 32-unit steps over y64..368, and horizontals at y64..352 in 32-unit steps over x152..408. A grid is used because the stem, counter, heights and radii share the actual unit field, not merely because the subject is geometric.3u2u8uuu = 32 diff --git a/apps/ui/skills/plotter/references/grammar/outlines.svg b/apps/ui/skills/plotter/references/grammar/outlines.svg index 7133e2c822..792cc228fe 100644 --- a/apps/ui/skills/plotter/references/grammar/outlines.svg +++ b/apps/ui/skills/plotter/references/grammar/outlines.svg @@ -1,4 +1,4 @@ - + Hierarchy within an assembled record Identity, name, and tools assemble into one record. Main silhouettes remain primary; face details, dimensions, projections, and the transfer arrow remain subordinate. Plotter grammar teaching example. Adapted from the user-liked registration revision, with C01 assembly, C02 allocation, and I06 identity as approved composition references. The drawing preserves the source line hierarchy. Local unit u=24; record336x96; source fragments translate148 vertically. No background grid or decorative hatch. diff --git a/apps/ui/skills/plotter/references/grammar/states.svg b/apps/ui/skills/plotter/references/grammar/states.svg index 84352f0155..8c1376bf3f 100644 --- a/apps/ui/skills/plotter/references/grammar/states.svg +++ b/apps/ui/skills/plotter/references/grammar/states.svg @@ -1,4 +1,4 @@ The channel behind the faceA solid front plate and circular opening stay visible while dashed boundaries describe a rear channel. The hidden state is named in the callout and view label.Plotter teaching drawing, native560×440. References:P00 focal circular construction; C02 commonframe and sparse measuredallocation; E02 geometry distinguished by state. Simplified front view of a hypothetical plate with a rearchannel; no manufacturing,pressure,orflow-performance claim. Visibleface336×248 withR16corners, centralopeningR52, mount holesR6. The hidden channel is16wide: outerbendR96, innerbendR80, centered(280,220), terminated byR8caps at(184,132)and(184,308). VisibleportopeningsR3.5. The304lowerdimension spans actualcorner tangenciesx128..432. Hiddenedges use3 5; centeraxes use8 3 1 3. No background grid or area tint. -Port APort BHiddenrear channel336R52304Front view · channel concealed behind the face +Port APort BHiddenrear channel336R52304Front view · channel concealed behind the face diff --git a/apps/ui/skills/plotter/references/visual-grammar.md b/apps/ui/skills/plotter/references/visual-grammar.md index d02dd160dc..25bb333179 100644 --- a/apps/ui/skills/plotter/references/visual-grammar.md +++ b/apps/ui/skills/plotter/references/visual-grammar.md @@ -4,6 +4,8 @@ Start with the main form. For each supporting mark, name the relationship it exp The user liked these eight **560 × 440** teaching constructions. On the Plotter page at `/ui/plotter#visual-grammar`, hovering, keyboard focus, or the touch toggle hides unrelated elements and isolates the marks named by each topic. The SVG’s `data-grammar-focus` attributes identify those marks; the complete drawing stays intact. Counterexamples were removed at the user’s request. +Isolation preserves the element’s original color, opacity, stroke width, and dash pattern. Exclude nearby labels and callout leaders unless they are the topic itself. Dimension values remain part of their dimension. Where a grid shares a stroke with a visible form, hidden supplemental grid segments restore the complete lattice only while that form is hidden. + Use the [drafting standard](drafting-standard.md#house-stroke-palette) for numerical treatment and the [approved references](approved-references.md) for composition. Registration, delegation, and iris provide additional user-liked evidence for these teaching adaptations; the approved reference selection remains unchanged. ## Primary and secondary outlines @@ -35,9 +37,9 @@ Geometric content alone is insufficient. Keep a grid when its coordinate or modu ## Movement and transfer arrows -![The arrow carries the quarter turn toward the proposed position.](grammar/arrows.svg) +![The curved dashed shaft and solid open arrowhead show a quarter turn.](grammar/arrows.svg) -One rigid part turns around a shared pivot. The movement arrow names the direction from its current position to the proposed one. Use an arrow only when a change or direction matters; alignment and equal size need different marks. +One rigid part turns around a shared pivot. The curved dashed shaft and solid open arrowhead show its movement from the current position to the proposed one. “Quarter turn” and its callout leader are separate annotations; they disappear during isolation. Use an arrow only when a change or direction matters; alignment and equal size need different marks. Movement and conceptual transfer use the house **`4 6` shaft with a solid open head**. Alignment and size use projections or dimensions instead. A functional connector needs its own stated connectivity meaning. @@ -69,11 +71,13 @@ Centerlines and a pitch circle reveal how repeated features share one center. Si The house **`8 3 1 3`** pattern identifies axes, centers, and theoretical pitch loci. A direct generating radius can instead use continuous secondary linework. -## Absent, hidden, and proposed outlines +## Hidden, absent, and proposed outlines + +![A dashed C-shaped boundary represents a channel concealed behind the plate’s front face.](grammar/states.svg) -![The view and callout make the hidden channel unambiguous.](grammar/states.svg) +The dashed C-shaped boundary represents a channel behind the plate’s visible front face. The channel exists, but this view cannot see it directly: this is **hidden geometry**. Only its dashed boundary remains during isolation. The solid line pointing to “Hidden rear channel” is a **callout leader** connecting a feature to explanatory text; both that leader and its label disappear on hover. -The dashed channel lies behind a visible front face. Its label explains why the geometry is not shown as a solid outline. The same dashed outline may mean hidden, absent, or proposed geometry. Name the state when the form alone cannot distinguish it. +Hidden means present but concealed; absent means missing; proposed means a possible future form. Name the intended state rather than expecting the dash pattern alone to distinguish these meanings. The house **`3 5`** outline means hidden or absent geometry. A proposed future form uses that absent outline with a state label and, when useful, a separate movement arrow. If hidden and absent features coexist, label their states or separate the views. From 96055b681ce4711028e298a1a79e26e43e091cf6 Mon Sep 17 00:00:00 2001 From: pasqualevitiello Date: Sat, 31 Oct 2026 15:56:48 +0100 Subject: [PATCH 13/15] feat(ui): document callout leaders in visual grammar --- apps/ui/app/plotter/visual-grammar-content.ts | 8 + .../app/plotter/visual-grammar-drawings.tsx | 191 ++++++++++++++++++ apps/ui/skills/plotter/SKILL.md | 2 +- .../ui/skills/plotter/references/direction.md | 4 + .../plotter/references/grammar/callouts.svg | 4 + .../plotter/references/visual-grammar.md | 11 +- 6 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 apps/ui/skills/plotter/references/grammar/callouts.svg diff --git a/apps/ui/app/plotter/visual-grammar-content.ts b/apps/ui/app/plotter/visual-grammar-content.ts index d9a5361474..9f16b31593 100644 --- a/apps/ui/app/plotter/visual-grammar-content.ts +++ b/apps/ui/app/plotter/visual-grammar-content.ts @@ -32,6 +32,14 @@ export const visualGrammarEntries = [ omit: "Use an arrow only when a change or direction matters; alignment and equal size need different marks.", caption: "On hover, only the dashed shaft and solid arrowhead remain.", }, + { + id: "callouts", + title: "Callout leaders and labels", + description: + "A callout connects a short label to the feature it names. Here the bent solid leader anchors “Quarter turn” to the curved movement path. The leader and label form one annotation.", + omit: "Omit labels that add no meaning. Keep the leader subordinate and end it on the named feature; use movement arrows for direction and dimensions for measurements.", + caption: "On hover, only the leader and “Quarter turn” label remain.", + }, { id: "projections", title: "Projection guides", diff --git a/apps/ui/app/plotter/visual-grammar-drawings.tsx b/apps/ui/app/plotter/visual-grammar-drawings.tsx index 36e4b4fe0a..a20abee196 100644 --- a/apps/ui/app/plotter/visual-grammar-drawings.tsx +++ b/apps/ui/app/plotter/visual-grammar-drawings.tsx @@ -192,6 +192,196 @@ function GrammarArrows() { ); } +function GrammarCallouts() { + return ( + + + + + + + + + + + {"90°"} + + + + + + {"Quarter turn"} + + + {"Proposed"} + + + + {"Current"} + + + + + {"8u"} + + + + {"2u"} + + + {"One pivot · one rigid part"} + + + ); +} + function GrammarCenterlines() { const id = useId(); return ( @@ -1569,6 +1759,7 @@ export const visualGrammarDrawings: Record< ReactNode > = { arrows: , + callouts: , centerlines: , dimensions: , grids: , diff --git a/apps/ui/skills/plotter/SKILL.md b/apps/ui/skills/plotter/SKILL.md index 318381aca8..7045555d9c 100644 --- a/apps/ui/skills/plotter/SKILL.md +++ b/apps/ui/skills/plotter/SKILL.md @@ -17,7 +17,7 @@ The snapshots establish composition and hierarchy. Some retain historical fills ## Compose -Read the [illustrated visual grammar](references/visual-grammar.md) before choosing supporting marks. It explains outlines, hatching, grids, arrows, projections, dimensions, axes, and absent forms through full-size construction studies. Choose a mark for the relationship it explains; use the drafting standard for its numerical treatment. +Read the [illustrated visual grammar](references/visual-grammar.md) before choosing supporting marks. It explains outlines, hatching, grids, arrows, callouts, projections, dimensions, axes, and absent forms through full-size construction studies. Choose a mark for the relationship it explains; use the drafting standard for its numerical treatment. - Use one flat plane. Exclude perspective, isometric views, skewed or tilted objects, physical depth, glossy 3D, colorful gradients, and science-class atom diagrams. Diagonal radius lines and callout leaders can explain geometry without tilting the form. - Keep the palette black, off-white, and soft gray. Adapt those values to the host’s theme while preserving the monochrome hierarchy. diff --git a/apps/ui/skills/plotter/references/direction.md b/apps/ui/skills/plotter/references/direction.md index 02ec279a0b..ad3347db22 100644 --- a/apps/ui/skills/plotter/references/direction.md +++ b/apps/ui/skills/plotter/references/direction.md @@ -272,3 +272,7 @@ Use these as starting points, not approved concepts or mandatory motifs: - **404/empty states:** an interrupted alignment or unresolved planar route can communicate absence. The revised E02 interrupted path is explicitly selected for the 404 page; E04 was removed. A currently open doorway URL is not approval evidence. Other categories remain open. Translate their subject into a flat construction relationship, then review the result against the anchors rather than assuming every object works in this style. + +## Callout leaders in the visual grammar + +The user pointed out that the “Quarter turn” annotation in Movement and transfer arrows still lacked its own grammar entry. The guide now includes **Callout leaders and labels**, reusing that construction and isolating only the bent solid leader and its text. Keep callouts separate from movement arrows, dimension marks, and hidden outlines: the leader connects a named feature to its label. Preserve both marks’ original color, opacity, and weight during isolation. diff --git a/apps/ui/skills/plotter/references/grammar/callouts.svg b/apps/ui/skills/plotter/references/grammar/callouts.svg new file mode 100644 index 0000000000..5105990b98 --- /dev/null +++ b/apps/ui/skills/plotter/references/grammar/callouts.svg @@ -0,0 +1,4 @@ + +A quarter turn, namedA bent solid leader connects the Quarter turn label to the curved movement arrow. Isolation keeps only the leader and its label, showing how a callout names one feature without covering it.Callout study: the leader starts at the 45° point on the R216 movement arc, bends once, and ends 8 units before the label. Plotter teaching construction, native560×440. References: P00 radius and orthogonal geometry; E02 curved path; C01 preserved fragment geometry. u=24 SVG units. Capsule end centers are(200,284),(392,284), separation192=8u; radius24, width48=2u. Proposed pose is a90° counterclockwise rotation about(200,284), taking the right center to(200,92). The R216 transfer arc is outside the moving part. R64 angle witness shares the pivot. Motion dash4 6 and solid open6-unit45°legs. This is a geometric movement illustration, not a load-bearing mechanism specification. +90°Quarter turnProposedCurrent8u2uOne pivot · one rigid part + diff --git a/apps/ui/skills/plotter/references/visual-grammar.md b/apps/ui/skills/plotter/references/visual-grammar.md index 25bb333179..1732618537 100644 --- a/apps/ui/skills/plotter/references/visual-grammar.md +++ b/apps/ui/skills/plotter/references/visual-grammar.md @@ -2,7 +2,7 @@ Start with the main form. For each supporting mark, name the relationship it explains. If removing the mark leaves that relationship equally clear, omit it. A drawing does not need every element below. -The user liked these eight **560 × 440** teaching constructions. On the Plotter page at `/ui/plotter#visual-grammar`, hovering, keyboard focus, or the touch toggle hides unrelated elements and isolates the marks named by each topic. The SVG’s `data-grammar-focus` attributes identify those marks; the complete drawing stays intact. Counterexamples were removed at the user’s request. +The guide uses full-size **560 × 440** teaching constructions based on illustrations the user liked. On the Plotter page at `/ui/plotter#visual-grammar`, hovering, keyboard focus, or the touch toggle hides unrelated elements and isolates the marks named by each topic. The SVG’s `data-grammar-focus` attributes identify those marks; the complete drawing stays intact. Counterexamples were removed at the user’s request. Isolation preserves the element’s original color, opacity, stroke width, and dash pattern. Exclude nearby labels and callout leaders unless they are the topic itself. Dimension values remain part of their dimension. Where a grid shares a stroke with a visible form, hidden supplemental grid segments restore the complete lattice only while that form is hidden. @@ -44,6 +44,15 @@ One rigid part turns around a shared pivot. The curved dashed shaft and solid op Movement and conceptual transfer use the house **`4 6` shaft with a solid open head**. Alignment and size use projections or dimensions instead. A functional connector needs its own stated connectivity meaning. +## Callout leaders and labels + +![The bent leader connects “Quarter turn” to the curved movement path.](grammar/callouts.svg) + +A callout connects a short label to the feature it names. Here the bent solid leader anchors “Quarter turn” to the curved movement path. The leader and label form one annotation, and only those two elements remain during isolation. + +Use a callout when a feature needs a name or explanation that its shape cannot supply. Omit labels that add no meaning. Keep the leader subordinate and end it on the named feature, with a clear gap before the text. Movement arrows convey direction; dimensions and radius leaders measure geometry. A callout supplies descriptive meaning. + + ## Projection guides ![Four quiet guides preserve the same two allocations.](grammar/projections.svg) From 093067a29cfd48bce213b3fe6627ac1154447a20 Mon Sep 17 00:00:00 2001 From: pasqualevitiello Date: Sat, 31 Oct 2026 16:15:04 +0100 Subject: [PATCH 14/15] feat(ui): add three-speed orbit animation to Plotter --- apps/ui/app/plotter/orbits-motion-drawing.tsx | 292 ++++++++++++++++++ apps/ui/app/plotter/page.tsx | 31 +- .../ui/skills/plotter/references/direction.md | 8 + 3 files changed, 330 insertions(+), 1 deletion(-) create mode 100644 apps/ui/app/plotter/orbits-motion-drawing.tsx diff --git a/apps/ui/app/plotter/orbits-motion-drawing.tsx b/apps/ui/app/plotter/orbits-motion-drawing.tsx new file mode 100644 index 0000000000..d286e9e34a --- /dev/null +++ b/apps/ui/app/plotter/orbits-motion-drawing.tsx @@ -0,0 +1,292 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; + +// One cycle holds 4, 2 and 1 orbits. Radii follow Kepler’s third law, +// T² ∝ r³, so the orbit spacing agrees with the periods rather than being +// chosen by eye. Planet sizes only help the eye keep the three apart. +const center = { x: 280, y: 212 } as const; +const cycle = 12; +const unit = 52; +const sunRadius = 22; +const planets = [ + { orbits: 4, radius: 6 }, + { orbits: 2, radius: 8 }, + { orbits: 1, radius: 10 }, +].map((planet) => { + const period = cycle / planet.orbits; + return { + ...planet, + period, + orbit: unit * (period / (cycle / 4)) ** (2 / 3), + }; +}); +const outerOrbit = Math.max(...planets.map((p) => p.orbit)); +// The angular scale sits just outside the swept envelope of the outermost planet. +const scaleRadius = 152; +const arrowRadius = 172; +const labelRadius = 184; + +// Counter-clockwise angle in degrees from the start axis; SVG y points down. +function polar(radius: number, degrees: number) { + const angle = (degrees * Math.PI) / 180; + return { + x: center.x + radius * Math.cos(angle), + y: center.y - radius * Math.sin(angle), + }; +} + +function ray(from: number, to: number, degrees: number) { + const a = polar(from, degrees); + const b = polar(to, degrees); + return `M${a.x.toFixed(3)} ${a.y.toFixed(3)}L${b.x.toFixed(3)} ${b.y.toFixed(3)}`; +} + +// 24 divisions: long at quarter turns, medium at 30°, short at 15°. +const scaleTicks = Array.from({ length: 24 }, (_, i) => i * 15).reduce( + (groups, degrees) => { + const length = degrees % 90 === 0 ? 12 : degrees % 30 === 0 ? 8 : 4; + const group = length === 12 ? "quarter" : length === 8 ? "third" : "step"; + groups[group] += `${ray(scaleRadius, scaleRadius + length, degrees)} `; + return groups; + }, + { quarter: "", third: "", step: "" }, +); + +// Dashed travel arrow ahead of the planets, drawn from its tip so the open +// head meets the first dash. It follows the same counter-clockwise direction. +const arrowTip = polar(arrowRadius, 72); +const arrowTail = polar(arrowRadius, 24); +const arrowTangent = { + x: -Math.sin((72 * Math.PI) / 180), + y: -Math.cos((72 * Math.PI) / 180), +}; +const arrowHead = [-1, 1] + .map((side) => { + const turn = (side * Math.PI) / 4; + const x = arrowTangent.x * Math.cos(turn) - arrowTangent.y * Math.sin(turn); + const y = arrowTangent.x * Math.sin(turn) + arrowTangent.y * Math.cos(turn); + return `${(arrowTip.x - 6 * x).toFixed(3)} ${(arrowTip.y - 6 * y).toFixed(3)}`; + }) + .join(` ${arrowTip.x.toFixed(3)} ${arrowTip.y.toFixed(3)} `); + +const quarterLabels = [ + { degrees: 0, text: "0° · Start", anchor: "start" }, + { degrees: 90, text: "90°", anchor: "middle" }, +] as const; + +function Sun() { + return ( + <> + + + + + ); +} + +// An orbit is a theoretical generating path, so it takes the house centerline +// pattern for loci. Like the pitch circle in the grammar’s centerline study, it +// passes through the transparent body that travels on it. +function Orbits() { + return ( + + {planets.map(({ orbit }) => ( + + ))} + + ); +} + +function AngularScale() { + return ( + <> + + + + + + + + + + ); +} + +// Planets leave dashed outlines on the start axis and return into them once +// per cycle. The state is named by the “Start” label at 0°. +function StartPositions() { + return ( + + {planets.map(({ orbit, radius }) => ( + + ))} + + ); +} + +// The outer orbit’s diameter, measured between its top and bottom tangents. +// Witnesses start beyond the planet’s swept band and the dimension line breaks +// around its label, so nothing crosses a moving part. +const dimension = { x: 84, from: 196 } as const; +const dimensionTop = center.y - outerOrbit; +const dimensionBottom = center.y + outerOrbit; + +function Dimension() { + return ( + <> + + + + ); +} + +function Labels() { + const labelFont = { fontSize: 10, opacity: 0.58 } as const; + return ( + + {quarterLabels.map(({ degrees, text, anchor }) => { + const { x, y } = polar(labelRadius, degrees); + return ( + + {text} + + ); + })} + + T² ∝ r³ + + + {`⌀${Math.round(2 * outerOrbit)}`} + + + ); +} + +const keyEntries = [ + { left: 92, planet: planets[0], text: "4 orbits · 3 s" }, + { left: 232, planet: planets[1], text: "2 orbits · 6 s" }, + { left: 372, planet: planets[2], text: "1 orbit · 12 s" }, +] as const; + +function Key() { + return ( + <> + + {keyEntries.map(({ left, planet }) => ( + + ))} + + + {keyEntries.map(({ left, text }) => ( + + {text} + + ))} + + + ); +} + +function Planet({ + animated, + orbit, + period, + radius, +}: { + animated: boolean; + orbit: number; + period: number; + radius: number; +}) { + const start = polar(orbit - radius, 0); + return ( + + {animated && ( + + )} + + + + ); +} + +function OrbitDrawing({ animated }: { animated: boolean }) { + return ( + + + + + + {planets.map((planet) => ( + + ))} + + + + + ); +} + +export function OrbitsStudy() { + return ; +} + +export function OrbitsMotionDrawing() { + return ; +} diff --git a/apps/ui/app/plotter/page.tsx b/apps/ui/app/plotter/page.tsx index 2c2cbb8dd9..4f38b521a3 100644 --- a/apps/ui/app/plotter/page.tsx +++ b/apps/ui/app/plotter/page.tsx @@ -11,6 +11,7 @@ import { IllustrationGallery } from "./illustration-gallery"; import { illustrations } from "./illustrations"; import { NewtonsCradleMotionDrawing } from "./newtons-cradle-motion-drawing"; import { NotFoundMotion } from "./not-found-motion"; +import { OrbitsMotionDrawing, OrbitsStudy } from "./orbits-motion-drawing"; import { ParticlesBlueprint } from "./particles-blueprint"; import { ParticlesMotion } from "./particles-motion"; import { PrimitivesBlueprint } from "./primitives-blueprint"; @@ -80,7 +81,7 @@ export default function Page() { > Animations -

    5 examples

    +

    6 examples

    @@ -204,6 +205,34 @@ export default function Page() {
    +
    +
    +

    Nature / M04

    +

    + Three orbits, three speeds +

    +

    + Three planets leave one line together. The inner one circles + four times for every turn of the outer one, and all three meet + again at the start. +

    +
    +
    + } + caption="Inner · Middle · Outer" + > + + +
    +
    diff --git a/apps/ui/skills/plotter/references/direction.md b/apps/ui/skills/plotter/references/direction.md index ad3347db22..c1d4223744 100644 --- a/apps/ui/skills/plotter/references/direction.md +++ b/apps/ui/skills/plotter/references/direction.md @@ -276,3 +276,11 @@ Other categories remain open. Translate their subject into a flat construction r ## Callout leaders in the visual grammar The user pointed out that the “Quarter turn” annotation in Movement and transfer arrows still lacked its own grammar entry. The guide now includes **Callout leaders and labels**, reusing that construction and isolating only the bent solid leader and its text. Keep callouts separate from movement arrows, dimension marks, and hidden outlines: the leader connects a named feature to its label. Preserve both marks’ original color, opacity, and weight during isolation. + +## M04 orbits motion study + +The user asked for a Plotter animation of planets orbiting to show their different speeds, with the sun at the center and only three planets. Their review of the first version was: “looks poor. no diagonal lines on the sun. level of detail is sparse, use more elements from the visual grammar, make it look stunning and interesting.” Do not hatch the sun; keep three planets but give the drawing real drafting content. + +M04 (Nature / “Three orbits, three speeds”) in `orbits-motion-drawing.tsx` is the revision. The cycle is 12s: planets complete 4, 2 and 1 orbits (3s, 6s, 12s), counter-clockwise from one start axis, and realign when the loop restarts. Orbit radii follow T² ∝ r³. Marks and the relationship each explains: a rotating generating radius per planet shows angular speed; a 24-division angular scale with a dashed `4 6` travel arrow gives direction and position; dashed `3 5` start outlines mark where the planets leave and return; the outer orbit’s ⌀ dimension and the T² ∝ r³ note tie spacing to period; a legend keys planet size to period; the sun uses concentric rings. Guides and the dimension sit outside the planets’ swept bands. These are implementation decisions awaiting review, not an approved motion reference. + +After the rayed, continuous-circle version the user said: “no sun rays. shouldn’t the orbit be represented by a specific element in the visual grammar?” Orbits are loci: draw them with the centerline pattern (`8 3 1 3`), not continuous circles, and give the sun no rays. An earlier experiment had replaced the circular orbits with real Keplerian orbits of Mercury, Earth, the Moon and Mars (Sun at a focus, dash-dot loci, dated to 31 October 2026) and removed the sun rays, ring and ticks. The user reviewed it and said “don’t like”, then asked to return to the version above. Treat that real-orbit direction as set aside, not as a preference for the rayed sun or continuous circles; the reason was not stated. From 54c5a6dd2527d91127bd5efd0dfe2ee055570f27 Mon Sep 17 00:00:00 2001 From: pasqualevitiello Date: Sat, 31 Oct 2026 17:34:22 +0100 Subject: [PATCH 15/15] feat(ui): add portable Plotter foundation and usage guide --- apps/ui/app/plotter/README.md | 34 ++ apps/ui/app/plotter/blueprint-drawing.tsx | 11 +- .../plotter/drawings/agent-registration.svg | 118 ++++++- apps/ui/app/plotter/engineering-studies.tsx | 42 ++- apps/ui/app/plotter/expanded-studies.tsx | 47 ++- apps/ui/app/plotter/page.tsx | 74 ++--- apps/ui/app/plotter/plotter-intro.tsx | 54 ++++ apps/ui/app/plotter/plotter-prompts.tsx | 220 +++++++++++++ apps/ui/app/plotter/plotter-token-guide.tsx | 294 ++++++++++++++++++ apps/ui/app/plotter/plotter-usage-content.ts | 16 + apps/ui/app/plotter/plotter-usage.tsx | 113 +++++++ .../app/plotter/production-chain-hatching.tsx | 13 +- .../production-chain-motion-drawing.tsx | 29 +- apps/ui/app/plotter/visual-grammar.tsx | 11 + .../2026-10-31/catalog-expansion/notes.md | 2 +- apps/ui/plotter-plan.md | 244 +++++++++++++++ .../plotter/downloads/agent-registration.svg | 77 +++++ .../plotter/downloads/assembly-line.html | 177 +++++++++++ apps/ui/public/plotter/downloads/plotter.zip | Bin 0 -> 264409 bytes .../plotter/downloads/production-chain.svg | 1 + apps/ui/scripts/build-plotter-downloads.tsx | 169 ++++++++++ apps/ui/scripts/build-plotter-drawings.py | 27 +- .../ui/scripts/build-plotter-registration.mjs | 9 + apps/ui/scripts/export-plotter-pilots.tsx | 50 +++ apps/ui/scripts/plotter_svg.py | 28 +- apps/ui/skills/plotter/README.md | 35 ++- apps/ui/skills/plotter/SKILL.md | 64 ++-- apps/ui/skills/plotter/agents/openai.yaml | 4 +- .../assets/examples/assembly-motion.html | 111 +++++++ .../plotter/assets/examples/registration.mjs | 7 + .../plotter/assets/examples/registration.svg | 165 ++++++++++ .../assets/examples/render-registration.mjs | 76 +++++ .../plotter/assets/foundation/index.d.mts | 70 +++++ .../plotter/assets/foundation/index.mjs | 173 +++++++++++ .../plotter/assets/foundation/roles.css | 87 ++++++ .../plotter/assets/foundation/standard.json | 107 +++++++ .../plotter/assets/foundation/standard.mjs | 125 ++++++++ .../plotter/references/approved-references.md | 18 +- .../ui/skills/plotter/references/direction.md | 287 +---------------- .../plotter/references/drafting-standard.md | 18 +- apps/ui/skills/plotter/references/drawing.md | 62 ++++ .../skills/plotter/references/foundation.md | 78 +++++ .../{ => history}/coss-product-concepts.md | 2 + .../plotter/references/history/direction.md | 288 +++++++++++++++++ .../examples/agent-registration-before.svg | 0 .../examples/agent-registration.md | 2 +- .../examples/agent-registration.svg | 0 .../plotter/references/history/index.md | 19 ++ .../references/history/reference-selection.md | 21 ++ .../{ => history}/remaining-source-audit.md | 2 + .../references/{ => history}/source-audit.md | 2 + .../plotter/references/implementation.md | 74 +++-- apps/ui/skills/plotter/references/motion.md | 51 +++ .../plotter/references/verification-prompt.md | 3 +- .../plotter/references/visual-grammar.md | 20 +- .../skills/plotter/scripts/build-standard.py | 78 +++++ apps/ui/skills/plotter/scripts/export-svg.py | 293 +++++++++++++++++ 57 files changed, 3688 insertions(+), 514 deletions(-) create mode 100644 apps/ui/app/plotter/plotter-intro.tsx create mode 100644 apps/ui/app/plotter/plotter-prompts.tsx create mode 100644 apps/ui/app/plotter/plotter-token-guide.tsx create mode 100644 apps/ui/app/plotter/plotter-usage-content.ts create mode 100644 apps/ui/app/plotter/plotter-usage.tsx create mode 100644 apps/ui/plotter-plan.md create mode 100644 apps/ui/public/plotter/downloads/agent-registration.svg create mode 100644 apps/ui/public/plotter/downloads/assembly-line.html create mode 100644 apps/ui/public/plotter/downloads/plotter.zip create mode 100644 apps/ui/public/plotter/downloads/production-chain.svg create mode 100644 apps/ui/scripts/build-plotter-downloads.tsx create mode 100644 apps/ui/scripts/build-plotter-registration.mjs create mode 100644 apps/ui/scripts/export-plotter-pilots.tsx create mode 100644 apps/ui/skills/plotter/assets/examples/assembly-motion.html create mode 100644 apps/ui/skills/plotter/assets/examples/registration.mjs create mode 100644 apps/ui/skills/plotter/assets/examples/registration.svg create mode 100644 apps/ui/skills/plotter/assets/examples/render-registration.mjs create mode 100644 apps/ui/skills/plotter/assets/foundation/index.d.mts create mode 100644 apps/ui/skills/plotter/assets/foundation/index.mjs create mode 100644 apps/ui/skills/plotter/assets/foundation/roles.css create mode 100644 apps/ui/skills/plotter/assets/foundation/standard.json create mode 100644 apps/ui/skills/plotter/assets/foundation/standard.mjs create mode 100644 apps/ui/skills/plotter/references/drawing.md create mode 100644 apps/ui/skills/plotter/references/foundation.md rename apps/ui/skills/plotter/references/{ => history}/coss-product-concepts.md (95%) create mode 100644 apps/ui/skills/plotter/references/history/direction.md rename apps/ui/skills/plotter/references/{ => history}/examples/agent-registration-before.svg (100%) rename apps/ui/skills/plotter/references/{ => history}/examples/agent-registration.md (96%) rename apps/ui/skills/plotter/references/{ => history}/examples/agent-registration.svg (100%) create mode 100644 apps/ui/skills/plotter/references/history/index.md create mode 100644 apps/ui/skills/plotter/references/history/reference-selection.md rename apps/ui/skills/plotter/references/{ => history}/remaining-source-audit.md (98%) rename apps/ui/skills/plotter/references/{ => history}/source-audit.md (99%) create mode 100644 apps/ui/skills/plotter/references/motion.md create mode 100644 apps/ui/skills/plotter/scripts/build-standard.py create mode 100644 apps/ui/skills/plotter/scripts/export-svg.py diff --git a/apps/ui/app/plotter/README.md b/apps/ui/app/plotter/README.md index 6901b79d9b..97d0954156 100644 --- a/apps/ui/app/plotter/README.md +++ b/apps/ui/app/plotter/README.md @@ -47,3 +47,37 @@ Isolation preserves the original stroke treatment. The grid’s missing shared s The visible Visual grammar section precedes the illustrations and is reachable from the top navigation at `/ui/plotter#visual-grammar`. 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. + +## Shared foundation pilot + +AG01 and W05 opt into the [portable drafting foundation](../../skills/plotter/references/foundation.md). AG01 keeps its editable SVG; its transfer arrow and width dimension come from the shared helpers. W05 uses the same arrow helper and shares rectangular hatching between its static and animated drawings. Its timeline and playback controls remain local and unchanged. + +From the repository root, regenerate standard styles, AG01's marked fragments, and the SVG-to-JSX output after relevant source changes: + +```sh +python3 apps/ui/skills/plotter/scripts/build-standard.py +node apps/ui/scripts/build-plotter-registration.mjs +python3 apps/ui/scripts/build-plotter-drawings.py +``` + +The registration script and portable example both use the skill-owned [`renderRegistration` recipe](../../skills/plotter/assets/examples/render-registration.mjs). It reads its width witnesses from the SVG's `registry-record` rectangle and local unit, and changes only marked helper fragments and the embedded generated stylesheet. Preserve those markers when editing the surrounding composition. + +To produce the two self-contained static SVGs, run: + +```sh +bun apps/ui/scripts/export-plotter-pilots.tsx /tmp/plotter-exports +``` + +`BlueprintDrawing` enables generated role styles only when passed `roles`. Legacy drawings keep their existing inherited defaults. Keep `data-plotter-role` on the original styled groups, and avoid applying opacity twice. The rest of the catalogue and the approved snapshot manifest are outside this pilot. + +## Using Plotter from the page + +`#use-plotter` provides installation, copyable drawing/animation briefs, and editable output downloads. `#plotter-tokens` shows supported CSS overrides on a separate W05 instance and reads token defaults from the shared standard. Existing catalogue filters, deep links, animation controls, and grammar isolation retain their behavior. + +The page serves a complete local skill ZIP, two resolved SVGs, and a portable W05 HTML player from `public/plotter/downloads/`. Regenerate these artifacts after changing the packaged skill or the pilot drawings: + +```sh +bun apps/ui/scripts/build-plotter-downloads.tsx +``` + +The script packages the current sources rather than assuming a branch has been published. It does not run checks or tests. Download URLs use the app's configured base path. The HTML embeds the current W05 motion and static SVGs plus generated role styles; it needs no coss runtime or external font. New presentation code lives in `plotter-intro.tsx`, `plotter-usage.tsx`, `plotter-prompts.tsx`, and `plotter-token-guide.tsx`. diff --git a/apps/ui/app/plotter/blueprint-drawing.tsx b/apps/ui/app/plotter/blueprint-drawing.tsx index 36070a82be..90dbb6f613 100644 --- a/apps/ui/app/plotter/blueprint-drawing.tsx +++ b/apps/ui/app/plotter/blueprint-drawing.tsx @@ -1,16 +1,25 @@ import type { ReactNode } from "react"; +import { roleCss } from "../../skills/plotter/assets/foundation/index.mjs"; -export function BlueprintDrawing({ children }: { children: ReactNode }) { +export function BlueprintDrawing({ + children, + roles = false, +}: { + children: ReactNode; + roles?: boolean; +}) { return ( ); diff --git a/apps/ui/app/plotter/drawings/agent-registration.svg b/apps/ui/app/plotter/drawings/agent-registration.svg index 0d0264275a..751b9710c3 100644 --- a/apps/ui/app/plotter/drawings/agent-registration.svg +++ b/apps/ui/app/plotter/drawings/agent-registration.svg @@ -1,10 +1,98 @@ - + + An agent, registered An identity, name, and two tools align into one registry record. The identity and tool shapes retain their sizes and horizontal positions. The record receives the example identifier agent_07. Study: agent-registration, revision 2, explicitly liked by the user on 2026-10-31. Mode: conceptual diagram. Registration is represented as the assembly of one discoverable record from an agent definition; this does not depict authentication, permission grants, or execution. References: C01 assembly, C02 allocation, I06 identity fragments. Local unit u = 24 SVG units. Record width 14u = 336; height 4u = 96; horizontal allocation 1u inset + 2u identity + 1u gap + 5u name + 1u gap + 3u tools + 1u inset. Source and assembled fragments differ only by a vertical translation of 148 units. The assigned identifier is new text within the name allocation. No background grid or hatch is needed. - + @@ -12,12 +100,12 @@ - + - + @@ -34,19 +122,21 @@ - - - + + + + + Registered agent - - + + - + @@ -58,13 +148,17 @@ agent_07 - - + + + + + 14u + u u diff --git a/apps/ui/app/plotter/engineering-studies.tsx b/apps/ui/app/plotter/engineering-studies.tsx index 8c9fb7a0ff..f3214f6604 100644 --- a/apps/ui/app/plotter/engineering-studies.tsx +++ b/apps/ui/app/plotter/engineering-studies.tsx @@ -1,3 +1,7 @@ +import { + arrow, + standard, +} from "../../skills/plotter/assets/foundation/index.mjs"; import { BlueprintDrawing } from "./blueprint-drawing"; import { ProductionChainHatching } from "./production-chain-hatching"; import type { IllustrationStudy } from "./study-types"; @@ -54,7 +58,6 @@ function constructionGrid( ].join(" "); } -const arrowOffset = 6 / Math.SQRT2; const cpu = { x: 220, y: 144, size: 120, pinPitch: 12, pinLength: 12 }; const cpuPinOffsets = Array.from( { length: 8 }, @@ -224,15 +227,26 @@ const assemblyGrid = constructionGrid( ], ); +const assemblyTransferArrow = arrow({ + kind: "line", + from: [256, 288], + to: [304, 288], +}); +const assemblyDropArrow = arrow({ + kind: "line", + from: [280, 182], + to: [280, 200], +}); + export function ProductionChainStudy() { return ( - - - + + + - + {assemblyStations.map((x) => ( {/* An open frame. The core shares its eventual side and bottom edges. */} @@ -246,25 +260,19 @@ export function ProductionChainStudy() { - + - - + + - + Core diff --git a/apps/ui/app/plotter/expanded-studies.tsx b/apps/ui/app/plotter/expanded-studies.tsx index a8fca38581..69372b7d89 100644 --- a/apps/ui/app/plotter/expanded-studies.tsx +++ b/apps/ui/app/plotter/expanded-studies.tsx @@ -5,9 +5,20 @@ import type { IllustrationStudy } from "./study-types"; function DrawingAgentRegistration() { return ( - - - + + + - + - + @@ -68,9 +79,12 @@ function DrawingAgentRegistration() { {"Tools"} - - - + + + {"Registered agent"} - - + + - + @@ -120,10 +141,10 @@ function DrawingAgentRegistration() { > {"agent_07"} - +
    -
    -

    - Plotter illustrations -

    -

    - Simple forms. Fine lines. A collection of monochrome illustrations - and animations exploring geometry, interfaces, and the systems - behind familiar things. -

    - - + + + + +
    - Illustrations + Catalogue +

    + Choose a construction relationship to borrow, then{" "} + + give Plotter your own brief + + . The{" "} + + visual grammar + {" "} + explains the marks behind these drawings. +

    @@ -258,18 +238,18 @@ export default function Page() {
    -
    -

    Draw in the same direction.

    +
    +

    Make a drawing of your own.

    - Plotter is also a reusable skill, with visual references and - guidance for creating new illustrations in this style. + Start with a relationship you found here. The skill includes the + references and the workflow to develop it.

    - Explore the Plotter skill + Install and use Plotter ↑
    diff --git a/apps/ui/app/plotter/plotter-intro.tsx b/apps/ui/app/plotter/plotter-intro.tsx new file mode 100644 index 0000000000..29e5cde125 --- /dev/null +++ b/apps/ui/app/plotter/plotter-intro.tsx @@ -0,0 +1,54 @@ +import { ArrowDownIcon } from "lucide-react"; +import { Button } from "@/registry/default/ui/button"; + +export function PlotterIntro() { + return ( +
    +

    + Plotter illustrations +

    +

    + Simple forms. Fine lines. A skill for drawing and animating editable + illustrations, with a shared visual language behind every mark. +

    + +
    + ); +} diff --git a/apps/ui/app/plotter/plotter-prompts.tsx b/apps/ui/app/plotter/plotter-prompts.tsx new file mode 100644 index 0000000000..758b8ab1d1 --- /dev/null +++ b/apps/ui/app/plotter/plotter-prompts.tsx @@ -0,0 +1,220 @@ +"use client"; + +import { CheckIcon, CopyIcon, DownloadIcon } from "lucide-react"; +import { type ReactNode, useEffect, useRef, useState } from "react"; +import { Button } from "@/registry/default/ui/button"; +import { Tabs, TabsList, TabsPanel, TabsTab } from "@/registry/default/ui/tabs"; +import { + animationPrompt, + drawingPrompt, + plotterDownload, +} from "./plotter-usage-content"; + +function Prompt({ text }: { text: string }) { + const [status, setStatus] = useState<"idle" | "copied" | "failed">("idle"); + const timer = useRef | null>(null); + useEffect( + () => () => { + if (timer.current) clearTimeout(timer.current); + }, + [], + ); + + async function copy() { + if (timer.current) clearTimeout(timer.current); + try { + if (!navigator.clipboard?.writeText) + throw new Error("Clipboard unavailable"); + await navigator.clipboard.writeText(text); + setStatus("copied"); + timer.current = setTimeout(() => setStatus("idle"), 2000); + } catch { + setStatus("failed"); + } + } + + return ( +
    +

    + {text} +

    +
    + + + {status === "failed" + ? "Select the prompt text and copy it manually." + : status === "copied" + ? "Ready to paste into your agent." + : "Adapt the subject and placement to your brief."} + +
    +
    + ); +} + +export function PlotterPrompts({ + drawing, + animation, +}: { + drawing: ReactNode; + animation: ReactNode; +}) { + return ( +
    + + + Draw an illustration + Animate a drawing + + +
    +
    +

    + Start with one relationship. +

    +

    + Give your agent a subject, a placement, and an output format. + Plotter supplies the construction references and drawing + workflow. +

    + +

    + Looking for a direction? Browse the{" "} + + catalogue + + , then use the{" "} + + visual grammar + {" "} + to choose supporting marks. +

    +
    +
    +
    +
    {drawing}
    +
    +
    +
    +

    + Agent registration{" "} + + AG01 + +

    +

    + Editable paths and labels · Self-contained SVG +

    +
    + +
    +
    +
    +
    + +
    +
    +

    + Give the drawing a reason to move. +

    +

    + Start from an editable still.{" "} + + Download the assembly-line SVG + {" "} + and attach it with this prompt. +

    + +

    + One skill, a separate motion workflow. Explore more{" "} + + animation examples + + . +

    +
    +
    +
    +
    {animation}
    +
    +
    +
    +

    + An assembly line{" "} + + W05 + +

    +

    + Editable SVG + player · Open the HTML in a browser +

    +
    + +
    +
    +
    +
    +
    +

    + Scroll sideways to see the full drawing. +

    +

    + These are editable examples to study and adapt. Downloads need no coss + runtime. Labels use Cal Sans when available, with a sans-serif fallback. +

    +
    + ); +} diff --git a/apps/ui/app/plotter/plotter-token-guide.tsx b/apps/ui/app/plotter/plotter-token-guide.tsx new file mode 100644 index 0000000000..a21115d2b1 --- /dev/null +++ b/apps/ui/app/plotter/plotter-token-guide.tsx @@ -0,0 +1,294 @@ +"use client"; + +import { + type CSSProperties, + type ReactNode, + useEffect, + useId, + useRef, + useState, +} from "react"; +import { Button } from "@/registry/default/ui/button"; +import standard from "../../skills/plotter/assets/foundation/standard.json"; + +const controls = [ + { + token: "--plotter-stroke-primary", + label: "Primary stroke", + hint: "The outlines of the carriers and cores.", + max: standard.tokens["--plotter-stroke-primary"] * 2, + }, + { + token: "--plotter-opacity-guide", + label: "Guide opacity", + hint: "The quiet alignment and extension lines.", + max: 1, + }, + { + token: "--plotter-opacity-hatch", + label: "Hatch opacity", + hint: "Only the diagonal lines on the core faces.", + max: 1, + }, +] as const; + +type ControlToken = (typeof controls)[number]["token"]; +type Values = Record; + +const defaults: Values = { + "--plotter-stroke-primary": standard.tokens["--plotter-stroke-primary"], + "--plotter-opacity-guide": standard.tokens["--plotter-opacity-guide"], + "--plotter-opacity-hatch": standard.tokens["--plotter-opacity-hatch"], +}; + +export function PlotterTokenGuide({ children }: { children: ReactNode }) { + const id = useId(); + const cssRef = useRef(null); + const [values, setValues] = useState(defaults); + const [copyState, setCopyState] = useState<"idle" | "copied" | "manual">( + "idle", + ); + const css = `.illustration {\n --plotter-ink: currentColor;\n${controls + .map(({ token }) => ` ${token}: ${values[token]};`) + .join("\n")}\n}`; + const previewStyle: CSSProperties & Values = values; + + useEffect(() => { + if (copyState !== "copied") return; + const timeout = window.setTimeout(() => setCopyState("idle"), 1600); + return () => window.clearTimeout(timeout); + }, [copyState]); + + async function copyCss() { + try { + await navigator.clipboard.writeText(css); + setCopyState("copied"); + } catch { + cssRef.current?.focus(); + cssRef.current?.select(); + setCopyState("manual"); + } + } + + return ( +
    +
    +

    + Make it fit your page +

    +

    + Adjust ink, stroke widths, and opacity without changing the drawing. + Try the shared tokens on W05, then copy the CSS to a wrapper around + your inline SVG. +

    +
    + +
    +
    +
    + {controls.map(({ token, label, hint, max }) => ( +
    +
    + + + {values[token].toFixed(2)} + +
    + { + const value = event.currentTarget.valueAsNumber; + setValues((current) => ({ + ...current, + [token]: value, + })); + setCopyState("idle"); + }} + step={0.01} + type="range" + value={values[token]} + /> +

    + {hint} +

    +
    + ))} +
    + +
    +
    + +
    + + +
    +
    +