forked from yvv/decision
v3 : interface Svelte — accueil, groupes, lecteur du texte en vigueur, panneau contextuel, éditeur à assistant, trustinWallet
- Accueil sobre, menu complet à gauche (Décisions, Docs de référence, Mandats, Protocoles), intro demandée, quatre sections, connexion Ğ1. - Lecteur : texte en vigueur en Literata, actions contextuelles par article (le vote, propositions, proposer), propositions en cours sous la « surface » avec leur niveau d'eau, aperçus « si les bien cotées passent » / « si tout passe », langue du texte fr/en/es (traductions éditoriales). - Panneau : vote pour/contre, seuil et formule (MathML), inertie et son changement (voté en inertie +), coquille sans vote, traduction, signalement de contradiction, historique des corrections. - trustinWallet (/acces) : groupes et membres, invitations à usage unique, administrateurs, retours d'usage. - Contrôles : vitest (markdown, catalogues, anti-lexique), Playwright C1–C10 + M1. - Sections du seed portées jusqu'à l'écran. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,174 @@
|
||||
<script lang="ts">
|
||||
import '../app.css';
|
||||
import { onMount, type Snippet } from 'svelte';
|
||||
import { afterNavigate } from '$app/navigation';
|
||||
import FeedbackDialog from '$lib/components/FeedbackDialog.svelte';
|
||||
import Icon from '$lib/components/Icon.svelte';
|
||||
import Seal from '$lib/components/Seal.svelte';
|
||||
import Sidebar from '$lib/components/Sidebar.svelte';
|
||||
import Stamp from '$lib/components/Stamp.svelte';
|
||||
import Toasts from '$lib/components/Toasts.svelte';
|
||||
import { i18n, t } from '$lib/i18n/index.svelte';
|
||||
import { mood } from '$lib/mood.svelte';
|
||||
import { session } from '$lib/session.svelte';
|
||||
|
||||
let { children }: { children: Snippet } = $props();
|
||||
let feedback: FeedbackDialog | undefined = $state();
|
||||
let drawer = $state(false);
|
||||
|
||||
onMount(() => {
|
||||
i18n.init();
|
||||
mood.init();
|
||||
void session.load();
|
||||
});
|
||||
|
||||
afterNavigate(() => {
|
||||
drawer = false;
|
||||
});
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{t('app.name')}</title>
|
||||
</svelte:head>
|
||||
|
||||
<a class="skip" href="#main">{t('app.skip')}</a>
|
||||
|
||||
<div class="shell">
|
||||
<aside class="side">
|
||||
<Sidebar onfeedback={() => feedback?.open()} />
|
||||
</aside>
|
||||
|
||||
<header class="topbar">
|
||||
<a class="brand" href="/"><Stamp size={2} /><span>libre<b>Decision</b></span></a>
|
||||
<button class="menu" type="button" aria-expanded={drawer} aria-controls="drawer" onclick={() => (drawer = true)}>
|
||||
<Icon name="menu" /><span class="visually-hidden">{t('nav.menu')}</span>
|
||||
</button>
|
||||
</header>
|
||||
|
||||
{#if drawer}
|
||||
<div class="veil" role="presentation" onclick={() => (drawer = false)}></div>
|
||||
<div class="drawer card" id="drawer" role="dialog" aria-modal="true" aria-label={t('nav.label')}>
|
||||
<button class="close" type="button" onclick={() => (drawer = false)} aria-label={t('nav.close')}><Icon name="close" /></button>
|
||||
<Sidebar onfeedback={() => { drawer = false; feedback?.open(); }} onnavigate={() => (drawer = false)} />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<main id="main" tabindex="-1">
|
||||
{#if session.ready}
|
||||
{@render children()}
|
||||
{/if}
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<FeedbackDialog bind:this={feedback} />
|
||||
<Toasts />
|
||||
<Seal />
|
||||
|
||||
<svelte:window onkeydown={(e) => e.key === 'Escape' && (drawer = false)} />
|
||||
|
||||
<style>
|
||||
.skip {
|
||||
position: absolute;
|
||||
left: -999px;
|
||||
top: 0.5rem;
|
||||
z-index: 100;
|
||||
background: var(--surface);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: var(--r-pill);
|
||||
}
|
||||
.skip:focus {
|
||||
left: 0.5rem;
|
||||
}
|
||||
.shell {
|
||||
min-height: 100dvh;
|
||||
}
|
||||
.side {
|
||||
position: fixed;
|
||||
inset: 0 auto 0 0;
|
||||
width: var(--sidebar);
|
||||
background: var(--surface);
|
||||
box-shadow: var(--shadow);
|
||||
z-index: 20;
|
||||
overflow-y: auto;
|
||||
}
|
||||
main {
|
||||
margin-left: var(--sidebar);
|
||||
min-height: 100dvh;
|
||||
outline: none;
|
||||
}
|
||||
.topbar {
|
||||
display: none;
|
||||
}
|
||||
.veil {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: var(--veil);
|
||||
z-index: 40;
|
||||
}
|
||||
.drawer {
|
||||
position: fixed;
|
||||
inset: 0.6rem auto 0.6rem 0.6rem;
|
||||
width: min(84vw, 18rem);
|
||||
z-index: 41;
|
||||
overflow-y: auto;
|
||||
animation: slide 0.22s var(--ease);
|
||||
}
|
||||
.close {
|
||||
position: absolute;
|
||||
top: 1.2rem;
|
||||
right: 0.8rem;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: var(--ink-soft);
|
||||
width: 2.25rem;
|
||||
height: 2.25rem;
|
||||
border-radius: var(--r-icon);
|
||||
}
|
||||
@keyframes slide {
|
||||
from {
|
||||
transform: translateX(-12px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@media (max-width: 899px) {
|
||||
.side {
|
||||
display: none;
|
||||
}
|
||||
main {
|
||||
margin-left: 0;
|
||||
}
|
||||
.topbar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 30;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.6rem 0.9rem;
|
||||
background: color-mix(in srgb, var(--bg) 88%, transparent);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
color: var(--ink);
|
||||
text-decoration: none;
|
||||
font-size: 1.02rem;
|
||||
}
|
||||
.brand b {
|
||||
font-weight: 800;
|
||||
}
|
||||
.menu {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border: 0;
|
||||
border-radius: var(--r-icon);
|
||||
background: var(--surface);
|
||||
box-shadow: var(--shadow);
|
||||
color: var(--ink);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user