Skip to content

Commit bf5c3cf

Browse files
authored
Merge pull request #402 from Abraham040203/feat/motion-domain-design-dials
Add motion domain and design dials (variance/motion/density)
2 parents 247bf48 + 6a61676 commit bf5c3cf

43 files changed

Lines changed: 1226 additions & 306 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/skills/ui-ux-pro-max/SKILL.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -413,6 +413,29 @@ If not, use the Master rules exclusively.
413413
Now, generate the code...
414414
```
415415

416+
### Step 2c: Design Dials (optional)
417+
418+
Three optional 1-10 sliders that tune `--design-system` output without changing your query. Add any combination of them to the same command:
419+
420+
```bash
421+
python3 skills/ui-ux-pro-max/scripts/search.py "<query>" --design-system --variance <1-10> --motion <1-10> --density <1-10>
422+
```
423+
424+
| Dial | Low (1-3) | Mid (4-7) | High (8-10) |
425+
|------|-----------|-----------|-------------|
426+
| `--variance` | Centered / minimal (biases toward Minimalism-style categories) | Balanced / modern | Bold / asymmetric (biases toward Brutalism, Bento Grids) |
427+
| `--motion` | Subtle micro-interactions | Standard scroll/stagger motion | Complex choreography (pin, Flip, SplitText) |
428+
| `--density` | Spacious (24-96px spacing scale) | Standard (16-64px, current default) | Dense/dashboard (8-32px spacing scale) |
429+
430+
- `--motion` attaches a ready-to-use GSAP snippet (with framework notes, Do/Don't, and performance notes) pulled from `--domain gsap`, matched to the resolved tier (Subtle/Standard/Complex).
431+
- `--density` overrides the `--space-*` CSS variable table in the ASCII/markdown/MASTER.md output — use it for dashboards (high) vs. marketing pages (low) without hand-editing tokens.
432+
- Leaving a dial unset keeps that part of the output exactly as it was before (no behavior change).
433+
434+
**Example:**
435+
```bash
436+
python3 skills/ui-ux-pro-max/scripts/search.py "internal analytics dashboard" --design-system --variance 8 --motion 7 --density 8 -p "Ops Console"
437+
```
438+
416439
### Step 3: Supplement with Detailed Searches (as needed)
417440

418441
After getting the design system, use domain searches to get additional details:
@@ -463,6 +486,7 @@ python3 skills/ui-ux-pro-max/scripts/search.py "<keyword>" --stack <your-stack>
463486
| `landing` | Page structure, CTA strategies | hero, hero-centric, testimonial, pricing, social-proof |
464487
| `chart` | Chart types, library recommendations | trend, comparison, timeline, funnel, pie |
465488
| `ux` | Best practices, anti-patterns | animation, accessibility, z-index, loading |
489+
| `gsap` | GSAP animation skeletons by intensity tier | scroll reveal, stagger, magnetic cursor, page transition |
466490
| `google-fonts` | Individual Google Fonts lookup | sans serif, monospace, japanese, variable font, popular |
467491
| `react` | React/Next.js performance | waterfall, bundle, suspense, memo, rerender, cache |
468492
| `web` | App interface guidelines (iOS/Android/React Native) | accessibilityLabel, touch targets, safe areas, Dynamic Type |
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
No,Category,Intensity Tier,Keywords,Trigger,Duration,Easing,GSAP Snippet,Framework Notes,Do,Don't,Performance Notes
2+
1,Hover Micro-interaction,Subtle,"hover, button, opacity, lift, press feedback",hover,150-200ms,power1.out,"gsap.to(el, { y: -1, opacity: 0.9, duration: 0.15, ease: 'power1.out' });",Bind on mouseenter/mouseleave; in React wrap in a ref + useEffect (or onMouseEnter/onMouseLeave props directly calling gsap.to),Keep displacement under 2px so it reads as feedback not motion,Don't animate layout-affecting props (width/height/margin) on hover,Runs on transform/opacity only so it stays on the compositor thread
3+
2,Hover Micro-interaction,Standard,"hover, card, scale, tilt, cursor feedback",hover,200-300ms,power2.out,"gsap.to(el, { y: -4, scale: 1.02, boxShadow: '0 12px 24px rgba(0,0,0,0.12)', duration: 0.25, ease: 'power2.out' });","Use gsap.quickTo(el, 'y') for cards with many hover targets to avoid re-creating tweens every event",Pair with a matching mouseleave tween that reverses the same properties,Don't leave the hover state stuck if the pointer leaves fast; always attach the reverse tween,quickTo() avoids GC churn on lists with 20+ hoverable cards
4+
3,Hover Micro-interaction,Complex,"hover, magnetic, cursor follow, 3d tilt",hover + mousemove,300-500ms,"elastic.out(1,0.4)","const xTo = gsap.quickTo(el, 'x', { duration: 0.4, ease: 'elastic.out(1,0.4)' }); const yTo = gsap.quickTo(el, 'y', { duration: 0.4, ease: 'elastic.out(1,0.4)' }); el.addEventListener('mousemove', (e) => { const r = el.getBoundingClientRect(); xTo((e.clientX - r.left - r.width/2) * 0.3); yTo((e.clientY - r.top - r.height/2) * 0.3); });",Debounce is not needed since quickTo interpolates; remove listeners on component unmount in React/Vue to avoid leaks,Clamp the pull strength (e.g. * 0.3) so the element never fully leaves its hit box,Don't apply magnetic effect to more than 1-2 focal elements per screen; it becomes noisy,Use will-change: transform on the target element for smoother compositing
5+
4,Scroll Reveal,Subtle,"scroll, fade in, reveal, on view",scroll (viewport enter),300-400ms,power1.out,"gsap.from(el, { opacity: 0, y: 12, duration: 0.35, ease: 'power1.out', scrollTrigger: { trigger: el, start: 'top 90%', toggleActions: 'play none none reverse' } });",Requires the ScrollTrigger plugin registered once via gsap.registerPlugin(ScrollTrigger),"Keep the y offset small (8-16px) so it reads as a fade, not a slide",Don't reveal below-the-fold content needed for SEO/crawlers as invisible-by-default without a no-JS fallback,toggleActions 'play none none reverse' avoids re-triggering on every scroll direction change
6+
5,Scroll Reveal,Standard,"scroll, slide up, staggered section, reveal",scroll (viewport enter),400-600ms,power2.out,"gsap.from(el.children, { opacity: 0, y: 24, duration: 0.5, stagger: 0.08, ease: 'power2.out', scrollTrigger: { trigger: el, start: 'top 85%' } });","In React use useGSAP(() => {...}, { scope: containerRef }) from @gsap/react to auto-cleanup on unmount",Scope the ScrollTrigger to the section container so it doesn't re-scan the whole page,Don't stagger more than ~8 children; beyond that the last items feel laggy,Set scroller/markers: false in production; markers is dev-only
7+
6,Scroll Reveal,Complex,"scroll, pin, scrub, storytelling, scrollytelling",scroll (continuous scrub),tied to scroll position,none (scrub-driven),"gsap.timeline({ scrollTrigger: { trigger: section, start: 'top top', end: '+=150%', scrub: 1, pin: true } }).from('.headline', { opacity: 0, y: 40 }).to('.bg-layer', { yPercent: -20 }, '<');",Pinning needs the section to have deterministic height; recalc ScrollTrigger.refresh() after images/fonts load,Use scrub: true or a small number (0.5-1.5) instead of instant jumps so it feels tied to the scrollbar,Don't pin more than 1-2 sections per page; excessive pinning fights native scroll feel and hurts mobile UX,"Pinning forces layout reflow; test on mid-tier mobile devices, not just desktop"
8+
7,Stagger List,Subtle,"list, stagger, cards, grid entrance",load or scroll,250-350ms,power1.out,"gsap.from('.list-item', { opacity: 0, y: 8, duration: 0.3, stagger: 0.03 });",Select items with a stable class/data-attribute (not array index) so re-renders in React don't break targeting,Keep per-item stagger delay small (0.02-0.04s) for lists longer than 10 items,Don't stagger by more than 0.1s per item on long lists; total reveal time becomes sluggish,"For virtualized lists, only animate items currently mounted in the DOM"
9+
8,Stagger List,Standard,"grid, bento, cards, staggered scale",load or scroll,300-450ms,back.out(1.4),"gsap.from('.grid-item', { opacity: 0, scale: 0.92, y: 16, duration: 0.4, stagger: { each: 0.06, from: 'start', grid: 'auto' }, ease: 'back.out(1.4)' });",grid: 'auto' lets GSAP infer rows/columns from a CSS grid layout for a natural wave stagger,Combine with from: 'center' for a bento-grid layout to draw the eye inward first,Don't use back.out on dense data tables; the overshoot reads as sloppy on informational UI,Group DOM writes; avoid interleaving layout reads (getBoundingClientRect) between staggered tweens
10+
9,Stagger List,Complex,"stagger, wave, text reveal, split text",load or scroll,400-700ms,expo.out,"const split = new SplitText(headline, { type: 'chars' }); gsap.from(split.chars, { opacity: 0, y: 20, rotateX: -40, duration: 0.6, stagger: 0.015, ease: 'expo.out' });",SplitText is a GSAP Club/paid plugin; confirm license before shipping and provide a plain fade fallback if unavailable,Revert SplitText on unmount/cleanup (split.revert()) to restore original text nodes for accessibility tools,Don't split-animate long paragraphs; reserve for short headlines (under ~8 words),Splitting text creates one element per character; keep it to headline-length copy only for DOM size
11+
10,Page Transition,Subtle,"route change, fade, page transition",route change,200-300ms,power1.inOut,"gsap.to(main, { opacity: 0, duration: 0.2, onComplete: () => { navigate(); gsap.fromTo(main, { opacity: 0 }, { opacity: 1, duration: 0.2 }); } });","Pair with the router's transition hooks (Next.js App Router transitions, React Router's useNavigate, Vue Router's beforeEach/afterEach)",Preload the destination route's critical assets before the exit tween finishes,Don't block navigation on animation; cap exit duration at ~250ms so the app never feels unresponsive,Exit animation should always resolve faster than entrance (asymmetric timing) so back/forward feels snappy
12+
11,Page Transition,Standard,"route change, slide, overlay wipe",route change,400-600ms,power2.inOut,"const tl = gsap.timeline(); tl.to('.transition-overlay', { yPercent: 0, duration: 0.4, ease: 'power2.inOut' }).call(navigate).to('.transition-overlay', { yPercent: -100, duration: 0.4, ease: 'power2.inOut', delay: 0.1 });",Keep the overlay element mounted at the layout root (outside the page component) so it survives the route swap,Show a lightweight loading indicator if the destination route's data fetch outlasts the overlay,Don't tie the overlay's reveal directly to data-fetch completion without a max-wait timeout; a slow API stalls the whole transition,Prefer CSS transform (yPercent) over top/left to keep the overlay animation on the compositor thread
13+
12,Page Transition,Complex,"shared element, morph, hero transition",route change,500-800ms,expo.inOut,"const state = Flip.getState('.hero-image'); navigate(); Flip.from(state, { duration: 0.6, ease: 'expo.inOut', absolute: true, zIndex: 100 });",Requires the GSAP Flip plugin; the 'from' and 'to' route must render the same element with a shared data-flip-id,Verify the shared element exists in both DOM states before calling Flip.from to avoid a silent no-op,Don't use shared-element transitions across more than one element pair per navigation; compounding Flips are hard to time correctly,Flip recalculates layout (FLIP technique) so test on low-end devices for jank
14+
13,Parallax Scroll,Subtle,"parallax, background, depth, scroll speed",scroll (continuous),tied to scroll position,linear (scrub),"gsap.to('.bg-layer', { yPercent: 10, ease: 'none', scrollTrigger: { trigger: section, scrub: true } });","Apply parallax to background/decorative layers only, never to text or interactive controls",Keep the yPercent delta small (5-15) so foreground and background never desync distractingly,Don't parallax body copy; it hurts reading comfort and can trigger motion sickness,will-change: transform on the parallax layer only; remove it after scroll settles to free GPU memory
15+
14,Parallax Scroll,Standard,"multi-layer parallax, depth, hero background",scroll (continuous),tied to scroll position,linear (scrub),"gsap.utils.toArray('.parallax-layer').forEach((layer, i) => { gsap.to(layer, { yPercent: (i + 1) * -8, ease: 'none', scrollTrigger: { trigger: layer.parentElement, scrub: 0.5 } }); });",Layer count beyond 3-4 has diminishing visual return and multiplies scroll-listener cost,"Vary speed per layer (background slowest, foreground fastest) to sell the depth illusion",Don't let parallax layers overflow their container; clip with overflow: hidden on the wrapper,Batch all layers under one ScrollTrigger container where possible instead of one per layer
16+
15,Loading / Skeleton,Subtle,"loading, skeleton, shimmer, pulse",on mount / async wait,1200-1600ms loop,sine.inOut,"gsap.to('.skeleton', { backgroundPosition: '200% 0', duration: 1.4, ease: 'sine.inOut', repeat: -1 });",Kill the loop tween (tween.kill()) as soon as real content mounts to avoid orphaned repeating animations,Use a CSS gradient background-position sweep rather than opacity pulsing; reads as 'loading' more clearly,Don't run more than one shimmer loop per skeleton group; sync them under one timeline so the wave reads as a single unit,repeat: -1 tweens are cheap but must be explicitly killed on unmount or they leak in SPA route changes
17+
16,Loading / Skeleton,Standard,"progress, spinner, morphing loader",on mount / async wait,800-1200ms loop,power1.inOut,"gsap.timeline({ repeat: -1 }).to('.loader-dot', { y: -8, duration: 0.4, stagger: { each: 0.15, yoyo: true, repeat: 1 } });",Wrap the whole loop timeline in useGSAP with { revertOnUpdate: false } in React so it isn't rebuilt every render,Cap total loop duration under ~1.5s so long waits don't feel like the UI froze on a single beat,Don't use elaborate loaders for sub-300ms waits; they flash and feel worse than no indicator,Pause the timeline (tl.pause()) when the loading tab/view is not visible to save CPU on background tabs

.claude/skills/ui-ux-pro-max/scripts/core.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@
5555
"search_cols": ["Category", "Icon Name", "Keywords", "Best For"],
5656
"output_cols": ["Category", "Icon Name", "Keywords", "Library", "Import Code", "Usage", "Best For", "Style"]
5757
},
58+
"gsap": {
59+
"file": "motion.csv",
60+
"search_cols": ["Category", "Intensity Tier", "Keywords", "Trigger"],
61+
"output_cols": ["Category", "Intensity Tier", "Trigger", "Duration", "Easing", "GSAP Snippet", "Framework Notes", "Do", "Don't", "Performance Notes"]
62+
},
5863
"react": {
5964
"file": "react-performance.csv",
6065
"search_cols": ["Category", "Issue", "Keywords", "Description"],
@@ -89,6 +94,12 @@
8994
"threejs": {"file": "stacks/threejs.csv"},
9095
"angular": {"file": "stacks/angular.csv"},
9196
"laravel": {"file": "stacks/laravel.csv"},
97+
"javafx": {"file": "stacks/javafx.csv"},
98+
"wpf": {"file": "stacks/wpf.csv"},
99+
"winui": {"file": "stacks/winui.csv"},
100+
"avalonia": {"file": "stacks/avalonia.csv"},
101+
"uno": {"file": "stacks/uno.csv"},
102+
"uwp": {"file": "stacks/uwp.csv"},
92103
}
93104

94105
# Common columns for all stacks
@@ -117,7 +128,7 @@ def __init__(self, k1=1.5, b=0.75):
117128
def tokenize(self, text):
118129
"""Lowercase, split, remove punctuation, filter short words"""
119130
text = re.sub(r'[^\w\s]', ' ', str(text).lower())
120-
return [w for w in text.split() if len(w) > 2]
131+
return [w for w in text.split() if len(w) >= 2]
121132

122133
def fit(self, documents):
123134
"""Build BM25 index from documents"""
@@ -209,6 +220,7 @@ def detect_domain(query):
209220
"typography": ["font pairing", "typography pairing", "heading font", "body font"],
210221
"google-fonts": ["google font", "font family", "font weight", "font style", "variable font", "noto", "font for", "find font", "font subset", "font language", "monospace font", "serif font", "sans serif font", "display font", "handwriting font", "font", "typography", "serif", "sans"],
211222
"icons": ["icon", "icons", "lucide", "heroicons", "symbol", "glyph", "pictogram", "svg icon"],
223+
"gsap": ["gsap", "quickto", "scrolltrigger", "stagger", "magnetic cursor", "parallax", "page transition", "scroll reveal", "scroll-triggered", "scrollytelling", "flip plugin", "splittext", "shimmer", "skeleton loader"],
212224
"react": ["react", "next.js", "nextjs", "suspense", "memo", "usecallback", "useeffect", "rerender", "bundle", "waterfall", "barrel", "dynamic import", "rsc", "server component"],
213225
"web": ["aria", "focus", "outline", "semantic", "virtualize", "autocomplete", "form", "input type", "preconnect"]
214226
}

0 commit comments

Comments
 (0)