1312 lines
85 KiB
HTML
1312 lines
85 KiB
HTML
<!-- 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 & 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 & 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 : 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 <span style="font-family:'IBM Plex Mono',monospace;font-size:.85em"><type></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 ?</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 : <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<String, QuillController></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 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>
|