lottery page

This commit is contained in:
Kakabay 2025-01-03 17:45:58 +05:00
parent fb6090d9fd
commit e46670628f
6 changed files with 125 additions and 68 deletions

View File

@ -93,8 +93,7 @@ html {
} }
.text-stroke { .text-stroke {
text-shadow: -1px -1px 0 white, 1px -1px 0 white, -1px 1px 0 white, text-shadow: -1px -1px 0 white, 1px -1px 0 white, -1px 1px 0 white, 1px 1px 0 white;
1px 1px 0 white;
} }
.big-swiper .swiper-pagination-bullet { .big-swiper .swiper-pagination-bullet {
@ -122,20 +121,20 @@ html {
.small-swiper .swiper-button-next:after { .small-swiper .swiper-button-next:after {
color: white; color: white;
content: url("/arrow-right-small.svg"); content: url('/arrow-right-small.svg');
} }
.small-swiper .swiper-button-prev:after { .small-swiper .swiper-button-prev:after {
color: white; color: white;
content: url("/arrow-left-small.svg"); content: url('/arrow-left-small.svg');
} }
.big-swiper .swiper-button-next:after { .big-swiper .swiper-button-next:after {
color: white; color: white;
content: url("/arrow-right-big.svg"); content: url('/arrow-right-big.svg');
} }
.big-swiper .swiper-button-prev:after { .big-swiper .swiper-button-prev:after {
color: white; color: white;
content: url("/arrow-left-big.svg"); content: url('/arrow-left-big.svg');
} }
video { video {
@ -188,15 +187,15 @@ big {
@apply bg-[#E6E6FA] rounded-xl py-3 px-4 placeholder:text-[#BCBCD6] outline-none; @apply bg-[#E6E6FA] rounded-xl py-3 px-4 placeholder:text-[#BCBCD6] outline-none;
} }
.calendar [aria-label="Go to next month"] { .calendar [aria-label='Go to next month'] {
@apply shadow-sm transition-all; @apply shadow-sm transition-all;
} }
.calendar [aria-label="Go to previous month"] { .calendar [aria-label='Go to previous month'] {
@apply shadow-sm transition-all; @apply shadow-sm transition-all;
} }
.day-styles [name="day"] { .day-styles [name='day'] {
@apply p-4 text-textDarkt leading-[140%] bg-purple-600 rounded-full; @apply p-4 text-textDarkt leading-[140%] bg-purple-600 rounded-full;
} }
@ -262,7 +261,7 @@ big {
} }
.wheel-circle { .wheel-circle {
background: url("/wheel-circle.svg"); background: url('/wheel-circle.svg');
background-position: center; background-position: center;
background-size: cover; background-size: cover;
background-repeat: no-repeat; background-repeat: no-repeat;
@ -296,7 +295,7 @@ big {
} */ } */
.slot-seperator { .slot-seperator {
content: url("/dash.svg"); content: url('/dash.svg');
background-position: center; background-position: center;
background-size: cover; background-size: cover;
background-repeat: no-repeat; background-repeat: no-repeat;
@ -315,7 +314,7 @@ big {
@media (max-width: 1024px) { @media (max-width: 1024px) {
.slot-seperator { .slot-seperator {
content: url("/dash-md.svg"); content: url('/dash-md.svg');
@apply mx-2; @apply mx-2;
} }
} }
@ -340,7 +339,7 @@ big {
@media (max-width: 426px) { @media (max-width: 426px) {
.slot-seperator { .slot-seperator {
content: url("/dash-sm.svg"); content: url('/dash-sm.svg');
@apply mx-2; @apply mx-2;
} }
} }
@ -353,14 +352,27 @@ big {
box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.2); /* Add depth */ box-shadow: 0px 4px 10px rgba(0, 0, 0, 0.2); /* Add depth */
} }
.span.flash {
animation: flash 1s infinite;
}
@keyframes dots-flash {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0;
}
}
@keyframes confetti-move { @keyframes confetti-move {
0% { 0% {
opacity: 1; opacity: 1;
transform: translateX(0) translateY(0) rotate(0deg); transform: translateX(0) translateY(0) rotate(0deg);
} }
50% { 50% {
transform: translateX(calc(var(--end-x) / 2)) translateY(-30vh) transform: translateX(calc(var(--end-x) / 2)) translateY(-30vh) rotate(180deg);
rotate(180deg);
} }
100% { 100% {
opacity: 0; opacity: 0;

View File

@ -1,3 +1,4 @@
import { cn } from '@/lib/utils';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
interface AnimatedTextProps { interface AnimatedTextProps {
@ -37,7 +38,9 @@ const AnimatedText = ({
delay: i * wordDelay, delay: i * wordDelay,
ease: 'easeOut', ease: 'easeOut',
}} }}
className={`inline-block mx-2 ${wordClassName}`}> className={cn(`inline-block mx-2`, wordClassName, {
'animate-dotsFlash': text === '...',
})}>
{word} {word}
</motion.span> </motion.span>
))} ))}

View File

@ -25,6 +25,9 @@ const LotteryWinnersSection = ({ lotteryStatus }: { lotteryStatus: string }) =>
const { width, height } = useWindowSize(); const { width, height } = useWindowSize();
const { lotteryData } = useLotteryAuth(); const { lotteryData } = useLotteryAuth();
const [isSlotCounterAnimating, setIsSlotCounterAnimating] = useState(false); const [isSlotCounterAnimating, setIsSlotCounterAnimating] = useState(false);
const [winnerSelectingStatus, setWinnerSelectingStatus] = useState<
'not-selected' | 'is-selecting' | 'selected'
>('not-selected');
const [pendingWinner, setPendingWinner] = useState<LotteryWinnerDataSimplified | null>(null); const [pendingWinner, setPendingWinner] = useState<LotteryWinnerDataSimplified | null>(null);
// Refs // Refs
@ -34,6 +37,7 @@ const LotteryWinnersSection = ({ lotteryStatus }: { lotteryStatus: string }) =>
// Add new state for display text // Add new state for display text
const [displayText, setDisplayText] = useState<string>('...'); const [displayText, setDisplayText] = useState<string>('...');
const [winnerText, setWinnerText] = useState<string>('');
// Initialize winners from lottery data // Initialize winners from lottery data
useEffect(() => { useEffect(() => {
@ -85,7 +89,7 @@ const LotteryWinnersSection = ({ lotteryStatus }: { lotteryStatus: string }) =>
setDisplayText(`${winnerData.winner_no}-nji ýeňiji saýlanýar`); setDisplayText(`${winnerData.winner_no}-nji ýeňiji saýlanýar`);
// Start the sequence // Start the sequence
setIsSlotCounterAnimating(true); setWinnerSelectingStatus('is-selecting');
setPendingWinner(winnerData); setPendingWinner(winnerData);
setCurrentNumber(winnerData.ticket); setCurrentNumber(winnerData.ticket);
@ -93,7 +97,9 @@ const LotteryWinnersSection = ({ lotteryStatus }: { lotteryStatus: string }) =>
await new Promise((resolve) => setTimeout(resolve, SLOT_COUNTER_DURATION)); await new Promise((resolve) => setTimeout(resolve, SLOT_COUNTER_DURATION));
// Update text to show winner's phone // Update text to show winner's phone
setDisplayText(winnerData.client); setDisplayText('The winner is');
setWinnerText(winnerData.client);
setWinnerSelectingStatus('selected');
setIsConfettiActive(true); setIsConfettiActive(true);
setWinners((prev) => [...prev, winnerData]); setWinners((prev) => [...prev, winnerData]);
@ -102,9 +108,11 @@ const LotteryWinnersSection = ({ lotteryStatus }: { lotteryStatus: string }) =>
setTimeout(() => { setTimeout(() => {
if (mountedRef.current) { if (mountedRef.current) {
setIsConfettiActive(false); setIsConfettiActive(false);
setIsSlotCounterAnimating(false); // setIsSlotCounterAnimating(false);
setWinnerSelectingStatus('not-selected');
setPendingWinner(null); setPendingWinner(null);
setDisplayText('...'); // Reset text setDisplayText('...'); // Reset text
setWinnerText('');
} }
}, 5000); }, 5000);
} catch (error) { } catch (error) {
@ -157,7 +165,6 @@ const LotteryWinnersSection = ({ lotteryStatus }: { lotteryStatus: string }) =>
Connection error. Please refresh the page. Connection error. Please refresh the page.
</div> </div>
)} )}
{isConfettiActive && ( {isConfettiActive && (
<div className="fixed top-0 left-0 z-50"> <div className="fixed top-0 left-0 z-50">
<ReactConfetti <ReactConfetti
@ -181,16 +188,32 @@ const LotteryWinnersSection = ({ lotteryStatus }: { lotteryStatus: string }) =>
<div className="container"> <div className="container">
<div <div
className="flex flex-col items-center rounded-[32px] pt-[40px]" className="flex flex-col items-center rounded-[32px] gap-[40px] pt-[40px]"
style={{ background: 'linear-gradient(180deg, #F0ECF4 0%, #E1E0FF 43.5%)' }}> style={{ background: 'linear-gradient(180deg, #F0ECF4 0%, #E1E0FF 43.5%)' }}>
<AnimatedText {winnerSelectingStatus === 'not-selected' || winnerSelectingStatus === 'is-selecting' ? (
text={displayText} <AnimatedText
className="text-center text-[100px] leading-[108px] text-[#E65E19]" text={displayText}
/> className="text-center flex items-center justify-center text-[100px] leading-[108px] text-[#E65E19]"
<div className="translate-y-1/2 z-10"> />
) : (
<div className="flex flex-col items-center justify-center">
<AnimatedText
text={displayText}
className="text-center text-[56px] leading-[64px] text-[#E65E19]"
/>
{winnerText && (
<AnimatedText
text={winnerText}
className="text-center text-[80px] leading-[88px] text-[#E65E19]"
/>
)}
</div>
)}
<div className="z-10">
<LotterySlotCounter numberString={currentNumber} isAnimating={isSlotCounterAnimating} /> <LotterySlotCounter numberString={currentNumber} isAnimating={isSlotCounterAnimating} />
</div> </div>
<div className="flex gap-6 rounded-[12px] flex-1 w-full items-center justify-center md:pt-[122px] sm:pt-[90px] pt-[40px] sm:pb-[62px] pb-[32px] px-4"> <div className="flex gap-6 rounded-[12px] flex-1 w-full items-center justify-center sm:pb-[62px] pb-[32px] px-4">
<LotteryWinnersList winners={winners} /> <LotteryWinnersList winners={winners} />
</div> </div>
</div> </div>

View File

@ -77,13 +77,14 @@ const LotterySlotCounter = ({ numberString, isAnimating }: LotterySlotCounterPro
<SlotCounter <SlotCounter
value={formattedNumber} value={formattedNumber}
startValue={formattedNumber} startValue={formattedNumber}
startValueOnce
charClassName="rolling-number" charClassName="rolling-number"
separatorClassName="slot-seperator" separatorClassName="slot-seperator"
duration={2} duration={2}
speed={2} speed={2}
startFromLastDigit
delay={2} delay={2}
animateUnchanged={false} startFromLastDigit
animateOnVisible={false}
autoAnimationStart={false} autoAnimationStart={false}
/> />
</div> </div>

View File

@ -204,6 +204,7 @@ export const theme = {
animation: { animation: {
buble: 'buble 7s infinite', buble: 'buble 7s infinite',
dotsFlash: 'dotsFlash 1.5s infinite',
}, },
keyframes: { keyframes: {
@ -221,6 +222,17 @@ export const theme = {
transform: 'scale(1) translate(0%, 0%)', transform: 'scale(1) translate(0%, 0%)',
}, },
}, },
dotsFlash: {
'0%': {
opacity: '1',
},
'50%': {
opacity: '0',
},
'100%': {
opacity: '1',
},
},
}, },
}, },
}; };

View File

@ -1,80 +1,86 @@
import type { Config } from "tailwindcss" import type { Config } from 'tailwindcss';
const config = { const config = {
darkMode: ["class"], darkMode: ['class'],
content: [ content: [
'./pages/**/*.{ts,tsx}', './pages/**/*.{ts,tsx}',
'./components/**/*.{ts,tsx}', './components/**/*.{ts,tsx}',
'./app/**/*.{ts,tsx}', './app/**/*.{ts,tsx}',
'./src/**/*.{ts,tsx}', './src/**/*.{ts,tsx}',
], ],
prefix: "", prefix: '',
theme: { theme: {
container: { container: {
center: true, center: true,
padding: "2rem", padding: '2rem',
screens: { screens: {
"2xl": "1400px", '2xl': '1400px',
}, },
}, },
extend: { extend: {
colors: { colors: {
border: "hsl(var(--border))", border: 'hsl(var(--border))',
input: "hsl(var(--input))", input: 'hsl(var(--input))',
ring: "hsl(var(--ring))", ring: 'hsl(var(--ring))',
background: "hsl(var(--background))", background: 'hsl(var(--background))',
foreground: "hsl(var(--foreground))", foreground: 'hsl(var(--foreground))',
primary: { primary: {
DEFAULT: "hsl(var(--primary))", DEFAULT: 'hsl(var(--primary))',
foreground: "hsl(var(--primary-foreground))", foreground: 'hsl(var(--primary-foreground))',
}, },
secondary: { secondary: {
DEFAULT: "hsl(var(--secondary))", DEFAULT: 'hsl(var(--secondary))',
foreground: "hsl(var(--secondary-foreground))", foreground: 'hsl(var(--secondary-foreground))',
}, },
destructive: { destructive: {
DEFAULT: "hsl(var(--destructive))", DEFAULT: 'hsl(var(--destructive))',
foreground: "hsl(var(--destructive-foreground))", foreground: 'hsl(var(--destructive-foreground))',
}, },
muted: { muted: {
DEFAULT: "hsl(var(--muted))", DEFAULT: 'hsl(var(--muted))',
foreground: "hsl(var(--muted-foreground))", foreground: 'hsl(var(--muted-foreground))',
}, },
accent: { accent: {
DEFAULT: "hsl(var(--accent))", DEFAULT: 'hsl(var(--accent))',
foreground: "hsl(var(--accent-foreground))", foreground: 'hsl(var(--accent-foreground))',
}, },
popover: { popover: {
DEFAULT: "hsl(var(--popover))", DEFAULT: 'hsl(var(--popover))',
foreground: "hsl(var(--popover-foreground))", foreground: 'hsl(var(--popover-foreground))',
}, },
card: { card: {
DEFAULT: "hsl(var(--card))", DEFAULT: 'hsl(var(--card))',
foreground: "hsl(var(--card-foreground))", foreground: 'hsl(var(--card-foreground))',
}, },
}, },
borderRadius: { borderRadius: {
lg: "var(--radius)", lg: 'var(--radius)',
md: "calc(var(--radius) - 2px)", md: 'calc(var(--radius) - 2px)',
sm: "calc(var(--radius) - 4px)", sm: 'calc(var(--radius) - 4px)',
}, },
keyframes: { keyframes: {
"accordion-down": { 'accordion-down': {
from: { height: "0" }, from: { height: '0' },
to: { height: "var(--radix-accordion-content-height)" }, to: { height: 'var(--radix-accordion-content-height)' },
}, },
"accordion-up": { 'accordion-up': {
from: { height: "var(--radix-accordion-content-height)" }, from: { height: 'var(--radix-accordion-content-height)' },
to: { height: "0" }, to: { height: '0' },
},
'dots-flash': {
from: { opacity: '0' },
to: { opacity: '1' },
}, },
}, },
animation: { animation: {
"accordion-down": "accordion-down 0.2s ease-out", 'accordion-down': 'accordion-down 0.2s ease-out',
"accordion-up": "accordion-up 0.2s ease-out", 'accordion-up': 'accordion-up 0.2s ease-out',
'dots-flash': 'dots-flash 1s infinite;',
}, },
}, },
}, },
plugins: [require("tailwindcss-animate")], plugins: [require('tailwindcss-animate')],
} satisfies Config } satisfies Config;
export default config export default config;