Files
devplacepy/devplacepy/static/css/CLAUDE.md
T
retoor ad1736ebf1
DevPlace CI / test (push) Failing after 1h19m47s
CSSS
2026-07-23 03:03:14 +02:00

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 always var(--token) references. A missing value means adding a token to variables.css, not inlining a literal.
  • Never give a global token a fallback. var(--accent), never var(--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 literal rgba(255, 107, 53, ...). The raw accent literal exists only at its definition in variables.css.
  • Status is semantic: --success/--warning/--danger/--info for 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-base and --shadow-md were removed for duplicating --space-sm and --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 gap only; 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.
  • !important is 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/*.css loaded via {% block extra_head %}, never an inline <style> block; shared component styles live once (components.css, feed.css vote buttons) and are never redefined per page.