Files
librodrome/app/composables/useTypewriter.ts
Yvv 97ba6dd04c 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>
2026-03-03 03:49:07 +01:00

109 lines
2.4 KiB
TypeScript

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,
}
}