feat: revolutionary single-element editable NumberFlow with live abacus updates
Major improvements to InteractiveAbacus component: ## 🎯 Single-Element Solution - Replace complex input overlay with elegant keyboard event handling - Use single NumberFlow element that responds to direct typing - No hidden input fields or z-index complexity ## ⚡ Real-Time Updates - Abacus beads update instantly as you type each digit - Live connection between keyboard input and bead positions - No delays - see changes immediately on every keystroke ## 🎨 Enhanced UX - Click number to start editing with visual feedback - Subtle color change and pulsing underline when editing - Type numbers directly, backspace, delete, Enter/Escape - Compact mode support for space-efficient layouts ## 🔧 Technical Excellence - Clean keyboard event handling (0-9, Backspace, Delete, Enter, Escape) - Boundary checking prevents exceeding column limits - Proper focus management with tabIndex and blur handling - Live value updates without complex state synchronization ## 💫 User Experience - Seamless editing: click → type → see beads move instantly - No mode switching or jarring transitions - Beautiful NumberFlow animations combined with direct editability - Intuitive keyboard shortcuts and visual indicators 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -757,6 +757,7 @@ function ReadingNumbersGuide() {
|
||||
<InteractiveAbacus
|
||||
initialValue={0}
|
||||
columns={3}
|
||||
showManualInput={true}
|
||||
className={css({
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
|
||||
@@ -13,6 +13,8 @@ interface InteractiveAbacusProps {
|
||||
onValueChange?: (value: number) => void
|
||||
showValue?: boolean
|
||||
showControls?: boolean
|
||||
showManualInput?: boolean
|
||||
compact?: boolean
|
||||
}
|
||||
|
||||
export function InteractiveAbacus({
|
||||
@@ -21,12 +23,17 @@ export function InteractiveAbacus({
|
||||
className,
|
||||
onValueChange,
|
||||
showValue = true,
|
||||
showControls = true
|
||||
showControls = true,
|
||||
showManualInput = false,
|
||||
compact = false
|
||||
}: InteractiveAbacusProps) {
|
||||
const [currentValue, setCurrentValue] = useState(initialValue)
|
||||
const [isChanging, setIsChanging] = useState(false)
|
||||
const [previousValue, setPreviousValue] = useState(initialValue)
|
||||
const [isEditing, setIsEditing] = useState(false)
|
||||
const [editingValue, setEditingValue] = useState('')
|
||||
const svgRef = useRef<HTMLDivElement>(null)
|
||||
const numberRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
// Remove the old spring animation since we're using NumberFlow now
|
||||
|
||||
@@ -192,110 +199,252 @@ export function InteractiveAbacus({
|
||||
setCurrentValue(value)
|
||||
}, [])
|
||||
|
||||
const handleKeyDown = useCallback((event: React.KeyboardEvent) => {
|
||||
if (!showManualInput) return
|
||||
|
||||
// Handle number keys and editing
|
||||
if (event.key >= '0' && event.key <= '9') {
|
||||
event.preventDefault()
|
||||
let newEditingValue: string
|
||||
if (!isEditing) {
|
||||
setIsEditing(true)
|
||||
newEditingValue = event.key
|
||||
setEditingValue(newEditingValue)
|
||||
} else {
|
||||
newEditingValue = editingValue + event.key
|
||||
const numValue = parseInt(newEditingValue)
|
||||
const maxValue = Math.pow(10, columns) - 1
|
||||
if (numValue <= maxValue) {
|
||||
setEditingValue(newEditingValue)
|
||||
} else {
|
||||
return // Don't update if exceeds max
|
||||
}
|
||||
}
|
||||
// Update abacus immediately
|
||||
const liveValue = parseInt(newEditingValue) || 0
|
||||
setCurrentValue(liveValue)
|
||||
} else if (event.key === 'Backspace') {
|
||||
event.preventDefault()
|
||||
if (isEditing) {
|
||||
let newEditingValue: string
|
||||
if (editingValue.length > 1) {
|
||||
newEditingValue = editingValue.slice(0, -1)
|
||||
} else {
|
||||
newEditingValue = '0'
|
||||
}
|
||||
setEditingValue(newEditingValue)
|
||||
// Update abacus immediately
|
||||
const liveValue = parseInt(newEditingValue) || 0
|
||||
setCurrentValue(liveValue)
|
||||
}
|
||||
} else if (event.key === 'Enter' || event.key === 'Escape') {
|
||||
event.preventDefault()
|
||||
if (isEditing) {
|
||||
setIsEditing(false)
|
||||
setEditingValue('')
|
||||
// Value is already set from live updates
|
||||
}
|
||||
} else if (event.key === 'Delete') {
|
||||
event.preventDefault()
|
||||
setEditingValue('0')
|
||||
setIsEditing(true)
|
||||
// Update abacus immediately
|
||||
setCurrentValue(0)
|
||||
}
|
||||
}, [showManualInput, isEditing, editingValue, columns])
|
||||
|
||||
const handleNumberClick = useCallback(() => {
|
||||
if (showManualInput && numberRef.current) {
|
||||
numberRef.current.focus()
|
||||
if (!isEditing) {
|
||||
setIsEditing(true)
|
||||
setEditingValue(String(currentValue))
|
||||
}
|
||||
}
|
||||
}, [showManualInput, isEditing, currentValue])
|
||||
|
||||
const handleNumberBlur = useCallback(() => {
|
||||
if (isEditing) {
|
||||
setIsEditing(false)
|
||||
setEditingValue('')
|
||||
// Value is already live-updated, no need to set again
|
||||
}
|
||||
}, [isEditing])
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className={css({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '6',
|
||||
flexDirection: compact ? 'row' : 'column',
|
||||
gap: compact ? '4' : '6',
|
||||
alignItems: 'center'
|
||||
})}>
|
||||
{/* Interactive Abacus using TypstSoroban */}
|
||||
<animated.div
|
||||
ref={svgRef}
|
||||
style={containerSpring}
|
||||
className={css({
|
||||
width: '300px',
|
||||
height: '400px',
|
||||
border: '3px solid',
|
||||
borderRadius: '12px',
|
||||
bg: 'gradient-to-br',
|
||||
gradientFrom: 'amber.50',
|
||||
gradientTo: 'orange.100',
|
||||
padding: '20px',
|
||||
boxShadow: '0 8px 24px rgba(0, 0, 0, 0.1)',
|
||||
position: 'relative',
|
||||
transition: 'all 0.2s ease',
|
||||
_hover: {
|
||||
boxShadow: '0 12px 32px rgba(0, 0, 0, 0.15)',
|
||||
}
|
||||
})}
|
||||
>
|
||||
<TypstSoroban
|
||||
number={currentValue}
|
||||
width="180pt"
|
||||
height="240pt"
|
||||
{/* Interactive Abacus Container */}
|
||||
<div className={css({
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center'
|
||||
})}>
|
||||
{/* Abacus with integrated value display */}
|
||||
<animated.div
|
||||
ref={svgRef}
|
||||
style={containerSpring}
|
||||
className={css({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
transition: 'all 0.3s ease',
|
||||
cursor: 'pointer',
|
||||
'& [data-bead-type]': {
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
_hover: {
|
||||
filter: 'brightness(1.2)',
|
||||
transform: 'scale(1.05)'
|
||||
}
|
||||
width: compact ? '240px' : '300px',
|
||||
height: compact ? '320px' : '400px',
|
||||
border: '3px solid',
|
||||
borderRadius: '12px',
|
||||
bg: 'gradient-to-br',
|
||||
gradientFrom: 'amber.50',
|
||||
gradientTo: 'orange.100',
|
||||
padding: compact ? '16px' : '20px',
|
||||
boxShadow: '0 8px 24px rgba(0, 0, 0, 0.1)',
|
||||
position: 'relative',
|
||||
transition: 'all 0.2s ease',
|
||||
_hover: {
|
||||
boxShadow: '0 12px 32px rgba(0, 0, 0, 0.15)',
|
||||
}
|
||||
})}
|
||||
/>
|
||||
|
||||
|
||||
</animated.div>
|
||||
|
||||
{/* Value Display */}
|
||||
{showValue && (
|
||||
<div
|
||||
className={css({
|
||||
fontSize: '3xl',
|
||||
fontWeight: 'bold',
|
||||
color: 'blue.600',
|
||||
bg: 'blue.50',
|
||||
px: '6',
|
||||
py: '3',
|
||||
rounded: 'xl',
|
||||
border: '2px solid',
|
||||
borderColor: 'blue.200',
|
||||
minW: '120px',
|
||||
textAlign: 'center',
|
||||
boxShadow: '0 4px 12px rgba(59, 130, 246, 0.15)'
|
||||
})}
|
||||
>
|
||||
<NumberFlow
|
||||
value={currentValue}
|
||||
style={{
|
||||
fontSize: 'inherit',
|
||||
fontWeight: 'inherit',
|
||||
color: 'inherit'
|
||||
}}
|
||||
<TypstSoroban
|
||||
number={currentValue}
|
||||
width={compact ? "144pt" : "180pt"}
|
||||
height={compact ? "192pt" : "240pt"}
|
||||
className={css({
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
transition: 'all 0.3s ease',
|
||||
cursor: 'pointer',
|
||||
'& [data-bead-type]': {
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
_hover: {
|
||||
filter: 'brightness(1.2)',
|
||||
transform: 'scale(1.05)'
|
||||
}
|
||||
}
|
||||
})}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</animated.div>
|
||||
|
||||
{/* Controls */}
|
||||
{/* Integrated Value Display with Always-Available Input */}
|
||||
{showValue && (
|
||||
<div
|
||||
className={css({
|
||||
position: 'absolute',
|
||||
bottom: compact ? '-50px' : '-60px',
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '2',
|
||||
bg: 'white',
|
||||
border: '2px solid',
|
||||
borderColor: 'blue.200',
|
||||
rounded: 'xl',
|
||||
boxShadow: '0 4px 12px rgba(59, 130, 246, 0.15)',
|
||||
px: '3',
|
||||
py: '2',
|
||||
minW: '160px'
|
||||
})}
|
||||
>
|
||||
{/* Single Unified Number Display/Input */}
|
||||
<div
|
||||
ref={numberRef}
|
||||
tabIndex={showManualInput ? 0 : -1}
|
||||
onClick={handleNumberClick}
|
||||
onKeyDown={handleKeyDown}
|
||||
onBlur={handleNumberBlur}
|
||||
className={css({
|
||||
position: 'relative',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
flex: '1',
|
||||
outline: 'none',
|
||||
cursor: showManualInput ? 'pointer' : 'default'
|
||||
})}
|
||||
title={showManualInput ? "Click to edit, type numbers, Enter to confirm" : undefined}
|
||||
>
|
||||
<NumberFlow
|
||||
value={isEditing ? parseInt(editingValue) || 0 : currentValue}
|
||||
style={{
|
||||
fontSize: compact ? '1.5rem' : '1.875rem',
|
||||
fontWeight: 'bold',
|
||||
color: isEditing ? '#1d4ed8' : '#2563eb', // Slightly darker when editing
|
||||
textAlign: 'center',
|
||||
minWidth: '80px',
|
||||
width: '100%'
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Visual editing indicator */}
|
||||
{isEditing && (
|
||||
<div className={css({
|
||||
position: 'absolute',
|
||||
bottom: '-2px',
|
||||
left: '50%',
|
||||
transform: 'translateX(-50%)',
|
||||
width: '80%',
|
||||
height: '2px',
|
||||
bg: 'blue.400',
|
||||
rounded: 'full',
|
||||
animation: 'pulse 1s infinite'
|
||||
})} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Input Indicator */}
|
||||
{showManualInput && (
|
||||
<div
|
||||
className={css({
|
||||
p: '1',
|
||||
rounded: 'full',
|
||||
bg: 'blue.50',
|
||||
color: 'blue.600',
|
||||
fontSize: 'xs',
|
||||
w: '6',
|
||||
h: '6',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
flexShrink: 0
|
||||
})}
|
||||
title="Click the number to edit directly"
|
||||
>
|
||||
✏️
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Compact Controls */}
|
||||
{showControls && (
|
||||
<div className={css({
|
||||
display: 'flex',
|
||||
flexDirection: compact ? 'column' : 'row',
|
||||
alignItems: 'center',
|
||||
gap: '3',
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'center'
|
||||
gap: '2',
|
||||
flexWrap: compact ? 'nowrap' : 'wrap',
|
||||
justifyContent: 'center',
|
||||
mt: showValue ? (compact ? '0' : '16') : '0'
|
||||
})}>
|
||||
<button
|
||||
onClick={handleReset}
|
||||
className={css({
|
||||
px: '4',
|
||||
px: '3',
|
||||
py: '2',
|
||||
bg: 'gray.100',
|
||||
color: 'gray.700',
|
||||
border: '1px solid',
|
||||
borderColor: 'gray.300',
|
||||
rounded: 'lg',
|
||||
fontSize: 'sm',
|
||||
rounded: 'md',
|
||||
fontSize: 'xs',
|
||||
fontWeight: 'medium',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
minW: compact ? '60px' : 'auto',
|
||||
_hover: {
|
||||
bg: 'gray.200',
|
||||
borderColor: 'gray.400',
|
||||
@@ -309,57 +458,68 @@ export function InteractiveAbacus({
|
||||
Clear
|
||||
</button>
|
||||
|
||||
{/* Quick preset buttons */}
|
||||
{[1, 5, 10, 25, 50, 99].map(preset => (
|
||||
<button
|
||||
key={preset}
|
||||
onClick={() => handleSetValue(preset)}
|
||||
className={css({
|
||||
px: '3',
|
||||
py: '2',
|
||||
bg: 'blue.100',
|
||||
color: 'blue.700',
|
||||
border: '1px solid',
|
||||
borderColor: 'blue.300',
|
||||
rounded: 'lg',
|
||||
fontSize: 'sm',
|
||||
fontWeight: 'medium',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
_hover: {
|
||||
bg: 'blue.200',
|
||||
borderColor: 'blue.400',
|
||||
transform: 'translateY(-1px)'
|
||||
},
|
||||
_active: {
|
||||
transform: 'scale(0.95)'
|
||||
}
|
||||
})}
|
||||
>
|
||||
{preset}
|
||||
</button>
|
||||
))}
|
||||
{/* Compact preset buttons */}
|
||||
<div className={css({
|
||||
display: 'flex',
|
||||
flexDirection: compact ? 'column' : 'row',
|
||||
gap: '1',
|
||||
flexWrap: 'wrap'
|
||||
})}>
|
||||
{(compact ? [1, 5, 10, 25] : [1, 5, 10, 25, 50, 99]).map(preset => (
|
||||
<button
|
||||
key={preset}
|
||||
onClick={() => handleSetValue(preset)}
|
||||
className={css({
|
||||
px: '2',
|
||||
py: '1',
|
||||
bg: 'blue.100',
|
||||
color: 'blue.700',
|
||||
border: '1px solid',
|
||||
borderColor: 'blue.300',
|
||||
rounded: 'md',
|
||||
fontSize: 'xs',
|
||||
fontWeight: 'medium',
|
||||
cursor: 'pointer',
|
||||
transition: 'all 0.2s ease',
|
||||
minW: compact ? '40px' : '32px',
|
||||
_hover: {
|
||||
bg: 'blue.200',
|
||||
borderColor: 'blue.400',
|
||||
transform: 'translateY(-1px)'
|
||||
},
|
||||
_active: {
|
||||
transform: 'scale(0.95)'
|
||||
}
|
||||
})}
|
||||
>
|
||||
{preset}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Instructions */}
|
||||
<div className={css({
|
||||
fontSize: 'sm',
|
||||
color: 'gray.600',
|
||||
textAlign: 'center',
|
||||
maxW: '450px',
|
||||
lineHeight: 'relaxed',
|
||||
bg: 'gray.50',
|
||||
px: '4',
|
||||
py: '3',
|
||||
rounded: 'lg',
|
||||
border: '1px solid',
|
||||
borderColor: 'gray.200'
|
||||
})}>
|
||||
<strong>How to use:</strong> Click on the beads to activate or deactivate them!
|
||||
Heaven beads (top) are worth 5 each, earth beads (bottom) are worth 1 each.
|
||||
You can also use the preset buttons below.
|
||||
</div>
|
||||
{/* Compact Instructions */}
|
||||
{!compact && (
|
||||
<div className={css({
|
||||
fontSize: 'sm',
|
||||
color: 'gray.600',
|
||||
textAlign: 'center',
|
||||
maxW: '450px',
|
||||
lineHeight: 'relaxed',
|
||||
bg: 'gray.50',
|
||||
px: '4',
|
||||
py: '3',
|
||||
rounded: 'lg',
|
||||
border: '1px solid',
|
||||
borderColor: 'gray.200',
|
||||
mt: showValue ? '16' : '0'
|
||||
})}>
|
||||
<strong>How to use:</strong> Click on the beads to activate or deactivate them!
|
||||
Heaven beads (top) are worth 5 each, earth beads (bottom) are worth 1 each.
|
||||
{showManualInput && ' You can also click the number to type directly.'}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,21 +1,123 @@
|
||||
@layer utilities {
|
||||
|
||||
.flex_column {
|
||||
flex-direction: column
|
||||
.fs_3xl {
|
||||
font-size: var(--font-sizes-3xl)
|
||||
}
|
||||
|
||||
.px_6 {
|
||||
padding-inline: var(--spacing-6)
|
||||
}
|
||||
|
||||
.min-w_120px {
|
||||
min-width: 120px
|
||||
}
|
||||
|
||||
.gap_3 {
|
||||
gap: var(--spacing-3)
|
||||
}
|
||||
|
||||
.min-w_140px {
|
||||
min-width: 140px
|
||||
}
|
||||
|
||||
.w_80px {
|
||||
width: 80px
|
||||
}
|
||||
|
||||
.fs_xl {
|
||||
font-size: var(--font-sizes-xl)
|
||||
}
|
||||
|
||||
.fs_2xl {
|
||||
font-size: var(--font-sizes-2xl)
|
||||
}
|
||||
|
||||
.text_transparent {
|
||||
color: var(--colors-transparent)
|
||||
}
|
||||
|
||||
.fs_1\.5rem {
|
||||
font-size: 1.5rem
|
||||
}
|
||||
|
||||
.fs_1\.875rem {
|
||||
font-size: 1.875rem
|
||||
}
|
||||
|
||||
.top_0 {
|
||||
top: var(--spacing-0)
|
||||
}
|
||||
|
||||
.left_0 {
|
||||
left: var(--spacing-0)
|
||||
}
|
||||
|
||||
.border_none {
|
||||
border: var(--borders-none)
|
||||
}
|
||||
|
||||
.bg_transparent {
|
||||
background: var(--colors-transparent)
|
||||
}
|
||||
|
||||
.font_bold {
|
||||
font-weight: var(--font-weights-bold)
|
||||
}
|
||||
|
||||
.text_\#2563eb {
|
||||
color: #2563eb
|
||||
}
|
||||
|
||||
.appearance_textfield {
|
||||
appearance: textfield;
|
||||
-webkit-appearance: textfield
|
||||
}
|
||||
|
||||
.font_inherit {
|
||||
font-family: inherit
|
||||
}
|
||||
|
||||
.\[\&\:\:-webkit-outer-spin-button\,_\&\:\:-webkit-inner-spin-button\]\:appearance_none::-webkit-outer-spin-button, .\[\&\:\:-webkit-outer-spin-button\,_\&\:\:-webkit-inner-spin-button\]\:appearance_none::-webkit-inner-spin-button {
|
||||
appearance: none;
|
||||
-webkit-appearance: none
|
||||
}
|
||||
|
||||
.\[\&\:\:-webkit-outer-spin-button\,_\&\:\:-webkit-inner-spin-button\]\:m_0::-webkit-outer-spin-button, .\[\&\:\:-webkit-outer-spin-button\,_\&\:\:-webkit-inner-spin-button\]\:m_0::-webkit-inner-spin-button {
|
||||
margin: var(--spacing-0)
|
||||
}
|
||||
|
||||
.gap_4 {
|
||||
gap: var(--spacing-4)
|
||||
}
|
||||
|
||||
.gap_6 {
|
||||
gap: var(--spacing-6)
|
||||
}
|
||||
|
||||
.w_240px {
|
||||
width: 240px
|
||||
}
|
||||
|
||||
.w_300px {
|
||||
width: 300px
|
||||
}
|
||||
|
||||
.h_320px {
|
||||
height: 320px
|
||||
}
|
||||
|
||||
.h_400px {
|
||||
height: 400px
|
||||
}
|
||||
|
||||
.p_16px {
|
||||
padding: 16px
|
||||
}
|
||||
|
||||
.p_20px {
|
||||
padding: 20px
|
||||
}
|
||||
|
||||
.border_3px_solid {
|
||||
border: 3px solid
|
||||
}
|
||||
@@ -36,18 +138,10 @@
|
||||
--gradient-to: var(--colors-orange-100)
|
||||
}
|
||||
|
||||
.p_20px {
|
||||
padding: 20px
|
||||
}
|
||||
|
||||
.shadow_0_8px_24px_rgba\(0\,_0\,_0\,_0\.1\) {
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1)
|
||||
}
|
||||
|
||||
.pos_relative {
|
||||
position: relative
|
||||
}
|
||||
|
||||
.w_100\% {
|
||||
width: 100%
|
||||
}
|
||||
@@ -66,28 +160,16 @@
|
||||
transition: all 0.2s ease
|
||||
}
|
||||
|
||||
.fs_3xl {
|
||||
font-size: var(--font-sizes-3xl)
|
||||
.bottom_-50px {
|
||||
bottom: -50px
|
||||
}
|
||||
|
||||
.font_bold {
|
||||
font-weight: var(--font-weights-bold)
|
||||
.bottom_-60px {
|
||||
bottom: -60px
|
||||
}
|
||||
|
||||
.text_blue\.600 {
|
||||
color: var(--colors-blue-600)
|
||||
}
|
||||
|
||||
.bg_blue\.50 {
|
||||
background: var(--colors-blue-50)
|
||||
}
|
||||
|
||||
.px_6 {
|
||||
padding-inline: var(--spacing-6)
|
||||
}
|
||||
|
||||
.rounded_xl {
|
||||
border-radius: var(--radii-xl)
|
||||
.bg_white {
|
||||
background: var(--colors-white)
|
||||
}
|
||||
|
||||
.border_2px_solid {
|
||||
@@ -98,34 +180,126 @@
|
||||
border-color: var(--colors-blue-200)
|
||||
}
|
||||
|
||||
.min-w_120px {
|
||||
min-width: 120px
|
||||
.rounded_xl {
|
||||
border-radius: var(--radii-xl)
|
||||
}
|
||||
|
||||
.shadow_0_4px_12px_rgba\(59\,_130\,_246\,_0\.15\) {
|
||||
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15)
|
||||
}
|
||||
|
||||
.d_flex {
|
||||
display: flex
|
||||
.min-w_160px {
|
||||
min-width: 160px
|
||||
}
|
||||
|
||||
.cursor_default {
|
||||
cursor: default
|
||||
}
|
||||
|
||||
.pos_relative {
|
||||
position: relative
|
||||
}
|
||||
|
||||
.flex_1 {
|
||||
flex: 1 1 0%
|
||||
}
|
||||
|
||||
.ring_none {
|
||||
outline: var(--borders-none)
|
||||
}
|
||||
|
||||
.pos_absolute {
|
||||
position: absolute
|
||||
}
|
||||
|
||||
.bottom_-2px {
|
||||
bottom: -2px
|
||||
}
|
||||
|
||||
.left_50\% {
|
||||
left: 50%
|
||||
}
|
||||
|
||||
.transform_translateX\(-50\%\) {
|
||||
transform: translateX(-50%)
|
||||
}
|
||||
|
||||
.w_80\% {
|
||||
width: 80%
|
||||
}
|
||||
|
||||
.h_2px {
|
||||
height: 2px
|
||||
}
|
||||
|
||||
.bg_blue\.400 {
|
||||
background: var(--colors-blue-400)
|
||||
}
|
||||
|
||||
.animation_pulse_1s_infinite {
|
||||
animation: pulse 1s infinite
|
||||
}
|
||||
|
||||
.p_1 {
|
||||
padding: var(--spacing-1)
|
||||
}
|
||||
|
||||
.rounded_full {
|
||||
border-radius: var(--radii-full)
|
||||
}
|
||||
|
||||
.bg_blue\.50 {
|
||||
background: var(--colors-blue-50)
|
||||
}
|
||||
|
||||
.text_blue\.600 {
|
||||
color: var(--colors-blue-600)
|
||||
}
|
||||
|
||||
.w_6 {
|
||||
width: var(--sizes-6)
|
||||
}
|
||||
|
||||
.h_6 {
|
||||
height: var(--sizes-6)
|
||||
}
|
||||
|
||||
.shrink_0 {
|
||||
flex-shrink: 0
|
||||
}
|
||||
|
||||
.flex-wrap_nowrap {
|
||||
flex-wrap: nowrap
|
||||
}
|
||||
|
||||
.items_center {
|
||||
align-items: center
|
||||
}
|
||||
|
||||
.gap_3 {
|
||||
gap: var(--spacing-3)
|
||||
}
|
||||
|
||||
.flex-wrap_wrap {
|
||||
flex-wrap: wrap
|
||||
.gap_2 {
|
||||
gap: var(--spacing-2)
|
||||
}
|
||||
|
||||
.justify_center {
|
||||
justify-content: center
|
||||
}
|
||||
|
||||
.min-w_60px {
|
||||
min-width: 60px
|
||||
}
|
||||
|
||||
.min-w_auto {
|
||||
min-width: auto
|
||||
}
|
||||
|
||||
.px_3 {
|
||||
padding-inline: var(--spacing-3)
|
||||
}
|
||||
|
||||
.py_2 {
|
||||
padding-block: var(--spacing-2)
|
||||
}
|
||||
|
||||
.bg_gray\.100 {
|
||||
background: var(--colors-gray-100)
|
||||
}
|
||||
@@ -138,12 +312,40 @@
|
||||
border-color: var(--colors-gray-300)
|
||||
}
|
||||
|
||||
.px_3 {
|
||||
padding-inline: var(--spacing-3)
|
||||
.flex_column {
|
||||
flex-direction: column
|
||||
}
|
||||
|
||||
.py_2 {
|
||||
padding-block: var(--spacing-2)
|
||||
.flex_row {
|
||||
flex-direction: row
|
||||
}
|
||||
|
||||
.d_flex {
|
||||
display: flex
|
||||
}
|
||||
|
||||
.gap_1 {
|
||||
gap: var(--spacing-1)
|
||||
}
|
||||
|
||||
.flex-wrap_wrap {
|
||||
flex-wrap: wrap
|
||||
}
|
||||
|
||||
.min-w_40px {
|
||||
min-width: 40px
|
||||
}
|
||||
|
||||
.min-w_32px {
|
||||
min-width: 32px
|
||||
}
|
||||
|
||||
.px_2 {
|
||||
padding-inline: var(--spacing-2)
|
||||
}
|
||||
|
||||
.py_1 {
|
||||
padding-block: var(--spacing-1)
|
||||
}
|
||||
|
||||
.bg_blue\.100 {
|
||||
@@ -158,6 +360,14 @@
|
||||
border-color: var(--colors-blue-300)
|
||||
}
|
||||
|
||||
.rounded_md {
|
||||
border-radius: var(--radii-md)
|
||||
}
|
||||
|
||||
.fs_xs {
|
||||
font-size: var(--font-sizes-xs)
|
||||
}
|
||||
|
||||
.font_medium {
|
||||
font-weight: var(--font-weights-medium)
|
||||
}
|
||||
@@ -170,6 +380,14 @@
|
||||
transition: all 0.2s ease
|
||||
}
|
||||
|
||||
.mt_16 {
|
||||
margin-top: var(--spacing-16)
|
||||
}
|
||||
|
||||
.mt_0 {
|
||||
margin-top: var(--spacing-0)
|
||||
}
|
||||
|
||||
.fs_sm {
|
||||
font-size: var(--font-sizes-sm)
|
||||
}
|
||||
@@ -214,14 +432,46 @@
|
||||
border-color: var(--colors-gray-200)
|
||||
}
|
||||
|
||||
.w_144pt {
|
||||
width: 144pt
|
||||
}
|
||||
|
||||
.w_180pt {
|
||||
width: 180pt
|
||||
}
|
||||
|
||||
.h_192pt {
|
||||
height: 192pt
|
||||
}
|
||||
|
||||
.h_240pt {
|
||||
height: 240pt
|
||||
}
|
||||
|
||||
.\[\&\:focus\]\:text_blue\.600:focus {
|
||||
color: var(--colors-blue-600)
|
||||
}
|
||||
|
||||
.\[\&\:focus\]\:bg_blue\.50:focus {
|
||||
background: var(--colors-blue-50)
|
||||
}
|
||||
|
||||
.\[\&\:focus\]\:rounded_md:focus {
|
||||
border-radius: var(--radii-md)
|
||||
}
|
||||
|
||||
.\[\&\:focus\]\:ring_none:focus {
|
||||
outline: var(--borders-none)
|
||||
}
|
||||
|
||||
.\[\&\:focus\]\:border_none:focus {
|
||||
border: var(--borders-none)
|
||||
}
|
||||
|
||||
.hover\:bg_blue\.100:is(:hover, [data-hover]) {
|
||||
background: var(--colors-blue-100)
|
||||
}
|
||||
|
||||
.hover\:shadow_0_12px_32px_rgba\(0\,_0\,_0\,_0\.15\):is(:hover, [data-hover]) {
|
||||
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.15)
|
||||
}
|
||||
|
||||
@@ -137,10 +137,6 @@
|
||||
transition: transform 0.3s ease
|
||||
}
|
||||
|
||||
.p_16px {
|
||||
padding: 16px
|
||||
}
|
||||
|
||||
.max-h_60vh {
|
||||
max-height: 60vh
|
||||
}
|
||||
@@ -419,10 +415,6 @@
|
||||
min-height: var(--sizes-0)
|
||||
}
|
||||
|
||||
.left_0 {
|
||||
left: var(--spacing-0)
|
||||
}
|
||||
|
||||
.w_100vw {
|
||||
width: 100vw
|
||||
}
|
||||
@@ -439,10 +431,6 @@
|
||||
top: 50%
|
||||
}
|
||||
|
||||
.left_50\% {
|
||||
left: 50%
|
||||
}
|
||||
|
||||
.transform_translate\(-50\%\,_-50\%\) {
|
||||
transform: translate(-50%, -50%)
|
||||
}
|
||||
@@ -575,10 +563,6 @@
|
||||
height: 100vh
|
||||
}
|
||||
|
||||
.shrink_0 {
|
||||
flex-shrink: 0
|
||||
}
|
||||
|
||||
.d_inline-flex {
|
||||
display: inline-flex
|
||||
}
|
||||
@@ -907,10 +891,6 @@
|
||||
grid-template-columns: repeat(1, minmax(0, 1fr))
|
||||
}
|
||||
|
||||
.mt_16 {
|
||||
margin-top: var(--spacing-16)
|
||||
}
|
||||
|
||||
.gap_12 {
|
||||
gap: var(--spacing-12)
|
||||
}
|
||||
@@ -1057,10 +1037,6 @@
|
||||
position: sticky
|
||||
}
|
||||
|
||||
.top_0 {
|
||||
top: var(--spacing-0)
|
||||
}
|
||||
|
||||
.z_30 {
|
||||
z-index: 30
|
||||
}
|
||||
@@ -1165,10 +1141,6 @@
|
||||
background: var(--colors-green-100)
|
||||
}
|
||||
|
||||
.px_2 {
|
||||
padding-inline: var(--spacing-2)
|
||||
}
|
||||
|
||||
.cursor_not-allowed {
|
||||
cursor: not-allowed
|
||||
}
|
||||
@@ -1205,10 +1177,6 @@
|
||||
border-color: var(--colors-green-200)
|
||||
}
|
||||
|
||||
.bg_transparent {
|
||||
background: var(--colors-transparent)
|
||||
}
|
||||
|
||||
.text_green\.700 {
|
||||
color: var(--colors-green-700)
|
||||
}
|
||||
@@ -1266,10 +1234,6 @@
|
||||
max-height: 300px
|
||||
}
|
||||
|
||||
.ring_none {
|
||||
outline: var(--borders-none)
|
||||
}
|
||||
|
||||
.\[\&\[data-state\=checked\]\]\:bg_brand\.100[data-state=checked] {
|
||||
background: var(--colors-brand-100)
|
||||
}
|
||||
@@ -1362,14 +1326,92 @@
|
||||
gap: var(--spacing-0)
|
||||
}
|
||||
|
||||
.px_6 {
|
||||
padding-inline: var(--spacing-6)
|
||||
}
|
||||
|
||||
.min-w_120px {
|
||||
min-width: 120px
|
||||
}
|
||||
|
||||
.min-w_140px {
|
||||
min-width: 140px
|
||||
}
|
||||
|
||||
.w_80px {
|
||||
width: 80px
|
||||
}
|
||||
|
||||
.text_transparent {
|
||||
color: var(--colors-transparent)
|
||||
}
|
||||
|
||||
.fs_1\.5rem {
|
||||
font-size: 1.5rem
|
||||
}
|
||||
|
||||
.fs_1\.875rem {
|
||||
font-size: 1.875rem
|
||||
}
|
||||
|
||||
.top_0 {
|
||||
top: var(--spacing-0)
|
||||
}
|
||||
|
||||
.left_0 {
|
||||
left: var(--spacing-0)
|
||||
}
|
||||
|
||||
.bg_transparent {
|
||||
background: var(--colors-transparent)
|
||||
}
|
||||
|
||||
.text_\#2563eb {
|
||||
color: #2563eb
|
||||
}
|
||||
|
||||
.appearance_textfield {
|
||||
appearance: textfield;
|
||||
-webkit-appearance: textfield
|
||||
}
|
||||
|
||||
.font_inherit {
|
||||
font-family: inherit
|
||||
}
|
||||
|
||||
.\[\&\:\:-webkit-outer-spin-button\,_\&\:\:-webkit-inner-spin-button\]\:appearance_none::-webkit-outer-spin-button, .\[\&\:\:-webkit-outer-spin-button\,_\&\:\:-webkit-inner-spin-button\]\:appearance_none::-webkit-inner-spin-button {
|
||||
appearance: none;
|
||||
-webkit-appearance: none
|
||||
}
|
||||
|
||||
.\[\&\:\:-webkit-outer-spin-button\,_\&\:\:-webkit-inner-spin-button\]\:m_0::-webkit-outer-spin-button, .\[\&\:\:-webkit-outer-spin-button\,_\&\:\:-webkit-inner-spin-button\]\:m_0::-webkit-inner-spin-button {
|
||||
margin: var(--spacing-0)
|
||||
}
|
||||
|
||||
.w_240px {
|
||||
width: 240px
|
||||
}
|
||||
|
||||
.w_300px {
|
||||
width: 300px
|
||||
}
|
||||
|
||||
.h_320px {
|
||||
height: 320px
|
||||
}
|
||||
|
||||
.h_400px {
|
||||
height: 400px
|
||||
}
|
||||
|
||||
.p_16px {
|
||||
padding: 16px
|
||||
}
|
||||
|
||||
.p_20px {
|
||||
padding: 20px
|
||||
}
|
||||
|
||||
.border_3px_solid {
|
||||
border: 3px solid
|
||||
}
|
||||
@@ -1390,10 +1432,6 @@
|
||||
--gradient-to: var(--colors-orange-100)
|
||||
}
|
||||
|
||||
.p_20px {
|
||||
padding: 20px
|
||||
}
|
||||
|
||||
.shadow_0_8px_24px_rgba\(0\,_0\,_0\,_0\.1\) {
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1)
|
||||
}
|
||||
@@ -1418,20 +1456,72 @@
|
||||
transition: all 0.2s ease
|
||||
}
|
||||
|
||||
.px_6 {
|
||||
padding-inline: var(--spacing-6)
|
||||
.bottom_-50px {
|
||||
bottom: -50px
|
||||
}
|
||||
|
||||
.min-w_120px {
|
||||
min-width: 120px
|
||||
.bottom_-60px {
|
||||
bottom: -60px
|
||||
}
|
||||
|
||||
.shadow_0_4px_12px_rgba\(59\,_130\,_246\,_0\.15\) {
|
||||
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15)
|
||||
}
|
||||
|
||||
.flex-wrap_wrap {
|
||||
flex-wrap: wrap
|
||||
.min-w_160px {
|
||||
min-width: 160px
|
||||
}
|
||||
|
||||
.cursor_default {
|
||||
cursor: default
|
||||
}
|
||||
|
||||
.ring_none {
|
||||
outline: var(--borders-none)
|
||||
}
|
||||
|
||||
.bottom_-2px {
|
||||
bottom: -2px
|
||||
}
|
||||
|
||||
.left_50\% {
|
||||
left: 50%
|
||||
}
|
||||
|
||||
.transform_translateX\(-50\%\) {
|
||||
transform: translateX(-50%)
|
||||
}
|
||||
|
||||
.w_80\% {
|
||||
width: 80%
|
||||
}
|
||||
|
||||
.h_2px {
|
||||
height: 2px
|
||||
}
|
||||
|
||||
.bg_blue\.400 {
|
||||
background: var(--colors-blue-400)
|
||||
}
|
||||
|
||||
.animation_pulse_1s_infinite {
|
||||
animation: pulse 1s infinite
|
||||
}
|
||||
|
||||
.shrink_0 {
|
||||
flex-shrink: 0
|
||||
}
|
||||
|
||||
.flex-wrap_nowrap {
|
||||
flex-wrap: nowrap
|
||||
}
|
||||
|
||||
.min-w_60px {
|
||||
min-width: 60px
|
||||
}
|
||||
|
||||
.min-w_auto {
|
||||
min-width: auto
|
||||
}
|
||||
|
||||
.bg_gray\.100 {
|
||||
@@ -1442,6 +1532,22 @@
|
||||
color: var(--colors-gray-700)
|
||||
}
|
||||
|
||||
.flex-wrap_wrap {
|
||||
flex-wrap: wrap
|
||||
}
|
||||
|
||||
.min-w_40px {
|
||||
min-width: 40px
|
||||
}
|
||||
|
||||
.min-w_32px {
|
||||
min-width: 32px
|
||||
}
|
||||
|
||||
.px_2 {
|
||||
padding-inline: var(--spacing-2)
|
||||
}
|
||||
|
||||
.bg_blue\.100 {
|
||||
background: var(--colors-blue-100)
|
||||
}
|
||||
@@ -1450,6 +1556,14 @@
|
||||
transition: all 0.2s ease
|
||||
}
|
||||
|
||||
.mt_16 {
|
||||
margin-top: var(--spacing-16)
|
||||
}
|
||||
|
||||
.mt_0 {
|
||||
margin-top: var(--spacing-0)
|
||||
}
|
||||
|
||||
.max-w_450px {
|
||||
max-width: 450px
|
||||
}
|
||||
@@ -1470,10 +1584,18 @@
|
||||
border-radius: var(--radii-lg)
|
||||
}
|
||||
|
||||
.w_144pt {
|
||||
width: 144pt
|
||||
}
|
||||
|
||||
.w_180pt {
|
||||
width: 180pt
|
||||
}
|
||||
|
||||
.h_192pt {
|
||||
height: 192pt
|
||||
}
|
||||
|
||||
.h_240pt {
|
||||
height: 240pt
|
||||
}
|
||||
@@ -2016,6 +2138,26 @@
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1)
|
||||
}
|
||||
|
||||
.\[\&\:focus\]\:text_blue\.600:focus {
|
||||
color: var(--colors-blue-600)
|
||||
}
|
||||
|
||||
.\[\&\:focus\]\:bg_blue\.50:focus {
|
||||
background: var(--colors-blue-50)
|
||||
}
|
||||
|
||||
.\[\&\:focus\]\:rounded_md:focus {
|
||||
border-radius: var(--radii-md)
|
||||
}
|
||||
|
||||
.\[\&\:focus\]\:ring_none:focus {
|
||||
outline: var(--borders-none)
|
||||
}
|
||||
|
||||
.\[\&\:focus\]\:border_none:focus {
|
||||
border: var(--borders-none)
|
||||
}
|
||||
|
||||
.hover\:bg_red\.600:is(:hover, [data-hover]) {
|
||||
background: var(--colors-red-600)
|
||||
}
|
||||
@@ -2116,6 +2258,10 @@
|
||||
background: var(--colors-brand-50)
|
||||
}
|
||||
|
||||
.hover\:bg_blue\.100:is(:hover, [data-hover]) {
|
||||
background: var(--colors-blue-100)
|
||||
}
|
||||
|
||||
.hover\:shadow_0_12px_32px_rgba\(0\,_0\,_0\,_0\.15\):is(:hover, [data-hover]) {
|
||||
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.15)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user