Skip to content

Commit

Permalink
feat: newyear counter
Browse files Browse the repository at this point in the history
  • Loading branch information
jbj338033 committed Dec 31, 2024
1 parent 4ffdeac commit e867701
Show file tree
Hide file tree
Showing 3 changed files with 266 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import AIDetail from "./components/ai/AIDetail";
import MathLayout from "./components/math/MathLayout";
import MathList from "./components/math/MathList";
import MathDetail from "./components/math/MathDetail";
import NewYearCounter from "./pages/NewYear";

function App() {
const theme = useThemeStore((state) => state.theme);
Expand Down Expand Up @@ -69,6 +70,8 @@ function App() {
element={<Navigate to="/math/polynomial" replace />}
/>
</Route>

<Route path="/newyear" element={<NewYearCounter />} />
</Routes>
</BrowserRouter>
</ThemeProvider>
Expand Down
109 changes: 109 additions & 0 deletions src/pages/NewYear/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { useState, useEffect } from "react";
import {
CounterContainer,
Title,
CountdownContainer,
TimeUnit,
Number,
Label,
Message,
} from "./style";

interface TimeLeft {
days: number;
hours: number;
minutes: number;
seconds: number;
}

const NewYear = () => {
const [timeLeft, setTimeLeft] = useState<TimeLeft>({
days: 0,
hours: 0,
minutes: 0,
seconds: 0,
});

useEffect(() => {
const calculateTimeLeft = () => {
const now = new Date();
const nextYear = now.getFullYear() + 1;
const nextNewYear = new Date(nextYear, 0, 1);
const difference = nextNewYear.getTime() - now.getTime();

if (difference > 0) {
setTimeLeft({
days: Math.floor(difference / (1000 * 60 * 60 * 24)),
hours: Math.floor((difference / (1000 * 60 * 60)) % 24),
minutes: Math.floor((difference / 1000 / 60) % 60),
seconds: Math.floor((difference / 1000) % 60),
});
}
};

const timer = setInterval(calculateTimeLeft, 1000);
calculateTimeLeft();

return () => clearInterval(timer);
}, []);

const containerVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
duration: 1,
staggerChildren: 0.2,
},
},
};

const itemVariants = {
hidden: { y: 20, opacity: 0 },
visible: {
y: 0,
opacity: 1,
},
};

const isNewYear =
timeLeft.days === 0 &&
timeLeft.hours === 0 &&
timeLeft.minutes === 0 &&
timeLeft.seconds === 0;

return (
<CounterContainer
initial="hidden"
animate="visible"
variants={containerVariants}
>
<Title variants={itemVariants}>새해까지 남은 시간</Title>
<CountdownContainer>
<TimeUnit variants={itemVariants}>
<Number>{timeLeft.days.toString().padStart(2, "0")}</Number>
<Label></Label>
</TimeUnit>
<TimeUnit variants={itemVariants}>
<Number>{timeLeft.hours.toString().padStart(2, "0")}</Number>
<Label>시간</Label>
</TimeUnit>
<TimeUnit variants={itemVariants}>
<Number>{timeLeft.minutes.toString().padStart(2, "0")}</Number>
<Label></Label>
</TimeUnit>
<TimeUnit variants={itemVariants}>
<Number>{timeLeft.seconds.toString().padStart(2, "0")}</Number>
<Label></Label>
</TimeUnit>
</CountdownContainer>
<Message variants={itemVariants}>
{isNewYear
? "새해 복 많이 받으세요! 🎊"
: "희망찬 새해를 기다리며... ✨"}
</Message>
</CounterContainer>
);
};

export default NewYear;
154 changes: 154 additions & 0 deletions src/pages/NewYear/style.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import styled from "@emotion/styled";
import { motion } from "framer-motion";

export const CounterContainer = styled(motion.div)`
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: #0a0a0f;
padding: 2rem;
font-family: "Pretendard", sans-serif;
position: relative;
overflow: hidden;
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: radial-gradient(
circle at 50% 50%,
rgba(51, 51, 255, 0.03) 0%,
rgba(0, 0, 0, 0) 70%
);
}
`;

