From c4865a6d20e4d528090dca38d977d5d44cb5dd85 Mon Sep 17 00:00:00 2001 From: Dmytro Stanchiev Date: Tue, 7 Apr 2026 23:17:14 -0400 Subject: [PATCH] feat: install ui skills Signed-off-by: Dmytro Stanchiev --- .../skills/frontend-design/.openskills.json | 6 + .claude/skills/frontend-design/LICENSE.txt | 177 +++++ .claude/skills/frontend-design/SKILL.md | 277 ++++++++ .../frontend-ui-dark-ts/.openskills.json | 6 + .claude/skills/frontend-ui-dark-ts/SKILL.md | 594 ++++++++++++++++ .../skills/react-ui-patterns/.openskills.json | 6 + .claude/skills/react-ui-patterns/SKILL.md | 295 ++++++++ .../skills/responsive-design/.openskills.json | 6 + .claude/skills/responsive-design/SKILL.md | 516 ++++++++++++++ .../references/breakpoint-strategies.md | 591 ++++++++++++++++ .../references/container-queries.md | 564 +++++++++++++++ .../references/fluid-layouts.md | 538 ++++++++++++++ .../tailwind-design-system/.openskills.json | 6 + .../skills/tailwind-design-system/SKILL.md | 36 + .../resources/implementation-playbook.md | 665 ++++++++++++++++++ .../skills/ui-ux-designer/.openskills.json | 6 + .claude/skills/ui-ux-designer/SKILL.md | 207 ++++++ .claude/skills/ui-ux-pro-max/.openskills.json | 6 + .claude/skills/ui-ux-pro-max/SKILL.md | 356 ++++++++++ .claude/skills/ui-ux-pro-max/data/charts.csv | 26 + .claude/skills/ui-ux-pro-max/data/colors.csv | 97 +++ .claude/skills/ui-ux-pro-max/data/icons.csv | 101 +++ .claude/skills/ui-ux-pro-max/data/landing.csv | 31 + .../skills/ui-ux-pro-max/data/products.csv | 97 +++ .claude/skills/ui-ux-pro-max/data/prompts.csv | 24 + .../ui-ux-pro-max/data/react-performance.csv | 45 ++ .../ui-ux-pro-max/data/stacks/flutter.csv | 53 ++ .../data/stacks/html-tailwind.csv | 56 ++ .../ui-ux-pro-max/data/stacks/nextjs.csv | 53 ++ .../ui-ux-pro-max/data/stacks/nuxt-ui.csv | 51 ++ .../ui-ux-pro-max/data/stacks/nuxtjs.csv | 59 ++ .../data/stacks/react-native.csv | 52 ++ .../ui-ux-pro-max/data/stacks/react.csv | 54 ++ .../ui-ux-pro-max/data/stacks/shadcn.csv | 61 ++ .../ui-ux-pro-max/data/stacks/svelte.csv | 54 ++ .../ui-ux-pro-max/data/stacks/swiftui.csv | 51 ++ .../skills/ui-ux-pro-max/data/stacks/vue.csv | 50 ++ .claude/skills/ui-ux-pro-max/data/styles.csv | 59 ++ .../skills/ui-ux-pro-max/data/typography.csv | 58 ++ .../ui-ux-pro-max/data/ui-reasoning.csv | 101 +++ .../ui-ux-pro-max/data/ux-guidelines.csv | 100 +++ .../ui-ux-pro-max/data/web-interface.csv | 31 + .claude/skills/ui-ux-pro-max/scripts/core.py | 257 +++++++ .../ui-ux-pro-max/scripts/design_system.py | 487 +++++++++++++ .../skills/ui-ux-pro-max/scripts/search.py | 76 ++ .../skills/uxui-principles/.openskills.json | 6 + .claude/skills/uxui-principles/SKILL.md | 48 ++ .../.openskills.json | 6 + .../skills/visual-design-foundations/SKILL.md | 318 +++++++++ .../references/color-systems.md | 417 +++++++++++ .../references/spacing-iconography.md | 425 +++++++++++ .../references/typography-systems.md | 432 ++++++++++++ .../web-component-design/.openskills.json | 6 + .claude/skills/web-component-design/SKILL.md | 271 +++++++ .../references/accessibility-patterns.md | 642 +++++++++++++++++ .../references/component-patterns.md | 437 ++++++++++++ .../references/css-styling-approaches.md | 590 ++++++++++++++++ .../web-design-guidelines/.openskills.json | 6 + .claude/skills/web-design-guidelines/SKILL.md | 41 ++ 59 files changed, 10687 insertions(+) create mode 100644 .claude/skills/frontend-design/.openskills.json create mode 100644 .claude/skills/frontend-design/LICENSE.txt create mode 100644 .claude/skills/frontend-design/SKILL.md create mode 100644 .claude/skills/frontend-ui-dark-ts/.openskills.json create mode 100644 .claude/skills/frontend-ui-dark-ts/SKILL.md create mode 100644 .claude/skills/react-ui-patterns/.openskills.json create mode 100644 .claude/skills/react-ui-patterns/SKILL.md create mode 100644 .claude/skills/responsive-design/.openskills.json create mode 100644 .claude/skills/responsive-design/SKILL.md create mode 100644 .claude/skills/responsive-design/references/breakpoint-strategies.md create mode 100644 .claude/skills/responsive-design/references/container-queries.md create mode 100644 .claude/skills/responsive-design/references/fluid-layouts.md create mode 100644 .claude/skills/tailwind-design-system/.openskills.json create mode 100644 .claude/skills/tailwind-design-system/SKILL.md create mode 100644 .claude/skills/tailwind-design-system/resources/implementation-playbook.md create mode 100644 .claude/skills/ui-ux-designer/.openskills.json create mode 100644 .claude/skills/ui-ux-designer/SKILL.md create mode 100644 .claude/skills/ui-ux-pro-max/.openskills.json create mode 100644 .claude/skills/ui-ux-pro-max/SKILL.md create mode 100644 .claude/skills/ui-ux-pro-max/data/charts.csv create mode 100644 .claude/skills/ui-ux-pro-max/data/colors.csv create mode 100644 .claude/skills/ui-ux-pro-max/data/icons.csv create mode 100644 .claude/skills/ui-ux-pro-max/data/landing.csv create mode 100644 .claude/skills/ui-ux-pro-max/data/products.csv create mode 100644 .claude/skills/ui-ux-pro-max/data/prompts.csv create mode 100644 .claude/skills/ui-ux-pro-max/data/react-performance.csv create mode 100644 .claude/skills/ui-ux-pro-max/data/stacks/flutter.csv create mode 100644 .claude/skills/ui-ux-pro-max/data/stacks/html-tailwind.csv create mode 100644 .claude/skills/ui-ux-pro-max/data/stacks/nextjs.csv create mode 100644 .claude/skills/ui-ux-pro-max/data/stacks/nuxt-ui.csv create mode 100644 .claude/skills/ui-ux-pro-max/data/stacks/nuxtjs.csv create mode 100644 .claude/skills/ui-ux-pro-max/data/stacks/react-native.csv create mode 100644 .claude/skills/ui-ux-pro-max/data/stacks/react.csv create mode 100644 .claude/skills/ui-ux-pro-max/data/stacks/shadcn.csv create mode 100644 .claude/skills/ui-ux-pro-max/data/stacks/svelte.csv create mode 100644 .claude/skills/ui-ux-pro-max/data/stacks/swiftui.csv create mode 100644 .claude/skills/ui-ux-pro-max/data/stacks/vue.csv create mode 100644 .claude/skills/ui-ux-pro-max/data/styles.csv create mode 100644 .claude/skills/ui-ux-pro-max/data/typography.csv create mode 100644 .claude/skills/ui-ux-pro-max/data/ui-reasoning.csv create mode 100644 .claude/skills/ui-ux-pro-max/data/ux-guidelines.csv create mode 100644 .claude/skills/ui-ux-pro-max/data/web-interface.csv create mode 100644 .claude/skills/ui-ux-pro-max/scripts/core.py create mode 100644 .claude/skills/ui-ux-pro-max/scripts/design_system.py create mode 100644 .claude/skills/ui-ux-pro-max/scripts/search.py create mode 100644 .claude/skills/uxui-principles/.openskills.json create mode 100644 .claude/skills/uxui-principles/SKILL.md create mode 100644 .claude/skills/visual-design-foundations/.openskills.json create mode 100644 .claude/skills/visual-design-foundations/SKILL.md create mode 100644 .claude/skills/visual-design-foundations/references/color-systems.md create mode 100644 .claude/skills/visual-design-foundations/references/spacing-iconography.md create mode 100644 .claude/skills/visual-design-foundations/references/typography-systems.md create mode 100644 .claude/skills/web-component-design/.openskills.json create mode 100644 .claude/skills/web-component-design/SKILL.md create mode 100644 .claude/skills/web-component-design/references/accessibility-patterns.md create mode 100644 .claude/skills/web-component-design/references/component-patterns.md create mode 100644 .claude/skills/web-component-design/references/css-styling-approaches.md create mode 100644 .claude/skills/web-design-guidelines/.openskills.json create mode 100644 .claude/skills/web-design-guidelines/SKILL.md diff --git a/.claude/skills/frontend-design/.openskills.json b/.claude/skills/frontend-design/.openskills.json new file mode 100644 index 0000000..cfeabbd --- /dev/null +++ b/.claude/skills/frontend-design/.openskills.json @@ -0,0 +1,6 @@ +{ + "source": "/tmp/skill-selector-curated-3996165046", + "sourceType": "local", + "localPath": "/tmp/skill-selector-curated-3996165046/frontend-design", + "installedAt": "2026-04-08T03:16:13.041Z" +} \ No newline at end of file diff --git a/.claude/skills/frontend-design/LICENSE.txt b/.claude/skills/frontend-design/LICENSE.txt new file mode 100644 index 0000000..f433b1a --- /dev/null +++ b/.claude/skills/frontend-design/LICENSE.txt @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/.claude/skills/frontend-design/SKILL.md b/.claude/skills/frontend-design/SKILL.md new file mode 100644 index 0000000..5d507bc --- /dev/null +++ b/.claude/skills/frontend-design/SKILL.md @@ -0,0 +1,277 @@ +--- +name: frontend-design +description: "You are a frontend designer-engineer, not a layout generator." +risk: unknown +source: community +date_added: "2026-02-27" +--- + +# Frontend Design (Distinctive, Production-Grade) + +You are a **frontend designer-engineer**, not a layout generator. + +Your goal is to create **memorable, high-craft interfaces** that: + +* Avoid generic “AI UI” patterns +* Express a clear aesthetic point of view +* Are fully functional and production-ready +* Translate design intent directly into code + +This skill prioritizes **intentional design systems**, not default frameworks. + +--- + +## 1. Core Design Mandate + +Every output must satisfy **all four**: + +1. **Intentional Aesthetic Direction** + A named, explicit design stance (e.g. *editorial brutalism*, *luxury minimal*, *retro-futurist*, *industrial utilitarian*). + +2. **Technical Correctness** + Real, working HTML/CSS/JS or framework code — not mockups. + +3. **Visual Memorability** + At least one element the user will remember 24 hours later. + +4. **Cohesive Restraint** + No random decoration. Every flourish must serve the aesthetic thesis. + +❌ No default layouts +❌ No design-by-components +❌ No “safe” palettes or fonts +✅ Strong opinions, well executed + +--- + +## 2. Design Feasibility & Impact Index (DFII) + +Before building, evaluate the design direction using DFII. + +### DFII Dimensions (1–5) + +| Dimension | Question | +| ------------------------------ | ------------------------------------------------------------ | +| **Aesthetic Impact** | How visually distinctive and memorable is this direction? | +| **Context Fit** | Does this aesthetic suit the product, audience, and purpose? | +| **Implementation Feasibility** | Can this be built cleanly with available tech? | +| **Performance Safety** | Will it remain fast and accessible? | +| **Consistency Risk** | Can this be maintained across screens/components? | + +### Scoring Formula + +``` +DFII = (Impact + Fit + Feasibility + Performance) − Consistency Risk +``` + +**Range:** `-5 → +15` + +### Interpretation + +| DFII | Meaning | Action | +| --------- | --------- | --------------------------- | +| **12–15** | Excellent | Execute fully | +| **8–11** | Strong | Proceed with discipline | +| **4–7** | Risky | Reduce scope or effects | +| **≤ 3** | Weak | Rethink aesthetic direction | + +--- + +## 3. Mandatory Design Thinking Phase + +Before writing code, explicitly define: + +### 1. Purpose + +* What action should this interface enable? +* Is it persuasive, functional, exploratory, or expressive? + +### 2. Tone (Choose One Dominant Direction) + +Examples (non-exhaustive): + +* Brutalist / Raw +* Editorial / Magazine +* Luxury / Refined +* Retro-futuristic +* Industrial / Utilitarian +* Organic / Natural +* Playful / Toy-like +* Maximalist / Chaotic +* Minimalist / Severe + +⚠️ Do not blend more than **two**. + +### 3. Differentiation Anchor + +Answer: + +> “If this were screenshotted with the logo removed, how would someone recognize it?” + +This anchor must be visible in the final UI. + +--- + +## 4. Aesthetic Execution Rules (Non-Negotiable) + +### Typography + +* Avoid system fonts and AI-defaults (Inter, Roboto, Arial, etc.) +* Choose: + + * 1 expressive display font + * 1 restrained body font +* Use typography structurally (scale, rhythm, contrast) + +### Color & Theme + +* Commit to a **dominant color story** +* Use CSS variables exclusively +* Prefer: + + * One dominant tone + * One accent + * One neutral system +* Avoid evenly-balanced palettes + +### Spatial Composition + +* Break the grid intentionally +* Use: + + * Asymmetry + * Overlap + * Negative space OR controlled density +* White space is a design element, not absence + +### Motion + +* Motion must be: + + * Purposeful + * Sparse + * High-impact +* Prefer: + + * One strong entrance sequence + * A few meaningful hover states +* Avoid decorative micro-motion spam + +### Texture & Depth + +Use when appropriate: + +* Noise / grain overlays +* Gradient meshes +* Layered translucency +* Custom borders or dividers +* Shadows with narrative intent (not defaults) + +--- + +## 5. Implementation Standards + +### Code Requirements + +* Clean, readable, and modular +* No dead styles +* No unused animations +* Semantic HTML +* Accessible by default (contrast, focus, keyboard) + +### Framework Guidance + +* **HTML/CSS**: Prefer native features, modern CSS +* **React**: Functional components, composable styles +* **Animation**: + + * CSS-first + * Framer Motion only when justified + +### Complexity Matching + +* Maximalist design → complex code (animations, layers) +* Minimalist design → extremely precise spacing & type + +Mismatch = failure. + +--- + +## 6. Required Output Structure + +When generating frontend work: + +### 1. Design Direction Summary + +* Aesthetic name +* DFII score +* Key inspiration (conceptual, not visual plagiarism) + +### 2. Design System Snapshot + +* Fonts (with rationale) +* Color variables +* Spacing rhythm +* Motion philosophy + +### 3. Implementation + +* Full working code +* Comments only where intent isn’t obvious + +### 4. Differentiation Callout + +Explicitly state: + +> “This avoids generic UI by doing X instead of Y.” + +--- + +## 7. Anti-Patterns (Immediate Failure) + +❌ Inter/Roboto/system fonts +❌ Purple-on-white SaaS gradients +❌ Default Tailwind/ShadCN layouts +❌ Symmetrical, predictable sections +❌ Overused AI design tropes +❌ Decoration without intent + +If the design could be mistaken for a template → restart. + +--- + +## 8. Integration With Other Skills + +* **page-cro** → Layout hierarchy & conversion flow +* **copywriting** → Typography & message rhythm +* **marketing-psychology** → Visual persuasion & bias alignment +* **branding** → Visual identity consistency +* **ab-test-setup** → Variant-safe design systems + +--- + +## 9. Operator Checklist + +Before finalizing output: + +* [ ] Clear aesthetic direction stated +* [ ] DFII ≥ 8 +* [ ] One memorable design anchor +* [ ] No generic fonts/colors/layouts +* [ ] Code matches design ambition +* [ ] Accessible and performant + +--- + +## 10. Questions to Ask (If Needed) + +1. Who is this for, emotionally? +2. Should this feel trustworthy, exciting, calm, or provocative? +3. Is memorability or clarity more important? +4. Will this scale to other pages/components? +5. What should users *feel* in the first 3 seconds? + +--- + +## When to Use +This skill is applicable to execute the workflow or actions described in the overview. diff --git a/.claude/skills/frontend-ui-dark-ts/.openskills.json b/.claude/skills/frontend-ui-dark-ts/.openskills.json new file mode 100644 index 0000000..187f927 --- /dev/null +++ b/.claude/skills/frontend-ui-dark-ts/.openskills.json @@ -0,0 +1,6 @@ +{ + "source": "/tmp/skill-selector-curated-3996165046", + "sourceType": "local", + "localPath": "/tmp/skill-selector-curated-3996165046/frontend-ui-dark-ts", + "installedAt": "2026-04-08T03:16:13.042Z" +} \ No newline at end of file diff --git a/.claude/skills/frontend-ui-dark-ts/SKILL.md b/.claude/skills/frontend-ui-dark-ts/SKILL.md new file mode 100644 index 0000000..8ee4e0e --- /dev/null +++ b/.claude/skills/frontend-ui-dark-ts/SKILL.md @@ -0,0 +1,594 @@ +--- +name: frontend-ui-dark-ts +description: "A modern dark-themed React UI system using Tailwind CSS and Framer Motion. Designed for dashboards, admin panels, and data-rich applications with glassmorphism effects and tasteful animations." +risk: unknown +source: community +date_added: "2026-02-27" +--- + +# Frontend UI Dark Theme (TypeScript) + +A modern dark-themed React UI system using **Tailwind CSS** and **Framer Motion**. Designed for dashboards, admin panels, and data-rich applications with glassmorphism effects and tasteful animations. + +## Stack + +| Package | Version | Purpose | +|---------|---------|---------| +| `react` | ^18.x | UI framework | +| `react-dom` | ^18.x | DOM rendering | +| `react-router-dom` | ^6.x | Routing | +| `framer-motion` | ^11.x | Animations | +| `clsx` | ^2.x | Class merging | +| `tailwindcss` | ^3.x | Styling | +| `vite` | ^5.x | Build tool | +| `typescript` | ^5.x | Type safety | + +## Quick Start + +```bash +npm create vite@latest my-app -- --template react-ts +cd my-app +npm install framer-motion clsx react-router-dom +npm install -D tailwindcss postcss autoprefixer +npx tailwindcss init -p +``` + +## Project Structure + +``` +public/ +├── favicon.ico # Classic favicon (32x32) +├── favicon.svg # Modern SVG favicon +├── apple-touch-icon.png # iOS home screen (180x180) +├── og-image.png # Social sharing image (1200x630) +└── site.webmanifest # PWA manifest +src/ +├── assets/ +│ └── fonts/ +│ ├── Segoe UI.ttf +│ ├── Segoe UI Bold.ttf +│ ├── Segoe UI Italic.ttf +│ └── Segoe UI Bold Italic.ttf +├── components/ +│ ├── ui/ +│ │ ├── Button.tsx +│ │ ├── Card.tsx +│ │ ├── Input.tsx +│ │ ├── Badge.tsx +│ │ ├── Dialog.tsx +│ │ ├── Tabs.tsx +│ │ └── index.ts +│ └── layout/ +│ ├── AppShell.tsx +│ ├── Sidebar.tsx +│ └── PageHeader.tsx +├── styles/ +│ └── globals.css +├── App.tsx +└── main.tsx +``` + +## Configuration + +### index.html + +The HTML entry point with mobile viewport, favicons, and social meta tags: + +```html + + + + + + + + + + + + + + + + + + + + + + + + + + + + + App Name + + +
+ + + +``` + +### public/site.webmanifest + +PWA manifest for installable web apps: + +```json +{ + "name": "App Name", + "short_name": "App", + "icons": [ + { "src": "/favicon.ico", "sizes": "32x32", "type": "image/x-icon" }, + { "src": "/apple-touch-icon.png", "sizes": "180x180", "type": "image/png" } + ], + "theme_color": "#18181B", + "background_color": "#18181B", + "display": "standalone" +} +``` + +### tailwind.config.js + +```js +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], + theme: { + extend: { + fontFamily: { + sans: ['Segoe UI', 'system-ui', 'sans-serif'], + }, + colors: { + brand: { + DEFAULT: '#8251EE', + hover: '#9366F5', + light: '#A37EF5', + subtle: 'rgba(130, 81, 238, 0.15)', + }, + neutral: { + bg1: 'hsl(240, 6%, 10%)', + bg2: 'hsl(240, 5%, 12%)', + bg3: 'hsl(240, 5%, 14%)', + bg4: 'hsl(240, 4%, 18%)', + bg5: 'hsl(240, 4%, 22%)', + bg6: 'hsl(240, 4%, 26%)', + }, + text: { + primary: '#FFFFFF', + secondary: '#A1A1AA', + muted: '#71717A', + }, + border: { + subtle: 'hsla(0, 0%, 100%, 0.08)', + DEFAULT: 'hsla(0, 0%, 100%, 0.12)', + strong: 'hsla(0, 0%, 100%, 0.20)', + }, + status: { + success: '#10B981', + warning: '#F59E0B', + error: '#EF4444', + info: '#3B82F6', + }, + dataviz: { + purple: '#8251EE', + blue: '#3B82F6', + green: '#10B981', + yellow: '#F59E0B', + red: '#EF4444', + pink: '#EC4899', + cyan: '#06B6D4', + }, + }, + borderRadius: { + DEFAULT: '0.5rem', + lg: '0.75rem', + xl: '1rem', + }, + boxShadow: { + glow: '0 0 20px rgba(130, 81, 238, 0.3)', + 'glow-lg': '0 0 40px rgba(130, 81, 238, 0.4)', + }, + backdropBlur: { + xs: '2px', + }, + animation: { + 'fade-in': 'fadeIn 0.3s ease-out', + 'slide-up': 'slideUp 0.3s ease-out', + 'slide-down': 'slideDown 0.3s ease-out', + }, + keyframes: { + fadeIn: { + '0%': { opacity: '0' }, + '100%': { opacity: '1' }, + }, + slideUp: { + '0%': { opacity: '0', transform: 'translateY(10px)' }, + '100%': { opacity: '1', transform: 'translateY(0)' }, + }, + slideDown: { + '0%': { opacity: '0', transform: 'translateY(-10px)' }, + '100%': { opacity: '1', transform: 'translateY(0)' }, + }, + }, + // Mobile: safe area insets for notched devices + spacing: { + 'safe-top': 'env(safe-area-inset-top)', + 'safe-bottom': 'env(safe-area-inset-bottom)', + 'safe-left': 'env(safe-area-inset-left)', + 'safe-right': 'env(safe-area-inset-right)', + }, + // Mobile: minimum touch target sizes (44px per Apple/Google guidelines) + minHeight: { + 'touch': '44px', + }, + minWidth: { + 'touch': '44px', + }, + }, + }, + plugins: [], +}; +``` + +### postcss.config.js + +```js +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; +``` + +### src/styles/globals.css + +```css +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* Font faces */ +@font-face { + font-family: 'Segoe UI'; + src: url('../assets/fonts/Segoe UI.ttf') format('truetype'); + font-weight: 400; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Segoe UI'; + src: url('../assets/fonts/Segoe UI Bold.ttf') format('truetype'); + font-weight: 700; + font-style: normal; + font-display: swap; +} + +@font-face { + font-family: 'Segoe UI'; + src: url('../assets/fonts/Segoe UI Italic.ttf') format('truetype'); + font-weight: 400; + font-style: italic; + font-display: swap; +} + +@font-face { + font-family: 'Segoe UI'; + src: url('../assets/fonts/Segoe UI Bold Italic.ttf') format('truetype'); + font-weight: 700; + font-style: italic; + font-display: swap; +} + +/* CSS Custom Properties */ +:root { + /* Brand colors */ + --color-brand: #8251EE; + --color-brand-hover: #9366F5; + --color-brand-light: #A37EF5; + --color-brand-subtle: rgba(130, 81, 238, 0.15); + + /* Neutral backgrounds */ + --color-bg-1: hsl(240, 6%, 10%); + --color-bg-2: hsl(240, 5%, 12%); + --color-bg-3: hsl(240, 5%, 14%); + --color-bg-4: hsl(240, 4%, 18%); + --color-bg-5: hsl(240, 4%, 22%); + --color-bg-6: hsl(240, 4%, 26%); + + /* Text colors */ + --color-text-primary: #FFFFFF; + --color-text-secondary: #A1A1AA; + --color-text-muted: #71717A; + + /* Border colors */ + --color-border-subtle: hsla(0, 0%, 100%, 0.08); + --color-border-default: hsla(0, 0%, 100%, 0.12); + --color-border-strong: hsla(0, 0%, 100%, 0.20); + + /* Status colors */ + --color-success: #10B981; + --color-warning: #F59E0B; + --color-error: #EF4444; + --color-info: #3B82F6; + + /* Spacing */ + --spacing-xs: 0.25rem; + --spacing-sm: 0.5rem; + --spacing-md: 1rem; + --spacing-lg: 1.5rem; + --spacing-xl: 2rem; + --spacing-2xl: 3rem; + + /* Border radius */ + --radius-sm: 0.375rem; + --radius-md: 0.5rem; + --radius-lg: 0.75rem; + --radius-xl: 1rem; + + /* Transitions */ + --transition-fast: 150ms ease; + --transition-normal: 200ms ease; + --transition-slow: 300ms ease; +} + +/* Base styles */ +html { + color-scheme: dark; +} + +body { + @apply bg-neutral-bg1 text-text-primary font-sans antialiased; + min-height: 100vh; +} + +/* Focus styles */ +*:focus-visible { + @apply outline-none ring-2 ring-brand ring-offset-2 ring-offset-neutral-bg1; +} + +/* Scrollbar styling */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + @apply bg-neutral-bg2; +} + +::-webkit-scrollbar-thumb { + @apply bg-neutral-bg5 rounded-full; +} + +::-webkit-scrollbar-thumb:hover { + @apply bg-neutral-bg6; +} + +/* Glass utility classes */ +@layer components { + .glass { + @apply backdrop-blur-md bg-white/5 border border-white/10; + } + + .glass-card { + @apply backdrop-blur-md bg-white/5 border border-white/10 rounded-xl; + } + + .glass-panel { + @apply backdrop-blur-lg bg-black/40 border border-white/5; + } + + .glass-overlay { + @apply backdrop-blur-sm bg-black/60; + } + + .glass-input { + @apply backdrop-blur-sm bg-white/5 border border-white/10 focus:border-brand focus:bg-white/10; + } +} + +/* Animation utilities */ +@layer utilities { + .animate-in { + animation: fadeIn 0.3s ease-out, slideUp 0.3s ease-out; + } +} +``` + +### src/main.tsx + +```tsx +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import { BrowserRouter } from 'react-router-dom'; +import App from './App'; +import './styles/globals.css'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + + + +); +``` + +### src/App.tsx + +```tsx +import { Routes, Route } from 'react-router-dom'; +import { AnimatePresence } from 'framer-motion'; +import { AppShell } from './components/layout/AppShell'; +import { Dashboard } from './pages/Dashboard'; +import { Settings } from './pages/Settings'; + +export default function App() { + return ( + + + + } /> + } /> + + + + ); +} +``` + +## Animation Patterns + +### Framer Motion Variants + +```tsx +// Fade in on mount +export const fadeIn = { + initial: { opacity: 0 }, + animate: { opacity: 1 }, + exit: { opacity: 0 }, + transition: { duration: 0.2 }, +}; + +// Slide up on mount +export const slideUp = { + initial: { opacity: 0, y: 20 }, + animate: { opacity: 1, y: 0 }, + exit: { opacity: 0, y: 20 }, + transition: { duration: 0.3, ease: 'easeOut' }, +}; + +// Scale on hover (for buttons/cards) +export const scaleOnHover = { + whileHover: { scale: 1.02 }, + whileTap: { scale: 0.98 }, + transition: { type: 'spring', stiffness: 400, damping: 17 }, +}; + +// Stagger children +export const staggerContainer = { + hidden: { opacity: 0 }, + visible: { + opacity: 1, + transition: { + staggerChildren: 0.05, + delayChildren: 0.1, + }, + }, +}; + +export const staggerItem = { + hidden: { opacity: 0, y: 10 }, + visible: { + opacity: 1, + y: 0, + transition: { duration: 0.2, ease: 'easeOut' }, + }, +}; +``` + +### Page Transition Wrapper + +```tsx +import { motion } from 'framer-motion'; +import { ReactNode } from 'react'; + +interface PageTransitionProps { + children: ReactNode; +} + +export function PageTransition({ children }: PageTransitionProps) { + return ( + + {children} + + ); +} +``` + +## Glass Effect Patterns + +### Glass Card + +```tsx +
+

Card Title

+

Card content goes here.

+
+``` + +### Glass Panel (Sidebar) + +```tsx + +``` + +### Glass Modal Overlay + +```tsx + + + {/* Modal content */} + + +``` + +## Typography + +| Element | Classes | +|---------|---------| +| Page title | `text-2xl font-semibold text-text-primary` | +| Section title | `text-lg font-semibold text-text-primary` | +| Card title | `text-base font-medium text-text-primary` | +| Body text | `text-sm text-text-secondary` | +| Caption | `text-xs text-text-muted` | +| Label | `text-sm font-medium text-text-secondary` | + +## Color Usage + +| Use Case | Color | Class | +|----------|-------|-------| +| Primary action | Brand purple | `bg-brand text-white` | +| Primary hover | Brand hover | `hover:bg-brand-hover` | +| Page background | Neutral bg1 | `bg-neutral-bg1` | +| Card background | Neutral bg2 | `bg-neutral-bg2` | +| Elevated surface | Neutral bg3 | `bg-neutral-bg3` | +| Input background | Neutral bg2 | `bg-neutral-bg2` | +| Input focus | Neutral bg3 | `focus:bg-neutral-bg3` | +| Border default | Border default | `border-border` | +| Border subtle | Border subtle | `border-border-subtle` | +| Success | Status success | `text-status-success` | +| Warning | Status warning | `text-status-warning` | +| Error | Status error | `text-status-error` | + +## Related Files + +- Design Tokens — Complete color system, spacing, typography scales +- Components — Button, Card, Input, Dialog, Tabs, and more +- Patterns — Page layouts, navigation, lists, forms + +## When to Use +This skill is applicable to execute the workflow or actions described in the overview. diff --git a/.claude/skills/react-ui-patterns/.openskills.json b/.claude/skills/react-ui-patterns/.openskills.json new file mode 100644 index 0000000..2a9ccd7 --- /dev/null +++ b/.claude/skills/react-ui-patterns/.openskills.json @@ -0,0 +1,6 @@ +{ + "source": "/tmp/skill-selector-curated-3996165046", + "sourceType": "local", + "localPath": "/tmp/skill-selector-curated-3996165046/react-ui-patterns", + "installedAt": "2026-04-08T03:16:13.042Z" +} \ No newline at end of file diff --git a/.claude/skills/react-ui-patterns/SKILL.md b/.claude/skills/react-ui-patterns/SKILL.md new file mode 100644 index 0000000..1b31e08 --- /dev/null +++ b/.claude/skills/react-ui-patterns/SKILL.md @@ -0,0 +1,295 @@ +--- +name: react-ui-patterns +description: "Modern React UI patterns for loading states, error handling, and data fetching. Use when building UI components, handling async data, or managing UI states." +risk: unknown +source: community +date_added: "2026-02-27" +--- + +# React UI Patterns + +## Core Principles + +1. **Never show stale UI** - Loading spinners only when actually loading +2. **Always surface errors** - Users must know when something fails +3. **Optimistic updates** - Make the UI feel instant +4. **Progressive disclosure** - Show content as it becomes available +5. **Graceful degradation** - Partial data is better than no data + +## Loading State Patterns + +### The Golden Rule + +**Show loading indicator ONLY when there's no data to display.** + +```typescript +// CORRECT - Only show loading when no data exists +const { data, loading, error } = useGetItemsQuery(); + +if (error) return ; +if (loading && !data) return ; +if (!data?.items.length) return ; + +return ; +``` + +```typescript +// WRONG - Shows spinner even when we have cached data +if (loading) return ; // Flashes on refetch! +``` + +### Loading State Decision Tree + +``` +Is there an error? + → Yes: Show error state with retry option + → No: Continue + +Is it loading AND we have no data? + → Yes: Show loading indicator (spinner/skeleton) + → No: Continue + +Do we have data? + → Yes, with items: Show the data + → Yes, but empty: Show empty state + → No: Show loading (fallback) +``` + +### Skeleton vs Spinner + +| Use Skeleton When | Use Spinner When | +|-------------------|------------------| +| Known content shape | Unknown content shape | +| List/card layouts | Modal actions | +| Initial page load | Button submissions | +| Content placeholders | Inline operations | + +## Error Handling Patterns + +### The Error Handling Hierarchy + +``` +1. Inline error (field-level) → Form validation errors +2. Toast notification → Recoverable errors, user can retry +3. Error banner → Page-level errors, data still partially usable +4. Full error screen → Unrecoverable, needs user action +``` + +### Always Show Errors + +**CRITICAL: Never swallow errors silently.** + +```typescript +// CORRECT - Error always surfaced to user +const [createItem, { loading }] = useCreateItemMutation({ + onCompleted: () => { + toast.success({ title: 'Item created' }); + }, + onError: (error) => { + console.error('createItem failed:', error); + toast.error({ title: 'Failed to create item' }); + }, +}); + +// WRONG - Error silently caught, user has no idea +const [createItem] = useCreateItemMutation({ + onError: (error) => { + console.error(error); // User sees nothing! + }, +}); +``` + +### Error State Component Pattern + +```typescript +interface ErrorStateProps { + error: Error; + onRetry?: () => void; + title?: string; +} + +const ErrorState = ({ error, onRetry, title }: ErrorStateProps) => ( +
+ +

{title ?? 'Something went wrong'}

+

{error.message}

+ {onRetry && ( + + )} +
+); +``` + +## Button State Patterns + +### Button Loading State + +```tsx + +``` + +### Disable During Operations + +**CRITICAL: Always disable triggers during async operations.** + +```tsx +// CORRECT - Button disabled while loading + + +// WRONG - User can tap multiple times + +``` + +## Empty States + +### Empty State Requirements + +Every list/collection MUST have an empty state: + +```tsx +// WRONG - No empty state +return ; + +// CORRECT - Explicit empty state +return ( + } + /> +); +``` + +### Contextual Empty States + +```tsx +// Search with no results + + +// List with no items yet + +``` + +## Form Submission Pattern + +```tsx +const MyForm = () => { + const [submit, { loading }] = useSubmitMutation({ + onCompleted: handleSuccess, + onError: handleError, + }); + + const handleSubmit = async () => { + if (!isValid) { + toast.error({ title: 'Please fix errors' }); + return; + } + await submit({ variables: { input: values } }); + }; + + return ( +
+ + +
+ ); +}; +``` + +## Anti-Patterns + +### Loading States + +```typescript +// WRONG - Spinner when data exists (causes flash) +if (loading) return ; + +// CORRECT - Only show loading without data +if (loading && !data) return ; +``` + +### Error Handling + +```typescript +// WRONG - Error swallowed +try { + await mutation(); +} catch (e) { + console.log(e); // User has no idea! +} + +// CORRECT - Error surfaced +onError: (error) => { + console.error('operation failed:', error); + toast.error({ title: 'Operation failed' }); +} +``` + +### Button States + +```typescript +// WRONG - Button not disabled during submission + + +// CORRECT - Disabled and shows loading + +``` + +## Checklist + +Before completing any UI component: + +**UI States:** +- [ ] Error state handled and shown to user +- [ ] Loading state shown only when no data exists +- [ ] Empty state provided for collections +- [ ] Buttons disabled during async operations +- [ ] Buttons show loading indicator when appropriate + +**Data & Mutations:** +- [ ] Mutations have onError handler +- [ ] All user actions have feedback (toast/visual) + +## Integration with Other Skills + +- **graphql-schema**: Use mutation patterns with proper error handling +- **testing-patterns**: Test all UI states (loading, error, empty, success) +- **formik-patterns**: Apply form submission patterns + +## When to Use +This skill is applicable to execute the workflow or actions described in the overview. diff --git a/.claude/skills/responsive-design/.openskills.json b/.claude/skills/responsive-design/.openskills.json new file mode 100644 index 0000000..f2c15ce --- /dev/null +++ b/.claude/skills/responsive-design/.openskills.json @@ -0,0 +1,6 @@ +{ + "source": "/tmp/skill-selector-curated-3996165046", + "sourceType": "local", + "localPath": "/tmp/skill-selector-curated-3996165046/responsive-design", + "installedAt": "2026-04-08T03:16:13.043Z" +} \ No newline at end of file diff --git a/.claude/skills/responsive-design/SKILL.md b/.claude/skills/responsive-design/SKILL.md new file mode 100644 index 0000000..038da96 --- /dev/null +++ b/.claude/skills/responsive-design/SKILL.md @@ -0,0 +1,516 @@ +--- +name: responsive-design +description: Implement modern responsive layouts using container queries, fluid typography, CSS Grid, and mobile-first breakpoint strategies. Use when building adaptive interfaces, implementing fluid layouts, or creating component-level responsive behavior. +--- + +# Responsive Design + +Master modern responsive design techniques to create interfaces that adapt seamlessly across all screen sizes and device contexts. + +## When to Use This Skill + +- Implementing mobile-first responsive layouts +- Using container queries for component-based responsiveness +- Creating fluid typography and spacing scales +- Building complex layouts with CSS Grid and Flexbox +- Designing breakpoint strategies for design systems +- Implementing responsive images and media +- Creating adaptive navigation patterns +- Building responsive tables and data displays + +## Core Capabilities + +### 1. Container Queries + +- Component-level responsiveness independent of viewport +- Container query units (cqi, cqw, cqh) +- Style queries for conditional styling +- Fallbacks for browser support + +### 2. Fluid Typography & Spacing + +- CSS clamp() for fluid scaling +- Viewport-relative units (vw, vh, dvh) +- Fluid type scales with min/max bounds +- Responsive spacing systems + +### 3. Layout Patterns + +- CSS Grid for 2D layouts +- Flexbox for 1D distribution +- Intrinsic layouts (content-based sizing) +- Subgrid for nested grid alignment + +### 4. Breakpoint Strategy + +- Mobile-first media queries +- Content-based breakpoints +- Design token integration +- Feature queries (@supports) + +## Quick Reference + +### Modern Breakpoint Scale + +```css +/* Mobile-first breakpoints */ +/* Base: Mobile (< 640px) */ +@media (min-width: 640px) { + /* sm: Landscape phones, small tablets */ +} +@media (min-width: 768px) { + /* md: Tablets */ +} +@media (min-width: 1024px) { + /* lg: Laptops, small desktops */ +} +@media (min-width: 1280px) { + /* xl: Desktops */ +} +@media (min-width: 1536px) { + /* 2xl: Large desktops */ +} + +/* Tailwind CSS equivalent */ +/* sm: @media (min-width: 640px) */ +/* md: @media (min-width: 768px) */ +/* lg: @media (min-width: 1024px) */ +/* xl: @media (min-width: 1280px) */ +/* 2xl: @media (min-width: 1536px) */ +``` + +## Key Patterns + +### Pattern 1: Container Queries + +```css +/* Define a containment context */ +.card-container { + container-type: inline-size; + container-name: card; +} + +/* Query the container, not the viewport */ +@container card (min-width: 400px) { + .card { + display: grid; + grid-template-columns: 200px 1fr; + gap: 1rem; + } + + .card-image { + aspect-ratio: 1; + } +} + +@container card (min-width: 600px) { + .card { + grid-template-columns: 250px 1fr; + } + + .card-title { + font-size: 1.5rem; + } +} + +/* Container query units */ +.card-title { + /* 5% of container width, clamped between 1rem and 2rem */ + font-size: clamp(1rem, 5cqi, 2rem); +} +``` + +```tsx +// React component with container queries +function ResponsiveCard({ title, image, description }) { + return ( +
+
+ +
+

