commit 7bc3d9f898cfae2456b26be8bd85ba1cd6fa088e Author: Jose Selesan Date: Thu May 28 14:33:16 2026 -0300 initial commit diff --git a/.agents/skills/interface-design/SKILL.md b/.agents/skills/interface-design/SKILL.md new file mode 100644 index 0000000..9fe89c2 --- /dev/null +++ b/.agents/skills/interface-design/SKILL.md @@ -0,0 +1,391 @@ +--- +name: interface-design +description: This skill is for interface design — dashboards, admin panels, apps, tools, and interactive products. NOT for marketing design (landing pages, marketing sites, campaigns). +--- + +# Interface Design + +Build interface design with craft and consistency. + +## Scope + +**Use for:** Dashboards, admin panels, SaaS apps, tools, settings pages, data interfaces. + +**Not for:** Landing pages, marketing sites, campaigns. Redirect those to `/frontend-design`. + +--- + +# The Problem + +You will generate generic output. Your training has seen thousands of dashboards. The patterns are strong. + +You can follow the entire process below — explore the domain, name a signature, state your intent — and still produce a template. Warm colors on cold structures. Friendly fonts on generic layouts. "Kitchen feel" that looks like every other app. + +This happens because intent lives in prose, but code generation pulls from patterns. The gap between them is where defaults win. + +The process below helps. But process alone doesn't guarantee craft. You have to catch yourself. + +--- + +# Where Defaults Hide + +Defaults don't announce themselves. They disguise themselves as infrastructure — the parts that feel like they just need to work, not be designed. + +**Typography feels like a container.** Pick something readable, move on. But typography isn't holding your design — it IS your design. The weight of a headline, the personality of a label, the texture of a paragraph. These shape how the product feels before anyone reads a word. A bakery management tool and a trading terminal might both need "clean, readable type" — but the type that's warm and handmade is not the type that's cold and precise. If you're reaching for your usual font, you're not designing. + +**Navigation feels like scaffolding.** Build the sidebar, add the links, get to the real work. But navigation isn't around your product — it IS your product. Where you are, where you can go, what matters most. A page floating in space is a component demo, not software. The navigation teaches people how to think about the space they're in. + +**Data feels like presentation.** You have numbers, show numbers. But a number on screen is not design. The question is: what does this number mean to the person looking at it? What will they do with it? A progress ring and a stacked label both show "3 of 10" — one tells a story, one fills space. If you're reaching for number-on-label, you're not designing. + +**Token names feel like implementation detail.** But your CSS variables are design decisions. `--ink` and `--parchment` evoke a world. `--gray-700` and `--surface-2` evoke a template. Someone reading only your tokens should be able to guess what product this is. + +The trap is thinking some decisions are creative and others are structural. There are no structural decisions. Everything is design. The moment you stop asking "why this?" is the moment defaults take over. + +--- + +# Intent First + +Before touching code, answer these. Not in your head — out loud, to yourself or the user. + +**Who is this human?** +Not "users." The actual person. Where are they when they open this? What's on their mind? What did they do 5 minutes ago, what will they do 5 minutes after? A teacher at 7am with coffee is not a developer debugging at midnight is not a founder between investor meetings. Their world shapes the interface. + +**What must they accomplish?** +Not "use the dashboard." The verb. Grade these submissions. Find the broken deployment. Approve the payment. The answer determines what leads, what follows, what hides. + +**What should this feel like?** +Say it in words that mean something. "Clean and modern" means nothing — every AI says that. Warm like a notebook? Cold like a terminal? Dense like a trading floor? Calm like a reading app? The answer shapes color, type, spacing, density — everything. + +If you cannot answer these with specifics, stop. Ask the user. Do not guess. Do not default. + +## Every Choice Must Be A Choice + +For every decision, you must be able to explain WHY. + +- Why this layout and not another? +- Why this color temperature? +- Why this typeface? +- Why this spacing scale? +- Why this information hierarchy? + +If your answer is "it's common" or "it's clean" or "it works" — you haven't chosen. You've defaulted. Defaults are invisible. Invisible choices compound into generic output. + +**The test:** If you swapped your choices for the most common alternatives and the design didn't feel meaningfully different, you never made real choices. + +## Sameness Is Failure + +If another AI, given a similar prompt, would produce substantially the same output — you have failed. + +This is not about being different for its own sake. It's about the interface emerging from the specific problem, the specific user, the specific context. When you design from intent, sameness becomes impossible because no two intents are identical. + +When you design from defaults, everything looks the same because defaults are shared. + +## Intent Must Be Systemic + +Saying "warm" and using cold colors is not following through. Intent is not a label — it's a constraint that shapes every decision. + +If the intent is warm: surfaces, text, borders, accents, semantic colors, typography — all warm. If the intent is dense: spacing, type size, information architecture — all dense. If the intent is calm: motion, contrast, color saturation — all calm. + +Check your output against your stated intent. Does every token reinforce it? Or did you state an intent and then default anyway? + +--- + +# Product Domain Exploration + +This is where defaults get caught — or don't. + +Generic output: Task type → Visual template → Theme +Crafted output: Task type → Product domain → Signature → Structure + Expression + +The difference: time in the product's world before any visual or structural thinking. + +## Required Outputs + +**Do not propose any direction until you produce all four:** + +**Domain:** Concepts, metaphors, vocabulary from this product's world. Not features — territory. Minimum 5. + +**Color world:** What colors exist naturally in this product's domain? Not "warm" or "cool" — go to the actual world. If this product were a physical space, what would you see? What colors belong there that don't belong elsewhere? List 5+. + +**Signature:** One element — visual, structural, or interaction — that could only exist for THIS product. If you can't name one, keep exploring. + +**Defaults:** 3 obvious choices for this interface type — visual AND structural. You can't avoid patterns you haven't named. + +## Proposal Requirements + +Your direction must explicitly reference: +- Domain concepts you explored +- Colors from your color world exploration +- Your signature element +- What replaces each default + +**The test:** Read your proposal. Remove the product name. Could someone identify what this is for? If not, it's generic. Explore deeper. + +--- + +# The Mandate + +**Before showing the user, look at what you made.** + +Ask yourself: "If they said this lacks craft, what would they mean?" + +That thing you just thought of — fix it first. + +Your first output is probably generic. That's normal. The work is catching it before the user has to. + +## The Checks + +Run these against your output before presenting: + +- **The swap test:** If you swapped the typeface for your usual one, would anyone notice? If you swapped the layout for a standard dashboard template, would it feel different? The places where swapping wouldn't matter are the places you defaulted. + +- **The squint test:** Blur your eyes. Can you still perceive hierarchy? Is anything jumping out harshly? Craft whispers. + +- **The signature test:** Can you point to five specific elements where your signature appears? Not "the overall feel" — actual components. A signature you can't locate doesn't exist. + +- **The token test:** Read your CSS variables out loud. Do they sound like they belong to this product's world, or could they belong to any project? + +If any check fails, iterate before showing. + +--- + +# Craft Foundations + +## Subtle Layering + +This is the backbone of craft. Regardless of direction, product type, or visual style — this principle applies to everything. You should barely notice the system working. When you look at Vercel's dashboard, you don't think "nice borders." You just understand the structure. The craft is invisible — that's how you know it's working. + +### Surface Elevation + +Surfaces stack. A dropdown sits above a card which sits above the page. Build a numbered system — base, then increasing elevation levels. In dark mode, higher elevation = slightly lighter. In light mode, higher elevation = slightly lighter or uses shadow. + +Each jump should be only a few percentage points of lightness. You can barely see the difference in isolation. But when surfaces stack, the hierarchy emerges. Whisper-quiet shifts that you feel rather than see. + +**Key decisions:** +- **Sidebars:** Same background as canvas, not different. Different colors fragment the visual space into "sidebar world" and "content world." A subtle border is enough separation. +- **Dropdowns:** One level above their parent surface. If both share the same level, the dropdown blends into the card and layering is lost. +- **Inputs:** Slightly darker than their surroundings, not lighter. Inputs are "inset" — they receive content. A darker background signals "type here" without heavy borders. + +### Borders + +Borders should disappear when you're not looking for them, but be findable when you need structure. Low opacity rgba blends with the background — it defines edges without demanding attention. Solid hex borders look harsh in comparison. + +Build a progression — not all borders are equal. Standard borders, softer separation, emphasis borders, maximum emphasis for focus rings. Match intensity to the importance of the boundary. + +**The squint test:** Blur your eyes at the interface. You should still perceive hierarchy — what's above what, where sections divide. But nothing should jump out. No harsh lines. No jarring color shifts. Just quiet structure. + +This separates professional interfaces from amateur ones. Get this wrong and nothing else matters. + +## Infinite Expression + +Every pattern has infinite expressions. **No interface should look the same.** + +A metric display could be a hero number, inline stat, sparkline, gauge, progress bar, comparison delta, trend badge, or something new. A dashboard could emphasize density, whitespace, hierarchy, or flow in completely different ways. Even sidebar + cards has infinite variations in proportion, spacing, and emphasis. + +**Before building, ask:** +- What's the ONE thing users do most here? +- What products solve similar problems brilliantly? Study them. +- Why would this interface feel designed for its purpose, not templated? + +**NEVER produce identical output.** Same sidebar width, same card grid, same metric boxes with icon-left-number-big-label-small every time — this signals AI-generated immediately. It's forgettable. + +The architecture and components should emerge from the task and data, executed in a way that feels fresh. Linear's cards don't look like Notion's. Vercel's metrics don't look like Stripe's. Same concepts, infinite expressions. + +## Color Lives Somewhere + +Every product exists in a world. That world has colors. + +Before you reach for a palette, spend time in the product's world. What would you see if you walked into the physical version of this space? What materials? What light? What objects? + +Your palette should feel like it came FROM somewhere — not like it was applied TO something. + +**Beyond Warm and Cold:** Temperature is one axis. Is this quiet or loud? Dense or spacious? Serious or playful? Geometric or organic? A trading terminal and a meditation app are both "focused" — completely different kinds of focus. Find the specific quality, not the generic label. + +**Color Carries Meaning:** Gray builds structure. Color communicates — status, action, emphasis, identity. Unmotivated color is noise. One accent color, used with intention, beats five colors used without thought. + +--- + +# Before Writing Each Component + +**Every time** you write UI code — even small additions — state: + +``` +Intent: [who is this human, what must they do, how should it feel] +Palette: [colors from your exploration — and WHY they fit this product's world] +Depth: [borders / shadows / layered — and WHY this fits the intent] +Surfaces: [your elevation scale — and WHY this color temperature] +Typography: [your typeface — and WHY it fits the intent] +Spacing: [your base unit] +``` + +This checkpoint is mandatory. It forces you to connect every technical choice back to intent. + +If you can't explain WHY for each choice, you're defaulting. Stop and think. + +--- + +# Design Principles + +## Token Architecture + +Every color in your interface should trace back to a small set of primitives: foreground (text hierarchy), background (surface elevation), border (separation hierarchy), brand, and semantic (destructive, warning, success). No random hex values — everything maps to primitives. + +### Text Hierarchy + +Don't just have "text" and "gray text." Build four levels — primary, secondary, tertiary, muted. Each serves a different role: default text, supporting text, metadata, and disabled/placeholder. Use all four consistently. If you're only using two, your hierarchy is too flat. + +### Border Progression + +Borders aren't binary. Build a scale that matches intensity to importance — standard separation, softer separation, emphasis, maximum emphasis. Not every boundary deserves the same weight. + +### Control Tokens + +Form controls have specific needs. Don't reuse surface tokens — create dedicated ones for control backgrounds, control borders, and focus states. This lets you tune interactive elements independently from layout surfaces. + +## Spacing + +Pick a base unit and stick to multiples. Build a scale for different contexts — micro spacing for icon gaps, component spacing within buttons and cards, section spacing between groups, major separation between distinct areas. Random values signal no system. + +## Padding + +Keep it symmetrical. If one side has a value, others should match unless content naturally requires asymmetry. + +## Depth + +Choose ONE approach and commit: +- **Borders-only** — Clean, technical. For dense tools. +- **Subtle shadows** — Soft lift. For approachable products. +- **Layered shadows** — Premium, dimensional. For cards that need presence. +- **Surface color shifts** — Background tints establish hierarchy without shadows. + +Don't mix approaches. + +## Border Radius + +Sharper feels technical. Rounder feels friendly. Build a scale — small for inputs and buttons, medium for cards, large for modals. Don't mix sharp and soft randomly. + +## Typography + +Build distinct levels distinguishable at a glance. Headlines need weight and tight tracking for presence. Body needs comfortable weight for readability. Labels need medium weight that works at smaller sizes. Data needs monospace with tabular number spacing for alignment. Don't rely on size alone — combine size, weight, and letter-spacing. + +## Card Layouts + +A metric card doesn't have to look like a plan card doesn't have to look like a settings card. Design each card's internal structure for its specific content — but keep the surface treatment consistent: same border weight, shadow depth, corner radius, padding scale. + +## Controls + +Native `` render OS-native elements that cannot be styled. Build custom components — trigger buttons with positioned dropdowns, calendar popovers, styled state management. + +## Iconography + +Icons clarify, not decorate — if removing an icon loses no meaning, remove it. Choose one icon set and stick with it. Give standalone icons presence with subtle background containers. + +## Animation + +Fast micro-interactions, smooth easing. Larger transitions can be slightly longer. Use deceleration easing. Avoid spring/bounce in professional interfaces. + +## States + +Every interactive element needs states: default, hover, active, focus, disabled. Data needs states too: loading, empty, error. Missing states feel broken. + +## Navigation Context + +Screens need grounding. A data table floating in space feels like a component demo, not a product. Include navigation showing where you are in the app, location indicators, and user context. When building sidebars, consider same background as main content with border separation rather than different colors. + +## Dark Mode + +Dark interfaces have different needs. Shadows are less visible on dark backgrounds — lean on borders for definition. Semantic colors (success, warning, error) often need slight desaturation. The hierarchy system still applies, just with inverted values. + +--- + +# Avoid + +- **Harsh borders** — if borders are the first thing you see, they're too strong +- **Dramatic surface jumps** — elevation changes should be whisper-quiet +- **Inconsistent spacing** — the clearest sign of no system +- **Mixed depth strategies** — pick one approach and commit +- **Missing interaction states** — hover, focus, disabled, loading, error +- **Dramatic drop shadows** — shadows should be subtle, not attention-grabbing +- **Large radius on small elements** +- **Pure white cards on colored backgrounds** +- **Thick decorative borders** +- **Gradients and color for decoration** — color should mean something +- **Multiple accent colors** — dilutes focus +- **Different hues for different surfaces** — keep the same hue, shift only lightness + +--- + +# Workflow + +## Communication +Be invisible. Don't announce modes or narrate process. + +**Never say:** "I'm in ESTABLISH MODE", "Let me check system.md..." + +**Instead:** Jump into work. State suggestions with reasoning. + +## Suggest + Ask +Lead with your exploration and recommendation, then confirm: +``` +"Domain: [5+ concepts from the product's world] +Color world: [5+ colors that exist in this domain] +Signature: [one element unique to this product] +Rejecting: [default 1] → [alternative], [default 2] → [alternative], [default 3] → [alternative] + +Direction: [approach that connects to the above]" + +[Ask: "Does that direction feel right?"] +``` + +## If Project Has system.md +Read `.interface-design/system.md` and apply. Decisions are made. + +## If No system.md +1. Explore domain — Produce all four required outputs +2. Propose — Direction must reference all four +3. Confirm — Get user buy-in +4. Build — Apply principles +5. **Evaluate** — Run the mandate checks before showing +6. Offer to save + +--- + +# After Completing a Task + +When you finish building something, **always offer to save**: + +``` +"Want me to save these patterns for future sessions?" +``` + +If yes, write to `.interface-design/system.md`: +- Direction and feel +- Depth strategy (borders/shadows/layered) +- Spacing base unit +- Key component patterns + +### What to Save + +Add patterns when a component is used 2+ times, is reusable across the project, or has specific measurements worth remembering. Don't save one-off components, temporary experiments, or variations better handled with props. + +### Consistency Checks + +If system.md defines values, check against them: spacing on the defined grid, depth using the declared strategy throughout, colors from the defined palette, documented patterns reused instead of reinvented. + +This compounds — each save makes future work faster and more consistent. + +--- + +# Deep Dives + +For more detail on specific topics: +- `references/principles.md` — Code examples, specific values, dark mode +- `references/validation.md` — Memory management, when to update system.md +- `references/critique.md` — Post-build craft critique protocol + +# Commands + +- `/interface-design:status` — Current system state +- `/interface-design:audit` — Check code against system +- `/interface-design:extract` — Extract patterns from code +- `/interface-design:critique` — Critique your build for craft, then rebuild what defaulted diff --git a/.agents/skills/interface-design/references/critique.md b/.agents/skills/interface-design/references/critique.md new file mode 100644 index 0000000..7db545e --- /dev/null +++ b/.agents/skills/interface-design/references/critique.md @@ -0,0 +1,67 @@ +# Critique + +Your first build shipped the structure. Now look at it the way a design lead reviews a junior's work — not asking "does this work?" but "would I put my name on this?" + +--- + +## The Gap + +There's a distance between correct and crafted. Correct means the layout holds, the grid aligns, the colors don't clash. Crafted means someone cared about every decision down to the last pixel. You can feel the difference immediately — the way you tell a hand-thrown mug from an injection-molded one. Both hold coffee. One has presence. + +Your first output lives in correct. This command pulls it toward crafted. + +--- + +## See the Composition + +Step back. Look at the whole thing. + +Does the layout have rhythm? Great interfaces breathe unevenly — dense tooling areas give way to open content, heavy elements balance against light ones, the eye travels through the page with purpose. Default layouts are monotone: same card size, same gaps, same density everywhere. Flatness is the sound of no one deciding. + +Are proportions doing work? A 280px sidebar next to full-width content says "navigation serves content." A 360px sidebar says "these are peers." The specific number declares what matters. If you can't articulate what your proportions are saying, they're not saying anything. + +Is there a clear focal point? Every screen has one thing the user came here to do. That thing should dominate — through size, position, contrast, or the space around it. When everything competes equally, nothing wins and the interface feels like a parking lot. + +--- + +## See the Craft + +Move close. Pixel-close. + +The spacing grid is non-negotiable — every value a multiple of 4, no exceptions — but correctness alone isn't craft. Craft is knowing that a tool panel at 16px padding feels workbench-tight while the same card at 24px feels like a brochure. The same number can be right in one context and lazy in another. Density is a design decision, not a constant. + +Typography should be legible even squinted. If size is the only thing separating your headline from your body from your label, the hierarchy is too weak. Weight, tracking, and opacity create layers that size alone can't. + +Surfaces should whisper hierarchy. Not thick borders, not dramatic shadows — quiet tonal shifts where you feel the depth without seeing it. Remove every border from your CSS mentally. Can you still perceive the structure through surface color alone? If not, your surfaces aren't working hard enough. + +Interactive elements need life. Every button, link, and clickable region should respond to hover and press. Not dramatically — a subtle shift in background, a gentle darkening. Missing states make an interface feel like a photograph of software instead of software. + +--- + +## See the Content + +Read every visible string as a user would. Not checking for typos — checking for truth. + +Does this screen tell one coherent story? Could a real person at a real company be looking at exactly this data right now? Or does the page title belong to one product, the article body to another, and the sidebar metrics to a third? + +Content incoherence breaks the illusion faster than any visual flaw. A beautifully designed interface with nonsensical content is a movie set with no script. + +--- + +## See the Structure + +Open the CSS and find the lies — the places that look right but are held together with tape. + +Negative margins undoing a parent's padding. Calc() values that exist only as workarounds. Absolute positioning to escape layout flow. Each is a shortcut where a clean solution exists. Cards with full-width dividers use flex column and section-level padding. Centered content uses max-width with auto margins. The correct answer is always simpler than the hack. + +--- + +## Again + +Look at your output one final time. + +Ask: "If they said this lacks craft, what would they point to?" + +That thing you just thought of — fix it. Then ask again. + +The first build was the draft. The critique is the design. diff --git a/.agents/skills/interface-design/references/example.md b/.agents/skills/interface-design/references/example.md new file mode 100644 index 0000000..6654906 --- /dev/null +++ b/.agents/skills/interface-design/references/example.md @@ -0,0 +1,86 @@ +# Craft in Action + +This shows how the subtle layering principle translates to real decisions. Learn the thinking, not the code. Your values will differ — the approach won't. + +--- + +## The Subtle Layering Mindset + +Before looking at any example, internalize this: **you should barely notice the system working.** + +When you look at Vercel's dashboard, you don't think "nice borders." You just understand the structure. When you look at Supabase, you don't think "good surface elevation." You just know what's above what. The craft is invisible — that's how you know it's working. + +--- + +## Example: Dashboard with Sidebar and Dropdown + +### The Surface Decisions + +**Why so subtle?** Each elevation jump should be only a few percentage points of lightness. You can barely see the difference in isolation. But when surfaces stack, the hierarchy emerges. This is the Vercel/Supabase way — whisper-quiet shifts that you feel rather than see. + +**What NOT to do:** Don't make dramatic jumps between elevations. That's jarring. Don't use different hues for different levels. Keep the same hue, shift only lightness. + +### The Border Decisions + +**Why rgba, not solid colors?** Low opacity borders blend with their background. A low-opacity white border on a dark surface is barely there — it defines the edge without demanding attention. Solid hex borders look harsh in comparison. + +**The test:** Look at your interface from arm's length. If borders are the first thing you notice, reduce opacity. If you can't find where regions end, increase slightly. + +### The Sidebar Decision + +**Why same background as canvas, not different?** + +Many dashboards make the sidebar a different color. This fragments the visual space — now you have "sidebar world" and "content world." + +Better: Same background, subtle border separation. The sidebar is part of the app, not a separate region. Vercel does this. Supabase does this. The border is enough. + +### The Dropdown Decision + +**Why surface-200, not surface-100?** + +The dropdown floats above the card it emerged from. If both were surface-100, the dropdown would blend into the card — you'd lose the sense of layering. Surface-200 is just light enough to feel "above" without being dramatically different. + +**Why border-overlay instead of border-default?** + +Overlays (dropdowns, popovers) often need slightly more definition because they're floating in space. A touch more border opacity helps them feel contained without being harsh. + +--- + +## Example: Form Controls + +### Input Background Decision + +**Why darker, not lighter?** + +Inputs are "inset" — they receive content, they don't project it. A slightly darker background signals "type here" without needing heavy borders. This is the alternative-background principle. + +### Focus State Decision + +**Why subtle focus states?** + +Focus needs to be visible, but you don't need a glowing ring or dramatic color. A noticeable increase in border opacity is enough for a clear state change. Subtle-but-noticeable — the same principle as surfaces. + +--- + +## Adapt to Context + +Your product might need: +- Warmer hues (slight yellow/orange tint) +- Cooler hues (blue-gray base) +- Different lightness progression +- Light mode (principles invert — higher elevation = shadow, not lightness) + +**The principle is constant:** barely different, still distinguishable. The values adapt to context. + +--- + +## The Craft Check + +Apply the squint test to your work: + +1. Blur your eyes or step back +2. Can you still perceive hierarchy? +3. Is anything jumping out at you? +4. Can you tell where regions begin and end? + +If hierarchy is visible and nothing is harsh — the subtle layering is working. diff --git a/.agents/skills/interface-design/references/principles.md b/.agents/skills/interface-design/references/principles.md new file mode 100644 index 0000000..6c4a502 --- /dev/null +++ b/.agents/skills/interface-design/references/principles.md @@ -0,0 +1,235 @@ +# Core Craft Principles + +These apply regardless of design direction. This is the quality floor. + +--- + +## Surface & Token Architecture + +Professional interfaces don't pick colors randomly — they build systems. Understanding this architecture is the difference between "looks okay" and "feels like a real product." + +### The Primitive Foundation + +Every color in your interface should trace back to a small set of primitives: + +- **Foreground** — text colors (primary, secondary, muted) +- **Background** — surface colors (base, elevated, overlay) +- **Border** — edge colors (default, subtle, strong) +- **Brand** — your primary accent +- **Semantic** — functional colors (destructive, warning, success) + +Don't invent new colors. Map everything to these primitives. + +### Surface Elevation Hierarchy + +Surfaces stack. A dropdown sits above a card which sits above the page. Build a numbered system: + +``` +Level 0: Base background (the app canvas) +Level 1: Cards, panels (same visual plane as base) +Level 2: Dropdowns, popovers (floating above) +Level 3: Nested dropdowns, stacked overlays +Level 4: Highest elevation (rare) +``` + +In dark mode, higher elevation = slightly lighter. In light mode, higher elevation = slightly lighter or uses shadow. The principle: **elevated surfaces need visual distinction from what's beneath them.** + +### The Subtlety Principle + +This is where most interfaces fail. Study Vercel, Supabase, Linear — their surfaces are **barely different** but still distinguishable. Their borders are **light but not invisible**. + +**For surfaces:** The difference between elevation levels should be subtle — a few percentage points of lightness, not dramatic jumps. In dark mode, surface-100 might be 7% lighter than base, surface-200 might be 9%, surface-300 might be 12%. You can barely see it, but you feel it. + +**For borders:** Borders should define regions without demanding attention. Use low opacity (0.05-0.12 alpha for dark mode, slightly higher for light). The border should disappear when you're not looking for it, but be findable when you need to understand the structure. + +**The test:** Squint at your interface. You should still perceive the hierarchy — what's above what, where regions begin and end. But no single border or surface should jump out at you. If borders are the first thing you notice, they're too strong. If you can't find where one region ends and another begins, they're too subtle. + +**Common AI mistakes to avoid:** +- Borders that are too visible (1px solid gray instead of subtle rgba) +- Surface jumps that are too dramatic (going from dark to light instead of dark to slightly-less-dark) +- Using different hues for different surfaces (gray card on blue background) +- Harsh dividers where subtle borders would do + +### Text Hierarchy via Tokens + +Don't just have "text" and "gray text." Build four levels: + +- **Primary** — default text, highest contrast +- **Secondary** — supporting text, slightly muted +- **Tertiary** — metadata, timestamps, less important +- **Muted** — disabled, placeholder, lowest contrast + +Use all four consistently. If you're only using two, your hierarchy is too flat. + +### Border Progression + +Borders aren't binary. Build a scale: + +- **Default** — standard borders +- **Subtle/Muted** — softer separation +- **Strong** — emphasis, hover states +- **Stronger** — maximum emphasis, focus rings + +Match border intensity to the importance of the boundary. + +### Dedicated Control Tokens + +Form controls (inputs, checkboxes, selects) have specific needs. Don't just reuse surface tokens — create dedicated ones: + +- **Control background** — often different from surface backgrounds +- **Control border** — needs to feel interactive +- **Control focus** — clear focus indication + +This separation lets you tune controls independently from layout surfaces. + +### Context-Aware Bases + +Different areas of your app might need different base surfaces: + +- **Marketing pages** — might use darker/richer backgrounds +- **Dashboard/app** — might use neutral working backgrounds +- **Sidebar** — might differ from main canvas + +The surface hierarchy works the same way — it just starts from a different base. + +### Alternative Backgrounds for Depth + +Beyond shadows, use contrasting backgrounds to create depth. An "alternative" or "inset" background makes content feel recessed. Useful for: + +- Empty states in data grids +- Code blocks +- Inset panels +- Visual grouping without borders + +--- + +## Spacing System + +Pick a base unit (4px and 8px are common) and use multiples throughout. The specific number matters less than consistency — every spacing value should be explainable as "X times the base unit." + +Build a scale for different contexts: +- Micro spacing (icon gaps, tight element pairs) +- Component spacing (within buttons, inputs, cards) +- Section spacing (between related groups) +- Major separation (between distinct sections) + +## Symmetrical Padding + +TLBR must match. If top padding is 16px, left/bottom/right must also be 16px. Exception: when content naturally creates visual balance. + +```css +/* Good */ +padding: 16px; +padding: 12px 16px; /* Only when horizontal needs more room */ + +/* Bad */ +padding: 24px 16px 12px 16px; +``` + +## Border Radius Consistency + +Sharper corners feel technical, rounder corners feel friendly. Pick a scale that fits your product's personality and use it consistently. + +The key is having a system: small radius for inputs and buttons, medium for cards, large for modals or containers. Don't mix sharp and soft randomly — inconsistent radius is as jarring as inconsistent spacing. + +## Depth & Elevation Strategy + +Match your depth approach to your design direction. Choose ONE and commit: + +**Borders-only (flat)** — Clean, technical, dense. Works for utility-focused tools where information density matters more than visual lift. Linear, Raycast, and many developer tools use almost no shadows — just subtle borders to define regions. + +**Subtle single shadows** — Soft lift without complexity. A simple `0 1px 3px rgba(0,0,0,0.08)` can be enough. Works for approachable products that want gentle depth. + +**Layered shadows** — Rich, premium, dimensional. Multiple shadow layers create realistic depth. Stripe and Mercury use this approach. Best for cards that need to feel like physical objects. + +**Surface color shifts** — Background tints establish hierarchy without any shadows. A card at `#fff` on a `#f8fafc` background already feels elevated. + +```css +/* Borders-only approach */ +--border: rgba(0, 0, 0, 0.08); +--border-subtle: rgba(0, 0, 0, 0.05); +border: 0.5px solid var(--border); + +/* Single shadow approach */ +--shadow: 0 1px 3px rgba(0, 0, 0, 0.08); + +/* Layered shadow approach */ +--shadow-layered: + 0 0 0 0.5px rgba(0, 0, 0, 0.05), + 0 1px 2px rgba(0, 0, 0, 0.04), + 0 2px 4px rgba(0, 0, 0, 0.03), + 0 4px 8px rgba(0, 0, 0, 0.02); +``` + +## Card Layouts + +Monotonous card layouts are lazy design. A metric card doesn't have to look like a plan card doesn't have to look like a settings card. + +Design each card's internal structure for its specific content — but keep the surface treatment consistent: same border weight, shadow depth, corner radius, padding scale, typography. + +## Isolated Controls + +UI controls deserve container treatment. Date pickers, filters, dropdowns — these should feel like crafted objects. + +**Never use native form elements for styled UI.** Native ``, and similar elements render OS-native dropdowns that cannot be styled. Build custom components instead: + +- Custom select: trigger button + positioned dropdown menu +- Custom date picker: input + calendar popover +- Custom checkbox/radio: styled div with state management + +Custom select triggers must use `display: inline-flex` with `white-space: nowrap` to keep text and chevron icons on the same row. + +## Typography Hierarchy + +Build distinct levels that are visually distinguishable at a glance: + +- **Headlines** — heavier weight, tighter letter-spacing for presence +- **Body** — comfortable weight for readability +- **Labels/UI** — medium weight, works at smaller sizes +- **Data** — often monospace, needs `tabular-nums` for alignment + +Don't rely on size alone. Combine size, weight, and letter-spacing to create clear hierarchy. If you squint and can't tell headline from body, the hierarchy is too weak. + +## Monospace for Data + +Numbers, IDs, codes, timestamps belong in monospace. Use `tabular-nums` for columnar alignment. Mono signals "this is data." + +## Iconography + +Icons clarify, not decorate — if removing an icon loses no meaning, remove it. Choose a consistent icon set and stick with it throughout the product. + +Give standalone icons presence with subtle background containers. Icons next to text should align optically, not mathematically. + +## Animation + +Keep it fast and functional. Micro-interactions (hover, focus) should feel instant — around 150ms. Larger transitions (modals, panels) can be slightly longer — 200-250ms. + +Use smooth deceleration easing (ease-out variants). Avoid spring/bounce effects in professional interfaces — they feel playful, not serious. + +## Contrast Hierarchy + +Build a four-level system: foreground (primary) → secondary → muted → faint. Use all four consistently. + +## Color Carries Meaning + +Gray builds structure. Color communicates — status, action, emphasis, identity. Unmotivated color is noise. Color that reinforces the product's world is character. + +## Navigation Context + +Screens need grounding. A data table floating in space feels like a component demo, not a product. Consider including: + +- **Navigation** — sidebar or top nav showing where you are in the app +- **Location indicator** — breadcrumbs, page title, or active nav state +- **User context** — who's logged in, what workspace/org + +When building sidebars, consider using the same background as the main content area. Rely on a subtle border for separation rather than different background colors. + +## Dark Mode + +Dark interfaces have different needs: + +**Borders over shadows** — Shadows are less visible on dark backgrounds. Lean more on borders for definition. + +**Adjust semantic colors** — Status colors (success, warning, error) often need to be slightly desaturated for dark backgrounds. + +**Same structure, different values** — The hierarchy system still applies, just with inverted values. diff --git a/.agents/skills/interface-design/references/validation.md b/.agents/skills/interface-design/references/validation.md new file mode 100644 index 0000000..7aa4a69 --- /dev/null +++ b/.agents/skills/interface-design/references/validation.md @@ -0,0 +1,48 @@ +# Memory Management + +When and how to update `.interface-design/system.md`. + +## When to Add Patterns + +Add to system.md when: +- Component used 2+ times +- Pattern is reusable across the project +- Has specific measurements worth remembering + +## Pattern Format + +```markdown +### Button Primary +- Height: 36px +- Padding: 12px 16px +- Radius: 6px +- Font: 14px, 500 weight +``` + +## Don't Document + +- One-off components +- Temporary experiments +- Variations better handled with props + +## Pattern Reuse + +Before creating a component, check system.md: +- Pattern exists? Use it. +- Need variation? Extend, don't create new. + +Memory compounds: each pattern saved makes future work faster and more consistent. + +--- + +# Validation Checks + +If system.md defines specific values, check consistency: + +**Spacing** — All values multiples of the defined base? + +**Depth** — Using the declared strategy throughout? (borders-only means no shadows) + +**Colors** — Using defined palette, not random hex codes? + +**Patterns** — Reusing documented patterns instead of creating new? diff --git a/.agents/skills/make-interfaces-feel-better/SKILL.md b/.agents/skills/make-interfaces-feel-better/SKILL.md new file mode 100644 index 0000000..cd19691 --- /dev/null +++ b/.agents/skills/make-interfaces-feel-better/SKILL.md @@ -0,0 +1,148 @@ +--- +name: make-interfaces-feel-better +description: Design engineering principles for making interfaces feel polished. Use when building UI components, reviewing frontend code, implementing animations, hover states, shadows, borders, typography, micro-interactions, enter/exit animations, or any visual detail work. Triggers on UI polish, design details, "make it feel better", "feels off", stagger animations, border radius, optical alignment, font smoothing, tabular numbers, image outlines, box shadows. +--- + +# Details that make interfaces feel better + +Great interfaces rarely come from a single thing. It's usually a collection of small details that compound into a great experience. Apply these principles when building or reviewing UI code. + +## Quick Reference + +| Category | When to Use | +| --- | --- | +| [Typography](typography.md) | Text wrapping, font smoothing, tabular numbers | +| [Surfaces](surfaces.md) | Border radius, optical alignment, shadows, image outlines, hit areas | +| [Animations](animations.md) | Interruptible animations, enter/exit transitions, icon animations, scale on press | +| [Performance](performance.md) | Transition specificity, `will-change` usage | + +## Core Principles + +### 1. Concentric Border Radius + +Outer radius = inner radius + padding. Mismatched radii on nested elements is the most common thing that makes interfaces feel off. + +### 2. Optical Over Geometric Alignment + +When geometric centering looks off, align optically. Buttons with icons, play triangles, and asymmetric icons all need manual adjustment. + +### 3. Shadows Over Borders + +Layer multiple transparent `box-shadow` values for natural depth. Shadows adapt to any background; solid borders don't. + +### 4. Interruptible Animations + +Use CSS transitions for interactive state changes — they can be interrupted mid-animation. Reserve keyframes for staged sequences that run once. + +### 5. Split and Stagger Enter Animations + +Don't animate a single container. Break content into semantic chunks and stagger each with ~100ms delay. + +### 6. Subtle Exit Animations + +Use a small fixed `translateY` instead of full height. Exits should be softer than enters. + +### 7. Contextual Icon Animations + +Animate icons with `opacity`, `scale`, and `blur` instead of toggling visibility. Use exactly these values: scale from `0.25` to `1`, opacity from `0` to `1`, blur from `4px` to `0px`. If the project has `motion` or `framer-motion` in `package.json`, use `transition: { type: "spring", duration: 0.3, bounce: 0 }` — bounce must always be `0`. If no motion library is installed, keep both icons in the DOM (one absolute-positioned) and cross-fade with CSS transitions using `cubic-bezier(0.2, 0, 0, 1)` — this gives both enter and exit animations without any dependency. + +### 8. Font Smoothing + +Apply `-webkit-font-smoothing: antialiased` to the root layout on macOS for crisper text. + +### 9. Tabular Numbers + +Use `font-variant-numeric: tabular-nums` for any dynamically updating numbers to prevent layout shift. + +### 10. Text Wrapping + +Use `text-wrap: balance` on headings. Use `text-wrap: pretty` for body text to avoid orphans. + +### 11. Image Outlines + +Add a subtle `1px` outline with low opacity to images for consistent depth. The color must be pure black in light mode (`rgba(0, 0, 0, 0.1)`) and pure white in dark mode (`rgba(255, 255, 255, 0.1)`) — never a near-black like slate, zinc, or any tinted neutral. A tinted outline picks up the surface color underneath it and reads as dirt on the image edge. + +### 12. Scale on Press + +A subtle `scale(0.96)` on click gives buttons tactile feedback. Always use `0.96`. Never use a value smaller than `0.95` — anything below feels exaggerated. Add a `static` prop to disable it when motion would be distracting. + +### 13. Skip Animation on Page Load + +Use `initial={false}` on `AnimatePresence` to prevent enter animations on first render. Verify it doesn't break intentional entrance animations. + +### 14. Never Use `transition: all` + +Always specify exact properties: `transition-property: scale, opacity`. Tailwind's `transition-transform` covers `transform, translate, scale, rotate`. + +### 15. Use `will-change` Sparingly + +Only for `transform`, `opacity`, `filter` — properties the GPU can composite. Never use `will-change: all`. Only add when you notice first-frame stutter. + +### 16. Minimum Hit Area + +Interactive elements need at least 40×40px hit area. Extend with a pseudo-element if the visible element is smaller. Never let hit areas of two elements overlap. + +## Common Mistakes + +| Mistake | Fix | +| --- | --- | +| Same border radius on parent and child | Calculate `outerRadius = innerRadius + padding` | +| Icons look off-center | Adjust optically with padding or fix SVG directly | +| Hard borders between sections | Use layered `box-shadow` with transparency | +| Jarring enter/exit animations | Split, stagger, and keep exits subtle | +| Numbers cause layout shift | Apply `tabular-nums` | +| Heavy text on macOS | Apply `antialiased` to root | +| Animation plays on page load | Add `initial={false}` to `AnimatePresence` | +| `transition: all` on elements | Specify exact properties | +| First-frame animation stutter | Add `will-change: transform` (sparingly) | +| Tiny hit areas on small controls | Extend with pseudo-element to 40×40px | + +## Review Output Format + +Always present changes as a markdown table with **Before** and **After** columns. Include every change you made — not just a subset. Never list findings as separate "Before:" / "After:" lines outside of a table. Group changes by principle using a heading above each table, and keep each row focused on a single diff so the reader can scan the whole list quickly. + +### Example + +#### Concentric border radius +| Before | After | +| --- | --- | +| `rounded-xl` on card + `rounded-xl` on inner button (`p-2`) | `rounded-2xl` on card (`12 + 8`), `rounded-lg` on inner button | +| `border-radius: 16px` on both nested surfaces | Outer `24px`, inner `16px` with `8px` padding | + +#### Tabular numbers +| Before | After | +| --- | --- | +| `{count}` on animated counter | `{count}` | +| Default numerals on timer | Added `font-variant-numeric: tabular-nums` to root | + +#### Scale on press +| Before | After | +| --- | --- | +| ` + + + ); +} +``` + +### CSS-Only Stagger + +```css +.stagger-item { + opacity: 0; + transform: translateY(12px); + filter: blur(4px); + animation: fadeInUp 400ms ease-out forwards; +} + +.stagger-item:nth-child(1) { animation-delay: 0ms; } +.stagger-item:nth-child(2) { animation-delay: 100ms; } +.stagger-item:nth-child(3) { animation-delay: 200ms; } + +@keyframes fadeInUp { + to { + opacity: 1; + transform: translateY(0); + filter: blur(0); + } +} +``` + +## Exit Animations + +Exit animations should be softer and less attention-grabbing than enter animations. The user's focus is moving to the next thing — don't fight for attention. + +### Subtle Exit (Recommended) + +```tsx +// Small fixed translateY — indicates direction without drama + + {content} + +``` + +### Full Exit (When Context Matters) + +```tsx +// Slide fully out — use when spatial context is important +// (e.g., a card returning to a list, a drawer closing) + + {content} + +``` + +### Good vs. Bad + +```css +/* Good — subtle exit */ +.item-exit { + opacity: 0; + transform: translateY(-12px); + transition: opacity 150ms ease-in, transform 150ms ease-in; +} + +/* Bad — dramatic exit that steals focus */ +.item-exit { + opacity: 0; + transform: translateY(-100%) scale(0.5); + transition: all 400ms ease-in; +} + +/* Bad — no exit animation at all (element just vanishes) */ +.item-exit { + display: none; +} +``` + +**Key points:** +- Use a small fixed `translateY` (e.g., `-12px`) instead of the full container height +- Keep some directional movement to indicate where the element went +- Exit duration should be shorter than enter duration (150ms vs 300ms) +- Don't remove exit animations entirely — subtle motion preserves context + +## Contextual Icon Animations + +When icons appear or disappear contextually (on hover, on state change), animate them with `opacity`, `scale`, and `blur` rather than just toggling visibility. + +### Motion Example + +```tsx +import { AnimatePresence, motion } from "motion/react"; + +function IconButton({ isActive, icon: Icon }) { + return ( + + ); +} +``` + +### CSS Transition Approach (No Motion) + +If the project doesn't use Motion (Framer Motion), keep both icons in the DOM and cross-fade them with CSS transitions. Because neither icon unmounts, both enter and exit animate smoothly. + +The trick: one icon is absolutely positioned on top of the other. Toggling state cross-fades them — the entering icon scales up from `0.25` while the exiting icon scales down to `0.25`, both with opacity and blur. + +```tsx +function IconButton({ isActive, ActiveIcon, InactiveIcon }) { + return ( + + ); +} +``` + +The non-absolute icon (InactiveIcon) defines the layout size. The absolute icon (ActiveIcon) overlays it without affecting flow. + +### Choosing Between Motion and CSS + +| | Motion (Framer Motion) | CSS transitions (both icons in DOM) | +| --- | --- | --- | +| **Enter animation** | Yes | Yes | +| **Exit animation** | Yes (via `AnimatePresence`) | Yes (cross-fade — icon never unmounts) | +| **Spring physics** | Yes | No — use `cubic-bezier(0.2, 0, 0, 1)` as approximation | +| **When to use** | Project already uses `motion/react` | No motion dependency, or keeping bundle small | + +**Rule:** Check the project's `package.json` for `motion` or `framer-motion`. If present, use the Motion approach. If not, use the CSS cross-fade pattern — don't add a dependency just for icon transitions. + +### When to Animate Icons + +| Animate | Don't animate | +| --- | --- | +| Icons that appear on hover (action buttons) | Static navigation icons | +| State change icons (play → pause, like → liked) | Decorative icons | +| Icons in contextual toolbars | Icons that are always visible | +| Loading/success state indicators | Icon labels (text next to icon) | + +**Important:** Always use exactly these values for contextual icon animations — do not deviate: +- `scale`: `0.25` → `1` (never use `0.5` or `0.6`) +- `opacity`: `0` → `1` +- `filter`: `"blur(4px)"` → `"blur(0px)"` +- `transition`: `{ type: "spring", duration: 0.3, bounce: 0 }` — **bounce must always be `0`**, never `0.1` or any other value + +## Scale on Press + +A subtle scale-down on click gives buttons tactile feedback. Always use `scale(0.96)`. Never use a value smaller than `0.95` — anything below feels exaggerated. Use CSS transitions for interruptibility — if the user releases mid-press, it should smoothly return. + +Not every button needs this. Add a `static` prop to your button component that disables the scale effect when the motion would be distracting. + +### CSS Example + +```css +.button { + transition-property: scale; + transition-duration: 150ms; + transition-timing-function: ease-out; +} + +.button:active { + scale: 0.96; +} +``` + +### Tailwind Example + +```tsx + +``` + +### Motion Example + +```tsx + + Click me + +``` + +### Static Prop Pattern + +Extract the scale class into a variable and conditionally apply it based on a `static` prop: + +```tsx +const tapScale = "active:not-disabled:scale-[0.96]"; + +function Button({ static: isStatic, className, children, ...props }) { + return ( + + ); +} + +// Usage + {/* scales on press */} + {/* no scale */} +``` + +## Skip Animation on Page Load + +Use `initial={false}` on `AnimatePresence` to prevent enter animations from firing on first render. Elements that are already in their default state shouldn't animate in on page load — only on subsequent state changes. + +### When It Works + +```tsx +// Good — icon doesn't animate in on mount, only on state change + + + + + +``` + +Works well for: icon swaps, toggles, tabs, segmented controls — anything that has a default state on page load. + +### When It Breaks + +Don't use `initial={false}` when the component relies on its `initial` prop to set up a first-time enter animation, like a staggered page hero or a loading state. In those cases, removing the initial animation skips the entire entrance. + +```tsx +// Bad — initial={false} would skip the staggered page enter entirely + + + ... + + +``` + +Verify the component still looks right on a full page refresh before applying this. diff --git a/.agents/skills/make-interfaces-feel-better/performance.md b/.agents/skills/make-interfaces-feel-better/performance.md new file mode 100644 index 0000000..1559549 --- /dev/null +++ b/.agents/skills/make-interfaces-feel-better/performance.md @@ -0,0 +1,88 @@ +# Performance + +Transition specificity and GPU compositing hints. + +## Transition Only What Changes + +Never use `transition: all` or Tailwind's `transition` shorthand (which maps to `transition-property: all`). Always specify the exact properties that change. + +### Why + +- `transition: all` forces the browser to watch every property for changes +- Causes unexpected transitions on properties you didn't intend to animate (colors, padding, shadows) +- Prevents browser optimizations + +### CSS Example + +```css +/* Good — only transition what changes */ +.button { + transition-property: scale, background-color; + transition-duration: 150ms; + transition-timing-function: ease-out; +} + +/* Bad — transition everything */ +.button { + transition: all 150ms ease-out; +} +``` + +### Tailwind + +```tsx +// Good — explicit properties + +``` + +### Play Button Triangles + +Play icons are triangular and their geometric center is not their visual center. Shift slightly right: + +```css +/* Good — optically centered */ +.play-button svg { + margin-left: 2px; /* shift right to account for triangle shape */ +} + +/* Bad — geometrically centered but looks off */ +.play-button svg { + /* no adjustment */ +} +``` + +### Asymmetric Icons (Stars, Arrows, Carets) + +Some icons have uneven visual weight. The best fix is adjusting the SVG directly so no extra margin/padding is needed in the component code. + +```tsx +// Best — fix in the SVG itself +// Adjust the viewBox or path to visually center the icon + +// Fallback — adjust with margin + + + +``` + +## Shadows Instead of Borders + +For **buttons, cards, and containers** that use a border for depth or elevation, prefer replacing it with a subtle `box-shadow`. Shadows adapt to any background since they use transparency; solid borders don't. This also helps when using images or multiple colors as backgrounds — solid border colors don't work well on backgrounds other than the ones they were designed for. + +**Do not apply this to dividers** (`border-b`, `border-t`, side borders) or any border whose purpose is layout separation rather than element depth. Those should stay as borders. + +### Shadow as Border (Light Mode) + +The shadow is comprised of three layers. The first acts as a 1px border ring, the second adds subtle lift, and the third provides ambient depth: + +```css +:root { + --shadow-border: + 0px 0px 0px 1px rgba(0, 0, 0, 0.06), + 0px 1px 2px -1px rgba(0, 0, 0, 0.06), + 0px 2px 4px 0px rgba(0, 0, 0, 0.04); + --shadow-border-hover: + 0px 0px 0px 1px rgba(0, 0, 0, 0.08), + 0px 1px 2px -1px rgba(0, 0, 0, 0.08), + 0px 2px 4px 0px rgba(0, 0, 0, 0.06); +} +``` + +### Shadow as Border (Dark Mode) + +In dark mode, simplify to a single white ring — layered depth shadows aren't visible on dark backgrounds: + +```css +/* Dark mode — adapt to whatever setup the project uses + (prefers-color-scheme, class, data attribute, etc.) */ +--shadow-border: 0 0 0 1px rgba(255, 255, 255, 0.08); +--shadow-border-hover: 0 0 0 1px rgba(255, 255, 255, 0.13); +``` + +### Usage with Hover Transition + +Apply the variable and add `transition-[box-shadow]` for a smooth hover: + +```css +.card { + box-shadow: var(--shadow-border); + transition-property: box-shadow; + transition-duration: 150ms; + transition-timing-function: ease-out; +} + +.card:hover { + box-shadow: var(--shadow-border-hover); +} +``` + +### When to Use Shadows vs. Borders + +| Use shadows | Use borders | +| --- | --- | +| Cards, containers with depth | Dividers between list items | +| Buttons with bordered styles | Table cell boundaries | +| Elevated elements (dropdowns, modals) | Form input outlines (for accessibility) | +| Elements on varied backgrounds | Hairline separators in dense UI | +| Hover/focus states for lift effect | | + +## Image Outlines + +Add a subtle `1px` outline with low opacity to images. This creates consistent depth, especially in design systems where other elements use borders or shadows. + +### Color rules (non-negotiable) + +- **Light mode**: pure black — `rgba(0, 0, 0, 0.1)`. Exact values: R=0, G=0, B=0. +- **Dark mode**: pure white — `rgba(255, 255, 255, 0.1)`. Exact values: R=255, G=255, B=255. +- Never use a near-black or near-white from the project palette (e.g. slate-900, zinc-900, `#0a0a0a`, `#111827`, `#f5f5f7`). Tinted outlines pick up the surrounding surface color and read as dirt on the image edge. +- Never match the outline to the project's accent or ink color. The outline is a neutral separator, not a themed element. + +### Light Mode + +```css +img { + outline: 1px solid rgba(0, 0, 0, 0.1); + outline-offset: -1px; /* inset so it doesn't add to layout */ +} +``` + +### Dark Mode + +```css +img { + outline: 1px solid rgba(255, 255, 255, 0.1); + outline-offset: -1px; +} +``` + +### Tailwind with Dark Mode + +```tsx +{alt} +``` + +Use `outline-black/10` and `outline-white/10` specifically — not `outline-slate-*`, `outline-zinc-*`, `outline-neutral-*`, or any tinted scale. + +**Why outline instead of border?** `outline` doesn't affect layout (no added width/height), and `outline-offset: -1px` keeps it inset so images stay their intended size. + +## Minimum Hit Area + +Interactive elements should have a minimum hit area of 44×44px (WCAG) or at least 40×40px. If the visible element is smaller (e.g., a 20×20 checkbox), extend the hit area with a pseudo-element. + +### CSS Example + +```css +/* Small checkbox with expanded hit area */ +.checkbox { + position: relative; + width: 20px; + height: 20px; +} + +.checkbox::after { + content: ""; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 40px; + height: 40px; +} +``` + +### Tailwind Example + +```tsx + +``` + +### Collision Rule + +If the extended hit area overlaps another interactive element, shrink the pseudo-element — but make it as large as possible without colliding. Two interactive elements should never have overlapping hit areas. diff --git a/.agents/skills/make-interfaces-feel-better/typography.md b/.agents/skills/make-interfaces-feel-better/typography.md new file mode 100644 index 0000000..6ca7ac7 --- /dev/null +++ b/.agents/skills/make-interfaces-feel-better/typography.md @@ -0,0 +1,135 @@ +# Typography + +Typography rendering details that make interfaces feel better. + +## Text Wrapping + +### text-wrap: balance + +Distributes text evenly across lines, preventing orphaned words on headings and short text blocks. **Only works on blocks of 6 lines or fewer** (Chromium) or 10 lines or fewer (Firefox) — the balancing algorithm is computationally expensive, so browsers limit it to short text. + +```css +/* Good — even line lengths on short text */ +h1, h2, h3 { + text-wrap: balance; +} +``` + +```css +/* Bad — default wrapping leaves orphans */ +h1 { + /* no text-wrap rule → "Read our + blog" instead of balanced lines */ +} +``` + +```css +/* Bad — balance on long paragraphs (silently ignored, wastes intent) */ +.article-body p { + text-wrap: balance; +} +``` + +**Tailwind:** `text-balance` + +### text-wrap: pretty + +Prevents orphaned words (a single word dangling on the last line) by adjusting line breaks throughout the paragraph. Unlike `balance`, it doesn't try to equalize line lengths — it just ensures the last line isn't embarrassingly short. Works on text of any length with no line-count limit. + +This should be your **default for short-to-medium text** — paragraphs, descriptions, captions, list items, card text. For very long text (10+ lines), skip both `pretty` and `balance` — the browser's default wrapping is fine and you avoid unnecessary layout cost. + +```css +/* Good — descriptions, captions, short paragraphs */ +p, li, figcaption, blockquote { + text-wrap: pretty; +} +``` + +```tsx +// Tailwind +

