feat(ui): implement timed haiku rotation with staggered line reveal and group fade-out

- Added haiku display block under board with Framer Motion animations
- Implement sequential line-by-line fade-in (staggered 2.4s per line)
- Implement full-haiku fade-out using AnimatePresence keyed by haikuIndex
- Added timed rotation logic using total animation duration (~14.4s)
- Integrated getHaiku() random selector for new haiku each cycle
- Ensured smooth transitions by updating haikuIndex on cycle end
- Added no-winner condition wrapper to show haikus during gameplay
This commit is contained in:
2025-11-29 03:42:33 +05:30
parent 0e22d1cd53
commit c4b44e872a
2 changed files with 160 additions and 1 deletions

View File

@@ -1,6 +1,7 @@
import React from "react";
import React, { useEffect, useState } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { useNakama } from "./providers/NakamaProvider";
import getHaiku from "./utils/haikus";
interface BoardProps {
board: string[][];
@@ -47,6 +48,21 @@ export default function Board({
status = isMyTurn ? "Your turn" : "Opponent's turn";
}
const [haiku, setHaiku] = useState(getHaiku());
const [haikuIndex, setHaikuIndex] = useState(0);
useEffect(() => {
const totalTime = 3 * 2400 + 6000 + 2400;
const timer = setTimeout(() => {
const next = getHaiku();
setHaiku(next);
setHaikuIndex((i) => i + 1);
}, totalTime);
return () => clearTimeout(timer);
}, [haikuIndex]);
return (
<>
{matchId && (
@@ -154,6 +170,58 @@ export default function Board({
)}
</motion.div>
{!winner && (
<div
style={{
minHeight: "90px",
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
marginTop: "14px",
position: "relative",
}}
>
<AnimatePresence mode="wait">
<motion.div
key={haikuIndex}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{
duration: 2.4,
ease: "easeInOut",
}}
style={{
textAlign: "center",
lineHeight: "1.35",
}}
>
{haiku.map((line, i) => (
<motion.div
key={i}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{
delay: i * 2.4, // line-by-line stagger timing
duration: 10.0,
ease: "easeOut",
}}
style={{
fontSize: "18px",
color: "#f1c40f",
fontWeight: 700,
whiteSpace: "nowrap",
}}
>
{line}
</motion.div>
))}
</motion.div>
</AnimatePresence>
</div>
)}
{/* Winner pulse animation */}
{winner && (
<motion.div

View File

@@ -0,0 +1,91 @@
export const HAIKUS: string[][] = [
// HAIKU STORY SET 1 — The Only Winning Move Is No Move
[
"Silence fills the board.",
"Two minds waiting, both flawless.",
"No move is the win.",
],
[
"Perfect strategies",
"Cancel out in quiet draws.",
"Stillness holds the key.",
],
[
"Victory fades out,",
"When both players see the truth:",
"Equilibrium.",
],
// // HAIKU STORY SET 2 — AI & Game Theory
// [
// "Grids bend under thought.",
// "Algorithms watch patterns.",
// "The future decides.",
// ],
// [
// "Zeroes read the board.",
// "Minimax breathes in the dark.",
// "Loss is calculated.",
// ],
// [
// "Two perfect AIs",
// "Stare across a tiny world.",
// "Neither one can win.",
// ],
//
// // HAIKU STORY SET 3 — Players Becoming Machines
// [
// "Hands learn old rhythms.",
// "Humans imitate the code.",
// "We evolve to think.",
// ],
// [
// "Soft neon whispers,",
// "The grid calls for your next move.",
// "Time waits for no one.",
// ],
// [
// "Your choices echo.",
// "Small decisions shape the board.",
// "You shape the story.",
// ],
//
// // HAIKU STORY SET 4 — Solving the Game
// [
// "Every path explored,",
// "Every outcome known too well.",
// "Beauty in the bones.",
// ],
// [
// "Corners dream of acts,",
// "Center knows its destiny.",
// "Balance is the law.",
// ],
// [
// "Three lines cross in fate.",
// "Nine spaces hold nine futures.",
// "All end in a draw.",
// ],
//
// // HAIKU STORY SET 5 — Existential Tic-Tac-Toe
// [
// "The board is a mirror.",
// "It reflects your quiet mind.",
// "Win by understanding.",
// ],
// [
// "Nothing left to prove.",
// "The shape of thought is perfect.",
// "The game simply is.",
// ],
// [
// "A small universe,",
// "Filled with silent decisions.",
// "Meaning in the moves.",
// ],
];
export default function getHaiku() {
const idx = Math.floor(Math.random() * HAIKUS.length);
return HAIKUS[idx];
}