feat(fx): extract clock + reveal + stagger from v7-5 (line 1567-1624)
3 SSR-safe utilities in one module: - fxClock() — live Thai Buddhist calendar (id=fx-time, id=fx-date) - fxReveal() — add .revealed to .fx-reveal on scroll (with 2 failsafes) - fxStagger() — add .staggered to .fx-stagger on scroll (with 2 failsafes) - fxInit() — convenience to run all 3 Typed with HTMLElement generics. Uses standard IntersectionObserver. Namespaced .fx-* classes — no collision with legacy src/lib/animations.ts (which uses .reveal / counterUp for bento components). Refs: .hermes/plans/2026-06-13_124000-moreminimore-v7-5-migration.md Task 1.3
This commit is contained in:
148
src/lib/fx-animations.ts
Normal file
148
src/lib/fx-animations.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* MOREMINIMORE FX-ANIMATIONS v1
|
||||
* Extracted from Desktop/moreminomore-mockup-v7-5.html bottom script (lines 1567-1624)
|
||||
*
|
||||
* 3 utilities — all SSR-safe (no-op on server):
|
||||
* 1. fxClock() — live Thai clock + Buddhist date for utility bar
|
||||
* 2. fxReveal() — add .revealed to .fx-reveal elements on scroll
|
||||
* 3. fxStagger() — add .staggered to .fx-stagger elements on scroll
|
||||
*
|
||||
* Note: these are SEPARATE from src/lib/animations.ts (which uses .reveal class).
|
||||
* v7-5 design system uses .fx-reveal / .fx-stagger to avoid collision with the
|
||||
* legacy bento animations.
|
||||
*
|
||||
* Usage in Base.astro or any .astro file:
|
||||
* <script>
|
||||
* import { fxInit } from '../lib/fx-animations';
|
||||
* if (document.readyState === 'loading') {
|
||||
* document.addEventListener('DOMContentLoaded', fxInit);
|
||||
* } else {
|
||||
* fxInit();
|
||||
* }
|
||||
* </script>
|
||||
*/
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* 1. LIVE CLOCK (utility bar) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/** Days in Thai (Buddhist calendar: Sun=0, Sat=6) */
|
||||
const THAI_DAYS = ['อา.', 'จ.', 'อ.', 'พ.', 'พฤ.', 'ศ.', 'ส.'];
|
||||
/** Months in Thai */
|
||||
const THAI_MONTHS = ['ม.ค.', 'ก.พ.', 'มี.ค.', 'เม.ย.', 'พ.ค.', 'มิ.ย.', 'ก.ค.', 'ส.ค.', 'ก.ย.', 'ต.ค.', 'พ.ย.', 'ธ.ค.'];
|
||||
|
||||
/**
|
||||
* Start the live clock. Looks for elements with id="fx-time" and id="fx-date".
|
||||
* Sets text every second.
|
||||
*/
|
||||
export function fxClock(): void {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
function update(): void {
|
||||
const now = new Date();
|
||||
const hh = String(now.getHours()).padStart(2, '0');
|
||||
const mm = String(now.getMinutes()).padStart(2, '0');
|
||||
const ss = String(now.getSeconds()).padStart(2, '0');
|
||||
const time = '⏱ ' + hh + ':' + mm + ':' + ss;
|
||||
const date = '📅 ' + THAI_DAYS[now.getDay()] + ' ' + now.getDate() + ' ' + THAI_MONTHS[now.getMonth()] + ' ' + (now.getFullYear() + 543);
|
||||
const t = document.getElementById('fx-time');
|
||||
const d = document.getElementById('fx-date');
|
||||
if (t) t.textContent = time;
|
||||
if (d) d.textContent = date;
|
||||
}
|
||||
|
||||
update();
|
||||
setInterval(update, 1000);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* 2. REVEAL-ON-SCROLL (.fx-reveal) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Add .revealed to .fx-reveal elements as they enter viewport.
|
||||
* Includes 2 failsafes:
|
||||
* - Force-reveal all after 1.5s (in case observer never fires)
|
||||
* - Force-reveal all after 100ms (in case section is already in view)
|
||||
*/
|
||||
export function fxReveal(): void {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
const reveals = document.querySelectorAll<HTMLElement>('.fx-reveal');
|
||||
if (reveals.length === 0) return;
|
||||
|
||||
if ('IntersectionObserver' in window) {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('revealed');
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
},
|
||||
{ threshold: 0.05, rootMargin: '0px 0px -40px 0px' }
|
||||
);
|
||||
reveals.forEach((r) => observer.observe(r));
|
||||
}
|
||||
|
||||
// Failsafe 1: force-reveal all after 1.5s
|
||||
setTimeout(() => {
|
||||
reveals.forEach((r) => r.classList.add('revealed'));
|
||||
}, 1500);
|
||||
|
||||
// Failsafe 2: trigger immediately for visible sections (100ms)
|
||||
setTimeout(() => {
|
||||
reveals.forEach((r) => r.classList.add('revealed'));
|
||||
}, 100);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* 3. STAGGER-ON-SCROLL (.fx-stagger) */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/**
|
||||
* Add .staggered to .fx-stagger elements as they enter viewport.
|
||||
* Same failsafe pattern as fxReveal.
|
||||
*/
|
||||
export function fxStagger(): void {
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
const staggers = document.querySelectorAll<HTMLElement>('.fx-stagger');
|
||||
if (staggers.length === 0) return;
|
||||
|
||||
if ('IntersectionObserver' in window) {
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.classList.add('staggered');
|
||||
observer.unobserve(entry.target);
|
||||
}
|
||||
});
|
||||
},
|
||||
{ threshold: 0.05, rootMargin: '0px 0px -40px 0px' }
|
||||
);
|
||||
staggers.forEach((s) => observer.observe(s));
|
||||
}
|
||||
|
||||
// Failsafes
|
||||
setTimeout(() => {
|
||||
staggers.forEach((s) => s.classList.add('staggered'));
|
||||
}, 1500);
|
||||
|
||||
setTimeout(() => {
|
||||
staggers.forEach((s) => s.classList.add('staggered'));
|
||||
}, 100);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* CONVENIENCE: init all 3 in one call */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
/** Run all fx animations. Safe to call multiple times (idempotent via querySelector). */
|
||||
export function fxInit(): void {
|
||||
fxClock();
|
||||
fxReveal();
|
||||
fxStagger();
|
||||
}
|
||||
Reference in New Issue
Block a user