CSS architecture, design system, templates, UI standards. Load when working on app/templates/*, *.css, *.html, or UI features.
Layer cascade (declared in base.html):
@layer design-system, components, pages;
design-system — tokens, base typography, animations, accessibilitycomponents — reusable component styles (via components/index.css)pages — page-specific overrides (via {% block extra_css %})mobile.css.btn, .form-control, .modal-content, .card, .badge, .empty-state) defined in components/.workflow-editor .form-control { ... }::-webkit-scrollbar) defined once in design-system/index.cssworkflows-modern.css loaded globally — do NOT add per-template <link> tagsAlways use var(--ds-*) variables:
| Element | Use | NEVER Use |
|---|---|---|
| Page wrapper | .page-content-wrapper | Inline style="background:..." |
| Page headers | .page-header-standard | Custom header divs |
| Buttons | .btn .btn-primary | wf-btn, inline styles |
| Colors | var(--ds-*) tokens | Hardcoded hex values |
| Spacing | var(--ds-space-*) | Hardcoded px/rem |
| Shadows | var(--ds-shadow-*) | Hardcoded box-shadow |
Variables file: app/static/css/design-system/_variables.css
Page headers in hybrid pages (Vue/Alpine + Jinja):
When the page uses a JS framework that mounts in a sub-container, the .page-header-standard must be placed OUTSIDE the framework mount point so it renders immediately (no FOUC). The JS app then bridges to header elements via getElementById. See builder.html for the canonical example.
--ds-font-family). No Poppins.bg-white border shadow-sm, not bg-slate-900Use exact names from base.html:
extra_css — Additional stylesheetsextra_scripts — Additional JavaScriptpage_title — Page titlecontent — Main contentFormat: ?v=YYYYMMDD[letter] (e.g., ?v=20260306b)
When changing CSS/JS:
?v= param on all <link>/<script> tags referencing the fileCACHE_NAME in app/static/sw.jsVerification after deploy: If CSS changes do not appear on the live site after deploy, first check that the ?v= param was bumped, then check Railway build status (builds may be failing silently). Use WebFetch against the live URL to compare deployed CSS with the repo version.
Source: app/static/images/courier_transparent.png (RGBA).
Refresh procedure documented in MEMORY: pattern_logo_and_favicon_asset_pipeline.md. Key points:
courier{,_transparent}{,-medium,-thumbnail}.{png,webp,jpeg} so legacy filenames update together.sips cannot output WebP — use .venv/bin/python3 + Pillow.?v=YYYYMMDD[letter] on every nav-logo <img> and favicon <link> in base.html. Landing pages reference assets without ?v= and pick up new files automatically.app/static/css/components/_navigation.css (.cf-nav-logo). Anchor percentage bumps to the original baseline, not the current size.<link> tags in base.htmlbase.html MUST include the four favicon link tags (favicon.ico, favicon-16x16.png, favicon-32x32.png, apple-touch-icon.png) immediately after <link rel="manifest">. errors/404.html previously had them while base.html did not — this was a latent SEO/UX bug fixed in PR #309. Verify presence on any new layout file that doesn't extend base.html.
?v= bumpslanding/index.html, landing/features.html, landing/pricing.html:
landing_base.html, NOT base.htmlrepeat(N, minmax(0, 1fr)) instead of repeat(N, 1fr). Plain 1fr allows content (long text, images) to inflate individual columns beyond their fair share. minmax(0, 1fr) constrains the minimum to 0, forcing truly equal widths.grid-auto-rows: minmax(<min>, 1fr) so rows do not size to content. Without this, rows with more content appear taller than empty rows.Check if existing component/page file already covers the need.
Dead CSS → _archived/css/
Dead templates → _archived/templates/