diff --git a/AGENTS.md b/AGENTS.md index d98c9d3aa..03e9b0a6b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # Impeccable -The vocabulary you didn't know you needed. 1 skill, 15 commands, and curated anti-patterns for impeccable style. Works with Cursor, Claude Code, Gemini CLI, and Codex CLI. +The vocabulary you didn't know you needed. 1 skill, 17 commands, and curated anti-patterns for impeccable style. Works with Cursor, Claude Code, Gemini CLI, and Codex CLI. ## Repository Purpose diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..9c8b27d6b --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,36 @@ +# Project Instructions for Claude + +## CSS Build Process + +**IMPORTANT**: After modifying any CSS files in `public/css/` (especially `workflow.css` or `main.css`), you MUST rebuild the Tailwind CSS: + +```bash +bunx @tailwindcss/cli -i public/css/main.css -o public/css/styles.css +``` + +The CSS architecture: +- `public/css/main.css` - Main entry point, imports Tailwind and all other CSS files +- `public/css/workflow.css` - Commands section, glass terminal, case studies styles +- `public/css/styles.css` - **Compiled output** (do not edit directly) + +## Development Server + +```bash +bun run dev +``` + +Runs at http://localhost:3000 + +## Build System + +The build system compiles skills and commands from `source/` to provider-specific formats in `dist/`: + +```bash +bun run build # Build all providers +bun run rebuild # Clean and rebuild +``` + +Source files use placeholders that get replaced per-provider: +- `{{model}}` - Model name (Claude, Gemini, GPT, etc.) +- `{{config_file}}` - Config file name (CLAUDE.md, .cursorrules, etc.) +- `{{ask_instruction}}` - How to ask user questions diff --git a/README.md b/README.md index a5ef4b979..a3ad7b951 100644 --- a/README.md +++ b/README.md @@ -11,25 +11,25 @@ Anthropic created [frontend-design](https://github.com/anthropics/skills/tree/ma Every LLM learned from the same generic templates. Without guidance, you get the same predictable mistakes: Inter font, purple gradients, cards nested in cards, gray text on colored backgrounds. Impeccable fights that bias with: -- **An expanded skill** with 7 domain-specific reference files -- **15 steering commands** to audit, polish, simplify, animate, and more +- **An expanded skill** with 7 domain-specific reference files ([view source](source/skills/frontend-design/)) +- **17 steering commands** to audit, review, polish, simplify, animate, and more - **Curated anti-patterns** that explicitly tell the AI what NOT to do ## What's Included ### The Skill: frontend-design -A comprehensive design skill with 7 domain-specific references: +A comprehensive design skill with 7 domain-specific references ([view skill](source/skills/frontend-design/SKILL.md)): | Reference | Covers | |-----------|--------| -| typography | Type systems, font pairing, modular scales, OpenType | -| color-and-contrast | OKLCH, tinted neutrals, dark mode, accessibility | -| spatial-design | Spacing systems, grids, visual hierarchy | -| motion-design | Easing curves, staggering, reduced motion | -| interaction-design | Forms, focus states, loading patterns | -| responsive-design | Mobile-first, fluid design, container queries | -| ux-writing | Button labels, error messages, empty states | +| [typography](source/skills/frontend-design/reference/typography.md) | Type systems, font pairing, modular scales, OpenType | +| [color-and-contrast](source/skills/frontend-design/reference/color-and-contrast.md) | OKLCH, tinted neutrals, dark mode, accessibility | +| [spatial-design](source/skills/frontend-design/reference/spatial-design.md) | Spacing systems, grids, visual hierarchy | +| [motion-design](source/skills/frontend-design/reference/motion-design.md) | Easing curves, staggering, reduced motion | +| [interaction-design](source/skills/frontend-design/reference/interaction-design.md) | Forms, focus states, loading patterns | +| [responsive-design](source/skills/frontend-design/reference/responsive-design.md) | Mobile-first, fluid design, container queries | +| [ux-writing](source/skills/frontend-design/reference/ux-writing.md) | Button labels, error messages, empty states | ### 17 Commands @@ -63,6 +63,10 @@ The skill includes explicit guidance on what to avoid: - Don't wrap everything in cards or nest cards inside cards - Don't use bounce/elastic easing (feels dated) +## See It In Action + +Visit [impeccable.style](https://impeccable.style#casestudies) to see before/after case studies of real projects transformed with Impeccable commands. + ## Installation ### Option 1: Download from Website (Recommended) diff --git a/public/app.js b/public/app.js index ea763a2ea..a1117b324 100644 --- a/public/app.js +++ b/public/app.js @@ -224,6 +224,79 @@ document.addEventListener("click", (e) => { } }); +// ============================================ +// CASE STUDIES TABS +// ============================================ + +function initCaseStudyTabs() { + const container = document.querySelector('.transformations-tabbed'); + if (!container) return; + + const tabs = container.querySelectorAll('.transformation-tab'); + const panels = container.querySelectorAll('.transformation-panel'); + + tabs.forEach(tab => { + tab.addEventListener('click', () => { + const tabIndex = tab.dataset.tab; + + // Update tabs + tabs.forEach(t => { + t.classList.remove('active'); + t.setAttribute('aria-selected', 'false'); + }); + tab.classList.add('active'); + tab.setAttribute('aria-selected', 'true'); + + // Update panels + panels.forEach(p => { + p.classList.remove('active'); + }); + const activePanel = container.querySelector(`[data-panel="${tabIndex}"]`); + if (activePanel) { + activePanel.classList.add('active'); + } + }); + }); +} + +// ============================================ +// LIGHTBOX +// ============================================ + +function initLightbox() { + const lightbox = document.getElementById('image-lightbox'); + if (!lightbox) return; + + const lightboxImage = lightbox.querySelector('.lightbox-image'); + const closeBtn = lightbox.querySelector('.lightbox-close'); + + // Click on transformation images to open lightbox + document.querySelectorAll('.transformation-before img, .transformation-after img').forEach(img => { + img.addEventListener('click', () => { + lightboxImage.src = img.src; + lightboxImage.alt = img.alt; + lightbox.classList.add('active'); + document.body.style.overflow = 'hidden'; + }); + }); + + // Close lightbox + function closeLightbox() { + lightbox.classList.remove('active'); + document.body.style.overflow = ''; + } + + closeBtn.addEventListener('click', closeLightbox); + lightbox.addEventListener('click', (e) => { + if (e.target === lightbox) closeLightbox(); + }); + document.addEventListener('keydown', (e) => { + if (e.key === 'Escape' && lightbox.classList.contains('active')) { + closeLightbox(); + } + }); +} + // ============================================ // STARTUP // ============================================ @@ -237,6 +310,8 @@ function init() { initScrollReveal(); initGlassTerminal(); initFrameworkViz(); + initLightbox(); + initCaseStudyTabs(); loadContent(); document.body.classList.add("loaded"); diff --git a/public/css/workflow.css b/public/css/workflow.css index 05a1124d4..4de1a3d89 100644 --- a/public/css/workflow.css +++ b/public/css/workflow.css @@ -109,7 +109,7 @@ color: var(--color-ink); } -/* Right: The Terminal (Sticky) */ +/* Right: The Terminal Stack (Sticky) */ .glass-terminal-wrapper { position: sticky; top: var(--spacing-lg); @@ -119,6 +119,138 @@ min-height: 500px; } +/* Stacked Windows Container */ +.terminal-stack { + position: relative; + height: 100%; + perspective: 1200px; +} + +.terminal-stack-tabs { + position: absolute; + top: -31px; + right: 8px; + display: flex; + gap: 4px; + z-index: 10; +} + +.terminal-stack-tab { + font-family: var(--font-mono); + font-size: 0.75rem; + padding: 5px 12px; + background: var(--color-mist); + border: 1px solid var(--color-mist); + border-bottom: none; + border-radius: 6px 6px 0 0; + color: var(--color-ash); + cursor: pointer; + transition: all 0.2s ease; +} + +.terminal-stack-tab:hover { + background: var(--color-paper); + color: var(--color-charcoal); +} + +.terminal-stack-tab.active { + background: var(--color-paper); + color: var(--color-ink); + border-color: var(--color-mist); +} + +/* Individual Windows */ +.terminal-window { + position: absolute; + inset: 0; + transition: transform 0.4s cubic-bezier(0.4, 0, 0.2, 1), + opacity 0.3s ease, + filter 0.3s ease; + transform-origin: center bottom; +} + +/* Demo window (front by default) */ +.terminal-window--demo { + z-index: 2; +} + +.terminal-window--demo.is-back { + transform: translateY(16px) translateX(12px) scale(0.96); + opacity: 0.6; + filter: brightness(0.92); + pointer-events: none; + z-index: 1; +} + +/* Source window (back by default) */ +.terminal-window--source { + z-index: 1; + transform: translateY(16px) translateX(12px) scale(0.96); + opacity: 0.6; + filter: brightness(0.92); + pointer-events: none; +} + +.terminal-window--source.is-front { + transform: translateY(0) translateX(0) scale(1); + opacity: 1; + filter: brightness(1); + pointer-events: auto; + z-index: 2; +} + +/* Source Window Content */ +.source-window { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(12px); + -webkit-backdrop-filter: blur(12px); + border: 1px solid var(--color-mist); + border-radius: 8px; + box-shadow: + 0 20px 60px -10px rgba(0,0,0,0.15), + 0 0 0 1px rgba(255,255,255,0.5) inset; + height: 100%; + display: flex; + flex-direction: column; + overflow: hidden; +} + +.source-header { + background: rgba(0,0,0,0.02); + padding: 12px 16px; + display: flex; + align-items: center; + gap: 8px; + border-bottom: 1px solid var(--color-mist); + flex-shrink: 0; +} + +.source-title { + font-family: var(--font-mono); + font-size: 0.875rem; + color: var(--color-ink); + font-weight: 500; +} + +.source-body { + flex: 1; + padding: var(--spacing-md); + font-family: var(--font-mono); + font-size: 0.75rem; + line-height: 1.5; + color: var(--color-charcoal); + overflow-y: auto; + overscroll-behavior: contain; + white-space: pre-wrap; + word-break: break-word; + background: var(--color-cream); +} + +.source-loading { + color: var(--color-ash); + font-style: italic; +} + @media (max-width: 900px) { .glass-terminal-wrapper { display: none; /* Hide on mobile for now, or stack */ @@ -329,3 +461,237 @@ } @keyframes blink { 50% { opacity: 0; } } + +/* ============================================ + CASE STUDIES / TRANSFORMATIONS SECTION + ============================================ */ + +.casestudies-section { + position: relative; + padding: var(--spacing-2xl) 0; + border-top: 1px solid var(--color-mist); +} + +/* Tabbed transformations */ +.transformations-tabbed { + margin-top: var(--spacing-xl); +} + +.transformation-tabs { + display: flex; + gap: var(--spacing-xs); + border-bottom: 1px solid var(--color-mist); + margin-bottom: var(--spacing-lg); +} + +.transformation-tab { + font-family: var(--font-display); + font-size: 0.9375rem; + font-weight: 500; + color: var(--color-ash); + background: none; + border: none; + padding: var(--spacing-sm) var(--spacing-md); + cursor: pointer; + position: relative; + transition: color 0.2s ease; +} + +.transformation-tab:hover { + color: var(--color-charcoal); +} + +.transformation-tab.active { + color: var(--color-ink); +} + +.transformation-tab.active::after { + content: ''; + position: absolute; + bottom: -1px; + left: 0; + right: 0; + height: 2px; + background: var(--color-accent); +} + +.transformation-panels { + position: relative; +} + +.transformation-panel { + display: none; + flex-direction: column; + gap: var(--spacing-lg); + animation: fadeInPanel 0.3s ease; +} + +.transformation-panel.active { + display: flex; +} + +@keyframes fadeInPanel { + from { + opacity: 0; + transform: translateY(8px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Side-by-side images */ +.transformation-images { + display: flex; + align-items: center; + gap: var(--spacing-md); +} + +.transformation-before, +.transformation-after { + flex: 1; + margin: 0; +} + +.transformation-before img, +.transformation-after img, +.transformation-placeholder { + width: 100%; + aspect-ratio: 16 / 10; + object-fit: cover; + border-radius: 8px; + border: 1px solid var(--color-mist); + cursor: pointer; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.transformation-before img:hover, +.transformation-after img:hover, +.transformation-placeholder:hover { + transform: scale(1.02); + box-shadow: 0 8px 24px -4px rgba(0,0,0,0.15); +} + +.transformation-placeholder { + background: linear-gradient(135deg, var(--color-mist) 0%, var(--color-cream) 100%); + display: flex; + align-items: center; + justify-content: center; + color: var(--color-ash); + font-size: 0.8125rem; + font-style: italic; +} + +.transformation-before figcaption, +.transformation-after figcaption { + font-size: 0.75rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + color: var(--color-ash); + margin-top: var(--spacing-xs); + text-align: center; +} + +.transformation-arrow { + font-size: 1.5rem; + color: var(--color-accent); + font-weight: 300; + flex-shrink: 0; +} + +/* Info section */ +.transformation-info { + max-width: 600px; +} + +.transformation-title { + font-family: var(--font-display); + font-size: 1.25rem; + font-weight: 600; + color: var(--color-ink); + margin: 0 0 var(--spacing-xs); +} + +.transformation-desc { + font-size: 0.9375rem; + color: var(--color-charcoal); + line-height: 1.6; + margin: 0 0 var(--spacing-sm); +} + +.transformation-commands { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.transformation-command { + font-family: var(--font-mono); + font-size: 0.75rem; + background: var(--color-mist); + color: var(--color-charcoal); + padding: 4px 10px; + border-radius: 4px; +} + +/* Lightbox */ +.lightbox { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.9); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + opacity: 0; + visibility: hidden; + transition: opacity 0.3s ease, visibility 0.3s ease; +} + +.lightbox.active { + opacity: 1; + visibility: visible; +} + +.lightbox-close { + position: absolute; + top: 20px; + right: 24px; + background: none; + border: none; + color: white; + font-size: 2.5rem; + cursor: pointer; + opacity: 0.7; + transition: opacity 0.2s ease; + line-height: 1; +} + +.lightbox-close:hover { + opacity: 1; +} + +.lightbox-image { + max-width: 90vw; + max-height: 85vh; + object-fit: contain; + border-radius: 8px; + box-shadow: 0 20px 60px rgba(0,0,0,0.5); +} + +@media (max-width: 768px) { + .transformation-images { + flex-direction: column; + } + + .transformation-arrow { + transform: rotate(90deg); + } + + .transformation-before, + .transformation-after { + width: 100%; + } +} diff --git a/public/index.html b/public/index.html index cddc646bf..b45e18487 100644 --- a/public/index.html +++ b/public/index.html @@ -4,20 +4,20 @@ Impeccable: The missing upgrade to Anthropic's frontend-design skill - + - + - + @@ -66,11 +66,11 @@
Steering Commands - 15 commands + 17 commands
@@ -206,7 +206,7 @@

