Files
Oxicloud/docs/DESIGN-SYSTEM.md
T
DioCrafts 81a93a489b feat: photo/video capture-date pipeline + premium UI/UX overhaul
Backend — Photos timeline now groups by real capture date instead of upload time. New MediaMetadataService (FileLifecycleHook) extracts EXIF DateTimeOriginal from images and container creation_time from videos (mov/mp4/mkv) via nom-exif, timezone-correct (OffsetTimeOriginal), persisting captured_at so the existing media_sort_date trigger takes over. Adds POST /admin/photos/metadata/reextract to backfill existing media. Falls back to upload date when no embedded date exists.

Frontend — premium grid cards: combined metadata line (relative date · size, owner avatar when shared), custom selection checkbox with a clear checked state, uniform full-width 4:3 thumbnail tiles independent of filename length, centered file-type icons, and a hit-test fix so checkbox/star/kebab clicks reach the controls (the decorative thumbnail no longer captures pointer events). Notification messages internationalised across all 16 locales. Broader polish: design tokens, a11y/focus-visible states, brand + PWA assets.

Chore — bump semver-compatible dependencies (cargo upgrade); add nom-exif 3.6.1.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 00:24:27 +02:00

106 lines
5.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# OxiCloud Design System
The single source of truth for how the frontend looks, behaves, and stays
accessible. Pairs with [TOKENS.md](TOKENS.md) (auto-generated token reference)
and [UIUX-ROADMAP.md](UIUX-ROADMAP.md) (the work plan).
---
## 1. Design tokens
Everything visual routes through a token in
[`static/css/base/variables.css`](../static/css/base/variables.css). Six scales,
plus a curated color system. **Never hand-write a raw px/hex for these** — consume
a token so the next change happens in one place.
| Axis | Tokens | Notes |
| --- | --- | --- |
| Spacing | `--space-0 … --space-24` | 4px grid (+ half-steps 2/6/10/14). |
| Radius | `--radius-xs … --radius-4xl`, `--radius-full` | `--radius` aliases `--radius-2xl`. |
| Type | `--text-2xs … --text-6xl`, `--leading-*`, `--weight-*`, `--tracking-*` | rem-based (honors zoom). `.heading-page/-section/-card` utilities decouple role from element. |
| Z-index | `--z-base … --z-max` | Semantic layers with gaps; no magic numbers. |
| Motion | `--motion-fast/base/moderate/slow`, `--ease-standard/-emphasized/...` | `--ease-standard` is the decelerate default for entrances. |
| Elevation | `--shadow-xs … --shadow-2xl` | Composed recipes layered on the `--color-shadow-*` alphas. |
**Color.** One brand accent (orange `#ff5e3a`), one neutral slate ramp, and five
semantics (success/warning/danger/info + accent), each unified to a single hue.
Text tiers collapse to a handful that all clear **WCAG AA (4.5:1)** — verified by
[`scripts/check-contrast.mjs`](../scripts/check-contrast.mjs). Use
`--color-accent-text` (not `--color-accent`) for accent *text/links*.
Governance: **one brand accent, one neutral ramp, five semantics — no new hero
hues.** Run `node scripts/gen-token-docs.mjs` after editing tokens.
---
## 2. Accessibility rules (non-negotiable)
These are enforced/aided by CI scripts and must hold for every new surface.
- **Keyboard.** Everything interactive is reachable and operable by keyboard.
Use `<button>`/`<a>` (not clickable `<div>`s). Nav exposes `aria-current="page"`.
- **Focus.** A global `:focus-visible` ring lives in
[`base/a11y.css`](../static/css/base/a11y.css). Never `outline: none` without a
paired `:focus-visible` style. Mouse focus stays ring-free; keyboard focus never.
- **Contrast.** Every text/background pair clears 4.5:1 in **light AND dark** —
`node scripts/check-contrast.mjs` fails the build otherwise.
- **Landmarks + skip link.** `<nav aria-label>`, `role="main"`/`<main id="main">`,
a skip link as the first focusable element, and `lang` on `<html>`.
- **Headings.** Exactly one `h1` per page, no skipped levels —
`node scripts/check-headings.mjs`.
- **Motion.** `@media (prefers-reduced-motion: reduce)` neutralizes animation
globally. Also honor `prefers-contrast` and `forced-colors`.
- **Dialogs.** `role="dialog"` + `aria-modal` + `aria-labelledby`, focus trapped
while open, focus restored to the trigger on close (see
[`modal.js`](../static/js/components/modal.js)).
- **Icon-only buttons.** Always an `aria-label`; decorative icons get
`aria-hidden="true"`.
- **Touch targets.** ≥44×44px on phones.
---
## 3. Brand
- **Mark.** The cloud glyph ([`logo-plain.svg`](../static/logo/logo-plain.svg)).
Always rendered from the real SVG (never a stock `fa-cloud`). On surfaces it
sits in an accent-gradient tile via the `.brand-mark` component.
- **Wordmark.** "OxiCloud", weight 700, tight tracking. The accent-coloured
"Oxi" lockup is the ownable treatment.
- **Accent.** Orange→coral `#ff5e3a → #ff2d55` — warm and distinctive in a
blue-dominated cloud-storage market; nods to Rust oxidation. It is the *only*
brand/primary hue (blue/indigo/purple were demoted to the single info ramp).
- **Logo gradient.** One token (`--color-logo-gradient`) everywhere.
- **Clear-space / min-size.** Keep ≥ the tile's corner-radius of padding around
the mark; don't render the wordmark below ~16px.
- **Don't:** recolor the mark, stretch it, place it on a low-contrast background,
or introduce a second "primary" hue.
- **Maskable / OG.** [`logo-maskable.svg`](../static/logo/logo-maskable.svg) (PWA,
safe-zone) and [`og-image.svg`](../static/logo/og-image.svg) (social). Export
both to PNG for full platform support (see roadmap).
---
## 4. CI guardrails (what keeps the score from regressing)
Run all the dependency-free checks with **`just frontend-check`**:
| Script | Enforces |
| --- | --- |
| `check-contrast.mjs` | WCAG AA on every text/bg + semantic token pair (light + dark). |
| `check-headings.mjs` | One `h1`, no skipped levels, per HTML page. |
| `check-locales.mjs` | Locale completeness + `{placeholder}` integrity across all 16 locales. |
| `check-dead-tokens.mjs` | Reports tokens defined but never referenced (cleanup aid). |
| `check-brand-drift.mjs` | Locks the logo SVG hash + `--color-logo-gradient` against silent change. |
| `gen-token-docs.mjs` | Regenerates [TOKENS.md](TOKENS.md). |
**Build pipeline.** `build.rs` (release) bundles + content-hashes + minifies all
CSS/JS into `static-dist/` using the Rust crates **lightningcss** and **oxc** — no
npm. It also injects early `<link rel="preload">` / `<link rel="modulepreload">`
hints for the hashed bundles. Debug builds (`cargo run`) serve raw `static/`
unprocessed, so any source CSS must be valid as-authored (e.g. `@custom-media`
is off the table — it has no native browser support).
Still to wire (genuinely need npm devDependencies / browsers): stylelint rules
banning raw px/hex outside `:root`, axe-core + Playwright (keyboard,
visual-regression, dark-parity, cross-browser), and Lighthouse-CI perf budgets.