diff --git a/src/lib/fx-animations.ts b/src/lib/fx-animations.ts
new file mode 100644
index 0000000..aab8618
--- /dev/null
+++ b/src/lib/fx-animations.ts
@@ -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:
+ *
+ */
+
+/* ------------------------------------------------------------------ */
+/* 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('.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('.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();
+}