feat: enhance memory quiz with dynamic columns and adaptive transitions
- Replace hardcoded 3-column limit with logarithmic calculation based on actual quiz numbers - Add adaptive transition timing that scales with display speed (faster flash for faster speeds) - Implement persistent abacus display with smooth bead animations between cards - Replace TypstSoroban with AbacusReact for consistent component usage - Add subtle background flash animation for visual feedback on card transitions 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -3,9 +3,11 @@
|
||||
import Link from 'next/link'
|
||||
import React, { useEffect, useReducer, useRef, useCallback, useMemo, useState } from 'react'
|
||||
import { css } from '../../../../styled-system/css'
|
||||
import { TypstSoroban } from '../../../components/TypstSoroban'
|
||||
import { AbacusReact } from '@soroban/abacus-react'
|
||||
import { useAbacusConfig } from '../../../contexts/AbacusDisplayContext'
|
||||
import { isPrefix } from '../../../lib/memory-quiz-utils'
|
||||
|
||||
|
||||
interface QuizCard {
|
||||
number: number
|
||||
svgComponent: JSX.Element
|
||||
@@ -161,7 +163,7 @@ const DIFFICULTY_LEVELS = {
|
||||
type DifficultyLevel = keyof typeof DIFFICULTY_LEVELS
|
||||
|
||||
// Generate quiz cards with difficulty-based number ranges
|
||||
const generateQuizCards = (count: number, difficulty: DifficultyLevel): QuizCard[] => {
|
||||
const generateQuizCards = (count: number, difficulty: DifficultyLevel, appConfig: any): QuizCard[] => {
|
||||
const { min, max } = DIFFICULTY_LEVELS[difficulty].range
|
||||
|
||||
// Generate unique numbers - no duplicates allowed
|
||||
@@ -188,11 +190,16 @@ const generateQuizCards = (count: number, difficulty: DifficultyLevel): QuizCard
|
||||
|
||||
return numbers.map(number => ({
|
||||
number,
|
||||
svgComponent: <TypstSoroban
|
||||
number={number}
|
||||
width="600pt"
|
||||
height="500pt"
|
||||
transparent={true}
|
||||
svgComponent: <AbacusReact
|
||||
value={number}
|
||||
columns="auto"
|
||||
beadShape={appConfig.beadShape}
|
||||
colorScheme={appConfig.colorScheme}
|
||||
hideInactiveBeads={appConfig.hideInactiveBeads}
|
||||
scaleFactor={1.0}
|
||||
interactive={false}
|
||||
showNumbers={false}
|
||||
animated={false}
|
||||
/>,
|
||||
element: null
|
||||
}))
|
||||
@@ -200,6 +207,8 @@ const generateQuizCards = (count: number, difficulty: DifficultyLevel): QuizCard
|
||||
|
||||
// React component for the setup phase
|
||||
function SetupPhase({ state, dispatch }: { state: SorobanQuizState; dispatch: React.Dispatch<QuizAction> }) {
|
||||
const appConfig = useAbacusConfig()
|
||||
|
||||
const handleCountSelect = (count: number) => {
|
||||
dispatch({ type: 'SET_SELECTED_COUNT', count })
|
||||
}
|
||||
@@ -213,7 +222,7 @@ function SetupPhase({ state, dispatch }: { state: SorobanQuizState; dispatch: Re
|
||||
}
|
||||
|
||||
const handleStartQuiz = () => {
|
||||
const quizCards = generateQuizCards(state.selectedCount, state.selectedDifficulty)
|
||||
const quizCards = generateQuizCards(state.selectedCount, state.selectedDifficulty, appConfig)
|
||||
dispatch({ type: 'START_QUIZ', quizCards })
|
||||
}
|
||||
|
||||
@@ -355,12 +364,33 @@ function SetupPhase({ state, dispatch }: { state: SorobanQuizState; dispatch: Re
|
||||
)
|
||||
}
|
||||
|
||||
// Calculate maximum columns needed for a set of numbers
|
||||
function calculateMaxColumns(numbers: number[]): number {
|
||||
if (numbers.length === 0) return 1
|
||||
const maxNumber = Math.max(...numbers)
|
||||
if (maxNumber === 0) return 1
|
||||
return Math.floor(Math.log10(maxNumber)) + 1
|
||||
}
|
||||
|
||||
// React component for the display phase
|
||||
function DisplayPhase({ state, dispatch }: { state: SorobanQuizState; dispatch: React.Dispatch<QuizAction> }) {
|
||||
const [countdown, setCountdown] = useState<string>('')
|
||||
const [showCard, setShowCard] = useState(false)
|
||||
const [currentCard, setCurrentCard] = useState<QuizCard | null>(null)
|
||||
const [isTransitioning, setIsTransitioning] = useState(false)
|
||||
const isDisplayPhaseActive = state.currentCardIndex < state.quizCards.length
|
||||
const isProcessingRef = useRef(false)
|
||||
const appConfig = useAbacusConfig()
|
||||
|
||||
// Calculate maximum columns needed for this quiz set
|
||||
const maxColumns = useMemo(() => {
|
||||
const allNumbers = state.quizCards.map(card => card.number)
|
||||
return calculateMaxColumns(allNumbers)
|
||||
}, [state.quizCards])
|
||||
|
||||
// Calculate adaptive animation duration
|
||||
const flashDuration = useMemo(() => {
|
||||
const displayTimeMs = state.displayTime * 1000
|
||||
return Math.min(Math.max(displayTimeMs * 0.3, 150), 600) / 1000 // Convert to seconds for CSS
|
||||
}, [state.displayTime])
|
||||
|
||||
const progressPercentage = (state.currentCardIndex / state.quizCards.length) * 100
|
||||
|
||||
@@ -379,30 +409,27 @@ function DisplayPhase({ state, dispatch }: { state: SorobanQuizState; dispatch:
|
||||
isProcessingRef.current = true
|
||||
const card = state.quizCards[state.currentCardIndex]
|
||||
console.log(`DisplayPhase: Showing card ${state.currentCardIndex + 1}/${state.quizCards.length}, number: ${card.number}`)
|
||||
|
||||
// Calculate adaptive timing based on display speed
|
||||
const displayTimeMs = state.displayTime * 1000
|
||||
const flashDuration = Math.min(Math.max(displayTimeMs * 0.3, 150), 600) // 30% of display time, between 150ms-600ms
|
||||
const transitionPause = Math.min(Math.max(displayTimeMs * 0.1, 50), 200) // 10% of display time, between 50ms-200ms
|
||||
|
||||
// Trigger adaptive transition effect
|
||||
setIsTransitioning(true)
|
||||
setCurrentCard(card)
|
||||
|
||||
// Show countdown for first card only
|
||||
if (state.currentCardIndex === 0) {
|
||||
const counts = ['3', '2', '1', 'GO!']
|
||||
for (let i = 0; i < counts.length; i++) {
|
||||
setCountdown(counts[i])
|
||||
await new Promise(resolve => setTimeout(resolve, 400))
|
||||
}
|
||||
} else {
|
||||
setCountdown('Next')
|
||||
await new Promise(resolve => setTimeout(resolve, 150))
|
||||
}
|
||||
// Reset transition effect with adaptive duration
|
||||
setTimeout(() => setIsTransitioning(false), flashDuration)
|
||||
|
||||
setCountdown('')
|
||||
setShowCard(true)
|
||||
console.log(`DisplayPhase: Card ${state.currentCardIndex + 1} now visible`)
|
||||
console.log(`DisplayPhase: Card ${state.currentCardIndex + 1} now visible (flash: ${flashDuration}ms, pause: ${transitionPause}ms)`)
|
||||
|
||||
// Display card for specified time
|
||||
await new Promise(resolve => setTimeout(resolve, state.displayTime * 1000 - 300))
|
||||
// Display card for specified time with adaptive transition pause
|
||||
await new Promise(resolve => setTimeout(resolve, displayTimeMs - transitionPause))
|
||||
|
||||
setShowCard(false)
|
||||
console.log(`DisplayPhase: Card ${state.currentCardIndex + 1} hidden, advancing to next`)
|
||||
await new Promise(resolve => setTimeout(resolve, 100))
|
||||
// Don't hide the abacus - just advance to next card for smooth transition
|
||||
console.log(`DisplayPhase: Card ${state.currentCardIndex + 1} transitioning to next`)
|
||||
await new Promise(resolve => setTimeout(resolve, transitionPause)) // Adaptive pause for visual transition
|
||||
|
||||
isProcessingRef.current = false
|
||||
dispatch({ type: 'NEXT_CARD' })
|
||||
@@ -412,15 +439,20 @@ function DisplayPhase({ state, dispatch }: { state: SorobanQuizState; dispatch:
|
||||
}, [state.currentCardIndex, state.displayTime, state.quizCards.length, dispatch])
|
||||
|
||||
return (
|
||||
<div className={css({
|
||||
textAlign: 'center',
|
||||
padding: '20px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
boxSizing: 'border-box',
|
||||
height: '100%'
|
||||
})}>
|
||||
<div
|
||||
className={css({
|
||||
textAlign: 'center',
|
||||
padding: '20px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
boxSizing: 'border-box',
|
||||
height: '100%'
|
||||
})}
|
||||
style={{
|
||||
animation: isTransitioning ? `subtlePageFlash ${flashDuration}s ease-out` : undefined
|
||||
}}
|
||||
>
|
||||
<div className={css({
|
||||
position: 'relative',
|
||||
width: '100%',
|
||||
@@ -478,43 +510,43 @@ function DisplayPhase({ state, dispatch }: { state: SorobanQuizState; dispatch:
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{countdown && (
|
||||
<div className={css({
|
||||
fontSize: '3rem',
|
||||
fontWeight: 'bold',
|
||||
color: countdown === 'GO!' ? 'green.500' : 'blue.500',
|
||||
margin: '20px 0',
|
||||
textShadow: '2px 2px 4px rgba(0,0,0,0.1)',
|
||||
animation: countdown === 'GO!' ? 'pulse 0.3s ease' : undefined
|
||||
})}>
|
||||
{countdown}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{showCard && currentCard && (
|
||||
{/* Persistent abacus container - stays mounted during entire memorize phase */}
|
||||
<div className={css({
|
||||
width: 'min(90vw, 800px)',
|
||||
height: 'min(80vh, 700px)',
|
||||
display: isDisplayPhaseActive ? 'flex' : 'none',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
margin: '0 auto',
|
||||
transition: 'opacity 0.3s ease',
|
||||
overflow: 'visible',
|
||||
padding: '40px 20px'
|
||||
})}>
|
||||
<div className={css({
|
||||
width: 'min(90vw, 800px)',
|
||||
height: 'min(70vh, 600px)',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
margin: '0 auto',
|
||||
transition: 'transform 0.3s ease',
|
||||
overflow: 'hidden'
|
||||
gap: '30px'
|
||||
})}>
|
||||
<div className={css({
|
||||
transform: 'scale(1.5)',
|
||||
transformOrigin: 'center'
|
||||
})}>
|
||||
<TypstSoroban
|
||||
number={currentCard.number}
|
||||
width="240pt"
|
||||
height="320pt"
|
||||
transparent={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Persistent abacus with smooth bead animations and dynamically calculated columns */}
|
||||
<AbacusReact
|
||||
value={currentCard?.number || 0}
|
||||
columns={maxColumns}
|
||||
beadShape={appConfig.beadShape}
|
||||
colorScheme={appConfig.colorScheme}
|
||||
hideInactiveBeads={appConfig.hideInactiveBeads}
|
||||
scaleFactor={5.5}
|
||||
interactive={false}
|
||||
showNumbers={false}
|
||||
animated={true}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -670,16 +702,26 @@ function CardGrid({ state }: { state: SorobanQuizState }) {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
overflow: 'hidden'
|
||||
overflow: 'hidden',
|
||||
padding: '4px'
|
||||
})}>
|
||||
<div className={css({
|
||||
transform: 'scale(2.2)',
|
||||
transformOrigin: 'center'
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
})}>
|
||||
<TypstSoroban
|
||||
number={card.number}
|
||||
width="120pt"
|
||||
height="160pt"
|
||||
<AbacusReact
|
||||
value={card.number}
|
||||
columns="auto"
|
||||
beadShape="diamond"
|
||||
colorScheme="place-value"
|
||||
hideInactiveBeads={false}
|
||||
scaleFactor={1.2}
|
||||
interactive={false}
|
||||
showNumbers={false}
|
||||
animated={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -845,16 +887,26 @@ function ResultsCardGrid({ state }: { state: SorobanQuizState }) {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
overflow: 'hidden'
|
||||
overflow: 'hidden',
|
||||
padding: '4px'
|
||||
})}>
|
||||
<div className={css({
|
||||
transform: 'scale(2.2)',
|
||||
transformOrigin: 'center'
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
})}>
|
||||
<TypstSoroban
|
||||
number={card.number}
|
||||
width="120pt"
|
||||
height="160pt"
|
||||
<AbacusReact
|
||||
value={card.number}
|
||||
columns="auto"
|
||||
beadShape="diamond"
|
||||
colorScheme="place-value"
|
||||
hideInactiveBeads={false}
|
||||
scaleFactor={1.2}
|
||||
interactive={false}
|
||||
showNumbers={false}
|
||||
animated={false}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -1283,6 +1335,7 @@ function InputPhase({ state, dispatch }: { state: SorobanQuizState; dispatch: Re
|
||||
|
||||
// React component for the results phase
|
||||
function ResultsPhase({ state, dispatch }: { state: SorobanQuizState; dispatch: React.Dispatch<QuizAction> }) {
|
||||
const appConfig = useAbacusConfig()
|
||||
const correct = state.foundNumbers.length
|
||||
const total = state.correctAnswers.length
|
||||
const percentage = Math.round((correct / total) * 100)
|
||||
@@ -1375,7 +1428,7 @@ function ResultsPhase({ state, dispatch }: { state: SorobanQuizState; dispatch:
|
||||
})}
|
||||
onClick={() => {
|
||||
dispatch({ type: 'RESET_QUIZ' })
|
||||
const quizCards = generateQuizCards(state.selectedCount, state.selectedDifficulty)
|
||||
const quizCards = generateQuizCards(state.selectedCount, state.selectedDifficulty, appConfig)
|
||||
dispatch({ type: 'START_QUIZ', quizCards })
|
||||
}}
|
||||
>
|
||||
@@ -1406,9 +1459,15 @@ function ResultsPhase({ state, dispatch }: { state: SorobanQuizState; dispatch:
|
||||
// CSS animations that need to be global
|
||||
const globalAnimations = `
|
||||
@keyframes pulse {
|
||||
0% { transform: scale(1); }
|
||||
50% { transform: scale(1.1); }
|
||||
100% { transform: scale(1); }
|
||||
0% { transform: scale(1); box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3); }
|
||||
50% { transform: scale(1.05); box-shadow: 0 6px 20px rgba(59, 130, 246, 0.5); }
|
||||
100% { transform: scale(1); box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3); }
|
||||
}
|
||||
|
||||
@keyframes subtlePageFlash {
|
||||
0% { background: linear-gradient(to bottom right, #f0fdf4, #ecfdf5); }
|
||||
50% { background: linear-gradient(to bottom right, #dcfce7, #d1fae5); }
|
||||
100% { background: linear-gradient(to bottom right, #f0fdf4, #ecfdf5); }
|
||||
}
|
||||
|
||||
@keyframes fadeInScale {
|
||||
|
||||
@@ -15,9 +15,6 @@
|
||||
.gap_4px {
|
||||
gap: 4px
|
||||
}
|
||||
.hover\:transform_translateY\(-1px\):is(:hover, [data-hover]) {
|
||||
transform: translateY(-1px)
|
||||
}
|
||||
|
||||
.fs_11px {
|
||||
font-size: 11px
|
||||
@@ -34,9 +31,6 @@
|
||||
.border_gray\.300 {
|
||||
border-color: var(--colors-gray-300)
|
||||
}
|
||||
.hover\:bg_gray\.50:is(:hover, [data-hover]) {
|
||||
background: var(--colors-gray-50)
|
||||
}
|
||||
|
||||
.border_2px_solid {
|
||||
border: 2px solid
|
||||
@@ -45,9 +39,10 @@
|
||||
.p_10px_20px {
|
||||
padding: 10px 20px
|
||||
}
|
||||
.hover\:border_blue\.400:is(:hover, [data-hover]) {
|
||||
border-color: var(--colors-blue-400)
|
||||
}
|
||||
|
||||
.m_20px_0 {
|
||||
margin: 20px 0
|
||||
}
|
||||
|
||||
.d_block {
|
||||
display: block
|
||||
@@ -57,6 +52,10 @@
|
||||
max-width: 300px
|
||||
}
|
||||
|
||||
.text_blue\.500 {
|
||||
color: var(--colors-blue-500)
|
||||
}
|
||||
|
||||
.min-w_50px {
|
||||
min-width: 50px
|
||||
}
|
||||
@@ -112,48 +111,29 @@
|
||||
.transition_background_0\.2s_ease {
|
||||
transition: background 0.2s ease
|
||||
}
|
||||
.hover\:bg_red\.600:is(:hover, [data-hover]) {
|
||||
background: var(--colors-red-600)
|
||||
}
|
||||
|
||||
.text_green\.500 {
|
||||
color: var(--colors-green-500)
|
||||
}
|
||||
|
||||
.text_blue\.500 {
|
||||
color: var(--colors-blue-500)
|
||||
}
|
||||
|
||||
.animation_pulse_0\.3s_ease {
|
||||
animation: pulse 0.3s ease
|
||||
}
|
||||
|
||||
.fs_3rem {
|
||||
font-size: 3rem
|
||||
}
|
||||
|
||||
.m_20px_0 {
|
||||
margin: 20px 0
|
||||
}
|
||||
|
||||
.text-shadow_2px_2px_4px_rgba\(0\,0\,0\,0\.1\) {
|
||||
text-shadow: 2px 2px 4px rgba(0,0,0,0.1)
|
||||
.d_none {
|
||||
display: none
|
||||
}
|
||||
|
||||
.w_min\(90vw\,_800px\) {
|
||||
width: min(90vw, 800px)
|
||||
}
|
||||
|
||||
.h_min\(70vh\,_600px\) {
|
||||
height: min(70vh, 600px)
|
||||
.h_min\(80vh\,_700px\) {
|
||||
height: min(80vh, 700px)
|
||||
}
|
||||
|
||||
.transition_transform_0\.3s_ease {
|
||||
transition: transform 0.3s ease
|
||||
.transition_opacity_0\.3s_ease {
|
||||
transition: opacity 0.3s ease
|
||||
}
|
||||
|
||||
.transform_scale\(1\.5\) {
|
||||
transform: scale(1.5)
|
||||
.overflow_visible {
|
||||
overflow: visible
|
||||
}
|
||||
|
||||
.p_40px_20px {
|
||||
padding: 40px 20px
|
||||
}
|
||||
|
||||
.p_16px {
|
||||
@@ -257,18 +237,14 @@
|
||||
transform: rotateY(180deg)
|
||||
}
|
||||
|
||||
.p_4px {
|
||||
padding: 4px
|
||||
}
|
||||
|
||||
.w_100\% {
|
||||
width: 100%
|
||||
}
|
||||
|
||||
.transform_scale\(2\.2\) {
|
||||
transform: scale(2.2)
|
||||
}
|
||||
|
||||
.origin_center {
|
||||
transform-origin: center
|
||||
}
|
||||
|
||||
.bg_red\.500 {
|
||||
background: var(--colors-red-500)
|
||||
}
|
||||
@@ -570,9 +546,6 @@
|
||||
.bg_blue\.500 {
|
||||
background: var(--colors-blue-500)
|
||||
}
|
||||
.hover\:bg_blue\.600:is(:hover, [data-hover]) {
|
||||
background: var(--colors-blue-600)
|
||||
}
|
||||
|
||||
.max-w_800px {
|
||||
max-width: 800px
|
||||
@@ -669,9 +642,6 @@
|
||||
.bg_green\.500 {
|
||||
background: var(--colors-green-500)
|
||||
}
|
||||
.hover\:bg_green\.600:is(:hover, [data-hover]) {
|
||||
background: var(--colors-green-600)
|
||||
}
|
||||
|
||||
.p_12px_24px {
|
||||
padding: 12px 24px
|
||||
@@ -708,9 +678,6 @@
|
||||
.text_white {
|
||||
color: var(--colors-white)
|
||||
}
|
||||
.hover\:bg_gray\.600:is(:hover, [data-hover]) {
|
||||
background: var(--colors-gray-600)
|
||||
}
|
||||
|
||||
.min-h_screen {
|
||||
min-height: 100vh
|
||||
@@ -779,9 +746,6 @@
|
||||
.mb_4 {
|
||||
margin-bottom: var(--spacing-4)
|
||||
}
|
||||
.hover\:text_gray\.800:is(:hover, [data-hover]) {
|
||||
color: var(--colors-gray-800)
|
||||
}
|
||||
|
||||
.bg_white {
|
||||
background: var(--colors-white)
|
||||
@@ -827,40 +791,44 @@
|
||||
overflow: auto
|
||||
}
|
||||
|
||||
.w_600pt {
|
||||
width: 600pt
|
||||
.columns_auto {
|
||||
columns: auto
|
||||
}
|
||||
|
||||
.h_500pt {
|
||||
height: 500pt
|
||||
.color-scheme_place-value {
|
||||
color-scheme: place-value
|
||||
}
|
||||
|
||||
.w_240pt {
|
||||
width: 240pt
|
||||
}
|
||||
|
||||
.h_320pt {
|
||||
height: 320pt
|
||||
}
|
||||
|
||||
.w_120pt {
|
||||
width: 120pt
|
||||
}
|
||||
|
||||
.h_160pt {
|
||||
height: 160pt
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
|
||||
.\[\@media_\(max-width\:_768px\)\]\:gap_10px {
|
||||
gap: 10px
|
||||
}
|
||||
.hover\:transform_translateY\(-1px\):is(:hover, [data-hover]) {
|
||||
transform: translateY(-1px)
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
|
||||
.\[\@media_\(max-width\:_480px\)\]\:gap_8px {
|
||||
gap: 8px
|
||||
}
|
||||
.hover\:bg_gray\.50:is(:hover, [data-hover]) {
|
||||
background: var(--colors-gray-50)
|
||||
}
|
||||
|
||||
.hover\:border_blue\.400:is(:hover, [data-hover]) {
|
||||
border-color: var(--colors-blue-400)
|
||||
}
|
||||
|
||||
.hover\:bg_red\.600:is(:hover, [data-hover]) {
|
||||
background: var(--colors-red-600)
|
||||
}
|
||||
|
||||
.hover\:bg_blue\.600:is(:hover, [data-hover]) {
|
||||
background: var(--colors-blue-600)
|
||||
}
|
||||
|
||||
.hover\:bg_green\.600:is(:hover, [data-hover]) {
|
||||
background: var(--colors-green-600)
|
||||
}
|
||||
|
||||
.hover\:bg_gray\.600:is(:hover, [data-hover]) {
|
||||
background: var(--colors-gray-600)
|
||||
}
|
||||
|
||||
.hover\:text_gray\.800:is(:hover, [data-hover]) {
|
||||
color: var(--colors-gray-800)
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
|
||||
@@ -896,22 +864,102 @@
|
||||
min-width: 90px
|
||||
}
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
|
||||
.\[\@media_\(max-width\:_768px\)\]\:fs_40px {
|
||||
font-size: 40px
|
||||
}
|
||||
}
|
||||
@media (max-width: 480px) {
|
||||
|
||||
.\[\@media_\(max-width\:_480px\)\]\:fs_32px {
|
||||
font-size: 32px
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 48em) {
|
||||
|
||||
.md\:px_4 {
|
||||
@media screen and (min-width: 48em) {
|
||||
.md\:px_4 {
|
||||
padding-inline: var(--spacing-4)
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 1024px) {
|
||||
.\[\@media_\(min-width\:_1024px\)\]\:aspect_3\/4 {
|
||||
aspect-ratio: 3/4
|
||||
}
|
||||
|
||||
.\[\@media_\(min-width\:_1024px\)\]\:h_120px {
|
||||
height: 120px
|
||||
}
|
||||
|
||||
.\[\@media_\(min-width\:_1024px\)\]\:min-w_90px {
|
||||
min-width: 90px
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.\[\@media_\(max-width\:_768px\)\]\:gap_10px {
|
||||
gap: 10px
|
||||
}
|
||||
.\[\@media_\(max-width\:_768px\)\]\:h_130px {
|
||||
height: 130px
|
||||
}
|
||||
|
||||
.\[\@media_\(max-width\:_768px\)\]\:min-w_100px {
|
||||
min-width: 100px
|
||||
}
|
||||
.\[\@media_\(max-width\:_768px\)\]\:fs_40px {
|
||||
font-size: 40px
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.\[\@media_\(max-width\:_480px\)\]\:gap_8px {
|
||||
gap: 8px
|
||||
}
|
||||
.\[\@media_\(max-width\:_480px\)\]\:h_120px {
|
||||
height: 120px
|
||||
}
|
||||
|
||||
.\[\@media_\(max-width\:_480px\)\]\:min-w_90px {
|
||||
min-width: 90px
|
||||
}
|
||||
.\[\@media_\(max-width\:_480px\)\]\:fs_32px {
|
||||
font-size: 32px
|
||||
}
|
||||
}
|
||||
}8px\)\]\:min-w_100px {
|
||||
min-width: 100px
|
||||
}
|
||||
|
||||
.\[\@media_\(max-width\:_768px\)\]\:gap_10px {
|
||||
gap: 10px
|
||||
}
|
||||
|
||||
.\[\@media_\(max-width\:_768px\)\]\:h_130px {
|
||||
height: 130px
|
||||
}
|
||||
|
||||
.\[\@media_\(max-width\:_768px\)\]\:min-w_100px {
|
||||
min-width: 100px
|
||||
}
|
||||
|
||||
.\[\@media_\(max-width\:_768px\)\]\:fs_40px {
|
||||
font-size: 40px
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.\[\@media_\(max-width\:_480px\)\]\:h_120px {
|
||||
height: 120px
|
||||
}
|
||||
|
||||
.\[\@media_\(max-width\:_480px\)\]\:min-w_90px {
|
||||
min-width: 90px
|
||||
}
|
||||
|
||||
.\[\@media_\(max-width\:_480px\)\]\:gap_8px {
|
||||
gap: 8px
|
||||
}
|
||||
|
||||
.\[\@media_\(max-width\:_480px\)\]\:h_120px {
|
||||
height: 120px
|
||||
}
|
||||
|
||||
.\[\@media_\(max-width\:_480px\)\]\:min-w_90px {
|
||||
min-width: 90px
|
||||
}
|
||||
|
||||
.\[\@media_\(max-width\:_480px\)\]\:fs_32px {
|
||||
font-size: 32px
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user