The Framework

-

One comprehensive skill with deep expertise, plus 15 commands that form the language of design.

+

One comprehensive skill with deep expertise, plus 17 commands that form the language of design.

@@ -232,10 +232,106 @@
- -
+ +
05 +

See It In Action

+

Sample projects transformed with Impeccable commands

+
+ +
+ +
+ + + +
+ + +
+
+
+
+
Before screenshot
+
Before
+
+ +
+
After screenshot
+
After
+
+
+
+

Dashboard Redesign

+

Generic analytics dashboard transformed into a distinctive, scannable interface.

+
+ /audit + /normalize + /bolder +
+
+
+ +
+
+
+
Before screenshot
+
Before
+
+ +
+
After screenshot
+
After
+
+
+
+

Landing Page Polish

+

AI-generated landing page refined from generic template to memorable brand experience.

+
+ /review + /polish + /animate +
+
+
+ +
+
+
+
Before screenshot
+
Before
+
+ +
+
After screenshot
+
After
+
+
+
+

Form UX Overhaul

+

Complex multi-step form simplified and clarified for better completion rates.

+
+ /simplify + /clarify + /harden +
+
+
+
+
+ + + +
+ + +
+
+ 06

Download for Your AI Harness

Same design intelligence, adapted for your workflow. Download and start using in seconds.

