2 Commits
Author SHA1 Message Date
Vick Scarlet 30b4791813 update: toast achievement icon & style 2026-08-16 14:24:29 +08:00
Vick Scarlet 9017780898 add: toast achievement & message 2026-08-16 13:37:32 +08:00
19 changed files with 351 additions and 25 deletions
+2 -1
View File
@@ -17,7 +17,8 @@
"@remake/vitex": "workspace:*",
"jotai": "^2.20.2",
"react": "^19.2.8",
"react-dom": "^19.2.8"
"react-dom": "^19.2.8",
"react-hot-toast": "^2.6.0"
},
"devDependencies": {
"@types/react": "^19.2.18",
+2 -1
View File
@@ -1,6 +1,7 @@
import { useEffect } from 'react'
import { useInit, useWatcher } from '@/hooks/storage'
import { useStep, Step } from '@remake/hooks'
import ToastContainer from '@/toast'
import Home from '@/containers/Home'
import Mode from '@/containers/Mode'
import Chara from '@/containers/Chara'
@@ -40,6 +41,6 @@ export function Game() {
const saving = useWatcher()
useEffect(() => { if (!inited) init() }, [inited, init])
if (!inited) return <Loading />
return <><Container /><Saving active={saving} /></>
return <><Container /><ToastContainer /><Saving active={saving} /></>
}
export default Game
+31
View File
@@ -0,0 +1,31 @@
.achievement-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 calc(0.5rem * var(--ratio-font));
height: calc(2rem * var(--ratio));
gap: calc(1rem * var(--ratio-font));
font-size: calc(1rem * var(--ratio-font));
color: var(--grade-color);
background: var(--background, var(--base-background-color));
cursor: pointer;
user-select: none;
border: 0.0625rem solid var(--grade-color);
box-sizing: border-box;
transition: all 0.2s ease-in-out;
svg {
fill: var(--grade-color);
transition: fill 0.2s ease-in-out;
stroke: none;
width: calc(1.2rem * var(--ratio-font));
height: calc(1.2rem * var(--ratio-font));
}
.name { font-weight: bold; }
.description {
font-size: calc(0.85rem * var(--ratio-font));
user-select: none;
&::before { content: '['; margin-right: 0.125rem; }
&::after { content: ']'; margin-left: 0.125rem; }
&::before, &::after { color: var(--grade-color); }
}
}
+33
View File
@@ -0,0 +1,33 @@
import { type Achievement as A, achievement } from '@remake/data'
import './Achievement.css'
const Icon = () => (
<svg
width="800px"
height="800px"
viewBox="0 0 6.3500002 6.3500002"
fill="currentColor"
>
<g>
<path d="m 2.5032059,-0.0095735 c -0.043217,-6.35e-4 -0.0909,0.00531 -0.1493441,0.024289 C 2.2369795,0.0525379 2.1848433,0.1094109 2.1259683,0.1650936 2.0670958,0.2207129 2.0130256,0.2808305 1.9606037,0.3407933 1.8557572,0.4607694 1.7291409,0.5919178 1.7440793,0.5810887 1.7590282,0.5703466 1.5965582,0.6493864 1.4500399,0.7118302 1.3767847,0.7430018 1.3005529,0.7771219 1.2293826,0.8156998 1.1582044,0.8544035 1.0898255,0.885282 1.0175096,0.9846817 0.9451937,1.0840436 0.9358089,1.159797 0.9208732,1.2394466 0.9059507,1.319096 0.8985953,1.400403 0.8914171,1.479742 0.8770502,1.6384195 0.8500416,1.8161314 0.8557619,1.7985856 c 0.00572,-0.017558 -0.07988,0.1429176 -0.1617478,0.2795696 -0.040934,0.068351 -0.082447,0.141311 -0.1173056,0.214457 -0.034859,0.073146 -0.069638,0.1380463 -0.069763,0.2609662 -1.217e-4,0.1229198 0.03557,0.1882605 0.070279,0.2614827 0.034711,0.073247 0.07651,0.1434605 0.1173057,0.2118733 0.081592,0.1368131 0.1663991,0.2976496 0.1607158,0.2800863 -0.00569,-0.017526 0.019526,0.1632389 0.033589,0.3219442 0.00704,0.079296 0.014684,0.1606161 0.029456,0.2402954 0.014764,0.079742 0.022966,0.1557634 0.095083,0.2552817 0.072115,0.099531 0.1412954,0.130652 0.2123916,0.1694987 0.071099,0.038784 0.1469443,0.071021 0.2201413,0.1023191 0.1463886,0.062722 0.3099884,0.1442029 0.2950712,0.1333254 -0.014922,-0.010713 0.109855,0.1175901 0.2144581,0.2377115 0.0523,0.060092 0.1060847,0.1214847 0.1648486,0.1772502 0.058759,0.055816 0.1110827,0.1117997 0.2278909,0.1498616 0.1168056,0.03815 0.190463,0.023744 0.2707852,0.013435 0.080328,-0.010432 0.1605915,-0.026186 0.2382282,-0.043925 0.1552734,-0.035351 0.3341952,-0.068207 0.3157432,-0.068213 -0.018441,-1.85e-5 0.1595093,0.03148 0.3147086,0.06718 0.077602,0.017849 0.1568874,0.035993 0.2371963,0.046509 0.08029,0.010893 0.1544188,0.024387 0.2713012,-0.013436 C 4.1130209,5.0582357 4.1651597,5.0033666 4.2240321,4.9477472 4.2829046,4.8921276 4.3395598,4.8298801 4.3919791,4.7699803 4.4968281,4.6500042 4.6208595,4.521427 4.6059211,4.5322686 4.5909722,4.543008 4.7555086,4.4639712 4.9020269,4.4015274 4.9752821,4.3703558 5.0494475,4.3357186 5.1206178,4.2971408 5.1917933,4.258437 5.2601749,4.2280628 5.3324908,4.1286758 5.4048041,4.0293137 5.4141915,3.9535603 5.4291272,3.8739109 5.4440497,3.7942615 5.4514051,3.7129545 5.4585806,3.6336155 5.4729475,3.4749377 5.4999562,3.2946422 5.4942385,3.312188 5.4885235,3.3297458 5.5741162,3.1692831 5.6559863,3.0326187 5.69692,2.9642673 5.7384331,2.8938913 5.7732919,2.8207451 5.8081508,2.7475992 5.8429303,2.6827115 5.8430546,2.5597792 5.8431763,2.4368593 5.807484,2.3715183 5.7727734,2.2982965 5.7380653,2.2250492 5.6967824,2.152252 5.6559863,2.0838392 5.5743941,1.9470137 5.4890712,1.7882568 5.4947545,1.8058201 5.500443,1.8233461 5.4752282,1.6425809 5.4611656,1.4838759 5.4541542,1.404454 5.4464812,1.3232598 5.4317096,1.2435805 5.4169458,1.163838 5.4087437,1.0878171 5.3366262,0.9882988 5.2645114,0.8887679 5.1953308,0.8581762 5.1242346,0.8193169 5.0531358,0.7805332 4.977285,0.7458378 4.9040933,0.7144138 4.7577047,0.6516914 4.5941022,0.5728705 4.6090221,0.5836723 4.6239446,0.5943853 4.499683,0.4630319 4.39508,0.3428602 4.3427798,0.282768 4.2884767,0.2219049 4.2297154,0.1661267 4.1709567,0.1103108 4.1186327,0.0543271 4.0018218,0.0162652 3.8850188,-0.0218851 3.8113668,-0.0055365 3.7310393,0.004896 3.6507118,0.0153285 3.5704478,0.0315991 3.4928111,0.0493378 3.3375377,0.0846891 3.1586212,0.1174185 3.1770679,0.1175506 3.1955094,0.1175694 3.0175559,0.0834873 2.8623566,0.0477876 2.784757,0.0299388 2.7054693,0.0138614 2.6251629,0.0033458 2.5850177,-0.002102 2.5464229,-0.0089361 2.5032059,-0.0095735 Z m 0.6810957,1.3916462 a 0.26407552,0.26514563 1.664302 0 1 0.2098067,0.1069702 l 0.3064404,0.4149617 0.5167657,0.1953371 a 0.26407552,0.26514563 1.664302 0 1 0.1209225,0.403076 L 4.0395461,2.9240974 4.0142255,3.4780688 A 0.26407552,0.26514563 1.664302 0 1 3.6721272,3.7188812 L 3.1791343,3.563335 2.6468667,3.7090625 A 0.26407552,0.26514563 1.664302 0 1 2.3109702,3.4548146 L 2.3068427,2.9364998 2.0050537,2.4734789 A 0.26407552,0.26514563 1.664302 0 1 2.1404463,2.0760872 L 2.6324046,1.9133065 2.9781198,1.4807747 a 0.26407552,0.26514563 1.664302 0 1 0.1865524,-0.098185 0.26407552,0.26514563 1.664302 0 1 0.019632,-5.292e-4 z" />
<path d="m 1.32395,4.6940153 v 0.8454266 c 0,0.22689 0.085796,0.4353634 0.2320264,0.5999633 0.073102,0.0823 0.1640284,0.155757 0.2831888,0.1953369 0.1191578,0.0396 0.2710842,0.033444 0.396875,-0.029456 L 3.1755175,5.8365814 4.1149947,6.3052865 a 0.26460996,0.26460996 0 0 0 0.1188561,0.027389 c 0.4351602,0 0.7932315,-0.3580833 0.7932315,-0.7932332 V 4.6976326 c -0.1063255,0.047962 -0.2399666,0.1090639 -0.2284096,0.1007693 0.01696,-0.0123 -0.1239096,0.1336209 -0.2428795,0.2697509 -0.059468,0.06796 -0.1238859,0.1384379 -0.1906852,0.2015381 -0.066799,0.0631 -0.1257724,0.1255652 -0.2583815,0.168465 -0.1326198,0.0429 -0.2168922,0.027386 -0.3079909,0.014986 -0.09112,-0.011999 -0.1806178,-0.03241 -0.2687188,-0.05271 -0.1760908,-0.0405 -0.3779838,-0.075984 -0.3570843,-0.075964 0.020929,0 -0.1824646,0.036898 -0.3586348,0.076998 -0.08809,0.0201 -0.1791679,0.038326 -0.2702666,0.050126 -0.09113,0.0117 -0.1744398,0.027797 -0.306959,-0.015502 C 2.1045317,5.3929098 2.0448735,5.3293743 1.9781747,5.2660743 1.9114944,5.2028043 1.8504047,5.133253 1.7911063,5.0650529 1.6724248,4.9287629 1.5313251,4.7831021 1.5482267,4.795302 1.5595777,4.803572 1.4276667,4.741909 1.32395,4.6940153 Z" />
</g>
</svg>
)
export interface AchievementProps {
id: A['id']
}
export function Achievement({ id }: AchievementProps) {
const ach = achievement.get(id)
if (!ach) return null
const grade = ach?.grade ?? 0
return (
<div className={`achievement-item grade-${grade}`}>
<Icon />
<span className="name">{ach.name}</span>
<span className="description">{ach.description}</span>
</div>
)
}
export default Achievement
+10
View File
@@ -0,0 +1,10 @@
.message-item {
display: flex;
justify-content: center;
align-items: center;
height: 2rem;
border: 0.0625rem solid var(--base-text-color);
color: var(--base-text-color);
background: var(--base-background-color);
padding: 0 1rem;
}
+10
View File
@@ -0,0 +1,10 @@
import './Message.css'
export interface MsgToastProps {
message: React.ReactNode
}
export function MsgToast({ message }: MsgToastProps) {
return <div className="message-item">{message}</div>
}
export default MsgToast
+5 -1
View File
@@ -2,6 +2,7 @@ import { useAchv, useGoHome } from '@remake/hooks'
import { judge, judgeGrade, judgeGradeByValue } from '@/config'
import { rates } from '@/display'
import { achievements, type Achievement } from '@remake/data'
import { toastMsg } from '@/toast'
import './Achv.css'
interface AchievementProps {
@@ -22,6 +23,7 @@ export function AchievementItem({ id, colled }: AchievementProps) {
export function Achv() {
const goHome = useGoHome()
const showRank = () => toastMsg('别卷了,没有排行榜', 'achv-rank')
const [stats, sorted] = useAchv()
const jtimes = judge('times', stats.times)
const gtimes = judgeGrade('times', jtimes)
@@ -34,7 +36,9 @@ export function Achv() {
return (
<div className="screen achv">
<div className="controls">
<button className="secondary"></button>
<button className="secondary" onClick={showRank}>
</button>
<button className="primary" onClick={goHome}>
</button>
+3 -5
View File
@@ -6,6 +6,7 @@ import { properties } from '@/display'
import { keys } from '@remake/vitex'
import { judgeGradeByValue } from '@/config'
import { talents } from '@remake/data'
import { toastAchvs, toastMsg } from '@/toast'
import Replaced from '@/components/Replaced'
import './Alloc.css'
@@ -65,12 +66,9 @@ export function Alloc() {
const start = useStart()
const [showDetail, setShowDetail] = useState(false)
const handleNext = () => {
if (left != 0) {
// TODO: Show a warning that there are still points left
return
}
if (left != 0) return toastMsg('还有剩余点数未分配', 'alloc-toast')
const achievements = start()
// TODO: Show achievements
toastAchvs(achievements)
console.log('Achievements', achievements)
}
return (
+7 -1
View File
@@ -5,6 +5,7 @@ import { judgeGradeByValue } from '@/config'
import { characters } from '@remake/data'
import { properties } from '@/display'
import { keys } from '@remake/vitex'
import { toastMsg } from '@/toast'
import Talent from '@/components/Talent'
import './Chara.css'
@@ -89,6 +90,11 @@ export function Chara() {
const [{ unique, characters }, puller] = useCharaPuller()
const [picked, picker] = useCharaPicker()
const submit = useCharaSubmit()
const handleSubmit = () => {
if (ready) return submit(picked)
if (!picked) toastMsg('请先选择角色', 'chara-toast-pick')
else toastMsg('请先生成唯一角色', 'chara-toast-unique')
}
const ready = picked && (picked.type === 'unique' ? !!u : true)
return (
<div className="screen chara">
@@ -114,7 +120,7 @@ export function Chara() {
</button>
<button
className={ready ? 'primary' : 'error'}
onClick={ready ? () => submit(picked!) : undefined}
onClick={handleSubmit}
>
</button>
+1 -2
View File
@@ -14,8 +14,7 @@
width: auto;
display: flex;
gap: 1rem;
>.github-btn,
>.theme-toggle-btn {
>button {
width: 2.5rem;
height: 2.5rem;
padding: 0.25rem;
+14 -10
View File
@@ -1,6 +1,7 @@
import { useTalentPuller, useTalentPicker } from '@remake/hooks'
import { useTalentSubmit, useSubmitIsEnable } from '@remake/hooks'
import { PullCount, dev } from '@/config'
import { toastMsg } from '@/toast'
import Talent from '@/components/Talent'
import './Pick.css'
@@ -9,7 +10,13 @@ export function Pick() {
const [talents, picker] = useTalentPicker()
const { enabled, min, max } = useSubmitIsEnable()
const submit = useTalentSubmit()
// useEffect(puller, [])
const msg = enabled
? '下一步'
: `请选取 ${min == max ? min : `${min}~${max}`} 个天赋`
const handleSubmit = () => {
if (enabled) return submit()
toastMsg(msg, 'pick-toast')
}
if (!pulled)
return (
<div className="screen talent-pick">
@@ -33,15 +40,12 @@ export function Pick() {
</li>
))}
</ul>
{enabled ? (
<button className="primary" onClick={() => submit()}>
</button>
) : (
<button className="error">
{min == max ? min : `${min}~${max}`}
</button>
)}
<button
className={enabled ? 'primary' : 'error'}
onClick={handleSubmit}
>
{msg}
</button>
</div>
)
}
+3 -2
View File
@@ -6,6 +6,7 @@ import { achievements, events, talents } from '@remake/data'
import { properties } from '@/display'
import { AutoInterval } from '@/config'
import { format } from '@remake/vitex'
import { toastAchvs } from '@/toast/Achv'
import './Play.css'
function LogTalent({ id }: { id: number }) {
@@ -153,13 +154,13 @@ export function Play() {
const handleNext = useCallback(() => {
if (ended) return
const achievements = next()
// TODO: Show achievements
toastAchvs(achievements)
console.debug('Achievements:', achievements)
}, [ended, next])
const handleGotoSummary = useCallback(() => {
if (!ended) return
const achievements = gotoSummary()
// TODO: Show achievements
toastAchvs(achievements)
console.debug('Achievements:', achievements)
}, [ended, gotoSummary])
useLayoutEffect(() => {
+7 -2
View File
@@ -2,6 +2,7 @@ import { useEffect, useRef } from 'react'
import { usePicked, useEnd, useProfile, useIsClassic } from '@remake/hooks'
import { useEndJudge } from '@/hooks/judge'
import { properties, judgeDisplay } from '@/display'
import { toastAchvs, toastMsg } from '@/toast'
import Talent from '@/components/Talent'
import './Summary.css'
@@ -54,9 +55,13 @@ function TalentList({ picked, picker }: TalentListProps) {
export default function Summary() {
const isClassic = useIsClassic()
const [locked, picker, end] = useEnd()
const handlePicker = (id: number) => {
if (isClassic) picker(id)
else toastMsg('名人天赋不可锁定', 'summary-toast')
}
const handleEnd = () => {
const achievements = end()
// TODO: Show achievements
toastAchvs(achievements)
console.log('Achievements', achievements)
}
return (
@@ -68,7 +73,7 @@ export default function Summary() {
? '你可以锁定一个天赋,下辈子还能抽到'
: '名人天赋不可锁定'}
</div>
<TalentList picked={locked} picker={picker} />
<TalentList picked={locked} picker={handlePicker} />
</div>
<button className="primary" onClick={handleEnd}>
+40
View File
@@ -0,0 +1,40 @@
.achv-toast-container {
position: fixed;
width: min(calc(100dvw - 2rem), 48rem);
top: 6rem;
left: 1rem;
pointer-events: none;
user-select: none;
.toast-item {
display: flex;
position: absolute;
opacity: 1;
transition: all 0.2s ease-in-out;
&.visible {
animation: achv-slide-in 0.2s ease-out forwards;
}
&.hidden {
animation: achv-fade-out 0.2s ease-out forwards;
}
}
}
@keyframes achv-slide-in {
0% { opacity: 0; transform: translateX(-100%); }
100% { opacity: 1; transform: translateX(0); }
}
@keyframes achv-slide-out {
0% { opacity: 1; transform: translateX(0); }
100% { opacity: 0; transform: translateX(-100%); }
}
@keyframes achv-fade-in {
0% { opacity: 0; }
100% { opacity: 1; }
}
@keyframes achv-fade-out {
0% { opacity: 1; }
100% { opacity: 0; }
}
+51
View File
@@ -0,0 +1,51 @@
import { useToaster, toast } from 'react-hot-toast/headless'
import Achievement from '@/components/Achievement'
import './Achv.css'
export function ToastAchvContainer() {
const { toasts, handlers } = useToaster({ duration: 4000 }, 'achv-toast')
const { startPause, endPause, calculateOffset, updateHeight } = handlers
return (
<div
className="achv-toast-container"
onMouseEnter={startPause}
onMouseLeave={endPause}
>
{toasts.map(toast => {
const offset = calculateOffset(toast, {
reverseOrder: true,
gutter: 8,
})
console.log('toast offset', toast.id, offset)
return (
<div
className={`toast-item ${toast.visible ? 'visible' : 'hidden'}`}
key={toast.id}
ref={el => {
if (el && typeof toast.height !== 'number') {
const height = el.getBoundingClientRect().height
console.log('toast height', toast.id, height)
updateHeight(toast.id, height)
}
}}
style={{
top: `${offset}px`,
}}
>
{typeof toast.message == 'function'
? toast.message(toast)
: toast.message}
</div>
)
})}
</div>
)
}
export default ToastAchvContainer
export function toastAchvs(achievements: number[]) {
if (achievements.length === 0) return
for (const id of achievements) {
toast(<Achievement id={id} />, { id: '' + id, toasterId: 'achv-toast' })
}
}
+45
View File
@@ -0,0 +1,45 @@
.msg-toast-container {
position: fixed;
width: min(calc(100dvw - 2rem), 48rem);
top: 6rem;
left: 50%;
transform: translateX(-50%);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
pointer-events: none;
user-select: none;
.toast-item {
display: flex;
position: absolute;
opacity: 1;
transition: all 0.2s ease-in-out;
&.visible {
animation: msg-slide-in 0.2s ease-out forwards;
}
&.hidden {
animation: msg-fade-out 0.2s ease-out forwards;
}
}
}
@keyframes msg-slide-in {
0% { opacity: 0; transform: translateY(-100%); }
100% { opacity: 1; transform: translateY(0); }
}
@keyframes msg-slide-out {
0% { opacity: 1; transform: translateY(0); }
100% { opacity: 0; transform: translateY(-100%); }
}
@keyframes msg-fade-in {
0% { opacity: 0; }
100% { opacity: 1; }
}
@keyframes msg-fade-out {
0% { opacity: 1; }
100% { opacity: 0; }
}
+48
View File
@@ -0,0 +1,48 @@
import { useToaster, toast } from 'react-hot-toast/headless'
import Message from '@/components/Message'
import './Msg.css'
export function ToastMsgContainer() {
const { toasts, handlers } = useToaster({ duration: 1500 }, 'msg-toast')
const { startPause, endPause, calculateOffset, updateHeight } = handlers
return (
<div
className="msg-toast-container"
onMouseEnter={startPause}
onMouseLeave={endPause}
>
{toasts.map(toast => {
const offset = calculateOffset(toast, {
reverseOrder: true,
gutter: 8,
})
console.log('toast offset', toast.id, offset)
return (
<div
className={`toast-item ${toast.visible ? 'visible' : 'hidden'}`}
key={toast.id}
ref={el => {
if (el && typeof toast.height !== 'number') {
const height = el.getBoundingClientRect().height
console.log('toast height', toast.id, height)
updateHeight(toast.id, height)
}
}}
style={{
top: `${offset}px`,
}}
>
{typeof toast.message == 'function'
? toast.message(toast)
: toast.message}
</div>
)
})}
</div>
)
}
export default ToastMsgContainer
export function toastMsg(message: string, key: string) {
toast(<Message message={message} />, { id: key, toasterId: 'msg-toast' })
}
+13
View File
@@ -0,0 +1,13 @@
export * from './Msg'
export * from './Achv'
import ToastAchvContainer from './Achv'
import ToastMsgContainer from './Msg'
export const ToastContainer = () => (
<>
<ToastAchvContainer />
<ToastMsgContainer />
</>
)
export default ToastContainer
+26
View File
@@ -56,6 +56,9 @@ importers:
react-dom:
specifier: ^19.2.8
version: 19.2.8(react@19.2.8)
react-hot-toast:
specifier: ^2.6.0
version: 2.6.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8)
devDependencies:
'@types/react':
specifier: ^19.2.18
@@ -1059,6 +1062,11 @@ packages:
resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
engines: {node: '>= 0.4'}
goober@2.1.19:
resolution: {integrity: sha512-U7veizMqxyKlM58+Z5j2ngJBH/r9siDmxpvNxSw0PylF6WQvrASJEZrxh1hidRBJc2jqoBVSyOban5u8m+6Rxg==}
peerDependencies:
csstype: ^3.0.10
gopd@1.2.0:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'}
@@ -1503,6 +1511,13 @@ packages:
peerDependencies:
react: ^19.2.8
react-hot-toast@2.6.0:
resolution: {integrity: sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==}
engines: {node: '>=10'}
peerDependencies:
react: '>=16'
react-dom: '>=16'
react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
@@ -2868,6 +2883,10 @@ snapshots:
define-properties: 1.2.1
gopd: 1.2.0
goober@2.1.19(csstype@3.2.3):
dependencies:
csstype: 3.2.3
gopd@1.2.0: {}
happy-dom@20.11.2:
@@ -3280,6 +3299,13 @@ snapshots:
react: 19.2.8
scheduler: 0.27.0
react-hot-toast@2.6.0(react-dom@19.2.8(react@19.2.8))(react@19.2.8):
dependencies:
csstype: 3.2.3
goober: 2.1.19(csstype@3.2.3)
react: 19.2.8
react-dom: 19.2.8(react@19.2.8)
react-is@16.13.1: {}
react-is@17.0.2: {}