원클릭으로
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