+ {title} +

+

+ {description} +

+
+
+
+ ); +} +``` + +### Pattern 2: Fluid Typography + +```css +/* Fluid type scale using clamp() */ +:root { + /* Min size, preferred (fluid), max size */ + --text-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem); + --text-sm: clamp(0.875rem, 0.8rem + 0.375vw, 1rem); + --text-base: clamp(1rem, 0.9rem + 0.5vw, 1.125rem); + --text-lg: clamp(1.125rem, 1rem + 0.625vw, 1.25rem); + --text-xl: clamp(1.25rem, 1rem + 1.25vw, 1.5rem); + --text-2xl: clamp(1.5rem, 1.25rem + 1.25vw, 2rem); + --text-3xl: clamp(1.875rem, 1.5rem + 1.875vw, 2.5rem); + --text-4xl: clamp(2.25rem, 1.75rem + 2.5vw, 3.5rem); +} + +/* Usage */ +h1 { + font-size: var(--text-4xl); +} +h2 { + font-size: var(--text-3xl); +} +h3 { + font-size: var(--text-2xl); +} +p { + font-size: var(--text-base); +} + +/* Fluid spacing scale */ +:root { + --space-xs: clamp(0.25rem, 0.2rem + 0.25vw, 0.5rem); + --space-sm: clamp(0.5rem, 0.4rem + 0.5vw, 0.75rem); + --space-md: clamp(1rem, 0.8rem + 1vw, 1.5rem); + --space-lg: clamp(1.5rem, 1.2rem + 1.5vw, 2.5rem); + --space-xl: clamp(2rem, 1.5rem + 2.5vw, 4rem); +} +``` + +```tsx +// Utility function for fluid values +function fluidValue( + minSize: number, + maxSize: number, + minWidth = 320, + maxWidth = 1280, +) { + const slope = (maxSize - minSize) / (maxWidth - minWidth); + const yAxisIntersection = -minWidth * slope + minSize; + + return `clamp(${minSize}rem, ${yAxisIntersection.toFixed(4)}rem + ${(slope * 100).toFixed(4)}vw, ${maxSize}rem)`; +} + +// Generate fluid type scale +const fluidTypeScale = { + sm: fluidValue(0.875, 1), + base: fluidValue(1, 1.125), + lg: fluidValue(1.25, 1.5), + xl: fluidValue(1.5, 2), + "2xl": fluidValue(2, 3), +}; +``` + +### Pattern 3: CSS Grid Responsive Layout + +```css +/* Auto-fit grid - items wrap automatically */ +.grid-auto { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(300px, 100%), 1fr)); + gap: 1.5rem; +} + +/* Auto-fill grid - maintains empty columns */ +.grid-auto-fill { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); + gap: 1rem; +} + +/* Responsive grid with named areas */ +.page-layout { + display: grid; + grid-template-areas: + "header" + "main" + "sidebar" + "footer"; + gap: 1rem; +} + +@media (min-width: 768px) { + .page-layout { + grid-template-columns: 1fr 300px; + grid-template-areas: + "header header" + "main sidebar" + "footer footer"; + } +} + +@media (min-width: 1024px) { + .page-layout { + grid-template-columns: 250px 1fr 300px; + grid-template-areas: + "header header header" + "nav main sidebar" + "footer footer footer"; + } +} + +.header { + grid-area: header; +} +.main { + grid-area: main; +} +.sidebar { + grid-area: sidebar; +} +.footer { + grid-area: footer; +} +``` + +```tsx +// Responsive grid component +function ResponsiveGrid({ children, minItemWidth = "250px", gap = "1.5rem" }) { + return ( +
+ {children} +
+ ); +} + +// Usage with Tailwind +function ProductGrid({ products }) { + return ( +
+ {products.map((product) => ( + + ))} +
+ ); +} +``` + +### Pattern 4: Responsive Navigation + +```tsx +function ResponsiveNav({ items }) { + const [isOpen, setIsOpen] = useState(false); + + return ( + + ); +} +``` + +### Pattern 5: Responsive Images + +```tsx +// Responsive image with art direction +function ResponsiveHero() { + return ( + + {/* Art direction: different crops for different screens */} + + + + + {/* Fallback */} + Hero image description + + ); +} + +// Responsive image with srcset for resolution switching +function ProductImage({ product }) { + return ( + {product.name} + ); +} +``` + +### Pattern 6: Responsive Tables + +```tsx +// Responsive table with horizontal scroll +function ResponsiveTable({ data, columns }) { + return ( +
+ + + + {columns.map((col) => ( + + ))} + + + + {data.map((row, i) => ( + + {columns.map((col) => ( + + ))} + + ))} + +
+ {col.label} +
+ {row[col.key]} +
+
+ ); +} + +// Card-based table for mobile +function ResponsiveDataTable({ data, columns }) { + return ( + <> + {/* Desktop table */} + + {/* ... standard table */} +
+ + {/* Mobile cards */} +
+ {data.map((row, i) => ( +
+ {columns.map((col) => ( +
+ + {col.label} + + {row[col.key]} +
+ ))} +
+ ))} +
+ + ); +} +``` + +## Viewport Units + +```css +/* Standard viewport units */ +.full-height { + height: 100vh; /* May cause issues on mobile */ +} + +/* Dynamic viewport units (recommended for mobile) */ +.full-height-dynamic { + height: 100dvh; /* Accounts for mobile browser UI */ +} + +/* Small viewport (minimum) */ +.min-full-height { + min-height: 100svh; +} + +/* Large viewport (maximum) */ +.max-full-height { + max-height: 100lvh; +} + +/* Viewport-relative font sizing */ +.hero-title { + /* 5vw with min/max bounds */ + font-size: clamp(2rem, 5vw, 4rem); +} +``` + +## Best Practices + +1. **Mobile-First**: Start with mobile styles, enhance for larger screens +2. **Content Breakpoints**: Set breakpoints based on content, not devices +3. **Fluid Over Fixed**: Use fluid values for typography and spacing +4. **Container Queries**: Use for component-level responsiveness +5. **Test Real Devices**: Simulators don't catch all issues +6. **Performance**: Optimize images, lazy load off-screen content +7. **Touch Targets**: Maintain 44x44px minimum on mobile +8. **Logical Properties**: Use inline/block for internationalization + +## Common Issues + +- **Horizontal Overflow**: Content breaking out of viewport +- **Fixed Widths**: Using px instead of relative units +- **Viewport Height**: 100vh issues on mobile browsers +- **Font Size**: Text too small on mobile +- **Touch Targets**: Buttons too small to tap accurately +- **Aspect Ratio**: Images squishing or stretching +- **Z-Index Stacking**: Overlays breaking on different screens diff --git a/.claude/skills/responsive-design/references/breakpoint-strategies.md b/.claude/skills/responsive-design/references/breakpoint-strategies.md new file mode 100644 index 0000000..97620dd --- /dev/null +++ b/.claude/skills/responsive-design/references/breakpoint-strategies.md @@ -0,0 +1,591 @@ +# Breakpoint Strategies + +## Overview + +Effective breakpoint strategies focus on content needs rather than device sizes. Modern responsive design uses fewer, content-driven breakpoints combined with fluid techniques. + +## Mobile-First Approach + +### Core Philosophy + +Start with the smallest screen, then progressively enhance for larger screens. + +```css +/* Base styles (mobile first) */ +.component { + display: flex; + flex-direction: column; + padding: 1rem; +} + +/* Enhance for larger screens */ +@media (min-width: 640px) { + .component { + flex-direction: row; + padding: 1.5rem; + } +} + +@media (min-width: 1024px) { + .component { + padding: 2rem; + } +} +``` + +### Benefits + +1. **Performance**: Mobile devices load only necessary CSS +2. **Progressive Enhancement**: Features add rather than subtract +3. **Content Priority**: Forces focus on essential content first +4. **Simplicity**: Easier to reason about cascading styles + +## Common Breakpoint Scales + +### Tailwind CSS Default + +```css +/* Tailwind breakpoints */ +/* sm: 640px - Landscape phones */ +/* md: 768px - Tablets */ +/* lg: 1024px - Laptops */ +/* xl: 1280px - Desktops */ +/* 2xl: 1536px - Large desktops */ + +@media (min-width: 640px) { + /* sm */ +} +@media (min-width: 768px) { + /* md */ +} +@media (min-width: 1024px) { + /* lg */ +} +@media (min-width: 1280px) { + /* xl */ +} +@media (min-width: 1536px) { + /* 2xl */ +} +``` + +### Bootstrap 5 + +```css +/* Bootstrap breakpoints */ +/* sm: 576px */ +/* md: 768px */ +/* lg: 992px */ +/* xl: 1200px */ +/* xxl: 1400px */ + +@media (min-width: 576px) { + /* sm */ +} +@media (min-width: 768px) { + /* md */ +} +@media (min-width: 992px) { + /* lg */ +} +@media (min-width: 1200px) { + /* xl */ +} +@media (min-width: 1400px) { + /* xxl */ +} +``` + +### Minimalist Scale + +```css +/* Simplified 3-breakpoint system */ +/* Base: Mobile (< 600px) */ +/* Medium: Tablets and small laptops (600px - 1024px) */ +/* Large: Desktops (> 1024px) */ + +:root { + --bp-md: 600px; + --bp-lg: 1024px; +} + +@media (min-width: 600px) { + /* Medium */ +} +@media (min-width: 1024px) { + /* Large */ +} +``` + +## Content-Based Breakpoints + +### Finding Natural Breakpoints + +Instead of using device-based breakpoints, identify where your content naturally needs to change. + +```css +/* Bad: Device-based thinking */ +@media (min-width: 768px) { + /* iPad breakpoint */ +} + +/* Good: Content-based thinking */ +/* Breakpoint where sidebar fits comfortably next to content */ +@media (min-width: 50rem) { + .layout { + display: grid; + grid-template-columns: 1fr 300px; + } +} + +/* Breakpoint where cards can show 3 across without crowding */ +@media (min-width: 65rem) { + .card-grid { + grid-template-columns: repeat(3, 1fr); + } +} +``` + +### Testing Content Breakpoints + +```javascript +// Find where content breaks +function findBreakpoints(selector) { + const element = document.querySelector(selector); + const breakpoints = []; + + for (let width = 320; width <= 1920; width += 10) { + element.style.width = `${width}px`; + + // Check for overflow, wrapping, or layout issues + if (element.scrollWidth > element.clientWidth) { + breakpoints.push({ width, issue: "overflow" }); + } + } + + return breakpoints; +} +``` + +## Design Token Integration + +### Breakpoint Tokens + +```css +:root { + /* Breakpoint values */ + --breakpoint-sm: 640px; + --breakpoint-md: 768px; + --breakpoint-lg: 1024px; + --breakpoint-xl: 1280px; + --breakpoint-2xl: 1536px; + + /* Container widths for each breakpoint */ + --container-sm: 640px; + --container-md: 768px; + --container-lg: 1024px; + --container-xl: 1280px; + --container-2xl: 1536px; +} + +.container { + width: 100%; + max-width: var(--container-lg); + margin-inline: auto; + padding-inline: var(--space-4); +} +``` + +### JavaScript Integration + +```typescript +// Breakpoint constants +export const breakpoints = { + sm: 640, + md: 768, + lg: 1024, + xl: 1280, + "2xl": 1536, +} as const; + +// Media query hook +function useMediaQuery(query: string): boolean { + const [matches, setMatches] = useState(false); + + useEffect(() => { + const media = window.matchMedia(query); + setMatches(media.matches); + + const listener = () => setMatches(media.matches); + media.addEventListener("change", listener); + return () => media.removeEventListener("change", listener); + }, [query]); + + return matches; +} + +// Breakpoint hook +function useBreakpoint() { + const isSmall = useMediaQuery(`(min-width: ${breakpoints.sm}px)`); + const isMedium = useMediaQuery(`(min-width: ${breakpoints.md}px)`); + const isLarge = useMediaQuery(`(min-width: ${breakpoints.lg}px)`); + const isXLarge = useMediaQuery(`(min-width: ${breakpoints.xl}px)`); + + return { + isMobile: !isSmall, + isTablet: isSmall && !isLarge, + isDesktop: isLarge, + current: isXLarge + ? "xl" + : isLarge + ? "lg" + : isMedium + ? "md" + : isSmall + ? "sm" + : "base", + }; +} +``` + +## Feature Queries + +### @supports for Progressive Enhancement + +```css +/* Feature detection instead of browser detection */ +@supports (display: grid) { + .layout { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + } +} + +@supports (container-type: inline-size) { + .card-container { + container-type: inline-size; + } + + @container (min-width: 400px) { + .card { + flex-direction: row; + } + } +} + +@supports (aspect-ratio: 16/9) { + .video-container { + aspect-ratio: 16/9; + } +} + +/* Fallback for older browsers */ +@supports not (gap: 1rem) { + .flex-container > * + * { + margin-left: 1rem; + } +} +``` + +### Combining Feature and Size Queries + +```css +/* Only apply grid layout if supported and screen is large enough */ +@supports (display: grid) { + @media (min-width: 768px) { + .layout { + display: grid; + grid-template-columns: 250px 1fr; + } + } +} +``` + +## Responsive Patterns by Component + +### Navigation + +```css +.nav { + /* Mobile: vertical stack */ + display: flex; + flex-direction: column; +} + +@media (min-width: 768px) { + .nav { + /* Tablet+: horizontal */ + flex-direction: row; + align-items: center; + } +} + +/* Or with container queries */ +.nav-container { + container-type: inline-size; +} + +@container (min-width: 600px) { + .nav { + flex-direction: row; + } +} +``` + +### Cards Grid + +```css +.cards { + display: grid; + gap: 1.5rem; + grid-template-columns: 1fr; +} + +@media (min-width: 640px) { + .cards { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (min-width: 1024px) { + .cards { + grid-template-columns: repeat(3, 1fr); + } +} + +@media (min-width: 1280px) { + .cards { + grid-template-columns: repeat(4, 1fr); + } +} + +/* Better: auto-fit with minimum size */ +.cards-auto { + display: grid; + gap: 1.5rem; + grid-template-columns: repeat(auto-fit, minmax(min(100%, 280px), 1fr)); +} +``` + +### Hero Section + +```css +.hero { + min-height: 50vh; + padding: var(--space-lg) var(--space-md); + text-align: center; +} + +.hero-title { + font-size: clamp(2rem, 5vw + 1rem, 4rem); +} + +.hero-subtitle { + font-size: clamp(1rem, 2vw + 0.5rem, 1.5rem); +} + +@media (min-width: 768px) { + .hero { + min-height: 70vh; + display: flex; + align-items: center; + text-align: left; + } + + .hero-content { + max-width: 60%; + } +} + +@media (min-width: 1024px) { + .hero { + min-height: 80vh; + } + + .hero-content { + max-width: 50%; + } +} +``` + +### Tables + +```css +/* Mobile: cards or horizontal scroll */ +.table-container { + overflow-x: auto; +} + +.responsive-table { + min-width: 600px; +} + +/* Alternative: transform to cards on mobile */ +@media (max-width: 639px) { + .responsive-table { + min-width: 0; + } + + .responsive-table thead { + display: none; + } + + .responsive-table tr { + display: block; + margin-bottom: 1rem; + border: 1px solid var(--border); + border-radius: 0.5rem; + padding: 1rem; + } + + .responsive-table td { + display: flex; + justify-content: space-between; + padding: 0.5rem 0; + border: none; + } + + .responsive-table td::before { + content: attr(data-label); + font-weight: 600; + } +} +``` + +## Print Styles + +```css +@media print { + /* Remove non-essential elements */ + .nav, + .sidebar, + .footer, + .ads { + display: none; + } + + /* Reset colors and backgrounds */ + * { + background: white !important; + color: black !important; + box-shadow: none !important; + } + + /* Ensure content fits on page */ + .container { + max-width: 100%; + padding: 0; + } + + /* Handle page breaks */ + h1, + h2, + h3 { + page-break-after: avoid; + } + + img, + table { + page-break-inside: avoid; + } + + /* Show URLs for links */ + a[href^="http"]::after { + content: " (" attr(href) ")"; + font-size: 0.8em; + } +} +``` + +## Preference Queries + +```css +/* Dark mode preference */ +@media (prefers-color-scheme: dark) { + :root { + --bg: #1a1a1a; + --text: #f0f0f0; + } +} + +/* Reduced motion preference */ +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + } +} + +/* High contrast preference */ +@media (prefers-contrast: high) { + :root { + --text: #000; + --bg: #fff; + --border: #000; + } + + .button { + border: 2px solid currentColor; + } +} + +/* Reduced data preference */ +@media (prefers-reduced-data: reduce) { + .hero-video { + display: none; + } + + .hero-image { + display: block; + } +} +``` + +## Testing Breakpoints + +```javascript +// Automated breakpoint testing +async function testBreakpoints(page, breakpoints) { + const results = []; + + for (const [name, width] of Object.entries(breakpoints)) { + await page.setViewportSize({ width, height: 800 }); + + // Check for horizontal overflow + const hasOverflow = await page.evaluate(() => { + return ( + document.documentElement.scrollWidth > + document.documentElement.clientWidth + ); + }); + + // Check for elements going off-screen + const offscreenElements = await page.evaluate(() => { + const elements = document.querySelectorAll("*"); + return Array.from(elements).filter((el) => { + const rect = el.getBoundingClientRect(); + return rect.right > window.innerWidth || rect.left < 0; + }).length; + }); + + results.push({ + breakpoint: name, + width, + hasOverflow, + offscreenElements, + }); + } + + return results; +} +``` + +## Resources + +- [Tailwind CSS Breakpoints](https://tailwindcss.com/docs/responsive-design) +- [The 100% Correct Way to Do CSS Breakpoints](https://www.freecodecamp.org/news/the-100-correct-way-to-do-css-breakpoints-88d6a5ba1862/) +- [Modern CSS Solutions](https://moderncss.dev/) +- [Defensive CSS](https://defensivecss.dev/) diff --git a/.claude/skills/responsive-design/references/container-queries.md b/.claude/skills/responsive-design/references/container-queries.md new file mode 100644 index 0000000..98471f8 --- /dev/null +++ b/.claude/skills/responsive-design/references/container-queries.md @@ -0,0 +1,564 @@ +# Container Queries Deep Dive + +## Overview + +Container queries enable component-based responsive design by allowing elements to respond to their container's size rather than the viewport. This paradigm shift makes truly reusable components possible. + +## Browser Support + +Container queries have excellent modern browser support (Chrome 105+, Firefox 110+, Safari 16+). For older browsers, provide graceful fallbacks. + +## Containment Basics + +### Container Types + +```css +/* Size containment - queries based on inline and block size */ +.container { + container-type: size; +} + +/* Inline-size containment - queries based on inline (width) size only */ +/* Most common and recommended */ +.container { + container-type: inline-size; +} + +/* Normal - style queries only, no size queries */ +.container { + container-type: normal; +} +``` + +### Named Containers + +```css +/* Named container for targeted queries */ +.card-wrapper { + container-type: inline-size; + container-name: card; +} + +/* Shorthand */ +.card-wrapper { + container: card / inline-size; +} + +/* Query specific container */ +@container card (min-width: 400px) { + .card-content { + display: flex; + } +} +``` + +## Container Query Syntax + +### Width-Based Queries + +```css +.container { + container-type: inline-size; +} + +/* Minimum width */ +@container (min-width: 300px) { + .element { + /* styles */ + } +} + +/* Maximum width */ +@container (max-width: 500px) { + .element { + /* styles */ + } +} + +/* Range syntax */ +@container (300px <= width <= 600px) { + .element { + /* styles */ + } +} + +/* Exact width */ +@container (width: 400px) { + .element { + /* styles */ + } +} +``` + +### Combining Conditions + +```css +/* AND condition */ +@container (min-width: 400px) and (max-width: 800px) { + .element { + /* styles */ + } +} + +/* OR condition */ +@container (max-width: 300px) or (min-width: 800px) { + .element { + /* styles */ + } +} + +/* NOT condition */ +@container not (min-width: 400px) { + .element { + /* styles */ + } +} +``` + +### Named Container Queries + +```css +/* Multiple named containers */ +.page-wrapper { + container: page / inline-size; +} + +.sidebar-wrapper { + container: sidebar / inline-size; +} + +/* Target specific containers */ +@container page (min-width: 1024px) { + .main-content { + max-width: 800px; + } +} + +@container sidebar (min-width: 300px) { + .sidebar-widget { + display: grid; + grid-template-columns: 1fr 1fr; + } +} +``` + +## Container Query Units + +```css +/* Container query length units */ +.element { + /* Container query width - 1cqw = 1% of container width */ + width: 50cqw; + + /* Container query height - 1cqh = 1% of container height */ + height: 50cqh; + + /* Container query inline - 1cqi = 1% of container inline size */ + padding-inline: 5cqi; + + /* Container query block - 1cqb = 1% of container block size */ + padding-block: 3cqb; + + /* Container query min - smaller of cqi and cqb */ + font-size: 5cqmin; + + /* Container query max - larger of cqi and cqb */ + margin: 2cqmax; +} + +/* Practical example: fluid typography based on container */ +.card-title { + font-size: clamp(1rem, 4cqi, 2rem); +} + +.card-body { + padding: clamp(0.75rem, 4cqi, 1.5rem); +} +``` + +## Style Queries + +Style queries allow querying CSS custom property values. Currently limited support. + +```css +/* Define a custom property */ +.card { + --layout: stack; +} + +/* Query the property value */ +@container style(--layout: stack) { + .card-content { + display: flex; + flex-direction: column; + } +} + +@container style(--layout: inline) { + .card-content { + display: flex; + flex-direction: row; + } +} + +/* Toggle layout via custom property */ +.card.horizontal { + --layout: inline; +} +``` + +## Practical Patterns + +### Responsive Card Component + +```css +.card-container { + container: card / inline-size; +} + +.card { + display: flex; + flex-direction: column; + gap: 1rem; + padding: clamp(1rem, 4cqi, 2rem); +} + +.card-image { + aspect-ratio: 16/9; + width: 100%; + object-fit: cover; + border-radius: 0.5rem; +} + +.card-title { + font-size: clamp(1rem, 4cqi, 1.5rem); + font-weight: 600; +} + +/* Medium container: side-by-side layout */ +@container card (min-width: 400px) { + .card { + flex-direction: row; + align-items: flex-start; + } + + .card-image { + width: 40%; + aspect-ratio: 1; + } + + .card-content { + flex: 1; + } +} + +/* Large container: enhanced layout */ +@container card (min-width: 600px) { + .card-image { + width: 250px; + } + + .card-title { + font-size: 1.5rem; + } + + .card-actions { + display: flex; + gap: 0.5rem; + } +} +``` + +### Responsive Grid Items + +```css +.grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 1.5rem; +} + +.grid-item { + container-type: inline-size; +} + +.item-content { + padding: 1rem; +} + +/* Item adapts to its own size, not the viewport */ +@container (min-width: 350px) { + .item-content { + padding: 1.5rem; + } + + .item-title { + font-size: 1.25rem; + } +} + +@container (min-width: 500px) { + .item-content { + display: grid; + grid-template-columns: auto 1fr; + gap: 1rem; + } +} +``` + +### Dashboard Widget + +```css +.widget-container { + container: widget / inline-size; +} + +.widget { + --chart-height: 150px; + padding: 1rem; +} + +.widget-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; +} + +.widget-chart { + height: var(--chart-height); +} + +.widget-stats { + display: grid; + grid-template-columns: 1fr; + gap: 0.5rem; +} + +@container widget (min-width: 300px) { + .widget { + --chart-height: 200px; + } + + .widget-stats { + grid-template-columns: repeat(2, 1fr); + } +} + +@container widget (min-width: 500px) { + .widget { + --chart-height: 250px; + padding: 1.5rem; + } + + .widget-stats { + grid-template-columns: repeat(4, 1fr); + } + + .widget-actions { + display: flex; + gap: 0.5rem; + } +} +``` + +### Navigation Component + +```css +.nav-container { + container: nav / inline-size; +} + +.nav { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.nav-link { + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem 1rem; + border-radius: 0.5rem; +} + +.nav-link-text { + display: none; +} + +.nav-link-icon { + width: 1.5rem; + height: 1.5rem; +} + +/* Show text when container is wide enough */ +@container nav (min-width: 200px) { + .nav-link-text { + display: block; + } +} + +/* Horizontal layout for wider containers */ +@container nav (min-width: 600px) { + .nav { + flex-direction: row; + } + + .nav-link { + padding: 0.5rem 1rem; + } +} +``` + +## Tailwind CSS Integration + +```tsx +// Tailwind v3.2+ supports container queries +// tailwind.config.js +module.exports = { + plugins: [require("@tailwindcss/container-queries")], +}; + +// Component usage +function Card({ title, image, description }) { + return ( + // @container creates containment context +
+
+ +
+

