From 02c2b5d38e9d81db601d940c04a3e741c416678e Mon Sep 17 00:00:00 2001 From: "m::r" Date: Thu, 15 Jan 2026 22:39:40 +0000 Subject: [PATCH 1/2] feat(pause): and styling --- src/App.js | 17 ------- src/components/flags/FlagsApi.js | 78 ++++++++++++++++++++------------ src/components/flags/styles.css | 67 +++++++++++++++++++++++++-- 3 files changed, 113 insertions(+), 49 deletions(-) diff --git a/src/App.js b/src/App.js index 7937817..83064bd 100644 --- a/src/App.js +++ b/src/App.js @@ -24,25 +24,8 @@ import Home from "./components/home/Home"; import FlagsApi from "./components/flags/FlagsApi"; import Profile from "./components/flags/Profile"; -// -// const store = storeCreator(); -// -// -// - const store = createStore(appReducers); -// function addTodo(text) { -// return { -// type: 'ADD_TODO', -// text -// } -// } - -// store.dispatch(addTodo('Read the docs')) -// store.dispatch(addTodo('Read about the middleware')) - - store.dispatch({type: 'initial'}); // store.dispatch('add'); function App() { diff --git a/src/components/flags/FlagsApi.js b/src/components/flags/FlagsApi.js index 9c9c7a7..0e48f48 100644 --- a/src/components/flags/FlagsApi.js +++ b/src/components/flags/FlagsApi.js @@ -24,7 +24,8 @@ class FlagsApi extends React.Component { answerLocked = false; state = { - loading: true + loading: true, + paused: false }; async handleClick(action) { @@ -119,7 +120,7 @@ class FlagsApi extends React.Component { async startGame() { this.gameEnded = false; this.answerLocked = false; - this.setState({ loading: true }); + this.setState({ loading: true, paused: false }); this.stopTimer(); await this.handleClick('api') .then(() => this.startTimer()) @@ -167,6 +168,35 @@ class FlagsApi extends React.Component { this.array = []; console.log('Timer stop'); } + + togglePause = () => { + if (this.props.lifes <= 0) return; + + if (this.state.paused) { + this.resumeGame(); + } else { + this.pauseGame(); + } + } + + pauseGame = () => { + this.stopTimer(); + this.setState({ paused: true }); + } + + resumeGame = () => { + this.setState({ paused: false }); + this.startTimerFromCurrent(); + } + + startTimerFromCurrent = () => { + let interval = setInterval(() => { + this.props.dispatch({type: 'tick'}); + this.tickTimer(); + }, 1000); + + this.array.push(interval); + } tickTimer() { if (this.props.timer == 15) { @@ -174,7 +204,6 @@ class FlagsApi extends React.Component { } else { if (this.props.timer == 0) { this.timeout(); - } } } @@ -222,25 +251,9 @@ class FlagsApi extends React.Component { answers = []; question = []; prepareStat() { - // console.log('FOO'); - // console.log(this.props); - // console.log(this.props.flags); this.props.flagi.map((item) => - // () => alert() this.question.push(item) - // console.log(item) - // (item) => { alert(); console.log('xaxa' + item)} ); - - - // this.array.map(item => clearInterval(item)); - - - // console.log(); - // for (let item of this.props.flagi) { - // this.question.push(item.getAllKeys()[0]) - // } - // console.log(this.question); } saveAnswer(correct) { @@ -275,20 +288,17 @@ class FlagsApi extends React.Component {
Select the flag of Time: {this.props.timer} -

{this.props.ques}

