diff --git a/.agents/analytics.md b/.agents/analytics.md deleted file mode 100644 index d5757b286..000000000 --- a/.agents/analytics.md +++ /dev/null @@ -1,377 +0,0 @@ -# Analytics - -Reference for the TanStack.com event taxonomy in Google Analytics 4. - -**GA4 property:** `G-JMT1Z50SPS` -**First-party proxy:** all hits route through `/_a/g/collect` on tanstack.com (see `netlify.toml`) -**Implementation:** [src/utils/analytics.ts](../src/utils/analytics.ts), [src/utils/analytics/events.ts](../src/utils/analytics/events.ts) - -## Design principles - -1. **Event names describe what happened.** Properties carry context. Adding a new partner placement is an enum value, not a new event. -2. **One event per outcome, not per intent.** No `_clicked`/`_attempted` events when an outcome event is going to fire anyway. -3. **No per-row events.** Counts and joined-string arrays as properties, not N rows per generation. -4. **Session context propagates.** Slow-changing props like `mode_used` and `idea_used` are stamped on every builder event so any breakdown works without joins. -5. **Typed registry.** Wrong props for an event = TypeScript error, not silent bad data. - ---- - -## The funnel - -Application Builder, four steps. No `OR` conditions, no overlapping event names. - -| # | Step | Event | Filter | -| --- | --------------------- | ------------------- | --------------------------------- | -| 1 | Landed on builder | `page_view` | `page_type = application_builder` | -| 2 | Got an analysis | `builder_analyzed` | — | -| 3 | Got a generation | `builder_generated` | — | -| 4 | Took action on result | `builder_activated` | — | - -Drop-off between any two steps is unambiguous. - -**Failure rates** are separate explorations, not branches off the funnel: - -- Analysis failure rate = `builder_failed[stage=analysis]` ÷ (`builder_analyzed` + `builder_failed[stage=analysis]`) -- Generation failure rate = `builder_failed[stage=generation]` ÷ (`builder_generated` + `builder_failed[stage=generation]`) -- Login wall rate = `builder_failed[stage=login_blocked]` ÷ `page_view[page_type=application_builder]` - ---- - -## Event reference (9 events) - -Every event automatically receives `page_location`, `page_path`, `page_title`, `page_type` from the analytics utility. - -### `page_view` - -Fires on initial load (auto from gtag config) and on every SPA navigation. - -| Prop | Type | Notes | -| --------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------ | -| `page_location` | string | Full URL | -| `page_path` | string | Pathname | -| `page_title` | string | `document.title` | -| `page_type` | enum | `home`, `partners_index`, `partner_detail`, `blog_index`, `blog_post`, `docs`, `partners_embed`, `application_builder`, `page` | - ---- - -### `partner_viewed` - -A partner UI element scrolled ≥50% into the viewport. Fires once per element-mount per session (the underlying `IntersectionObserver` disconnects after first fire). - -| Prop | Type | Notes | -| ------------ | ------- | --------------------------------------------------- | -| `partner_id` | string | Stable partner identifier | -| `placement` | enum | See `PartnerPlacement` below | -| `slot_index` | number? | Position in the surface (0-indexed) when applicable | - -**Note on inflation:** when filters change on the partners directory, cards unmount/remount and `partner_viewed` re-fires. Treat session-unique impressions as the dedup'd metric (compute in BigQuery with `FIRST_VALUE(... PARTITION BY session_id, partner_id)`). - ---- - -### `partner_clicked` - -User clicked a partner UI element to navigate somewhere. - -| Prop | Type | Notes | -| ------------------ | ------- | -------------------------------------------------------------------------- | -| `partner_id` | string | | -| `placement` | enum | See `PartnerPlacement` below | -| `destination` | enum | `external` (partner's site) or `internal_detail` (our partner detail page) | -| `destination_host` | string? | Host of the destination URL when `external` | -| `slot_index` | number? | Position in the surface when applicable | - -CTR per placement = `partner_clicked` ÷ `partner_viewed` filtered to same `placement`. - ---- - -### `partner_filter_applied` - -User changed the filter state on the partners directory. - -| Prop | Type | Notes | -| ----------------- | -------------- | --------------------------------------------------------------------------------------------- | -| `change` | enum | `libraries_changed`, `status_changed`, `cleared_all` | -| `library_filters` | string | Comma-joined library ids in the filter, or empty string | -| `status_filter` | string \| null | `null` means "no status filter applied" — distinguishes from "user explicitly chose 'active'" | -| `result_count` | number | Number of partners visible after the filter | - ---- - -### `partner_inquiry_started` - -User clicked a "get in touch", "let's chat", or "become a partner" CTA. - -| Prop | Type | Notes | -| ----------- | ---- | ---------------------------- | -| `placement` | enum | See `PartnerPlacement` below | - ---- - -### `builder_analyzed` - -Analysis API call succeeded. Outcome event — always preceded by user intent, no separate `_requested` event. - -| Prop | Type | Notes | -| ------------------------ | ------- | --------------------------------------------- | -| `mode_used` | enum | Session context: `lucky`, `confident`, `none` | -| `idea_used` | string | Session context: idea label, or `none` | -| `analysis_deployment` | string? | Inferred deploy target | -| `inferred_library_count` | number | | -| `inferred_partner_count` | number | | -| `feature_count` | number | | - ---- - -### `builder_generated` - -Generation API call succeeded. - -| Prop | Type | Notes | -| ----------------------- | ------- | ---------------------------------------------------------------------- | -| `mode_used` | enum | Session context | -| `idea_used` | string | Session context | -| `final_deployment` | string? | The chosen deploy target on the final result | -| `final_package_manager` | string | `pnpm`, `npm`, `yarn`, `bun` | -| `final_library_count` | number | | -| `final_partner_count` | number | | -| `final_addon_count` | number | | -| `library_ids` | string | Comma-joined LibraryIds — use `SPLIT()` in BigQuery for top-N analysis | -| `partner_ids` | string | Comma-joined | -| `addon_ids` | string | Comma-joined | - ---- - -### `builder_failed` - -Single umbrella event for analysis failures, generation failures, and login-wall blocks. Use the `stage` prop to distinguish. - -| Prop | Type | Notes | -| --------------------------------- | ------- | -------------------------------------------------------------------------------------------- | -| `mode_used` | enum | Session context | -| `idea_used` | string | Session context | -| `stage` | enum | `analysis`, `generation`, `login_blocked` | -| `error_message` | string? | Free-form error message — high cardinality, don't register as a dimension; query in BigQuery | -| `retry_after` | number? | Seconds until retry permitted (login_blocked only) | -| `anonymous_generations_remaining` | number? | When the failure was rate-limit related | - ---- - -### `builder_activated` - -User took an action on the generated result. Single event with `action` prop covers all post-generation actions. - -| Prop | Type | Notes | -| ----------- | ------- | ---------------------------------------------------------------------------------------------------------------------------- | -| `mode_used` | enum | Session context | -| `idea_used` | string | Session context | -| `action` | enum | See `BuilderAction` below | -| `surface` | enum | `result_panel` (main builder UI) or `deploy_dialog` | -| `provider` | string? | Build or deploy provider when applicable: `cloudflare`, `lovable`, `netlify`, `railway`, `render`, `vercel` | -| `automatic` | boolean | `true` for system-driven actions (e.g., deploy_dialog auto-redirect countdown). Filter to `false` for true user click rates. | - -**Important:** automatic prompt-copies that fire as a side-effect of generation do NOT emit `builder_activated`. Only user-driven actions count as activation. - ---- - -## Enums - -### `PartnerPlacement` - -| Value | Where it appears | -| -------------------- | --------------------------------------------------------------------------------------------------------------------------------- | -| `directory` | Partner cards in `/partners` index | -| `detail` | Partner detail page CTA | -| `docs_rail` | Right rail on docs pages — partner cards AND the "Become a Partner" link both fire with this placement (event name distinguishes) | -| `blog_rail` | Right rail on blog pages | -| `grid` | Generic partners grid (fallback) | -| `home_grid` | Home page social-proof grid | -| `library_grid` | Library page partners section — filter `page_path` to know which library | -| `embed_grid` | Partners embed view | -| `docs_strip` | Mobile partner strip in docs | -| `ecosystem_game` | 3D ecosystem game islands | -| `partners_index_cta` | "Get in touch" mailto on `/partners` | -| `library_callout` | "Let's chat" callout per library | -| `navbar` | "Partnership Inquiry" mailto in the Support menu | - -### `BuilderAction` - -| Value | Means | -| -------------------------- | ---------------------------------------------------------------------- | -| `copy_prompt` | User clicked a copy button on the prompt | -| `deploy` | Started a deploy through the deploy dialog | -| `clone_repo` | Cloned the GitHub repo | -| `open_codex` | Opened the result in Codex | -| `open_claude` | Opened the result in Claude | -| `open_cursor` | Opened the result in Cursor | -| `open_prompt_builder` | Opened the result in a partner's prompt-based builder | -| `download` | Downloaded the project as a zip | -| `open_advanced` | Opened the advanced builder editor | -| `netlify_start` | Started a Netlify deploy from the result | -| `provider_redirect_manual` | User clicked through to deploy provider | -| `provider_redirect_auto` | Countdown auto-redirected user to deploy provider (`automatic = true`) | -| `open_repo` | Opened the project repo from the deploy dialog | - ---- - -## Session context - -`mode_used` and `idea_used` are tracked in the builder hook and stamped on **every** builder event. This means any builder event can be sliced by mode or idea without session joins. - -**`mode_used`** transitions: - -- `none` → `lucky` when user clicks "I'm feeling lucky" -- `none` → `confident` when user clicks "I'm feeling confident" - -**`idea_used`** transitions: - -- `none` → idea label string when user picks a suggested idea -- Reset back to `none` if user clears or types fresh input (TBD — currently sticks for the session) - ---- - -## Custom dimensions to register in GA4 - -Admin → Custom definitions → Create custom dimension. **Event scope** for all of these. Without registration, they're stored in BigQuery export but invisible in the GA4 UI. - -| Dimension name | API name | Used on | -| --------------------- | ----------------------- | -------------------------------------------------------- | -| Placement | `placement` | partner_viewed, partner_clicked, partner_inquiry_started | -| Partner ID | `partner_id` | partner_viewed, partner_clicked | -| Mode used | `mode_used` | all builder events | -| Idea used | `idea_used` | all builder events | -| Action | `action` | builder_activated | -| Surface | `surface` | builder_activated | -| Stage | `stage` | builder_failed | -| Final deployment | `final_deployment` | builder_generated | -| Final package manager | `final_package_manager` | builder_generated | -| Final library count | `final_library_count` | builder_generated | -| Final partner count | `final_partner_count` | builder_generated | -| Page type | `page_type` | all events | - -12 dimensions. Well under the 50-dimension event-scoped limit. - -**Don't register:** `error_message`, `library_ids`, `partner_ids`, `addon_ids`, `destination_host`. High cardinality. Query in BigQuery. - ---- - -## Common GA4 queries - -### "What's our funnel completion rate?" - -**Explore → Funnel exploration**, four steps as defined above. Open funnel. Show elapsed time. - -### "Lucky vs Confident: which mode converts better?" - -Same funnel, breakdown dropdown = `mode_used`. Compare side by side. - -### "Which deploy targets do successful generations land on?" - -**Explore → Free-form**, dimension = `final_deployment`, metric = event count of `builder_generated`. - -### "What % of users hit the login wall?" - -Free-form, filter `event_name = builder_failed`, breakdown by `stage`. - -### "Which placement converts best for partner discovery?" - -Free-form, filter `event_name = partner_clicked OR partner_viewed`, breakdown by `placement`. Compute CTR yourself: clicks ÷ views per placement. - -### "Top 10 libraries in completed generations" - -Requires BigQuery — `library_ids` is high-cardinality. Sample query: - -```sql -SELECT - library_id, - COUNT(*) AS generations -FROM `tanstack.analytics_*.events_*`, -UNNEST(SPLIT((SELECT value.string_value - FROM UNNEST(event_params) - WHERE key = 'library_ids'), ',')) AS library_id -WHERE event_name = 'builder_generated' - AND _TABLE_SUFFIX BETWEEN '20260101' AND '20260131' -GROUP BY library_id -ORDER BY generations DESC -LIMIT 10 -``` - ---- - -## BigQuery export - -**Strongly recommended.** Free at our event volume. Without it, anything beyond stock reports is painful. - -Setup: - -1. GA4 Admin → BigQuery Links → Link -2. Pick a GCP project, daily export, US multi-region -3. Tables appear at `tanstack.analytics_.events_YYYYMMDD` after ~24h - -Once enabled, all event properties are queryable — including the ones not registered as custom dimensions. Use SQL for everything dimensional or aggregate-heavy. Use the GA4 UI for funnel exploration and headline numbers. - ---- - -## Adding new events - -Anything additive should not require schema migration of the existing taxonomy. - -**Add a new partner placement:** - -1. Add the value to `PartnerPlacement` in [src/utils/analytics/events.ts](../src/utils/analytics/events.ts) -2. Use it at the call site -3. (Optional) update the placement table in this doc - -**Add a new builder action:** - -1. Add the value to `BuilderAction` -2. Use it at the `builder_activated` call site - -**Add a new event:** - -1. Add a new union member to `AnalyticsEvent` with its prop interface -2. Call `trackEvent({ name: '...', props: { ... } })` -3. Register relevant breakdown props as custom dimensions in GA4 admin -4. Document the event in this file - -**Don't:** add new properties to existing events without updating both the type definition and this doc. Schema drift in analytics events is the slowest bug to detect. - ---- - -## Code locations - -| File | Purpose | -| ------------------------------------------------------------------------------------- | -------------------------------------------------------------------- | -| [src/utils/analytics.ts](../src/utils/analytics.ts) | `trackEvent`, `useTrackedImpression`, `trackPageView`, `getPageType` | -| [src/utils/analytics/events.ts](../src/utils/analytics/events.ts) | Typed event registry — discriminated union of all events | -| [src/utils/analytics/providers/google.ts](../src/utils/analytics/providers/google.ts) | gtag wrapper | -| [src/utils/analytics/types.ts](../src/utils/analytics/types.ts) | Provider interface | -| [src/routes/\_\_root.tsx](../src/routes/__root.tsx) | gtag bootstrap, `PageViewTracker` | -| [netlify.toml](../netlify.toml) | First-party proxy redirects | - ---- - -## Migration history - -### 2026-05 — schema v2 - -Collapsed 28 events into 9. Removed `application_starter_*` event family. Replaced with `builder_*` events. Mode and idea selection moved from standalone events into session-context props on every builder event. - -**Events removed entirely** (no replacement): - -- `application_starter_library_toggled`, `_integration_toggled`, `_package_manager_toggled`, `_toolchain_toggled` — config exploration depth no longer tracked. Final config is on `builder_generated`. -- `application_starter_continue_clicked`, `_generate_clicked` — intent implied by outcome events. -- `application_starter_login_clicked`, `_value_copied` (auto), `_builder_result_applied`, `_final_partner_in_prompt`, `_final_addon_in_prompt`. - -**Events folded into others:** - -- `application_starter_action_clicked` (mode_selected) → `mode_used` prop on subsequent events -- `application_starter_idea_selected` → `idea_used` prop on subsequent events -- `application_starter_login_required` → `builder_failed[stage=login_blocked]` -- `application_starter_value_copied` (user trigger) → `builder_activated[action=copy_prompt]` -- All other `application_starter_action_clicked` calls → `builder_activated` -- `partner_card_clicked`, `partner_click` → `partner_clicked` -- `partner_impression`, `partner_detail_viewed` → `partner_viewed` -- `partners_filter_changed` → `partner_filter_applied` -- `partner_inquiry_clicked`, `become_partner_clicked` → `partner_inquiry_started` - -Historical data with old event names is still queryable in GA4 and BigQuery. Cutover date: see git log for the migration commit. diff --git a/.agents/index.md b/.agents/index.md deleted file mode 100644 index ea6eee1cb..000000000 --- a/.agents/index.md +++ /dev/null @@ -1,46 +0,0 @@ -# Agent Guidelines - -TanStack.com marketing site built with TanStack Start. - -## Core principles for every task - -Before writing code, walk this ladder. Stop at the first answer that works. - -1. **Does this need to exist?** → no: skip it (YAGNI) -2. **Already in this codebase?** → reuse it, don't rewrite -3. **Stdlib does it?** → use it -4. **Native platform feature?** → use it -5. **Installed dependency?** → use it -6. **One line?** → one line -7. **Only then:** the minimum that works - -Inspired by https://github.com/DietrichGebert/ponytail - -### Abstraction & repetition - -- **KISS** — keep it simple, stupid. Boring code beats clever code. -- **WET > DRY** — write everything twice (or more) before abstracting. Three similar usages with diverging needs is the cue to extract; two is not. -- Premature DRY couples unrelated code through a shared abstraction that ends up fighting both callers. Duplication is cheaper than the wrong abstraction. - -### What this means in practice - -- Don't add error handling, fallbacks, or validation for cases that can't happen. -- Don't introduce helpers, wrappers, or config knobs "for later." -- Don't refactor surrounding code while fixing a bug. -- When tempted to abstract: count the call sites and check they actually want the same behavior. If not, leave the duplication. - -## Essentials - -- Package manager: `pnpm` -- Run `pnpm test` before commits not after every tiny edit -- Don't run builds or tests after every change. This is a visual site; assume changes work unless reported otherwise. -- Rely on `tsc` or your built-in LSP integration (hopefully you have this) -- **Typesafety is paramount.** Never cast types; fix at source instead. See [typescript.md](./typescript.md). - -## Topic Guides - -- [TypeScript Conventions](./typescript.md): Type inference, casting rules, generic naming -- [TanStack Patterns](./tanstack-patterns.md): Loaders, server functions, environment shaking -- [UI Style Guide](./ui-style.md): Visual design principles for 2026 -- [Workflow](./workflow.md): Build commands, debugging, Playwright -- [Analytics](./analytics.md): GA4 event taxonomy, funnel definition, custom dimensions diff --git a/.agents/skills/design-motion-principles/SKILL.md b/.agents/skills/design-motion-principles/SKILL.md deleted file mode 100644 index 0a52ee0fd..000000000 --- a/.agents/skills/design-motion-principles/SKILL.md +++ /dev/null @@ -1,124 +0,0 @@ ---- -name: design-motion-principles -description: "Motion and interaction design expert based on Emil Kowalski, Jakub Krehel, and Jhey Tompkins' techniques. Two modes — build interactive components with purposeful motion, or audit existing animations to catch AI-slop motion patterns (audit emits a branded HTML report with looping demos). Use when creating, adding, animating, or reviewing UI motion: transitions, hover states, micro-interactions, enter/exit animations, or any motion design work in React, Framer Motion, CSS, or HTML. Provides per-designer perspectives with context-aware weighting." ---- - -# Design Motion Principles - -You are a senior design engineer specializing in motion and interaction design. This skill operates in two modes: - -- **Create** — Build interactive components with purposeful motion → `workflows/create.md` -- **Audit** — Review existing motion design and report findings → `workflows/audit.md` - -**Scope**: Web and app UI motion — HTML/CSS, React, Framer Motion / Motion, iOS/Android transitions, design system animations. The frequency framework still applies to other motion work (game engines, Lottie, Rive, video), but designer-specific techniques may not translate. - ---- - -## STEP 0: Detect Mode (DO THIS FIRST) - -| Signal in the request | Mode | -| -------------------------------------------------------------------------------- | ------------ | -| "build", "create", "add animation", "animate this", "implement", "make it feel…" | **Create** | -| "audit", "review", "evaluate", "check", "feedback on", "is this motion good" | **Audit** | -| Ambiguous (e.g. "look at this modal animation") | Ask the user | - -For ambiguous requests, if `AskUserQuestion` is available, present: - -- **Create** — Build or improve the component's motion -- **Audit** — Review existing motion and report findings - -Otherwise ask in plain text: "Should I build/improve the motion (Create mode), or review existing motion and report findings (Audit mode)?" - -**Once the mode is known, read the matching workflow file and follow it exactly.** - ---- - -## The Three Designers - -- **Emil Kowalski** (Linear, ex-Vercel) — Restraint, speed, purposeful motion. Best for productivity tools. -- **Jakub Krehel** (jakub.kr) — Subtle production polish, professional refinement. Best for shipped consumer apps. -- **Jhey Tompkins** (@jh3yy) — Playful experimentation, CSS innovation. Best for creative sites, kids apps, portfolios. - -> These three lenses distill each designer's _publicly published_ work — courses, articles, talks, and open-source projects. The weighting framework and the "lens" framing are this skill's interpretation of their principles, named in tribute; they are not authored or endorsed by the designers themselves. - -Each designer answers a different question: - -- **Emil** — _"Should this animate at all?"_ -- **Jakub** — _"Is this subtle and polished enough for production?"_ -- **Jhey** — _"What could this become?"_ - -**Critical insight**: These perspectives are context-dependent, not universal rules. A kids' app should prioritize Jakub + Jhey (polish + delight), not Emil's productivity-focused speed rules. Both modes weight the designers by project context before doing anything. - ---- - -## Context-to-Perspective Mapping - -| Project Type | Primary | Secondary | Selective | -| ----------------------------------- | ------- | --------- | ---------------------------------- | -| Productivity tool (Linear, Raycast) | Emil | Jakub | Jhey (onboarding only) | -| Kids app / Educational | Jakub | Jhey | Emil (high-freq game interactions) | -| Creative portfolio | Jakub | Jhey | Emil (high-freq interactions) | -| Marketing/landing page | Jakub | Jhey | Emil (forms, nav) | -| SaaS dashboard | Emil | Jakub | Jhey (empty states) | -| Mobile app | Jakub | Emil | Jhey (delighters) | -| E-commerce | Jakub | Emil | Jhey (product showcase) | - ---- - -## Core Principles (Both Modes) - -### The Frequency Gate - -Before adding or approving any animation, ask how often the user triggers it: - -| Frequency | Recommendation | -| ------------------- | ------------------------------------- | -| Rare (monthly) | Delightful, expressive motion welcome | -| Occasional (daily) | Subtle, fast motion | -| Frequent (100s/day) | No animation or instant transition | -| Keyboard-initiated | Never animate | - -### Duration Guidelines (Context-Dependent) - -| Context | Guideline | -| ---------------------------- | -------------------------- | -| Productivity UI (Emil) | Under 300ms — 180ms ideal | -| Production polish (Jakub) | 200-500ms for smoothness | -| Creative/kids/playful (Jhey) | Whatever serves the effect | - -**Do not universally flag or cap durations.** Check the context weighting first. - -### The Golden Rule - -> "The best animation is that which goes unnoticed." - -If users comment "nice animation!" on every interaction, it's probably too prominent for production. (Exception: kids apps and playful contexts where delight IS the goal.) - -### Accessibility is NOT Optional - -Every animation — generated in Create mode or reviewed in Audit mode — must handle `prefers-reduced-motion`. No exceptions. See `references/accessibility.md`. - ---- - -## Reference Index - -| File | Contents | Load When | -| -------------------------------------------------- | ------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | -| [Motion Cookbook](references/motion-cookbook.md) | All motion recipes — enter/exit, easing, springs, clip-path, @property, FLIP, scroll-driven | Create mode (always); Audit mode for implementation recommendations | -| [Creation Gotchas](references/creation-gotchas.md) | Claude's failure modes when writing motion | Create mode (always) | -| [Audit Checklist](references/audit-checklist.md) | Systematic audit checklist | Audit mode (always) | -| [Anti-Checklist](references/anti-checklist.md) | Quality gate — AI-slop motion categories + anti-patterns to flag | Audit mode (always) | -| [Emil Kowalski](references/emil-kowalski.md) | Restraint philosophy, frequency rule, decision frameworks | Either mode, if Emil is weighted | -| [Jakub Krehel](references/jakub-krehel.md) | Production polish philosophy and decision frameworks | Either mode, if Jakub is weighted | -| [Jhey Tompkins](references/jhey-tompkins.md) | Playful experimentation philosophy and frameworks | Either mode, if Jhey is weighted | -| [Accessibility](references/accessibility.md) | prefers-reduced-motion, vestibular safety | Both modes (mandatory) | -| [Performance](references/performance.md) | GPU optimization, will-change, layout thrash | Either mode, for complex animations | -| [Output Format](references/output-format.md) | Audit report template — HTML mode (default) + terminal mode (flag) | Audit mode only | -| [Demo Shell](references/demo-shell.html) | Visual container template for per-finding demo cards in the HTML report | Audit mode, HTML output | - -## Workflow Index - -| Workflow | Purpose | -| ----------------------------- | ------------------------------------------------------------ | -| [Create](workflows/create.md) | Build interactive components with purposeful motion | -| [Audit](workflows/audit.md) | Review existing motion design, produce a per-designer report | diff --git a/.agents/skills/design-motion-principles/references/accessibility.md b/.agents/skills/design-motion-principles/references/accessibility.md deleted file mode 100644 index 99247ab02..000000000 --- a/.agents/skills/design-motion-principles/references/accessibility.md +++ /dev/null @@ -1,52 +0,0 @@ -# Accessibility - -**This is not optional.** Motion can cause discomfort, nausea, or distraction for many users. - ---- - -## Respect User Preferences - -```css -@media (prefers-reduced-motion: reduce) { - *, - *::before, - *::after { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - scroll-behavior: auto !important; - } -} -``` - -**What this does**: Effectively disables animations while preserving final states (so layouts don't break). - ---- - -## Functional vs. Decorative Motion - -| Type | Purpose | Reduced Motion Behavior | -| -------------- | ----------------------------------------------------------- | ---------------------------------------------------------- | -| **Functional** | Indicates state changes, spatial relationships, orientation | May need alternative (instant state change, no transition) | -| **Decorative** | Pure delight, visual interest | Can be fully removed | - -**The test**: Does removing this animation break the user's ability to understand what happened? If yes, it's functional. - ---- - -## Motion Sensitivity Considerations - -- Avoid large-scale motion (full-screen transitions, parallax) -- Avoid continuous or looping animations that can't be paused -- Provide pause controls for any ambient animation -- Be especially careful with vestibular triggers: zooming, spinning, parallax - ---- - -## Implementation Checklist - -- [ ] Tested with `prefers-reduced-motion: reduce` enabled -- [ ] No vestibular triggers (excessive zoom, spin, parallax) -- [ ] Looping animations can be paused -- [ ] Functional animations have non-motion alternatives -- [ ] Users can complete all tasks with animations disabled diff --git a/.agents/skills/design-motion-principles/references/anti-checklist.md b/.agents/skills/design-motion-principles/references/anti-checklist.md deleted file mode 100644 index e11cb002e..000000000 --- a/.agents/skills/design-motion-principles/references/anti-checklist.md +++ /dev/null @@ -1,295 +0,0 @@ -# Anti-Checklist - -This file is the audit's quality gate. The categories below describe motion patterns to **flag** in audited code — AI-slop tells at the top (where most 2026 motion problems live), followed by perspective-specific anti-patterns from Emil, Jakub, and Jhey, then general motion mistakes and code-shaped red flags. When audited code matches a pattern here, the audit surfaces a finding and the agent generates a per-finding motion suggestion by reading the relevant philosophy reference (`emil-kowalski.md`, `jakub-krehel.md`, `jhey-tompkins.md`). - -The file frames patterns as "things to flag," not "mistakes to avoid" — language that makes the audit's adversarial posture explicit. - ---- - -## AI-Slop Motion Patterns - -These are the recognizable motion fingerprints of AI-generated UIs in 2026. They're not always wrong in isolation — what makes them slop is _frequency_ and _uniformity_. Finding one instance is normal polish; finding the same pattern slapped across the codebase is the tell. Each category includes a flagging heuristic below the definition so the audit isn't tripped by single intentional uses. - ---- - -### Pulsing indicators - -Glowing dots, breathing CTAs, throbbing rings, "live"/"online"/"recording"/"AI active" pulse animations, dark-mode pulse glows — any looped scale/opacity pulse used to draw attention to a status element. - -**Flag when you see:** - -- `@keyframes` rules with names containing `pulse`, `glow`, `breathe`, `throb` -- `animation: ... infinite` on small UI elements (dots, badges, status indicators) -- `box-shadow` or `opacity` loops on status icons -- Tailwind `animate-pulse` on indicator dots or active-state elements - -**Heuristic:** Flag _any_ instance. Pulsing indicators are almost always slop — the only exception is a single brand element with explicit design rationale stated in code comments or design docs. - -**Fix lens:** Emil — purposeful restraint. See `references/emil-kowalski.md`. - ---- - -### Blur-everywhere entrances - -`filter: blur(Npx)` applied to every entering element on mount — sections, cards, images, paragraphs. Jakub's enter recipe (`opacity + translateY + blur`) is excellent in moderation; AI-slop versions apply it uniformly across the page. - -**Flag when you see:** - -- `initial={{ filter: 'blur(Npx)' }}` or `from { filter: blur(Npx); }` on multiple distinct components in the same view -- Identical blur values (e.g., `blur(4px)`) repeated across components without context distinction -- Blur on text-bearing entrances (headings, paragraphs) where it impairs first-paint readability - -**Heuristic:** Flag when ≥3 distinct components in the same view share the same `filter: blur()` enter pattern. Single uses with intent (a hero element, a modal) are fine. - -**Fix lens:** Jakub — production polish, but selective. See `references/jakub-krehel.md`. - ---- - -### Hover-scale-on-everything - -`transform: scale(1.0X)` on `:hover` applied to every card, button, and image without intent. The micro-bounce-on-hover feels polished in moderation; AI-slop versions slap it on indiscriminately. - -**Flag when you see:** - -- `transition` rules with `transform: scale(1.0X)` on `:hover` across multiple card/button/image components -- Identical scale values (e.g., `scale(1.05)`) repeated across selectors with no discriminating context -- Tailwind `hover:scale-105` applied to grids of repeated items - -**Heuristic:** Flag when ≥3 distinct components share the same `transform: scale(1.0X)` on `:hover` with no other discriminating selector context. Single intentional uses (e.g., a primary CTA) are fine. - -**Fix lens:** Emil for utility-shaped elements (none); Jakub for product-shaped elements (selective). See `references/emil-kowalski.md` and `references/jakub-krehel.md`. - ---- - -### Stagger-spam-on-every-list - -`stagger`, `staggerChildren`, or hand-rolled `animation-delay: calc(var(--i) * 50ms)` patterns applied to every list, grid, or repeated-element block. Jhey-style stagger on a deliberate moment is delightful; AI-slop spreads it across every list as default polish. - -**Flag when you see:** - -- `staggerChildren` in framer-motion `variants` across multiple list components -- `animation-delay: calc(...)` with item-index multipliers across multiple components -- Sequential delays applied to lists that don't read as a moment (search results, settings options, table rows) - -**Heuristic:** Flag when ≥2 lists in the same view use stagger entrance. One intentional moment is fine; two or more is the tell. - -**Fix lens:** Emil for utility lists (no stagger); Jhey for delight moments (selective). See `references/emil-kowalski.md` and `references/jhey-tompkins.md`. - ---- - -### Bouncy-springs-on-utility-actions - -`type: 'spring'` with bounce on dropdown opens, toggle switches, menu reveals, modal entrances — utility actions where bounce reads as "playful" but the action itself is productivity-oriented. - -**Flag when you see:** - -- `transition={{ type: 'spring', bounce: > 0 }}` on dropdowns, popovers, menus, toggles, modal opens, settings panels -- CSS `cubic-bezier(...)` with overshoot values on utility elements -- Identical spring configs across utility components - -**Heuristic:** Flag _any_ spring with bounce > 0 on a utility action (dropdown, menu, toggle, modal, settings panel). Bounce belongs on playful elements — celebration moments, kids apps, intentional delight — not productivity UI. - -**Fix lens:** Emil — speed and purpose. See `references/emil-kowalski.md`. - ---- - -### Uniform-fade-in-on-every-element - -Identical `opacity + translateY` (with or without blur) enter animations applied to every section, card, paragraph, and heading. The "polished entrance" treatment from Jakub used uniformly across the page, regardless of element type or context. - -**Flag when you see:** - -- Multiple components sharing identical `initial`/`animate` opacity+translateY values -- `whileInView` with identical viewport options applied to every block on a page -- CSS keyframes with generic names (`fadeInUp`, `enter`, `reveal`) attached to many selectors - -**Heuristic:** Flag when ≥4 distinct components share identical enter animations (same opacity, same translateY, same duration, same easing). Three is acceptable polish baseline; four is uniformity slop. - -**Fix lens:** Jakub — selective polish with hierarchy. See `references/jakub-krehel.md`. - ---- - -### Motion-on-mount-for-static-content - -Entrance animations on headings, body paragraphs, navigation links, and other content that should appear instantly. The "fade in everything" pattern that delays reading and makes the page feel sluggish. - -**Flag when you see:** - -- `initial`/`animate` on `

`, `

`, `

`, `