@ericrisco/rsc 1.1.0 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/manifest.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.1.0",
2
+ "version": "1.1.1",
3
3
  "counts": {
4
4
  "skills": 278
5
5
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ericrisco/rsc",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Eric Risco's agent-skills catalog as a granular, self-recommending CLI installer.",
5
5
  "type": "module",
6
6
  "bin": {
package/scripts/doctor.js CHANGED
@@ -8,6 +8,7 @@ import { listBackups } from './lib/backups.js';
8
8
  import { SDD_GATE_TEXT } from '../targets/hook-once.mjs';
9
9
  import { isEnabled, checkSello, readSello, countFindings, readEffectiveConfig, validateRiskConfig } from '../targets/sello.mjs';
10
10
  import { designIdentity } from './lib/design-identity.js';
11
+ import { startingPointSummary } from './lib/starting-point.js';
11
12
  import { countGaps } from './lib/capabilities.js';
12
13
 
13
14
  // The sello's health, surfaced where the user already looks (spec: non-blocking
@@ -62,6 +63,10 @@ export function doctor({ target, home, cwd }) {
62
63
  // stops without one; until lib/design-identity.js nothing checked it (P2). Reported here,
63
64
  // never nagged about and never blocking: a missing identity is low risk (P7).
64
65
  designIdentity: designIdentity(root),
66
+ // The other half of the same question: an identity is what this project settled on, a starting
67
+ // point is what it has to settle FROM. Three skills promise to propose one and none could look;
68
+ // this is the look. One field, not a section — a report grows for every user who runs it (P5).
69
+ designStartingPoint: startingPointSummary(root),
65
70
  // An inert gate must never read as an armed one (the same rule the sello follows):
66
71
  // the gitmoji guard has a per-project kill switch, so say which state it is in.
67
72
  gitmojiGuard: existsSync(join(root, '.rsc', '.no-gitmoji')) ? 'opted-out' : 'armed',
@@ -0,0 +1,314 @@
1
+ // What does this project already own to start from, and does its colour hold up?
2
+ //
3
+ // The design area declares three times that it will propose a starting point when the user has
4
+ // none — `design-loop` phase 1 on `skip`, the closing line of `design`'s brand grounding, and
5
+ // `design-dna`'s REUSE mode — and none of the three had anything behind it. An unbacked promise to
6
+ // propose falls back to the model's prior, which is the AI-template median the area exists to
7
+ // escape. Spec: 02-DOCS/wiki/sdd/specs/design-starting-point.md
8
+ //
9
+ // This file is the half that can be an algorithm (P1): listing what is installed, and refusing to
10
+ // propose a colour that fails contrast. The half that needs judgement — which reference is the right
11
+ // bar for what you are building — is prose, in design/references/starting-point.md.
12
+ //
13
+ // Read-only, always. `02-DOCS` is untracked and has no undo (P9), and a discovery pass has no
14
+ // business writing anywhere.
15
+ import { existsSync, lstatSync, readFileSync, readdirSync, statSync } from 'node:fs';
16
+ import { join } from 'node:path';
17
+ import { homedir } from 'node:os';
18
+ import { designIdentity, BRAND_DIR } from './design-identity.js';
19
+
20
+ // WCAG 2.2 AA. Body text, and the relaxed floor for large text and UI components.
21
+ export const TEXT_MIN = 4.5;
22
+ export const UI_MIN = 3.0;
23
+
24
+ // Which palette roles are held to which floor. `design-dna`'s schema fixes the role vocabulary
25
+ // (ground, ink, accent, secondary, hairline); anything outside it is skipped rather than guessed at,
26
+ // because inventing a threshold for an unknown role is how a gate starts approving things.
27
+ const TEXT_ROLES = new Set(['ink', 'secondary']);
28
+ const UI_ROLES = new Set(['accent']);
29
+ const GROUND_ROLE = 'ground';
30
+
31
+ // A record is only reusable if it carries the fields a style is actually rebuilt from. Half a record
32
+ // cannot be reused, so it is reported as unreadable instead of offered as a candidate.
33
+ const REQUIRED = ['palette'];
34
+ const DIMENSIONS = ['palette', 'type', 'space', 'surface', 'motion', 'signatures', 'voice'];
35
+
36
+ /** Where `design-dna` emits, and where rsc installs, in both scopes. */
37
+ export function defaultRoots({ home = homedir(), cwd = process.cwd() } = {}) {
38
+ const roots = [];
39
+ for (const base of home === cwd ? [cwd] : [cwd, home]) {
40
+ roots.push(join(base, '.claude', 'skills'));
41
+ roots.push(join(base, '.rsc', 'skills'));
42
+ }
43
+ return roots;
44
+ }
45
+
46
+ function parseHex(hex) {
47
+ if (typeof hex !== 'string') return null;
48
+ const m = /^#([0-9a-fA-F]{6})$/.exec(hex.trim());
49
+ if (!m) return null;
50
+ const n = parseInt(m[1], 16);
51
+ return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
52
+ }
53
+
54
+ // WCAG relative luminance. The sRGB transfer function, not a shortcut: an approximation here is a
55
+ // wrong ratio everywhere downstream, and it would pass a test written against this file's own output.
56
+ function luminance([r, g, b]) {
57
+ const lin = [r, g, b].map((c) => {
58
+ const s = c / 255;
59
+ return s <= 0.03928 ? s / 12.92 : ((s + 0.055) / 1.055) ** 2.4;
60
+ });
61
+ return 0.2126 * lin[0] + 0.7152 * lin[1] + 0.0722 * lin[2];
62
+ }
63
+
64
+ /**
65
+ * WCAG 2.x contrast ratio between two colours, 1.0 … 21.0. Symmetric.
66
+ * @param {string} a sRGB hex, `#rrggbb`
67
+ * @param {string} b sRGB hex, `#rrggbb`
68
+ */
69
+ export function contrastRatio(a, b) {
70
+ const ca = parseHex(a);
71
+ const cb = parseHex(b);
72
+ if (!ca || !cb) throw new TypeError(`not an sRGB hex colour: ${!ca ? a : b}`);
73
+ const la = luminance(ca);
74
+ const lb = luminance(cb);
75
+ const [hi, lo] = la >= lb ? [la, lb] : [lb, la];
76
+ return (hi + 0.05) / (lo + 0.05);
77
+ }
78
+
79
+ /**
80
+ * Does this palette's text hold up against its own ground?
81
+ *
82
+ * Runs before a measured colour is written as a proposed dimension: a value that fails is not
83
+ * proposed at all, rather than proposed and flagged. Three outcomes are kept apart on purpose —
84
+ * measured-and-passing, measured-and-failing, and **not measured**. Collapsing the third into the
85
+ * first is the vacuous pass this area has already paid for twice.
86
+ *
87
+ * @param {Array<{role: string, hex: string, name?: string}>} colors — `palette.colors` from a dna.json
88
+ * @returns {{ok: boolean, pairs: Array<object>, failures: Array<object>, skipped: Array<object>,
89
+ * unparsed: string[], reason: string}}
90
+ */
91
+ export function checkPairings(colors) {
92
+ const list = Array.isArray(colors) ? colors : [];
93
+ const pairs = [];
94
+ const skipped = [];
95
+ const unparsed = [];
96
+
97
+ const ground = list.find((c) => c && c.role === GROUND_ROLE);
98
+ const groundHex = ground ? parseHex(ground.hex) : null;
99
+ if (!ground) {
100
+ return { ok: false, pairs, failures: [], skipped, unparsed, reason: 'no ground role in the palette — nothing to measure against' };
101
+ }
102
+ if (!groundHex) {
103
+ unparsed.push(String(ground.hex));
104
+ return { ok: false, pairs, failures: [], skipped, unparsed, reason: `ground colour is not an sRGB hex: ${ground.hex}` };
105
+ }
106
+
107
+ for (const c of list) {
108
+ if (!c || c.role === GROUND_ROLE) continue;
109
+ const isText = TEXT_ROLES.has(c.role);
110
+ const isUi = UI_ROLES.has(c.role);
111
+ if (!isText && !isUi) {
112
+ // A hairline is not text. Saying so is not the same as saying it passed.
113
+ skipped.push({ role: c.role, name: c.name, reason: 'role carries no text, so no text floor applies' });
114
+ continue;
115
+ }
116
+ if (!parseHex(c.hex)) {
117
+ unparsed.push(String(c.hex));
118
+ continue;
119
+ }
120
+ const min = isText ? TEXT_MIN : UI_MIN;
121
+ const ratio = Math.round(contrastRatio(c.hex, ground.hex) * 100) / 100;
122
+ pairs.push({ role: c.role, name: c.name, fg: c.hex, bg: ground.hex, ratio, min, ok: ratio >= min });
123
+ }
124
+
125
+ const failures = pairs.filter((p) => !p.ok);
126
+ // Not measuring anything is not a pass. An empty failure list only means green when at least one
127
+ // pair was actually evaluated and nothing was left unparsed.
128
+ const ok = failures.length === 0 && unparsed.length === 0 && pairs.length > 0;
129
+ const reason = unparsed.length
130
+ ? `${unparsed.length} colour(s) could not be parsed, so they were not measured`
131
+ : pairs.length === 0
132
+ ? 'no text or UI role to measure against the ground'
133
+ : failures.length
134
+ ? `${failures.length} pair(s) below their floor`
135
+ : `${pairs.length} pair(s) measured, all above their floor`;
136
+ return { ok, pairs, failures, skipped, unparsed, reason };
137
+ }
138
+
139
+ function readRecord(dir, slug) {
140
+ const file = join(dir, 'dna.json');
141
+ let raw;
142
+ try {
143
+ raw = readFileSync(file, 'utf8');
144
+ } catch (e) {
145
+ return { error: `cannot read dna.json: ${e.code || e.message}` };
146
+ }
147
+ let dna;
148
+ try {
149
+ dna = JSON.parse(raw);
150
+ } catch (e) {
151
+ return { error: `dna.json is not valid JSON: ${e.message}` };
152
+ }
153
+ const missing = REQUIRED.filter((k) => !dna || !dna[k]);
154
+ if (missing.length) return { error: `record is missing ${missing.join(', ')} — half a record cannot be reused` };
155
+
156
+ return {
157
+ record: {
158
+ slug: (dna.meta && dna.meta.slug) || slug,
159
+ name: (dna.meta && dna.meta.name) || slug,
160
+ path: file,
161
+ covers: DIMENSIONS.filter((d) => dna[d]),
162
+ palette: (dna.palette && dna.palette.colors) || [],
163
+ },
164
+ };
165
+ }
166
+
167
+ // What the harness's own identity articles mention. A record the project actually used outranks one
168
+ // that merely happens to be installed — but the others stay on the list, because choosing for the
169
+ // user is exactly what the spec forbids.
170
+ function citedSlugs(harnessRoot) {
171
+ const dir = join(harnessRoot, BRAND_DIR);
172
+ let text = '';
173
+ try {
174
+ for (const f of readdirSync(dir)) {
175
+ if (!f.endsWith('.md')) continue;
176
+ try { text += readFileSync(join(dir, f), 'utf8').toLowerCase(); } catch { /* one unreadable article is not the answer */ }
177
+ }
178
+ } catch { /* no identity here; designIdentity reports that half */ }
179
+ return text;
180
+ }
181
+
182
+ /**
183
+ * What this project already owns to start a design from.
184
+ *
185
+ * @param {string[]} roots — skill directories to scan. Defaults to both scopes, both layouts.
186
+ * @param {string} harnessRoot — where `02-DOCS/` lives, for the identity half and the ordering.
187
+ * @returns {{state: 'owned'|'none'|'inconclusive', identity: object, records: object[],
188
+ * unreadable: Array<{path: string, reason: string}>, reason: string, fix?: string}}
189
+ * `owned` — an identity or at least one legible record exists.
190
+ * `none` — looked everywhere, found nothing. Carries the way out (P6).
191
+ * `inconclusive` — something could not be read. Never reported as `none`, never as green.
192
+ */
193
+ export function ownedStartingPoints(roots = defaultRoots(), harnessRoot = process.cwd()) {
194
+ const identity = designIdentity(harnessRoot);
195
+ const records = [];
196
+ const unreadable = [];
197
+ let blind = 0;
198
+
199
+ for (const root of roots) {
200
+ // existsSync follows symlinks, so a dangling one answers false and would be filed as "nothing
201
+ // installed here". That is unreadability, not absence — the exact conflation designIdentity was
202
+ // fixed for. lstat sees the link itself and tells them apart.
203
+ if (!existsSync(root)) {
204
+ let dangling = false;
205
+ try { dangling = lstatSync(root).isSymbolicLink(); } catch { /* genuinely absent */ }
206
+ if (dangling) {
207
+ blind += 1;
208
+ unreadable.push({ path: root, reason: 'skills root is a symlink that does not resolve' });
209
+ }
210
+ continue;
211
+ }
212
+ let entries;
213
+ try {
214
+ if (!statSync(root).isDirectory()) {
215
+ blind += 1;
216
+ unreadable.push({ path: root, reason: 'skills root exists but is not a directory' });
217
+ continue;
218
+ }
219
+ entries = readdirSync(root, { withFileTypes: true });
220
+ } catch (e) {
221
+ blind += 1;
222
+ unreadable.push({ path: root, reason: `cannot read skills root: ${e.code || e.message}` });
223
+ continue;
224
+ }
225
+ for (const entry of entries) {
226
+ // Dirents, not statSync: a symlinked entry could otherwise report on a directory outside the
227
+ // root we were asked to scan.
228
+ if (!entry.isDirectory()) continue;
229
+ const dir = join(root, entry.name);
230
+ if (!existsSync(join(dir, 'dna.json'))) continue;
231
+ const { record, error } = readRecord(dir, entry.name);
232
+ if (error) { unreadable.push({ path: join(dir, 'dna.json'), reason: error }); continue; }
233
+ if (!records.some((r) => r.slug === record.slug)) records.push(record);
234
+ }
235
+ }
236
+
237
+ const cited = citedSlugs(harnessRoot);
238
+ records.sort((a, b) => {
239
+ const ac = cited.includes(a.slug.toLowerCase()) ? 0 : 1;
240
+ const bc = cited.includes(b.slug.toLowerCase()) ? 0 : 1;
241
+ return ac - bc || a.slug.localeCompare(b.slug);
242
+ });
243
+
244
+ if (records.length || identity.state === 'present') {
245
+ return {
246
+ state: 'owned',
247
+ identity,
248
+ records,
249
+ unreadable,
250
+ reason: [
251
+ identity.state === 'present' ? `identity present (${identity.articles.length} article(s))` : 'no harness identity',
252
+ `${records.length} style record(s)`,
253
+ unreadable.length ? `${unreadable.length} unreadable` : null,
254
+ ].filter(Boolean).join(', '),
255
+ };
256
+ }
257
+
258
+ // Something was there and we could not use it. Saying "nothing to propose" would be a finding we
259
+ // did not earn, and saying nothing at all would hide it.
260
+ if (unreadable.length || blind || identity.state === 'inconclusive') {
261
+ return {
262
+ state: 'inconclusive',
263
+ identity,
264
+ records,
265
+ unreadable,
266
+ reason: unreadable.length
267
+ ? `could not read ${unreadable.length} candidate(s): ${unreadable[0].reason}`
268
+ : `identity could not be read: ${identity.reason}`,
269
+ fix: 'name what could not be read, and ask for another route to it — never propose past a blind spot',
270
+ };
271
+ }
272
+
273
+ return {
274
+ state: 'none',
275
+ identity,
276
+ records: [],
277
+ unreadable,
278
+ reason: 'no harness identity and no style record installed in any scope',
279
+ fix: 'propose at most three openable references as candidate bars (design/references/starting-point.md), then run `design-loop`',
280
+ };
281
+ }
282
+
283
+ /**
284
+ * The compact projection `doctor` reports: one object, not a section (P5 — every byte of a report is
285
+ * paid by every user who runs it). Contrast is folded in per record because a record whose own
286
+ * palette fails is not a starting point anyone should be offered.
287
+ *
288
+ * @returns {{state: string, records: string[], contrast: string, unreadable: number, reason: string}}
289
+ */
290
+ export function startingPointSummary(root = process.cwd(), roots = defaultRoots({ cwd: root })) {
291
+ const r = ownedStartingPoints(roots, root);
292
+ let failing = 0;
293
+ let unmeasured = 0;
294
+ for (const rec of r.records) {
295
+ const c = checkPairings(rec.palette);
296
+ if (c.failures.length) failing += 1;
297
+ else if (!c.ok) unmeasured += 1;
298
+ }
299
+ const contrast = r.records.length === 0
300
+ ? 'no record to measure'
301
+ : failing
302
+ ? `${failing} record(s) with a pair below its floor`
303
+ : unmeasured
304
+ ? `${unmeasured} record(s) could not be measured`
305
+ : 'all records above their floors';
306
+ return {
307
+ state: r.state,
308
+ records: r.records.map((x) => x.slug),
309
+ contrast,
310
+ unreadable: r.unreadable.length,
311
+ reason: r.reason,
312
+ ...(r.fix ? { fix: r.fix } : {}),
313
+ };
314
+ }
@@ -35,6 +35,8 @@ Follow the harness 02-DOCS convention (brand study = wiki articles under `02-DOC
35
35
 
36
36
  The completeness checklist spans visual identity (OKLCH color system, type pairing & scale, logo, imagery/illustration mood, density, radius/shadow/motion personality), reference/inspiration sites the user loves, layout preferences, dark-mode stance, accessibility & performance constraints, and brand voice/positioning (so copy and design agree). Full checklist + exact question script → `references/brand-grounding.md`.
37
37
 
38
+ **When a dimension has no answer** — the non-technical default, and the reason this gate used to be impassable: do not invent one and do not wave the STOP through. `references/starting-point.md` is how a starting point gets proposed instead: what the project already owns is checked first, the value comes from a reference you opened and measured, and it is written marked `propuesto` — which does **not** count as complete until the user confirms it.
39
+
38
40
  The order is: **brand grounding → trend research → build.**
39
41
 
40
42
  ## Pick a direction first
@@ -66,7 +68,7 @@ Then map the project type to composition, density, and motion budget. Density an
66
68
  Trends churn quarterly and your built-in aesthetic prior is the median of every AI template ever scraped. Never prescribe from stale memory; counter it with a loop:
67
69
 
68
70
  1. Define 2–3 reference archetypes from the DIRECTION BRIEF (e.g. "Linear-grade dev tool, dark, type-led").
69
- 2. WebSearch award galleries and tier-1 product sites: `awwwards.com`, `godly.website`, `land-book.com`, `mobbin.com`, Refactoring UI, and the tier-1 sites (Linear, Stripe, Vercel, Cursor, Resend).
71
+ 2. Browse the dated registry in `references/inspiration-sources.md` — whole pages to beat, SaaS and conversion surfaces, real product flows (Mobbin, Page Flows), single patterns, and the design systems that are specification rather than inspiration — plus the tier-1 sites (Linear, Stripe, Vercel, Cursor, Resend). Every entry there carries a verification date; `godly.website` now redirects to `recent.design`, which is exactly why the list is dated and not inline.
70
72
  3. WebFetch 3–5 exemplars, prompting each for type, color, layout, motion, and copy voice — concrete details, not adjectives.
71
73
  4. Extract a pattern table from what they share and where they differ.
72
74
  5. Synthesize a one-paragraph DESIGN DIRECTION with citations (which URL contributed what).
@@ -96,7 +96,7 @@ Ask **one batch at a time**. Send the batch, wait for the answer, persist what y
96
96
  extend rather than replace?
97
97
  ```
98
98
 
99
- If the user can't answer a question, that dimension stays incomplete — note the gap, keep the STOP in place for that dimension, and offer to propose a researched default (from the research-first protocol / `trends-2026.md`) they can confirm, rather than silently defaulting to your generic prior.
99
+ If the user can't answer a question, that dimension stays incomplete — note the gap, keep the STOP in place for that dimension, and offer to propose a researched default they can confirm, rather than silently defaulting to your generic prior. **How to actually propose one: `starting-point.md`.** It is the difference between offering something and improvising something: what the project already owns is checked first, the value comes from a reference you opened and measured, and it is written marked `propuesto`. A proposed-and-unconfirmed dimension does **not** count as complete against the checklist above — the STOP stays up until the user confirms it.
100
100
 
101
101
  ## Persistence format
102
102
 
@@ -39,3 +39,11 @@ Everything in `../SKILL.md` that is not styling: the brand gate, the accessibili
39
39
  the Core Web Vitals budget, the 5-second value-prop test, and the tell registry in
40
40
  `../scripts/verify.sh`. An official design system settles which components you use. It does
41
41
  not settle whether the page says anything.
42
+
43
+ ## Where to browse for component ideas
44
+
45
+ When a surface needs an idea rather than a primitive, `../../pick-ui-library/references/component-galleries.md`
46
+ names where to go — Aceternity for marketing effects, 21st.dev for breadth, React Bits for tunable
47
+ animation — and what each is good for. Take the technique and rebuild it in this project's tokens;
48
+ check the brand study for what it refuses first, because a component that contradicts a stated ban
49
+ makes the page look less designed, not more.
@@ -0,0 +1,96 @@
1
+ # Reference — where to go looking for a bar
2
+
3
+ > Last verified: **2026-08-24**. Every URL below was requested on that date and answered.
4
+ > Four sites block automated requests (403/429) but are alive in a browser — marked *(bot-blocked)*.
5
+ > A source with no date does not exist. Re-check before quoting one as current.
6
+
7
+ **Go and look.** Your built-in aesthetic prior is the median of every template ever scraped, so the
8
+ fastest way past it is to open real work and name what it does. This file is where to open.
9
+
10
+ Two rules that make the browsing worth anything:
11
+
12
+ - **Come back with a specific page, not a gallery.** `../../design-loop/SKILL.md` needs a bar you can
13
+ open — one URL, one screenshot. "Something like Awwwards" is not a bar; a named site from it is.
14
+ - **Extract principles, not pixels.** "Headline is 8× the body, one accent at 3% coverage" travels to
15
+ your project. Cloning a competitor's exact grid is theatre, and `research-method.md` says so.
16
+
17
+ ## A whole page to beat
18
+
19
+ The default bucket. Use it when the goal is a site or landing and you need something to converge on.
20
+
21
+ | Source | Go here for |
22
+ |---|---|
23
+ | [Awwwards](https://www.awwwards.com) | The award tier. Ambitious, often over-built — take the craft, leave the load time. |
24
+ | [Recent](https://recent.design) | Formerly **Godly**; `godly.website` now 301s here. Curated modern web, strong signal-to-noise. |
25
+ | [SiteInspire](https://www.siteinspire.com) *(bot-blocked)* | Long-running, filterable by style, type and subject. Calmer taste than Awwwards. |
26
+ | [Land-book](https://land-book.com) *(bot-blocked)* | Landing pages at volume, filterable by industry and style. |
27
+ | [Httpster](https://httpster.net) | Opinionated and current, less polished-agency than the rest. |
28
+ | [Minimal Gallery](https://minimal.gallery) | When the brief is restraint and you need proof it can still sell. |
29
+ | [Curated Design](https://curated.design) | Aggregated across galleries — a fast first sweep. |
30
+ | [Nicely Done](https://nicelydone.club) | Real product sites and app UI rather than portfolios. |
31
+ | [Lapa Ninja](https://lapa.ninja) *(bot-blocked)* | Large landing archive, free, categorised. |
32
+ | [One Page Love](https://onepagelove.com) | Single-page sites specifically — the constraint is the point. |
33
+
34
+ ## SaaS and conversion surfaces
35
+
36
+ | Source | Go here for |
37
+ |---|---|
38
+ | [SaaS Landing Page](https://saaslandingpage.com) | Real SaaS landings, browsable **by section** (hero, pricing, testimonials, footer) — the most useful axis when you are building one section at a time. |
39
+ | [Landingfolio](https://www.landingfolio.com) | Landings and page sections, heavy on conversion patterns. |
40
+
41
+ ## Real product, real flows — not portfolios
42
+
43
+ The most under-used bucket. Portfolio galleries show the hero; these show what happens after the click.
44
+
45
+ | Source | Go here for |
46
+ |---|---|
47
+ | [Mobbin](https://mobbin.com) | Screens and full flows from shipped apps, mobile and web. The reference for "how does a real onboarding actually go". |
48
+ | [Page Flows](https://pageflows.com) | Recorded user flows end to end — signup, checkout, cancellation. Video, so you see the motion too. |
49
+ | [Screensdesign](https://screensdesign.com) | Mobile app screens with an emphasis on what is currently shipping. |
50
+ | [Refero](https://refero.design) | UI/UX inspiration organised for browsing by pattern. |
51
+
52
+ ## One section, one pattern
53
+
54
+ Use when the piece is small and named: `design-loop` splits a goal into three or four pieces, and
55
+ these are for when a piece is "the footer" or "the empty state".
56
+
57
+ | Source | Go here for |
58
+ |---|---|
59
+ | [Footer.design](https://www.footer.design) | Footers only. Narrow on purpose, and the narrowness is why it is good. |
60
+ | [Collect UI](https://collectui.com) | Daily-UI-style collections by component and pattern. |
61
+ | [UI Garage](https://uigarage.net) | Patterns tagged by element and by industry. |
62
+ | [UI Movement](https://uimovement.com) | Motion and interaction specifically — pair with the frequency gate in `../../animate/SKILL.md` before copying anything. |
63
+ | [Dark.design](https://dark.design) · [Dark Mode Design](https://darkmodedesign.com) | Dark interfaces done properly, which is a different problem from inverting a light one. |
64
+ | [Scrnshts](https://scrnshts.club) | App Store screenshot design — a real discipline of its own. |
65
+
66
+ ## Design systems as the canonical answer
67
+
68
+ Not inspiration: **specification**. When the question is "what is the correct behaviour for this
69
+ component", these outrank any gallery.
70
+
71
+ | Source | Go here for |
72
+ |---|---|
73
+ | [Material 3](https://m3.material.io) | Google's system. Exhaustive on states, motion and tokens. |
74
+ | [Apple HIG](https://developer.apple.com/design/human-interface-guidelines) | The reference for platform behaviour and for what *not* to invent on Apple platforms. |
75
+ | [Shopify Polaris](https://polaris.shopify.com) | Dense, commerce-grade product UI with unusually good content guidance. |
76
+ | [IBM Carbon](https://carbondesignsystem.com) | Enterprise density and data-heavy patterns. |
77
+ | [GitHub Primer](https://primer.style) | Developer-tool UI, which is the closest analogue for most rsc projects. |
78
+ | [The Component Gallery](https://component.gallery) *(bot-blocked)* | The same component compared **across** design systems — the fastest way to see the range of correct answers. |
79
+ | [Adele](https://adele.uxpin.com) | Index of public design systems and their pattern libraries. |
80
+ | [Designsystems.com](https://www.designsystems.com) | Essays on building and maintaining a system, not a system itself. |
81
+
82
+ ## Niche, but the right answer when it fits
83
+
84
+ | Source | Go here for |
85
+ |---|---|
86
+ | [Interface In Game](https://interfaceingame.com) | Game UI. Useful well beyond games for diegetic and high-density HUD ideas. |
87
+ | [Mobile.design](https://mobile.design) | Mobile-first patterns collected on their own terms. |
88
+
89
+ ## Housekeeping
90
+
91
+ - **[Bestfolios](https://www.bestfolios.com) returned 502 on 2026-08-24** and is excluded until it
92
+ comes back. Do not cite it.
93
+ - **Component ideas**, as opposed to whole pages, live in
94
+ `../../pick-ui-library/references/component-galleries.md` — Aceternity, 21st.dev, React Bits.
95
+ - **Identity outranks all of this.** Check `02-DOCS/wiki/brand/` for what the project refuses before
96
+ bringing an idea back. A gallery cannot know what your brand vetoed.
@@ -145,3 +145,10 @@ When the direction changes mid-project (new competitor, pivot), re-run the loop
145
145
  - `visual-system.md` — turn the filled direction into tokens.
146
146
  - `../../nextjs/SKILL.md` — implement the chosen direction on the App Router stack.
147
147
  - *frontend-design-direction* — product-domain direction judgment (external reference, not a repo skill).
148
+
149
+ ## Where to look
150
+
151
+ `inspiration-sources.md` is the dated registry of where to browse: whole pages to beat, SaaS and
152
+ conversion surfaces, real product flows (Mobbin, Page Flows — the bucket most people skip), single
153
+ patterns like footers and empty states, and the design systems that are specification rather than
154
+ inspiration. Extract principles, not pixels, and come back with a named page rather than a gallery.
@@ -0,0 +1,75 @@
1
+ # Reference — proposing a starting point
2
+
3
+ Three places promise to propose one: `../../design-loop/SKILL.md` phase 1 when the user says *skip*,
4
+ the closing line of `brand-grounding.md` when a visual dimension has no direction, and
5
+ `../../design-dna/SKILL.md`'s REUSE mode. This file is what they propose *from*. Without it the
6
+ fallback is your own prior, which is the AI-template median — the thing the whole area exists to escape.
7
+
8
+ ## 0. What the project already owns wins
9
+
10
+ **Look before you offer.** Run it; do not reason about it:
11
+
12
+ ```bash
13
+ npx @ericrisco/rsc doctor --json # read `designStartingPoint`
14
+ ```
15
+
16
+ | State | What it means | What you do |
17
+ | --- | --- | --- |
18
+ | `owned` | An identity article or at least one legible style record exists | Propose **that**, by name, first. Offer nothing external until the user rejects it. More than one record → all of them are candidates, ordered as reported. Never pick one silently. |
19
+ | `none` | Looked in both scopes, found nothing | Go to §1. |
20
+ | `inconclusive` | Something could not be read | Say what, and ask for another route to it. **Do not propose past a blind spot** — a proposal made without looking is the defect this file exists to remove. |
21
+
22
+ If the owned identity **contradicts** what the user is now asking for, name the conflicting rule and let
23
+ them decide which wins. Same rule the loop's preflight already applies to a bar from outside.
24
+
25
+ A record that `doctor` reports with a pair below its contrast floor is not offered as a starting point.
26
+
27
+ ## 1. What are you building → which bar
28
+
29
+ At most **three** candidates. Each one gets a name, one line on why it fits *this* product, and one line
30
+ on when it would be the wrong choice. Then **wait**. No answer → take the most demanding one and say so.
31
+
32
+ Sources are in `inspiration-sources.md`; the tier-1 set and the dated-looking list are in
33
+ `trends-2026.md`. **Only propose what you can actually open** — that registry marks several sources as
34
+ bot-blocked, and a bar that cannot be fetched dies in the loop's preflight.
35
+
36
+ | Building | Candidate bars | Wrong choice when |
37
+ | --- | --- | --- |
38
+ | SaaS marketing | Stripe · [SaaS Landing Page](https://saaslandingpage.com) browsed by section · Linear | The product is a daily tool, not a purchase decision — a marketing skin on a workhorse |
39
+ | Dev tool | Linear · Vercel · Resend · [Primer](https://primer.style) | The audience is not technical: dev-tool restraint reads as unfinished to them |
40
+ | Dashboard / internal tool | [Carbon](https://carbondesignsystem.com) · [Polaris](https://polaris.shopify.com) · [Mobbin](https://mobbin.com) flows | It is a landing page with a chart on it — density is not the job |
41
+ | Portfolio / editorial | [Awwwards](https://www.awwwards.com) · [Recent](https://recent.design) · [One Page Love](https://onepagelove.com) | Anything with a conversion target: award craft routinely costs LCP and the 5s test |
42
+ | E-commerce | [Minimal Gallery](https://minimal.gallery) for restraint · [Nicely Done](https://nicelydone.club) for real product surfaces | The catalogue is huge and the real problem is search and filtering, not the hero |
43
+ | Docs | [Stripe docs](https://docs.stripe.com) · [Material 3](https://m3.material.io) on states | Reading is not the job — it is a reference table, so density beats calm |
44
+
45
+ Nothing here fits what they are building? **Say so and interview.** Do not stretch the nearest row: a bar
46
+ chosen because it was closest is the vague bar that makes the loop approve everything on round one.
47
+
48
+ ## 2. A proposal is not an answer
49
+
50
+ 1. **Open the reference.** No value is proposed from a page you did not fetch. Blocked → say so, pick
51
+ another. Never substitute a description from memory.
52
+ 2. **Measure it** — the loop's teardown already does this and writes 5 to 7 mechanisms. Those measured
53
+ values *are* the researched default this file was missing. They are not invented, and they come from
54
+ a page that already ships.
55
+ 3. **Check colour before writing it.** Any text pairing must measure **≥ 4.5:1** (≥ 3:1 for large text
56
+ and UI). Below that, it is **not proposed** — not proposed-with-a-warning.
57
+ 4. **Write it marked `propuesto`, naming the reference it came from.** Never as a confirmed value.
58
+ 5. A dimension that is proposed and not yet confirmed **does not count as complete** against
59
+ `brand-grounding.md`'s checklist. The hard STOP stays up for it. Otherwise the prefill becomes the
60
+ way to cross the gate with nobody having confirmed anything.
61
+ 6. Only what the user confirms becomes the project's identity.
62
+
63
+ ## 3. The base is the floor, not the concept
64
+
65
+ A starting point gets you a defensible floor in one exchange. It does **not** satisfy the ONE signature
66
+ element (`signature-and-craft.md` §2), and it never will: every project that picked the same bar would
67
+ converge on the same page. If the only thing making this surface distinctive is the bar it started from,
68
+ there is no concept yet.
69
+
70
+ ## See Also
71
+
72
+ - `inspiration-sources.md` — the source registry, dated, with the bot-blocked entries marked.
73
+ - `trends-2026.md` — the tier-1 set, and what currently reads as a template.
74
+ - `brand-grounding.md` — the checklist this fills in, and the STOP it does not lift.
75
+ - `signature-and-craft.md` — the part a starting point cannot give you.
@@ -25,6 +25,10 @@ Nobody wrote down **why** the winner was beautiful. Next time you ask for "the s
25
25
 
26
26
  If a `dna.json` already exists for the style named, you are in REUSE. Never re-derive a style that has a record.
27
27
 
28
+ To find out which records exist — in either scope, without guessing at paths — run
29
+ `npx @ericrisco/rsc doctor --json` and read `designStartingPoint`. A record it reports as `unreadable`
30
+ is **not** a record you can reuse: say what is missing, do not use half of one.
31
+
28
32
  ## Hard rules (these are the skill)
29
33
 
30
34
  1. **A specification that cannot fail is not a specification.** If every rule you wrote is one a bad output could still satisfy, you wrote a mood board.
@@ -34,7 +34,15 @@ website", "good SaaS design"), push once for the specific page or file: a critic
34
34
  reference invents the comparison and approves everything on round one.
35
35
 
36
36
  On *skip*, propose three candidate bars, one line each on why, and wait. No answer → take the
37
- hardest one and say so.
37
+ hardest one and say so. **Where they come from:** `../design/references/starting-point.md` — run its
38
+ step 0 first, because a bar the project already owns outranks anything you would pick from outside,
39
+ and proposing from memory is how this promise used to resolve to the AI-template median.
40
+
41
+ **Where to go looking.** For a whole page to beat, and for the flows and single patterns behind it:
42
+ `../design/references/inspiration-sources.md`. For a component idea rather than a whole reference:
43
+ `../pick-ui-library/references/component-galleries.md`. Browse for the technique, never to clone a
44
+ competitor's exact grid — and come back with **a specific page you can open**, because a gallery
45
+ is not a bar.
38
46
 
39
47
  ## Phase 2 — Preflight
40
48
 
@@ -27,6 +27,7 @@ A lookup skill. When invoked with a task ("I need toasts", "what should I use fo
27
27
  2. **Check what's already installed.** Look at `package.json` first. If the project already uses a listed library, use it. If it uses a competitor (e.g. react-window instead of Virtuoso), flag the recommendation but don't churn the dependency without being asked.
28
28
  3. **Recommend one library**, state what it's for in one sentence, and install/wire it up if that's part of the request. Don't present a menu of options when the list has a clear answer.
29
29
  4. If the task isn't covered by the list, say so explicitly and recommend from your own knowledge — but be clear you've left the curated list.
30
+ 5. **Need an idea rather than a library?** Go and look: `references/component-galleries.md` names where to browse for component ideas (Aceternity for marketing effects, 21st.dev for breadth, React Bits for tunable animation) and what each is good for. Take the technique, build it in the project's own tokens.
30
31
 
31
32
  ## The list
32
33
 
@@ -0,0 +1,59 @@
1
+ # Reference — where to go looking for component ideas
2
+
3
+ > Last verified: **2026-08-24** — all three returned HTTP 200 on that date.
4
+
5
+ **Go and look.** When you need a component idea and your own prior is the median of every AI template
6
+ ever scraped, these are the places to browse instead of inventing from memory. Open them, see what
7
+ exists, come back with something specific.
8
+
9
+ ## Where to go, and for what
10
+
11
+ ### [Aceternity UI](https://ui.aceternity.com/components) — effects for a surface that must stop the scroll
12
+
13
+ Go here for **motion and atmosphere on a marketing surface**, not for primitives. What's there:
14
+
15
+ - **Backgrounds:** Aurora, Beams, Vortex, Sparkles, gradient fields
16
+ - **Cards:** 3D tilt, hover reveal, card stack, spotlight
17
+ - **Text:** typewriter, flip, encrypted/scramble, generate-in, canvas text
18
+ - **Scroll:** sticky scroll, container scroll, hero parallax
19
+ - **Navigation:** floating navbar, floating dock, sidebar, notch, tabs
20
+ - **Buttons:** magnetic, moving border, hover gradient
21
+ - **Data as spectacle:** globe, timeline, world map, before/after compare
22
+ - **Whole sections:** hero, pricing, testimonials, CTA, feature blocks
23
+
24
+ Best used when you can name the feeling you want and need to see how someone already built it.
25
+
26
+ ### [21st.dev](https://21st.dev/community/components) — breadth, ranked by what people actually use
27
+
28
+ A community registry rather than a curated library, so it's the widest net of the three. Marketing
29
+ blocks *and* real UI — buttons, cards, forms, modals, inputs, tables — plus Shaders and ASCII art.
30
+ Sortable by popular and newest, with view counts.
31
+
32
+ Go here when you don't know what you're looking for yet and want to see the range. Read the code of
33
+ anything you like: community means unvetted, and popularity is not review.
34
+
35
+ ### [React Bits](https://reactbits.dev/get-started/index) — animated interactions, deeply customizable
36
+
37
+ ~46k stars. Animations, 3D, CSS effects, built to be tuned rather than dropped in.
38
+
39
+ Go here when Aceternity's version of an effect is close but you need to control it.
40
+
41
+ ## Two things to carry with you
42
+
43
+ **Ideas travel; code stays there.** Take the technique, the ratio, the interaction — then build it in
44
+ the project's own tokens. Never vendor a gallery's source into this catalog: rsc ships publicly (P9),
45
+ and React Bits' licence (MIT + Commons Clause) forbids redistributing its components outright. In the
46
+ user's own project, installing any of the three is fine.
47
+
48
+ **Identity outranks the gallery.** Check `02-DOCS/wiki/brand/` for what this project refuses before
49
+ pulling an idea. A lot of what these galleries do best — aurora and gradient-mesh backgrounds,
50
+ glassmorphism, neon, blobs, decorative 3D — is also on the AI-tell list in
51
+ `../../design/references/ai-tells.md`, and some brand studies veto it by name. And run the frequency gate
52
+ from `../../animate/SKILL.md`: a spotlight card earns its place on a hero someone sees once, and never on
53
+ a table they open forty times a day.
54
+
55
+ ## Not a substitute for the primitives
56
+
57
+ A dialog still comes from `base-ui`, a toast from Sonner, a command palette from cmdk. Focus trapping,
58
+ dismissal and accessibility are solved; an animated card does not replace a solved primitive. Use
59
+ `SKILL.md`'s list for the task, and this file for the look.