一键导入
indiestack-frontend
Frontend UI polish and design tokens enforcing IndieStack styling
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Frontend UI polish and design tokens enforcing IndieStack styling
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Fact-check an email from Ed against the IndieStack codebase for hallucinations
Facilitate a structured CONVERSATIONAL meeting between Patrick and all orchestra agents — named phases (Diverge → Challenge → Build → Stress Test) with state machine navigation. Agents push back on each other, build on each other's ideas, and the chair navigates phases autonomously. Early exit only after Stress Test completes.
Audit your current project for token-saving opportunities by finding IndieStack tools that replace code you'd write from scratch
| name | indiestack-frontend |
| description | Frontend UI polish and design tokens enforcing IndieStack styling |
Use this skill for ANY visual or UI work on IndieStack — new pages, redesigns, component updates, or polish passes. This enforces the exact aesthetic, tokens, and constraints so nothing drifts.
Linear.app meets Stripe Docs. Clean, precise, confident. Dark mode that looks intentional, not inverted. Warm personality without gratuitous decoration.
References to study: Linear (spatial hierarchy, dark mode confidence, typography weight), Stripe Docs (spacing, code blocks, information density), Vercel (motion, transitions, compositional balance).
Anti-references (NEVER do these):
Pure Python f-string HTML. No React, no Jinja2, no build step. All pages are FastAPI route functions returning HTMLResponse(page_shell(...)). CSS lives in components.py design tokens or inline styles.
File structure:
src/indiestack/routes/components.py — design tokens (:root), nav, footer, shared componentssrc/indiestack/routes/<page>.py — page-specific routes with inline HTMLcomponents.py design_tokens() functioncomponents.py as Python functions/* Primary */ --terracotta: #1A2D4A; /* Navy — legacy name, DO NOT rename */
/* Primary light */ --terracotta-light: #2B4A6E;
/* Primary dark */ --terracotta-dark: #0F1D30;
/* Accent */ --accent / --slate: #00D4F5; /* Cyan */
/* Highlight */ --gold: #E2B764;
/* Surfaces */ --cream: #F7F9FC; --cream-dark: #EDF1F7; --card-bg: white;
/* Text */ --ink: #1A1A2E; --ink-light: #475569; --ink-muted: #64748B;
/* Border */ --border: #E2E8F0;
Rules:
var(--token-name)rgba() with magic numbers — define as a token if needed--accent) for interactive elements and emphasis only — not backgrounds--terracotta) for primary actions and strong emphasis--font-display: 'DM Serif Display', serif; /* Headings ONLY */
--font-body: 'DM Sans', sans-serif; /* Everything else */
--font-mono: 'JetBrains Mono', monospace; /* Code, tags, technical */
Hierarchy (use these exact sizes):
| Level | Size | Weight | Font | Use |
|---|---|---|---|---|
| Page title | clamp(28px, 4vw, 40px) | 400 | Display | One per page |
| Section heading | 22px | 400 | Display | Section breaks |
| Card title | 17px | 400 | Display | Card headers |
| Body | 15px | 400 | Body | Paragraphs, descriptions |
| Label | 14px | 600 | Body | Form labels, nav items |
| Small | 13px | 500 | Body | Metadata, timestamps |
| Micro | 12px | 600 | Mono | Tags, badges, code |
Rules:
0.5px on uppercase labels, 0 elsewhereONLY use these values: 4, 8, 12, 16, 20, 24, 32, 40, 48, 64, 80px
| Token | Value | Use |
|---|---|---|
xs | 4px | Badge padding, icon gaps |
sm | 8px | Tight groupings, inline gaps |
md | 16px | Standard element spacing |
lg | 24px | Card padding, section gaps |
xl | 32px | Between sections |
2xl | 48px | Major section breaks |
3xl | 64px | Page section padding |
4xl | 80px | Hero/CTA vertical padding |
NEVER use: 10px, 15px, 18px, 22px, 28px, 36px, 50px, 60px or any value not on the scale.
/* Light mode */
--shadow-sm: 0 1px 3px rgba(26,45,74,0.06);
--shadow-md: 0 4px 12px rgba(26,45,74,0.08);
--shadow-lg: 0 12px 40px rgba(26,45,74,0.10);
/* Use shadow layering for depth */
/* Resting card: shadow-sm */
/* Hover card: shadow-md + translateY(-2px) */
/* Floating: shadow-lg */
/* Modal/dropdown: shadow-lg + border */
Rules:
box-shadow: var(--shadow-sm) with border: 1px solid var(--border)box-shadow: var(--shadow-md); transform: translateY(-2px) — NOT -3px (too jumpy)var(--shadow-lg) with borderbox-shadow: none on interactive cards12px (--radius)8px (--radius-sm)999px (full round)4px, 6px, or 16px radius/* Standard */ transition: all 0.15s ease;
/* Emphasis */ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
/* Page enter */ animation: fadeUp 0.4s ease-out;
Rules:
transform: scale(1.02) max — never 1.05+transform: scale(0.98) for tactile feedbackanimation-delay in 50ms incrementsf'''<div class="card" style="padding:24px;">
<div style="font-family:var(--font-display);font-size:17px;color:var(--ink);margin-bottom:8px;">{title}</div>
<div style="font-size:13px;color:var(--ink-muted);line-height:1.6;">{description}</div>
</div>'''
Always use .btn class. Never inline-style a button from scratch.
f'<a href="{url}" class="btn btn-primary">Label</a>'
f'<button class="btn btn-secondary">Label</button>'
Use the .pill-filter class (add to components.py if missing):
.pill-filter {
display: inline-flex; align-items: center; gap: 4px;
padding: 8px 16px; border-radius: 999px;
font-size: 13px; font-weight: 600;
border: 1px solid var(--border);
background: var(--card-bg); color: var(--ink-light);
cursor: pointer; transition: all 0.15s ease;
min-height: 36px;
}
.pill-filter:hover { border-color: var(--ink-muted); color: var(--ink); }
.pill-filter.active { background: var(--terracotta); color: white; border-color: var(--terracotta); }
f'''<section style="padding:64px 24px;">
<div class="container" style="max-width:700px;">
<h2 style="font-family:var(--font-display);font-size:22px;color:var(--ink);margin-bottom:24px;">
Section Title
</h2>
<!-- content -->
</div>
</section>'''
Always use .badge class with variant: .badge-success, .badge-info, .badge-warning, .badge-gold, .badge-muted.
border-left: 3px solid var(--accent) for emphasis, not just uniform 1px bordersbackground: linear-gradient(180deg, var(--cream) 0%, var(--card-bg) 100%) for section transitionsbackdrop-filter: blur(12px); background: rgba(247,249,252,0.85) on nav (light) / rgba(15,20,32,0.85) (dark)<select> with styled <details> dropdowns/ shortcut on search, Esc on modalsbackground: linear-gradient(90deg, var(--cream-dark) 25%, var(--cream) 50%, var(--cream-dark) 75%); background-size: 200% 100%; animation: shimmer 1.5s infinite;outline: 2px solid var(--accent); outline-offset: 2px; on all focusable elements.reveal class with IntersectionObserver (already in landing.py, extend to all pages)var(--token)backdrop-filter: blur(12px); background: rgba(15,20,32,0.85);When doing visual work:
python3 -c "import ast; ast.parse(open('file.py').read())" passes