indiestack-frontend — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited indiestack-frontend (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
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~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.