flutter build apk --debug vert, APK produit. SectionArticle et SectionEvent sont branchés dans main_view.getContent ; seul SectionParcours reste dehors, et définitivement. Quatre choses que l'implémentation a corrigées ou apprises, toutes reportées dans le plan, STATUS et la maquette : - La barre de pied de la maquette n'appartient pas aux écrans. section_page_detail la dessine déjà pour les treize types, avec les clés back/menu selon isFromMenu. La dessiner aurait affiché deux boutons retour. La maquette est corrigée sur ce point. - Un seul constructeur de marqueurs au lieu des quatre de mymuseum. MapAnnotationDTO et MapAnnotation ont des champs rigoureusement identiques mais sont deux types Dart distincts. Normalisé par une classe interne — L5 appliqué au front, et il porte d'autant plus que la convention [lng, lat] est l'endroit exact où une divergence ne lève aucune erreur. - L'audio d'article se résout dans contents avant l'API, donc sans appel réseau quand il est embarqué, donc hors ligne aussi. Mymuseum appelle l'API systématiquement en ligne. - Clé i18n event.live ajoutée aux 10 langues. Sans les 10, getFromLocale renvoie "" et la pastille rendrait une boîte vide. PL, CN, UK et AR sont de ma main et demandent une relecture humaine. Ce qui n'est pas prouvé, et qui est écrit comme tel : le rendu. Aucun des deux écrans n'a été vu à l'œil — un build vert ne dit rien d'une mise en page, c'est la leçon du §1bis appliquée au design. Et aucun SectionEvent n'existe en base, le type étant né avec Postgres v3 : le §19.13 cas E en créera un. Kanban : la carte « Types de section manquants sur le kiosk » quitte Urgent (2 -> 1) pour Fait récemment (40 -> 41), bandeau du haut et libellé « Quarante et un » compris. Compteurs des six colonnes revérifiés un à un. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1000 lines
42 KiB
HTML
1000 lines
42 KiB
HTML
<title>Borne en paysage — Article & Événement</title>
|
||
|
||
<style>
|
||
:root {
|
||
--ground: #F7F8F9;
|
||
--surface: #FFFFFF;
|
||
--surface-2: #EDF0F2;
|
||
--ink: #16202A;
|
||
--ink-2: #4A5966;
|
||
--ink-3: #7C8B98;
|
||
--line: #DCE2E7;
|
||
--line-soft: #E8ECEF;
|
||
--accent: #2F4858;
|
||
--accent-ink: #FFFFFF;
|
||
--accent-soft: #E3EBF0;
|
||
--live: #C4622D;
|
||
--live-soft: #F6E7DC;
|
||
|
||
--display: ui-serif, "Iowan Old Style", "Palatino Linotype", Palatino, Georgia, serif;
|
||
--body: ui-sans-serif, "Segoe UI", system-ui, -apple-system, sans-serif;
|
||
--mono: ui-monospace, "Cascadia Mono", Consolas, "SF Mono", monospace;
|
||
|
||
--measure: 66ch;
|
||
--s1: 0.4rem; --s2: 0.75rem; --s3: 1.15rem;
|
||
--s4: 1.9rem; --s5: 3rem; --s6: 4.75rem;
|
||
}
|
||
|
||
@media (prefers-color-scheme: dark) {
|
||
:root:not([data-theme="light"]) {
|
||
--ground: #10161C;
|
||
--surface: #172029;
|
||
--surface-2: #1F2A34;
|
||
--ink: #E6ECF1;
|
||
--ink-2: #A9B7C2;
|
||
--ink-3: #74838F;
|
||
--line: #2A3742;
|
||
--line-soft: #212D37;
|
||
--accent: #7FB0C8;
|
||
--accent-ink: #0E141A;
|
||
--accent-soft: #1E2C36;
|
||
--live: #E08A4F;
|
||
--live-soft: #2E2118;
|
||
}
|
||
}
|
||
|
||
:root[data-theme="dark"] {
|
||
--ground: #10161C;
|
||
--surface: #172029;
|
||
--surface-2: #1F2A34;
|
||
--ink: #E6ECF1;
|
||
--ink-2: #A9B7C2;
|
||
--ink-3: #74838F;
|
||
--line: #2A3742;
|
||
--line-soft: #212D37;
|
||
--accent: #7FB0C8;
|
||
--accent-ink: #0E141A;
|
||
--accent-soft: #1E2C36;
|
||
--live: #E08A4F;
|
||
--live-soft: #2E2118;
|
||
}
|
||
|
||
* { box-sizing: border-box; }
|
||
|
||
body {
|
||
background: var(--ground);
|
||
color: var(--ink);
|
||
font-family: var(--body);
|
||
font-size: 17px;
|
||
line-height: 1.65;
|
||
margin: 0;
|
||
padding: var(--s5) var(--s3) var(--s6);
|
||
-webkit-font-smoothing: antialiased;
|
||
}
|
||
|
||
.page { max-width: 68rem; margin: 0 auto; display: flex; flex-direction: column; gap: var(--s6); }
|
||
|
||
/* ── Header ─────────────────────────────────────────── */
|
||
|
||
.masthead { display: flex; flex-direction: column; gap: var(--s3); }
|
||
|
||
.eyebrow {
|
||
font-family: var(--mono);
|
||
font-size: 0.72rem;
|
||
letter-spacing: 0.13em;
|
||
text-transform: uppercase;
|
||
color: var(--ink-3);
|
||
display: flex; flex-wrap: wrap; gap: var(--s2) var(--s3);
|
||
}
|
||
|
||
h1 {
|
||
font-family: var(--display);
|
||
font-size: clamp(2.1rem, 5.2vw, 3.4rem);
|
||
line-height: 1.08;
|
||
font-weight: 600;
|
||
letter-spacing: -0.015em;
|
||
text-wrap: balance;
|
||
margin: 0;
|
||
}
|
||
|
||
.standfirst {
|
||
font-size: 1.2rem;
|
||
line-height: 1.55;
|
||
color: var(--ink-2);
|
||
max-width: var(--measure);
|
||
margin: 0;
|
||
text-wrap: pretty;
|
||
}
|
||
|
||
.rule { height: 1px; background: var(--line); border: 0; margin: 0; }
|
||
|
||
/* ── Sections ───────────────────────────────────────── */
|
||
|
||
section { display: flex; flex-direction: column; gap: var(--s4); }
|
||
|
||
h2 {
|
||
font-family: var(--display);
|
||
font-size: clamp(1.5rem, 3vw, 2rem);
|
||
font-weight: 600;
|
||
line-height: 1.15;
|
||
letter-spacing: -0.01em;
|
||
margin: 0;
|
||
text-wrap: balance;
|
||
}
|
||
|
||
h3 {
|
||
font-size: 1.02rem;
|
||
font-weight: 650;
|
||
line-height: 1.3;
|
||
margin: 0;
|
||
letter-spacing: -0.005em;
|
||
}
|
||
|
||
p { margin: 0; max-width: var(--measure); text-wrap: pretty; }
|
||
|
||
.lede { color: var(--ink-2); }
|
||
|
||
code {
|
||
font-family: var(--mono);
|
||
font-size: 0.87em;
|
||
background: var(--surface-2);
|
||
padding: 0.1em 0.36em;
|
||
border-radius: 3px;
|
||
color: var(--ink);
|
||
}
|
||
|
||
strong { font-weight: 650; }
|
||
|
||
/* ── Kiosk mockup frame ─────────────────────────────── */
|
||
|
||
.kiosk {
|
||
background: var(--surface);
|
||
border: 1px solid var(--line);
|
||
border-radius: 10px;
|
||
padding: var(--s2);
|
||
box-shadow: 0 1px 2px rgba(22, 32, 42, 0.05);
|
||
}
|
||
|
||
.kiosk-label {
|
||
font-family: var(--mono);
|
||
font-size: 0.68rem;
|
||
letter-spacing: 0.1em;
|
||
text-transform: uppercase;
|
||
color: var(--ink-3);
|
||
padding: 0 var(--s1) var(--s2);
|
||
display: flex; justify-content: space-between; gap: var(--s2);
|
||
}
|
||
|
||
.screen {
|
||
aspect-ratio: 16 / 10;
|
||
background: var(--surface-2);
|
||
border-radius: 4px;
|
||
overflow: hidden;
|
||
container-type: inline-size;
|
||
display: flex; flex-direction: column;
|
||
}
|
||
|
||
/* mockup internals scale with the frame width */
|
||
/* La dalle est peinte en clair en dur, quel que soit le thème du lecteur :
|
||
tablet-app est clair (kBackgroundColor #FFFFFF, kBackgroundLight #F3F3F3)
|
||
et une maquette qui bascule avec le thème du lecteur ne montre plus l'app. */
|
||
.screen {
|
||
--u: 1cqw;
|
||
--surface: #FFFFFF;
|
||
--surface-2: #F3F3F3;
|
||
--ink: #16202A;
|
||
--ink-2: #4A5966;
|
||
--ink-3: #7C8B98;
|
||
--line: #DCE2E7;
|
||
--line-soft: #E8ECEF;
|
||
--accent: #2F4858;
|
||
--accent-ink: #FFFFFF;
|
||
--accent-soft: #E3EBF0;
|
||
--live: #C4622D;
|
||
--live-soft: #F6E7DC;
|
||
background: var(--surface-2);
|
||
color: var(--ink);
|
||
}
|
||
.screen, .screen * { font-size: calc(1.35 * var(--u)); line-height: 1.4; }
|
||
|
||
.zone {
|
||
position: relative;
|
||
background: var(--surface);
|
||
border: 1px dashed var(--line);
|
||
}
|
||
|
||
.ztag {
|
||
position: absolute; top: calc(0.6 * var(--u)); left: calc(0.6 * var(--u));
|
||
font-family: var(--mono);
|
||
font-size: calc(1.15 * var(--u));
|
||
font-weight: 700;
|
||
color: var(--accent-ink);
|
||
background: var(--accent);
|
||
width: calc(2.4 * var(--u)); height: calc(2.4 * var(--u));
|
||
display: grid; place-items: center;
|
||
border-radius: 2px;
|
||
z-index: 3;
|
||
}
|
||
|
||
/* generic mockup atoms */
|
||
.bar { background: var(--ink-3); opacity: 0.28; border-radius: 1px; height: calc(1.1 * var(--u)); }
|
||
.bar.t { opacity: 0.5; }
|
||
.stack { display: flex; flex-direction: column; gap: calc(0.9 * var(--u)); }
|
||
.imgblock {
|
||
background: linear-gradient(135deg, var(--accent-soft), var(--surface-2));
|
||
border: 1px solid var(--line-soft);
|
||
display: grid; place-items: center;
|
||
color: var(--ink-3);
|
||
font-family: var(--mono);
|
||
font-size: calc(1.1 * var(--u));
|
||
}
|
||
|
||
/* ── Article mockup ─────────────────────────────────── */
|
||
|
||
.m-article { display: grid; grid-template-columns: 38fr 62fr; height: 100%; }
|
||
|
||
.m-media { padding: calc(2 * var(--u)); display: flex; flex-direction: column; gap: calc(1.4 * var(--u)); }
|
||
.m-media .imgblock { flex: 1; border-radius: calc(0.6 * var(--u)); }
|
||
|
||
.m-thumbs { display: grid; grid-template-columns: repeat(4, 1fr); gap: calc(0.8 * var(--u)); height: calc(7 * var(--u)); }
|
||
.m-thumbs .imgblock { border-radius: calc(0.4 * var(--u)); font-size: calc(0.9 * var(--u)); }
|
||
|
||
.m-audio {
|
||
background: var(--accent);
|
||
color: var(--accent-ink);
|
||
border-radius: calc(0.6 * var(--u));
|
||
padding: calc(1.2 * var(--u)) calc(1.4 * var(--u));
|
||
display: flex; align-items: center; gap: calc(1.2 * var(--u));
|
||
height: calc(8.5 * var(--u));
|
||
}
|
||
.m-audio .play {
|
||
width: calc(5 * var(--u)); height: calc(5 * var(--u));
|
||
border-radius: 50%;
|
||
background: var(--accent-ink); opacity: 0.9;
|
||
display: grid; place-items: center;
|
||
color: var(--accent);
|
||
font-size: calc(1.8 * var(--u));
|
||
flex: none;
|
||
}
|
||
.m-audio .track { flex: 1; height: calc(0.9 * var(--u)); background: currentColor; opacity: 0.35; border-radius: 1px; }
|
||
|
||
.m-text { padding: calc(2.4 * var(--u)) calc(2.4 * var(--u)) 0; display: flex; flex-direction: column; overflow: hidden; }
|
||
.m-title { font-family: var(--display); font-size: calc(3.1 * var(--u)) !important; font-weight: 600; line-height: 1.1; color: var(--ink); }
|
||
.m-kicker { font-family: var(--mono); font-size: calc(1 * var(--u)) !important; letter-spacing: 0.1em; text-transform: uppercase; color: var(--accent); }
|
||
.m-measure { max-width: calc(52 * var(--u)); }
|
||
|
||
.m-footer {
|
||
border-top: 1px solid var(--line);
|
||
padding: calc(1.2 * var(--u)) calc(2.4 * var(--u));
|
||
display: flex; align-items: center; justify-content: flex-end;
|
||
background: var(--surface-2);
|
||
height: calc(8 * var(--u));
|
||
flex: none;
|
||
}
|
||
.btn-back {
|
||
background: var(--accent); color: var(--accent-ink);
|
||
border-radius: calc(0.4 * var(--u));
|
||
padding: calc(0.8 * var(--u)) calc(1.8 * var(--u));
|
||
font-size: calc(1.3 * var(--u)) !important;
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* ── Event mockup ───────────────────────────────────── */
|
||
|
||
.m-event { display: flex; flex-direction: column; height: 100%; }
|
||
|
||
.m-hero {
|
||
height: 26%;
|
||
background: linear-gradient(135deg, var(--accent), var(--accent-soft));
|
||
padding: calc(2 * var(--u)) calc(2.4 * var(--u));
|
||
display: flex; align-items: flex-end; justify-content: space-between; gap: calc(2 * var(--u));
|
||
flex: none;
|
||
position: relative;
|
||
}
|
||
.m-hero-title { font-family: var(--display); font-size: calc(3 * var(--u)) !important; font-weight: 600; color: #fff; line-height: 1.1; }
|
||
.m-hero-date {
|
||
background: rgba(255,255,255,0.9); color: var(--accent);
|
||
border-radius: calc(2 * var(--u));
|
||
padding: calc(0.6 * var(--u)) calc(1.4 * var(--u));
|
||
font-size: calc(1.2 * var(--u)) !important;
|
||
font-weight: 600;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.m-body { display: grid; grid-template-columns: 44fr 56fr; flex: 1; min-height: 0; }
|
||
|
||
.m-prog { padding: calc(1.8 * var(--u)); display: flex; flex-direction: column; gap: calc(1 * var(--u)); overflow: hidden; }
|
||
.m-sechead {
|
||
font-family: var(--mono); font-size: calc(1.05 * var(--u)) !important;
|
||
letter-spacing: 0.1em; text-transform: uppercase; color: var(--ink-3);
|
||
}
|
||
.m-block {
|
||
border: 1px solid var(--line);
|
||
border-radius: calc(0.5 * var(--u));
|
||
padding: calc(1.1 * var(--u)) calc(1.3 * var(--u));
|
||
display: flex; align-items: center; gap: calc(1.2 * var(--u));
|
||
background: var(--surface);
|
||
}
|
||
.m-block .time { font-family: var(--mono); font-size: calc(1.15 * var(--u)) !important; color: var(--ink-3); flex: none; font-variant-numeric: tabular-nums; }
|
||
.m-block .name { flex: 1; color: var(--ink-2); font-size: calc(1.3 * var(--u)) !important; }
|
||
.m-block.live { background: var(--live-soft); border-color: var(--live); }
|
||
.m-block.live .name { color: var(--ink); font-weight: 650; }
|
||
.m-block.live .time { color: var(--live); font-weight: 700; }
|
||
.pill-live {
|
||
background: var(--live); color: #fff;
|
||
border-radius: calc(2 * var(--u));
|
||
padding: calc(0.35 * var(--u)) calc(1 * var(--u));
|
||
font-size: calc(1 * var(--u)) !important;
|
||
font-weight: 700; letter-spacing: 0.04em;
|
||
flex: none;
|
||
}
|
||
|
||
.m-map { position: relative; border-left: 1px solid var(--line); background: var(--surface-2); overflow: hidden; }
|
||
.m-map-grid {
|
||
position: absolute; inset: 0;
|
||
background-image:
|
||
linear-gradient(var(--line-soft) 1px, transparent 1px),
|
||
linear-gradient(90deg, var(--line-soft) 1px, transparent 1px);
|
||
background-size: calc(6 * var(--u)) calc(6 * var(--u));
|
||
}
|
||
.pin {
|
||
position: absolute;
|
||
width: calc(2.2 * var(--u)); height: calc(2.2 * var(--u));
|
||
border-radius: 50% 50% 50% 0;
|
||
transform: rotate(-45deg);
|
||
background: var(--accent);
|
||
border: 1px solid var(--surface);
|
||
}
|
||
.pin.live { background: var(--live); width: calc(2.8 * var(--u)); height: calc(2.8 * var(--u)); }
|
||
.m-map-cap {
|
||
position: absolute; left: calc(1.4 * var(--u)); bottom: calc(1.4 * var(--u));
|
||
background: var(--surface); border: 1px solid var(--line);
|
||
border-radius: calc(0.4 * var(--u));
|
||
padding: calc(0.6 * var(--u)) calc(1.1 * var(--u));
|
||
font-family: var(--mono); font-size: calc(1 * var(--u)) !important;
|
||
color: var(--ink-2);
|
||
}
|
||
|
||
/* ── Annotations ────────────────────────────────────── */
|
||
|
||
.notes { display: grid; grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: var(--s3); }
|
||
|
||
/* ── Variants (états incomplets) ────────────────────── */
|
||
|
||
.variants { display: grid; grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr)); gap: var(--s3); }
|
||
.variant { display: flex; flex-direction: column; gap: var(--s2); }
|
||
.variant .kiosk { padding: calc(var(--s1) * 1.2); }
|
||
.variant h3 { font-size: 0.94rem; }
|
||
.variant p { font-size: 0.9rem; color: var(--ink-2); max-width: none; }
|
||
.variant .cond {
|
||
font-family: var(--mono); font-size: 0.72rem; color: var(--ink-3);
|
||
display: block; margin-top: var(--s1);
|
||
}
|
||
|
||
.note {
|
||
display: grid;
|
||
grid-template-columns: 1.6rem 1fr;
|
||
gap: var(--s2);
|
||
align-items: start;
|
||
}
|
||
.note .key {
|
||
font-family: var(--mono); font-size: 0.72rem; font-weight: 700;
|
||
color: var(--accent-ink); background: var(--accent);
|
||
width: 1.6rem; height: 1.6rem;
|
||
display: grid; place-items: center; border-radius: 2px;
|
||
margin-top: 0.12rem;
|
||
}
|
||
.note p { font-size: 0.94rem; line-height: 1.55; color: var(--ink-2); max-width: none; }
|
||
.note strong { color: var(--ink); }
|
||
|
||
/* ── Rules list ─────────────────────────────────────── */
|
||
|
||
.rules { display: flex; flex-direction: column; gap: 0; border-top: 1px solid var(--line); }
|
||
|
||
.ruleitem {
|
||
display: grid;
|
||
grid-template-columns: minmax(9rem, 15rem) 1fr;
|
||
gap: var(--s2) var(--s4);
|
||
padding: var(--s3) 0;
|
||
border-bottom: 1px solid var(--line);
|
||
align-items: start;
|
||
}
|
||
.ruleitem h3 { padding-top: 0.1rem; }
|
||
.ruleitem p { font-size: 0.96rem; color: var(--ink-2); }
|
||
.ruleitem .before {
|
||
display: block;
|
||
font-family: var(--mono);
|
||
font-size: 0.75rem;
|
||
color: var(--ink-3);
|
||
margin-top: var(--s1);
|
||
}
|
||
|
||
/* ── Callout ────────────────────────────────────────── */
|
||
|
||
.callout {
|
||
background: var(--surface);
|
||
border: 1px solid var(--line);
|
||
border-left: 3px solid var(--accent);
|
||
border-radius: 0 6px 6px 0;
|
||
padding: var(--s3);
|
||
display: flex; flex-direction: column; gap: var(--s2);
|
||
}
|
||
.callout h3 { font-size: 0.96rem; }
|
||
.callout p { font-size: 0.94rem; color: var(--ink-2); }
|
||
|
||
footer {
|
||
font-family: var(--mono); font-size: 0.72rem;
|
||
color: var(--ink-3); letter-spacing: 0.04em;
|
||
border-top: 1px solid var(--line);
|
||
padding-top: var(--s3);
|
||
display: flex; flex-wrap: wrap; gap: var(--s2) var(--s4);
|
||
}
|
||
|
||
@media (max-width: 44rem) {
|
||
.ruleitem { grid-template-columns: 1fr; }
|
||
body { font-size: 16px; }
|
||
}
|
||
</style>
|
||
|
||
<div class="page">
|
||
|
||
<header class="masthead">
|
||
<div class="eyebrow">
|
||
<span>tablet-app · lot K</span>
|
||
<span>K3 — SectionEvent</span>
|
||
<span>K7 — SectionArticle</span>
|
||
<span>12 août 2026</span>
|
||
</div>
|
||
<h1>Deux écrans à faire passer du téléphone debout à la borne couchée</h1>
|
||
<p class="standfirst">
|
||
Le code se recopie depuis <code>mymuseum-visitapp</code> — ce n'est pas là qu'est le travail.
|
||
Ces écrans sont dessinés pour un téléphone tenu à la verticale, à trente centimètres des yeux.
|
||
Une borne fait 1280 px de large, elle est fixe, et on la lit debout à un mètre.
|
||
Cette page arrête la mise en page des deux écrans <em>ensemble</em>, parce que les trancher
|
||
séparément donnerait deux grammaires différentes sur la même dalle.
|
||
<strong>Validée le 12 août</strong> — les quatre décisions de fin de page sont prises.
|
||
</p>
|
||
</header>
|
||
|
||
<hr class="rule">
|
||
|
||
<!-- ── RÈGLES COMMUNES ────────────────────────────── -->
|
||
|
||
<section>
|
||
<h2>Ce qui change, pour les deux</h2>
|
||
<p class="lede">
|
||
Six décisions valables sur toute la borne. Elles ne sortent pas d'une préférence :
|
||
chacune corrige un idiome de téléphone qui devient faux sur une dalle large et fixe.
|
||
</p>
|
||
|
||
<div class="rules">
|
||
|
||
<div class="ruleitem">
|
||
<h3>Deux colonnes, jamais une</h3>
|
||
<p>
|
||
La largeur est la ressource qu'on gagne, et une colonne unique la gaspille intégralement.
|
||
Chaque écran se découpe en un <strong>ancrage</strong> qui ne bouge pas et un
|
||
<strong>flux</strong> qui défile. Sur l'article, le média ancre et le texte défile ; sur
|
||
l'événement, la carte ancre et le programme défile.
|
||
<span class="before">mobile — une colonne, tout défile ensemble</span>
|
||
</p>
|
||
</div>
|
||
|
||
<div class="ruleitem">
|
||
<h3>Le texte reste étroit</h3>
|
||
<p>
|
||
Élargir la colonne de texte à 1280 px la rend illisible : l'œil perd la ligne au retour.
|
||
Le corps de l'article est plafonné autour de <strong>65 caractères</strong> et le reste de
|
||
la largeur va au média, pas au texte. C'est la règle qui justifie à elle seule les deux colonnes.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="ruleitem">
|
||
<h3>Le héros est une bande, pas un écran</h3>
|
||
<p>
|
||
Sur mobile, l'événement ouvre sur une image qui occupe <strong>52 % de la hauteur</strong>,
|
||
et le parallaxe récompense le défilement. Debout devant une borne, personne ne défile pour
|
||
découvrir qu'il y a un programme dessous. Le héros tombe à <strong>26 %</strong> : il situe,
|
||
il n'accueille pas.
|
||
<span class="before">mobile — SliverAppBar, expandedHeight = 0.52 × hauteur</span>
|
||
</p>
|
||
</div>
|
||
|
||
<div class="ruleitem">
|
||
<h3>Pas de feuille modale</h3>
|
||
<p>
|
||
<code>showModalBottomSheet</code> est une réponse à l'étroitesse : sur un téléphone il n'y a
|
||
pas de place à côté, alors on empile. Sur une borne il y a la place à côté. Le détail d'un
|
||
bloc de programme s'ouvre <strong>dans la colonne de droite</strong>, sous la carte, sans
|
||
masquer ce qu'on regardait.
|
||
<span class="before">mobile — _showBlockDetail() en bottom sheet</span>
|
||
</p>
|
||
</div>
|
||
|
||
<div class="ruleitem">
|
||
<h3>Rien à agrandir</h3>
|
||
<p>
|
||
La carte de l'événement est un aperçu de 220 px qu'il faut toucher pour ouvrir une page
|
||
pleine. Sur une borne d'accueil, la carte <em>est</em> la raison pour laquelle on s'arrête :
|
||
elle est affichée à sa taille utile d'emblée, et <code>EventMapFullPage</code> ne se porte pas.
|
||
<span class="before">mobile — aperçu 220 px + bouton « Agrandir »</span>
|
||
</p>
|
||
</div>
|
||
|
||
<div class="ruleitem">
|
||
<h3>Les couleurs viennent de la configuration</h3>
|
||
<p>
|
||
<code>mymuseum-visitapp</code> code ses couleurs en dur par flavor client — rouge MDLF, bleu
|
||
Fort. <code>tablet-app</code> n'a pas de flavor : un seul APK sert tous les clients et la
|
||
charte arrive avec la configuration choisie au pincode. Tout accent dérive de
|
||
<code>configuration.primaryColor</code>, <strong>comme l'écran Game livré en K4</strong>.
|
||
Le bleu ardoise de cette page est le repli <code>kTestSecondColor</code>, pas une proposition
|
||
de charte.
|
||
</p>
|
||
</div>
|
||
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ── ARTICLE ────────────────────────────────────── -->
|
||
|
||
<section>
|
||
<h2>Article — le média ancre, le texte défile</h2>
|
||
<p class="lede">
|
||
Un article porte un corps HTML, souvent un audio, et parfois une série d'images.
|
||
Sur mobile les trois s'empilent et l'audio finit en lecteur flottant par-dessus le texte.
|
||
En paysage, chacun a sa place et le lecteur audio n'a plus besoin de flotter.
|
||
</p>
|
||
|
||
<div class="kiosk">
|
||
<div class="kiosk-label">
|
||
<span>Article — 1280 × 800</span>
|
||
<span>MDLF · « Ruche 7 »</span>
|
||
</div>
|
||
<div class="screen">
|
||
<div class="m-article">
|
||
|
||
<div class="zone m-media">
|
||
<span class="ztag">A</span>
|
||
<div class="imgblock">image principale</div>
|
||
<div class="m-thumbs">
|
||
<div class="imgblock">2</div>
|
||
<div class="imgblock">3</div>
|
||
<div class="imgblock">4</div>
|
||
<div class="imgblock">5</div>
|
||
</div>
|
||
<div class="m-audio">
|
||
<span class="play">▶</span>
|
||
<span class="track"></span>
|
||
<span style="font-family: var(--mono); font-variant-numeric: tabular-nums;">3:12</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="display: flex; flex-direction: column; min-width: 0;">
|
||
<div class="zone m-text" style="flex: 1;">
|
||
<span class="ztag">B</span>
|
||
<div class="stack m-measure" style="margin-top: calc(2.6 * var(--u));">
|
||
<div class="m-kicker">Rucher pédagogique</div>
|
||
<div class="m-title">Ruche 7</div>
|
||
<div class="stack" style="margin-top: calc(1.4 * var(--u));">
|
||
<div class="bar" style="width: 100%;"></div>
|
||
<div class="bar" style="width: 97%;"></div>
|
||
<div class="bar" style="width: 99%;"></div>
|
||
<div class="bar" style="width: 62%;"></div>
|
||
<div class="bar t" style="width: 44%; margin-top: calc(1.2 * var(--u));"></div>
|
||
<div class="bar" style="width: 98%;"></div>
|
||
<div class="bar" style="width: 95%;"></div>
|
||
<div class="bar" style="width: 71%;"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="zone m-footer" style="border-style: dashed dashed none none;">
|
||
<span class="ztag" style="top: calc(-1.4 * var(--u));">C</span>
|
||
<span class="btn-back">Retour</span>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="notes">
|
||
<div class="note">
|
||
<span class="key">A</span>
|
||
<p>
|
||
<strong>Rail média, 38 %.</strong> L'image principale prend la hauteur disponible, les
|
||
autres ressources deviennent une bande de vignettes — c'est <code>SliderImages</code> à plat
|
||
plutôt qu'en carrousel, parce qu'une borne n'a pas de raison de cacher quatre images
|
||
derrière un geste. <strong>L'audio est docké en bas du rail</strong> : sur mobile,
|
||
<code>audio_player_floating</code> flotte au-dessus du texte faute de place. Ici il a une
|
||
place, donc il ne flotte plus.
|
||
</p>
|
||
</div>
|
||
<div class="note">
|
||
<span class="key">B</span>
|
||
<p>
|
||
<strong>Colonne de lecture, 62 %, texte plafonné.</strong> Le rendu HTML
|
||
(<code>flutter_widget_from_html</code>) se recopie tel quel ; ce qui change est le
|
||
<code>maxWidth</code> qui l'entoure. Le titre passe en display, avec l'étiquette de section
|
||
au-dessus — sur une borne, un visiteur arrive au milieu d'un parcours et doit savoir
|
||
<em>où</em> il est avant <em>quoi</em> il lit.
|
||
</p>
|
||
</div>
|
||
<div class="note">
|
||
<span class="key">C</span>
|
||
<p>
|
||
<strong>Barre de pied — et elle n'appartient pas à ces écrans.</strong>
|
||
⛔ <em>Corrigé le 12/08 en implémentant</em> : <code>section_page_detail</code> la dessine
|
||
déjà pour les treize types, avec les clés <code>back</code> / <code>menu</code> selon qu'on
|
||
vient d'un menu. Les deux écrans ne rendent que leur contenu — dessiner ce pied de page
|
||
aurait affiché <strong>deux boutons retour</strong>. Elle reste dans les maquettes parce que
|
||
le visiteur la voit, mais c'est la coquille qui la fournit.
|
||
<strong>Pas de sélecteur de langue</strong> — elle est choisie en amont. Et rien d'autre :
|
||
<code>SectionPageDetail</code> ne reçoit qu'<em>une</em> section et un booléen
|
||
<code>isFromMenu</code>, jamais la liste de ses voisines, donc un repère du type « 3 / 8 »
|
||
n'est adossé à aucune donnée.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<h2 style="font-size: 1.25rem; margin-top: var(--s3);">Quand il manque l'audio, les photos, ou les deux</h2>
|
||
<p class="lede">
|
||
Les trois cas sont possibles en configuration, et le rail média de 38 % ne survit pas tel quel
|
||
à la disparition de son contenu. La règle qui les couvre tous :
|
||
<strong>le rail n'existe que s'il a quelque chose à ancrer</strong>. Sinon la colonne de lecture
|
||
se centre — ce qui ne contredit pas la règle des deux colonnes, dont l'objet est d'empêcher une
|
||
ligne de texte de s'étirer sur 1280 px, pas d'imposer une colonne vide.
|
||
</p>
|
||
|
||
<div class="variants">
|
||
|
||
<div class="variant">
|
||
<div class="kiosk">
|
||
<div class="screen">
|
||
<div class="m-article">
|
||
<div class="zone m-media">
|
||
<div class="imgblock">image principale</div>
|
||
<div class="m-thumbs">
|
||
<div class="imgblock">2</div>
|
||
<div class="imgblock">3</div>
|
||
<div class="imgblock">4</div>
|
||
<div class="imgblock">5</div>
|
||
</div>
|
||
</div>
|
||
<div style="display: flex; flex-direction: column; min-width: 0;">
|
||
<div class="zone m-text" style="flex: 1;">
|
||
<div class="stack m-measure" style="margin-top: calc(2.6 * var(--u));">
|
||
<div class="m-kicker">Rucher pédagogique</div>
|
||
<div class="m-title">Ruche 7</div>
|
||
<div class="stack" style="margin-top: calc(1.4 * var(--u));">
|
||
<div class="bar" style="width: 100%;"></div>
|
||
<div class="bar" style="width: 96%;"></div>
|
||
<div class="bar" style="width: 99%;"></div>
|
||
<div class="bar" style="width: 58%;"></div>
|
||
<div class="bar t" style="width: 44%; margin-top: calc(1.2 * var(--u));"></div>
|
||
<div class="bar" style="width: 97%;"></div>
|
||
<div class="bar" style="width: 74%;"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="zone m-footer" style="border-style: dashed none none none;">
|
||
<span class="btn-back">Retour</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<h3>Sans audio</h3>
|
||
<p>
|
||
Le dock disparaît, l'image reprend la hauteur libérée. <strong>Pas de lecteur grisé, pas de
|
||
repli sur une autre langue</strong> — le lecteur n'existe pas pour ce visiteur.
|
||
<span class="cond">pas d'entrée audioIds pour la langue choisie</span>
|
||
</p>
|
||
</div>
|
||
|
||
<div class="variant">
|
||
<div class="kiosk">
|
||
<div class="screen">
|
||
<div style="display: flex; flex-direction: column; height: 100%;">
|
||
<div class="zone m-text" style="flex: 1; align-items: center;">
|
||
<div class="stack m-measure" style="margin-top: calc(3.2 * var(--u)); width: calc(52 * var(--u));">
|
||
<div class="m-kicker">Rucher pédagogique</div>
|
||
<div class="m-title">Ruche 7</div>
|
||
<div class="m-audio" style="margin-top: calc(1.6 * var(--u)); height: calc(7.5 * var(--u));">
|
||
<span class="play">▶</span>
|
||
<span class="track"></span>
|
||
<span style="font-family: var(--mono);">3:12</span>
|
||
</div>
|
||
<div class="stack" style="margin-top: calc(1.6 * var(--u));">
|
||
<div class="bar" style="width: 100%;"></div>
|
||
<div class="bar" style="width: 97%;"></div>
|
||
<div class="bar" style="width: 99%;"></div>
|
||
<div class="bar" style="width: 63%;"></div>
|
||
<div class="bar t" style="width: 44%; margin-top: calc(1.2 * var(--u));"></div>
|
||
<div class="bar" style="width: 96%;"></div>
|
||
<div class="bar" style="width: 81%;"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="zone m-footer" style="border-style: dashed none none none;">
|
||
<span class="btn-back">Retour</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<h3>Sans photo, avec audio</h3>
|
||
<p>
|
||
Le rail tombe — une colonne de 38 % contenant un seul lecteur serait un trou, pas une mise
|
||
en page. L'audio passe <strong>en tête de la colonne de lecture</strong>, à la largeur du
|
||
texte, juste sous le titre : c'est là qu'on le cherche quand il est la pièce principale.
|
||
<span class="cond">imageSource nul et contents vide</span>
|
||
</p>
|
||
</div>
|
||
|
||
<div class="variant">
|
||
<div class="kiosk">
|
||
<div class="screen">
|
||
<div style="display: flex; flex-direction: column; height: 100%;">
|
||
<div class="zone m-text" style="flex: 1; align-items: center;">
|
||
<div class="stack m-measure" style="margin-top: calc(4.5 * var(--u)); width: calc(52 * var(--u));">
|
||
<div class="m-kicker">Rucher pédagogique</div>
|
||
<div class="m-title">Ruche 7</div>
|
||
<div class="stack" style="margin-top: calc(1.8 * var(--u));">
|
||
<div class="bar" style="width: 100%;"></div>
|
||
<div class="bar" style="width: 98%;"></div>
|
||
<div class="bar" style="width: 96%;"></div>
|
||
<div class="bar" style="width: 99%;"></div>
|
||
<div class="bar" style="width: 57%;"></div>
|
||
<div class="bar t" style="width: 44%; margin-top: calc(1.4 * var(--u));"></div>
|
||
<div class="bar" style="width: 97%;"></div>
|
||
<div class="bar" style="width: 99%;"></div>
|
||
<div class="bar" style="width: 68%;"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="zone m-footer" style="border-style: dashed none none none;">
|
||
<span class="btn-back">Retour</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<h3>Texte seul</h3>
|
||
<p>
|
||
Une seule colonne, centrée, toujours plafonnée à 65 caractères. Le vide de part et d'autre
|
||
est <strong>voulu</strong> : c'est ce qui garde le texte lisible. La tentation d'étirer la
|
||
ligne pour « remplir » est précisément ce que la règle interdit.
|
||
<span class="cond">ni image, ni contents, ni audio</span>
|
||
</p>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="callout">
|
||
<h3>Deux choses vérifiées dans <code>ArticleDTO</code>, pas supposées</h3>
|
||
<p>
|
||
<strong><code>audioIds</code> est une <code>List<TranslationDTO></code>, pas un id.</strong>
|
||
L'audio est donc <em>par langue</em> : un article peut avoir sa version française et pas sa
|
||
néerlandaise. La règle est donc stricte — <strong>pas d'entrée pour la langue choisie, pas de
|
||
lecteur du tout.</strong> Ni lecteur grisé, ni repli sur une autre langue : un visiteur
|
||
néerlandophone à qui la borne servirait l'audio français serait plus mal servi que par le
|
||
silence. « Sans audio » est un état <strong>du visiteur</strong>, pas de l'article.
|
||
</p>
|
||
<p>
|
||
<strong><code>isContentTop</code> existe déjà</strong> et dit si le texte passe avant le média.
|
||
En portrait, « avant » veut dire au-dessus ; en paysage il n'y a plus de dessus, donc
|
||
<strong>le drapeau devient le côté</strong> :
|
||
<code>isContentTop == true</code> → <strong>texte à gauche, rail média à droite</strong> ;
|
||
sinon la disposition par défaut des maquettes, média à gauche. Le rail garde sa composition
|
||
dans les deux cas — image, vignettes, audio docké — il change de bord, pas de contenu. À
|
||
câbler, sinon un réglage déjà offert dans <code>manager-app</code> reste sans effet sur la borne.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="callout">
|
||
<h3>Les statistiques sont déjà là — vérifié dans le code</h3>
|
||
<p>
|
||
Rien à câbler. <code>section_page_detail.dart</code> émet <code>sectionView</code> à
|
||
l'ouverture et <code>sectionLeave</code> avec la durée au <code>dispose</code>, et il
|
||
<strong>enveloppe les treize types</strong> : l'article sera compté dès que son <code>case</code>
|
||
entre dans le <code>switch</code>. <code>statisticsService</code> est câblé à sept endroits de
|
||
l'app. L'événement <code>articleRead</code> de mymuseum, plus spécifique, n'existe pas ici —
|
||
c'est un ajout facultatif, pas un manque.
|
||
</p>
|
||
<p>
|
||
En revanche, le <strong>marquage « déjà lu » par visiteur</strong>
|
||
(<code>articleRead</code> en base locale, <code>DatabaseHelper</code>) ne se recopie pas : sur
|
||
un appareil partagé, le lecteur suivant n'est pas le précédent.
|
||
</p>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ── ÉVÉNEMENT ──────────────────────────────────── -->
|
||
|
||
<section>
|
||
<h2>Événement — la carte ancre, le programme défile</h2>
|
||
<p class="lede">
|
||
C'est l'écran du cas E du test-plan : la borne d'accueil d'un carnaval ou d'un festival, qui
|
||
répond à deux questions et deux seulement — <em>qu'est-ce qui se passe maintenant</em> et
|
||
<em>où est-ce que ça se passe</em>. La mise en page les met côte à côte, sans geste entre elles.
|
||
</p>
|
||
|
||
<div class="kiosk">
|
||
<div class="kiosk-label">
|
||
<span>Événement — 1280 × 800</span>
|
||
<span>Cas E · carnaval</span>
|
||
</div>
|
||
<div class="screen">
|
||
<div class="m-event">
|
||
|
||
<div class="zone m-hero" style="border-style: none none dashed none;">
|
||
<span class="ztag">D</span>
|
||
<div class="m-hero-title">Carnaval des Légendes</div>
|
||
<span class="m-hero-date">14/03/2026 → 16/03/2026</span>
|
||
</div>
|
||
|
||
<div class="m-body">
|
||
|
||
<div class="zone m-prog">
|
||
<span class="ztag">E</span>
|
||
<div class="m-sechead" style="margin-top: calc(2.4 * var(--u));">Programme du jour</div>
|
||
<div class="m-block">
|
||
<span class="time">10:00</span>
|
||
<span class="name">Ouverture des portes</span>
|
||
</div>
|
||
<div class="m-block live">
|
||
<span class="time">14:00</span>
|
||
<span class="name">Grand cortège — départ place du Marché</span>
|
||
<span class="pill-live">EN COURS</span>
|
||
</div>
|
||
<div class="m-block">
|
||
<span class="time">16:30</span>
|
||
<span class="name">Remise des prix des chars</span>
|
||
</div>
|
||
<div class="m-block">
|
||
<span class="time">18:00</span>
|
||
<span class="name">Brûlage du Bonhomme Hiver</span>
|
||
</div>
|
||
<div class="m-block">
|
||
<span class="time">20:00</span>
|
||
<span class="name">Feu d'artifice</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="zone m-map">
|
||
<span class="ztag">F</span>
|
||
<div class="m-map-grid"></div>
|
||
<div class="pin live" style="left: 44%; top: 46%;"></div>
|
||
<div class="pin" style="left: 22%; top: 28%;"></div>
|
||
<div class="pin" style="left: 68%; top: 34%;"></div>
|
||
<div class="pin" style="left: 58%; top: 66%;"></div>
|
||
<div class="pin" style="left: 30%; top: 72%;"></div>
|
||
<span class="m-map-cap">5 points · le point orange suit le bloc en cours</span>
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="notes">
|
||
<div class="note">
|
||
<span class="key">D</span>
|
||
<p>
|
||
<strong>Bande héros, 26 %.</strong> Assez pour porter l'image, le titre et les dates ;
|
||
pas assez pour repousser le programme sous la ligne de flottaison. Le dégradé de repli
|
||
dérive de <code>primaryColor</code> — c'est exactement le calcul déjà écrit dans l'écran
|
||
Game de K4, à réutiliser plutôt qu'à refaire.
|
||
</p>
|
||
</div>
|
||
<div class="note">
|
||
<span class="key">E</span>
|
||
<p>
|
||
<strong>Programme, 44 %.</strong> La frise verticale à pastilles et trait de liaison de
|
||
mymuseum tombe : elle coûte 56 px de gouttière pour exprimer une chronologie que l'heure
|
||
en tête de ligne dit déjà. Le bloc actif garde son fond plein et son pastillage
|
||
<strong>« EN COURS »</strong> — c'est l'information la plus demandée de l'écran, elle doit
|
||
se voir à trois mètres.
|
||
</p>
|
||
</div>
|
||
<div class="note">
|
||
<span class="key">F</span>
|
||
<p>
|
||
<strong>Carte, 56 %, toujours vive.</strong> Les annotations globales sont dans l'accent,
|
||
celles du bloc en cours dans l'orange sémantique — la même paire de couleurs que le
|
||
programme, pour que l'œil relie une ligne à un point sans légende. Défilement et zoom
|
||
restent actifs ; <code>InteractiveFlag.none</code> était une contrainte d'aperçu.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="callout">
|
||
<h3>Le piège à ne pas déplacer</h3>
|
||
<p>
|
||
Les coordonnées de <code>MapAnnotationDTO.geometry.coordinates</code> sont en
|
||
<strong><code>[lng, lat]</code></strong> — GeoJSON. Celles de <code>GuidedStep</code> sont en
|
||
<code>[lat, lng]</code>. Les deux ordres sont en production, et une confusion
|
||
<strong>ne lève aucune erreur</strong> : elle déplace les points sur une carte qui reste
|
||
plausible. La lecture de mymuseum (<code>coords[1]</code> = lat, <code>coords[0]</code> = lng)
|
||
se recopie telle quelle, sans passer par un helper venu d'un autre contexte. Le volet parcours
|
||
étant exclu de la borne, cet écran ne voit qu'une seule des deux conventions — c'est une raison
|
||
de plus de ne pas y introduire l'autre.
|
||
</p>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ── QUESTIONS ──────────────────────────────────── -->
|
||
|
||
<section>
|
||
<h2>Tranché le 12 août</h2>
|
||
|
||
<div class="rules">
|
||
|
||
<div class="ruleitem">
|
||
<h3>Fond clair</h3>
|
||
<p>
|
||
La nouvelle section ne doit pas dénoter dans l'app. <code>tablet-app</code> est clair —
|
||
<code>kBackgroundColor</code> vaut <code>#FFFFFF</code> et <code>kBackgroundLight</code>
|
||
<code>#F3F3F3</code> — et les maquettes ci-dessus emploient <strong>ces valeurs-là</strong>,
|
||
pas une palette inventée. Le <code>#111111</code> en dur de l'écran Événement de mymuseum ne
|
||
se recopie donc pas : il vient d'un téléphone consulté en extérieur.
|
||
<span class="before">mymuseum — Scaffold backgroundColor: Color(0xFF111111)</span>
|
||
</p>
|
||
</div>
|
||
|
||
<div class="ruleitem">
|
||
<h3>Hors dates, aucune prose</h3>
|
||
<p>
|
||
Le carnaval dure trois jours, la borne tourne les 362 autres. Le programme reste affiché,
|
||
<strong>sans pastille « EN COURS »</strong> et avec les blocs en gris — le surlignage est
|
||
calculé sur <code>DateTime.now()</code>, il s'éteint tout seul. La bande héros continue de
|
||
porter les dates, qui sont <strong>numériques donc sans traduction</strong>, et suffisent à
|
||
dire qu'on est avant ou après.
|
||
<span class="before">état passé = les mêmes blocs, désaturés — pas un écran différent</span>
|
||
</p>
|
||
</div>
|
||
|
||
<div class="ruleitem">
|
||
<h3>L'i18n existe — corrigé le 12/08</h3>
|
||
<p>
|
||
⛔ <strong>Une affirmation de cette page était fausse et a été retirée</strong> :
|
||
« <code>tablet-app</code> n'a aucun i18n ». Il en a un —
|
||
<code>lib/Helpers/translations.dart</code>, une table de <strong>10 langues</strong>
|
||
(FR, EN, NL, DE, IT, ES, PL, CN, AR, UK) lue par
|
||
<code>TranslationHelper.getFromLocale</code> à <strong>19 endroits</strong>, avec déjà une
|
||
clé <code>back</code>. Ce n'est pas de l'ARB, c'est une table à la main — le même pattern que
|
||
mymuseum. Chercher <code>l10n/</code> et <code>AppLocalizations</code> ne le trouve pas.
|
||
</p>
|
||
<p style="margin-top: var(--s2);">
|
||
Ce que ça change : un libellé traduit coûte <strong>une clé × 10 langues</strong>, pas un
|
||
chantier. La règle « aucune prose » ci-dessus reste défendable — les dates suffisent, et
|
||
rien à traduire vaut mieux que quelque chose à traduire — mais elle devient un
|
||
<strong>choix</strong> et non une contrainte. <strong>À confirmer.</strong> Et le bouton
|
||
« Retour » des maquettes doit passer par la clé <code>back</code> existante, pas être écrit
|
||
en dur.
|
||
</p>
|
||
</div>
|
||
|
||
<div class="ruleitem">
|
||
<h3>Retour à l'accueil après 5 min</h3>
|
||
<p>
|
||
Adopté, et <strong>écrit une seule fois pour les treize types</strong> :
|
||
<code>section_page_detail</code> enveloppe déjà toutes les sections et porte déjà les crochets
|
||
<code>initState</code> / <code>dispose</code> des statistiques. Le minuteur d'inactivité va
|
||
là, pas dans les deux écrans de cette page. Bénéfice de bord : un retour automatique passe
|
||
par le même <code>dispose</code>, donc il émet un <code>sectionLeave</code> avec sa durée
|
||
réelle — la statistique reste juste au lieu de compter une consultation ouverte jusqu'au
|
||
prochain visiteur.
|
||
<span class="before">hors périmètre K3/K7 — à ouvrir comme chantier propre</span>
|
||
</p>
|
||
</div>
|
||
|
||
</div>
|
||
</section>
|
||
|
||
<footer>
|
||
<span>DOCS/claude design/kiosk-paysage-article-event.html</span>
|
||
<span>source de vérité — l'artifact ne sert qu'à la diffusion (DB0)</span>
|
||
</footer>
|
||
|
||
</div>
|