@ericrisco/rsc 3.1.3 → 3.3.0

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.
Files changed (105) hide show
  1. package/README.md +8 -6
  2. package/manifest.json +43 -69
  3. package/package.json +6 -1
  4. package/scripts/design-upstream.js +92 -0
  5. package/scripts/doctor.js +0 -24
  6. package/scripts/lib/default-skill-floor.js +5 -1
  7. package/scripts/lib/domains.js +2 -2
  8. package/scripts/lib/recommend.js +1 -3
  9. package/scripts/lib/retired-skills.js +7 -1
  10. package/skills/ai-media/SKILL.md +2 -2
  11. package/skills/design/IMPECCABLE-LICENSE.txt +191 -0
  12. package/skills/design/SKILL.md +79 -328
  13. package/skills/design/THIRD_PARTY.md +26 -0
  14. package/skills/design/engine/LICENSE +21 -0
  15. package/skills/design/engine/RUN.md +515 -0
  16. package/skills/design/engine/UPSTREAM.json +4 -0
  17. package/skills/design/engine/assets/app.css +216 -0
  18. package/skills/design/engine/assets/base.css +105 -0
  19. package/skills/design/engine/assets/base.js +37 -0
  20. package/skills/design/engine/assets/charts.js +617 -0
  21. package/skills/design/engine/assets/scenes/stage.css +28 -0
  22. package/skills/design/engine/assets/scenes/stage.js +133 -0
  23. package/skills/design/engine/library/app.json +1327 -0
  24. package/skills/design/engine/library/directions.json +1078 -0
  25. package/skills/design/engine/library/heroes.json +1086 -0
  26. package/skills/design/engine/library/motions.json +92 -0
  27. package/skills/design/engine/library/palettes.json +1485 -0
  28. package/skills/design/engine/library/photos.json +7240 -0
  29. package/skills/design/engine/library/sections.json +4429 -0
  30. package/skills/design/engine/library/sites.json +8350 -0
  31. package/skills/design/engine/library/type.json +2024 -0
  32. package/skills/design/engine/references/app-screens.md +1199 -0
  33. package/skills/design/engine/references/colour.md +141 -0
  34. package/skills/design/engine/references/copy.md +177 -0
  35. package/skills/design/engine/references/critique.md +182 -0
  36. package/skills/design/engine/references/imagery.md +791 -0
  37. package/skills/design/engine/references/layout.md +292 -0
  38. package/skills/design/engine/references/motion.md +980 -0
  39. package/skills/design/engine/references/research.md +356 -0
  40. package/skills/design/engine/references/scenes.md +174 -0
  41. package/skills/design/engine/references/tells.md +115 -0
  42. package/skills/design/engine/references/type.md +170 -0
  43. package/skills/design/engine/scripts/assets.mjs +865 -0
  44. package/skills/design/engine/scripts/draw.mjs +584 -0
  45. package/skills/design/engine/scripts/inspect.mjs +303 -0
  46. package/skills/design/engine/scripts/lib/app-draw.mjs +203 -0
  47. package/skills/design/engine/scripts/lib/cdp.mjs +248 -0
  48. package/skills/design/engine/scripts/lib/probe.mjs +909 -0
  49. package/skills/design/engine/scripts/lib/rules.mjs +243 -0
  50. package/skills/design/engine/scripts/lint.mjs +119 -0
  51. package/skills/design/engine/scripts/scenes.mjs +124 -0
  52. package/skills/design/evals/cases.yaml +35 -46
  53. package/skills/design/references/design-md.md +49 -0
  54. package/skills/design/references/finish-review.md +46 -0
  55. package/skills/design/references/modes.md +51 -0
  56. package/skills/expo/SKILL.md +3 -4
  57. package/skills/marketing/references/landing-copy.md +2 -2
  58. package/skills/react/SKILL.md +2 -2
  59. package/skills/react-native/SKILL.md +3 -3
  60. package/skills/scroll-film/SKILL.md +220 -0
  61. package/skills/scroll-film/assets/page-template.html +128 -0
  62. package/skills/scroll-film/assets/scrub-engine.js +352 -0
  63. package/skills/scroll-film/evals/README.md +26 -0
  64. package/skills/scroll-film/evals/cases.yaml +81 -0
  65. package/skills/scroll-film/references/chain.md +218 -0
  66. package/skills/scroll-film/references/engine.md +132 -0
  67. package/skills/scroll-film/scripts/verify.sh +158 -0
  68. package/skills/swift-ios/SKILL.md +1 -1
  69. package/skills/ui-engineering/SKILL.md +3 -4
  70. package/skills/ui-engineering/evals/cases.yaml +2 -2
  71. package/skills/variant-explorer/SKILL.md +4 -4
  72. package/skills/variant-explorer/evals/cases.yaml +1 -1
  73. package/targets/clone-bootstrap.mjs +1 -1
  74. package/scripts/lib/design-identity.js +0 -117
  75. package/scripts/lib/starting-point.js +0 -314
  76. package/skills/design/evals/README.md +0 -53
  77. package/skills/design/references/ai-tells.md +0 -75
  78. package/skills/design/references/brand-grounding.md +0 -197
  79. package/skills/design/references/copywriting-frameworks.md +0 -138
  80. package/skills/design/references/design-systems.md +0 -49
  81. package/skills/design/references/inspiration-sources.md +0 -96
  82. package/skills/design/references/landing-anatomy-and-cro.md +0 -202
  83. package/skills/design/references/motion-and-interaction.md +0 -228
  84. package/skills/design/references/research-method.md +0 -154
  85. package/skills/design/references/signature-and-craft.md +0 -148
  86. package/skills/design/references/starting-point.md +0 -75
  87. package/skills/design/references/trends-2026.md +0 -80
  88. package/skills/design/references/visual-system.md +0 -237
  89. package/skills/design/scripts/verify.sh +0 -357
  90. package/skills/design-dna/PROMPT.md +0 -211
  91. package/skills/design-dna/SKILL.md +0 -162
  92. package/skills/design-dna/design-dna.schema.json +0 -230
  93. package/skills/design-dna/evals/README.md +0 -64
  94. package/skills/design-dna/evals/cases.yaml +0 -68
  95. package/skills/design-dna/references/method.md +0 -436
  96. package/skills/design-dna/scripts/emit.py +0 -164
  97. package/skills/design-dna/templates/PROMPT.md.tmpl +0 -42
  98. package/skills/design-dna/templates/SKILL.md.tmpl +0 -42
  99. package/skills/design-dna/templates/check.py.tmpl +0 -143
  100. package/skills/design-loop/SKILL.md +0 -160
  101. package/skills/design-loop/evals/README.md +0 -56
  102. package/skills/design-loop/evals/cases.yaml +0 -60
  103. package/skills/motion-craft/SKILL.md +0 -125
  104. package/skills/motion-craft/evals/cases.yaml +0 -38
  105. package/skills/motion-craft/scripts/verify.sh +0 -75
