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:
@@ -1,6 +1,7 @@
|
|||||||
import React from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { motion, AnimatePresence } from "framer-motion";
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
import { useNakama } from "./providers/NakamaProvider";
|
import { useNakama } from "./providers/NakamaProvider";
|
||||||
|
import getHaiku from "./utils/haikus";
|
||||||
|
|
||||||
interface BoardProps {
|
interface BoardProps {
|
||||||
board: string[][];
|
board: string[][];
|
||||||
@@ -47,6 +48,21 @@ export default function Board({
|
|||||||
status = isMyTurn ? "Your turn" : "Opponent's turn";
|
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 (
|
return (
|
||||||
<>
|
<>
|
||||||
{matchId && (
|
{matchId && (
|
||||||
@@ -154,6 +170,58 @@ export default function Board({
|
|||||||
)}
|
)}
|
||||||
</motion.div>
|
</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 pulse animation */}
|
||||||
{winner && (
|
{winner && (
|
||||||
<motion.div
|
<motion.div
|
||||||
|
|||||||
91
src/tictactoe/utils/haikus.ts
Normal file
91
src/tictactoe/utils/haikus.ts
Normal 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];
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user