Redesign accueil : grille 3 axes, hero fade/swipe, pages gestation et décision
- Hero : animation fade-in/fade-out + swipe (useTypewriter composable + TypewriterText) - 3 axes : Autonomie numérique, économique, citoyenne (AxisBlock + AxisGrid) - Pages gestation avec présentations (wishBounty, trustWallet, Cloud libre) - Page /decision : plateforme Décision collective (lien Glibredecision) - Bloc événement distinct en bas des axes - Nav : Numérique / Économique / Citoyenne / Événement - Dark theme éclairci (bg 7→10%, surface 12→14%) - Suppression BookSection + GrateWizardTeaser (remplacés par AxisGrid) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
108
app/composables/useTypewriter.ts
Normal file
108
app/composables/useTypewriter.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
export interface TypewriterSentence {
|
||||
text: string
|
||||
style?: 'title' | 'citation' | 'text'
|
||||
stays?: boolean
|
||||
separator?: boolean
|
||||
}
|
||||
|
||||
interface SequenceOptions {
|
||||
holdMs?: number
|
||||
gapMs?: number
|
||||
}
|
||||
|
||||
export function useTypewriter(sentences: TypewriterSentence[], options: SequenceOptions = {}) {
|
||||
const {
|
||||
holdMs = 2400,
|
||||
gapMs = 300,
|
||||
} = options
|
||||
|
||||
const currentIndex = ref(-1)
|
||||
const showActive = ref(false)
|
||||
const lockedSentences = ref<TypewriterSentence[]>([])
|
||||
const isComplete = ref(false)
|
||||
|
||||
let holdTimer: ReturnType<typeof setTimeout> | null = null
|
||||
|
||||
const currentSentence = computed(() =>
|
||||
currentIndex.value >= 0 && currentIndex.value < sentences.length
|
||||
? sentences[currentIndex.value]
|
||||
: null,
|
||||
)
|
||||
|
||||
function clearTimer() {
|
||||
if (holdTimer) {
|
||||
clearTimeout(holdTimer)
|
||||
holdTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
function showNext() {
|
||||
const nextIdx = currentIndex.value + 1
|
||||
if (nextIdx >= sentences.length) {
|
||||
isComplete.value = true
|
||||
return
|
||||
}
|
||||
|
||||
currentIndex.value = nextIdx
|
||||
const sentence = sentences[nextIdx]
|
||||
|
||||
if (sentence.separator) {
|
||||
lockedSentences.value = [...lockedSentences.value, { text: '', separator: true }]
|
||||
}
|
||||
|
||||
showActive.value = true
|
||||
}
|
||||
|
||||
/** Called by component via @after-enter on Transition */
|
||||
function onEntered() {
|
||||
holdTimer = setTimeout(() => {
|
||||
showActive.value = false
|
||||
}, holdMs)
|
||||
}
|
||||
|
||||
/** Called by component via @after-leave on Transition */
|
||||
function onLeft() {
|
||||
const sentence = sentences[currentIndex.value]
|
||||
if (sentence?.stays) {
|
||||
lockedSentences.value = [...lockedSentences.value, { ...sentence }]
|
||||
}
|
||||
setTimeout(showNext, gapMs)
|
||||
}
|
||||
|
||||
function start() {
|
||||
showNext()
|
||||
}
|
||||
|
||||
function skipToEnd() {
|
||||
clearTimer()
|
||||
showActive.value = false
|
||||
|
||||
const locked: TypewriterSentence[] = []
|
||||
for (const sentence of sentences) {
|
||||
if (sentence.separator) {
|
||||
locked.push({ text: '', separator: true })
|
||||
}
|
||||
if (sentence.stays) {
|
||||
locked.push({ ...sentence })
|
||||
}
|
||||
}
|
||||
|
||||
lockedSentences.value = locked
|
||||
currentIndex.value = sentences.length - 1
|
||||
isComplete.value = true
|
||||
}
|
||||
|
||||
onUnmounted(clearTimer)
|
||||
|
||||
return {
|
||||
currentIndex: readonly(currentIndex),
|
||||
currentSentence,
|
||||
showActive,
|
||||
lockedSentences: readonly(lockedSentences),
|
||||
isComplete: readonly(isComplete),
|
||||
onEntered,
|
||||
onLeft,
|
||||
start,
|
||||
skipToEnd,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user