@@ -0,0 +1,28 @@
1
+ /* tasteskill / scenes / stage.css — the held stage under every scroll scene (references/scenes.md).
2
+ Without .is-live (no script, a failed CDN, reduced motion, a phone set to 'rest') a scene is an ordinary
3
+ section showing its finished composition. With it, the section is (hold + 1) screens tall and the
4
+ stage sticks to the viewport for the hold. */
5
+
6
+ .scene { position: relative; }
7
+ .scene > .scene-stage { position: relative; }
8
+
9
+ html.js .scene.is-live { height: calc((var(--hold, 2) + 1) * 100svh); }
10
+ html.js .scene.is-live > .scene-stage {
11
+ position: sticky;
12
+ top: 0;
13
+ height: 100svh;
14
+ overflow: clip; /* clip, not hidden: hidden would make the stage a scroll container */
15
+ contain: layout paint;
16
+ }
17
+
18
+ /* Everything a scene moves is composited; nothing it moves is laid out again. */
19
+ .scene.is-live [data-move] { will-change: transform, opacity; }
20
+
21
+ /* Lenis */
22
+ html.is-smooth { scroll-behavior: auto; }
23
+ html.is-smooth body { overscroll-behavior: none; }
24
+
25
+ @media (prefers-reduced-motion: reduce) {
26
+ html.js .scene.is-live { height: auto; }
27
+ html.js .scene.is-live > .scene-stage { position: relative; height: auto; }
28
+ }
@@ -0,0 +1,133 @@
1
+ // tasteskill / scenes / stage.js — the shared stage every scroll scene runs on (references/scenes.md).
2
+ //
3
+ // A scene is a tall section whose stage sticks to the viewport while the page scrolls past it; a GSAP
4
+ // timeline is scrubbed by that scroll. The page always scrolls natively (no wheel capture), the
5
+ // scrollbar always tells the truth, and the finished composition is what reduced motion, no script and
6
+ // a failed CDN all show.
7
+ //
8
+ // Load at the end of <body>, in this order (versions pinned):
9
+ // <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
10
+ // <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/ScrollTrigger.min.js"></script>
11
+ // <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/SplitText.min.js"></script> (only if a scene splits text)
12
+ // <script src="https://cdn.jsdelivr.net/npm/lenis@1.3.26/dist/lenis.min.js"></script> (optional smoothing)
13
+ // <script src="assets/scenes/stage.js"></script>
14
+ // <script src="assets/scenes/<scene>.js"></script>
15
+ // and keep the one-line <script>document.documentElement.classList.add('js')</script> in <head>.
16
+ //
17
+ // Stage.smooth() weighted wheel scrolling (Lenis), off for touch and reduced motion
18
+ // Stage.scene(root, { hold, scrub, build, phone, rest })
19
+ // Stage.split(el, 'lines' | 'words' | 'chars') masked split text, SplitText when loaded
20
+ // Stage.ready() resolves when fonts and the scenes' pictures are ready
21
+ // Stage.init() starts every <section class="scene" data-scene="<id>"> on the page
22
+ //
23
+ // A page: Stage.smooth(); Stage.init();
24
+ (() => {
25
+ const html = document.documentElement;
26
+ const mq = (q) => window.matchMedia(q).matches;
27
+ const live = !!(window.gsap && window.ScrollTrigger);
28
+ if (live) gsap.registerPlugin(ScrollTrigger);
29
+ if (live && window.SplitText) gsap.registerPlugin(SplitText);
30
+
31
+ const Stage = {
32
+ live,
33
+ get reduced() { return mq('(prefers-reduced-motion: reduce)'); },
34
+ get phone() { return mq('(max-width: 767px)'); },
35
+ get touch() { return mq('(pointer: coarse)'); },
36
+ lenis: null,
37
+ scenes: [],
38
+ };
39
+
40
+ // Weighted wheel scrolling. lerp 0.1-0.14 feels heavy without lagging behind the hand; touch keeps the
41
+ // phone's own scrolling, and reduced motion keeps the browser's.
42
+ Stage.smooth = ({ lerp = 0.12 } = {}) => {
43
+ if (!live || !window.Lenis || Stage.reduced || Stage.touch || Stage.lenis) return Stage.lenis;
44
+ const lenis = new Lenis({ lerp, smoothWheel: true, anchors: true, autoRaf: false });
45
+ lenis.on('scroll', ScrollTrigger.update);
46
+ gsap.ticker.add((t) => lenis.raf(t * 1000));
47
+ gsap.ticker.lagSmoothing(0);
48
+ html.classList.add('is-smooth');
49
+ Stage.lenis = lenis;
50
+ return lenis;
51
+ };
52
+
53
+ // One scroll scene.
54
+ // root <section class="scene"> holding one <div class="scene-stage">
55
+ // hold screens of scroll the stage is held for (1-4); the section is (hold + 1) screens tall
56
+ // scrub true = locked to the scrollbar; a number = seconds of catch-up (0.3-0.8 reads as weight)
57
+ // build(tl, ctx) add the tweens to tl; the timeline runs 0 → 1 over the hold. ctx: { root, stage, phone, q }
58
+ // phone 'same' (default) | 'rest' (the phone gets the finished composition) | { hold, build }
59
+ // rest(ctx) optional: anything extra the finished composition needs (most scenes need nothing)
60
+ // Sets --p (0-1) and data-beat on root, so CSS and captions can follow the scene too.
61
+ Stage.scene = (root, opts = {}) => {
62
+ if (!root) return null;
63
+ const { hold = 2, scrub = true, build, phone = 'same', rest, beats = 0 } = opts;
64
+ const stage = root.querySelector(':scope > .scene-stage') || root.firstElementChild;
65
+ const q = (sel) => [...root.querySelectorAll(sel)];
66
+ const toRest = (ctx) => { root.classList.remove('is-live'); root.classList.add('is-rest'); root.style.removeProperty('--hold'); if (rest) rest(ctx); };
67
+ if (!live) { toRest({ root, stage, phone: Stage.phone, q }); return null; }
68
+ const mm = gsap.matchMedia();
69
+ mm.add({ motion: '(prefers-reduced-motion: no-preference)', phone: '(max-width: 767px)' }, (c) => {
70
+ const isPhone = c.conditions.phone;
71
+ const ctx = { root, stage, phone: isPhone, q };
72
+ const plan = isPhone ? (phone === 'rest' ? null : phone === 'same' ? { hold, build } : { hold: phone.hold ?? hold, build: phone.build ?? build }) : { hold, build };
73
+ if (!c.conditions.motion || !plan || !plan.build) { toRest(ctx); return () => root.classList.remove('is-rest'); }
74
+ root.classList.remove('is-rest');
75
+ root.classList.add('is-live');
76
+ root.style.setProperty('--hold', plan.hold);
77
+ const tl = gsap.timeline({
78
+ defaults: { ease: 'none' },
79
+ scrollTrigger: {
80
+ trigger: root, start: 'top top', end: 'bottom bottom', scrub, invalidateOnRefresh: true,
81
+ onUpdate: (self) => {
82
+ root.style.setProperty('--p', self.progress.toFixed(4));
83
+ if (beats) root.dataset.beat = String(Math.min(beats - 1, Math.floor(self.progress * beats)));
84
+ },
85
+ },
86
+ });
87
+ plan.build(tl, ctx);
88
+ // pad the timeline to exactly 1 so positions in build() read as fractions of the hold
89
+ if (tl.duration() < 1) tl.to({}, { duration: 1 - tl.duration() }, tl.duration());
90
+ return () => { root.classList.remove('is-live'); root.style.removeProperty('--hold'); root.style.removeProperty('--p'); };
91
+ });
92
+ Stage.scenes.push(mm);
93
+ return mm;
94
+ };
95
+
96
+ // Masked split text. Lines rise out of their own mask; words and characters are for short headings.
97
+ Stage.split = (el, type = 'lines') => {
98
+ if (!el) return [];
99
+ if (window.SplitText && live) {
100
+ const s = SplitText.create(el, { type: type === 'lines' ? 'lines' : type === 'words' ? 'lines,words' : 'lines,words,chars', mask: 'lines', aria: 'auto', autoSplit: false });
101
+ return type === 'lines' ? s.lines : type === 'words' ? s.words : s.chars;
102
+ }
103
+ // fallback: words in inline-block spans, the full text kept for screen readers
104
+ if (!el.dataset.split) {
105
+ el.setAttribute('aria-label', el.textContent.trim());
106
+ el.innerHTML = el.textContent.trim().split(/\s+/).map((w) => `<span aria-hidden="true" style="display:inline-block">${w}</span>`).join(' ');
107
+ el.dataset.split = 'words';
108
+ }
109
+ return [...el.children];
110
+ };
111
+
112
+ // Fonts and the scenes' pictures decide every height a trigger measures; refresh once they are in.
113
+ Stage.ready = () => {
114
+ const imgs = [...document.querySelectorAll('.scene img')].map((i) => (i.complete ? null : i.decode().catch(() => {}))).filter(Boolean);
115
+ return Promise.all([document.fonts ? document.fonts.ready : null, ...imgs]).then(() => { if (live) ScrollTrigger.refresh(); });
116
+ };
117
+
118
+ // Every <section class="scene" data-scene="<id>" data-options='{"hold":3}'> on the page, started by
119
+ // the scene file of that id (assets/scenes/<id>.js registers Scenes['<id>']).
120
+ Stage.init = () => {
121
+ for (const el of document.querySelectorAll('.scene[data-scene]')) {
122
+ if (el.__scene) continue;
123
+ const make = window.Scenes[el.dataset.scene];
124
+ if (!make) { console.warn('No scene file loaded for data-scene="' + el.dataset.scene + '"'); continue; }
125
+ let o = {}; try { o = JSON.parse(el.dataset.options || '{}'); } catch {}
126
+ el.__scene = make(el, o) || true;
127
+ }
128
+ };
129
+
130
+ window.Scenes = window.Scenes || {};
131
+ window.Stage = Stage;
132
+ if (live) { window.addEventListener('load', () => Stage.ready()); }
133
+ })();