+ A short paragraph that won't leave an orphan on the last line. +

+``` + +**Tailwind:** `text-pretty` + +### When to Use Which + +| Scenario | Use | +| --- | --- | +| Headings, titles where even distribution matters | `text-wrap: balance` | +| Short-to-medium text — paragraphs, descriptions, captions, UI text | `text-wrap: pretty` | +| Long text (10+ lines), code blocks, pre-formatted text | Neither — leave default | + +## Font Smoothing (macOS) + +On macOS, text renders heavier than intended by default. Apply antialiased smoothing to the root layout so all text renders crisper and thinner. + +```css +/* CSS */ +html { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +``` + +```tsx +// Tailwind — apply to root layout + +``` + +### Good vs. Bad + +```css +/* Good — applied once at the root */ +html { + -webkit-font-smoothing: antialiased; +} + +/* Bad — applied per-element, inconsistent */ +.heading { + -webkit-font-smoothing: antialiased; +} +.body { + /* no smoothing → heavier than heading */ +} +``` + +**Note:** This only affects macOS rendering. Other platforms ignore these properties, so it's safe to apply universally. + +## Tabular Numbers + +When numbers update dynamically (counters, prices, timers, table columns), use tabular-nums to make all digits equal width. This prevents layout shift as values change. + +```css +/* CSS */ +.counter { + font-variant-numeric: tabular-nums; +} +``` + +```tsx +// Tailwind +{count} +``` + +### When to Use + +| Use tabular-nums | Don't use tabular-nums | +| --- | --- | +| Counters and timers | Static display numbers | +| Prices that update | Decorative large numbers | +| Table columns with numbers | Phone numbers, zip codes | +| Animated number transitions | Version numbers (v2.1.0) | +| Scoreboards, dashboards | | + +### Caveat + +Some fonts (like Inter) change the visual appearance of numerals with this property — specifically, the digit `1` becomes wider and centered. This is expected behavior and usually desirable for alignment, but verify it looks right in your specific font. + +```css +/* With Inter font: + Default: 1234 → proportional, "1" is narrow + Tabular: 1234 → all digits equal width, "1" centered */ +``` diff --git a/.agents/skills/shadcn/SKILL.md b/.agents/skills/shadcn/SKILL.md new file mode 100644 index 0000000..016f824 --- /dev/null +++ b/.agents/skills/shadcn/SKILL.md @@ -0,0 +1,260 @@ +--- +name: shadcn +description: Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI. Provides project context, component docs, and usage examples. Applies when working with shadcn/ui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for "shadcn init", "create an app with --preset", or "switch to --preset". +user-invocable: false +allowed-tools: Bash(npx shadcn@latest *), Bash(pnpm dlx shadcn@latest *), Bash(bunx --bun shadcn@latest *) +--- + +# shadcn/ui + +A framework for building ui, components and design systems. Components are added as source code to the user's project via the CLI. + +> **IMPORTANT:** Run all CLI commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest` — based on the project's `packageManager`. Examples below use `npx shadcn@latest` but substitute the correct runner for the project. + +## Current Project Context + +```json +!`npx shadcn@latest info --json` +``` + +The JSON above contains the project config and installed components. Use `npx shadcn@latest docs ` to get documentation and example URLs for any component. + +## Principles + +1. **Use existing components first.** Use `npx shadcn@latest search` to check registries before writing custom UI. Check community registries too. +2. **Compose, don't reinvent.** Settings page = Tabs + Card + form controls. Dashboard = Sidebar + Card + Chart + Table. +3. **Use built-in variants before custom styles.** `variant="outline"`, `size="sm"`, etc. +4. **Use semantic colors.** `bg-primary`, `text-muted-foreground` — never raw values like `bg-blue-500`. + +## Critical Rules + +These rules are **always enforced**. Each links to a file with Incorrect/Correct code pairs. + +### Styling & Tailwind → [styling.md](./rules/styling.md) + +- **`className` for layout, not styling.** Never override component colors or typography. +- **No `space-x-*` or `space-y-*`.** Use `flex` with `gap-*`. For vertical stacks, `flex flex-col gap-*`. +- **Use `size-*` when width and height are equal.** `size-10` not `w-10 h-10`. +- **Use `truncate` shorthand.** Not `overflow-hidden text-ellipsis whitespace-nowrap`. +- **No manual `dark:` color overrides.** Use semantic tokens (`bg-background`, `text-muted-foreground`). +- **Use `cn()` for conditional classes.** Don't write manual template literal ternaries. +- **No manual `z-index` on overlay components.** Dialog, Sheet, Popover, etc. handle their own stacking. + +### Forms & Inputs → [forms.md](./rules/forms.md) + +- **Forms use `FieldGroup` + `Field`.** Never use raw `div` with `space-y-*` or `grid gap-*` for form layout. +- **`InputGroup` uses `InputGroupInput`/`InputGroupTextarea`.** Never raw `Input`/`Textarea` inside `InputGroup`. +- **Buttons inside inputs use `InputGroup` + `InputGroupAddon`.** +- **Option sets (2–7 choices) use `ToggleGroup`.** Don't loop `Button` with manual active state. +- **`FieldSet` + `FieldLegend` for grouping related checkboxes/radios.** Don't use a `div` with a heading. +- **Field validation uses `data-invalid` + `aria-invalid`.** `data-invalid` on `Field`, `aria-invalid` on the control. For disabled: `data-disabled` on `Field`, `disabled` on the control. + +### Component Structure → [composition.md](./rules/composition.md) + +- **Items always inside their Group.** `SelectItem` → `SelectGroup`. `DropdownMenuItem` → `DropdownMenuGroup`. `CommandItem` → `CommandGroup`. +- **Use `asChild` (radix) or `render` (base) for custom triggers.** Check `base` field from `npx shadcn@latest info`. → [base-vs-radix.md](./rules/base-vs-radix.md) +- **Dialog, Sheet, and Drawer always need a Title.** `DialogTitle`, `SheetTitle`, `DrawerTitle` required for accessibility. Use `className="sr-only"` if visually hidden. +- **Use full Card composition.** `CardHeader`/`CardTitle`/`CardDescription`/`CardContent`/`CardFooter`. Don't dump everything in `CardContent`. +- **Button has no `isPending`/`isLoading`.** Compose with `Spinner` + `data-icon` + `disabled`. +- **`TabsTrigger` must be inside `TabsList`.** Never render triggers directly in `Tabs`. +- **`Avatar` always needs `AvatarFallback`.** For when the image fails to load. + +### Use Components, Not Custom Markup → [composition.md](./rules/composition.md) + +- **Use existing components before custom markup.** Check if a component exists before writing a styled `div`. +- **Callouts use `Alert`.** Don't build custom styled divs. +- **Empty states use `Empty`.** Don't build custom empty state markup. +- **Toast via `sonner`.** Use `toast()` from `sonner`. +- **Use `Separator`** instead of `
` or `
`. +- **Use `Skeleton`** for loading placeholders. No custom `animate-pulse` divs. +- **Use `Badge`** instead of custom styled spans. + +### Icons → [icons.md](./rules/icons.md) + +- **Icons in `Button` use `data-icon`.** `data-icon="inline-start"` or `data-icon="inline-end"` on the icon. +- **No sizing classes on icons inside components.** Components handle icon sizing via CSS. No `size-4` or `w-4 h-4`. +- **Pass icons as objects, not string keys.** `icon={CheckIcon}`, not a string lookup. + +### CLI + +- **Never decode preset codes or build preset URLs manually.** Use `npx shadcn@latest preset decode `, `preset url `, or `preset open `. For project-aware preset detection, use `npx shadcn@latest preset resolve`. +- **Apply preset codes directly with the CLI.** Use `npx shadcn@latest apply ` for existing projects, or `npx shadcn@latest init --preset ` when initializing. + +## Key Patterns + +These are the most common patterns that differentiate correct shadcn/ui code. For edge cases, see the linked rule files above. + +```tsx +// Form layout: FieldGroup + Field, not div + Label. + + + Email + + + + +// Validation: data-invalid on Field, aria-invalid on the control. + + Email + + Invalid email. + + +// Icons in buttons: data-icon, no sizing classes. + + +// Spacing: gap-*, not space-y-*. +
// correct +
// wrong + +// Equal dimensions: size-*, not w-* h-*. + // correct + // wrong + +// Status colors: Badge variants or semantic tokens, not raw colors. ++20.1% // correct ++20.1% // wrong +``` + +## Component Selection + +| Need | Use | +| -------------------------- | --------------------------------------------------------------------------------------------------- | +| Button/action | `Button` with appropriate variant | +| Form inputs | `Input`, `Select`, `Combobox`, `Switch`, `Checkbox`, `RadioGroup`, `Textarea`, `InputOTP`, `Slider` | +| Toggle between 2–5 options | `ToggleGroup` + `ToggleGroupItem` | +| Data display | `Table`, `Card`, `Badge`, `Avatar` | +| Navigation | `Sidebar`, `NavigationMenu`, `Breadcrumb`, `Tabs`, `Pagination` | +| Overlays | `Dialog` (modal), `Sheet` (side panel), `Drawer` (bottom sheet), `AlertDialog` (confirmation) | +| Feedback | `sonner` (toast), `Alert`, `Progress`, `Skeleton`, `Spinner` | +| Command palette | `Command` inside `Dialog` | +| Charts | `Chart` (wraps Recharts) | +| Layout | `Card`, `Separator`, `Resizable`, `ScrollArea`, `Accordion`, `Collapsible` | +| Empty states | `Empty` | +| Menus | `DropdownMenu`, `ContextMenu`, `Menubar` | +| Tooltips/info | `Tooltip`, `HoverCard`, `Popover` | + +## Key Fields + +The injected project context contains these key fields: + +- **`aliases`** → use the actual alias prefix for imports (e.g. `@/`, `~/`), never hardcode. +- **`isRSC`** → when `true`, components using `useState`, `useEffect`, event handlers, or browser APIs need `"use client"` at the top of the file. Always reference this field when advising on the directive. +- **`tailwindVersion`** → `"v4"` uses `@theme inline` blocks; `"v3"` uses `tailwind.config.js`. +- **`tailwindCssFile`** → the global CSS file where custom CSS variables are defined. Always edit this file, never create a new one. +- **`style`** → component visual treatment (e.g. `nova`, `vega`). +- **`base`** → primitive library (`radix` or `base`). Affects component APIs and available props. +- **`iconLibrary`** → determines icon imports. Use `lucide-react` for `lucide`, `@tabler/icons-react` for `tabler`, etc. Never assume `lucide-react`. +- **`resolvedPaths`** → exact file-system destinations for components, utils, hooks, etc. +- **`framework`** → routing and file conventions (e.g. Next.js App Router vs Vite SPA). +- **`packageManager`** → use this for any non-shadcn dependency installs (e.g. `pnpm add date-fns` vs `npm install date-fns`). +- **`preset`** → resolved preset code and values for the current project. Use `npx shadcn@latest preset resolve --json` when you only need preset information. + +See [cli.md — `info` command](./cli.md) for the full field reference. + +## Component Docs, Examples, and Usage + +Run `npx shadcn@latest docs ` to get the URLs for a component's documentation, examples, and API reference. Fetch these URLs to get the actual content. + +```bash +npx shadcn@latest docs button dialog select +``` + +**When creating, fixing, debugging, or using a component, always run `npx shadcn@latest docs` and fetch the URLs first.** This ensures you're working with the correct API and usage patterns rather than guessing. + +## Workflow + +1. **Get project context** — already injected above. Run `npx shadcn@latest info` again if you need to refresh. +2. **Check installed components first** — before running `add`, always check the `components` list from project context or list the `resolvedPaths.ui` directory. Don't import components that haven't been added, and don't re-add ones already installed. +3. **Find components** — `npx shadcn@latest search`. +4. **Get docs and examples** — run `npx shadcn@latest docs ` to get URLs, then fetch them. Use `npx shadcn@latest view` to browse registry items you haven't installed. To preview changes to installed components, use `npx shadcn@latest add --diff`. +5. **Install or update** — `npx shadcn@latest add`. When updating existing components, use `--dry-run` and `--diff` to preview changes first (see [Updating Components](#updating-components) below). +6. **Fix imports in third-party components** — After adding components from community registries (e.g. `@bundui`, `@magicui`), check the added non-UI files for hardcoded import paths like `@/components/ui/...`. These won't match the project's actual aliases. Use `npx shadcn@latest info` to get the correct `ui` alias (e.g. `@workspace/ui/components`) and rewrite the imports accordingly. The CLI rewrites imports for its own UI files, but third-party registry components may use default paths that don't match the project. +7. **Review added components** — After adding a component or block from any registry, **always read the added files and verify they are correct**. Check for missing sub-components (e.g. `SelectItem` without `SelectGroup`), missing imports, incorrect composition, or violations of the [Critical Rules](#critical-rules). Also replace any icon imports with the project's `iconLibrary` from the project context (e.g. if the registry item uses `lucide-react` but the project uses `hugeicons`, swap the imports and icon names accordingly). Fix all issues before moving on. +8. **Registry must be explicit** — When the user asks to add a block or component, **do not guess the registry**. If no registry is specified (e.g. user says "add a login block" without specifying `@shadcn`, `@tailark`, etc.), ask which registry to use. Never default to a registry on behalf of the user. +9. **Switching presets** — Ask the user first: **overwrite**, **partial**, **merge**, or **skip**? + - **Inspect current preset**: `npx shadcn@latest preset resolve`. Use `--json` when you need structured values. + - **Inspect incoming preset**: `npx shadcn@latest preset decode `. Use `preset url ` or `preset open ` to share or open the preset builder. + - **Overwrite**: `npx shadcn@latest apply `. Overwrites detected components, fonts, and CSS variables. + - **Partial**: `npx shadcn@latest apply --only theme,font`. Updates only the selected preset parts without reinstalling UI components. Supported values are `theme` and `font`; comma-separated combinations are allowed. `icon` is intentionally not supported, because icon changes may require full component reinstall and transforms. + - **Merge**: `npx shadcn@latest init --preset --force --no-reinstall`, then run `npx shadcn@latest info` to list installed components, then for each installed component use `--dry-run` and `--diff` to [smart merge](#updating-components) it individually. + - **Skip**: `npx shadcn@latest init --preset --force --no-reinstall`. Only updates config and CSS, leaves components as-is. + - **Important**: Always run preset commands inside the user's project directory. `apply` only works in an existing project with a `components.json` file. The CLI automatically preserves the current base (`base` vs `radix`) from `components.json`. If you must use a scratch/temp directory (e.g. for `--dry-run` comparisons), pass `--base ` explicitly — preset codes do not encode the base. + +## Updating Components + +When the user asks to update a component from upstream while keeping their local changes, use `--dry-run` and `--diff` to intelligently merge. **NEVER fetch raw files from GitHub manually — always use the CLI.** + +1. Run `npx shadcn@latest add --dry-run` to see all files that would be affected. +2. For each file, run `npx shadcn@latest add --diff ` to see what changed upstream vs local. +3. Decide per file based on the diff: + - No local changes → safe to overwrite. + - Has local changes → read the local file, analyze the diff, and apply upstream updates while preserving local modifications. + - User says "just update everything" → use `--overwrite`, but confirm first. +4. **Never use `--overwrite` without the user's explicit approval.** + +## Quick Reference + +```bash +# Create a new project. +npx shadcn@latest init --name my-app --preset base-nova +npx shadcn@latest init --name my-app --preset a2r6bw --template vite + +# Create a monorepo project. +npx shadcn@latest init --name my-app --preset base-nova --monorepo +npx shadcn@latest init --name my-app --preset base-nova --template next --monorepo + +# Initialize existing project. +npx shadcn@latest init --preset base-nova +npx shadcn@latest init --defaults # shortcut: --template=next --preset=nova (base style implied) + +# Apply a preset to an existing project. +npx shadcn@latest apply a2r6bw +npx shadcn@latest apply a2r6bw --only theme +npx shadcn@latest apply a2r6bw --only font +npx shadcn@latest apply a2r6bw --only theme,font + +# Inspect preset codes and project preset state. +npx shadcn@latest preset decode a2r6bw +npx shadcn@latest preset url a2r6bw +npx shadcn@latest preset open a2r6bw +npx shadcn@latest preset resolve +npx shadcn@latest preset resolve --json + +# Add components. +npx shadcn@latest add button card dialog +npx shadcn@latest add @magicui/shimmer-button +npx shadcn@latest add --all + +# Preview changes before adding/updating. +npx shadcn@latest add button --dry-run +npx shadcn@latest add button --diff button.tsx +npx shadcn@latest add @acme/form --view button.tsx + +# Search registries. +npx shadcn@latest search @shadcn -q "sidebar" +npx shadcn@latest search @tailark -q "stats" + +# Get component docs and example URLs. +npx shadcn@latest docs button dialog select + +# View registry item details (for items not yet installed). +npx shadcn@latest view @shadcn/button +``` + +**Named presets:** `nova`, `vega`, `maia`, `lyra`, `mira`, `luma` +**Templates:** `next`, `vite`, `start`, `react-router`, `astro` (all support `--monorepo`) and `laravel` (not supported for monorepo) +**Preset codes:** Version-prefixed base62 strings (e.g. `a2r6bw` or `b0`), from [ui.shadcn.com](https://ui.shadcn.com). + +## Detailed References + +- [rules/forms.md](./rules/forms.md) — FieldGroup, Field, InputGroup, ToggleGroup, FieldSet, validation states +- [rules/composition.md](./rules/composition.md) — Groups, overlays, Card, Tabs, Avatar, Alert, Empty, Toast, Separator, Skeleton, Badge, Button loading +- [rules/icons.md](./rules/icons.md) — data-icon, icon sizing, passing icons as objects +- [rules/styling.md](./rules/styling.md) — Semantic colors, variants, className, spacing, size, truncate, dark mode, cn(), z-index +- [rules/base-vs-radix.md](./rules/base-vs-radix.md) — asChild vs render, Select, ToggleGroup, Slider, Accordion +- [cli.md](./cli.md) — Commands, flags, presets, templates +- [customization.md](./customization.md) — Theming, CSS variables, extending components diff --git a/.agents/skills/shadcn/agents/openai.yml b/.agents/skills/shadcn/agents/openai.yml new file mode 100644 index 0000000..ab636da --- /dev/null +++ b/.agents/skills/shadcn/agents/openai.yml @@ -0,0 +1,5 @@ +interface: + display_name: "shadcn/ui" + short_description: "Manages shadcn/ui components — adding, searching, fixing, debugging, styling, and composing UI." + icon_small: "./assets/shadcn-small.png" + icon_large: "./assets/shadcn.png" diff --git a/.agents/skills/shadcn/assets/shadcn-small.png b/.agents/skills/shadcn/assets/shadcn-small.png new file mode 100644 index 0000000..547154b Binary files /dev/null and b/.agents/skills/shadcn/assets/shadcn-small.png differ diff --git a/.agents/skills/shadcn/assets/shadcn.png b/.agents/skills/shadcn/assets/shadcn.png new file mode 100644 index 0000000..b7b6814 Binary files /dev/null and b/.agents/skills/shadcn/assets/shadcn.png differ diff --git a/.agents/skills/shadcn/cli.md b/.agents/skills/shadcn/cli.md new file mode 100644 index 0000000..c3a0f0a --- /dev/null +++ b/.agents/skills/shadcn/cli.md @@ -0,0 +1,276 @@ +# shadcn CLI Reference + +Configuration is read from `components.json`. + +> **IMPORTANT:** Always run commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest`. Check `packageManager` from project context to choose the right one. Examples below use `npx shadcn@latest` but substitute the correct runner for the project. + +> **IMPORTANT:** Only use the flags documented below. Do not invent or guess flags — if a flag isn't listed here, it doesn't exist. The CLI auto-detects the package manager from the project's lockfile; there is no `--package-manager` flag. + +## Contents + +- Commands: init, apply, add (dry-run, smart merge), search, view, docs, info, build +- Templates: next, vite, start, react-router, astro +- Presets: named, code, URL formats and fields +- Switching presets + +--- + +## Commands + +### `init` — Initialize or create a project + +```bash +npx shadcn@latest init [components...] [options] +``` + +Initializes shadcn/ui in an existing project or creates a new project (when `--name` is provided). Optionally installs components in the same step. + +| Flag | Short | Description | Default | +| ----------------------- | ----- | --------------------------------------------------------- | ------- | +| `--template