- +
{ this.props.flags.map(item => ( @@ -302,7 +312,12 @@ class FlagsApi extends React.Component { ) ) } - + {this.state.paused && ( +
+ PAUSED - Click to Resume +
+ )} +
GAME OVER! Your score: {this.props.counter} @@ -335,13 +350,20 @@ class FlagsApi extends React.Component { {/**/} -
+
  + +
) diff --git a/src/components/flags/styles.css b/src/components/flags/styles.css index 6ec01ad..1ad9254 100644 --- a/src/components/flags/styles.css +++ b/src/components/flags/styles.css @@ -13,16 +13,37 @@ h1 { } .flag { - font-size: 80px; + font-size: 64px; border: 3px dotted transparent; display: inline-block; cursor: pointer; - transition: transform 0.2s; + transition: transform 0.2s ease, filter 0.3s; max-width: 100%; + transform: scale(1); +} + +.flags-container > span { + display: inline-block; +} + +.flags-container > span:hover .flag { + transform: scale(1.25) !important; +} + +@media (max-width: 400px) { + .flag { + font-size: 56px; + } +} + +@media (max-width: 350px) { + .flag { + font-size: 48px; + } } .flag:hover { - transform: scale(1.05); + transform: scale(1.25); } /* Don't show correct answer by default - showCorrect() will set border dynamically */ @@ -36,9 +57,18 @@ h1 { /* Game area toast - responsive width */ .game-toast { - width: 90vw; + width: 100%; max-width: 500px; min-height: 300px; + margin: 0 auto; +} + +@media (max-width: 520px) { + .game-toast { + max-width: 100%; + width: 100%; + border-radius: 0; + } } @media (min-width: 576px) { @@ -135,3 +165,32 @@ td:nth-of-type(2){ font-weight: bold; animation: shake 0.4s ease-out; } + +/* Pause functionality */ +.flags-container { + position: relative; + display: flex; + justify-content: space-between; +} + +.flags-container.paused .flag { + filter: blur(15px); + pointer-events: none; + user-select: none; +} + +.pause-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + display: flex; + align-items: center; + justify-content: center; + background: rgba(255, 255, 255, 0.7); + font-size: 1.2em; + font-weight: bold; + color: #6c757d; + cursor: pointer; +} From 4cc1f3eb348fa53bbb732bf6e80fd393b31aaee2 Mon Sep 17 00:00:00 2001 From: "m::r" Date: Thu, 15 Jan 2026 22:58:06 +0000 Subject: [PATCH 2/2] feat(main-screen): landing animation --- src/components/home/Home.js | 141 +++++++++++++++++++------- src/components/home/styles.css | 180 +++++++++++++++++++++++++++++++++ 2 files changed, 287 insertions(+), 34 deletions(-) diff --git a/src/components/home/Home.js b/src/components/home/Home.js index 29d8039..f687f4b 100644 --- a/src/components/home/Home.js +++ b/src/components/home/Home.js @@ -18,6 +18,17 @@ const Home = () => { const { login: oauthLogin, isLoading } = useOAuth(); const history = useHistory(); + // Animation states + const [animPhase, setAnimPhase] = useState(0); + // Phase 0: blank + // Phase 1: title visible + // Phase 2: description visible + // Phase 3: table slides in (blurred) + // Phase 4: "High Scores" bounces in + // Phase 5: rows unblur from bottom (except top) + // Phase 6: top scorer revealed + buttons visible + const [revealedRows, setRevealedRows] = useState([]); + const isTokenValid = () => { const token = localStorage.getItem('accessToken'); if (!token) return false; @@ -57,6 +68,47 @@ const Home = () => { }); }, []); + // Animation sequence + useEffect(() => { + if (loading) return; + + const timers = []; + + // Phase 1: Title appears (after 300ms) + timers.push(setTimeout(() => setAnimPhase(1), 300)); + + // Phase 2: Description appears (after 900ms) + timers.push(setTimeout(() => setAnimPhase(2), 900)); + + // Phase 3: Table slides in blurred (after 1600ms) + timers.push(setTimeout(() => setAnimPhase(3), 1600)); + + // Phase 4: "High Scores" bounces in (after 2400ms) + timers.push(setTimeout(() => setAnimPhase(4), 2400)); + + // Phase 5: Rows unblur from bottom one by one (after 3600ms) + timers.push(setTimeout(() => { + setAnimPhase(5); + // Reveal rows from bottom to top (except index 0) + const rowCount = Math.min(leaderboard.length, 10); + for (let i = rowCount - 1; i >= 1; i--) { + const delay = (rowCount - 1 - i) * 200; + timers.push(setTimeout(() => { + setRevealedRows(prev => [...prev, i]); + }, delay)); + } + }, 3600)); + + // Phase 6: Top scorer + buttons (after all rows revealed + pause) + const totalRowDelay = Math.max(0, leaderboard.length - 1) * 200; + timers.push(setTimeout(() => { + setRevealedRows(prev => [...prev, 0]); + setAnimPhase(6); + }, 3600 + totalRowDelay + 1500)); + + return () => timers.forEach(t => clearTimeout(t)); + }, [loading, leaderboard.length]); + const handlePlay = () => { history.push('/flagsapi'); }; @@ -78,7 +130,7 @@ const Home = () => { }; return ( -
+
{/* Logout Confirmation Popup */} {showLogoutPopup && (
@@ -107,7 +159,7 @@ const Home = () => {
- {isLoggedIn && ( + {isLoggedIn && animPhase >= 6 && ( )} -

🏁 Flags Quiz

-

Test your geography knowledge and compete for the top spot!

- {isLoggedIn ? ( - <> +

= 1 ? 'anim-title-visible' : 'anim-hidden'}> + 🏁 Flags Quiz +

+

= 2 ? 'anim-desc-visible' : 'anim-hidden'}> + Test your geography knowledge and compete for the top spot! +

+
= 6 ? 'anim-buttons-visible' : 'anim-hidden'}> + {isLoggedIn ? ( + <> + + {' '} + + + ) : ( - {' '} - - - ) : ( - - )} + )} +
@@ -160,14 +218,24 @@ const Home = () => { ) : leaderboard.length === 0 ? (

No scores yet. Be the first to play!

) : ( - +
= 3 ? 'anim-table-visible' : 'anim-table-hidden'}`} + style={{ width: '100%' }} + > - - + = 4 ? 'anim-fade-in' : 'anim-hidden'}> @@ -176,7 +244,7 @@ const Home = () => { - + = 3 ? '' : 'anim-hidden'}> {leaderboard.map((player, index) => { const rank = index + 1; let medal = null; @@ -184,8 +252,13 @@ const Home = () => { else if (rank === 2) medal = 🥈 ; else if (rank === 3) medal = 🥉 ; + const isRevealed = revealedRows.includes(index); + const rowClass = animPhase >= 5 + ? (isRevealed ? 'anim-row-revealed' : 'anim-row-blurred') + : 'anim-row-blurred'; + return ( - + diff --git a/src/components/home/styles.css b/src/components/home/styles.css index 022d36a..cc83984 100644 --- a/src/components/home/styles.css +++ b/src/components/home/styles.css @@ -3,10 +3,20 @@ display: flex; flex-direction: column; min-height: 100vh; + overflow-x: hidden; +} + +.page-wrapper.animating { + overflow: hidden; +} + +body:has(.page-wrapper.animating) { + overflow: hidden; } .main-content { flex: 1; + overflow: hidden; } /* Close/Logout button */ @@ -249,3 +259,173 @@ text-align: center; } } + +/* ===== PAGE LOAD ANIMATIONS ===== */ + +/* Hidden state for all animated elements */ +.anim-hidden { + opacity: 0; + visibility: hidden; +} + +/* Title animation - fade in and scale */ +@keyframes titleAppear { + 0% { + opacity: 0; + transform: scale(0.8); + } + 100% { + opacity: 1; + transform: scale(1); + } +} + +.anim-title-visible { + animation: titleAppear 0.6s ease-out forwards; +} + +/* Description fade in */ +@keyframes descFadeIn { + 0% { + opacity: 0; + transform: translateY(-10px); + } + 100% { + opacity: 1; + transform: translateY(0); + } +} + +.anim-desc-visible { + animation: descFadeIn 0.5s ease-out forwards; +} + +/* Buttons appear */ +@keyframes buttonsAppear { + 0% { + opacity: 0; + transform: scale(0.9); + } + 50% { + transform: scale(1.05); + } + 100% { + opacity: 1; + transform: scale(1); + } +} + +.anim-buttons-visible { + animation: buttonsAppear 0.5s ease-out forwards; +} + +/* Table slide up from below (blurred) */ +@keyframes tableSlideUp { + 0% { + opacity: 0; + transform: translateY(100px); + } + 100% { + opacity: 1; + transform: translateY(0); + } +} + +.anim-table-hidden { + opacity: 0; + transform: translateY(100px); +} + +.anim-table-visible { + animation: tableSlideUp 0.8s ease-out forwards; +} + +/* Hide scrollbars on table during animation */ +.table-responsive { + overflow: hidden !important; +} + +.animating .leaderboard-table, +.animating .table-responsive { + overflow: hidden !important; + scrollbar-width: none; + -ms-overflow-style: none; +} + +.animating .leaderboard-table::-webkit-scrollbar, +.animating .table-responsive::-webkit-scrollbar { + display: none; +} + +/* High Scores bouncing text */ +@keyframes highScoresBounce { + 0% { + opacity: 0; + transform: translateX(100%); + } + 25% { + opacity: 1; + transform: translateX(-10%); + } + 45% { + transform: translateX(8%); + } + 65% { + transform: translateX(-4%); + } + 80% { + transform: translateX(2%); + } + 100% { + transform: translateX(0); + } +} + +.anim-highscores-bounce { + animation: highScoresBounce 1.2s ease-out forwards; +} + +/* Simple fade in */ +@keyframes fadeIn { + 0% { + opacity: 0; + } + 100% { + opacity: 1; + } +} + +.anim-fade-in { + animation: fadeIn 0.4s ease-out forwards; +} + +/* Row states - blurred and revealed */ +.anim-row-blurred td { + filter: blur(8px); + transition: filter 0.4s ease-out; +} + +.anim-row-revealed td { + filter: blur(0); + transition: filter 0.4s ease-out; +} + +/* Top scorer special reveal */ +@keyframes topScorerReveal { + 0% { + filter: blur(8px); + transform: scale(1); + } + 50% { + filter: blur(0); + transform: scale(1.02); + } + 100% { + filter: blur(0); + transform: scale(1); + } +} + +.leaderboard-table tbody tr:first-child.anim-row-revealed td { + animation: topScorerReveal 0.6s ease-out forwards; +}
+ = 4 ? 'anim-highscores-bounce' : 'anim-hidden'}`} + style={{ fontSize: '1.5rem', padding: '1rem' }} + > High Scores
Rank Player Top ScoreTime Total
{medal}{rank} {player.firstName} {player.highScore}