5.3 KiB
CSS system (devplacepy/static/css/)
This file documents the CSS conventions for every stylesheet in this directory. Claude Code auto-loads it when a file under static/css/ is read or edited. The member-facing style guide lives at /docs/styles*.html; keep both in sync when a convention changes.
Tokens (variables.css is the single source of truth)
- Never hardcode a themable value. Colours, spacing (
--space-xs...--space-2xl), radii, shadows, fonts, layout measures, and z-indexes are alwaysvar(--token)references. A missing value means adding a token tovariables.css, not inlining a literal. - Never give a global token a fallback.
var(--accent), nevervar(--accent, #hex). A fallback silently masks a dead or misspelled token - the page keeps rendering from a value nobody maintains. This was a real bug class:var(--topic-discussion, #ef4444),var(--bg-code, #1a1a2e),var(--accent, #6366f1)referenced tokens that never existed and shipped a foreign palette via their fallbacks. - Accent tints compose the channel token:
rgba(var(--accent-rgb), 0.3), never a literalrgba(255, 107, 53, ...). The raw accent literal exists only at its definition invariables.css. - Status is semantic:
--success/--warning/--danger/--infofor state,--topic-*only for topic badges. Never repurpose a topic token as a status colour or vice versa. - One name per value. Do not add alias tokens (
--space-baseand--shadow-mdwere removed for duplicating--space-smand--shadow).
File-scoped palettes
A feature with its own semantic palette defines it ONCE as a custom-property block at the top of its own stylesheet and references only those properties below: devii.css scopes --devii-* on devii-terminal; isslop.css scopes --isslop-* (solid + -rgb channel pairs for alpha composition) on :root (the file loads only on its own pages). Raw hex values may exist in exactly two places: variables.css and these blocks. A repeated hex or rgba() literal below a token block is a defect.
Stacking (--z-* tokens, two bands)
Every z-index is a token from variables.css; literal values are forbidden except small local ladders (z-index: 1/2) inside an already-stacked component.
| Band | Order (bottom to top) |
|---|---|
| Content | --z-fab 900, --z-nav-overlay 998, --z-nav-panel 999, --z-nav 1000, --z-nav-drop 1001, --z-modal 2000, --z-popover 2100, --z-lightbox 10000 |
| Chrome | --z-chrome-fw 2147480000, --z-chrome-devii 2147483000, --z-chrome-toast 2147483400, --z-chrome-menu 2147483500, --z-chrome-dialog 2147483647 |
The chrome band is user-CSS-proof by design: per-user injected CSS (the customization feature) must never stack above floating windows, the Devii terminal, toasts, the context menu, or confirm dialogs, so they live at the top of the integer range AND carry will-change: transform (the layer-promotion group in base.css - any new always-on fixed chrome element must join both). Toasts sit in the chrome band deliberately: they fire over open modals and stay clickable. The Devii tutorial-overlay ladder (2147483600-602 in devii.css) is an intentional local sequence between --z-chrome-menu and the devii avatar. A new stacked element picks the matching token or adds one between two stops in variables.css - never a number at the use site.
Breakpoints (exact, closed set)
@media (max-width: 1024px | 768px | 480px | 360px) plus the capability/preference queries (hover: none) and (pointer: coarse) and (prefers-reduced-motion: reduce). Nothing else. The historical one-offs (900/720/640/600/560/520) were consolidated by snapping each UP to the next canonical stop - snap-up is the required direction because collapsing earlier can never overflow, while snapping down leaves a viewport band the compact styles no longer protect. Element max-width values (content measures like max-width: 720px on an article column) are not breakpoints and are unaffected by this rule.
Reduced motion
One global rule at the end of base.css collapses every animation/transition to 0.01ms under prefers-reduced-motion: reduce. Its four !importants are the documented exception to the no-!important rule: a zero-specificity * selector cannot otherwise beat any class rule, and per-rule opt-outs would need editing every declaration. Durations are near-zero rather than none so JS transitionend/animationend handlers still fire. Pages add their own reduced-motion query ONLY when content must change (e.g. deepsearch.css stopping an indeterminate bar), never to re-disable motion.
Layout rules
- Flexbox + CSS Grid with
gaponly; floats for layout are forbidden. - Approved page layouts and the shared shell are documented in
/docs/styles-layout.html; a page supplies exactly one layout container inside.page. - Every fluid grid/flex column sets
min-width: 0. !importantis allowed only for: the.hidden/[hidden]display utilities, the global reduced-motion rule, and the devii-avatar third-party-beating override. Anything else is a specificity problem to be fixed structurally.- Page-specific CSS lives in its own
static/css/*.cssloaded via{% block extra_head %}, never an inline<style>block; shared component styles live once (components.css,feed.cssvote buttons) and are never redefined per page.