diff --git a/src/components/SectionList.tsx b/src/components/SectionList.tsx index fb857d1..ac8c6a0 100644 --- a/src/components/SectionList.tsx +++ b/src/components/SectionList.tsx @@ -40,8 +40,8 @@ export default function SectionList({ return (
- {/* Header image — 22vh */} -
+ {/* Header image — covers the image band + the search row below it */} +
{configImageSource ? ( ) : ( @@ -53,63 +53,61 @@ export default function SectionList({ {/* Back button */} -
- {/* Search row */} -
-
- - - - { setSearch(e.target.value); setSearchNumber('') }} - placeholder="Rechercher..." - className="flex-1 bg-transparent text-sm outline-none" - style={{ color: 'var(--color-text)' }} - /> - {search && ( - - )} -
-
- { setSearchNumber(e.target.value); setSearch('') }} - placeholder="#" - className="w-full bg-transparent text-sm outline-none text-center" - style={{ color: 'var(--color-text)' }} - /> + {/* Search row — same spot as before, image visible through translucent fields. + marginBottom leaves room for the list below to overlap the image, not this row. */} +
+
+ + + + { setSearch(e.target.value); setSearchNumber('') }} + placeholder="Rechercher..." + className="flex-1 bg-transparent text-sm outline-none" + style={{ color: 'var(--color-text)' }} + /> + {search && ( + + )} +
+
+ { setSearchNumber(e.target.value); setSearch('') }} + placeholder="#" + className="w-full bg-transparent text-sm outline-none text-center" + style={{ color: 'var(--color-text)' }} + /> +
- {/* List */} + {/* List — overlaps the image slightly so its rounded corners reveal the image, not the page background */}
{/* Hero */} @@ -280,17 +278,6 @@ export default function EventSection({ section, slug, configId, languages }: Pro )} - {/* À propos */} - {description && ( -
-
-
- )} -
{/* Block detail bottom sheet */} diff --git a/src/components/sections/ParcoursSection.tsx b/src/components/sections/ParcoursSection.tsx index cbe2f71..69bc6e5 100644 --- a/src/components/sections/ParcoursSection.tsx +++ b/src/components/sections/ParcoursSection.tsx @@ -55,7 +55,6 @@ export default function ParcoursSection({ section, languages }: Props) { }, []) const sectionTitle = tPlain(section.title, language) - const sectionDesc = t(section.description, language) const steps: GuidedStepDTO[] = activePath ? [...(activePath.steps ?? [])].sort((a, b) => (a.order ?? 0) - (b.order ?? 0)) @@ -212,13 +211,6 @@ export default function ParcoursSection({ section, languages }: Props) {

{sectionTitle}

- {sectionDesc && ( -

- )}

@@ -954,12 +946,12 @@ function MiniToast({ message, isGame, onDismiss }: { message: string; isGame: bo useEffect(() => { const id = setTimeout(onDismiss, 2500) return () => clearTimeout(id) - }, [message, onDismiss]) + }, [message]) return (
@@ -2001,49 +1993,90 @@ function StepQuiz({ questions, puzzleQuestions, simpleQuestions, answered, puzzl )} {kind === 'simple' && ( - <> - onSimpleAnswer(current.id, e.target.value)} - disabled={checked} - placeholder="Votre réponse…" - style={{ - width: '100%', padding: '13px 14px', borderRadius: 14, fontSize: 15, - border: `1.5px solid ${hasAnswer ? (simpleIsCorrect ? '#2E9E6B' : '#D14343') : (isGame ? 'rgba(255,255,255,0.1)' : '#E2DCD2')}`, - background: hasAnswer - ? (simpleIsCorrect ? 'rgba(46,158,107,0.14)' : 'rgba(209,67,67,0.12)') - : (isGame ? 'rgba(255,255,255,0.04)' : '#F9F6F1'), - color: isGame ? '#F4ECD8' : '#1E2A33', - }} - /> - {!checked && ( - + )} + {hasAnswer && !simpleIsCorrect && ( + revealImmediately ? ( +

+ Le bon code était : +

+ ) : ( +

+ Ce n'est pas le bon code, réessayez. +

+ ) + )} + + ) : ( + <> + onSimpleAnswer(current.id, e.target.value)} + disabled={checked} + placeholder="Votre réponse…" style={{ - width: '100%', border: 'none', marginTop: 8, borderRadius: 14, padding: '13px 14px', - fontSize: 15, fontWeight: 700, - cursor: (simpleAnswered[current.id] ?? '').trim().length === 0 ? 'not-allowed' : 'pointer', - opacity: (simpleAnswered[current.id] ?? '').trim().length === 0 ? 0.5 : 1, - background: accentColor, color: isGame ? '#1a1305' : '#fff', + width: '100%', padding: '13px 14px', borderRadius: 14, fontSize: 15, + border: `1.5px solid ${hasAnswer ? (simpleIsCorrect ? '#2E9E6B' : '#D14343') : (isGame ? 'rgba(255,255,255,0.1)' : '#E2DCD2')}`, + background: hasAnswer + ? (simpleIsCorrect ? 'rgba(46,158,107,0.14)' : 'rgba(209,67,67,0.12)') + : (isGame ? 'rgba(255,255,255,0.04)' : '#F9F6F1'), + color: isGame ? '#F4ECD8' : '#1E2A33', }} - > - Valider cette réponse - - )} - {hasAnswer && !simpleIsCorrect && ( - revealImmediately ? ( -

- La bonne réponse était : -

- ) : ( -

- Ce n'est pas la bonne réponse, réessayez. -

- ) - )} - + /> + {!checked && ( + + )} + {hasAnswer && !simpleIsCorrect && ( + revealImmediately ? ( +

+ La bonne réponse était : +

+ ) : ( +

+ Ce n'est pas la bonne réponse, réessayez. +

+ ) + )} + + ) )}
@@ -2189,6 +2222,7 @@ function StepCarousel({ contents, stepIndexKey, isGame, accentColor, language }: language: string }) { const [imgIndex, setImgIndex] = useState(0) + const [lightboxOpen, setLightboxOpen] = useState(false) useEffect(() => { setImgIndex(0) }, [stepIndexKey]) @@ -2201,18 +2235,23 @@ function StepCarousel({ contents, stepIndexKey, isGame, accentColor, language }: {tPlain(current.title, setLightboxOpen(true)} + style={{ width: '100%', height: '100%', objectFit: 'cover', cursor: 'zoom-in' }} /> )} -
+ {lightboxOpen && current?.resource?.url && ( + setLightboxOpen(false)} /> + )} +
{/* Counter badge */}
- 🖼 {imgIndex + 1} / {contents.length} + {imgIndex + 1} / {contents.length}
{/* Arrows */} @@ -2265,6 +2304,74 @@ function StepCarousel({ contents, stepIndexKey, isGame, accentColor, language }: ) } +function ContentLightbox({ content, isGame, language, onClose }: { + content: ContentDTO + isGame: boolean + language: string + onClose: () => void +}) { + const title = tPlain(content.title, language) + const description = t(content.description, language) + + return ( +
+
e.stopPropagation()} + style={{ + width: '100%', maxWidth: 480, maxHeight: '90vh', overflowY: 'auto', + borderRadius: 20, + background: isGame ? '#0B1018' : '#fff', + border: isGame ? '1px solid rgba(212,175,55,0.3)' : 'none', + boxShadow: '0 20px 50px rgba(0,0,0,0.4)', + }} + > +
+ {/* eslint-disable-next-line @next/next/no-img-element */} + {title} + +
+ {(title || description) && ( +
+ {title && ( +

+ {title} +

+ )} + {description && ( +
+ )} +
+ )} +
+
+ ) +} + // ── Waveform decorative ──────────────────────────────────────────────────────── function WaveformDecor({ currentTime, duration, accentColor, onSeek }: { @@ -2370,6 +2477,77 @@ function stripHtml(html: string): string { return html.replace(/<[^>]*>/g, ' ').replace(/ /g, ' ').replace(/\s+/g, ' ').trim() } +// Une réponse "Simple" purement numérique s'affiche comme un digicode (pavé +// de boutons) plutôt qu'un champ de texte libre. +function isDigicodeAnswer(expectedHtml: string): boolean { + const s = stripHtml(expectedHtml) + return s.length > 0 && /^[0-9]+$/.test(s) +} + +function DigicodePad({ value, length, disabled, accentColor, isGame, hasAnswer, isCorrect, onChange }: { + value: string + length: number + disabled: boolean + accentColor: string + isGame: boolean + hasAnswer: boolean + isCorrect: boolean + onChange: (next: string) => void +}) { + const ink = isGame ? '#F4ECD8' : '#1E2A33' + const boxBorder = hasAnswer + ? (isCorrect ? '#2E9E6B' : '#D14343') + : (isGame ? 'rgba(255,255,255,0.15)' : '#E2DCD2') + const boxBg = hasAnswer + ? (isCorrect ? 'rgba(46,158,107,0.14)' : 'rgba(209,67,67,0.12)') + : (isGame ? 'rgba(255,255,255,0.04)' : '#F9F6F1') + const keyBg = isGame ? 'rgba(255,255,255,0.06)' : '#F0EBE3' + + const press = (d: string) => { + if (disabled) return + if (d === 'back') { onChange(value.slice(0, -1)); return } + if (value.length >= length) return + onChange(value + d) + } + + return ( +
+
+ {Array.from({ length }).map((_, i) => ( +
+ {value[i] ?? ''} +
+ ))} +
+
+ {['1', '2', '3', '4', '5', '6', '7', '8', '9'].map((d) => ( + + ))} + + +
+
+
+ ) +} + function normalizeAnswer(text: string): string { const withoutAccents = stripHtml(text).toLowerCase().normalize('NFD') .split('').filter((ch) => { @@ -2440,6 +2618,16 @@ function ClockIcon({ color = 'currentColor', size = 14 }: { color?: string; size ) } +function ImageIcon({ color = 'currentColor', size = 14 }: { color?: string; size?: number }) { + return ( + + + + + + ) +} + function LockIcon({ color = 'currentColor', size = 28 }: { color?: string; size?: number }) { return (