DOCS/claude design/refonte-configuration-sections.html
2026-09-04 16:48:04 +02:00

1312 lines
85 KiB
HTML
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.

<!-- Maquette : refonte des ecrans Configuration et Section (manager-app), et de leurs dialogues.
Artifact de diffusion 4d0c2ad6-2e55-4084-8df9-55ec19689cab, publie le 2026-09-03.
Source de verite du design : ce fichier. L'artifact ne sert qu'a la diffusion.
Toutes les valeurs sont reprises de manager-app/lib/constants.dart : ne pas en inventer ici.
Coquille de dialogue de reference : Components/confirmation_dialog.dart.
Voisins : mediatheque-ajout-ressource.html (dialogue d'ajout), DOCS/v1-mediatheque-plan.md
(contenu du selecteur de ressource — ce fichier n'en reprend que le cadre, cf. section 06). -->
<title>Refonte Configuration &amp; Sections</title>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Archivo:wght@500;600;700&family=Source+Serif+4:ital,opsz,wght@0,8..60,400;0,8..60,600;1,8..60,400&family=IBM+Plex+Mono:wght@400;500&family=Roboto:wght@300;400;500;600&family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0&display=swap">
<style>
/* ─── Page chrome tokens (theme-aware) ─── */
:root {
--bg: #eceff1;
--bg-deep: #e0e5e8;
--surface: #ffffff;
--ink: #16212a;
--ink-soft: #4c5f6c;
--ink-faint: #7c8d99;
--rule: #cdd6db;
--accent: #1d5c7a;
--accent-soft: #dceaf1;
--warn: #8a4a2b;
--warn-soft: #f3e4da;
--ok: #3f6b23;
--ok-soft: #e4eeda;
--shadow: 0 1px 2px rgba(20,35,45,.06), 0 8px 24px rgba(20,35,45,.07);
/* ─── manager-app tokens : copiés de constants.dart, jamais inversés ─── */
--app-ground: #f5f5f7; /* kGround */
--app-white: #ffffff; /* kSurface */
--app-surface2: #f3f5f8; /* kSurface2 */
--app-surface3: #e7ebf1; /* kSurface3 */
--app-ink: #303030; /* kInk */
--app-ink2: #4a5a6b; /* kInk2 */
--app-muted: #7c8b9a; /* kInk3 */
--app-line: #d6dde5; /* kLine */
--app-line-soft: #e6ebf1; /* kLineSoft */
--app-primary: #264863; /* kBrand */
--app-brand2: #3c6c90; /* kBrand2 */
--app-second: #c2c9d6; /* kBrandSoft */
--app-success: #8bc34a; /* kGood */
--app-error: #ca413f; /* kSerious */
--app-warning: #9a6608; /* kWarning */
/* rayons : kRadiusInput / Card / Shell / Pill */
--r-input: 5px;
--r-card: 8px;
--r-shell: 10px;
--r-pill: 999px;
}
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) {
--bg: #121a20;
--bg-deep: #0d1418;
--surface: #1a252c;
--ink: #e6edf1;
--ink-soft: #a6b7c1;
--ink-faint: #74868f;
--rule: #2c3b45;
--accent: #6fb6d8;
--accent-soft: #1c3440;
--warn: #d99b6c;
--warn-soft: #33241a;
--ok: #9dc97a;
--ok-soft: #1f2c18;
--shadow: 0 1px 2px rgba(0,0,0,.4), 0 10px 30px rgba(0,0,0,.35);
}
}
:root[data-theme="dark"] {
--bg: #121a20;
--bg-deep: #0d1418;
--surface: #1a252c;
--ink: #e6edf1;
--ink-soft: #a6b7c1;
--ink-faint: #74868f;
--rule: #2c3b45;
--accent: #6fb6d8;
--accent-soft: #1c3440;
--warn: #d99b6c;
--warn-soft: #33241a;
--ok: #9dc97a;
--ok-soft: #1f2c18;
--shadow: 0 1px 2px rgba(0,0,0,.4), 0 10px 30px rgba(0,0,0,.35);
}
* { box-sizing: border-box; }
body {
background: var(--bg);
color: var(--ink);
font-family: "Source Serif 4", Georgia, serif;
font-size: 17px;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
}
.wrap { max-width: 1240px; margin: 0 auto; padding: 0 32px 96px; }
.prose { max-width: 68ch; }
h1, h2, h3, .ui, .eyebrow, .chip, .tag { font-family: Archivo, "Helvetica Neue", sans-serif; }
h1 {
font-size: clamp(34px, 5vw, 56px);
line-height: 1.02;
letter-spacing: -.025em;
font-weight: 700;
margin: 0 0 18px;
text-wrap: balance;
}
h2 {
font-size: clamp(23px, 2.6vw, 31px);
line-height: 1.14;
letter-spacing: -.018em;
font-weight: 600;
margin: 0 0 6px;
text-wrap: balance;
}
h3 {
font-size: 18px;
letter-spacing: -.01em;
font-weight: 600;
margin: 0 0 8px;
}
p { margin: 0 0 16px; }
a { color: var(--accent); }
code, .mono { font-family: "IBM Plex Mono", ui-monospace, monospace; font-size: .86em; }
code { background: var(--bg-deep); border: 1px solid var(--rule); border-radius: 4px; padding: 1px 5px; }
strong { font-weight: 600; }
.eyebrow {
font-size: 11px;
font-weight: 600;
letter-spacing: .16em;
text-transform: uppercase;
color: var(--ink-faint);
}
header.masthead {
padding: 72px 0 40px;
border-bottom: 1px solid var(--rule);
margin-bottom: 56px;
}
header.masthead .lede {
font-size: 21px;
line-height: 1.5;
color: var(--ink-soft);
max-width: 62ch;
}
.meta-row {
display: flex; flex-wrap: wrap; gap: 10px; margin-top: 26px;
}
.tag {
font-size: 11.5px; font-weight: 600; letter-spacing: .04em;
padding: 5px 11px; border-radius: 999px;
background: var(--surface); border: 1px solid var(--rule); color: var(--ink-soft);
}
section.block { margin-bottom: 76px; scroll-margin-top: 24px; }
.section-head { display: flex; align-items: baseline; gap: 14px; margin-bottom: 24px; }
.section-head .num {
font-family: "IBM Plex Mono", monospace; font-size: 13px; font-weight: 500;
color: var(--accent); padding-top: 6px; white-space: nowrap;
}
.section-head .sub { color: var(--ink-soft); font-size: 16px; margin: 4px 0 0; }
/* ─── Diagnostic ─── */
.diag { display: grid; gap: 1px; background: var(--rule); border: 1px solid var(--rule); border-radius: 10px; overflow: hidden; }
.diag-row { background: var(--surface); padding: 18px 20px; display: grid; grid-template-columns: minmax(0,1.15fr) minmax(0,1.5fr) minmax(0,1fr); gap: 24px; align-items: start; }
.diag-row.head { background: var(--bg-deep); font-family: Archivo, sans-serif; font-size: 11px; font-weight: 600; letter-spacing: .13em; text-transform: uppercase; color: var(--ink-faint); padding: 10px 20px; }
.diag-row .what { font-weight: 600; font-size: 16px; }
.diag-row .what .mono { display: block; font-weight: 400; font-size: 12px; color: var(--ink-faint); margin-top: 3px; }
.diag-row .why, .diag-row .fix { font-size: 15px; line-height: 1.5; color: var(--ink-soft); }
.diag-row .fix { color: var(--ink); }
@media (max-width: 860px) { .diag-row { grid-template-columns: 1fr; gap: 10px; } .diag-row.head { display: none; } }
/* ─── Mockup frame ─── */
figure.mock { margin: 0 0 12px; }
.frame {
background: var(--app-ground);
border: 1px solid var(--rule);
border-radius: 12px;
box-shadow: var(--shadow);
overflow: hidden;
font-family: Roboto, "Segoe UI", sans-serif;
font-size: 13px;
line-height: 1.4;
color: var(--app-ink);
}
.frame .bar {
display: flex; align-items: center; gap: 7px;
padding: 9px 14px; background: #dfe3e9; border-bottom: 1px solid var(--app-second);
}
.frame .bar i { width: 9px; height: 9px; border-radius: 50%; background: #b9c0cb; display: block; }
.frame .bar span { margin-left: 10px; font-family: "IBM Plex Mono", monospace; font-size: 10.5px; color: #6b7684; letter-spacing: .01em; }
figcaption {
font-size: 14px; color: var(--ink-soft); margin-top: 12px; max-width: 78ch;
}
figcaption b { font-family: Archivo, sans-serif; font-weight: 600; color: var(--ink); font-size: 13px; }
.msym { font-family: "Material Symbols Outlined"; font-weight: normal; font-style: normal; line-height: 1; letter-spacing: normal; text-transform: none; display: inline-block; white-space: nowrap; direction: ltr; font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24; -webkit-font-feature-settings: 'liga'; }
/* ─── App shell inside mockups ─── */
.app-head {
display: flex; align-items: center; gap: 12px;
background: var(--app-white); border-bottom: 1px solid var(--app-line);
padding: 11px 18px;
}
.app-head .back { color: var(--app-primary); font-size: 20px; }
.app-head .titleblock { min-width: 0; }
.app-head .t { font-size: 16px; font-weight: 600; color: var(--app-ink); display: flex; align-items: center; gap: 7px; }
.app-head .t .msym { font-size: 15px; color: var(--app-muted); }
.app-head .s { font-size: 11.5px; color: var(--app-muted); }
.app-head .spacer { flex: 1; }
.type-chip {
font-size: 10.5px; font-weight: 600; letter-spacing: .069em; text-transform: uppercase;
color: var(--app-primary); background: var(--app-surface3);
border-radius: var(--r-input); padding: 4px 9px; display: inline-flex; align-items: center; gap: 5px;
}
.type-chip .msym { font-size: 13px; }
/* Boutons : kRadiusPill, 13px w600 — la convention de confirmation_dialog.dart */
.ghost-btn, .solid-btn, .danger-btn {
font-family: Roboto, sans-serif; font-size: 13px; font-weight: 600;
border-radius: var(--r-pill); padding: 8px 16px; display: inline-flex; align-items: center; gap: 6px;
border: 1px solid transparent; cursor: default; white-space: nowrap;
}
.ghost-btn { color: var(--app-ink2); background: none; }
.solid-btn { color: #fff; background: var(--app-primary); border-color: var(--app-primary); }
.danger-btn { color: #fff; background: var(--app-error); border-color: var(--app-error); }
.ghost-btn .msym, .solid-btn .msym, .danger-btn .msym { font-size: 15px; }
.icon-btn { width: 30px; height: 30px; border-radius: var(--r-input); display: grid; place-items: center; color: var(--app-ink2); border: 1px solid var(--app-line); background: var(--app-white); }
.icon-btn .msym { font-size: 16px; }
.app-body { padding: 16px; display: grid; gap: 14px; }
.cols { display: grid; grid-template-columns: minmax(0, 1fr) 300px; gap: 14px; align-items: start; }
@media (max-width: 900px) { .cols { grid-template-columns: 1fr; } }
.pane {
background: var(--app-white); border: 1px solid var(--app-line);
border-radius: var(--r-card); padding: 17px 18px 19px;
}
/* Titre de carte : kTitleCard — 14.5 / w700 / kInk, pas une capitale grise */
.pane > .pane-t {
font-size: 14.5px; font-weight: 700;
color: var(--app-ink); margin-bottom: 12px; display: flex; align-items: center; gap: 7px;
}
.pane > .pane-t .msym { font-size: 14px; color: var(--app-primary); }
.pane > .pane-t .spacer { flex: 1; }
/* ─── The redesigned field ─── */
.fgrid { display: grid; grid-template-columns: repeat(12, 1fr); gap: 12px 14px; }
.f { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
/* Label : kLabelField — 12.5 / w600 / kInk2, pas de capitales */
.f > label {
font-size: 12.5px; font-weight: 600;
color: var(--app-ink2); display: flex; align-items: center; gap: 5px;
}
.f > label em { font-style: normal; color: var(--app-error); }
.input {
border: 1px solid var(--app-line); background: var(--app-surface2); border-radius: var(--r-input);
padding: 8px 12px; font-size: 13px; color: var(--app-ink);
display: flex; align-items: center; gap: 8px; min-height: 35px;
}
.input .val { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.input .count { font-size: 10.5px; color: var(--app-muted); font-variant-numeric: tabular-nums; }
.input .msym { font-size: 16px; color: var(--app-muted); }
.input.select { justify-content: space-between; }
/* kTextHint — 12 / kInk3 */
.hint { font-size: 12px; color: var(--app-muted); }
.langs { display: flex; gap: 5px; }
.lang { font-size: 10.5px; font-weight: 600; letter-spacing: .05em; padding: 3px 7px; border-radius: var(--r-input); background: var(--app-surface3); color: var(--app-primary); }
.lang.off { background: none; color: #a6afbb; box-shadow: inset 0 0 0 1px var(--app-line-soft); }
.trad {
border: 1px solid var(--app-line); border-radius: var(--r-input); padding: 7px 12px;
display: flex; align-items: center; gap: 10px; background: var(--app-surface2);
}
.trad .v { flex: 1; font-size: 13px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.trad .st { font-size: 10.5px; font-weight: 600; color: #1f6b4d; background: #e4f0ea; border-radius: var(--r-pill); padding: 2px 8px; white-space: nowrap; }
.trad .st.part { color: var(--app-warning); background: #fdf6e3; }
.sw { display: flex; align-items: center; gap: 9px; }
.sw .track { width: 34px; height: 19px; border-radius: 999px; background: var(--app-primary); position: relative; flex: none; }
.sw .track::after { content: ""; position: absolute; top: 2px; right: 2px; width: 15px; height: 15px; border-radius: 50%; background: #fff; }
.sw .track.off { background: #cdd3dd; }
.sw .track.off::after { right: auto; left: 2px; }
.sw .lbl { font-size: 13px; }
.sw .lbl small { display: block; font-size: 11px; color: var(--app-muted); }
/* ─── Media slots ─── */
.slot {
border: 1px dashed var(--app-line); border-radius: var(--r-card); overflow: hidden;
background: var(--app-ground); position: relative;
}
.slot.filled { border-style: solid; }
.slot .ph { aspect-ratio: 16/10; display: grid; place-items: center; color: var(--app-muted); gap: 4px; text-align: center; }
.slot .ph .msym { font-size: 22px; }
.slot .ph .cta { font-size: 11px; }
.slot .thumb { aspect-ratio: 16/10; display: block; background-size: cover; background-position: center; }
.slot .cap {
display: flex; align-items: center; gap: 6px; padding: 6px 9px;
border-top: 1px solid var(--app-line); background: var(--app-white); font-size: 11.5px; color: var(--app-muted);
}
.slot .cap .spacer { flex: 1; }
.slot .cap .msym { font-size: 14px; }
.sky { background: linear-gradient(#9fc9e6 0%, #cfe2ef 55%, #b9a894 56%, #8d7f70 100%); }
.brandmark { display: grid; place-items: center; background: #fff; }
.brandmark svg { width: 54%; }
/* ─── Sections grid ─── */
.toolbar { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 12px; }
.search {
flex: 1; min-width: 160px; border: 1px solid var(--app-line); border-radius: var(--r-input);
padding: 7px 10px; display: flex; align-items: center; gap: 7px; color: var(--app-muted); font-size: 13px; background: var(--app-white);
}
.search .msym { font-size: 16px; }
.fchip { font-size: 11.5px; padding: 5px 10px; border-radius: 999px; border: 1px solid var(--app-line); color: var(--app-muted); background: var(--app-white); }
.fchip.on { background: var(--app-primary); border-color: var(--app-primary); color: #fff; font-weight: 500; }
.sgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(146px, 1fr)); gap: 10px; }
border: 1px solid var(--app-line); border-radius: var(--r-card); background: var(--app-white);
overflow: hidden; position: relative;
}
.scard .top { aspect-ratio: 16/9; position: relative; background: var(--app-ground); background-size: cover; background-position: center; }
.scard .ord {
position: absolute; top: 6px; left: 6px; min-width: 19px; height: 19px; padding: 0 5px;
border-radius: var(--r-input); background: rgba(38,72,99,.92); color: #fff;
font-size: 11px; font-weight: 600; display: grid; place-items: center; font-variant-numeric: tabular-nums;
}
.scard .grip { position: absolute; top: 5px; right: 5px; color: #fff; opacity: .85; }
.scard .grip .msym { font-size: 17px; text-shadow: 0 1px 3px rgba(0,0,0,.5); }
.scard .noimg { position: absolute; inset: 0; display: grid; place-items: center; color: #b6bfcb; }
.scard .noimg .msym { font-size: 26px; }
.scard .bot { padding: 8px 9px 9px; }
.scard .nm { font-size: 12.5px; font-weight: 500; line-height: 1.25; }
.scard .ty { font-size: 10.5px; color: var(--app-muted); display: flex; align-items: center; gap: 4px; margin-top: 3px; }
.scard .ty .msym { font-size: 12px; }
.scard.add {
border-style: dashed; border-color: var(--app-second); display: grid; place-items: center;
color: var(--app-primary); gap: 3px; text-align: center; padding: 14px 8px; min-height: 118px;
}
.scard.add .msym { font-size: 22px; }
.scard.add .l { font-size: 11.5px; font-weight: 500; }
.scard.hidden-s .top, .scard.hidden-s .bot { opacity: .5; }
.scard .flag { position: absolute; bottom: 44px; left: 6px; font-size: 10px; background: #fff; border: 1px solid var(--app-line); border-radius: 5px; padding: 1px 5px; color: var(--app-muted); }
/* ─── Master / detail collection editor ─── */
.md { display: grid; grid-template-columns: 208px minmax(0,1fr); gap: 0; border: 1px solid var(--app-line); border-radius: var(--r-card); overflow: hidden; }
@media (max-width: 780px) { .md { grid-template-columns: 1fr; } }
.md .list { background: var(--app-ground); border-right: 1px solid var(--app-line); padding: 8px; display: grid; gap: 5px; align-content: start; }
.md .li { display: flex; align-items: center; gap: 8px; padding: 7px 8px; border-radius: var(--r-input); font-size: 12.5px; background: transparent; }
.md .li .msym.g { font-size: 15px; color: #b3bcc8; }
.md .li .n { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.md .li .idx { font-size: 10.5px; color: var(--app-muted); font-variant-numeric: tabular-nums; }
.md .li.sel { background: var(--app-white); border: 1px solid var(--app-primary); font-weight: 500; }
.md .li.new { color: var(--app-primary); font-weight: 500; border: 1px dashed var(--app-second); justify-content: center; }
.md .detail { padding: 14px 16px 16px; background: var(--app-white); }
.md .detail .dt { font-size: 11px; font-weight: 600; letter-spacing: .09em; text-transform: uppercase; color: var(--app-muted); margin-bottom: 11px; display: flex; align-items: center; gap: 8px; }
.md .detail .dt .spacer { flex: 1; }
.answers { display: grid; gap: 6px; }
.ans { display: flex; align-items: center; gap: 9px; border: 1px solid var(--app-line); border-radius: var(--r-input); padding: 7px 10px; font-size: 13px; }
.ans .dot { width: 15px; height: 15px; border-radius: 50%; border: 1.5px solid var(--app-second); flex: none; }
.ans.good { border-color: rgba(139,195,74,.6); background: rgba(139,195,74,.09); }
.ans.good .dot { border-color: var(--app-success); background: var(--app-success); }
.ans .t { flex: 1; }
.ans .msym { font-size: 15px; color: var(--app-muted); }
/* ─── Diffusion pane ─── */
.qrwrap { display: flex; gap: 13px; align-items: flex-start; }
.qrbox { width: 88px; height: 88px; flex: none; border: 1px solid var(--app-line); border-radius: var(--r-card); padding: 6px; background: #fff; }
.qrbox svg { display: block; width: 100%; height: 100%; }
.qrside { min-width: 0; display: grid; gap: 7px; align-content: start; }
.urlbox {
font-family: "IBM Plex Mono", monospace; font-size: 10px; color: var(--app-muted);
background: var(--app-ground); border: 1px solid var(--app-line); border-radius: var(--r-input);
padding: 6px 8px; line-height: 1.45; word-break: break-all;
}
.qrside .row { display: flex; gap: 6px; flex-wrap: wrap; }
.qrside .row .ghost-btn, .qrside .row .solid-btn { font-size: 11.5px; padding: 6px 10px; }
/* ─── Coquille de dialogue (déjà fixée par confirmation_dialog.dart) ─── */
.stage {
background: var(--app-ground); border: 1px solid var(--rule); border-radius: 12px;
padding: 26px 22px; display: grid; place-items: center;
font-family: Roboto, "Segoe UI", sans-serif; color: var(--app-ink); box-shadow: var(--shadow);
}
.dlg {
background: var(--app-white); border: 1px solid var(--app-line); border-radius: var(--r-shell);
box-shadow: 0 2px 6px rgba(20,32,43,.08), 0 18px 42px rgba(20,32,43,.14);
overflow: hidden; width: 100%;
}
.dlg.s { max-width: 420px; } .dlg.m { max-width: 512px; } .dlg.l { max-width: 780px; } .dlg.xl { max-width: 100%; }
/* Contenu encastré (ResourcesScreen) — représenté en schéma, pas redessiné */
.embed { display: grid; grid-template-columns: 152px minmax(0,1fr); min-height: 216px; }
.embed .facets { border-right: 1px solid var(--app-line-soft); padding: 10px; display: grid; gap: 12px; align-content: start; }
.embed .facets .fg { display: grid; gap: 5px; }
.embed .facets .ft { font-size: 10px; letter-spacing: .069em; color: var(--app-muted); }
.embed .facets .fi { height: 8px; border-radius: 3px; background: var(--app-surface3); }
.embed .facets .fi.w1 { width: 58%; } .embed .facets .fi.w2 { width: 78%; } .embed .facets .fi.w3 { width: 44%; }
.embed .main { padding: 10px; display: grid; gap: 9px; align-content: start; min-width: 0; }
.embed .mgrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(72px, 1fr)); gap: 7px; }
.embed .th { aspect-ratio: 1/.78; border-radius: var(--r-input); background: var(--app-surface3); display: grid; place-items: center; color: #aeb8c4; }
.embed .th .msym { font-size: 16px; }
.embed .th.pick { box-shadow: inset 0 0 0 2px var(--app-primary); }
.embed .veil {
grid-column: 1 / -1; margin-top: 2px; font-size: 11px; color: var(--app-muted);
display: flex; align-items: center; gap: 6px;
}
.embed .veil .msym { font-size: 13px; }
.dlg-head {
display: flex; align-items: center; gap: 10px;
padding: 14px 16px 12px; border-bottom: 1px solid var(--app-line-soft);
}
.dlg-head .h { font-size: 14.5px; font-weight: 700; color: var(--app-ink); }
.dlg-head .sub { font-size: 12px; color: var(--app-muted); }
.dlg-head .spacer { flex: 1; }
.xbtn {
width: 28px; height: 28px; border-radius: var(--r-input); border: 1px solid transparent;
color: var(--app-muted); display: grid; place-items: center; flex: none;
}
.xbtn .msym { font-size: 17px; }
.dlg-body { padding: 16px; display: grid; gap: 14px; }
.dlg-foot {
display: flex; align-items: center; gap: 8px; padding: 12px;
background: var(--app-surface2); border-top: 1px solid var(--app-line-soft);
}
.dlg-foot .spacer { flex: 1; }
.sizerow { display: grid; gap: 10px; }
.sizebar { display: flex; align-items: center; gap: 12px; font-size: 14.5px; color: var(--ink-soft); }
.sizebar .b { height: 24px; border-radius: 5px; background: var(--accent-soft); border: 1px solid var(--accent); flex: none; }
.sizebar .n { font-family: Archivo, sans-serif; font-weight: 600; font-size: 12px; color: var(--accent); width: 74px; flex: none; }
/* ─── Modal de traduction ─── */
.tr { display: grid; grid-template-columns: 138px minmax(0,1fr); border: 1px solid var(--app-line); border-radius: var(--r-card); overflow: hidden; }
@media (max-width: 760px) { .tr { grid-template-columns: 1fr; } }
.tr .rail { background: var(--app-surface2); border-right: 1px solid var(--app-line-soft); padding: 8px; display: grid; gap: 4px; align-content: start; }
.tr .rail .r {
display: flex; align-items: center; gap: 8px; padding: 7px 9px; border-radius: var(--r-input);
font-size: 13px; border: 1px solid transparent;
}
.tr .rail .r.sel { background: var(--app-white); border-color: var(--app-primary); font-weight: 600; }
.tr .rail .r .code { font-weight: 600; font-size: 12px; width: 20px; }
.tr .rail .r .spacer { flex: 1; }
.tr .rail .r .dt { width: 7px; height: 7px; border-radius: 50%; flex: none; }
.tr .rail .r .dt.full { background: #2e8b62; }
.tr .rail .r .dt.empty { box-shadow: inset 0 0 0 1.5px #b9c3ce; }
.tr .rail .r .dt.stale { background: var(--app-warning); }
.tr .pane2 { background: var(--app-white); min-width: 0; }
.tr .ref {
display: flex; gap: 9px; align-items: baseline; padding: 9px 12px;
background: var(--app-surface2); border-bottom: 1px solid var(--app-line-soft); font-size: 12.5px;
}
.tr .ref .rl { font-weight: 600; font-size: 11px; color: var(--app-muted); flex: none; }
.tr .ref .rv { color: var(--app-ink2); min-width: 0; }
.qbar { display: flex; align-items: center; gap: 2px; padding: 6px 8px; border-bottom: 1px solid var(--app-line-soft); flex-wrap: wrap; }
.qbar .qb { width: 25px; height: 25px; border-radius: 4px; display: grid; place-items: center; color: var(--app-ink2); }
.qbar .qb.on { background: var(--app-surface3); color: var(--app-primary); }
.qbar .qb .msym { font-size: 15px; }
.qbar .sep { width: 1px; height: 17px; background: var(--app-line-soft); margin: 0 4px; }
.qedit { padding: 11px 12px; min-height: 92px; font-size: 13.5px; line-height: 1.5; color: var(--app-ink); }
.qedit .caret { display: inline-block; width: 1px; height: 15px; background: var(--app-primary); vertical-align: -3px; }
.qfoot { display: flex; align-items: center; gap: 8px; padding: 7px 12px; border-top: 1px solid var(--app-line-soft); font-size: 11.5px; color: var(--app-muted); }
.qfoot .spacer { flex: 1; }
/* ─── Choix du type de section ─── */
.fam { font-size: 11.5px; letter-spacing: .069em; color: var(--app-muted); margin: 2px 0 7px; }
.typegrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(122px, 1fr)); gap: 7px; }
.tcard {
border: 1px solid var(--app-line); border-radius: var(--r-card); padding: 10px;
display: grid; gap: 3px; background: var(--app-white); min-width: 0; align-content: start;
}
.tcard .msym { font-size: 19px; color: var(--app-primary); }
.tcard .tn { font-size: 12.5px; font-weight: 600; color: var(--app-ink); }
.tcard .td { font-size: 11px; color: var(--app-muted); line-height: 1.35; }
.tcard.sel { border-color: var(--app-primary); box-shadow: inset 0 0 0 1px var(--app-primary); background: var(--app-surface2); }
/* ─── Before / after pair ─── */
.ba { display: grid; grid-template-columns: 1fr 1fr; gap: 18px; }
@media (max-width: 820px) { .ba { grid-template-columns: 1fr; } }
.ba .lab { display: flex; align-items: center; gap: 8px; font-family: Archivo, sans-serif; font-size: 11px; font-weight: 600; letter-spacing: .12em; text-transform: uppercase; margin-bottom: 9px; }
.ba .lab .pin { width: 8px; height: 8px; border-radius: 50%; }
.ba .before .pin { background: var(--warn); } .ba .before .lab { color: var(--warn); }
.ba .after .pin { background: var(--ok); } .ba .after .lab { color: var(--ok); }
.demo { background: var(--app-white); border: 1px solid var(--rule); border-radius: 10px; padding: 16px; font-family: Roboto, sans-serif; color: var(--app-ink); }
.old-row { display: flex; align-items: center; gap: 8px; }
.old-row .ol { font-size: 14px; white-space: nowrap; }
.old-pill { flex: 1; background: var(--app-second); border-radius: 29px; padding: 14px 20px 8px; min-height: 62px; position: relative; font-size: 14px; }
.old-pill .cnt { position: absolute; right: 16px; bottom: 4px; font-size: 10.5px; color: #5d6a7a; }
.old-center { text-align: center; margin-top: 14px; }
.old-center .ol { font-size: 14px; margin-right: 8px; }
.old-blob { display: inline-flex; flex-direction: column; justify-content: center; background: var(--app-second); border-radius: 50px; width: 190px; height: 52px; padding: 0 14px; text-align: left; color: #fff; }
.old-blob b { font-size: 12.5px; font-weight: 600; }
.old-blob small { font-size: 10px; opacity: .85; }
.ba figcaption { margin-top: 10px; }
/* ─── Archetype table ─── */
.arch { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: 14px; margin-bottom: 26px; }
@media (max-width: 900px) { .arch { grid-template-columns: 1fr; } }
.acard { background: var(--surface); border: 1px solid var(--rule); border-radius: 11px; padding: 18px; box-shadow: var(--shadow); }
.acard .k { font-family: "IBM Plex Mono", monospace; font-size: 12px; color: var(--accent); }
.acard h3 { margin: 6px 0 8px; }
.acard p { font-size: 15px; color: var(--ink-soft); margin: 0 0 12px; }
.acard .who { display: flex; flex-wrap: wrap; gap: 5px; }
.acard .who span { font-family: Archivo, sans-serif; font-size: 11px; font-weight: 600; padding: 3px 8px; border-radius: 5px; background: var(--accent-soft); color: var(--accent); }
table.map { width: 100%; border-collapse: collapse; font-size: 15px; }
table.map th, table.map td { text-align: left; padding: 9px 12px; border-bottom: 1px solid var(--rule); vertical-align: top; }
table.map th { font-family: Archivo, sans-serif; font-size: 11px; font-weight: 600; letter-spacing: .12em; text-transform: uppercase; color: var(--ink-faint); }
table.map td:first-child { font-family: Archivo, sans-serif; font-weight: 600; font-size: 14px; white-space: nowrap; }
table.map td.g { font-family: "IBM Plex Mono", monospace; font-size: 12.5px; color: var(--accent); white-space: nowrap; }
table.map td.n { color: var(--ink-soft); font-size: 14.5px; }
.tbl-scroll { overflow-x: auto; border: 1px solid var(--rule); border-radius: 11px; background: var(--surface); padding: 4px 6px; }
/* ─── Files list ─── */
.files { display: grid; gap: 10px; }
.file { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1.1fr); gap: 18px; background: var(--surface); border: 1px solid var(--rule); border-radius: 10px; padding: 13px 16px; align-items: baseline; }
@media (max-width: 820px) { .file { grid-template-columns: 1fr; gap: 5px; } }
.file .p { font-family: "IBM Plex Mono", monospace; font-size: 12.5px; color: var(--ink); word-break: break-all; }
.file .d { font-size: 14.5px; color: var(--ink-soft); }
.file .badge { font-family: Archivo, sans-serif; font-size: 10px; font-weight: 700; letter-spacing: .08em; text-transform: uppercase; padding: 2px 7px; border-radius: 4px; margin-right: 7px; vertical-align: 2px; }
.badge.core { background: var(--warn-soft); color: var(--warn); }
.badge.screen { background: var(--accent-soft); color: var(--accent); }
.badge.later { background: var(--bg-deep); color: var(--ink-faint); }
.callout { border-left: 3px solid var(--accent); background: var(--surface); border-radius: 0 10px 10px 0; padding: 16px 20px; margin: 0 0 24px; }
.callout p:last-child { margin-bottom: 0; }
.callout .eyebrow { display: block; margin-bottom: 6px; }
footer.end { border-top: 1px solid var(--rule); padding-top: 26px; color: var(--ink-faint); font-size: 14px; }
@media (prefers-reduced-motion: reduce) { * { animation: none !important; transition: none !important; } }
</style>
<div class="wrap">
<header class="masthead">
<div class="eyebrow">manager-app · proposition d'interface</div>
<h1>Refonte Configuration &amp; Sections</h1>
<p class="lede">Le blanc de l'écran actuel ne vient pas de la mise en page de <code>configuration_detail_screen.dart</code> : il vient de quatre composants partagés qui imposent des pastilles pleine largeur, des labels centrés et des listes horizontales à hauteur fixe. On les corrige une fois, et les 13 types de section en héritent.</p>
<div class="meta-row">
<span class="tag">Écran Configuration</span>
<span class="tag">Écran Section</span>
<span class="tag">3 gabarits pour 13 types</span>
<span class="tag">2 dialogues</span>
<span class="tag">Tokens de constants.dart</span>
</div>
<div class="callout" style="margin-top: 32px">
<span class="eyebrow">Rien à inventer : le système existe</span>
<p><code>constants.dart</code> porte déjà un système complet — <code>kSurface2</code>, <code>kInk2</code>, <code>kLine</code>, <code>kSpace1</code><code>kSpace8</code>, <code>kRadiusInput</code> 5 / <code>kRadiusCard</code> 8 / <code>kRadiusShell</code> 10 / <code>kRadiusPill</code>, <code>kTitleCard</code>, <code>kLabelField</code>, <code>kTextHint</code>. <code>confirmation_dialog.dart</code> et <code>resource_picker.dart</code> l'appliquent déjà.</p>
<p>Ces deux écrans sont simplement <strong>antérieurs</strong> à ce système. Il n'y a donc pas de direction visuelle à trancher ici : toutes les valeurs des maquettes ci-dessous sont reprises de <code>constants.dart</code>, et là où une première version de cette page avait inventé un rayon ou une capitale, c'est le token qui a gagné.</p>
</div>
</header>
<!-- ══════════ 1. DIAGNOSTIC ══════════ -->
<section class="block">
<div class="section-head">
<div class="num">01</div>
<div>
<h2>Ce qui fabrique le blanc</h2>
<p class="sub">Quatre causes, toutes dans des composants réutilisés par tous les écrans d'édition.</p>
</div>
</div>
<div class="diag">
<div class="diag-row head"><div>Cause</div><div>Effet observé</div><div>Correction</div></div>
<div class="diag-row">
<div class="what">Le champ texte est une pastille pleine largeur
<span class="mono">text_field_container.dart — width: double.infinity, radius 29, margin 10px</span></div>
<div class="why">« Config test » (11 caractères) occupe une barre grise de 1 400 px de large et 60 px de haut. Le compteur <code>11/50</code> se retrouve à un mètre du texte.</div>
<div class="fix">Champ à largeur intrinsèque (rang de 12 colonnes), radius 8, bordure 1 px au lieu d'un fond plein.</div>
</div>
<div class="diag-row">
<div class="what">Le label est à gauche, dans un <code>Row</code>
<span class="mono">string_input_container.dart · resource_input_container.dart</span></div>
<div class="why">Chaque ligne a une colonne de label d'une largeur différente : rien ne s'aligne verticalement, et le champ démarre à un endroit imprévisible.</div>
<div class="fix">Label au-dessus, capitales 10,5 px lettrées. Une grille, une seule gouttière.</div>
</div>
<div class="diag-row">
<div class="what">Deux composants se centrent eux-mêmes
<span class="mono">mainAxisAlignment: MainAxisAlignment.center</span></div>
<div class="why">« Titre affiché », « Langues » et « Hors ligne » flottent au milieu de la carte, désolidarisés des champs au-dessus.</div>
<div class="fix">Aucun composant ne décide de son alignement : c'est le parent qui place.</div>
</div>
<div class="diag-row">
<div class="what">Les sections sont une liste horizontale à hauteur fixe
<span class="mono">section_reorderList.dart — height: size.height * 0.3, cartes 150×150</span></div>
<div class="why">30 % de la hauteur d'écran réservés quoi qu'il arrive, cartes carrées trop grandes, et les sections au-delà de la 8<sup>e</sup> sont hors champ à droite sans indice visuel.</div>
<div class="fix">Grille responsive <code>auto-fill minmax(146px, 1fr)</code>, hauteur pilotée par le contenu, réordonnancement conservé.</div>
</div>
</div>
</section>
<!-- ══════════ 2. LE CHAMP ══════════ -->
<section class="block">
<div class="section-head">
<div class="num">02</div>
<div>
<h2>Le champ, avant et après</h2>
<p class="sub">C'est la brique qui décide de la densité de tous les écrans. Elle se corrige dans trois fichiers.</p>
</div>
</div>
<div class="ba">
<figure class="mock before">
<div class="lab"><span class="pin"></span>Aujourd'hui</div>
<div class="demo">
<div class="old-row">
<div class="ol">Identifiant :</div>
<div class="old-pill">Config test <span class="cnt">11/50</span></div>
</div>
<div class="old-center">
<span class="ol">Titre affiché:</span>
<span class="old-blob"><b>Config Test</b><small>3 / 3 langues traduites</small></span>
</div>
</div>
<figcaption>Deux champs, deux alignements, 210 px de hauteur. Le libellé « Identifiant » est répété par le titre de carte juste au-dessus.</figcaption>
</figure>
<figure class="mock after">
<div class="lab"><span class="pin"></span>Proposé</div>
<div class="demo">
<div class="fgrid">
<div class="f" style="grid-column: span 5">
<label>Identifiant <em>*</em></label>
<div class="input"><span class="val">Config test</span><span class="count">11/50</span></div>
<div class="hint">Nom interne, jamais affiché aux visiteurs.</div>
</div>
<div class="f" style="grid-column: span 7">
<label>Titre affiché</label>
<div class="trad">
<span class="v">Config Test</span>
<span class="st">FR NL EN</span>
<span class="msym">edit</span>
</div>
<div class="hint">3 / 3 langues traduites.</div>
</div>
</div>
</div>
<figcaption>Deux champs sur une ligne, 92 px de hauteur, une seule ligne de base. Le badge de traduction porte l'information à la place d'un sous-titre gris tronqué.</figcaption>
</figure>
</div>
</section>
<!-- ══════════ 3. ÉCRAN CONFIGURATION ══════════ -->
<section class="block">
<div class="section-head">
<div class="num">03</div>
<div>
<h2>Édition d'une configuration</h2>
<p class="sub">Trois cartes empilées deviennent une colonne de travail et un rail latéral. Les actions quittent la barre flottante du bas pour rejoindre l'en-tête.</p>
</div>
</div>
<div class="callout">
<span class="eyebrow">Pas de bento à ce niveau</span>
<p>Le placement bento ne concerne pas cet écran : <code>gridColSpan</code> / <code>gridRowSpan</code> vivent sur <code>AppConfigurationLink</code>, donc sur le lien entre une <em>instance d'application</em> et une configuration. Les tuiles de la grille bento sont les <strong>configurations</strong> de l'accueil, réglées dans <code>app_configuration_link_screen.dart</code> via <code>CardShapeSelector</code>.</p>
<p>À l'intérieur d'une configuration, les sections n'ont ni span ni grille : elles ne portent qu'un <code>order</code>, et le visiteur les voit en liste verticale de cartes pleine largeur (<code>SectionList.tsx</code>, <code>SectionCard</code> à 160 px, accent alterné). La grille de la maquette ci-dessous est donc un outil de réordonnancement, pas une prévisualisation.</p>
</div>
<div class="callout">
<span class="eyebrow">Et pas d'aperçu visiteur non plus</span>
<p>Un aperçu ne se justifie que là où la décision d'édition est invisible dans le formulaire — les spans bento, le HTML traduit. Ici l'ordre se lit dans la grille, l'image est en vignette, les langues sont des pastilles : l'aperçu ne redessinerait, en plus petit, que ce qui est déjà à l'écran. En échange il faudrait maintenir un <strong>troisième moteur de rendu</strong> en Dart, alors que le bento est déjà dupliqué entre <code>myinfomate_layout</code> et le CSS Grid de <code>visitapp-web</code> — et un aperçu qui dérive du vrai rendu est pire que pas d'aperçu. Le rail droit porte donc un bouton d'ouverture et le QR&nbsp;: le rendu réel, sans rien à synchroniser.</p>
</div>
<figure class="mock">
<div class="frame">
<div class="bar"><i></i><i></i><i></i><span>manager.myinfomate.be · Configurations / Config test</span></div>
<div class="app-head">
<span class="msym back">arrow_back</span>
<div class="titleblock">
<div class="t">Config test <span class="msym">edit</span></div>
<div class="s">Créée le 22/12/2023 · 8 sections · 3 langues</div>
</div>
<div class="spacer"></div>
<span class="icon-btn" title="Exporter"><span class="msym">download</span></span>
<span class="danger-btn"><span class="msym">delete</span>Supprimer</span>
<span class="ghost-btn">Annuler</span>
<span class="solid-btn"><span class="msym">check</span>Enregistrer</span>
</div>
<div class="app-body">
<div class="cols">
<div style="display: grid; gap: 14px;">
<div class="pane">
<div class="pane-t"><span class="msym">tune</span>Réglages</div>
<div class="fgrid">
<div class="f" style="grid-column: span 5">
<label>Identifiant <em>*</em></label>
<div class="input"><span class="val">Config test</span><span class="count">11/50</span></div>
</div>
<div class="f" style="grid-column: span 7">
<label>Titre affiché</label>
<div class="trad"><span class="v">Config Test</span><span class="st">3/3</span><span class="msym">edit</span></div>
</div>
<div class="f" style="grid-column: span 5">
<label>Langues</label>
<div class="input select">
<span class="langs"><span class="lang">FR</span><span class="lang">NL</span><span class="lang">EN</span><span class="lang off">DE</span></span>
<span class="msym">expand_more</span>
</div>
</div>
<div class="f" style="grid-column: span 7; justify-content: flex-end">
<div class="sw">
<span class="track off"></span>
<span class="lbl">Mode hors ligne<small>Le contenu est téléchargé au premier lancement.</small></span>
</div>
</div>
</div>
</div>
<div class="pane">
<div class="pane-t"><span class="msym">dashboard</span>Sections<span class="spacer"></span><span class="ghost-btn"><span class="msym">add</span>Ajouter</span></div>
<div class="toolbar">
<span class="search"><span class="msym">search</span>Rechercher une section…</span>
<span class="fchip on">Tous · 8</span>
<span class="fchip">Contenu</span>
<span class="fchip">Carte</span>
<span class="fchip">Interactif</span>
</div>
<div class="sgrid">
<div class="scard">
<div class="top sky"><span class="ord">1</span><span class="grip"><span class="msym">drag_indicator</span></span></div>
<div class="bot"><div class="nm">Vue panoramique</div><div class="ty"><span class="msym">collections</span>Slider</div></div>
</div>
<div class="scard">
<div class="top" style="background: linear-gradient(135deg,#3d6b52,#8fb98c)"><span class="ord">2</span><span class="grip"><span class="msym">drag_indicator</span></span></div>
<div class="bot"><div class="nm">Plan du parc</div><div class="ty"><span class="msym">location_on</span>Map</div></div>
</div>
<div class="scard">
<div class="top"><span class="ord">3</span><span class="noimg"><span class="msym">question_answer</span></span><span class="grip" style="color:#93a0ad"><span class="msym">drag_indicator</span></span></div>
<div class="bot"><div class="nm">Quiz des collections</div><div class="ty"><span class="msym">question_answer</span>Quiz</div></div>
</div>
<div class="scard">
<div class="top" style="background: linear-gradient(135deg,#7a5a86,#c9a6cf)"><span class="ord">4</span><span class="grip"><span class="msym">drag_indicator</span></span></div>
<div class="bot"><div class="nm">Parcours famille</div><div class="ty"><span class="msym">route</span>Parcours</div></div>
</div>
<div class="scard">
<div class="top"><span class="ord">5</span><span class="noimg"><span class="msym">calendar_month</span></span><span class="grip" style="color:#93a0ad"><span class="msym">drag_indicator</span></span></div>
<div class="bot"><div class="nm">Agenda du mois</div><div class="ty"><span class="msym">calendar_month</span>Agenda</div></div>
</div>
<div class="scard hidden-s">
<div class="top"><span class="ord">6</span><span class="noimg"><span class="msym">picture_as_pdf</span></span><span class="flag">Masquée</span><span class="grip" style="color:#93a0ad"><span class="msym">drag_indicator</span></span></div>
<div class="bot"><div class="nm">Brochure 2026</div><div class="ty"><span class="msym">picture_as_pdf</span>PDF</div></div>
</div>
<div class="scard">
<div class="top"><span class="ord">7</span><span class="noimg"><span class="msym">sunny</span></span><span class="grip" style="color:#93a0ad"><span class="msym">drag_indicator</span></span></div>
<div class="bot"><div class="nm">Météo Liège</div><div class="ty"><span class="msym">sunny</span>Météo</div></div>
</div>
<div class="scard add"><span class="msym">add</span><span class="l">Ajouter<br>une section</span></div>
</div>
</div>
</div>
<div style="display: grid; gap: 14px;">
<div class="pane">
<div class="pane-t"><span class="msym">image</span>Habillage</div>
<div style="display: grid; gap: 12px;">
<div class="f">
<label>Image principale</label>
<div class="slot filled">
<span class="thumb sky"></span>
<span class="cap"><span class="msym">check_circle</span>telepherique.jpg<span class="spacer"></span><span class="msym">more_horiz</span></span>
</div>
</div>
<div class="f">
<label>Loader</label>
<div class="slot filled">
<span class="thumb brandmark">
<svg viewBox="0 0 100 64" aria-hidden="true">
<defs><linearGradient id="g" x1="0" y1="1" x2="1" y2="0">
<stop offset="0" stop-color="#e879c8"/><stop offset="1" stop-color="#6d28d9"/>
</linearGradient></defs>
<path d="M8 58 C30 58 34 12 50 12 C66 12 70 58 92 58" fill="none" stroke="url(#g)" stroke-width="13" stroke-linecap="round"/>
<circle cx="50" cy="8" r="7" fill="#8b3ad4"/>
</svg>
</span>
<span class="cap"><span class="msym">check_circle</span>unov-loader.svg<span class="spacer"></span><span class="msym">more_horiz</span></span>
</div>
</div>
</div>
</div>
<div class="pane">
<div class="pane-t"><span class="msym">share</span>Diffusion</div>
<div class="qrwrap">
<div class="qrbox" data-qr="7"></div>
<div class="qrside">
<div class="urlbox">app.myinfomate.be/musee-liege/<wbr>6d41f0…</div>
<div class="row">
<span class="solid-btn"><span class="msym">open_in_new</span>Ouvrir</span>
<span class="ghost-btn"><span class="msym">content_copy</span>Lien</span>
</div>
</div>
</div>
<div class="hint" style="margin-top: 10px">Le lien ouvre le rendu réel : rien à maquetter, rien qui puisse dériver.</div>
</div>
</div>
</div>
</div>
</div>
</figure>
<figcaption>
<b>Ce qui change</b> — L'en-tête porte le contexte (8 sections, 3 langues) et les actions, ce qui supprime la barre flottante du bas et rend 60 px au contenu. La colonne principale contient les deux choses qu'on vient vraiment modifier ; le rail droit regroupe les médias et la diffusion. La carte « Identifiant » devient « Réglages » : le titre d'une carte ne répète plus le nom du champ qu'elle contient.
</figcaption>
</section>
<!-- ══════════ 4. ÉCRAN SECTION ══════════ -->
<section class="block">
<div class="section-head">
<div class="num">04</div>
<div>
<h2>Édition d'une section</h2>
<p class="sub">Même squelette, même grille, même rail. Seul le contenu de la carte « Configuration&nbsp;<span style="font-family:'IBM Plex Mono',monospace;font-size:.85em">&lt;type&gt;</span> » change d'un type à l'autre.</p>
</div>
</div>
<figure class="mock">
<div class="frame">
<div class="bar"><i></i><i></i><i></i><span>manager.myinfomate.be · Config test / Quiz des collections</span></div>
<div class="app-head">
<span class="msym back">arrow_back</span>
<span class="type-chip"><span class="msym">question_answer</span>Quiz</span>
<div class="titleblock">
<div class="t">Quiz des collections <span class="msym">edit</span></div>
<div class="s">Section 3 sur 8 · créée le 14/03/2026</div>
</div>
<div class="spacer"></div>
<span class="icon-btn" title="Exporter en PDF"><span class="msym">picture_as_pdf</span></span>
<span class="danger-btn"><span class="msym">delete</span>Supprimer</span>
<span class="ghost-btn">Annuler</span>
<span class="solid-btn"><span class="msym">check</span>Enregistrer</span>
</div>
<div class="app-body">
<div class="cols">
<div style="display: grid; gap: 14px;">
<div class="pane">
<div class="pane-t"><span class="msym">tune</span>Réglages</div>
<div class="fgrid">
<div class="f" style="grid-column: span 5">
<label>Identifiant <em>*</em></label>
<div class="input"><span class="val">Quiz des collections</span><span class="count">20/50</span></div>
</div>
<div class="f" style="grid-column: span 7">
<label>Titre affiché</label>
<div class="trad"><span class="v">Testez vos connaissances</span><span class="st part">2/3</span><span class="msym">edit</span></div>
</div>
<div class="f" style="grid-column: span 12">
<div class="sw">
<span class="track off"></span>
<span class="lbl">Balise iBeacon<small>Déclenche la section à l'approche du visiteur.</small></span>
</div>
</div>
</div>
</div>
<div class="pane">
<div class="pane-t"><span class="msym">question_answer</span>Questions<span class="spacer"></span><span class="hint">6 questions · glisser pour réordonner</span></div>
<div class="md">
<div class="list">
<div class="li"><span class="msym g">drag_indicator</span><span class="idx">1</span><span class="n">Quel peintre…</span></div>
<div class="li sel"><span class="msym g">drag_indicator</span><span class="idx">2</span><span class="n">En quelle année…</span></div>
<div class="li"><span class="msym g">drag_indicator</span><span class="idx">3</span><span class="n">Que représente…</span></div>
<div class="li"><span class="msym g">drag_indicator</span><span class="idx">4</span><span class="n">Combien de salles…</span></div>
<div class="li"><span class="msym g">drag_indicator</span><span class="idx">5</span><span class="n">Qui a financé…</span></div>
<div class="li"><span class="msym g">drag_indicator</span><span class="idx">6</span><span class="n">Où se trouve…</span></div>
<div class="li new"><span class="msym g" style="color:currentColor">add</span>Nouvelle question</div>
</div>
<div class="detail">
<div class="dt">Question 2<span class="spacer"></span><span class="hint">Modifiée à l'instant</span></div>
<div class="fgrid" style="margin-bottom: 13px">
<div class="f" style="grid-column: span 8">
<label>Intitulé</label>
<div class="trad"><span class="v">En quelle année le musée a-t-il ouvert&nbsp;?</span><span class="st">3/3</span><span class="msym">edit</span></div>
</div>
<div class="f" style="grid-column: span 4">
<label>Points</label>
<div class="input"><span class="val">10</span><span class="msym">unfold_more</span></div>
</div>
</div>
<div class="f" style="margin-bottom: 10px">
<label>Réponses — cochez la bonne</label>
<div class="answers">
<div class="ans"><span class="dot"></span><span class="t">1897</span><span class="msym">drag_indicator</span></div>
<div class="ans good"><span class="dot"></span><span class="t">1905</span><span class="msym">drag_indicator</span></div>
<div class="ans"><span class="dot"></span><span class="t">1923</span><span class="msym">drag_indicator</span></div>
<div class="ans"><span class="dot"></span><span class="t">1951</span><span class="msym">drag_indicator</span></div>
</div>
</div>
<span class="ghost-btn"><span class="msym">add</span>Ajouter une réponse</span>
</div>
</div>
</div>
</div>
<div style="display: grid; gap: 14px;">
<div class="pane">
<div class="pane-t"><span class="msym">image</span>Habillage</div>
<div class="f">
<label>Image de la section</label>
<div class="slot">
<span class="ph"><span class="msym">add_photo_alternate</span><span class="cta">Choisir dans la médiathèque</span></span>
</div>
</div>
</div>
<div class="pane">
<div class="pane-t"><span class="msym">share</span>Diffusion</div>
<div class="qrwrap">
<div class="qrbox" data-qr="23"></div>
<div class="qrside">
<div class="urlbox">…/musee-liege/6d41f0/<wbr>sections/a17c9b</div>
<div class="row">
<span class="solid-btn"><span class="msym">open_in_new</span>Ouvrir</span>
<span class="ghost-btn"><span class="msym">download</span>QR</span>
</div>
</div>
</div>
<div class="hint" style="margin-top: 10px">Le QR imprimé pointe cette section. Identifiant&nbsp;: <code style="font-size:10px">a17c9b…</code></div>
</div>
</div>
</div>
</div>
</div>
</figure>
<figcaption>
<b>Ce qui change</b> — Le QR code et l'identifiant quittent leur carte pleine largeur pour le rail droit, sous la même forme que sur l'écran Configuration : ils servent au setup et à l'impression, pas à chaque édition. Les questions, aujourd'hui une liste horizontale de 150×150 avec une modale par question, deviennent une liste verticale et un panneau d'édition en place — le réordonnancement reste du glisser-déposer.
</figcaption>
</section>
<!-- ══════════ 5. GABARITS ══════════ -->
<section class="block">
<div class="section-head">
<div class="num">05</div>
<div>
<h2>Trois gabarits, pas treize maquettes</h2>
<p class="sub">Réponse à ta question : non. Les 13 types se rangent dans trois formes, et c'est la forme qui se dessine.</p>
</div>
</div>
<div class="callout">
<span class="eyebrow">Pourquoi trois suffisent</span>
<p>Les configs par type ne diffèrent que par leurs champs, pas par leur structure : <code>slider_config</code>, <code>menu_config</code>, <code>quizz_config</code>, <code>article_config</code>, <code>map_config</code>, <code>agenda_config</code>, <code>event_config</code> et <code>parcours_config</code> font tous rigoureusement la même chose — une liste réordonnable + un bouton vert flottant + une modale d'édition, chacun avec sa propre hauteur en <code>MediaQuery.size * 0.x</code>. Une seule brique <code>CollectionEditor</code> les remplace tous les huit.</p>
</div>
<div class="arch">
<div class="acard">
<div class="k">gabarit A</div>
<h3>Champs simples</h3>
<p>Un à quatre champs dans la carte de type. Rien à dessiner de plus : la grille de 12 colonnes de l'écran suffit.</p>
<div class="who"><span>Web</span><span>Vidéo</span><span>Météo</span></div>
</div>
<div class="acard">
<div class="k">gabarit B</div>
<h3>Éditeur de collection</h3>
<p>Liste ordonnée d'éléments + édition en place. Maquetté ci-dessus avec le Quiz ; l'élément édité change, la mécanique non.</p>
<div class="who"><span>Slider</span><span>Menu</span><span>Quiz</span><span>Article</span><span>PDF</span><span>Agenda</span><span>Parcours</span></div>
</div>
<div class="acard">
<div class="k">gabarit C</div>
<h3>Éditeur avec canevas</h3>
<p>Gabarit B, plus un canevas de travail à droite : la carte, la grille du puzzle, le plan d'événement. Le canevas prend la place du rail latéral, qui repasse au-dessus du canevas.</p>
<div class="who"><span>Map</span><span>Événement</span><span>Jeu</span></div>
</div>
</div>
<div class="tbl-scroll">
<table class="map">
<thead><tr><th>Type</th><th>Gabarit</th><th>Contenu de la carte de type</th><th>Dette actuelle</th></tr></thead>
<tbody>
<tr><td>Web</td><td class="g">A</td><td class="n">URL</td><td class="n"></td></tr>
<tr><td>Météo</td><td class="g">A</td><td class="n">Ville</td><td class="n">Champ marqué <code>isUrl</code> à tort</td></tr>
<tr><td>Vidéo</td><td class="g">A</td><td class="n">Source (YouTube / Vimeo / ressource) + URL</td><td class="n">Déjà refait, sert de référence</td></tr>
<tr><td>PDF</td><td class="g">B</td><td class="n">Liste de fichiers traduits</td><td class="n">Bloc figé <code>70 × 440 px</code> centré</td></tr>
<tr><td>Slider</td><td class="g">B</td><td class="n">Contenus ordonnés : image, titre, description</td><td class="n">Liste horizontale <code>height: 300</code></td></tr>
<tr><td>Menu</td><td class="g">B</td><td class="n">Sous-sections ordonnées</td><td class="n">Liste horizontale <code>0.35 × hauteur</code></td></tr>
<tr><td>Article</td><td class="g">B</td><td class="n">Contenus + audio + lecture auto</td><td class="n">Liste horizontale <code>height: 250</code></td></tr>
<tr><td>Quiz</td><td class="g">B</td><td class="n">Questions, réponses, points, scores</td><td class="n">Le plus dense : 4 zones à hauteur fixe</td></tr>
<tr><td>Agenda</td><td class="g">B</td><td class="n">Événements datés</td><td class="n">Modale par événement</td></tr>
<tr><td>Parcours</td><td class="g">B</td><td class="n">Étapes, mode de progression</td><td class="n">Éditeur dédié de 785 lignes à raccorder</td></tr>
<tr><td>Map</td><td class="g">C</td><td class="n">Points géo, catégories, images par point</td><td class="n">753 lignes, pas d'aperçu carte à l'édition</td></tr>
<tr><td>Événement</td><td class="g">C</td><td class="n">Blocs de programme, annotations sur plan</td><td class="n">Trois modales imbriquées</td></tr>
<tr><td>Jeu</td><td class="g">C</td><td class="n">Image, messages, lignes × colonnes</td><td class="n"><code>TabBar</code> dans un <code>SizedBox(height: 650)</code></td></tr>
</tbody>
</table>
</div>
</section>
<!-- ══════════ 6. DIALOGUES ══════════ -->
<section class="block">
<div class="section-head">
<div class="num">06</div>
<div>
<h2>Les dialogues</h2>
<p class="sub">29 fichiers ouvrent un dialogue. Il n'en faut pas 29 maquettes : dix disparaissent ou fusionnent, la coquille est déjà tranchée, et deux seulement portent une vraie décision de design.</p>
</div>
</div>
<div class="callout">
<span class="eyebrow">La coquille existe déjà — ne pas en inventer une deuxième</span>
<p><code>confirmation_dialog.dart</code> l'a fixée, et <code>resource_picker.dart</code> la suit : rayon <code>kRadiusShell</code>, bordure <code>kLine</code> d'1 px, pied en bande <code>kSurface2</code> séparé par un filet <code>kLineSoft</code>, un <code>TextButton</code> « Annuler » à 13 px et un <code>FilledButton</code> en pastille <code>kRadiusPill</code> qui passe au rouge quand l'action détruit. Les maquettes ci-dessous ne proposent rien de neuf sur ce plan : elles appliquent cette coquille.</p>
</div>
<div class="sizerow" style="margin-bottom: 30px">
<div class="sizebar"><span class="n">S · 420</span><span class="b" style="width: 130px"></span> Confirmation, renommage — un message et deux boutons.</div>
<div class="sizebar"><span class="n">M · 512</span><span class="b" style="width: 158px"></span> Ajout d'une ressource, création — un formulaire court.</div>
<div class="sizebar"><span class="n">L · 780</span><span class="b" style="width: 242px"></span> Traduction, choix du type — plusieurs zones à comparer.</div>
</div>
<h3 style="margin-bottom: 6px">Le modal de traduction</h3>
<p class="prose" style="font-size: 16px; color: var(--ink-soft)">C'est l'interface la plus utilisée de l'app : elle s'ouvre depuis chaque titre et chaque contenu de chaque type. Aujourd'hui elle empile un éditeur Quill par langue les uns sous les autres, puis pose deux boutons de 370 × 70 px centrés <em>dans le flux du contenu</em> — « Appliquer à toutes les langues » et « Traduire avec l'IA ». Sur trois langues, l'action IA est sous la ligne de flottaison.</p>
<figure class="mock" style="margin-top: 18px">
<div class="stage">
<div class="dlg l">
<div class="dlg-head">
<div>
<div class="h">Titre affiché</div>
<div class="sub">Section « Quiz des collections »</div>
</div>
<div class="spacer"></div>
<span class="type-chip">2 / 3 traduites</span>
<span class="xbtn"><span class="msym">close</span></span>
</div>
<div class="dlg-body">
<div class="tr">
<div class="rail">
<div class="r"><span class="code">FR</span><span class="spacer"></span><span class="dt full"></span></div>
<div class="r sel"><span class="code">NL</span><span class="spacer"></span><span class="dt empty"></span></div>
<div class="r"><span class="code">EN</span><span class="spacer"></span><span class="dt stale"></span></div>
</div>
<div class="pane2">
<div class="ref">
<span class="rl">FR</span>
<span class="rv">Testez vos connaissances</span>
</div>
<div class="qbar">
<span class="qb on"><span class="msym">format_bold</span></span>
<span class="qb"><span class="msym">format_italic</span></span>
<span class="qb"><span class="msym">format_underlined</span></span>
<span class="sep"></span>
<span class="qb"><span class="msym">format_list_bulleted</span></span>
<span class="qb"><span class="msym">format_list_numbered</span></span>
<span class="sep"></span>
<span class="qb"><span class="msym">link</span></span>
<span class="qb"><span class="msym">format_clear</span></span>
</div>
<div class="qedit"><b>Test</b> uw kennis<span class="caret"></span></div>
<div class="qfoot">
<span>18 / 60</span>
<span class="spacer"></span>
<span class="msym" style="font-size: 14px">auto_awesome</span>
<span>Proposé par l'IA — à relire</span>
</div>
</div>
</div>
</div>
<div class="dlg-foot">
<span class="ghost-btn"><span class="msym">content_copy</span>Copier le FR partout</span>
<span class="ghost-btn"><span class="msym">auto_awesome</span>Traduire les 2 manquantes</span>
<span class="hint">42 crédits restants</span>
<span class="spacer"></span>
<span class="ghost-btn">Annuler</span>
<span class="solid-btn">Enregistrer</span>
</div>
</div>
</div>
<div class="callout" style="margin-top: 16px">
<span class="eyebrow">⚠️ Le piège d'implémentation : le cycle de vie des contrôleurs Quill</span>
<p>Aujourd'hui ça marche <em>parce que</em> l'empilement vertical monte toutes les langues en même temps : <code>_controllers</code> est une <code>Map&lt;String, QuillController&gt;</code> construite une seule fois dans <code>initState</code> (<code>translation_input_container.dart:52</code>) et libérée seulement dans <code>dispose</code> (198-202). Aucun éditeur n'est jamais démonté en cours de route.</p>
<p><strong>Le rail de langues casse cette propriété si on l'implémente naïvement.</strong> N'afficher que la langue sélectionnée en reconstruisant l'éditeur à chaque bascule, c'est créer et détruire des <code>QuillController</code> en boucle — exactement le chemin fragile. La règle : <strong>construire la map une fois pour toutes les langues, comme aujourd'hui, et basculer avec un <code>IndexedStack</code> ou un <code>Offstage</code></strong> — jamais en rebâtissant l'éditeur. Et surtout <strong>pas de <code>TabBarView</code></strong> : il construit paresseusement et peut lâcher ses enfants hors écran sans <code>AutomaticKeepAliveClientMixin</code>.</p>
<p>Le code garde la trace d'une tentative dans ce sens : <code>translation_tab.dart</code> existe, en <code>TabBar</code> + <code>TabBarView</code> — mais son corps est un <code>TextFormField</code>, pas du Quill, et son appel est <strong>en commentaire</strong> dans <code>multi_input_modal.dart</code> ligne 46-51. Une version à onglets a été essayée puis abandonnée.</p>
<p><strong>Deux chemins déjà existants libèrent et recréent les contrôleurs</strong>, et ce sont eux qu'il faut retester en priorité après la refonte : la traduction par IA (ligne 142) et « appliquer à toutes les langues » (ligne 183). Ils remplacent l'entrée de la map ; une référence gardée ailleurs deviendrait caduque.</p>
</div>
<figcaption>
<b>Ce qui change</b> — Les deux gros boutons quittent le contenu pour le pied : ce sont des actions, pas du texte. Le rail de gauche donne l'état des trois langues d'un coup d'œil — pleine, vide, ou à relire — au lieu de le faire deviner en scrollant. Et la langue source reste affichée au-dessus de l'éditeur : on traduit <em>depuis</em> une référence, c'est le geste réel. Le quota IA passe à côté du bouton qu'il conditionne, plus sous le formulaire.
</figcaption>
</figure>
<h3 style="margin: 40px 0 6px">Le choix du type de section</h3>
<p class="prose" style="font-size: 16px; color: var(--ink-soft)">C'est la première interface que voit un nouveau client, et c'est aujourd'hui un menu déroulant de 13 entrées dans un <code>SizedBox(height: 250)</code>, avec le champ du nom enfermé dans un <code>height: 100</code>. Un menu déroulant demande de connaître les noms avant de choisir ; une grille les montre.</p>
<figure class="mock" style="margin-top: 18px">
<div class="stage">
<div class="dlg l">
<div class="dlg-head">
<div>
<div class="h">Nouvelle section</div>
<div class="sub">Configuration « Config test »</div>
</div>
<div class="spacer"></div>
<span class="xbtn"><span class="msym">close</span></span>
</div>
<div class="dlg-body">
<div class="f">
<label>Nom de la section <em>*</em></label>
<div class="input"><span class="val">Salle des maquettes</span><span class="count">19/50</span></div>
</div>
<div>
<div class="fam">Contenu</div>
<div class="typegrid">
<div class="tcard"><span class="msym">collections</span><span class="tn">Slider</span><span class="td">Suite d'images légendées</span></div>
<div class="tcard"><span class="msym">article</span><span class="tn">Article</span><span class="td">Texte, images, audio</span></div>
<div class="tcard"><span class="msym">picture_as_pdf</span><span class="tn">PDF</span><span class="td">Documents à consulter</span></div>
<div class="tcard"><span class="msym">ondemand_video</span><span class="tn">Vidéo</span><span class="td">YouTube, Vimeo, fichier</span></div>
<div class="tcard"><span class="msym">web</span><span class="tn">Web</span><span class="td">Page externe intégrée</span></div>
</div>
</div>
<div>
<div class="fam">Lieux et déplacements</div>
<div class="typegrid">
<div class="tcard sel"><span class="msym">location_on</span><span class="tn">Map</span><span class="td">Plan avec points d'intérêt</span></div>
<div class="tcard"><span class="msym">route</span><span class="tn">Parcours</span><span class="td">Étapes guidées, énigmes</span></div>
<div class="tcard"><span class="msym">event</span><span class="tn">Événement</span><span class="td">Programme et plan du site</span></div>
</div>
</div>
<div>
<div class="fam">Interactif</div>
<div class="typegrid">
<div class="tcard"><span class="msym">question_answer</span><span class="tn">Quiz</span><span class="td">Questions et scores</span></div>
<div class="tcard"><span class="msym">sports_esports</span><span class="tn">Jeu</span><span class="td">Puzzle d'image</span></div>
<div class="tcard"><span class="msym">apps</span><span class="tn">Menu</span><span class="td">Regroupe des sous-sections</span></div>
</div>
</div>
<div>
<div class="fam">Informations pratiques</div>
<div class="typegrid">
<div class="tcard"><span class="msym">calendar_month</span><span class="tn">Agenda</span><span class="td">Événements datés</span></div>
<div class="tcard"><span class="msym">sunny</span><span class="tn">Météo</span><span class="td">Prévisions d'une ville</span></div>
</div>
</div>
</div>
<div class="dlg-foot">
<span class="hint">Le type ne pourra plus être changé après la création.</span>
<span class="spacer"></span>
<span class="ghost-btn">Annuler</span>
<span class="solid-btn"><span class="msym">add</span>Créer la section</span>
</div>
</div>
</div>
<figcaption>
<b>Ce qui change</b> — Les 13 types sont visibles, regroupés par famille, avec une ligne qui dit ce qu'ils font : le client choisit sans avoir à connaître le vocabulaire du produit. Les icônes sont celles de <code>getSectionIcon</code>, déjà écrites — la grille ne coûte que la mise en page. Et l'avertissement qui compte (le type est définitif) est là où il sert, à côté du bouton qui valide.
</figcaption>
</figure>
<h3 style="margin: 40px 0 6px">Le reste de l'inventaire</h3>
<div class="tbl-scroll" style="margin-bottom: 24px">
<table class="map">
<thead><tr><th>Dialogue</th><th>Sort</th><th>Pourquoi</th></tr></thead>
<tbody>
<tr><td>8 × <code>showNewOrUpdate*</code></td><td class="g">supprimé</td><td class="n">Question de quiz, image de slider, point géo, catégorie, fichier PDF, événement d'agenda, bloc de programme, annotation de plan — tous absorbés par le panneau d'édition en place du gabarit&nbsp;B.</td></tr>
<tr><td>Traduction</td><td class="g">maquetté</td><td class="n">L'interface la plus utilisée de l'app. Ci-dessus.</td></tr>
<tr><td>Nouvelle section</td><td class="g">maquetté</td><td class="n">Première impression client. Ci-dessus.</td></tr>
<tr><td>Nouvelle configuration</td><td class="g">aligné</td><td class="n">Formulaire court : la coquille M et la grille de champs suffisent, rien à décider.</td></tr>
<tr><td>Confirmation</td><td class="g">fait</td><td class="n">Déjà repris, et c'est lui la référence.</td></tr>
<tr><td>Ajouter une ressource</td><td class="g">fait</td><td class="n">Maquette dédiée du 03/09, déjà publiée à part.</td></tr>
<tr><td>Sélecteur de ressource</td><td class="g">cadre seul</td><td class="n">Il embarque <code>ResourcesScreen</code> : son contenu appartient au lot Médiathèque (<code>v1-mediatheque-plan.md</code> §3.3, « inchangé si possible »). Seul son cadre est à reprendre — voir ci-dessous.</td></tr>
<tr><td>Traduction sans HTML<br><span class="hint">multi_input_modal.dart</span></td><td class="g">fusionné</td><td class="n"><strong>Le jumeau du modal de traduction.</strong> <code>multi_string_input_container</code> appelle l'un ou l'autre selon <code>isHTML</code> : même travail, deux implémentations (560 px de large, une zone de 500 × 200, des boutons de 180 px). La maquette ci-dessus couvre les deux — une fois fusionnés en un seul, la barre de format en moins quand le champ n'accepte pas de HTML.</td></tr>
<tr><td>Point géo, géométrie<br><span class="hint">geoloc_ / geometry_input_container.dart</span></td><td class="g">absorbé</td><td class="n">Deux <code>AlertDialog</code> de <code>0,75 × largeur</code> pour poser un point ou tracer une zone : c'est précisément ce que le canevas du gabarit C met à plat dans l'écran. Placer un point sur une carte enfermée dans une modale est le geste le plus contraint qu'on puisse offrir.</td></tr>
<tr><td>Fichiers PDF, catégories de carte, sélecteur de couleur</td><td class="g">aligné</td><td class="n">Trois corps de dialogue honnêtes dans trois cadres cassés (<code>0,65 × 0,7</code> d'écran, boutons de 180 × 70). Ils héritent de la coquille sans rien décider. <strong>Au passage</strong> : <code>pdf_file_input_container</code> et <code>category_input_container</code> sont un copier-coller l'un de l'autre — mêmes dimensions, même structure — sauf que le second a ses libellés en dur (« Annuler », « Valider » au lieu des traductions) et un <code>onGetResult(result)</code> <em>commenté</em>. À vérifier : une catégorie modifiée ne remonte peut-être pas à la volée.</td></tr>
<tr><td>Éditeur de parcours<br><span class="hint">guided_path_editor.dart</span></td><td class="g">à part</td><td class="n">785 lignes avec ses propres dialogues. Il a déjà sa maquette (<code>sectionparcours-refonte-flux.html</code>) : à raccorder, pas à redessiner.</td></tr>
<tr><td>Lien d'application, device kiosk, utilisateurs, clés API, audit, guide IA, menu principal, notifications</td><td class="g">aligné</td><td class="n">Hors périmètre de cette refonte, mais héritent de la coquille et des boutons dès qu'on y touche.</td></tr>
</tbody>
</table>
</div>
<div class="callout">
<span class="eyebrow">Le sélecteur de ressource : le cadre, pas le contenu</span>
<p><code>showSelectResourceModal</code> encastre l'écran Médiathèque entier dans un <code>AlertDialog</code>. Son contenu a déjà deux sources de vérité — <code>v1-mediatheque-plan.md</code> §3.3 et la maquette « Ajouter une ressource » — et je n'en ajoute pas une troisième. En revanche son cadre a un vrai défaut, pas seulement un défaut de goût.</p>
<p><b>Le débordement.</b> Un <code>AlertDialog</code> s'insère avec 24 px de marge verticale : il dispose de <code>hauteur 48</code>. Le contenu en demande <code>0,85 × hauteur</code>, plus un titre, plus une ligne d'actions de 70 px — soit environ 895 px demandés pour 852 disponibles sur un écran de 900 de haut. C'est la raison d'être du <code>SingleChildScrollView</code> qui l'entoure, et la conséquence est un <strong>défilement dans un défilement</strong> : on fait glisser le dialogue au lieu de la grille de médias.</p>
<p><b>Ce qui change, et rien de plus.</b> Coquille standard avec en-tête à la place du <code>title: Center(Text(…))</code>, <code>kRadiusShell</code> au lieu du rayon 20, le bouton « Annuler » de 180 × 70 px descendu dans le pied — il est seul, dans un <code>Row</code> en <code>spaceEvenly</code>, puisque la sélection se fait au clic sur la vignette. La hauteur figée cède la place à un corps souple sous un <code>maxHeight</code> : plus de débordement, plus de double défilement. Et <code>showValues</code>, lignes 64-78, est du code mort — comme la copie qu'en garde <code>multi_input_modal.dart</code> ligne 186 : son unique appel, ligne 53, est en commentaire. Les deux peuvent partir.</p>
<p><b>Ce qui ne change pas, volontairement : la largeur.</b> Le <code>size.width * 0.85</code> est conservé tel quel. La règle du rail de facettes, en §3.3 du plan Médiathèque, est écrite <em>sur cette hypothèse</em> — « elle fait déjà <code>size.width * 0.85</code>, et un rail de 194 px y est acceptable ». Rétrécir la modale invaliderait une décision déjà codée dans un autre lot. On corrige donc la hauteur et la coquille, pas la largeur.</p>
</div>
<figure class="mock" style="margin-top: 22px">
<div class="stage" style="padding: 20px 16px">
<div class="dlg xl">
<div class="dlg-head">
<div>
<div class="h">Sélectionner une image</div>
<div class="sub">Champ « Image principale » — Config test</div>
</div>
<div class="spacer"></div>
<span class="type-chip">Images seulement</span>
<span class="xbtn"><span class="msym">close</span></span>
</div>
<div class="embed">
<div class="facets">
<div class="fg"><div class="ft">TYPE</div><div class="fi w2"></div><div class="fi w1"></div><div class="fi w3"></div></div>
<div class="fg"><div class="ft">USAGE</div><div class="fi w1"></div><div class="fi w2"></div></div>
<div class="fg"><div class="ft">CONFIGURATION</div><div class="fi w2"></div><div class="fi w3"></div><div class="fi w1"></div></div>
</div>
<div class="main">
<div class="search"><span class="msym">search</span>Rechercher dans la médiathèque…</div>
<div class="mgrid">
<div class="th sky"></div>
<div class="th pick sky"></div>
<div class="th"><span class="msym">image</span></div>
<div class="th" style="background: linear-gradient(135deg,#3d6b52,#8fb98c)"></div>
<div class="th"><span class="msym">image</span></div>
<div class="th" style="background: linear-gradient(135deg,#7a5a86,#c9a6cf)"></div>
<div class="th"><span class="msym">image</span></div>
<div class="th"><span class="msym">image</span></div>
<div class="veil"><span class="msym">lock</span>Contenu inchangé — c'est <code style="font-size: 10.5px">ResourcesScreen</code>, il appartient au lot Médiathèque.</div>
</div>
</div>
</div>
<div class="dlg-foot">
<span class="hint">Un clic sur une vignette sélectionne et referme.</span>
<span class="spacer"></span>
<span class="ghost-btn">Annuler</span>
</div>
</div>
</div>
<figcaption>
<b>Seul le cadre est dessiné.</b> L'en-tête, le pied et les contraintes de taille sont à moi ; le rail de facettes et la grille sont schématisés à dessein — ils sont déjà spécifiés ailleurs et déjà codés. Le pied ne porte qu'« Annuler », puisqu'il n'y a rien à valider : la sélection se fait au clic. La largeur reste celle d'aujourd'hui.
</figcaption>
</figure>
</section>
<!-- ══════════ 7. FICHIERS ══════════ -->
<section class="block">
<div class="section-head">
<div class="num">07</div>
<div>
<h2>Où ça se code, dans l'ordre</h2>
<p class="sub">Les trois premiers fichiers portent l'essentiel du gain visuel — ils sont importés par tous les écrans d'édition, y compris ceux qui ne sont pas dans cette proposition.</p>
</div>
</div>
<div class="files">
<div class="file">
<div class="p"><span class="badge core">1</span>Components/text_field_container.dart<br>Components/rounded_input_field.dart</div>
<div class="d">Sortir le <code>width: double.infinity</code> et le radius 29. Bordure 1 px, radius 8, marges rendues au parent. Tous les écrans changent d'un coup — à vérifier au passage sur Ressources et Instances.</div>
</div>
<div class="file">
<div class="p"><span class="badge core">2</span>Components/string_input_container.dart<br>Components/multi_string_input_container.dart<br>Components/resource_input_container.dart<br>Components/check_input_container.dart</div>
<div class="d">Label au-dessus du champ, suppression des <code>MainAxisAlignment.center</code> et des tailles en dur (<code>width: 200</code>, <code>height: 54</code>, la case à cocher de 50 px). Le composant ne décide plus de sa largeur ni de sa position.</div>
</div>
<div class="file">
<div class="p"><span class="badge screen">3</span>Screens/Configurations/section_reorderList.dart<br>Screens/Configurations/listView_card_section.dart</div>
<div class="d">Grille responsive au lieu de la liste horizontale, hauteur pilotée par le contenu, carte au format 16/9 avec badge d'ordre, poignée et type. Le bouton vert flottant devient une tuile « Ajouter » dans la grille.</div>
</div>
<div class="file">
<div class="p"><span class="badge screen">4</span>Screens/Configurations/configuration_detail_screen.dart<br>Screens/Configurations/Section/section_detail_screen.dart</div>
<div class="d">Grille à deux colonnes, actions remontées dans l'en-tête, suppression du <code>_buildFooter</code>. Le <code>_cardQR</code> devient le panneau « Diffusion » du rail droit.</div>
</div>
<div class="file">
<div class="p"><span class="badge screen">5</span>Components/multi_string_input_html_modal.dart<br>Components/translation_input_container.dart<br>Screens/Configurations/new_section_popup.dart<br>Screens/Resources/select_resource_modal.dart</div>
<div class="d">Les dialogues : coquille standard, boutons sortis du contenu vers le pied, grille de types à la place du menu déroulant. Le sélecteur de ressource ne reçoit que son cadre.</div>
</div>
<div class="file">
<div class="p"><span class="badge later">6</span>Components/collection_editor.dart <span class="hint">(à créer)</span></div>
<div class="d">La brique du gabarit B, puis migration des huit configs qui la dupliquent — et suppression des huit dialogues <code>showNewOrUpdate*</code>. C'est le plus gros morceau, et le seul qui puisse s'étaler dans le temps : chaque type migré est indépendant.</div>
</div>
</div>
<div class="callout" style="margin-top: 30px">
<span class="eyebrow">Ce n'est que du visuel — sauf sur deux points</span>
<p>Aucune donnée, aucune migration, aucun contrat d'API n'est touché, et tout est réversible par <code>git</code>. Le risque n'est pas la gravité, c'est la <strong>portée</strong> : les lots 1 et 2 changent des composants importés par une trentaine d'écrans, et <code>manager-app</code> n'a que trois tests, aucun sur ces composants. La vérification est donc manuelle.</p>
<p>Deux pièges concrets. D'abord, retirer une pastille pleine largeur change la hauteur des lignes — et il reste des conteneurs à hauteur figée autour (<code>height: 70</code>, <code>height: 100</code>, <code>SizedBox(height: 250)</code>) : ce qui rentrait juste peut déborder, et un débordement Flutter est visible, pas silencieux. Ensuite, ces composants ne sont pas purement décoratifs : dans <code>section_detail_screen.dart</code>, <code>MultiStringInputContainer.onGetResult</code> déclenche un <code>save(true, …)</code> à la ligne 270, et la case iBeacon un <code>save(false, …)</code> à la ligne 381. Il y a un chemin d'écriture câblé dans le composant — à ne pas perdre ni dédoubler en le refactorisant.</p>
</div>
</section>
<footer class="end">
Proposition d'interface pour <strong>manager-app</strong> · palette et iconographie existantes conservées (<code>#264863</code>, <code>#f5f5f7</code>, <code>#c2c9d6</code>, <code>#8bc34a</code>, <code>#ca413f</code>) · aucune dépendance nouvelle.
</footer>
</div>
<script>
// Motif de QR décoratif : trois repères d'angle + modules déterministes (mockup).
document.querySelectorAll('.qrbox').forEach(function (box) {
var n = 21, seed = parseInt(box.dataset.qr || '1', 10) * 2654435761 % 4294967296;
function rnd() { seed = (seed * 1103515245 + 12345) & 0x7fffffff; return seed / 0x7fffffff; }
var on = [];
function finder(ox, oy) {
for (var y = 0; y < 7; y++) for (var x = 0; x < 7; x++) {
var edge = x === 0 || y === 0 || x === 6 || y === 6;
var core = x > 1 && x < 5 && y > 1 && y < 5;
if (edge || core) on.push([ox + x, oy + y]);
}
}
finder(0, 0); finder(n - 7, 0); finder(0, n - 7);
function reserved(x, y) {
return (x < 8 && y < 8) || (x > n - 9 && y < 8) || (x < 8 && y > n - 9);
}
for (var y = 0; y < n; y++) for (var x = 0; x < n; x++) {
if (!reserved(x, y) && rnd() > 0.48) on.push([x, y]);
}
var rects = on.map(function (p) {
return '<rect x="' + p[0] + '" y="' + p[1] + '" width="1" height="1"/>';
}).join('');
box.innerHTML = '<svg viewBox="0 0 ' + n + ' ' + n + '" role="img" aria-label="QR code (illustration)" fill="#264863" shape-rendering="crispEdges">' + rects + '</svg>';
});
</script>