Files
YvvandClaude Opus 5.5 67ddee28b2 v3 : socle Rust (moteur pur, accès trustinWallet, serveur) ; maquette Nuxt rangée dans maquette/
- crates/ld-engine : formule inertielle (parité avec la maquette, garde C),
  coquille / changement de fond, assistant d'édition à règles, composition
  d'un document sous vote permanent (adoptions, aperçus, rivales).
- crates/trustin-access : qui voit, qui ouvre, qui gère les accès.
- crates/ld-server : API axum + SQLite, connexion Ğ1 (client OAuth2 de
  connect.monnaie-libre.fr), invitations, entrée en vigueur automatique,
  signalements de contradiction, traductions éditoriales, publication git,
  taille de la toile lue sur la chaîne.
- seeds/acte-engagement-certification.json : 33 articles, fr + en + es ;
  en vigueur : préambule, E1, E2 (seed a minima).
- maquette/ : frontend, backend et docker v2 déplacés tels quels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 02:04:42 +02:00

238 lines
7.6 KiB
Vue

<script setup lang="ts">
// The three tunnel chips — Concernés / Réversible ? / Ça engage quoi ? —
// one unfolded at a time (mobile-first), plus the discreet urgency toggle,
// the never-blocking « Ce que ça engage » suggestion line and the
// « Aujourd'hui : … » status-quo line.
import type { Decision, Person, Reversibility, Weight } from '~/types/domain'
import {
BASELINE_PREFIX,
ENGAGES_LABEL,
REVERSIBILITY_LABELS,
URGENT_TOGGLE,
WEIGHT_LABELS,
} from '~/lexicon'
defineProps<{
stack: { person: Person; origin?: 'computed' | 'declared'; reason?: string }[]
engagesOpen: boolean
baselineSuggested: boolean
}>()
const scope = defineModel<Decision['scope']>('scope', { required: true })
const reversibility = defineModel<Reversibility>('reversibility', { required: true })
const weight = defineModel<Weight>('weight', { required: true })
const urgent = defineModel<boolean>('urgent', { required: true })
const resourceNote = defineModel<string>('resourceNote', { required: true })
const resourceAmount = defineModel<number | null>('resourceAmount', { required: true })
const resourceUnit = defineModel<string>('resourceUnit', { required: true })
const baselineNote = defineModel<string>('baselineNote', { required: true })
const openChip = ref<'scope' | 'rev' | 'weight' | null>(null)
const REV_OPTIONS = Object.keys(REVERSIBILITY_LABELS) as Reversibility[]
const WEIGHT_OPTIONS = Object.keys(WEIGHT_LABELS) as Weight[]
function toggle(chip: 'scope' | 'rev' | 'weight') {
openChip.value = openChip.value === chip ? null : chip
}
</script>
<template>
<!-- ld-v2 -->
<div class="chips">
<div class="chips__row">
<button
class="chips__chip"
:class="{ 'chips__chip--open': openChip === 'scope' }"
type="button"
@click="toggle('scope')"
>
<span class="chips__name">Concernés</span>
<span class="chips__value">
{{ scope.selfOnly ? 'moi seul' : `${stack.length} personne${stack.length > 1 ? 's' : ''}` }}
</span>
</button>
<button
class="chips__chip"
:class="{ 'chips__chip--open': openChip === 'rev' }"
type="button"
@click="toggle('rev')"
>
<span class="chips__name">Réversible ?</span>
<span class="chips__value">{{ REVERSIBILITY_LABELS[reversibility] }}</span>
</button>
<button
class="chips__chip"
:class="{ 'chips__chip--open': openChip === 'weight' }"
type="button"
@click="toggle('weight')"
>
<span class="chips__name">Ça engage quoi ?</span>
<span class="chips__value">{{ WEIGHT_LABELS[weight] }}</span>
</button>
<label class="chips__urgent" :class="{ 'chips__urgent--on': urgent }">
<input v-model="urgent" type="checkbox">
<UIcon name="i-lucide-siren" />
<span>{{ URGENT_TOGGLE }}</span>
</label>
</div>
<div v-if="openChip === 'scope'" class="ld-card chips__panel">
<CheminScope v-model:scope="scope" :stack="stack" />
</div>
<div v-else-if="openChip === 'rev'" class="ld-card chips__panel">
<div class="chips__options">
<button
v-for="opt in REV_OPTIONS"
:key="opt"
class="chips__option"
:class="{ 'chips__option--on': reversibility === opt }"
type="button"
@click="reversibility = opt"
>
{{ REVERSIBILITY_LABELS[opt] }}
</button>
</div>
</div>
<div v-else-if="openChip === 'weight'" class="ld-card chips__panel">
<div class="chips__options">
<button
v-for="opt in WEIGHT_OPTIONS"
:key="opt"
class="chips__option"
:class="{ 'chips__option--on': weight === opt }"
type="button"
@click="weight = opt"
>
{{ WEIGHT_LABELS[opt] }}
</button>
</div>
</div>
<!-- « Ce que ça engage » — suggestion dépliée, JAMAIS bloquante -->
<div v-if="engagesOpen" class="chips__engages">
<span class="chips__engages-label">{{ ENGAGES_LABEL }}</span>
<input
v-model="resourceNote"
class="chips__engages-note"
type="text"
placeholder="En une phrase — suggéré, jamais exigé ici"
>
<div class="chips__engages-amount">
<input
v-model.number="resourceAmount"
type="number"
min="0"
placeholder="Montant"
>
<select v-model="resourceUnit">
<option value="heures">heures</option>
<option value="€">€</option>
<option value="DU">DU</option>
<option value="jours">jours</option>
</select>
</div>
</div>
<!-- « Aujourd'hui : … » — le statu quo, suggéré sur avis/collectif -->
<label v-if="baselineSuggested" class="chips__baseline">
<span>{{ BASELINE_PREFIX }}</span>
<input
v-model="baselineNote"
type="text"
placeholder="ce qui se passe si rien ne change"
>
</label>
</div>
</template>
<style scoped>
.chips { display: flex; flex-direction: column; gap: 0.625rem; }
.chips__row { display: flex; flex-wrap: wrap; align-items: stretch; gap: 0.5rem; }
.chips__chip {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 0.125rem;
padding: 0.5rem 0.875rem;
border-radius: var(--r-icon);
background: var(--mood-surface);
box-shadow: var(--shadow-card);
cursor: pointer;
text-align: left;
min-height: 2.25rem;
transition: transform 0.1s ease;
}
.chips__chip:hover { transform: translateY(-1px); }
.chips__chip--open { box-shadow: 0 0 0 2px var(--mood-accent), var(--shadow-card); }
.chips__name {
font-size: 0.6875rem;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--mood-text-muted);
}
.chips__value { font-size: 0.875rem; font-weight: 700; color: var(--mood-text); }
.chips__urgent {
display: inline-flex;
align-items: center;
gap: 0.35rem;
margin-left: auto;
padding: 0.4rem 0.875rem;
border-radius: var(--r-pill);
font-size: 0.8125rem;
font-weight: 600;
color: var(--mood-text-muted);
cursor: pointer;
align-self: center;
}
.chips__urgent input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.chips__urgent--on {
color: var(--route-urgent);
background: color-mix(in srgb, var(--route-urgent) 12%, transparent);
}
.chips__panel { padding: 1rem; }
.chips__options { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.chips__option {
padding: 0.5rem 1rem;
border-radius: var(--r-pill);
font-size: 0.875rem;
font-weight: 600;
background: var(--mood-input-bg);
color: var(--mood-text-muted);
cursor: pointer;
min-height: 2.25rem;
}
.chips__option--on {
background: var(--mood-accent-soft);
color: var(--mood-accent);
box-shadow: 0 0 0 1.5px var(--mood-accent);
}
.chips__engages {
display: flex;
flex-direction: column;
gap: 0.5rem;
padding: 0.75rem;
border-radius: var(--r-input);
background: color-mix(in srgb, var(--mood-accent) 6%, transparent);
}
.chips__engages-label { font-size: 0.8125rem; font-weight: 700; color: var(--mood-accent); }
.chips__engages-note { width: 100%; padding: 0.5rem 0.75rem; font-size: 0.9375rem; }
.chips__engages-amount { display: flex; gap: 0.5rem; }
.chips__engages-amount input { width: 7rem; padding: 0.5rem 0.75rem; font-size: 0.9375rem; }
.chips__engages-amount select { padding: 0.5rem 0.75rem; font-size: 0.9375rem; }
.chips__baseline {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.9375rem;
font-weight: 700;
}
.chips__baseline input {
flex: 1;
min-width: 0;
padding: 0.5rem 0.75rem;
font-size: 0.9375rem;
font-weight: 500;
}
</style>