MSG_MOTOC / static /index.html
Samuel ADONE
Poste de travail : liste et conversation côte à côte au-delà de 1024 px
ec3b8ae
Raw
History Blame Contribute Delete
29.8 kB
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8" />
<meta
name="viewport"
content="width=device-width, initial-scale=1, viewport-fit=cover, maximum-scale=1"
/>
<title>MSG MOTOC</title>
<meta name="description" content="Messagerie de groupe temps réel." />
<meta name="theme-color" content="#071318" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="MOTOC" />
<link rel="manifest" href="/manifest.webmanifest" />
<link rel="icon" href="/static/icons/favicon-64.png" sizes="64x64" />
<link rel="apple-touch-icon" href="/static/icons/apple-touch-icon.png" />
<link rel="stylesheet" href="/static/styles.css" />
</head>
<body>
<!-- Bibliothèque d'icônes (traits, style « outline ») -->
<svg style="display: none" xmlns="http://www.w3.org/2000/svg">
<symbol id="i-back" viewBox="0 0 24 24"><path d="M15 18l-6-6 6-6" /></symbol>
<symbol id="i-settings" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="3" />
<path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 11-2.83 2.83l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 11-4 0v-.09A1.65 1.65 0 008.6 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 11-2.83-2.83l.06-.06a1.65 1.65 0 00.33-1.82 1.65 1.65 0 00-1.51-1H3a2 2 0 110-4h.09A1.65 1.65 0 004.6 8.6a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 112.83-2.83l.06.06a1.65 1.65 0 001.82.33H9a1.65 1.65 0 001-1.51V3a2 2 0 114 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 112.83 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9a1.65 1.65 0 001.51 1H21a2 2 0 110 4h-.09a1.65 1.65 0 00-1.51 1z" />
</symbol>
<symbol id="i-shield" viewBox="0 0 24 24">
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
</symbol>
<symbol id="i-info" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" /><path d="M12 16v-4M12 8h.01" />
</symbol>
<symbol id="i-image" viewBox="0 0 24 24">
<rect x="3" y="3" width="18" height="18" rx="2" />
<circle cx="8.5" cy="8.5" r="1.5" /><path d="M21 15l-5-5L5 21" />
</symbol>
<symbol id="i-send" viewBox="0 0 24 24"><path d="M2 21l21-9L2 3v7l15 2-15 2v7z" /></symbol>
<symbol id="i-reply" viewBox="0 0 24 24"><path d="M9 17l-6-6 6-6M3 11h11a6 6 0 016 6v3" /></symbol>
<symbol id="i-pin" viewBox="0 0 24 24">
<path d="M9 4h6l-1 6 4 3v2H6v-2l4-3-1-6zM12 15v5" />
</symbol>
<symbol id="i-copy" viewBox="0 0 24 24">
<rect x="9" y="9" width="13" height="13" rx="2" />
<path d="M5 15H4a2 2 0 01-2-2V4a2 2 0 012-2h9a2 2 0 012 2v1" />
</symbol>
<symbol id="i-trash" viewBox="0 0 24 24">
<path d="M3 6h18M8 6V4a1 1 0 011-1h6a1 1 0 011 1v2M19 6l-1 14a2 2 0 01-2 2H8a2 2 0 01-2-2L5 6" />
</symbol>
<symbol id="i-bell" viewBox="0 0 24 24">
<path d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9M13.7 21a2 2 0 01-3.4 0" />
</symbol>
<symbol id="i-logout" viewBox="0 0 24 24">
<path d="M9 21H5a2 2 0 01-2-2V5a2 2 0 012-2h4M16 17l5-5-5-5M21 12H9" />
</symbol>
<symbol id="i-users" viewBox="0 0 24 24">
<path d="M17 21v-2a4 4 0 00-4-4H5a4 4 0 00-4 4v2" />
<circle cx="9" cy="7" r="4" /><path d="M23 21v-2a4 4 0 00-3-3.87M16 3.13a4 4 0 010 7.75" />
</symbol>
<symbol id="i-plus" viewBox="0 0 24 24"><path d="M12 5v14M5 12h14" /></symbol>
<symbol id="i-check" viewBox="0 0 24 24"><path d="M20 6L9 17l-5-5" /></symbol>
<!-- Double coche : accusé de lecture. -->
<symbol id="i-check2" viewBox="0 0 30 24">
<path d="M15 6L6.5 17 2 12" />
<path d="M28 6l-11 11-2.2-2.4" />
</symbol>
<symbol id="i-chat" viewBox="0 0 24 24">
<path d="M21 11.5a8.38 8.38 0 01-.9 3.8 8.5 8.5 0 01-7.6 4.7 8.38 8.38 0 01-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 01-.9-3.8 8.5 8.5 0 014.7-7.6 8.38 8.38 0 013.8-.9h.5a8.48 8.48 0 018 8v.5z" />
</symbol>
<symbol id="i-download" viewBox="0 0 24 24">
<path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4M7 10l5 5 5-5M12 15V3" />
</symbol>
<symbol id="i-loader" viewBox="0 0 24 24">
<path d="M21 12a9 9 0 11-6.2-8.6" />
</symbol>
<symbol id="i-alert" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" /><path d="M12 8v4M12 16h.01" />
</symbol>
<symbol id="i-key" viewBox="0 0 24 24">
<path d="M21 2l-2 2m-7.6 7.6a5 5 0 11-7.1 7.1 5 5 0 017.1-7.1zm0 0L15.5 7.5m0 0l3 3L22 7l-3-3" />
</symbol>
</svg>
<div id="app">
<!-- ============================ AUTHENTIFICATION ============================ -->
<section id="screen-auth" class="screen active">
<div class="auth-inner">
<div class="auth-mark">
<img src="/static/icons/icon-192.png" alt="" />
<h1>MSG Motoc</h1>
<p>Messagerie de groupe</p>
</div>
<div class="bolt-rule"></div>
<div class="auth-card">
<div id="auth-error" class="form-error"></div>
<!-- Étape 0 : choix du profil -->
<div id="step-role" class="auth-step">
<h2 class="auth-step-title">Vous êtes…</h2>
<p class="auth-step-sub">Choisissez votre profil pour continuer.</p>
<button class="role-card" data-role="admin">
<svg><use href="#i-shield" /></svg>
<div>
<strong>Admin</strong>
<span>Groupes, membres, rôles et supervision</span>
</div>
</button>
<button class="role-card benevole" data-role="benevole">
<svg><use href="#i-users" /></svg>
<div>
<strong>Bénévole</strong>
<span>Vos groupes et vos conversations</span>
</div>
</button>
</div>
<!-- Étape 1 : numéro -->
<div id="step-phone" class="auth-step" hidden>
<div id="role-pill-slot"></div>
<h2 class="auth-step-title">Votre numéro</h2>
<p class="auth-step-sub">
Votre numéro de mobile est votre identifiant. Si vous n'avez jamais utilisé
l'application, vous créerez votre compte à l'étape suivante.
</p>
<div class="field">
<label for="in-phone">Téléphone</label>
<input
id="in-phone"
class="input-phone"
type="tel"
inputmode="numeric"
autocomplete="tel"
placeholder="06 12 34 56 78"
maxlength="14"
/>
</div>
<button id="btn-phone" class="btn btn-primary">Continuer</button>
<button class="btn-link" data-back-to-role>Changer de profil</button>
</div>
<!-- Étape 2 : inscription -->
<div id="step-register" class="auth-step" hidden>
<h2 class="auth-step-title">Créer votre profil</h2>
<p class="auth-step-sub">
Première connexion pour le <strong id="reg-phone"></strong>.
</p>
<p id="reg-newcomer" class="auth-note" hidden>
Votre numéro n'était pas encore connu : vous créez votre compte. Un administrateur
vous ajoutera ensuite à vos groupes — ils apparaîtront tout seuls.
</p>
<div class="field-row">
<div class="field">
<label for="in-first">Prénom</label>
<input id="in-first" type="text" autocomplete="given-name" maxlength="60" />
</div>
<div class="field">
<label for="in-last">Nom</label>
<input id="in-last" type="text" autocomplete="family-name" maxlength="60" />
</div>
</div>
<div class="field">
<label for="in-nick">Pseudo</label>
<input id="in-nick" type="text" maxlength="40" placeholder="Affiché dans les groupes" />
<div class="hint">Laissez vide pour utiliser votre prénom.</div>
</div>
<div class="field">
<label for="in-pin1">Code PIN (6 chiffres)</label>
<input
id="in-pin1"
class="input-pin"
type="password"
inputmode="numeric"
autocomplete="new-password"
maxlength="6"
/>
</div>
<div class="field">
<label for="in-pin2">Confirmer le code PIN</label>
<input
id="in-pin2"
class="input-pin"
type="password"
inputmode="numeric"
autocomplete="new-password"
maxlength="6"
/>
<div class="hint">Il protège votre compte : ne le partagez pas.</div>
</div>
<button id="btn-register" class="btn btn-ember">Créer mon compte</button>
<button class="btn-link" data-back-to-phone>Changer de numéro</button>
<button class="btn-link" data-back-to-role>Changer de profil</button>
</div>
<!-- Étape 3 : connexion -->
<div id="step-login" class="auth-step" hidden>
<h2 class="auth-step-title">Bon retour</h2>
<p class="auth-step-sub">
<strong id="login-name"></strong><br /><span id="login-phone"></span>
</p>
<div class="field">
<label for="in-pin">Code PIN</label>
<input
id="in-pin"
class="input-pin"
type="password"
inputmode="numeric"
autocomplete="current-password"
maxlength="6"
/>
</div>
<button id="btn-login" class="btn btn-primary">Se connecter</button>
<button class="btn-link" data-open-help>Code oublié ou compte bloqué ?</button>
<button class="btn-link" data-back-to-phone>Changer de numéro</button>
<button class="btn-link" data-back-to-role>Changer de profil</button>
</div>
<!-- Étape 4 : demande d'assistance (compte verrouillé / PIN oublié) -->
<div id="step-help" class="auth-step" hidden>
<h2 class="auth-step-title">Contacter un administrateur</h2>
<p class="auth-step-sub">
Votre demande part aux administrateurs de vos groupes. Ils pourront déverrouiller
votre compte ou réinitialiser votre code PIN.
</p>
<div class="field">
<label for="in-help-reason">Motif</label>
<select id="in-help-reason">
<option value="locked">Compte verrouillé après trop d'essais</option>
<option value="forgot_pin">J'ai oublié mon code PIN</option>
<option value="blocked">Mon compte est bloqué</option>
<option value="other">Autre</option>
</select>
</div>
<div class="field">
<label for="in-help-msg">Message (facultatif)</label>
<textarea id="in-help-msg" rows="3" maxlength="400" placeholder="Précisez si besoin…"></textarea>
</div>
<button id="btn-help-send" class="btn btn-ember">Envoyer la demande</button>
<button class="btn-link" data-back-to-phone>Retour</button>
</div>
</div>
</div>
</section>
<!-- ============================ LISTE DES GROUPES ============================ -->
<section id="screen-groups" class="screen">
<header class="topbar">
<div style="width: 8px"></div>
<h2>
MSG Motoc
<div class="sub" id="conn-state">Connexion…</div>
</h2>
<button class="icon-btn" id="btn-admin" hidden title="Administration">
<svg><use href="#i-shield" /></svg>
</button>
<button class="icon-btn" id="btn-settings" title="Réglages">
<svg><use href="#i-settings" /></svg>
</button>
</header>
<div class="search-wrap">
<input id="in-search" type="search" placeholder="Rechercher un groupe…" />
</div>
<!-- Demandes d'assistance : visible par tout administrateur de groupe,
pas seulement par le superadmin. -->
<div id="help-requests-inline" style="padding: 0 12px"></div>
<div class="list-body" id="groups-list"></div>
</section>
<!-- Volet droit du poste de travail, quand aucune conversation n'est
ouverte. Masqué sur téléphone, où la liste occupe tout l'écran. -->
<div id="chat-placeholder">
<svg><use href="#i-chat" /></svg>
<h3>Vos conversations</h3>
<p>Choisissez un groupe dans la liste pour l'ouvrir ici.</p>
</div>
<!-- ============================ CONVERSATION ============================ -->
<section id="screen-chat" class="screen overlay">
<header class="topbar">
<button class="icon-btn" data-close-chat>
<svg><use href="#i-back" /></svg>
</button>
<h2>
<span id="chat-title">Groupe</span>
<div class="sub" id="chat-sub"></div>
</h2>
<button class="icon-btn" id="btn-group-info">
<svg><use href="#i-info" /></svg>
</button>
</header>
<div class="pin-bar" id="pin-bar" hidden></div>
<div class="thread" id="thread"></div>
<div class="composer">
<div class="typing-line" id="typing-line"></div>
<div class="reply-bar" id="reply-bar">
<div><strong id="reply-author"></strong> <span id="reply-text"></span></div>
<button class="icon-btn" id="btn-cancel-reply" style="width: 30px; height: 30px">
<svg viewBox="0 0 24 24" style="width: 16px; height: 16px">
<path d="M18 6L6 18M6 6l12 12" />
</svg>
</button>
</div>
<div class="composer-row">
<button class="icon-btn" id="btn-image" title="Envoyer une photo (P2P)">
<svg><use href="#i-image" /></svg>
</button>
<textarea
id="in-message"
rows="1"
placeholder="Message…"
enterkeyhint="send"
maxlength="4000"
></textarea>
<button class="send-btn" id="btn-send" disabled>
<svg><use href="#i-send" /></svg>
</button>
</div>
</div>
<input type="file" id="file-image" accept="image/*" hidden />
</section>
<!-- ============================ RÉGLAGES ============================ -->
<section id="screen-settings" class="screen overlay">
<header class="topbar">
<button class="icon-btn" data-close-overlay="screen-settings">
<svg><use href="#i-back" /></svg>
</button>
<h2>Réglages</h2>
</header>
<div class="list-body" style="padding: 14px">
<div class="panel-card">
<h4>Profil</h4>
<div class="field-row">
<div class="field">
<label for="set-first">Prénom</label>
<input id="set-first" type="text" maxlength="60" />
</div>
<div class="field">
<label for="set-last">Nom</label>
<input id="set-last" type="text" maxlength="60" />
</div>
</div>
<div class="field">
<label for="set-nick">Pseudo</label>
<input id="set-nick" type="text" maxlength="40" />
</div>
<div class="field">
<label>Numéro (identifiant)</label>
<input id="set-phone" type="text" disabled />
</div>
<button id="btn-save-profile" class="btn btn-primary btn-sm" style="width: 100%">
Enregistrer
</button>
</div>
<div class="panel-card">
<h4>Notifications</h4>
<div class="opt-row">
<div>
<div class="label">Notifications système</div>
<div class="desc" id="push-desc">Recevoir les messages même application fermée.</div>
</div>
<button class="switch" id="sw-push" aria-label="Activer les notifications"></button>
</div>
<button id="btn-test-push" class="btn btn-ghost btn-sm" style="width: 100%; margin-top: 12px">
Envoyer une notification test
</button>
</div>
<div class="panel-card">
<h4>Sécurité</h4>
<div class="field">
<label for="set-pin-old">Code PIN actuel</label>
<input id="set-pin-old" class="input-pin" type="password" inputmode="numeric" maxlength="6" />
</div>
<div class="field">
<label for="set-pin-new">Nouveau code PIN</label>
<input id="set-pin-new" class="input-pin" type="password" inputmode="numeric" maxlength="6" />
</div>
<button id="btn-change-pin" class="btn btn-ghost btn-sm" style="width: 100%">
Changer le code PIN
</button>
</div>
<button id="btn-logout" class="btn btn-danger" style="margin-top: 6px">
Se déconnecter
</button>
<p style="text-align: center; color: var(--text-faint); font-size: 12px; margin-top: 18px">
MSG MOTOC — les photos transitent directement entre appareils (P2P), jamais par le
serveur.
</p>
</div>
</section>
<!-- ============================ ADMINISTRATION ============================ -->
<section id="screen-admin" class="screen overlay">
<header class="topbar">
<button class="icon-btn" data-close-overlay="screen-admin">
<svg><use href="#i-back" /></svg>
</button>
<h2>Administration</h2>
</header>
<nav class="tabs">
<button class="active" data-tab="tab-dash">Bord</button>
<button data-tab="tab-groups">Groupes</button>
<button data-tab="tab-users">Membres</button>
<button data-tab="tab-moderation">
Modération <span class="tab-badge" id="moderation-badge" hidden>0</span>
</button>
<button data-tab="tab-system">Machine</button>
<button data-tab="tab-playground">Bac à sable</button>
<button data-tab="tab-stats">Système</button>
</nav>
<!-- ---------------------- TABLEAU DE BORD ---------------------- -->
<div class="tab-panel active" id="tab-dash">
<div id="help-requests-card"></div>
<div class="panel-card">
<h4>Vue d'ensemble</h4>
<div class="kpi-grid" id="dash-kpis"></div>
</div>
<div class="panel-card">
<div class="chart-head">
<h4>Messages par jour</h4>
<span class="chart-note" id="daily-note"></span>
</div>
<div class="chart-scroll"><div id="chart-daily"></div></div>
</div>
<div class="panel-card">
<div class="chart-head">
<h4>Activité des dernières 24 h</h4>
<span class="chart-note" id="hourly-note"></span>
</div>
<div class="chart-scroll"><div id="chart-hourly"></div></div>
</div>
<div class="panel-card">
<div class="chart-head">
<h4>Groupes les plus actifs</h4>
<span class="chart-note">30 derniers jours</span>
</div>
<div id="chart-groups"></div>
</div>
<div class="panel-card">
<div class="chart-head">
<h4>Membres les plus actifs</h4>
<span class="chart-note">30 derniers jours</span>
</div>
<div id="chart-members"></div>
</div>
<div class="panel-card">
<div class="chart-head">
<h4>Journal d'audit</h4>
<button class="btn-link" id="btn-audit-all" style="width: auto; padding: 0">
Tout voir
</button>
</div>
<div id="audit-list"></div>
</div>
</div>
<div class="tab-panel" id="tab-groups">
<div class="panel-card">
<h4>Nouveau groupe</h4>
<div class="field">
<label for="ng-name">Nom</label>
<input id="ng-name" type="text" maxlength="80" placeholder="Ex. Bénévoles Scène 1" />
</div>
<div class="field">
<label for="ng-parent">Groupe parent</label>
<select id="ng-parent">
<option value="">— Aucun (groupe principal) —</option>
</select>
<div class="hint">Choisir un parent crée un sous-groupe.</div>
</div>
<div class="field">
<label for="ng-desc">Description</label>
<input id="ng-desc" type="text" maxlength="300" />
</div>
<button id="btn-create-group" class="btn btn-ember btn-sm" style="width: 100%">
Créer le groupe
</button>
</div>
<div class="panel-card">
<h4>Groupes existants</h4>
<div id="admin-groups"></div>
</div>
</div>
<div class="tab-panel" id="tab-users">
<div class="panel-card">
<h4>Enregistrer des numéros</h4>
<div class="field">
<label for="nu-phones">Numéros (un par ligne)</label>
<textarea
id="nu-phones"
rows="4"
placeholder="06 12 34 56 78&#10;07 98 76 54 32"
></textarea>
<div class="hint">
Les personnes créeront elles-mêmes leur profil et leur code PIN.
</div>
</div>
<button id="btn-add-users" class="btn btn-ember btn-sm" style="width: 100%">
Enregistrer
</button>
</div>
<div class="panel-card">
<h4>Importer un fichier</h4>
<p class="panel-note">
Excel (.xlsx) ou CSV. Les colonnes prénom, nom et téléphone sont retrouvées
automatiquement — vous pourrez vérifier avant d'importer.
</p>
<div class="field">
<label for="in-import-group">Ajouter aussi à un groupe (facultatif)</label>
<select id="in-import-group">
<option value="">Aucun groupe</option>
</select>
</div>
<input id="in-import-file" type="file" accept=".csv,.tsv,.txt,.xlsx,.xlsm" hidden />
<button id="btn-pick-file" class="btn btn-ghost btn-sm" style="width: 100%">
Choisir un fichier…
</button>
<div id="import-result" hidden></div>
</div>
<div class="panel-card">
<h4>Annuaire</h4>
<div class="field">
<input id="in-user-search" type="search" placeholder="Rechercher (nom, numéro)…" />
</div>
<div id="admin-users"></div>
</div>
</div>
<div class="tab-panel" id="tab-stats">
<div class="panel-card">
<h4>Fréquentation</h4>
<div class="stat-grid" id="stat-grid"></div>
</div>
<div class="panel-card">
<h4>Stockage</h4>
<div class="meter"><span id="storage-bar" style="width: 0%"></span></div>
<div id="storage-text" style="font-size: 13px; color: var(--text-dim)"></div>
<button id="btn-purge" class="btn btn-ghost btn-sm" style="width: 100%; margin-top: 14px">
Lancer une purge maintenant
</button>
</div>
</div>
<!-- ---------------------- MODÉRATION ---------------------- -->
<div class="tab-panel" id="tab-moderation">
<div class="panel-card">
<h4>Signalements</h4>
<p class="panel-note">
Les messages surveillés sont <strong>livrés normalement</strong> — l'expéditeur voit
seulement une mention discrète. Un faux positif ne bloque jamais une consigne.
</p>
<div class="segmented" id="seg-moderation">
<button data-status="pending" class="active">À traiter</button>
<button data-status="">Tout l'historique</button>
</div>
<div id="moderation-list"></div>
</div>
<div class="panel-card">
<h4>Termes surveillés</h4>
<p class="panel-note">
Un terme par ligne. Les accents, la casse, les espaces intercalaires
(« g r a t u i t »), les chiffres-substituts (« gr4tuit ») et les fautes de frappe
sont reconnus automatiquement.
</p>
<div id="terms-editor"></div>
<button id="btn-save-terms" class="btn btn-ember btn-sm" style="width: 100%">
Enregistrer la liste
</button>
</div>
</div>
<!-- ---------------------- MACHINE ---------------------- -->
<div class="tab-panel" id="tab-system">
<div class="sys-live">
<span class="sys-dot" id="sys-dot"></span>
<span id="sys-status">Mesure en cours…</span>
<button class="btn-link" id="btn-sys-toggle">Suspendre</button>
</div>
<div class="sys-grid" id="sys-grid"></div>
<div class="panel-card">
<h4>Réseau</h4>
<div class="spark-row">
<div class="spark-block">
<div class="spark-label">Entrant <strong id="net-rx"></strong></div>
<svg class="spark" id="spark-rx" viewBox="0 0 200 44" preserveAspectRatio="none">
<polyline points="" />
</svg>
</div>
<div class="spark-block">
<div class="spark-label">Sortant <strong id="net-tx"></strong></div>
<svg class="spark ember" id="spark-tx" viewBox="0 0 200 44" preserveAspectRatio="none">
<polyline points="" />
</svg>
</div>
</div>
</div>
<div class="panel-card">
<h4>Processeur</h4>
<svg class="spark tall" id="spark-cpu" viewBox="0 0 200 70" preserveAspectRatio="none">
<polyline points="" />
</svg>
<div id="cpu-detail" class="sys-detail"></div>
</div>
</div>
<!-- ---------------------- BAC À SABLE ---------------------- -->
<div class="tab-panel" id="tab-playground">
<div class="panel-card">
<h4>Bac à sable</h4>
<p class="panel-note">
Un festival fictif pour prendre l'application en main. Ces comptes
<strong>ne peuvent pas se connecter</strong> et n'apparaissent nulle part ailleurs :
ni dans l'annuaire, ni dans les statistiques, ni pour les bénévoles.
</p>
<div id="playground-groups"></div>
</div>
<div class="panel-card" id="playground-people-card">
<h4>Comptes fictifs</h4>
<div id="playground-users"></div>
</div>
</div>
</section>
<!-- ============================ BAC À SABLE : CONVERSATION ============================ -->
<section id="screen-sandbox" class="screen overlay">
<header class="topbar">
<button class="icon-btn" data-close-overlay="screen-sandbox">
<svg><use href="#i-back" /></svg>
</button>
<h2>
<span id="sandbox-title">Bac à sable</span>
<div class="sub" id="sandbox-sub"></div>
</h2>
<button class="icon-btn" id="btn-sandbox-menu" title="Options">
<svg><use href="#i-info" /></svg>
</button>
</header>
<div class="sandbox-banner">
Bac à sable — rien de ce qui est écrit ici n'est visible par les bénévoles.
</div>
<div class="thread" id="sandbox-thread"></div>
<div class="composer">
<div class="field" style="margin: 0 0 8px">
<label for="sandbox-as">Écrire en tant que</label>
<select id="sandbox-as"></select>
</div>
<div class="composer-row">
<textarea id="sandbox-message" rows="1" placeholder="Message…" maxlength="4000"></textarea>
<button class="send-btn" id="btn-sandbox-send">
<svg><use href="#i-send" /></svg>
</button>
</div>
</div>
</section>
</div>
<!-- Feuilles contextuelles -->
<div class="sheet-backdrop" id="sheet-backdrop">
<div class="sheet" id="sheet"></div>
</div>
<div id="toasts"></div>
<script src="/static/app.js"></script>
</body>
</html>