diff --git a/apps/ui/AGENTS.md b/apps/ui/AGENTS.md index 7ccb23602..ceba1a655 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/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. --- @@ -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/plotter/README.md b/apps/ui/app/plotter/README.md new file mode 100644 index 000000000..eeb665d91 --- /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/plotter/blueprint-drawing.tsx b/apps/ui/app/plotter/blueprint-drawing.tsx new file mode 100644 index 000000000..36070a82b --- /dev/null +++ b/apps/ui/app/plotter/blueprint-drawing.tsx @@ -0,0 +1,44 @@ +import type { ReactNode } from "react"; + +export function BlueprintDrawing({ children }: { children: ReactNode }) { + return ( + + ); +} + +// 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 new file mode 100644 index 000000000..b92795a4e --- /dev/null +++ b/apps/ui/app/plotter/cal-studies.tsx @@ -0,0 +1,187 @@ +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 TwoTimeZones() { + return ( + + + + + + + + + + + {[104, 176, 248, 320, 392, 464].map((x) => ( + + ))} + + + + + + + + + + + + UTC + + + UTC+03 + + + 09:00 + + + 12:00 + + + + {["07", "08", "09", "10", "11", "12"].map((hour, index) => ( + + {hour} + + ))} + {["10", "11", "12", "13", "14", "15"].map((hour, index) => ( + + {hour} + + ))} + + 1h + + + Same booking + + + + + ); +} + +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 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 000000000..e90acb307 --- /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/coss-concepts/common-interface-study.tsx b/apps/ui/app/plotter/coss-concepts/common-interface-study.tsx new file mode 100644 index 000000000..099693117 --- /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 000000000..e17415cba --- /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 000000000..03a6a6455 --- /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/dimensions.tsx b/apps/ui/app/plotter/dimensions.tsx new file mode 100644 index 000000000..062e57d5e --- /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/disconnected-plug.tsx b/apps/ui/app/plotter/disconnected-plug.tsx new file mode 100644 index 000000000..9da10c182 --- /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 000000000..3a33eeaf2 --- /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 000000000..11730b1e1 --- /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 000000000..94393c7ec --- /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/flat-cal-studies.tsx b/apps/ui/app/plotter/flat-cal-studies.tsx new file mode 100644 index 000000000..c45bf4f85 --- /dev/null +++ b/apps/ui/app/plotter/flat-cal-studies.tsx @@ -0,0 +1,257 @@ +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: "Scheduling", + illustration: , + }, + { + 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: "Scheduling", + illustration: , + }, + { + 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: "Scheduling", + illustration: , + }, +]; diff --git a/apps/ui/app/plotter/flat-object-studies.tsx b/apps/ui/app/plotter/flat-object-studies.tsx new file mode 100644 index 000000000..b1549daab --- /dev/null +++ b/apps/ui/app/plotter/flat-object-studies.tsx @@ -0,0 +1,215 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; +import type { IllustrationStudy } from "./study-types"; + +const keyboardKeys = [ + { 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(({ x, y, label }) => ( + + ))} + + + + + + + + + + {keyboardKeys.map(({ x, y, label }) => ( + + {label} + + ))} + + + + + + + + + Pitch / u + + + 64 + + + ¼u + + + Stagger / ¼u + + + + ); +} + +function LampElevationStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + 2r + + + r + + + a + + + a + + + Generating circle + + + + ); +} + +const folders = [ + { x: 104, width: 216, height: 176, opacity: 1 }, + { x: 352, width: 136, height: 104, opacity: 0.62 }, +]; + +function FolderPlanStudy() { + return ( + + + + + + + + + {folders.map(({ x, width, height, opacity }) => ( + + ))} + + + + + + + + + + + + + + + + + + Tab / 64 + + + Tab / 64 + + + R8 + + + w₁ + + + w₂ + + + One tab / two widths + + + + ); +} + +export const flatObjectStudies: IllustrationStudy[] = [ + { + id: "object-keyboard-plan", + code: "O03", + title: "A keyboard, in detail", + description: + "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: , + }, + { + id: "object-lamp-elevation", + code: "O04", + title: "A lamp, drawn in elevation", + description: + "Half a circle becomes the shade. Its center establishes the support axis and divides the base into equal halves.", + category: "Objects", + illustration: , + }, + { + id: "object-folder-plan", + code: "O05", + title: "A family of folders", + description: + "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/flat-system-studies.tsx b/apps/ui/app/plotter/flat-system-studies.tsx new file mode 100644 index 000000000..8bebd8a7c --- /dev/null +++ b/apps/ui/app/plotter/flat-system-studies.tsx @@ -0,0 +1,266 @@ +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: , + }, + { + 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: "Systems", + illustration: , + }, + { + 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: "Systems", + illustration: , + }, +]; diff --git a/apps/ui/app/plotter/illustration-gallery.tsx b/apps/ui/app/plotter/illustration-gallery.tsx new file mode 100644 index 000000000..bd31ab94c --- /dev/null +++ b/apps/ui/app/plotter/illustration-gallery.tsx @@ -0,0 +1,129 @@ +"use client"; + +import { useSearchParams } from "next/navigation"; +import { useEffect } from "react"; +import { Button } from "@/registry/default/ui/button"; +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 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 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); + 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}` : ""}#illustrations`, + ); + } + + return ( + <> +
+
+ {["All", ...categories].map((item) => ( + + ))} +
+