export const Title = styled(motion.h1)`
color: #fff;
font-size: clamp(2.5rem, 6vw, 4rem);
text-align: center;
margin-bottom: 4rem;
font-weight: 800;
letter-spacing: -0.03em;
text-shadow: 0 0 7px rgba(255, 255, 255, 0.6),
0 0 10px rgba(255, 255, 255, 0.4), 0 0 21px rgba(255, 255, 255, 0.2),
0 0 42px rgba(51, 51, 255, 0.4), 0 0 82px rgba(51, 51, 255, 0.2),
0 0 92px rgba(51, 51, 255, 0.1);
position: relative;
z-index: 1;
`;

export const CountdownContainer = styled(motion.div)`
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: clamp(1rem, 3vw, 1.5rem);
width: 100%;
max-width: 800px;
padding: 0.5rem;
margin: 0 auto;
position: relative;
z-index: 1;
`;

export const TimeUnit = styled(motion.div)`
background: rgba(10, 10, 15, 0.8);
padding: clamp(1.5rem, 4vw, 2.5rem);
border-radius: 12px;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
border: 1px solid rgba(51, 51, 255, 0.2);
box-shadow: 0 0 5px rgba(51, 51, 255, 0.2),
inset 0 0 15px rgba(51, 51, 255, 0.1);
&::after {
content: "";
position: absolute;
inset: 0;
border-radius: 12px;
padding: 1px;
background: linear-gradient(
135deg,
rgba(51, 51, 255, 0.5),
rgba(51, 51, 255, 0)
);
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-webkit-mask: linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
-webkit-mask-composite: destination-out;
mask-composite: exclude;
}
`;

export const Number = styled(motion.span)`
font-size: clamp(3rem, 8vw, 4rem);
font-weight: 700;
color: #fff;
font-feature-settings: "tnum";
font-variant-numeric: tabular-nums;
text-shadow: 0 0 7px rgba(255, 255, 255, 0.6),
0 0 10px rgba(255, 255, 255, 0.4), 0 0 21px rgba(51, 51, 255, 0.4),
0 0 42px rgba(51, 51, 255, 0.2);
position: relative;
z-index: 1;
transition: text-shadow 0.3s ease;
${TimeUnit}:hover & {
text-shadow: 0 0 7px rgba(255, 255, 255, 0.8),
0 0 10px rgba(255, 255, 255, 0.6), 0 0 21px rgba(51, 51, 255, 0.6),
0 0 42px rgba(51, 51, 255, 0.4), 0 0 82px rgba(51, 51, 255, 0.2);
}
`;

export const Label = styled.span`
font-size: clamp(0.9rem, 2vw, 1.1rem);
color: rgba(255, 255, 255, 0.7);
margin-top: 1rem;
font-weight: 500;
letter-spacing: 0.1em;
text-transform: uppercase;
position: relative;
z-index: 1;
text-shadow: 0 0 10px rgba(51, 51, 255, 0.3);
`;

export const Message = styled(motion.div)`
font-size: clamp(1.2rem, 3vw, 1.5rem);
margin-top: 4rem;
color: #fff;
font-weight: 600;
text-align: center;
line-height: 1.5;
position: relative;
padding: 0.8em 2em;
text-shadow: 0 0 7px rgba(255, 255, 255, 0.4),
0 0 10px rgba(255, 255, 255, 0.2), 0 0 21px rgba(51, 51, 255, 0.2);
border: 1px solid rgba(51, 51, 255, 0.3);
border-radius: 100px;
box-shadow: 0 0 15px rgba(51, 51, 255, 0.1);
&::before {
content: "";
position: absolute;
inset: 0;
border-radius: 100px;
padding: 1px;
background: linear-gradient(
90deg,
rgba(51, 51, 255, 0),
rgba(51, 51, 255, 0.5),
rgba(51, 51, 255, 0)
);
mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
-webkit-mask: linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
-webkit-mask-composite: destination-out;
mask-composite: exclude;
}
`;

0 comments on commit e867701

Please sign in to comment.