diff --git a/.gitignore b/.gitignore index a2ff331..0a7cb1f 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,4 @@ htmlcov/ # IPFS ipfs-data/ +docs/*.pdf diff --git a/CLAUDE.md b/CLAUDE.md index 8c01bfb..f0a41b2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,75 +1,73 @@ # libreDecision -Boîte à outils de gouvernance collective pour la communauté Duniter/G1. -Documents modulaires sous vote permanent + protocoles de vote + mandats. -Architecture marque blanche — vocation à être intégré dans sweethomeCloud et librodrome. +Boîte à outils de la première démocratie — décider ensemble, du trio à la population. +v2 local-first : 6 routes de décision, 5 modalités, documents sous vote permanent, mandats, +observatoire. Marque blanche par construction (seeds Ğ1 + Atelier du Canal). +**Référence produit : `docs/dev/BLUEPRINT-V2.md`** — toute décision de comportement s'y adosse. ## Protocole de début de session 1. `git pull --rebase origin main` -2. Si des migrations DB sont attendues : `cd backend && alembic upgrade head` -3. Si l'objectif de la session n'est pas précisé, le demander +2. Si l'objectif de la session n'est pas précisé, le demander ## Stack -- **Frontend** : Nuxt 4 (Vue 3, TypeScript) + Nuxt UI v3 + Pinia + UnoCSS ; package manager : npm -- **Backend** : Python FastAPI + SQLAlchemy 2.0 async + PostgreSQL asyncpg ; migrations Alembic -- **Auth** : Duniter V2 Ed25519 challenge-response (substrate-interface — stub en dev) -- **Sanctuaire** : IPFS kubo + hash on-chain (system.remark) — TODO sprint 2 -- Déploiement : Docker multi-stage + Traefik (postgres + backend + frontend + ipfs) ; CI Woodpecker +- **Frontend** : Nuxt 4 (Vue 3, TypeScript, SPA — SSR off) + Nuxt UI v3 + Pinia + UnoCSS ; + package manager : npm ; port **3002** strict +- **Local-first** : IndexedDB (idb-keyval) via `app/data/persistence.ts` — une clé + `ld2:` par collectif, export/import de bundles JSON, seeds par le même chemin +- **Backend v1** (FastAPI + SQLAlchemy async, port 8002) : conservé pour la **future synchro** + — non requis pour le dev frontend ; `runtimeConfig.public.apiBase` gardé dans nuxt.config + en prévision +- Déploiement : Docker multi-stage + Traefik ; CI Woodpecker ## Structure ``` frontend/ app/ - components/ # composants Vue - layouts/ # layouts Nuxt - pages/ # routing file-based (9 pages sprint 1) - composables/ # (1 composable sprint 1) - stores/ # 5 Pinia stores (auth, ...) - assets/css/ - moods.css # système de palettes (.mood-* sur ) - utils/ # (2 utils sprint 1) - nuxt.config.ts # port 3002, host 0.0.0.0, apiBase via NUXT_PUBLIC_API_BASE -backend/ - app/ - routers/ # 8 routers : auth, communes, documents, protocols, votes, ... - services/ # 6 services - engine/ # 5 modules : formule inertie, critères Smith/TechComm, médiane - models/ # 14 tables SQLAlchemy - alembic/versions/ # migrations - tests/ # 186 tests (63 intégration TDD sprint 1) - seed.py # Engagement Certification (33 items) + Forgeron (51 items) + Runtime Upgrade -docker/ - docker-compose.yml # postgres + backend + frontend + ipfs - backend.Dockerfile - frontend.Dockerfile -docs/content/ # 7 docs dev + 8 docs user -public/ - hexagram-tsing.svg # sceau 井 (embossed) - hexagram-tsing-flat.svg + engine/ # moteurs PURS (zéro dépendance Nuxt) : threshold (+electionResult), + # nuanced, modeParams, parametric (+crystallize), settings, state (canTransition), + # triage, impact — import UNIQUEMENT via le barrel ~/engine + data/ + persistence.ts # IndexedDB local-first + templates.ts # 7 gabarits de collectif (invariant : protocole Consentement partout) + seeds/ # duniter-g1.bundle.json (324 Ko) + atelier-du-canal.bundle.json + stores/ + collective.ts # le tenant : collectif actif, état complet, cycle de vie des bundles + decisions.ts # façade d'actions sans état propre (LWW updatedAt, persist debounced) + composables/ # useMood (useLibreMood), useFeed (le Fil = sélecteur pur), useSearch + components/ # par domaine : chemin/ decisions/ votes/ texts/ mandates/ feed/ + # onboarding/ common/ (primitives Ld*) + pages/ # 14 routes : / · decider · decisions (+[id], vote, observatoire) · + # textes (+[slug], formules) · mandats (+[id], nouveau) · + # creer (layout bare) · donnees + lexicon.ts # SOURCE UNIQUE des libellés FR + FORBIDDEN_UI_TERMS + types/domain.ts # le noyau du modèle + tests/ # 342 tests vitest : engine/, stores/, seeds/ (non-perte), lexicon.spec.ts +backend/ # v1 FastAPI conservé pour la synchro future — ne pas développer dessus + scripts/export_seed_bundle.py # extrait seed.py → duniter-g1.bundle.json (source vivante = bundle) +docker/ # compose + Dockerfiles +docs/dev/BLUEPRINT-V2.md # la référence produit v2 +public/hexagram-tsing*.svg # sceau 井 (#48 Tsing) ``` ## Données runtime -- **postgres-data** : volume Docker PostgreSQL — jamais écrasé par les builds -- **ipfs-data** : volume Docker IPFS kubo -- `.env` à la racine : `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB`, `SECRET_KEY`, `DOMAIN`, `DUNITER_RPC_URL` +- **postgres-data** / **ipfs-data** : volumes Docker du backend v1 — jamais écrasés +- `.env` racine : secrets backend — jamais commité ## Commandes ```bash -# Backend -cd backend && . venv/bin/activate -uvicorn app.main:app --port 8002 --host 0.0.0.0 --reload -pytest tests/ -v -alembic upgrade head -python seed.py # reseed Engagement Certification + Forgeron + Runtime Upgrade - -# Frontend +# Frontend (tout le dev v2) cd frontend && npm run dev # :3002 -npm run build +npm test # vitest — 342 tests, référence à maintenir +npm run build # zéro erreur exigé avant commit + +# Seeds : le bundle est la source vivante ; seed.py (backend) est remplacé par +cd backend && .venv/bin/python scripts/export_seed_bundle.py # régénère duniter-g1.bundle.json +# → test de non-perte bloquant : tests/seeds/duniter-g1.spec.ts # Docker docker compose -f docker/docker-compose.yml up @@ -78,11 +76,25 @@ docker compose -f docker/docker-compose.yml up ## Conventions / pièges - **UI français, code anglais** (variables, commentaires, docstrings) -- **API** : préfixe `/api/v1/`, Pydantic v2 pour tous les schémas, async partout (SQLAlchemy + FastAPI) -- **Auth** : `get_current_admin` (24h), `get_current_citizen` (4h), `require_super_admin` -- **Formule inertie** : `Result = C + B^W + (M + (1-M) × (1 - (T/W)^G)) × max(0, T-C)` — voir `backend/app/engine/` -- **Mood system** : `useMood.ts` synchronise `colorMode.preference` avec la palette — **jamais** de `:global()` dans ` diff --git a/frontend/app/assets/css/moods.css b/frontend/app/assets/css/moods.css index ebb7df8..2504dec 100644 --- a/frontend/app/assets/css/moods.css +++ b/frontend/app/assets/css/moods.css @@ -1,158 +1,196 @@ /* ========================================================================== - libreDecision — Mood / Ambiance System - Palettes harmoniques variees, colores en lite, lumineux en dark. + libreDecision v2 — Ambiances (champ lexical du puits 井) + Source (light, eau claire) · Margelle (light, pierre chaude) + Nappe (dark, eau profonde) · Minuit (dark, encre & lanterne) + Borderless : profondeur par background + box-shadow, jamais de bordures. ========================================================================== */ /* -------------------------------------------------------------------------- - Peps — Chaud, colore, tonique (Light) - Palette: corail vif / ocre / indigo / vert franc + Source — l'eau vive au grand jour (Light) -------------------------------------------------------------------------- */ -.mood-peps { - --mood-bg: #faf8f5; - --mood-surface: #ffffff; - --mood-surface-hover: #fdf2ec; - --mood-text: #1e1410; - --mood-text-muted: #7a5e48; - --mood-accent: #d44a10; - --mood-accent-soft: rgba(212, 74, 16, 0.08); +.mood-source { + --mood-bg: #f0f5f6; + --mood-surface: #fcfefe; + --mood-surface-hover: #e6eff2; + --mood-text: #102126; + --mood-text-muted: #52707a; + --mood-accent: #0f7fa8; + --mood-accent-soft: rgba(15, 127, 168, 0.09); --mood-accent-text: #ffffff; - --mood-border: #e8d8c8; - --mood-secondary: #4a5ec0; - --mood-tertiary: #c07820; - --mood-success: #1a8c3e; - --mood-warning: #c47d0a; - --mood-error: #c23028; - --mood-gradient: linear-gradient(145deg, #fdf8f0 0%, #ffffff 40%, #f0f4fb 100%); - --mood-shadow: rgba(180, 80, 20, 0.07); - --mood-input-bg: #ffffff; - --mood-input-border: #d4c0aa; - --mood-input-focus: #d44a10; + --mood-secondary: #a8742c; /* laiton — le mandat, le pouvoir confié */ + --mood-tertiary: #2e8a72; /* eau végétale — l'avis, le dialogue */ + --mood-success: #1d8a4a; + --mood-warning: #b07c14; + --mood-error: #bc3d33; + --mood-gradient: linear-gradient(150deg, #eaf3f5 0%, #fcfefe 45%, #eef2ee 100%); + --mood-shadow: rgba(20, 90, 120, 0.08); + --mood-input-bg: #fcfefe; + --mood-input-border: #b8ccd2; + --mood-input-focus: #0f7fa8; - --mood-status-prepa: #b8600e; - --mood-status-prepa-bg: rgba(184, 96, 14, 0.12); - --mood-status-vote: #4a5ec0; - --mood-status-vote-bg: rgba(74, 94, 192, 0.10); - --mood-status-vigueur: #1a8c3e; - --mood-status-vigueur-bg: rgba(26, 140, 62, 0.10); - --mood-status-clos: #6e5844; - --mood-status-clos-bg: rgba(110, 88, 68, 0.08); + --mood-status-prepa: #8a7040; + --mood-status-prepa-bg: rgba(138, 112, 64, 0.10); + --mood-status-fenetre: #b07c14; + --mood-status-fenetre-bg: rgba(176, 124, 20, 0.11); + --mood-status-vote: #2661b8; + --mood-status-vote-bg: rgba(38, 97, 184, 0.10); + --mood-status-fige: #58708c; + --mood-status-fige-bg: rgba(88, 112, 140, 0.12); + --mood-status-vigueur: #1d8a4a; + --mood-status-vigueur-bg: rgba(29, 138, 74, 0.10); + --mood-status-revoque: #a4553a; + --mood-status-revoque-bg: rgba(164, 85, 58, 0.10); + --mood-status-clos: #5f6f74; + --mood-status-clos-bg: rgba(95, 111, 116, 0.09); } /* -------------------------------------------------------------------------- - Zen — Vegetal lumineux, nature vivante (Light) - Palette: vert franc / terre sienne / bleu canard / bois chaud + Margelle — la pierre du puits, calcaire et bronze (Light) -------------------------------------------------------------------------- */ -.mood-zen { - --mood-bg: #f4f6f0; - --mood-surface: #fbfcf8; - --mood-surface-hover: #eaf0e4; - --mood-text: #1a2418; - --mood-text-muted: #5a7050; - --mood-accent: #2e8b48; - --mood-accent-soft: rgba(46, 139, 72, 0.09); +.mood-margelle { + --mood-bg: #f6f3ec; + --mood-surface: #fdfbf6; + --mood-surface-hover: #efe9dc; + --mood-text: #221c10; + --mood-text-muted: #77684c; + --mood-accent: #96682a; + --mood-accent-soft: rgba(150, 104, 42, 0.10); --mood-accent-text: #ffffff; - --mood-border: #b8d0a8; - --mood-secondary: #8a6030; - --mood-tertiary: #2a7888; - --mood-success: #268040; - --mood-warning: #a87818; - --mood-error: #b83838; - --mood-gradient: linear-gradient(145deg, #f0f5ea 0%, #fbfcf8 40%, #f4efe6 100%); - --mood-shadow: rgba(40, 100, 50, 0.07); - --mood-input-bg: #fbfcf8; - --mood-input-border: #a8c898; - --mood-input-focus: #2e8b48; + --mood-secondary: #285e88; /* le ciel dans l'eau du seau */ + --mood-tertiary: #4f7a3c; /* mousse entre les pierres */ + --mood-success: #3d7e34; + --mood-warning: #a8720e; + --mood-error: #b23f30; + --mood-gradient: linear-gradient(150deg, #f3eee2 0%, #fdfbf6 45%, #f0eee6 100%); + --mood-shadow: rgba(120, 90, 40, 0.09); + --mood-input-bg: #fdfbf6; + --mood-input-border: #cfc2a6; + --mood-input-focus: #96682a; - --mood-status-prepa: #a87818; - --mood-status-prepa-bg: rgba(168, 120, 24, 0.11); - --mood-status-vote: #2a7888; - --mood-status-vote-bg: rgba(42, 120, 136, 0.10); - --mood-status-vigueur: #268040; - --mood-status-vigueur-bg: rgba(38, 128, 64, 0.10); - --mood-status-clos: #607858; - --mood-status-clos-bg: rgba(96, 120, 88, 0.08); + --mood-status-prepa: #7c6a48; + --mood-status-prepa-bg: rgba(124, 106, 72, 0.10); + --mood-status-fenetre: #a8720e; + --mood-status-fenetre-bg: rgba(168, 114, 14, 0.11); + --mood-status-vote: #285e88; + --mood-status-vote-bg: rgba(40, 94, 136, 0.10); + --mood-status-fige: #6e6a58; + --mood-status-fige-bg: rgba(110, 106, 88, 0.12); + --mood-status-vigueur: #3d7e34; + --mood-status-vigueur-bg: rgba(61, 126, 52, 0.10); + --mood-status-revoque: #9c5236; + --mood-status-revoque-bg: rgba(156, 82, 54, 0.10); + --mood-status-clos: #6f6a5c; + --mood-status-clos-bg: rgba(111, 106, 92, 0.09); } /* -------------------------------------------------------------------------- - Chagrine — Nuit profonde, reflets varies (Dark) - Palette: bleu lavande / ambre chaud / rose ancien / vert d'eau + Nappe — l'eau profonde sous la ville (Dark) -------------------------------------------------------------------------- */ -.mood-chagrine { - --mood-bg: #0e1018; - --mood-surface: #151a28; - --mood-surface-hover: #1c2640; - --mood-text: #dce0ec; - --mood-text-muted: #8898b8; - --mood-accent: #6488d8; - --mood-accent-soft: rgba(100, 136, 216, 0.12); - --mood-accent-text: #ffffff; - --mood-border: #222e48; - --mood-secondary: #d0a040; - --mood-tertiary: #c87090; - --mood-success: #48c87a; - --mood-warning: #d8a838; - --mood-error: #d86060; - --mood-gradient: linear-gradient(145deg, #0e1018 0%, #151a28 40%, #101420 100%); - --mood-shadow: rgba(60, 100, 180, 0.12); - --mood-input-bg: #151a28; - --mood-input-border: #2a3858; - --mood-input-focus: #6488d8; +.mood-nappe { + --mood-bg: #0a141b; + --mood-surface: #101f2a; + --mood-surface-hover: #16293a; + --mood-text: #d6e3ea; + --mood-text-muted: #7e98a8; + --mood-accent: #3fa9cc; + --mood-accent-soft: rgba(63, 169, 204, 0.13); + --mood-accent-text: #06141c; + --mood-secondary: #cfa14a; /* laiton à la lueur de la lampe */ + --mood-tertiary: #52b894; + --mood-success: #46bf7c; + --mood-warning: #d2a13c; + --mood-error: #d86458; + --mood-gradient: linear-gradient(150deg, #0a141b 0%, #101f2a 45%, #0d1a24 100%); + --mood-shadow: rgba(30, 120, 160, 0.14); + --mood-input-bg: #101f2a; + --mood-input-border: #24425a; + --mood-input-focus: #3fa9cc; - --mood-status-prepa: #d8a838; - --mood-status-prepa-bg: rgba(216, 168, 56, 0.14); - --mood-status-vote: #6488d8; - --mood-status-vote-bg: rgba(100, 136, 216, 0.14); - --mood-status-vigueur: #48c87a; - --mood-status-vigueur-bg: rgba(72, 200, 122, 0.14); - --mood-status-clos: #7888a8; - --mood-status-clos-bg: rgba(120, 136, 168, 0.10); + --mood-status-prepa: #a5987c; + --mood-status-prepa-bg: rgba(165, 152, 124, 0.12); + --mood-status-fenetre: #d2a13c; + --mood-status-fenetre-bg: rgba(210, 161, 60, 0.14); + --mood-status-vote: #5f97e0; + --mood-status-vote-bg: rgba(95, 151, 224, 0.14); + --mood-status-fige: #7d97b5; + --mood-status-fige-bg: rgba(125, 151, 181, 0.14); + --mood-status-vigueur: #46bf7c; + --mood-status-vigueur-bg: rgba(70, 191, 124, 0.13); + --mood-status-revoque: #cd7a56; + --mood-status-revoque-bg: rgba(205, 122, 86, 0.13); + --mood-status-clos: #7d8fa0; + --mood-status-clos-bg: rgba(125, 143, 160, 0.11); } /* -------------------------------------------------------------------------- - Grave — Ambre mineral, ocre lumineux (Dark) - Palette: ambre dore / ocre rouge / bleu ardoise / vert mousse + Minuit — l'encre, et la lanterne posée sur la margelle (Dark) -------------------------------------------------------------------------- */ -.mood-grave { - --mood-bg: #131210; - --mood-surface: #1c1a16; - --mood-surface-hover: #28241c; - --mood-text: #ece4d4; - --mood-text-muted: #a89870; - --mood-accent: #d8a030; - --mood-accent-soft: rgba(216, 160, 48, 0.11); - --mood-accent-text: #131210; - --mood-border: #342e24; - --mood-secondary: #6898b8; - --mood-tertiary: #c07040; - --mood-success: #58c870; - --mood-warning: #d0a030; - --mood-error: #d05050; - --mood-gradient: linear-gradient(145deg, #131210 0%, #1c1a16 40%, #16181c 100%); - --mood-shadow: rgba(180, 130, 40, 0.10); - --mood-input-bg: #1c1a16; - --mood-input-border: #3c3428; - --mood-input-focus: #d8a030; +.mood-minuit { + --mood-bg: #101014; + --mood-surface: #17171e; + --mood-surface-hover: #202029; + --mood-text: #e2e2e8; + --mood-text-muted: #8e8e9e; + --mood-accent: #cf9c3e; + --mood-accent-soft: rgba(207, 156, 62, 0.12); + --mood-accent-text: #131309; + --mood-secondary: #6c94d8; + --mood-tertiary: #58ae8e; + --mood-success: #52bd7e; + --mood-warning: #cfa03a; + --mood-error: #d25f55; + --mood-gradient: linear-gradient(150deg, #101014 0%, #17171e 45%, #131318 100%); + --mood-shadow: rgba(190, 140, 50, 0.11); + --mood-input-bg: #17171e; + --mood-input-border: #33333f; + --mood-input-focus: #cf9c3e; - --mood-status-prepa: #c07040; - --mood-status-prepa-bg: rgba(192, 112, 64, 0.15); - --mood-status-vote: #6898b8; - --mood-status-vote-bg: rgba(104, 152, 184, 0.14); - --mood-status-vigueur: #58c870; - --mood-status-vigueur-bg: rgba(88, 200, 112, 0.14); - --mood-status-clos: #807860; - --mood-status-clos-bg: rgba(128, 120, 96, 0.12); + --mood-status-prepa: #9e9584; + --mood-status-prepa-bg: rgba(158, 149, 132, 0.12); + --mood-status-fenetre: #cfa03a; + --mood-status-fenetre-bg: rgba(207, 160, 58, 0.14); + --mood-status-vote: #6c94d8; + --mood-status-vote-bg: rgba(108, 148, 216, 0.14); + --mood-status-fige: #8b8ba0; + --mood-status-fige-bg: rgba(139, 139, 160, 0.14); + --mood-status-vigueur: #52bd7e; + --mood-status-vigueur-bg: rgba(82, 189, 126, 0.13); + --mood-status-revoque: #c97a5c; + --mood-status-revoque-bg: rgba(201, 122, 92, 0.13); + --mood-status-clos: #85859a; + --mood-status-clos-bg: rgba(133, 133, 154, 0.11); } /* ========================================================================== - Global design tokens — Plus Jakarta Sans, rounded, borderless + Tokens dérivés — routes, tailles, ombres (identiques dans toutes les ambiances) ========================================================================== */ +.mood-source, .mood-margelle, .mood-nappe, .mood-minuit { + /* Routes du chemin — vocabulaire visuel fixe */ + --route-solo: var(--mood-accent); + --route-mandate: var(--mood-secondary); + --route-transmit: var(--mood-text-muted); + --route-advice: var(--mood-tertiary); + --route-collective: var(--mood-status-vote); + --route-record: var(--mood-status-prepa); + --route-urgent: var(--mood-error); -*, -*::before, -*::after { - border-color: transparent; + /* Rayons (consignes) */ + --r-card: 16px; + --r-input: 12px; + --r-pill: 20px; + --r-icon: 14px; + + /* Ombres borderless — deux couches, jamais de bordure */ + --shadow-card: 0 1px 2px var(--mood-shadow), 0 5px 18px var(--mood-shadow); + --shadow-card-hover: 0 2px 4px var(--mood-shadow), 0 10px 28px var(--mood-shadow); + --shadow-raised: 0 1px 3px var(--mood-shadow), 0 8px 24px var(--mood-shadow); } +/* ========================================================================== + Socle global — Plus Jakarta Sans, borderless, gestes + ========================================================================== */ +*, *::before, *::after { border-color: transparent; } + html { font-family: 'Plus Jakarta Sans', system-ui, -apple-system, sans-serif; font-size: 16px; @@ -160,89 +198,154 @@ html { body { font-family: 'Plus Jakarta Sans', system-ui, -apple-system, sans-serif; + background: var(--mood-bg); + color: var(--mood-text); transition: background-color 0.3s ease, color 0.3s ease; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } -/* --- No borders anywhere --- */ button, input, select, textarea { border: none !important; outline: none; font-family: inherit; + color: inherit; } -input:focus, select:focus, textarea:focus { +input, select, textarea { + background: var(--mood-input-bg); + border-radius: var(--r-input); +} + +input:focus-visible, select:focus-visible, textarea:focus-visible, +button:focus-visible, a:focus-visible, [tabindex]:focus-visible { outline: none; - box-shadow: 0 0 0 2.5px var(--mood-accent-soft); + box-shadow: 0 0 0 2.5px var(--mood-accent-soft), 0 0 0 1.5px var(--mood-accent); } -/* --- Status pills --- */ +@media (prefers-reduced-motion: reduce) { + *, *::before, *::after { + animation-duration: 0.01ms !important; + transition-duration: 0.01ms !important; + } +} + +/* --- Cartes --- */ +.ld-card { + background: var(--mood-surface); + border-radius: var(--r-card); + box-shadow: var(--shadow-card); + transition: transform 0.12s ease, box-shadow 0.12s ease; +} +.ld-card--hover:hover { + transform: translateY(-3px); + box-shadow: var(--shadow-card-hover); +} +.ld-card--hover:active { transform: translateY(0); } + +/* --- Boutons pill --- */ +.ld-btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 0.5rem; + min-height: 2.25rem; + padding: 0.5rem 1.25rem; + border-radius: var(--r-pill); + font-size: 0.9375rem; + font-weight: 700; + cursor: pointer; + background: var(--mood-accent); + color: var(--mood-accent-text); + transition: transform 0.1s ease, box-shadow 0.1s ease, filter 0.1s ease; + user-select: none; +} +.ld-btn:hover { transform: translateY(-1px); box-shadow: 0 4px 12px var(--mood-shadow); } +.ld-btn:active { transform: translateY(0); } +.ld-btn:disabled { opacity: 0.45; cursor: not-allowed; transform: none; box-shadow: none; } +.ld-btn--ghost { + background: var(--mood-accent-soft); + color: var(--mood-accent); +} +.ld-btn--quiet { + background: none; + color: var(--mood-text-muted); +} +.ld-btn--quiet:hover { background: var(--mood-accent-soft); color: var(--mood-text); box-shadow: none; } + +/* --- Pills d'état (mapping unique état→couleur) --- */ .status-pill { display: inline-flex; align-items: center; - padding: 5px 14px; - border-radius: 20px; + gap: 0.3rem; + padding: 4px 13px; + border-radius: var(--r-pill); font-size: 0.8125rem; font-weight: 700; letter-spacing: 0.01em; - cursor: pointer; - transition: all 0.15s ease; user-select: none; - border: none !important; + transition: all 0.15s ease; } -.status-pill:hover { - filter: brightness(1.08); - transform: translateY(-1px); -} -.status-pill.active { - box-shadow: 0 0 0 2.5px var(--mood-accent); +.status-pill.is-clickable { cursor: pointer; } +.status-pill.is-clickable:hover { filter: brightness(1.08); transform: translateY(-1px); } +.status-pill.active { box-shadow: 0 0 0 2.5px var(--mood-accent); } + +.status-draft { background: var(--mood-status-prepa-bg); color: var(--mood-status-prepa); } +.status-advice, +.status-objection { background: var(--mood-status-fenetre-bg); color: var(--mood-status-fenetre); } +.status-framing { background: var(--mood-status-prepa-bg); color: var(--mood-status-prepa); } +.status-voting { background: var(--mood-status-vote-bg); color: var(--mood-status-vote); } +.status-frozen { background: var(--mood-status-fige-bg); color: var(--mood-status-fige); } +.status-adopted { background: var(--mood-status-vigueur-bg); color: var(--mood-status-vigueur); } +.status-rejected { background: var(--mood-status-clos-bg); color: var(--mood-status-clos); text-decoration: line-through; } +.status-revoked { background: var(--mood-status-revoque-bg); color: var(--mood-status-revoque); } +.status-transmitted { background: var(--mood-status-fige-bg); color: var(--mood-status-fige); } +.status-closed { background: var(--mood-status-clos-bg); color: var(--mood-status-clos); } + +/* Suspension (frontière contestée) : fond strié */ +.status-suspended { + background: repeating-linear-gradient( + -45deg, + var(--mood-status-fenetre-bg), + var(--mood-status-fenetre-bg) 6px, + transparent 6px, + transparent 10px + ); + color: var(--mood-status-fenetre); } -.status-prepa { - background: var(--mood-status-prepa-bg); - color: var(--mood-status-prepa); -} -.status-vote { - background: var(--mood-status-vote-bg); - color: var(--mood-status-vote); -} -.status-vigueur { - background: var(--mood-status-vigueur-bg); +/* --- Le tampon 井 (signature) --- */ +.ld-stamp { + display: inline-flex; + align-items: center; + gap: 0.4rem; + padding: 0.35rem 0.85rem; + border-radius: 10px; + transform: rotate(-10deg); + font-weight: 800; + font-size: 0.8125rem; + letter-spacing: 0.04em; color: var(--mood-status-vigueur); -} -.status-clos { - background: var(--mood-status-clos-bg); - color: var(--mood-status-clos); + box-shadow: inset 0 0 0 2px var(--mood-status-vigueur); + background: var(--mood-status-vigueur-bg); + user-select: none; } -/* ========================================================================== - Nuxt UI overrides — borderless everything - ========================================================================== */ - -:root .mood-peps, -:root .mood-zen, -:root .mood-chagrine, -:root .mood-grave { - --ui-border: transparent; - --ui-bg: var(--mood-input-bg); - --ui-text-highlighted: var(--mood-accent); +/* --- Sceau hexagramme #48 Tsing --- */ +.app-seal { + display: block; + width: 44px; + color: var(--mood-accent); + opacity: 0.28; + filter: drop-shadow(1px 1px 0.5px rgba(0, 0, 0, 0.25)) + drop-shadow(-0.5px -0.5px 0.5px rgba(255, 255, 255, 0.15)); } -:root button, -:root [class*="UButton"], -:root [class*="u-button"], -:root [data-variant] { - border: none !important; - font-family: 'Plus Jakarta Sans', system-ui, sans-serif !important; -} - -:root input, -:root textarea, -:root select, -:root [class*="UInput"], -:root [class*="USelect"], -:root [class*="UTextarea"] { - border: none !important; - font-family: 'Plus Jakarta Sans', system-ui, sans-serif !important; +/* --- Impression A4 (PV, affiches de session) --- */ +@media print { + body { background: #ffffff !important; color: #111111 !important; } + .app-header, .app-sidebar, .app-fab, .app-seal, .no-print { display: none !important; } + .ld-card { box-shadow: none !important; } + .print-only { display: block !important; } } +.print-only { display: none; } diff --git a/frontend/app/components/SectionLayout.vue b/frontend/app/components/SectionLayout.vue deleted file mode 100644 index b90c35c..0000000 --- a/frontend/app/components/SectionLayout.vue +++ /dev/null @@ -1,373 +0,0 @@ - - - - - diff --git a/frontend/app/components/WorkspaceSelector.vue b/frontend/app/components/WorkspaceSelector.vue deleted file mode 100644 index 0ba4393..0000000 --- a/frontend/app/components/WorkspaceSelector.vue +++ /dev/null @@ -1,252 +0,0 @@ - - - - - diff --git a/frontend/app/components/chemin/CheminAlternatives.vue b/frontend/app/components/chemin/CheminAlternatives.vue new file mode 100644 index 0000000..85124c7 --- /dev/null +++ b/frontend/app/components/chemin/CheminAlternatives.vue @@ -0,0 +1,165 @@ + + +