+ {visible.length} illustrations +

+
+ +
    + {visible.map((item) => ( +
  • +
    +
    +
    + {item.illustration} +
    +
    +
    +
    +

    {item.title}

    +

    + {item.category} +

    +

    + {item.description} +

    +
    + + {item.code} + +
    +
    +
  • + ))} +
+ + ); +} diff --git a/apps/ui/app/plotter/illustrations.tsx b/apps/ui/app/plotter/illustrations.tsx new file mode 100644 index 000000000..2d888e647 --- /dev/null +++ b/apps/ui/app/plotter/illustrations.tsx @@ -0,0 +1,75 @@ +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"; +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 { systemStateStudies } from "./system-state-studies"; +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, + ...contentStateStudies, + ...systemStateStudies, + ...engineeringStudies, + ...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/plotter/interface-detail-studies.tsx b/apps/ui/app/plotter/interface-detail-studies.tsx new file mode 100644 index 000000000..4dc4047da --- /dev/null +++ b/apps/ui/app/plotter/interface-detail-studies.tsx @@ -0,0 +1,239 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; +import type { IllustrationStudy } from "./study-types"; + +function BrowserPlanStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + coss.com + + + + + R24 + + + R12 + + + Content inset + + + 24 + + + + ); +} + +function PhonePlanStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Safe area + + + + + R32 + + + Offset / 8 + + + 10u + + + 3u + + + 12u + + + 3u + + + 1.5u + + + 7u + + + 1.5u + + + u = 16 + + + + ); +} + +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 browser corner relates the window radius to its tab radius and content inset. Cropped edges leave the rest of the screen outside the drawing.", + category: "Interfaces", + illustration: , + }, + { + id: "interface-phone-plan", + code: "I05", + title: "The screen within the frame", + description: + "Shared corner centers and an eight-pixel offset construct the frame. Symmetric vertical and horizontal margins locate the safe area within it.", + 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: , + }, +]; diff --git a/apps/ui/app/plotter/missing-destination.tsx b/apps/ui/app/plotter/missing-destination.tsx new file mode 100644 index 000000000..685fe45df --- /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/network-studies.tsx b/apps/ui/app/plotter/network-studies.tsx new file mode 100644 index 000000000..18021140b --- /dev/null +++ b/apps/ui/app/plotter/network-studies.tsx @@ -0,0 +1,79 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; +import type { IllustrationStudy } from "./study-types"; + +function RegistrationMarks() { + return ( + + + + + ); +} + +function BranchingWorkflowStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + r + + + 4r + + + 4r + + + r + + + in + + + 01 + + + 02 + + + 03 + + + + + ); +} + +export const networkStudies: IllustrationStudy[] = [ + { + id: "workflow-branch", + code: "W02", + title: "A route, constructed", + description: + "One selected route and two alternatives share a junction. A repeated bend radius establishes the spacing between the three outlets.", + category: "Systems", + illustration: , + }, +]; 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 000000000..54fbd5ab7 --- /dev/null +++ b/apps/ui/app/plotter/newtons-cradle-motion-drawing.tsx @@ -0,0 +1,227 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; + +const cradle = { + pivots: [216, 260, 304, 348, 392], + pivotY: 120, + length: 176, + radius: 22, + pivotRadius: 2.4, + amplitude: 30, + interiorAmplitude: 0.75, + duration: 2, + turnaroundEmphasis: 0.25, +} 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. +// 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. +// 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); + 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 }) => { + 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, + duration: cradle.duration, + }; +} + +const { samples, duration } = swingSamples(); +const keyTimes = samples.map(({ time }) => time).join(";"); + +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: sample.angle * swingScale(side, 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); + // 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 * response * scale).toFixed(9)} 0.666666667 ${(1 - next.velocity * response * scale).toFixed(9)}`, + ]; + }) + .join(";"), + }; +} + +function Pendulum({ x }: { x: number }) { + return ( + <> + + + {x === 304 && ( + + )} + + ); +} + +function SwingingPendulum({ side, x }: { side: PendulumRole; x: number }) { + const motion = swingMotion(side, x); + return ( + + + + + ); +} + +const radiusOffset = cradle.radius / Math.SQRT2; + +function radiusLeader(angle: number) { + const radians = (angle * Math.PI) / 180; + const x = 304 - cradle.length * Math.sin(radians) - radiusOffset; + const y = cradle.pivotY + cradle.length * Math.cos(radians) + radiusOffset; + // Keep the label and horizontal landing fixed; the 45° tip tracks the bob. + return `M${x} ${y} ${x + y - 328} 328H248 M${x} ${y + 6}V${y}H${x - 6}`; +} + +const radiusLeaderValues = samples + .map(({ angle }) => radiusLeader(angle * swingScale("middle", angle))) + .join(";"); + +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. */} + + + + + + + + + 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 new file mode 100644 index 000000000..3ed154d6f --- /dev/null +++ b/apps/ui/app/plotter/not-found-architecture-studies.tsx @@ -0,0 +1,111 @@ +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 + + + + + + + + + ); +} + +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: "Empty states", + 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: "Empty states", + illustration: , + }, +]; diff --git a/apps/ui/app/plotter/not-found-blueprint.tsx b/apps/ui/app/plotter/not-found-blueprint.tsx new file mode 100644 index 000000000..db9281762 --- /dev/null +++ b/apps/ui/app/plotter/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/plotter/not-found-geometry-studies.tsx b/apps/ui/app/plotter/not-found-geometry-studies.tsx new file mode 100644 index 000000000..8dab4e62a --- /dev/null +++ b/apps/ui/app/plotter/not-found-geometry-studies.tsx @@ -0,0 +1,78 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; +import type { IllustrationStudy } from "./study-types"; + +function UnbuiltArch() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Missing keystone + + + R126 + + + R90 + + + 7u + + + h + + + Unbuilt / Section A + + + 1 : 1 + + + + ); +} + +export const notFoundGeometryStudies: IllustrationStudy[] = [ + { + 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: "Empty states", + illustration: , + }, +]; 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 000000000..3a33eeaf2 --- /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 000000000..fcd9d76a2 --- /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/object-studies.tsx b/apps/ui/app/plotter/object-studies.tsx new file mode 100644 index 000000000..d5b1454e3 --- /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/plotter/original-not-found-blueprint.tsx b/apps/ui/app/plotter/original-not-found-blueprint.tsx new file mode 100644 index 000000000..2cd5064c3 --- /dev/null +++ b/apps/ui/app/plotter/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/plotter/original-particles-blueprint.tsx b/apps/ui/app/plotter/original-particles-blueprint.tsx new file mode 100644 index 000000000..42049125c --- /dev/null +++ b/apps/ui/app/plotter/original-particles-blueprint.tsx @@ -0,0 +1,80 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; + +export function OriginalParticlesBlueprint() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Ready + + + Workspace + + + Available + + + + + R16 + + + u + + + u + + + u + + + 10u + + + Shared inset / 24 + + + + ); +} diff --git a/apps/ui/app/plotter/page.tsx b/apps/ui/app/plotter/page.tsx new file mode 100644 index 000000000..28780aed9 --- /dev/null +++ b/apps/ui/app/plotter/page.tsx @@ -0,0 +1,242 @@ +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", + description: + "A collection of monochrome illustrations and animations 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 + and animations exploring geometry, interfaces, and the systems + behind familiar things. +