+ {title} +

+

+ {description} +

+
+
+
+ ); +} + +// Named containers +function Dashboard() { + return ( +
+ +
{/* ... */}
+
+ ); +} +``` + +## Fallback Strategies + +```css +/* Provide fallbacks for browsers without support */ +.card { + /* Default (fallback) styles */ + display: flex; + flex-direction: column; +} + +/* Feature query for container support */ +@supports (container-type: inline-size) { + .card-container { + container-type: inline-size; + } + + @container (min-width: 400px) { + .card { + flex-direction: row; + } + } +} + +/* Alternative: media query fallback */ +.card { + display: flex; + flex-direction: column; +} + +/* Viewport-based fallback */ +@media (min-width: 768px) { + .card { + flex-direction: row; + } +} + +/* Enhanced with container queries when supported */ +@supports (container-type: inline-size) { + @media (min-width: 768px) { + .card { + flex-direction: column; /* Reset */ + } + } + + @container (min-width: 400px) { + .card { + flex-direction: row; + } + } +} +``` + +## Performance Considerations + +```css +/* Avoid over-nesting containers */ +/* Bad: Too many nested containers */ +.level-1 { + container-type: inline-size; +} +.level-2 { + container-type: inline-size; +} +.level-3 { + container-type: inline-size; +} +.level-4 { + container-type: inline-size; +} + +/* Good: Strategic container placement */ +.component-wrapper { + container-type: inline-size; +} + +/* Use inline-size instead of size when possible */ +/* size containment is more expensive */ +.container { + container-type: inline-size; /* Preferred */ + /* container-type: size; */ /* Only when needed */ +} +``` + +## Testing Container Queries + +```javascript +// Test container query support +const supportsContainerQueries = CSS.supports("container-type", "inline-size"); + +// Resize observer for testing +const observer = new ResizeObserver((entries) => { + for (const entry of entries) { + console.log("Container width:", entry.contentRect.width); + } +}); + +observer.observe(document.querySelector(".container")); +``` + +## Resources + +- [MDN Container Queries](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_container_queries) +- [CSS Container Queries Spec](https://www.w3.org/TR/css-contain-3/) +- [Una Kravets: Container Queries](https://web.dev/cq-stable/) +- [Ahmad Shadeed: Container Queries Guide](https://ishadeed.com/article/container-queries-are-finally-here/) diff --git a/.claude/skills/responsive-design/references/fluid-layouts.md b/.claude/skills/responsive-design/references/fluid-layouts.md new file mode 100644 index 0000000..29fde9c --- /dev/null +++ b/.claude/skills/responsive-design/references/fluid-layouts.md @@ -0,0 +1,538 @@ +# Fluid Layouts and Typography + +## Overview + +Fluid design creates smooth scaling experiences by using relative units and mathematical functions instead of fixed breakpoints. This approach reduces the need for media queries and creates more natural-feeling interfaces. + +## Fluid Typography + +### The clamp() Function + +```css +/* clamp(minimum, preferred, maximum) */ +.heading { + /* Never smaller than 1.5rem, never larger than 3rem */ + /* Scales at 5vw between those values */ + font-size: clamp(1.5rem, 5vw, 3rem); +} +``` + +### Calculating Fluid Values + +The preferred value in `clamp()` typically combines a base size with a viewport-relative portion: + +```css +/* Formula: clamp(min, base + scale * vw, max) */ + +/* For text that scales from 16px (320px viewport) to 24px (1200px viewport): */ +/* slope = (24 - 16) / (1200 - 320) = 8 / 880 = 0.00909 */ +/* y-intercept = 16 - 0.00909 * 320 = 13.09px = 0.818rem */ + +.text { + font-size: clamp(1rem, 0.818rem + 0.909vw, 1.5rem); +} +``` + +### Type Scale Generator + +```javascript +// Generate a fluid type scale +function fluidType({ + minFontSize, + maxFontSize, + minViewport = 320, + maxViewport = 1200, +}) { + const minFontRem = minFontSize / 16; + const maxFontRem = maxFontSize / 16; + const minViewportRem = minViewport / 16; + const maxViewportRem = maxViewport / 16; + + const slope = (maxFontRem - minFontRem) / (maxViewportRem - minViewportRem); + const yAxisIntersection = minFontRem - slope * minViewportRem; + + return `clamp(${minFontRem}rem, ${yAxisIntersection.toFixed(4)}rem + ${(slope * 100).toFixed(4)}vw, ${maxFontRem}rem)`; +} + +// Usage +const typeScale = { + xs: fluidType({ minFontSize: 12, maxFontSize: 14 }), + sm: fluidType({ minFontSize: 14, maxFontSize: 16 }), + base: fluidType({ minFontSize: 16, maxFontSize: 18 }), + lg: fluidType({ minFontSize: 18, maxFontSize: 20 }), + xl: fluidType({ minFontSize: 20, maxFontSize: 24 }), + "2xl": fluidType({ minFontSize: 24, maxFontSize: 32 }), + "3xl": fluidType({ minFontSize: 30, maxFontSize: 48 }), + "4xl": fluidType({ minFontSize: 36, maxFontSize: 60 }), +}; +``` + +### Complete Type Scale + +```css +:root { + /* Base: 16-18px */ + --text-base: clamp(1rem, 0.9rem + 0.5vw, 1.125rem); + + /* Smaller sizes */ + --text-sm: clamp(0.875rem, 0.8rem + 0.375vw, 1rem); + --text-xs: clamp(0.75rem, 0.7rem + 0.25vw, 0.875rem); + + /* Larger sizes */ + --text-lg: clamp(1.125rem, 1rem + 0.625vw, 1.25rem); + --text-xl: clamp(1.25rem, 1.1rem + 0.75vw, 1.5rem); + --text-2xl: clamp(1.5rem, 1.2rem + 1.5vw, 2rem); + --text-3xl: clamp(1.875rem, 1.4rem + 2.375vw, 2.5rem); + --text-4xl: clamp(2.25rem, 1.5rem + 3.75vw, 3.5rem); + --text-5xl: clamp(3rem, 1.8rem + 6vw, 5rem); + + /* Line heights scale inversely */ + --leading-tight: 1.25; + --leading-normal: 1.5; + --leading-relaxed: 1.75; +} + +/* Apply to elements */ +body { + font-size: var(--text-base); + line-height: var(--leading-normal); +} + +h1 { + font-size: var(--text-4xl); + line-height: var(--leading-tight); +} +h2 { + font-size: var(--text-3xl); + line-height: var(--leading-tight); +} +h3 { + font-size: var(--text-2xl); + line-height: var(--leading-tight); +} +h4 { + font-size: var(--text-xl); + line-height: var(--leading-normal); +} +h5 { + font-size: var(--text-lg); + line-height: var(--leading-normal); +} +h6 { + font-size: var(--text-base); + line-height: var(--leading-normal); +} + +small { + font-size: var(--text-sm); +} +``` + +## Fluid Spacing + +### Spacing Scale + +```css +:root { + /* Spacing tokens that scale with viewport */ + --space-3xs: clamp(0.25rem, 0.2rem + 0.25vw, 0.375rem); + --space-2xs: clamp(0.375rem, 0.3rem + 0.375vw, 0.5rem); + --space-xs: clamp(0.5rem, 0.4rem + 0.5vw, 0.75rem); + --space-sm: clamp(0.75rem, 0.6rem + 0.75vw, 1rem); + --space-md: clamp(1rem, 0.8rem + 1vw, 1.5rem); + --space-lg: clamp(1.5rem, 1.2rem + 1.5vw, 2rem); + --space-xl: clamp(2rem, 1.5rem + 2.5vw, 3rem); + --space-2xl: clamp(3rem, 2rem + 5vw, 5rem); + --space-3xl: clamp(4rem, 2.5rem + 7.5vw, 8rem); + + /* One-up pairs (for asymmetric spacing) */ + --space-xs-sm: clamp(0.5rem, 0.3rem + 1vw, 1rem); + --space-sm-md: clamp(0.75rem, 0.5rem + 1.25vw, 1.5rem); + --space-md-lg: clamp(1rem, 0.6rem + 2vw, 2rem); + --space-lg-xl: clamp(1.5rem, 1rem + 2.5vw, 3rem); +} + +/* Usage examples */ +.section { + padding-block: var(--space-xl); + padding-inline: var(--space-md); +} + +.card { + padding: var(--space-md); + gap: var(--space-sm); +} + +.stack > * + * { + margin-top: var(--space-md); +} +``` + +### Container Widths + +```css +:root { + /* Fluid max-widths */ + --container-xs: min(100% - 2rem, 20rem); + --container-sm: min(100% - 2rem, 30rem); + --container-md: min(100% - 2rem, 45rem); + --container-lg: min(100% - 2rem, 65rem); + --container-xl: min(100% - 3rem, 80rem); + --container-2xl: min(100% - 4rem, 96rem); +} + +.container { + width: var(--container-lg); + margin-inline: auto; +} + +.prose { + max-width: var(--container-md); +} + +.full-bleed { + width: 100vw; + margin-inline: calc(-50vw + 50%); +} +``` + +## CSS Grid Fluid Layouts + +### Auto-fit Grid + +```css +/* Grid that fills available space */ +.auto-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(100%, 250px), 1fr)); + gap: var(--space-md); +} + +/* With maximum columns */ +.auto-grid-max-4 { + display: grid; + grid-template-columns: repeat( + auto-fit, + minmax(min(100%, max(200px, calc((100% - 3 * var(--space-md)) / 4))), 1fr) + ); + gap: var(--space-md); +} +``` + +### Responsive Grid Areas + +```css +.page-grid { + display: grid; + grid-template-columns: + 1fr + min(var(--container-lg), 100%) + 1fr; + grid-template-rows: auto 1fr auto; +} + +.page-grid > * { + grid-column: 2; +} + +.full-width { + grid-column: 1 / -1; +} + +/* Content with sidebar */ +.content-grid { + display: grid; + grid-template-columns: 1fr; + gap: var(--space-lg); +} + +@media (min-width: 768px) { + .content-grid { + grid-template-columns: 1fr min(300px, 30%); + } +} +``` + +### Fluid Aspect Ratios + +```css +/* Maintain aspect ratio fluidly */ +.aspect-video { + aspect-ratio: 16 / 9; +} + +.aspect-square { + aspect-ratio: 1; +} + +/* Fluid aspect ratio that changes */ +.hero-image { + aspect-ratio: 1; /* Mobile: square */ +} + +@media (min-width: 640px) { + .hero-image { + aspect-ratio: 4 / 3; + } +} + +@media (min-width: 1024px) { + .hero-image { + aspect-ratio: 16 / 9; + } +} +``` + +## Flexbox Fluid Patterns + +### Flexible Sidebar + +```css +/* Sidebar that collapses when too narrow */ +.with-sidebar { + display: flex; + flex-wrap: wrap; + gap: var(--space-lg); +} + +.with-sidebar > :first-child { + flex-basis: 300px; + flex-grow: 1; +} + +.with-sidebar > :last-child { + flex-basis: 0; + flex-grow: 999; + min-width: 60%; +} +``` + +### Cluster Layout + +```css +/* Items cluster and wrap naturally */ +.cluster { + display: flex; + flex-wrap: wrap; + gap: var(--space-sm); + justify-content: flex-start; + align-items: center; +} + +/* Center-aligned cluster */ +.cluster-center { + display: flex; + flex-wrap: wrap; + gap: var(--space-sm); + justify-content: center; + align-items: center; +} + +/* Space-between cluster */ +.cluster-spread { + display: flex; + flex-wrap: wrap; + gap: var(--space-sm); + justify-content: space-between; + align-items: center; +} +``` + +### Switcher Layout + +```css +/* Switches from horizontal to vertical based on container */ +.switcher { + display: flex; + flex-wrap: wrap; + gap: var(--space-md); +} + +.switcher > * { + /* Items go vertical when container is narrower than threshold */ + flex-grow: 1; + flex-basis: calc((30rem - 100%) * 999); +} + +/* Limit columns */ +.switcher > :nth-last-child(n + 4), +.switcher > :nth-last-child(n + 4) ~ * { + flex-basis: 100%; +} +``` + +## Intrinsic Sizing + +### Content-Based Widths + +```css +/* Size based on content */ +.fit-content { + width: fit-content; + max-width: 100%; +} + +/* Minimum content size */ +.min-content { + width: min-content; +} + +/* Maximum content size */ +.max-content { + width: max-content; +} + +/* Practical examples */ +.button { + width: fit-content; + min-width: 8rem; /* Prevent too-narrow buttons */ + padding-inline: var(--space-md); +} + +.tag { + width: fit-content; + padding: var(--space-2xs) var(--space-xs); +} + +.modal { + width: min(90vw, 600px); + max-height: min(90vh, 800px); +} +``` + +### min() and max() Functions + +```css +/* Responsive sizing without media queries */ +.container { + /* 90% of viewport or 1200px, whichever is smaller */ + width: min(90%, 1200px); + margin-inline: auto; +} + +.hero-text { + /* At least 2rem, at most 4rem */ + font-size: max(2rem, min(5vw, 4rem)); +} + +.sidebar { + /* At least 200px, at most 25% of parent */ + width: max(200px, min(300px, 25%)); +} + +.card-grid { + /* Each card at least 200px, fill available space */ + grid-template-columns: repeat(auto-fit, minmax(max(200px, 100%/4), 1fr)); +} +``` + +## Viewport Units + +### Modern Viewport Units + +```css +/* Dynamic viewport height - accounts for mobile browser UI */ +.full-height { + min-height: 100dvh; +} + +/* Small viewport - minimum size when UI is visible */ +.hero { + min-height: 100svh; +} + +/* Large viewport - maximum size when UI is hidden */ +.backdrop { + height: 100lvh; +} + +/* Viewport-relative positioning */ +.fixed-nav { + position: fixed; + inset-inline: 0; + top: 0; + height: max(60px, 8vh); +} + +/* Safe area insets for notched devices */ +.safe-area { + padding-top: env(safe-area-inset-top); + padding-right: env(safe-area-inset-right); + padding-bottom: env(safe-area-inset-bottom); + padding-left: env(safe-area-inset-left); +} +``` + +### Combining Viewport and Container Units + +```css +/* Responsive based on both viewport and container */ +.component { + container-type: inline-size; +} + +.component-text { + /* Uses viewport when small, container when in container */ + font-size: clamp(1rem, 2vw + 0.5rem, 1.5rem); +} + +@container (min-width: 400px) { + .component-text { + font-size: clamp(1rem, 4cqi, 1.5rem); + } +} +``` + +## Utility Classes + +```css +/* Tailwind-style fluid utilities */ +.text-fluid-sm { + font-size: var(--text-sm); +} +.text-fluid-base { + font-size: var(--text-base); +} +.text-fluid-lg { + font-size: var(--text-lg); +} +.text-fluid-xl { + font-size: var(--text-xl); +} +.text-fluid-2xl { + font-size: var(--text-2xl); +} +.text-fluid-3xl { + font-size: var(--text-3xl); +} +.text-fluid-4xl { + font-size: var(--text-4xl); +} + +.p-fluid-sm { + padding: var(--space-sm); +} +.p-fluid-md { + padding: var(--space-md); +} +.p-fluid-lg { + padding: var(--space-lg); +} + +.gap-fluid-sm { + gap: var(--space-sm); +} +.gap-fluid-md { + gap: var(--space-md); +} +.gap-fluid-lg { + gap: var(--space-lg); +} +``` + +## Resources + +- [Utopia Fluid Type Calculator](https://utopia.fyi/) +- [Modern Fluid Typography](https://www.smashingmagazine.com/2022/01/modern-fluid-typography-css-clamp/) +- [Every Layout](https://every-layout.dev/) +- [CSS min(), max(), and clamp()](https://web.dev/min-max-clamp/) diff --git a/.claude/skills/tailwind-design-system/.openskills.json b/.claude/skills/tailwind-design-system/.openskills.json new file mode 100644 index 0000000..632555d --- /dev/null +++ b/.claude/skills/tailwind-design-system/.openskills.json @@ -0,0 +1,6 @@ +{ + "source": "/tmp/skill-selector-curated-3996165046", + "sourceType": "local", + "localPath": "/tmp/skill-selector-curated-3996165046/tailwind-design-system", + "installedAt": "2026-04-08T03:16:13.043Z" +} \ No newline at end of file diff --git a/.claude/skills/tailwind-design-system/SKILL.md b/.claude/skills/tailwind-design-system/SKILL.md new file mode 100644 index 0000000..2845509 --- /dev/null +++ b/.claude/skills/tailwind-design-system/SKILL.md @@ -0,0 +1,36 @@ +--- +name: tailwind-design-system +description: "Build production-ready design systems with Tailwind CSS, including design tokens, component variants, responsive patterns, and accessibility." +risk: safe +source: community +date_added: "2026-02-27" +--- + +# Tailwind Design System + +Build production-ready design systems with Tailwind CSS, including design tokens, component variants, responsive patterns, and accessibility. + +## Use this skill when + +- Creating a component library with Tailwind +- Implementing design tokens and theming +- Building responsive and accessible components +- Standardizing UI patterns across a codebase +- Migrating to or extending Tailwind CSS +- Setting up dark mode and color schemes + +## Do not use this skill when + +- The task is unrelated to tailwind design system +- You need a different domain or tool outside this scope + +## Instructions + +- Clarify goals, constraints, and required inputs. +- Apply relevant best practices and validate outcomes. +- Provide actionable steps and verification. +- If detailed examples are required, open `resources/implementation-playbook.md`. + +## Resources + +- `resources/implementation-playbook.md` for detailed patterns and examples. diff --git a/.claude/skills/tailwind-design-system/resources/implementation-playbook.md b/.claude/skills/tailwind-design-system/resources/implementation-playbook.md new file mode 100644 index 0000000..aa902cc --- /dev/null +++ b/.claude/skills/tailwind-design-system/resources/implementation-playbook.md @@ -0,0 +1,665 @@ +# Tailwind Design System Implementation Playbook + +This file contains detailed patterns, checklists, and code samples referenced by the skill. + +# Tailwind Design System + +Build production-ready design systems with Tailwind CSS, including design tokens, component variants, responsive patterns, and accessibility. + +## When to Use This Skill + +- Creating a component library with Tailwind +- Implementing design tokens and theming +- Building responsive and accessible components +- Standardizing UI patterns across a codebase +- Migrating to or extending Tailwind CSS +- Setting up dark mode and color schemes + +## Core Concepts + +### 1. Design Token Hierarchy + +``` +Brand Tokens (abstract) + └── Semantic Tokens (purpose) + └── Component Tokens (specific) + +Example: + blue-500 → primary → button-bg +``` + +### 2. Component Architecture + +``` +Base styles → Variants → Sizes → States → Overrides +``` + +## Quick Start + +```typescript +// tailwind.config.ts +import type { Config } from 'tailwindcss' + +const config: Config = { + content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'], + darkMode: 'class', + theme: { + extend: { + colors: { + // Semantic color tokens + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))', + }, + secondary: { + DEFAULT: 'hsl(var(--secondary))', + foreground: 'hsl(var(--secondary-foreground))', + }, + destructive: { + DEFAULT: 'hsl(var(--destructive))', + foreground: 'hsl(var(--destructive-foreground))', + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))', + }, + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))', + }, + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + border: 'hsl(var(--border))', + ring: 'hsl(var(--ring))', + }, + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)', + }, + }, + }, + plugins: [require('tailwindcss-animate')], +} + +export default config +``` + +```css +/* globals.css */ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 222.2 84% 4.9%; + --primary: 222.2 47.4% 11.2%; + --primary-foreground: 210 40% 98%; + --secondary: 210 40% 96.1%; + --secondary-foreground: 222.2 47.4% 11.2%; + --muted: 210 40% 96.1%; + --muted-foreground: 215.4 16.3% 46.9%; + --accent: 210 40% 96.1%; + --accent-foreground: 222.2 47.4% 11.2%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 210 40% 98%; + --border: 214.3 31.8% 91.4%; + --ring: 222.2 84% 4.9%; + --radius: 0.5rem; + } + + .dark { + --background: 222.2 84% 4.9%; + --foreground: 210 40% 98%; + --primary: 210 40% 98%; + --primary-foreground: 222.2 47.4% 11.2%; + --secondary: 217.2 32.6% 17.5%; + --secondary-foreground: 210 40% 98%; + --muted: 217.2 32.6% 17.5%; + --muted-foreground: 215 20.2% 65.1%; + --accent: 217.2 32.6% 17.5%; + --accent-foreground: 210 40% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 210 40% 98%; + --border: 217.2 32.6% 17.5%; + --ring: 212.7 26.8% 83.9%; + } +} +``` + +## Patterns + +### Pattern 1: CVA (Class Variance Authority) Components + +```typescript +// components/ui/button.tsx +import { cva, type VariantProps } from 'class-variance-authority' +import { forwardRef } from 'react' +import { cn } from '@/lib/utils' + +const buttonVariants = cva( + // Base styles + 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50', + { + variants: { + variant: { + default: 'bg-primary text-primary-foreground hover:bg-primary/90', + destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90', + outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground', + secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80', + ghost: 'hover:bg-accent hover:text-accent-foreground', + link: 'text-primary underline-offset-4 hover:underline', + }, + size: { + default: 'h-10 px-4 py-2', + sm: 'h-9 rounded-md px-3', + lg: 'h-11 rounded-md px-8', + icon: 'h-10 w-10', + }, + }, + defaultVariants: { + variant: 'default', + size: 'default', + }, + } +) + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean +} + +const Button = forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : 'button' + return ( + + ) + } +) +Button.displayName = 'Button' + +export { Button, buttonVariants } + +// Usage + + + +``` + +### Pattern 2: Compound Components + +```typescript +// components/ui/card.tsx +import { cn } from '@/lib/utils' +import { forwardRef } from 'react' + +const Card = forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +) +Card.displayName = 'Card' + +const CardHeader = forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +) +CardHeader.displayName = 'CardHeader' + +const CardTitle = forwardRef>( + ({ className, ...props }, ref) => ( +

+ ) +) +CardTitle.displayName = 'CardTitle' + +const CardDescription = forwardRef>( + ({ className, ...props }, ref) => ( +

+ ) +) +CardDescription.displayName = 'CardDescription' + +const CardContent = forwardRef>( + ({ className, ...props }, ref) => ( +

+ ) +) +CardContent.displayName = 'CardContent' + +const CardFooter = forwardRef>( + ({ className, ...props }, ref) => ( +
+ ) +) +CardFooter.displayName = 'CardFooter' + +export { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } + +// Usage + + + Account + Manage your account settings + + +
...
+
+ + + +
+``` + +### Pattern 3: Form Components + +```typescript +// components/ui/input.tsx +import { forwardRef } from 'react' +import { cn } from '@/lib/utils' + +export interface InputProps extends React.InputHTMLAttributes { + error?: string +} + +const Input = forwardRef( + ({ className, type, error, ...props }, ref) => { + return ( +
+ + {error && ( + + )} +
+ ) + } +) +Input.displayName = 'Input' + +// components/ui/label.tsx +import { cva, type VariantProps } from 'class-variance-authority' + +const labelVariants = cva( + 'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70' +) + +const Label = forwardRef>( + ({ className, ...props }, ref) => ( +