@@ -336,6 +432,7 @@ diff --git a/public/js/components/glass-terminal.js b/public/js/components/glass-terminal.js index 348bda1e6..5ba8084cd 100644 --- a/public/js/components/glass-terminal.js +++ b/public/js/components/glass-terminal.js @@ -5,6 +5,7 @@ import { commandProcessSteps, commandCategories, commandRelationships } from ".. // Track current split instance and command for cleanup let currentSplitInstance = null; let currentCommandId = null; +let sourceCache = {}; // Cache fetched source content const MOBILE_BREAKPOINT = 900; @@ -79,17 +80,35 @@ function renderDesktopLayout(container, commands) { ${manualHTML}
-
-
- - - - zsh — 80x24 +
+
+ +
-
-
- - Waiting for input... +
+
+
+ command.md +
+
+ Select a command to view source... +
+
+
+
+
+
+ + + + zsh — 80x24 +
+
+
+ + Waiting for input... +
+
@@ -97,6 +116,8 @@ function renderDesktopLayout(container, commands) {
`; + setupStackTabs(); + setupDesktopScrollSpy(commands); if (commands.length > 0) { @@ -175,6 +196,9 @@ function updateTerminal(cmd, container, allCommands) { if (currentCommandId === cmd.id) return; currentCommandId = cmd.id; + // Also update source content + updateSourceContent(cmd.id); + if (currentSplitInstance) { currentSplitInstance.destroy(); currentSplitInstance = null; @@ -314,3 +338,73 @@ function setupMobileInteractions(commands) { }); } +// ============================================ +// STACKED WINDOWS - Tab Switching +// ============================================ + +function setupStackTabs() { + const tabs = document.querySelectorAll('.terminal-stack-tab'); + const demoWindow = document.querySelector('.terminal-window--demo'); + const sourceWindow = document.querySelector('.terminal-window--source'); + + tabs.forEach(tab => { + tab.addEventListener('click', () => { + const view = tab.dataset.view; + + // Update tab states + tabs.forEach(t => t.classList.remove('active')); + tab.classList.add('active'); + + // Switch windows + if (view === 'source') { + demoWindow.classList.add('is-back'); + sourceWindow.classList.add('is-front'); + } else { + demoWindow.classList.remove('is-back'); + sourceWindow.classList.remove('is-front'); + } + }); + }); +} + +async function fetchCommandSource(cmdId) { + // Check cache first + if (sourceCache[cmdId]) { + return sourceCache[cmdId]; + } + + try { + const response = await fetch(`/api/command-source/${cmdId}`); + if (!response.ok) throw new Error('Failed to fetch source'); + const data = await response.json(); + sourceCache[cmdId] = data.content; + return data.content; + } catch (error) { + console.error('Error fetching command source:', error); + return null; + } +} + +function escapeHtml(text) { + const div = document.createElement('div'); + div.textContent = text; + return div.innerHTML; +} + +async function updateSourceContent(cmdId) { + const titleEl = document.getElementById('source-title'); + const contentEl = document.getElementById('source-content'); + + if (!titleEl || !contentEl) return; + + titleEl.textContent = `${cmdId}.md`; + contentEl.innerHTML = 'Loading...'; + + const source = await fetchCommandSource(cmdId); + if (source) { + contentEl.textContent = source; + } else { + contentEl.innerHTML = 'Source not available'; + } +} + diff --git a/server/index.js b/server/index.js index c554bb3cd..0d7872e44 100644 --- a/server/index.js +++ b/server/index.js @@ -3,6 +3,7 @@ import homepage from "../public/index.html"; import { getSkills, getCommands, + getCommandSource, getPatterns, handleFileDownload, handleBundleDownload @@ -49,6 +50,16 @@ const server = serve({ }, }, + // API: Get command source content + "/api/command-source/:id": async (req) => { + const { id } = req.params; + const content = await getCommandSource(id); + if (!content) { + return Response.json({ error: "Command not found" }, { status: 404 }); + } + return Response.json({ content }); + }, + // API: Download individual file "/api/download/:type/:provider/:id": async (req) => { const { type, provider, id } = req.params; diff --git a/server/lib/api-handlers.js b/server/lib/api-handlers.js index 6ae017d2f..60e8d5422 100644 --- a/server/lib/api-handlers.js +++ b/server/lib/api-handlers.js @@ -72,6 +72,23 @@ export async function getCommands() { return commands; } +// Get command source content +export async function getCommandSource(id) { + const sourceDir = join(PROJECT_ROOT, "source"); + const commandPath = join(sourceDir, "commands", `${id}.md`); + + try { + if (!existsSync(commandPath)) { + return null; + } + const content = await readFileContent(commandPath); + return content; + } catch (error) { + console.error("Error reading command source:", error); + return null; + } +} + // Get the appropriate file path for a provider export function getFilePath(type, provider, id) { const distDir = join(PROJECT_ROOT, "dist");