+ +
+
+
+

+ Animations +

+

5 examples

+
+
+
+

Foundations / M01

+

+ Changing geometry +

+

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

+
+
+ + + +
+
+
+
+

Particles / C03

+

+ Three assemblies +

+

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

+
+
+ + + +
+
+
+
+

+ 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" + > + + +
+
+
+ +
+

+ Illustrations +

+ + 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/plotter/particles-assembly-content.tsx b/apps/ui/app/plotter/particles-assembly-content.tsx new file mode 100644 index 000000000..fc1232553 --- /dev/null +++ b/apps/ui/app/plotter/particles-assembly-content.tsx @@ -0,0 +1,109 @@ +import type { ReactNode } from "react"; + +export type AssemblyScene = "workspace" | "booking" | "activity"; + +export function AssemblyContent({ + scene, + part, + trailingMotion, + thumbMotion, +}: { + scene: AssemblyScene; + part: 0 | 1 | 2; + trailingMotion?: ReactNode; + thumbMotion?: ReactNode; +}) { + const label = { + workspace: ["Studio", "Public", "Open"], + booking: ["Wed 28", "10:30", "Meet"], + activity: ["", "", "Ready"], + }[scene][part]; + const labelX = part === 0 || (scene === "booking" && part === 1) ? 40 : 14; + + return ( + + + {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 000000000..53cfbeacb --- /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 000000000..51a08f4f1 --- /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 new file mode 100644 index 000000000..ffe2c4818 --- /dev/null +++ b/apps/ui/app/plotter/particles-blueprint.tsx @@ -0,0 +1,113 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; + +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"} + + + + ); +} + +export function ParticlesBlueprint() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 000000000..3a33eeaf2 --- /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 000000000..fb4f53caa --- /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 new file mode 100644 index 000000000..4cf10a624 --- /dev/null +++ b/apps/ui/app/plotter/popular-service-studies.tsx @@ -0,0 +1,340 @@ +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 ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Text + + + Title + + + Tasks + + + + + 5u + + + Insert block + + + Page + + + Inset + + + + ); +} + +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: + "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: , + }, + { + 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: , + }, +]; diff --git a/apps/ui/app/plotter/primitives-blueprint.tsx b/apps/ui/app/plotter/primitives-blueprint.tsx new file mode 100644 index 000000000..eccb0fc4b --- /dev/null +++ b/apps/ui/app/plotter/primitives-blueprint.tsx @@ -0,0 +1,98 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; + +const gridCoordinates = [164, 204, 244, 284, 324, 364, 404]; +const radiusDiagonal = 120 / Math.sqrt(2); +const angleDiagonal = 24 / Math.sqrt(2); +const radiusEndpoint = { x: 284 + radiusDiagonal, y: 204 - radiusDiagonal }; + +export function PrimitivesBlueprint() { + return ( + + + {gridCoordinates.map((coordinate) => ( + + ))} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 6u + + + 6u + + + u + + + r = 3u + + + 45° + + + 01 + + + 02 + + + + + + + + + ); +} diff --git a/apps/ui/app/plotter/primitives-button.tsx b/apps/ui/app/plotter/primitives-button.tsx new file mode 100644 index 000000000..2a51c30d8 --- /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 new file mode 100644 index 000000000..f0f8a6975 --- /dev/null +++ b/apps/ui/app/plotter/primitives-construction-motion.tsx @@ -0,0 +1,72 @@ +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. +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 * morphStageDuration}s`, + calcMode: "spline" as const, + 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: morphKeyTimes( + 0, + delay, + delay + duration, + 2.45, + 2.5, + morphCycleDuration, + ), + 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 000000000..61c7c9f2d --- /dev/null +++ b/apps/ui/app/plotter/primitives-morph-details.tsx @@ -0,0 +1,213 @@ +import { ButtonConstruction } from "./primitives-button"; +import { + ConstructionAnnotation, + ConstructionMotion, +} from "./primitives-construction-motion"; +import { secondaryLine } from "./primitives-morph-style"; +import { SliderConstruction } from "./primitives-slider"; +import { SwitchConstruction } from "./primitives-switch"; + +export const morphDetailIds = [ + "circle", + "capsule", + "switch", + "button", + "rounded", + "slider", +] 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 / Avatar + + + + + ); + } + + if (stage === "capsule") { + return ( + + + {[204, 364].map((center) => ( + + + + + ))} + + + + {[204, 364].map((center) => ( + + ))} + + + + + + 2u + + + + + + + + + 4u + + + 02 / Pill + + + + + ); + } + + if (stage === "switch") return ; + + if (stage === "button") return ; + + if (stage === "slider") return ; + + if (stage === "rounded") { + return ( + + {/* A 1u inset of the R1u outline produces a sharp 4u square. */} + + + + + + + + + + + + + + + + + + + + + + 1u + + + 4u + + + 05 / Card + + + + + ); + } + + 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 000000000..4e375694f --- /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 000000000..afb6704a5 --- /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 = 6 * 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 new file mode 100644 index 000000000..f2c6527e8 --- /dev/null +++ b/apps/ui/app/plotter/primitives-morph-transitions.tsx @@ -0,0 +1,142 @@ +import { secondaryLine } from "./primitives-morph-style"; +import { morphCycleDuration as duration } from "./primitives-morph-timing"; + +type Timing = { start: number; end: number }; + +const circleToCapsule = { start: 2.4, end: 3.2 }; +const buttonToCard = { start: 12, end: 12.8 }; + +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 000000000..181a64a95 --- /dev/null +++ b/apps/ui/app/plotter/primitives-morph.tsx @@ -0,0 +1,306 @@ +"use client"; + +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 { SliderGridMask } from "./primitives-slider"; +import { SwitchThumb } from "./primitives-switch"; + +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 }: Shape) { + const left = 284 - width / 2; + const right = 284 + width / 2; + const top = 204 - height / 2; + const bottom = 204 + height / 2; + const a = r * 0.55228475; + 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, + 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} + C${right} ${bottom - r + a} ${right - r + a} ${bottom} ${right - r} ${bottom} + L${left + r} ${bottom} + C${left + r - a} ${bottom} ${left} ${bottom - r + a} ${left} ${bottom - r} + L${left} ${top + r} + 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 + M164 348V372 M204 348V372`, + dimensions: `M${left} 46H264 M304 46H${right} + 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`, + }; +} + +const circle = construct({ width: 240, height: 240, corner: 120 }); +const pill = construct({ width: 320, height: 160, corner: 80 }); +const stages = [ + circle, + pill, + pill, + construct({ width: 320, height: 160, corner: 24 }), + construct({ width: 240, height: 240, corner: 40 }), + construct({ width: 320, height: 12, corner: 6 }), + 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 ( + ["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) => + 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.showHeight)} + /> + + stage.heightDimension)} + /> + + + + stage.outline)} + /> + + + stage.showHeight)} + /> + {tangentIds.map((id) => ( + + stage.points[id][0])} + /> + stage.points[id][1])} + /> + + ))} + + + {morphDetailIds.map((stage, index) => ( + + + + + ))} + + + 6u + + + stage.showHeight)} + /> + + 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 000000000..586fdb601 --- /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 000000000..65202934b --- /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/app/plotter/primitives-slider.tsx b/apps/ui/app/plotter/primitives-slider.tsx new file mode 100644 index 000000000..a3d9006c5 --- /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/app/plotter/primitives-switch.tsx b/apps/ui/app/plotter/primitives-switch.tsx new file mode 100644 index 000000000..7918526b0 --- /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/app/plotter/production-chain-motion-drawing.tsx b/apps/ui/app/plotter/production-chain-motion-drawing.tsx new file mode 100644 index 000000000..f10a181ae --- /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 new file mode 100644 index 000000000..4a14025d6 --- /dev/null +++ b/apps/ui/app/plotter/service-studies.tsx @@ -0,0 +1,470 @@ +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 payments + + + 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 + + + Cycle end + + + + + 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: , + }, + { + 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: , + }, + { + id: "service-stripe-settlement", + code: "B03", + title: "Stripe · Payments, combined", + description: + "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: , + }, + { + 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 within a common cycle boundary.", + 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/plotter/state-studies.tsx b/apps/ui/app/plotter/state-studies.tsx new file mode 100644 index 000000000..effa4058c --- /dev/null +++ b/apps/ui/app/plotter/state-studies.tsx @@ -0,0 +1,22 @@ +import { + MissingDestinationBlueprint, + MissingDestinationCompact, +} from "./missing-destination"; +import type { IllustrationStudy } from "./study-types"; + +export const stateStudies: IllustrationStudy[] = [ + { + id: "state-missing-page", + code: "S01", + title: "The missing destination", + 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: , + 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 new file mode 100644 index 000000000..8d4c40cb7 --- /dev/null +++ b/apps/ui/app/plotter/study-types.ts @@ -0,0 +1,24 @@ +import type { ReactNode } from "react"; + +export type StudyCategory = + | "Foundations" + | "Interfaces" + | "Scheduling" + | "Services" + | "Systems" + | "Objects" + | "Empty states"; + +export type IllustrationStudy = { + id: string; + code: string; + title: string; + 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 000000000..eab53be46 --- /dev/null +++ b/apps/ui/app/plotter/system-state-studies.tsx @@ -0,0 +1,582 @@ +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 +// 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 ( + + + + + ); +} + +const gearModule = 14; +const meshAngle = Math.atan2(78.4, 58.8); + +type Gear = { + cx: number; + cy: number; + teeth: number; + phase: number; + 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 [ + 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) }; + }); + 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`; +} + +// 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: 408.8, + cy: 270.4, + teeth: 6, + phase: meshAngle + Math.PI - Math.PI / 6, + hub: 10.5, + }, +]; + +function GearsForm() { + return ( + <> + {gears.map((gear) => ( + + + + + ))} + + ); +} + +function MaintenanceStudy() { + return ( + + + {gears.map((gear) => ( + + + + + ))} + + + + + + + + + + + + Drive disengaged + + + + + Common pitch · 10 : 8 : 6 + + + + ); +} + +function MaintenanceCompact() { + 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 a common pitch. A separated drive interrupts the mechanism.", + 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 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: , + 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 new file mode 100644 index 000000000..7190f7cc7 --- /dev/null +++ b/apps/ui/app/plotter/system-studies.tsx @@ -0,0 +1,437 @@ +import { BlueprintDrawing } from "./blueprint-drawing"; +import { ParticlesBlueprint } from "./particles-blueprint"; +import type { IllustrationStudy } from "./study-types"; + +function Registration() { + return ( + + + + + ); +} + +function TangencyStudy() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + 2r + + + r + + + d + + + Common tangent + + + + + ); +} + +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: + "Two radii share a baseline and an inclined tangent. The contact geometry connects unequal circles through one construction rule.", + category: "Foundations", + 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: "Foundations", + 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: "Foundations", + 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: "Interfaces", + 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: "Interfaces", + 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: "Interfaces", + illustration: , + }, +]; diff --git a/apps/ui/next.config.ts b/apps/ui/next.config.ts index cdd60e02d..069903578 100644 --- a/apps/ui/next.config.ts +++ b/apps/ui/next.config.ts @@ -7,6 +7,16 @@ 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: "/plotter", + 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 000000000..f6352765d --- /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 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). + +## 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 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 new file mode 100644 index 000000000..58e17ecc9 --- /dev/null +++ b/apps/ui/skills/plotter/SKILL.md @@ -0,0 +1,50 @@ +--- +name: plotter +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 + +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 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 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. + +## 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 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? +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 000000000..99ec3f0a2 --- /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/approved-references.md b/apps/ui/skills/plotter/references/approved-references.md new file mode 100644 index 000000000..2b246774d --- /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/coss-product-concepts.md b/apps/ui/skills/plotter/references/coss-product-concepts.md new file mode 100644 index 000000000..cc335f2bd --- /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 new file mode 100644 index 000000000..7ca30cb82 --- /dev/null +++ b/apps/ui/skills/plotter/references/direction.md @@ -0,0 +1,223 @@ +# 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. + +## Current references + +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. + +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. + +## 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. 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 + +These entries record earlier feedback and its implementation response. Current reference status comes from the explicit selection above. + +| 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 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. | +| 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” | 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. 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. 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 | 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 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. + +## 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. + +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. + +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. + +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 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: + +- **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. + +## 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 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 + +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: + +- 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 was our response and was later explicitly included in the approved reference set. + +## 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/drafting-standard.md b/apps/ui/skills/plotter/references/drafting-standard.md new file mode 100644 index 000000000..c03ff4046 --- /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 new file mode 100644 index 000000000..cecb72de1 --- /dev/null +++ b/apps/ui/skills/plotter/references/implementation.md @@ -0,0 +1,50 @@ +# SVG and coss integration + +## 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. + +- 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/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` | +| 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 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 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=` 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/linear.png b/apps/ui/skills/plotter/references/linear.png new file mode 100644 index 000000000..f0445c393 Binary files /dev/null and b/apps/ui/skills/plotter/references/linear.png differ diff --git a/apps/ui/skills/plotter/references/not-found-interrupted-path.png b/apps/ui/skills/plotter/references/not-found-interrupted-path.png new file mode 100644 index 000000000..e2b13267a Binary files /dev/null and b/apps/ui/skills/plotter/references/not-found-interrupted-path.png differ 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 000000000..22844f0d8 Binary files /dev/null and b/apps/ui/skills/plotter/references/particles-recombination.png differ diff --git a/apps/ui/skills/plotter/references/particles.png b/apps/ui/skills/plotter/references/particles.png new file mode 100644 index 000000000..aaac9bc6f Binary files /dev/null and b/apps/ui/skills/plotter/references/particles.png differ diff --git a/apps/ui/skills/plotter/references/primitives.png b/apps/ui/skills/plotter/references/primitives.png new file mode 100644 index 000000000..ac4ef3d87 Binary files /dev/null and b/apps/ui/skills/plotter/references/primitives.png differ 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 000000000..ddd8423b9 --- /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 000000000..25aa6754e --- /dev/null +++ b/apps/ui/skills/plotter/references/source-audit.md @@ -0,0 +1,167 @@ +# 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. + +**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` | `3930716d38c6` | +| `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 000000000..0366ab260 --- /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.