From ce4661a251e3a927288ef7579a0c4a1e93c804ef Mon Sep 17 00:00:00 2001 From: Lesia Soloviova Date: Sat, 24 Jan 2026 16:26:42 +0200 Subject: [PATCH 01/61] feat(quiz-ui): quiz UI polish - tabs, category accents, color scheme (issues #181, #193, #194) - Refactor QaTabButton to shared CategoryTabButton component - Add category accent colors to QuizCard, buttons, progress indicators - Standardize colors with CSS variables, traffic light timer - Add DynamicGridBackground to quizzes list page - Border-only answer feedback, semi-transparent progress styles --- frontend/.gitignore | 6 +-- frontend/app/[locale]/quizzes/page.tsx | 11 +++-- frontend/components/q&a/AccordionList.tsx | 4 +- frontend/components/q&a/QaSection.tsx | 12 +++--- frontend/components/quiz/CountdownTimer.tsx | 4 +- frontend/components/quiz/QuizCard.tsx | 42 +++++++++++++++---- frontend/components/quiz/QuizContainer.tsx | 26 +++++++++--- frontend/components/quiz/QuizProgress.tsx | 19 ++++++--- frontend/components/quiz/QuizQuestion.tsx | 25 +++++++---- frontend/components/quiz/QuizResult.tsx | 4 +- frontend/components/quiz/QuizzesSection.tsx | 36 +++++++++------- .../CategoryTabButton.tsx} | 13 +++--- frontend/components/ui/button.tsx | 2 +- .../data/{qaTabs.ts => categoryStyles.ts} | 6 +-- frontend/messages/en.json | 1 + frontend/messages/pl.json | 1 + frontend/messages/uk.json | 1 + 17 files changed, 142 insertions(+), 71 deletions(-) rename frontend/components/{q&a/QaTabButton.tsx => shared/CategoryTabButton.tsx} (87%) rename frontend/data/{qaTabs.ts => categoryStyles.ts} (95%) diff --git a/frontend/.gitignore b/frontend/.gitignore index e7c6bf8f..1f9b14cf 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -45,9 +45,5 @@ next-env.d.ts # Documentation (only for development) CLAUDE.md -docs/ +_dev-notes/ .claude - -!docs/ -!docs/security/ -!docs/security/origin-posture.md diff --git a/frontend/app/[locale]/quizzes/page.tsx b/frontend/app/[locale]/quizzes/page.tsx index 2f6ad19d..a060320b 100644 --- a/frontend/app/[locale]/quizzes/page.tsx +++ b/frontend/app/[locale]/quizzes/page.tsx @@ -2,6 +2,7 @@ import { getActiveQuizzes, getUserQuizzesProgress } from '@/db/queries/quiz'; import { getCurrentUser } from '@/lib/auth'; import { getTranslations } from 'next-intl/server'; import QuizzesSection from '@/components/quiz/QuizzesSection'; +import { DynamicGridBackground } from '@/components/shared/DynamicGridBackground'; type PageProps = { params: Promise<{ locale: string }> }; @@ -23,7 +24,7 @@ export default async function QuizzesPage({ params }: PageProps) { if (!quizzes.length) { return ( -
+

{t('title')}

{t('noQuizzes')} @@ -33,9 +34,10 @@ export default async function QuizzesPage({ params }: PageProps) { } return ( -

+ +
-

+

{t('practice')}

{t('title')}

@@ -45,6 +47,7 @@ export default async function QuizzesPage({ params }: PageProps) {
-
+ + ); } diff --git a/frontend/components/q&a/AccordionList.tsx b/frontend/components/q&a/AccordionList.tsx index 890e1411..544cc030 100644 --- a/frontend/components/q&a/AccordionList.tsx +++ b/frontend/components/q&a/AccordionList.tsx @@ -7,7 +7,7 @@ import { AccordionTrigger, AccordionContent, } from '@/components/ui/accordion'; -import { qaTabStyles } from '@/data/qaTabs'; +import { categoryTabStyles } from '@/data/categoryStyles'; import CodeBlock from '@/components/q&a/CodeBlock'; import type { @@ -241,7 +241,7 @@ export default function AccordionList({ items }: { items: QuestionEntry[] }) { {items.map((q, idx) => { const key = q.id ?? idx; const accent = - qaTabStyles[q.category as keyof typeof qaTabStyles]?.accent; + categoryTabStyles[q.category as keyof typeof categoryTabStyles]?.accent; return ( {categoryData.map(category => { - const slug = category.slug as keyof typeof qaTabStyles; + const slug = category.slug as keyof typeof categoryTabStyles; const value = slug as CategorySlug; return ( - ); @@ -78,7 +78,7 @@ export default function TabsSection() { currentPage={currentPage} totalPages={totalPages} onPageChange={handlePageChange} - accentColor={qaTabStyles[active as keyof typeof qaTabStyles].accent} + accentColor={categoryTabStyles[active as keyof typeof categoryTabStyles].accent} /> )}
diff --git a/frontend/components/quiz/CountdownTimer.tsx b/frontend/components/quiz/CountdownTimer.tsx index e4fb3fb5..c6d19451 100644 --- a/frontend/components/quiz/CountdownTimer.tsx +++ b/frontend/components/quiz/CountdownTimer.tsx @@ -68,13 +68,13 @@ export function CountdownTimer({ if (percentage <= 30) { return 'text-yellow-600 dark:text-yellow-400 bg-yellow-50 dark:bg-yellow-950/30 border-yellow-200 dark:border-yellow-800'; } - return 'text-blue-600 dark:text-blue-400 bg-blue-50 dark:bg-blue-950/30 border-blue-200 dark:border-blue-800'; +return 'text-green-600 dark:text-green-400 bg-green-50 dark:bg-green-950/30 border-green-200 dark:border-green-800'; }; const getProgressBarColor = () => { if (percentage <= 10) return 'bg-red-600'; if (percentage <= 30) return 'bg-yellow-600'; - return 'bg-blue-600'; + return 'bg-green-600'; }; if (!isActive) return null; diff --git a/frontend/components/quiz/QuizCard.tsx b/frontend/components/quiz/QuizCard.tsx index d30a8291..10a39370 100644 --- a/frontend/components/quiz/QuizCard.tsx +++ b/frontend/components/quiz/QuizCard.tsx @@ -4,6 +4,7 @@ import { useTranslations } from 'next-intl'; import { Link } from '@/i18n/routing'; import { Badge } from '@/components/ui/badge'; import { FileText, Clock } from 'lucide-react'; +import { categoryTabStyles } from '@/data/categoryStyles'; interface QuizCardProps { quiz: { @@ -14,6 +15,7 @@ interface QuizCardProps { questionsCount: number; timeLimitSeconds: number | null; categoryName: string | null; + categorySlug: string | null; }; userProgress?: { bestScore: number; @@ -24,16 +26,33 @@ interface QuizCardProps { export function QuizCard({ quiz, userProgress }: QuizCardProps) { const t = useTranslations('quiz.card'); + const slug = quiz.categorySlug as keyof typeof categoryTabStyles | null; + const style = slug && categoryTabStyles[slug] ? categoryTabStyles[slug] : null; + const accentColor = style?.accent ?? '#3B82F6'; // fallback blue + const percentage = userProgress && userProgress.totalQuestions > 0 ? Math.round((userProgress.bestScore / userProgress.totalQuestions) * 100) : 0; return ( -
+
+
- + {quiz.categoryName ?? t('uncategorized')} {userProgress && {t('completed')}} @@ -48,11 +67,11 @@ export function QuizCard({ quiz, userProgress }: QuizCardProps) { )}
- + {quiz.questionsCount} {t('questions')} - + {Math.floor( (quiz.timeLimitSeconds ?? quiz.questionsCount * 30) / 60 )}{' '} @@ -76,17 +95,26 @@ export function QuizCard({ quiz, userProgress }: QuizCardProps) {
)} {userProgress ? t('retake') : t('start')} +
); diff --git a/frontend/components/quiz/QuizContainer.tsx b/frontend/components/quiz/QuizContainer.tsx index 9bf55be1..07ff51e6 100644 --- a/frontend/components/quiz/QuizContainer.tsx +++ b/frontend/components/quiz/QuizContainer.tsx @@ -18,6 +18,7 @@ import type { QuizQuestionClient } from '@/db/queries/quiz'; import { ConfirmModal } from '@/components/ui/confirm-modal'; import { Button } from '@/components/ui/button'; import { FileText, Ban, AlertTriangle, Clock } from 'lucide-react'; +import { categoryTabStyles } from '@/data/categoryStyles'; interface Answer { questionId: string; @@ -149,6 +150,8 @@ export function QuizContainer({ const tRules = useTranslations('quiz.rules'); const tExit = useTranslations('quiz.exitModal'); const tQuestion = useTranslations('quiz.question'); + const categoryStyle = categorySlug ? categoryTabStyles[categorySlug as keyof typeof categoryTabStyles] : null; + const accentColor = categoryStyle?.accent ?? '#3B82F6'; const [isPending, startTransition] = useTransition(); const [state, dispatch] = useReducer(quizReducer, { status: 'rules', @@ -195,7 +198,6 @@ const { markQuitting } = useQuizGuards({ resetViolations, }); - // Sync seed to URL for language switch persistence useEffect(() => { if (!searchParams.has('seed')) { @@ -369,7 +371,7 @@ const confirmQuit = () => { if (onBackToTopics) { onBackToTopics(); } else { - window.location.href = `/${locale}/`; + window.location.href = `/${locale}/q&a`; } }; @@ -423,10 +425,21 @@ const confirmQuit = () => {
- - +
); } @@ -493,6 +506,7 @@ const confirmQuit = () => { onAnswer={handleAnswer} onNext={handleNext} isLoading={isPending} + accentColor={accentColor} /> {isAnswered ? ( @@ -103,7 +103,14 @@ export function QuizProgress({ current, total, answers }: QuizProgressProps) { )} {isCurrent && ( -
+
)}
); diff --git a/frontend/components/quiz/QuizQuestion.tsx b/frontend/components/quiz/QuizQuestion.tsx index fcd47c99..6d4898e7 100644 --- a/frontend/components/quiz/QuizQuestion.tsx +++ b/frontend/components/quiz/QuizQuestion.tsx @@ -16,6 +16,7 @@ interface QuizQuestionProps { onAnswer: (answerId: string) => void; onNext: () => void; isLoading?: boolean; + accentColor?: string; } export function QuizQuestion({ @@ -26,6 +27,7 @@ export function QuizQuestion({ onAnswer, onNext, isLoading = false, + accentColor, }: QuizQuestionProps) { const t = useTranslations('quiz.question'); const isAnswering = status === 'answering'; @@ -58,8 +60,8 @@ export function QuizQuestion({ isSelected && isAnswering && 'border-blue-500 bg-blue-50 dark:bg-blue-950', - showCorrect && 'border-green-500 bg-green-50 dark:bg-green-950', - showIncorrect && 'border-red-500 bg-red-50 dark:bg-red-950', + showCorrect && 'border-1 border-green-500', + showIncorrect && 'border-1 border-red-500', !isAnswering && 'cursor-default' )} > @@ -83,7 +85,7 @@ export function QuizQuestion({
@@ -96,7 +98,7 @@ export function QuizQuestion({
@@ -113,13 +115,22 @@ export function QuizQuestion({
)} {isRevealed && ( - + + )}
); diff --git a/frontend/components/quiz/QuizResult.tsx b/frontend/components/quiz/QuizResult.tsx index 06be09a1..6965ddc2 100644 --- a/frontend/components/quiz/QuizResult.tsx +++ b/frontend/components/quiz/QuizResult.tsx @@ -141,7 +141,9 @@ export function QuizResult({ }`}> {pointsAwarded > 0 ? t('pointsAwarded', { points: pointsAwarded }) - : t('noPointsAwarded')} + : violationsCount > 3 + ? t('disqualified') + : t('noPointsAwarded')}

)} diff --git a/frontend/components/quiz/QuizzesSection.tsx b/frontend/components/quiz/QuizzesSection.tsx index 24b7d336..c6b64510 100644 --- a/frontend/components/quiz/QuizzesSection.tsx +++ b/frontend/components/quiz/QuizzesSection.tsx @@ -3,8 +3,10 @@ import { useTranslations } from 'next-intl'; import { useParams, useSearchParams, useRouter } from 'next/navigation'; import { QuizCard } from './QuizCard'; -import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs'; +import { Tabs, TabsList, TabsContent } from '@/components/ui/tabs'; import { categoryData } from '@/data/category'; +import { CategoryTabButton } from '@/components/shared/CategoryTabButton'; +import { categoryTabStyles } from '@/data/categoryStyles'; interface Quiz { id: string; @@ -59,28 +61,32 @@ export default function QuizzesSection({ return (
- - {categoryData.map(category => ( - - {category.translations[localeKey] ?? + + {categoryData.map(category => { + const slug = category.slug as keyof typeof categoryTabStyles; + return ( + - ))} + slug + } + style={categoryTabStyles[slug]} + isActive={activeCategory === slug} + /> + ); + })} - {categoryData.map(category => { const categoryQuizzes = quizzes.filter( quiz => quiz.categorySlug === category.slug ); - return ( {categoryQuizzes.length > 0 ? ( +
{categoryQuizzes.map(quiz => ( ))}
+
) : (

diff --git a/frontend/components/q&a/QaTabButton.tsx b/frontend/components/shared/CategoryTabButton.tsx similarity index 87% rename from frontend/components/q&a/QaTabButton.tsx rename to frontend/components/shared/CategoryTabButton.tsx index 9bfe1c47..817971ca 100644 --- a/frontend/components/q&a/QaTabButton.tsx +++ b/frontend/components/shared/CategoryTabButton.tsx @@ -4,22 +4,21 @@ import Image from 'next/image'; import { TabsTrigger } from '@/components/ui/tabs'; import { cn } from '@/lib/utils'; -import type { CategorySlug } from '@/components/q&a/types'; -import type { QaTabStyle } from '@/data/qaTabs'; +import type { CategoryTabStyle } from '@/data/categoryStyles'; -type QaTabButtonProps = { - value: CategorySlug; +type CategoryTabButtonProps = { + value: string; label: string; - style: QaTabStyle; + style: CategoryTabStyle; isActive: boolean; }; -export function QaTabButton({ +export function CategoryTabButton({ value, label, style, isActive, -}: QaTabButtonProps) { +}: CategoryTabButtonProps) { return ( ; +} as const satisfies Record; diff --git a/frontend/messages/en.json b/frontend/messages/en.json index 1dbd7394..6822736c 100644 --- a/frontend/messages/en.json +++ b/frontend/messages/en.json @@ -150,6 +150,7 @@ "violations": "Quiz completed with rule violations ({count} violations). Result not counted towards leaderboard.", "pointsAwarded": "+{points} points added to rating", "noPointsAwarded": "No points awarded (result not improved)", + "disqualified": "No points awarded (disqualified due to violations)", "guestMessage": "To save your result and appear on the leaderboard, log in or sign up", "loginButton": "Log In", "signupButton": "Sign Up", diff --git a/frontend/messages/pl.json b/frontend/messages/pl.json index e1e229a2..8188020a 100644 --- a/frontend/messages/pl.json +++ b/frontend/messages/pl.json @@ -150,6 +150,7 @@ "violations": "Quiz zakończony z naruszeniami ({count} naruszeń). Wynik nie został zaliczony do rankingu.", "pointsAwarded": "+{points} punktów dodano do rankingu", "noPointsAwarded": "Nie przyznano punktów (wynik nie uległ poprawie)", + "disqualified": "Nie przyznano punktów (zdyskwalifikowany z powodu naruszeń)", "guestMessage": "Aby zapisać swój wynik i pojawić się w rankingu, zaloguj się lub zarejestruj", "loginButton": "Zaloguj się", "signupButton": "Zarejestruj się", diff --git a/frontend/messages/uk.json b/frontend/messages/uk.json index e67168f0..6b3200eb 100644 --- a/frontend/messages/uk.json +++ b/frontend/messages/uk.json @@ -150,6 +150,7 @@ "violations": "Квіз завершено з порушеннями правил ({count} порушень). Результат не зараховано до рейтингу.", "pointsAwarded": "+{points} балів додано до рейтингу", "noPointsAwarded": "Бали не нараховано (результат не покращено)", + "disqualified": "Бали не нараховано (дискваліфіковано через порушення)", "guestMessage": "Щоб зберегти результат та потрапити в рейтинг, увійдіть або зареєструйтесь", "loginButton": "Увійти", "signupButton": "Зареєструватися", From 521ce1cd6a7ec98caa9c2b973bafbc1166e9af9d Mon Sep 17 00:00:00 2001 From: Lesia Soloviova Date: Sat, 24 Jan 2026 16:29:46 +0200 Subject: [PATCH 02/61] docs: update .gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3f78fe62..8705e153 100644 --- a/.gitignore +++ b/.gitignore @@ -64,7 +64,7 @@ next-env.d.ts # Documentation (development only) .claude/ CLAUDE.md -frontend/docs/ +frontend/_dev-notes/ frontend/.env.bak From 934528953c314f35a3ccbe5cc0a7a3b47e77cd29 Mon Sep 17 00:00:00 2001 From: Lesia Soloviova Date: Sat, 24 Jan 2026 16:43:18 +0200 Subject: [PATCH 03/61] fix(quiz): align disqualification threshold with warning banner Changed violationsCount > 3 to >= 3 in QuizResult points block to match the warning banner threshold at line 124. --- frontend/components/quiz/QuizResult.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/components/quiz/QuizResult.tsx b/frontend/components/quiz/QuizResult.tsx index 6965ddc2..fad7f5ec 100644 --- a/frontend/components/quiz/QuizResult.tsx +++ b/frontend/components/quiz/QuizResult.tsx @@ -141,7 +141,7 @@ export function QuizResult({ }`}> {pointsAwarded > 0 ? t('pointsAwarded', { points: pointsAwarded }) - : violationsCount > 3 + : violationsCount >= 3 ? t('disqualified') : t('noPointsAwarded')}

From c8317ebcb7c24ac28ae9ecc4b8aa3de80a6f2fd1 Mon Sep 17 00:00:00 2001 From: Lesia Soloviova Date: Sat, 24 Jan 2026 23:12:59 +0200 Subject: [PATCH 04/61] feat(quiz-testing): add quiz unit tests - Configure Vitest for quiz module - Add test factories and setup utilities - Add quiz-crypto tests (13 tests) - Add quiz-session tests (12 tests) --- frontend/lib/tests/factories/quiz/quiz.ts | 97 +++++++++++ frontend/lib/tests/quiz/quiz-crypto.test.ts | 160 +++++++++++++++++++ frontend/lib/tests/quiz/quiz-session.test.ts | 156 ++++++++++++++++++ frontend/lib/tests/quiz/quiz-setup.test.ts | 106 ++++++++++++ frontend/lib/tests/quiz/setup.ts | 79 +++++++++ 5 files changed, 598 insertions(+) create mode 100644 frontend/lib/tests/factories/quiz/quiz.ts create mode 100644 frontend/lib/tests/quiz/quiz-crypto.test.ts create mode 100644 frontend/lib/tests/quiz/quiz-session.test.ts create mode 100644 frontend/lib/tests/quiz/quiz-setup.test.ts create mode 100644 frontend/lib/tests/quiz/setup.ts diff --git a/frontend/lib/tests/factories/quiz/quiz.ts b/frontend/lib/tests/factories/quiz/quiz.ts new file mode 100644 index 00000000..d83d7e8b --- /dev/null +++ b/frontend/lib/tests/factories/quiz/quiz.ts @@ -0,0 +1,97 @@ +/** + * Test factories for quiz module + * Creates consistent test data for unit and integration tests + */ + +export interface MockQuestion { + id: string; + answers: Array<{ + id: string; + isCorrect: boolean; + answerText?: string; + }>; +} + +export interface MockQuizSession { + status: 'rules' | 'in_progress' | 'completed'; + currentIndex: number; + answers: Array<{ + questionId: string; + selectedAnswerId: string; + isCorrect: boolean; + answeredAt: number; + }>; + questionStatus: 'answering' | 'revealed'; + selectedAnswerId: string | null; + startedAt: number | null; + savedAt: number; +} + +let questionCounter = 0; + +/** + * Creates a mock question with one correct answer + */ +export function createMockQuestion(overrides?: Partial): MockQuestion { + questionCounter++; + const qId = `q-${questionCounter}`; + + return { + id: qId, + answers: [ + { id: `${qId}-a1`, isCorrect: true, answerText: 'Correct answer' }, + { id: `${qId}-a2`, isCorrect: false, answerText: 'Wrong answer 1' }, + { id: `${qId}-a3`, isCorrect: false, answerText: 'Wrong answer 2' }, + { id: `${qId}-a4`, isCorrect: false, answerText: 'Wrong answer 3' }, + ], + ...overrides, + }; +} + +/** + * Creates multiple mock questions + */ +export function createMockQuestions(count: number): MockQuestion[] { + return Array.from({ length: count }, () => createMockQuestion()); +} + +/** + * Creates a mock quiz session for localStorage tests + */ +export function createMockQuizSession( + overrides?: Partial +): MockQuizSession { + return { + status: 'in_progress', + currentIndex: 0, + answers: [], + questionStatus: 'answering', + selectedAnswerId: null, + startedAt: Date.now(), + savedAt: Date.now(), + ...overrides, + }; +} + +/** + * Creates a correct answers map from questions + */ +export function createCorrectAnswersMap( + questions: MockQuestion[] +): Record { + const map: Record = {}; + for (const q of questions) { + const correct = q.answers.find(a => a.isCorrect); + if (correct) { + map[q.id] = correct.id; + } + } + return map; +} + +/** + * Reset counters between test files + */ +export function resetFactoryCounters(): void { + questionCounter = 0; +} diff --git a/frontend/lib/tests/quiz/quiz-crypto.test.ts b/frontend/lib/tests/quiz/quiz-crypto.test.ts new file mode 100644 index 00000000..84370f6b --- /dev/null +++ b/frontend/lib/tests/quiz/quiz-crypto.test.ts @@ -0,0 +1,160 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { + encryptAnswers, + decryptAnswers, + createEncryptedAnswersBlob, +} from '@/lib/quiz/quiz-crypto'; +import { setupQuizTestEnv, cleanupQuizTestEnv } from './setup'; +import { + createMockQuestions, + createCorrectAnswersMap, + resetFactoryCounters, +} from '../factories/quiz/quiz'; + +describe('quiz-crypto', () => { + // Setup: set encryption key before each test + beforeEach(() => { + setupQuizTestEnv(); + resetFactoryCounters(); + }); + + // Cleanup: remove encryption key after each test + afterEach(() => { + cleanupQuizTestEnv(); + }); + + describe('encryptAnswers', () => { + it('returns a base64 string', () => { + const answers = { 'q-1': 'a-1' }; + + const result = encryptAnswers(answers); + + // Base64 pattern: letters, numbers, +, /, ends with optional = + expect(result).toMatch(/^[A-Za-z0-9+/]+=*$/); + }); + + it('returns different output for same input (random IV)', () => { + const answers = { 'q-1': 'a-1' }; + + const result1 = encryptAnswers(answers); + const result2 = encryptAnswers(answers); + + // Each encryption uses random IV, so outputs differ + expect(result1).not.toBe(result2); + }); + + it('handles empty object', () => { + const result = encryptAnswers({}); + + expect(result).toBeDefined(); + expect(typeof result).toBe('string'); + }); + + it('handles multiple questions', () => { + const answers = { + 'q-1': 'a-1', + 'q-2': 'a-2', + 'q-3': 'a-3', + }; + + const result = encryptAnswers(answers); + + expect(result).toBeDefined(); + expect(result.length).toBeGreaterThan(0); + }); + }); + + describe('decryptAnswers', () => { + it('decrypts back to original data', () => { + const original = { 'q-1': 'a-1', 'q-2': 'a-2' }; + + const encrypted = encryptAnswers(original); + const decrypted = decryptAnswers(encrypted); + + expect(decrypted).toEqual(original); + }); + + it('returns null for tampered data', () => { + const original = { 'q-1': 'a-1' }; + const encrypted = encryptAnswers(original); + + // Tamper: change last 5 characters + const tampered = encrypted.slice(0, -5) + 'XXXXX'; + + const result = decryptAnswers(tampered); + + expect(result).toBeNull(); + }); + + it('returns null for invalid base64', () => { + const result = decryptAnswers('not-valid-base64!!!'); + + expect(result).toBeNull(); + }); + + it('returns null for empty string', () => { + const result = decryptAnswers(''); + + expect(result).toBeNull(); + }); + + it('returns null for truncated data', () => { + const original = { 'q-1': 'a-1' }; + const encrypted = encryptAnswers(original); + + // Truncate: remove half of the data + const truncated = encrypted.slice(0, encrypted.length / 2); + + const result = decryptAnswers(truncated); + + expect(result).toBeNull(); + }); + }); + + describe('createEncryptedAnswersBlob', () => { + it('creates encrypted blob from questions', () => { + const questions = createMockQuestions(3); + + const blob = createEncryptedAnswersBlob(questions); + + expect(blob).toBeDefined(); + expect(typeof blob).toBe('string'); + expect(blob.length).toBeGreaterThan(0); + }); + + it('encrypted blob decrypts to correct answers map', () => { + const questions = createMockQuestions(3); + const expectedMap = createCorrectAnswersMap(questions); + + const blob = createEncryptedAnswersBlob(questions); + const decrypted = decryptAnswers(blob); + + expect(decrypted).toEqual(expectedMap); + }); + + it('handles questions with no correct answer', () => { + const questions = [ + { + id: 'q-no-correct', + answers: [ + { id: 'a-1', isCorrect: false }, + { id: 'a-2', isCorrect: false }, + ], + }, + ]; + + const blob = createEncryptedAnswersBlob(questions); + const decrypted = decryptAnswers(blob); + + // Question with no correct answer is not included in map + expect(decrypted).toEqual({}); + }); + + it('handles empty questions array', () => { + const blob = createEncryptedAnswersBlob([]); + const decrypted = decryptAnswers(blob); + + expect(decrypted).toEqual({}); + }); + }); +}); diff --git a/frontend/lib/tests/quiz/quiz-session.test.ts b/frontend/lib/tests/quiz/quiz-session.test.ts new file mode 100644 index 00000000..8b204578 --- /dev/null +++ b/frontend/lib/tests/quiz/quiz-session.test.ts @@ -0,0 +1,156 @@ +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { + saveQuizSession, + loadQuizSession, + clearQuizSession, +} from '@/lib/quiz/quiz-session'; +import { installMockLocalStorage } from './setup'; +import { createMockQuizSession, resetFactoryCounters } from '../factories/quiz/quiz'; + +describe('quiz-session', () => { + beforeEach(() => { + installMockLocalStorage(); + resetFactoryCounters(); + }); + + describe('saveQuizSession', () => { + it('saves session to localStorage', () => { + const session = createMockQuizSession(); + + saveQuizSession('quiz-123', session); + + const stored = localStorage.getItem('quiz_session_quiz-123'); + expect(stored).not.toBeNull(); + }); + + it('saves session with updated savedAt timestamp', () => { + const session = createMockQuizSession({ savedAt: 0 }); + + saveQuizSession('quiz-123', session); + + const stored = JSON.parse(localStorage.getItem('quiz_session_quiz-123')!); + expect(stored.savedAt).toBeGreaterThan(0); + }); + + it('overwrites existing session', () => { + const session1 = createMockQuizSession({ currentIndex: 1 }); + const session2 = createMockQuizSession({ currentIndex: 5 }); + + saveQuizSession('quiz-123', session1); + saveQuizSession('quiz-123', session2); + + const stored = JSON.parse(localStorage.getItem('quiz_session_quiz-123')!); + expect(stored.currentIndex).toBe(5); + }); + }); + + describe('loadQuizSession', () => { + it('loads saved session', () => { + const session = createMockQuizSession({ + currentIndex: 3, + status: 'in_progress', + }); + saveQuizSession('quiz-123', session); + + const loaded = loadQuizSession('quiz-123'); + + expect(loaded).not.toBeNull(); + expect(loaded!.currentIndex).toBe(3); + expect(loaded!.status).toBe('in_progress'); + }); + + it('returns null for non-existent session', () => { + const loaded = loadQuizSession('non-existent'); + + expect(loaded).toBeNull(); + }); + + it('returns null for expired session (>30 min)', () => { + const thirtyOneMinutesAgo = Date.now() - 31 * 60 * 1000; + const session = createMockQuizSession({ + savedAt: thirtyOneMinutesAgo, + status: 'in_progress', + }); + + // Directly set localStorage to bypass saveQuizSession's timestamp update + localStorage.setItem( + 'quiz_session_quiz-123', + JSON.stringify(session) + ); + + const loaded = loadQuizSession('quiz-123'); + + expect(loaded).toBeNull(); + }); + + it('returns session if within 30 min', () => { + const twentyMinutesAgo = Date.now() - 20 * 60 * 1000; + const session = createMockQuizSession({ + savedAt: twentyMinutesAgo, + status: 'in_progress', + }); + + localStorage.setItem( + 'quiz_session_quiz-123', + JSON.stringify(session) + ); + + const loaded = loadQuizSession('quiz-123'); + + expect(loaded).not.toBeNull(); + }); + + it('returns null for completed session', () => { + const session = createMockQuizSession({ status: 'completed' }); + localStorage.setItem( + 'quiz_session_quiz-123', + JSON.stringify({ ...session, savedAt: Date.now() }) + ); + + const loaded = loadQuizSession('quiz-123'); + + expect(loaded).toBeNull(); + }); + + it('returns null for rules session', () => { + const session = createMockQuizSession({ status: 'rules' }); + localStorage.setItem( + 'quiz_session_quiz-123', + JSON.stringify({ ...session, savedAt: Date.now() }) + ); + + const loaded = loadQuizSession('quiz-123'); + + expect(loaded).toBeNull(); + }); + + it('clears expired session from storage', () => { + const thirtyOneMinutesAgo = Date.now() - 31 * 60 * 1000; + const session = createMockQuizSession({ savedAt: thirtyOneMinutesAgo }); + localStorage.setItem( + 'quiz_session_quiz-123', + JSON.stringify(session) + ); + + loadQuizSession('quiz-123'); + + expect(localStorage.getItem('quiz_session_quiz-123')).toBeNull(); + }); + }); + + describe('clearQuizSession', () => { + it('removes session from localStorage', () => { + const session = createMockQuizSession(); + saveQuizSession('quiz-123', session); + + clearQuizSession('quiz-123'); + + expect(localStorage.getItem('quiz_session_quiz-123')).toBeNull(); + }); + + it('does nothing if session does not exist', () => { + // Should not throw + expect(() => clearQuizSession('non-existent')).not.toThrow(); + }); + }); +}); diff --git a/frontend/lib/tests/quiz/quiz-setup.test.ts b/frontend/lib/tests/quiz/quiz-setup.test.ts new file mode 100644 index 00000000..3f4b1004 --- /dev/null +++ b/frontend/lib/tests/quiz/quiz-setup.test.ts @@ -0,0 +1,106 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { + createMockQuestion, + createMockQuestions, + createMockQuizSession, + createCorrectAnswersMap, + resetFactoryCounters, +} from '../factories/quiz/quiz'; +import { + setupQuizTestEnv, + cleanupQuizTestEnv, + installMockLocalStorage, + TEST_ENCRYPTION_KEY, +} from './setup'; + +describe('Quiz Test Infrastructure', () => { + describe('factories', () => { + beforeEach(() => { + resetFactoryCounters(); + }); + + it('createMockQuestion generates valid question', () => { + const question = createMockQuestion(); + + expect(question.id).toBe('q-1'); + expect(question.answers).toHaveLength(4); + expect(question.answers.filter(a => a.isCorrect)).toHaveLength(1); + }); + + it('createMockQuestions generates multiple questions', () => { + const questions = createMockQuestions(5); + + expect(questions).toHaveLength(5); + expect(questions[0].id).toBe('q-1'); + expect(questions[4].id).toBe('q-5'); + }); + + it('createCorrectAnswersMap extracts correct answers', () => { + const questions = createMockQuestions(3); + const map = createCorrectAnswersMap(questions); + + expect(Object.keys(map)).toHaveLength(3); + expect(map['q-1']).toBe('q-1-a1'); + expect(map['q-2']).toBe('q-2-a1'); + }); + + it('createMockQuizSession creates valid session', () => { + const session = createMockQuizSession(); + + expect(session.status).toBe('in_progress'); + expect(session.currentIndex).toBe(0); + expect(session.answers).toEqual([]); + }); + + it('createMockQuizSession accepts overrides', () => { + const session = createMockQuizSession({ + status: 'completed', + currentIndex: 5, + }); + + expect(session.status).toBe('completed'); + expect(session.currentIndex).toBe(5); + }); + }); + + describe('environment setup', () => { + afterEach(() => { + cleanupQuizTestEnv(); + }); + + it('setupQuizTestEnv sets encryption key', () => { + expect(process.env.QUIZ_ENCRYPTION_KEY).toBeUndefined(); + + setupQuizTestEnv(); + + expect(process.env.QUIZ_ENCRYPTION_KEY).toBe(TEST_ENCRYPTION_KEY); + }); + + it('cleanupQuizTestEnv removes encryption key', () => { + setupQuizTestEnv(); + cleanupQuizTestEnv(); + + expect(process.env.QUIZ_ENCRYPTION_KEY).toBeUndefined(); + }); + }); + + describe('localStorage mock', () => { + it('installMockLocalStorage provides working storage', () => { + const { store } = installMockLocalStorage(); + + localStorage.setItem('test-key', 'test-value'); + + expect(localStorage.getItem('test-key')).toBe('test-value'); + expect(store['test-key']).toBe('test-value'); + }); + + it('localStorage.removeItem works', () => { + installMockLocalStorage(); + + localStorage.setItem('key', 'value'); + localStorage.removeItem('key'); + + expect(localStorage.getItem('key')).toBeNull(); + }); + }); +}); diff --git a/frontend/lib/tests/quiz/setup.ts b/frontend/lib/tests/quiz/setup.ts new file mode 100644 index 00000000..237e1b38 --- /dev/null +++ b/frontend/lib/tests/quiz/setup.ts @@ -0,0 +1,79 @@ +import { vi, beforeEach, afterEach } from 'vitest'; + +/** + * 32-byte hex key for AES-256 (64 hex characters) + * Only used in tests — not a real secret + */ +export const TEST_ENCRYPTION_KEY = + 'a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2'; + +/** + * Sets up test environment with encryption key + * Call in beforeEach() or at top of test file + */ +export function setupQuizTestEnv(): void { + process.env.QUIZ_ENCRYPTION_KEY = TEST_ENCRYPTION_KEY; +} + +/** + * Cleans up test environment + * Call in afterEach() to prevent test pollution + */ +export function cleanupQuizTestEnv(): void { + delete process.env.QUIZ_ENCRYPTION_KEY; +} + +/** + * Mock localStorage for session tests + * Returns object with mock functions for assertions + */ +export function createMockLocalStorage() { + const store: Record = {}; + + const mockStorage = { + getItem: vi.fn((key: string) => store[key] ?? null), + setItem: vi.fn((key: string, value: string) => { + store[key] = value; + }), + removeItem: vi.fn((key: string) => { + delete store[key]; + }), + clear: vi.fn(() => { + Object.keys(store).forEach(key => delete store[key]); + }), + get length() { + return Object.keys(store).length; + }, + key: vi.fn((index: number) => Object.keys(store)[index] ?? null), + }; + + return { + storage: mockStorage, + store, // direct access to data for assertions + }; +} + +/** + * Installs mock localStorage on global object + * Use in beforeEach() for session tests + */ +export function installMockLocalStorage() { + const mock = createMockLocalStorage(); + + Object.defineProperty(globalThis, 'localStorage', { + value: mock.storage, + writable: true, + configurable: true, + }); + + // Mock window for browser environment check + if (typeof globalThis.window === 'undefined') { + Object.defineProperty(globalThis, 'window', { + value: { localStorage: mock.storage }, + writable: true, + configurable: true, + }); + } + + return mock; +} From cdd20a09874095f9b43ffbc57d722adacbce19ac Mon Sep 17 00:00:00 2001 From: Lesia Soloviova Date: Sun, 25 Jan 2026 14:28:01 +0200 Subject: [PATCH 05/61] test(quiz): add integration tests for verify-answer API and useAntiCheat hook (#199) - verify-answer.test.ts: 8 tests for API endpoint - Correct/wrong answer verification - Validation errors (missing fields, tampered data) - Security: rejects modified encrypted answers - quiz-anticheat.test.ts: 10 tests for useAntiCheat hook - Detects copy, paste, context-menu, tab-switch events - Respects isActive flag - Reset and cleanup functionality Total quiz tests: 52 (9 setup + 25 unit + 18 integration) --- .../lib/tests/quiz/quiz-anticheat.test.ts | 176 ++++++++++++++++++ frontend/lib/tests/quiz/verify-answer.test.ts | 174 +++++++++++++++++ frontend/package-lock.json | 158 +++++++++++++++- frontend/package.json | 1 + frontend/vitest.config.ts | 5 + 5 files changed, 504 insertions(+), 10 deletions(-) create mode 100644 frontend/lib/tests/quiz/quiz-anticheat.test.ts create mode 100644 frontend/lib/tests/quiz/verify-answer.test.ts diff --git a/frontend/lib/tests/quiz/quiz-anticheat.test.ts b/frontend/lib/tests/quiz/quiz-anticheat.test.ts new file mode 100644 index 00000000..ea5967df --- /dev/null +++ b/frontend/lib/tests/quiz/quiz-anticheat.test.ts @@ -0,0 +1,176 @@ +// @vitest-environment jsdom +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { renderHook, act } from '@testing-library/react'; + +// Mock next-intl before importing the hook +vi.mock('next-intl', () => ({ + useTranslations: () => (key: string) => `translated:${key}`, +})); + +// Mock sonner toast +vi.mock('sonner', () => ({ + toast: { + warning: vi.fn(), + }, +})); + +import { useAntiCheat } from '@/hooks/useAntiCheat'; +import { toast } from 'sonner'; + +describe('useAntiCheat', () => { + beforeEach(() => { + vi.useFakeTimers(); + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + describe('when isActive = true (default)', () => { + it('starts with zero violations', () => { + const { result } = renderHook(() => useAntiCheat(true)); + + expect(result.current.violations).toHaveLength(0); + expect(result.current.violationsCount).toBe(0); + }); + + it('detects copy event and adds violation', () => { + const { result } = renderHook(() => useAntiCheat(true)); + + act(() => { + const event = new Event('copy', { bubbles: true, cancelable: true }); + document.dispatchEvent(event); + }); + + expect(result.current.violationsCount).toBe(1); + expect(result.current.violations[0].type).toBe('copy'); + expect(toast.warning).toHaveBeenCalledWith('translated:copy', { duration: 3000 }); + }); + + it('detects paste event and adds violation', () => { + const { result } = renderHook(() => useAntiCheat(true)); + + act(() => { + const event = new Event('paste', { bubbles: true, cancelable: true }); + document.dispatchEvent(event); + }); + + expect(result.current.violationsCount).toBe(1); + expect(result.current.violations[0].type).toBe('paste'); + }); + + it('detects context-menu event and adds violation', () => { + const { result } = renderHook(() => useAntiCheat(true)); + + act(() => { + const event = new Event('contextmenu', { bubbles: true, cancelable: true }); + document.dispatchEvent(event); + }); + + expect(result.current.violationsCount).toBe(1); + expect(result.current.violations[0].type).toBe('context-menu'); + }); + + it('detects tab switch (visibilitychange) and adds violation', () => { + const { result } = renderHook(() => useAntiCheat(true)); + + // Mock document.hidden = true (tab became hidden) + Object.defineProperty(document, 'hidden', { + value: true, + writable: true, + configurable: true, + }); + + act(() => { + document.dispatchEvent(new Event('visibilitychange')); + }); + + expect(result.current.violationsCount).toBe(1); + expect(result.current.violations[0].type).toBe('tab-switch'); + expect(result.current.isTabActive).toBe(false); + + // Restore + Object.defineProperty(document, 'hidden', { + value: false, + writable: true, + configurable: true, + }); + }); + + it('accumulates multiple violations', () => { + const { result } = renderHook(() => useAntiCheat(true)); + + act(() => { + document.dispatchEvent(new Event('copy')); + document.dispatchEvent(new Event('paste')); + document.dispatchEvent(new Event('contextmenu')); + }); + + expect(result.current.violationsCount).toBe(3); + }); + + it('sets showWarning to true when violation occurs', () => { + const { result } = renderHook(() => useAntiCheat(true)); + + expect(result.current.showWarning).toBe(false); + + act(() => { + document.dispatchEvent(new Event('copy')); + }); + + expect(result.current.showWarning).toBe(true); + }); + + + it('resetViolations clears all violations', () => { + const { result } = renderHook(() => useAntiCheat(true)); + + act(() => { + document.dispatchEvent(new Event('copy')); + document.dispatchEvent(new Event('paste')); + }); + + expect(result.current.violationsCount).toBe(2); + + act(() => { + result.current.resetViolations(); + }); + + expect(result.current.violationsCount).toBe(0); + expect(result.current.violations).toHaveLength(0); + }); + }); + + describe('when isActive = false', () => { + it('does not track violations', () => { + const { result } = renderHook(() => useAntiCheat(false)); + + act(() => { + document.dispatchEvent(new Event('copy')); + document.dispatchEvent(new Event('paste')); + document.dispatchEvent(new Event('contextmenu')); + }); + + expect(result.current.violationsCount).toBe(0); + expect(toast.warning).not.toHaveBeenCalled(); + }); + }); + + describe('cleanup on unmount', () => { + it('removes event listeners on unmount', () => { + const { unmount } = renderHook(() => useAntiCheat(true)); + + const removeEventListenerSpy = vi.spyOn(document, 'removeEventListener'); + + unmount(); + + expect(removeEventListenerSpy).toHaveBeenCalledWith('copy', expect.any(Function)); + expect(removeEventListenerSpy).toHaveBeenCalledWith('paste', expect.any(Function)); + expect(removeEventListenerSpy).toHaveBeenCalledWith('contextmenu', expect.any(Function)); + expect(removeEventListenerSpy).toHaveBeenCalledWith('visibilitychange', expect.any(Function)); + + removeEventListenerSpy.mockRestore(); + }); + }); +}); diff --git a/frontend/lib/tests/quiz/verify-answer.test.ts b/frontend/lib/tests/quiz/verify-answer.test.ts new file mode 100644 index 00000000..8bccbd60 --- /dev/null +++ b/frontend/lib/tests/quiz/verify-answer.test.ts @@ -0,0 +1,174 @@ +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { NextRequest } from 'next/server'; +import { POST } from '@/app/api/quiz/verify-answer/route'; +import { encryptAnswers } from '@/lib/quiz/quiz-crypto'; +import { setupQuizTestEnv, cleanupQuizTestEnv } from './setup'; +import { + createMockQuestions, + createCorrectAnswersMap, + resetFactoryCounters, +} from '../factories/quiz/quiz'; + +/** + * Creates a mock NextRequest for POST /api/quiz/verify-answer + */ +function createVerifyRequest(body: unknown): NextRequest { + return new NextRequest('http://localhost/api/quiz/verify-answer', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body), + }); +} + +describe('POST /api/quiz/verify-answer', () => { + beforeEach(() => { + setupQuizTestEnv(); + resetFactoryCounters(); + }); + + afterEach(() => { + cleanupQuizTestEnv(); + }); + + describe('successful verification', () => { + it('returns isCorrect: true for correct answer', async () => { + const questions = createMockQuestions(3); + const correctAnswersMap = createCorrectAnswersMap(questions); + const encryptedAnswers = encryptAnswers(correctAnswersMap); + + const questionId = questions[0].id; + const correctAnswerId = correctAnswersMap[questionId]; + + const request = createVerifyRequest({ + questionId, + answerId: correctAnswerId, + encryptedAnswers, + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data.isCorrect).toBe(true); + }); + + it('returns isCorrect: false for wrong answer', async () => { + const questions = createMockQuestions(3); + const correctAnswersMap = createCorrectAnswersMap(questions); + const encryptedAnswers = encryptAnswers(correctAnswersMap); + + const questionId = questions[0].id; + const wrongAnswerId = questions[0].answers[1].id; // second answer is wrong + + const request = createVerifyRequest({ + questionId, + answerId: wrongAnswerId, + encryptedAnswers, + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data.isCorrect).toBe(false); + }); + }); + + describe('validation errors (400)', () => { + it('returns 400 for missing questionId', async () => { + const questions = createMockQuestions(1); + const correctAnswersMap = createCorrectAnswersMap(questions); + const encryptedAnswers = encryptAnswers(correctAnswersMap); + + const request = createVerifyRequest({ + answerId: 'some-answer', + encryptedAnswers, + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(400); + expect(data.error).toBe('Missing required fields'); + }); + + it('returns 400 for missing answerId', async () => { + const questions = createMockQuestions(1); + const correctAnswersMap = createCorrectAnswersMap(questions); + const encryptedAnswers = encryptAnswers(correctAnswersMap); + + const request = createVerifyRequest({ + questionId: questions[0].id, + encryptedAnswers, + }); + + const response = await POST(request); + + expect(response.status).toBe(400); + }); + + it('returns 400 for missing encryptedAnswers', async () => { + const request = createVerifyRequest({ + questionId: 'q-1', + answerId: 'a-1', + }); + + const response = await POST(request); + + expect(response.status).toBe(400); + }); + + it('returns 400 for tampered encryptedAnswers', async () => { + const questions = createMockQuestions(1); + const correctAnswersMap = createCorrectAnswersMap(questions); + const encryptedAnswers = encryptAnswers(correctAnswersMap); + + // Tamper with the encrypted data + const tamperedAnswers = encryptedAnswers.slice(0, -10) + 'XXXXXXXXXX'; + + const request = createVerifyRequest({ + questionId: questions[0].id, + answerId: questions[0].answers[0].id, + encryptedAnswers: tamperedAnswers, + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(400); + expect(data.error).toBe('Invalid encrypted data'); + }); + + it('returns 400 for invalid base64 encryptedAnswers', async () => { + const request = createVerifyRequest({ + questionId: 'q-1', + answerId: 'a-1', + encryptedAnswers: 'not-valid-base64!!!', + }); + + const response = await POST(request); + + expect(response.status).toBe(400); + }); + }); + + describe('not found (404)', () => { + it('returns 404 for unknown questionId', async () => { + const questions = createMockQuestions(1); + const correctAnswersMap = createCorrectAnswersMap(questions); + const encryptedAnswers = encryptAnswers(correctAnswersMap); + + const request = createVerifyRequest({ + questionId: 'unknown-question-id', + answerId: 'some-answer', + encryptedAnswers, + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(404); + expect(data.error).toBe('Question not found'); + }); + }); +}); diff --git a/frontend/package-lock.json b/frontend/package-lock.json index ba86bae7..b35b2343 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -53,6 +53,7 @@ "@types/nodemailer": "^7.0.4", "@types/react": "^19.2.8", "@types/react-dom": "^19", + "@vitest/coverage-v8": "^4.0.18", "drizzle-kit": "^0.18.1", "eslint": "^9", "eslint-config-next": "16.0.1", @@ -1150,6 +1151,16 @@ "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@csstools/color-helpers": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", @@ -5586,6 +5597,37 @@ } } }, + "node_modules/@vitest/coverage-v8": { + "version": "4.0.18", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.18.tgz", + "integrity": "sha512-7i+N2i0+ME+2JFZhfuz7Tg/FqKtilHjGyGvoHYQ6iLV0zahbsJ9sljC9OcFcPDbhYKCet+sG8SsVqlyGvPflZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.0.18", + "ast-v8-to-istanbul": "^0.3.10", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.1", + "obug": "^2.1.1", + "std-env": "^3.10.0", + "tinyrainbow": "^3.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.0.18", + "vitest": "4.0.18" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, "node_modules/@vitest/expect": { "version": "4.0.18", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.0.18.tgz", @@ -5976,6 +6018,25 @@ "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", "dev": true }, + "node_modules/ast-v8-to-istanbul": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.10.tgz", + "integrity": "sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^9.0.1" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, "node_modules/async-function": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", @@ -8718,6 +8779,13 @@ "node": "^20.19.0 || ^22.12.0 || >=24.0.0" } }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -9260,6 +9328,45 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/iterator.prototype": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", @@ -9878,6 +9985,47 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/magicast": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.1.tgz", + "integrity": "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@babel/types": "^7.28.5", + "source-map-js": "^1.2.1" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/make-dir/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -10193,16 +10341,6 @@ } } }, - "node_modules/next-intl/node_modules/@swc/helpers": { - "version": "0.5.18", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.18.tgz", - "integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==", - "optional": true, - "peer": true, - "dependencies": { - "tslib": "^2.8.0" - } - }, "node_modules/next-themes": { "version": "0.4.6", "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", diff --git a/frontend/package.json b/frontend/package.json index 6a58522d..26ff0c9b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -64,6 +64,7 @@ "@types/nodemailer": "^7.0.4", "@types/react": "^19.2.8", "@types/react-dom": "^19", + "@vitest/coverage-v8": "^4.0.18", "drizzle-kit": "^0.18.1", "eslint": "^9", "eslint-config-next": "16.0.1", diff --git a/frontend/vitest.config.ts b/frontend/vitest.config.ts index b880cfdc..2fbd4ebf 100644 --- a/frontend/vitest.config.ts +++ b/frontend/vitest.config.ts @@ -15,5 +15,10 @@ export default defineConfig({ environment: 'node', include: ['lib/tests/**/*.test.ts', 'components/tests/**/*.test.tsx'], globals: true, + coverage: { + provider: 'v8', + reporter: ['text', 'html'], + include: ['lib/quiz/**', 'hooks/**', 'app/api/quiz/**'], + }, }, }); From d3cad15cbd84fb36f3d8882ec5f8976815fc9f98 Mon Sep 17 00:00:00 2001 From: Lesia Soloviova Date: Sun, 25 Jan 2026 17:27:59 +0200 Subject: [PATCH 06/61] test(quiz): expand test coverage to 90%+ with hooks, API routes, and UI flow Add 28 new tests covering: - useQuizSession hook (6 tests) - useQuizGuards hook (8 tests) - guest-quiz storage (5 tests) - guest-result API route (5 tests) - quiz-slug API route (3 tests) - QuizContainer UI flow (1 test) Coverage: 35% -> 90.94% (quiz scope) Tests: 52 -> 80 --- .../quiz/tests/quiz-container-flow.test.tsx | 118 +++ .../app/api/quiz/[slug]/index.html | 116 +++ .../app/api/quiz/[slug]/route.ts.html | 238 +++++++ .../app/api/quiz/guest-result/index.html | 116 +++ .../app/api/quiz/guest-result/route.ts.html | 670 ++++++++++++++++++ .../app/api/quiz/verify-answer/index.html | 116 +++ .../app/api/quiz/verify-answer/route.ts.html | 232 ++++++ frontend/coverage-quiz/base.css | 224 ++++++ frontend/coverage-quiz/block-navigation.js | 87 +++ frontend/coverage-quiz/favicon.png | Bin 0 -> 445 bytes frontend/coverage-quiz/hooks/index.html | 131 ++++ .../coverage-quiz/hooks/useQuizGuards.ts.html | 430 +++++++++++ .../hooks/useQuizSession.ts.html | 313 ++++++++ frontend/coverage-quiz/index.html | 176 +++++ .../coverage-quiz/lib/quiz/guest-quiz.ts.html | 244 +++++++ frontend/coverage-quiz/lib/quiz/index.html | 161 +++++ .../lib/quiz/quiz-crypto.ts.html | 331 +++++++++ .../lib/quiz/quiz-session.ts.html | 283 ++++++++ .../lib/quiz/quiz-storage-keys.ts.html | 100 +++ frontend/coverage-quiz/prettify.css | 1 + frontend/coverage-quiz/prettify.js | 2 + frontend/coverage-quiz/sort-arrow-sprite.png | Bin 0 -> 138 bytes frontend/coverage-quiz/sorter.js | 210 ++++++ frontend/lib/tests/quiz/guest-quiz.test.ts | 101 +++ .../lib/tests/quiz/guest-result-route.test.ts | 179 +++++ .../lib/tests/quiz/quiz-slug-route.test.ts | 95 +++ .../lib/tests/quiz/use-quiz-guards.test.ts | 161 +++++ .../lib/tests/quiz/use-quiz-session.test.ts | 182 +++++ frontend/vitest.config.ts | 2 +- 29 files changed, 5018 insertions(+), 1 deletion(-) create mode 100644 frontend/components/quiz/tests/quiz-container-flow.test.tsx create mode 100644 frontend/coverage-quiz/app/api/quiz/[slug]/index.html create mode 100644 frontend/coverage-quiz/app/api/quiz/[slug]/route.ts.html create mode 100644 frontend/coverage-quiz/app/api/quiz/guest-result/index.html create mode 100644 frontend/coverage-quiz/app/api/quiz/guest-result/route.ts.html create mode 100644 frontend/coverage-quiz/app/api/quiz/verify-answer/index.html create mode 100644 frontend/coverage-quiz/app/api/quiz/verify-answer/route.ts.html create mode 100644 frontend/coverage-quiz/base.css create mode 100644 frontend/coverage-quiz/block-navigation.js create mode 100644 frontend/coverage-quiz/favicon.png create mode 100644 frontend/coverage-quiz/hooks/index.html create mode 100644 frontend/coverage-quiz/hooks/useQuizGuards.ts.html create mode 100644 frontend/coverage-quiz/hooks/useQuizSession.ts.html create mode 100644 frontend/coverage-quiz/index.html create mode 100644 frontend/coverage-quiz/lib/quiz/guest-quiz.ts.html create mode 100644 frontend/coverage-quiz/lib/quiz/index.html create mode 100644 frontend/coverage-quiz/lib/quiz/quiz-crypto.ts.html create mode 100644 frontend/coverage-quiz/lib/quiz/quiz-session.ts.html create mode 100644 frontend/coverage-quiz/lib/quiz/quiz-storage-keys.ts.html create mode 100644 frontend/coverage-quiz/prettify.css create mode 100644 frontend/coverage-quiz/prettify.js create mode 100644 frontend/coverage-quiz/sort-arrow-sprite.png create mode 100644 frontend/coverage-quiz/sorter.js create mode 100644 frontend/lib/tests/quiz/guest-quiz.test.ts create mode 100644 frontend/lib/tests/quiz/guest-result-route.test.ts create mode 100644 frontend/lib/tests/quiz/quiz-slug-route.test.ts create mode 100644 frontend/lib/tests/quiz/use-quiz-guards.test.ts create mode 100644 frontend/lib/tests/quiz/use-quiz-session.test.ts diff --git a/frontend/components/quiz/tests/quiz-container-flow.test.tsx b/frontend/components/quiz/tests/quiz-container-flow.test.tsx new file mode 100644 index 00000000..88b87008 --- /dev/null +++ b/frontend/components/quiz/tests/quiz-container-flow.test.tsx @@ -0,0 +1,118 @@ +// @vitest-environment jsdom +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; + +const { routerReplace, routerBack, toastMock } = vi.hoisted(() => ({ + routerReplace: vi.fn(), + routerBack: vi.fn(), + toastMock: Object.assign(vi.fn(), { error: vi.fn() }), +})); + +vi.mock('next-intl', () => ({ + useTranslations: () => (key: string) => key, + useLocale: () => 'en', +})); + +vi.mock('next/navigation', () => ({ + useRouter: () => ({ replace: routerReplace, back: routerBack }), + useSearchParams: () => new URLSearchParams(), +})); + +vi.mock('sonner', () => ({ toast: toastMock })); + + +vi.mock('@/hooks/useAntiCheat', () => ({ + useAntiCheat: () => ({ + violations: [], + violationsCount: 0, + resetViolations: vi.fn(), + }), +})); + +vi.mock('@/hooks/useQuizGuards', () => ({ + useQuizGuards: () => ({ markQuitting: vi.fn() }), +})); + +vi.mock('@/hooks/useQuizSession', () => ({ + useQuizSession: () => {}, +})); + +vi.mock('@/components/quiz/CountdownTimer', () => ({ + CountdownTimer: () => null, +})); + +vi.mock('@/actions/quiz', () => ({ + submitQuizAttempt: vi.fn(async () => ({ success: true, pointsAwarded: 10 })), +})); + +import { QuizContainer } from '@/components/quiz/QuizContainer'; + +describe('QuizContainer flow', () => { + let fetchMock: ReturnType; + + beforeEach(() => { + fetchMock = vi.fn().mockResolvedValue({ + ok: true, + json: async () => ({ isCorrect: true }), + }); + vi.stubGlobal('fetch', fetchMock); + localStorage.clear(); + sessionStorage.clear(); + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.unstubAllGlobals(); + }); + + it('runs a guest flow from rules to result', async () => { + const questions = [ + { + id: 'q1', + displayOrder: 1, + difficulty: null, + questionText: 'Question 1', + explanation: null, + answers: [ + { id: 'a1', displayOrder: 1, answerText: 'Answer 1' }, + { id: 'a2', displayOrder: 2, answerText: 'Answer 2' }, + ], + }, + ]; + + render( + + ); + + fireEvent.click(screen.getByRole('button', { name: 'startButton' })); + + expect(await screen.findByText('Question 1')).toBeTruthy(); + + fireEvent.click(screen.getByText('Answer 1')); + + await waitFor(() => { + expect(fetchMock).toHaveBeenCalledTimes(1); + }); + + const [url, options] = fetchMock.mock.calls[0]; + const payload = JSON.parse(options.body); + + expect(url).toBe('/api/quiz/verify-answer'); + expect(payload.questionId).toBe('q1'); + expect(payload.answerId).toBe('a1'); + expect(payload.encryptedAnswers).toBe('encrypted'); + + fireEvent.click(await screen.findByRole('button', { name: 'nextButton' })); + + expect(await screen.findByRole('button', { name: 'loginButton' })).toBeTruthy(); + }); +}); diff --git a/frontend/coverage-quiz/app/api/quiz/[slug]/index.html b/frontend/coverage-quiz/app/api/quiz/[slug]/index.html new file mode 100644 index 00000000..1e79676d --- /dev/null +++ b/frontend/coverage-quiz/app/api/quiz/[slug]/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for app/api/quiz/[slug] + + + + + + + + + +
+
+

All files app/api/quiz/[slug]

+
+ +
+ 100% + Statements + 13/13 +
+ + +
+ 75% + Branches + 3/4 +
+ + +
+ 100% + Functions + 3/3 +
+ + +
+ 100% + Lines + 12/12 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
route.ts +
+
100%13/1375%3/4100%3/3100%12/12
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage-quiz/app/api/quiz/[slug]/route.ts.html b/frontend/coverage-quiz/app/api/quiz/[slug]/route.ts.html new file mode 100644 index 00000000..6bb40b45 --- /dev/null +++ b/frontend/coverage-quiz/app/api/quiz/[slug]/route.ts.html @@ -0,0 +1,238 @@ + + + + + + Code coverage report for app/api/quiz/[slug]/route.ts + + + + + + + + + +
+
+

All files / app/api/quiz/[slug] route.ts

+
+ +
+ 100% + Statements + 13/13 +
+ + +
+ 75% + Branches + 3/4 +
+ + +
+ 100% + Functions + 3/3 +
+ + +
+ 100% + Lines + 12/12 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52  +  +  +  +  +  +  +3x +3x +  +3x +3x +  +2x +1x +  +  +1x +  +1x +  +  +  +  +1x +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +1x +1x +  +  +  +  +  + 
import { NextRequest, NextResponse } from 'next/server';
+import { getQuizBySlug, getQuizQuestionsRandomized } from '@/db/queries/quiz';
+ 
+export async function GET(
+  request: NextRequest,
+  { params }: { params: Promise<{ slug: string }> }
+) {
+  const { slug } = await params;
+  const locale = request.nextUrl.searchParams.get('locale') || 'uk';
+ 
+  try {
+    const quiz = await getQuizBySlug(slug, locale);
+ 
+    if (!quiz) {
+      return NextResponse.json({ error: 'Quiz not found' }, { status: 404 });
+    }
+ 
+    const questions = await getQuizQuestionsRandomized(quiz.id, locale);
+ 
+    const formattedQuestions = questions.map((q, index) => ({
+      id: q.id,
+      number: index + 1,
+      text: q.questionText,
+      difficulty: q.difficulty,
+      answers: q.answers.map(a => ({
+        id: a.id,
+        text: a.answerText,
+        isCorrect: a.isCorrect,
+      })),
+      explanation: q.explanation,
+    }));
+ 
+    return NextResponse.json({
+      quiz: {
+        id: quiz.id,
+        slug: quiz.slug,
+        title: quiz.title,
+        description: quiz.description,
+        questionsCount: quiz.questionsCount,
+        timeLimitSeconds: quiz.timeLimitSeconds,
+      },
+      questions: formattedQuestions,
+    });
+  } catch (error) {
+    console.error('Error fetching quiz:', error);
+    return NextResponse.json(
+      { error: 'Internal server error' },
+      { status: 500 }
+    );
+  }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage-quiz/app/api/quiz/guest-result/index.html b/frontend/coverage-quiz/app/api/quiz/guest-result/index.html new file mode 100644 index 00000000..d89af522 --- /dev/null +++ b/frontend/coverage-quiz/app/api/quiz/guest-result/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for app/api/quiz/guest-result + + + + + + + + + +
+
+

All files app/api/quiz/guest-result

+
+ +
+ 85% + Statements + 51/60 +
+ + +
+ 75.75% + Branches + 25/33 +
+ + +
+ 80% + Functions + 4/5 +
+ + +
+ 85.71% + Lines + 48/56 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
route.ts +
+
85%51/6075.75%25/3380%4/585.71%48/56
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage-quiz/app/api/quiz/guest-result/route.ts.html b/frontend/coverage-quiz/app/api/quiz/guest-result/route.ts.html new file mode 100644 index 00000000..463ce5b9 --- /dev/null +++ b/frontend/coverage-quiz/app/api/quiz/guest-result/route.ts.html @@ -0,0 +1,670 @@ + + + + + + Code coverage report for app/api/quiz/guest-result/route.ts + + + + + + + + + +
+
+

All files / app/api/quiz/guest-result route.ts

+
+ +
+ 85% + Statements + 51/60 +
+ + +
+ 75.75% + Branches + 25/33 +
+ + +
+ 80% + Functions + 4/5 +
+ + +
+ 85.71% + Lines + 48/56 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116 +117 +118 +119 +120 +121 +122 +123 +124 +125 +126 +127 +128 +129 +130 +131 +132 +133 +134 +135 +136 +137 +138 +139 +140 +141 +142 +143 +144 +145 +146 +147 +148 +149 +150 +151 +152 +153 +154 +155 +156 +157 +158 +159 +160 +161 +162 +163 +164 +165 +166 +167 +168 +169 +170 +171 +172 +173 +174 +175 +176 +177 +178 +179 +180 +181 +182 +183 +184 +185 +186 +187 +188 +189 +190 +191 +192 +193 +194 +195 +196  +  +  +  +  +  +  +  +  +  +  +  +1x +  +  +5x +  +5x +  +  +  +  +  +  +5x +  +5x +  +  +  +  +  +  +5x +5x +1x +  +  +  +  +  +4x +  +4x +  +  +  +  +4x +1x +  +  +  +  +  +3x +1x +  +  +  +  +  +3x +2x +2x +  +2x +3x +  +  +  +  +  +  +3x +  +  +  +  +  +  +3x +  +  +  +  +  +  +3x +3x +  +  +2x +  +  +  +  +  +  +  +  +2x +1x +  +  +  +  +  +2x +1x +  +1x +1x +  +1x +2x +2x +  +  +  +  +  +  +2x +  +2x +  +  +  +  +  +  +  +  +1x +1x +1x +5x +5x +5x +  +5x +  +  +  +  +5x +5x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +1x +2x +  +  +  +  +  +1x +  +  +  +  +  +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  + 
import { NextResponse } from "next/server";
+import { getCurrentUser } from "@/lib/auth";
+import { db } from "@/db";
+import {
+  quizAttempts,
+  quizAttemptAnswers,
+  quizQuestions,
+  quizAnswers,
+} from "@/db/schema/quiz";
+import { awardQuizPoints, calculateQuizPoints } from "@/db/queries/points";
+import { eq, inArray } from "drizzle-orm";
+ 
+export const runtime = "nodejs";
+ 
+export async function POST(req: Request) {
+  const body = await req.json().catch(() => null);
+ 
+  Iif (!body) {
+    return NextResponse.json(
+      { success: false, error: "Invalid input" },
+      { status: 400 }
+    );
+  }
+ 
+  const { quizId, answers, violations, timeSpentSeconds } = body;
+ 
+  Iif (!quizId || !Array.isArray(answers) || answers.length === 0) {
+    return NextResponse.json(
+      { success: false, error: "Invalid input" },
+      { status: 400 }
+    );
+  }
+ 
+  const session = await getCurrentUser();
+  if (!session) {
+    return NextResponse.json(
+      { success: false, error: "Unauthorized" },
+      { status: 401 }
+    );
+  }
+ 
+  const userId = session.id;
+ 
+  const questionRows = await db
+    .select({ id: quizQuestions.id })
+    .from(quizQuestions)
+    .where(eq(quizQuestions.quizId, quizId));
+ 
+  if (questionRows.length === 0) {
+    return NextResponse.json(
+      { success: false, error: "Quiz not found" },
+      { status: 404 }
+    );
+  }
+ 
+  if (answers.length !== questionRows.length) {
+    return NextResponse.json(
+      { success: false, error: "Invalid input: answers count mismatch" },
+      { status: 400 }
+    );
+  }
+ 
+  const questionIdSet = new Set(questionRows.map(r => r.id));
+  const seenQuestionIds = new Set<string>();
+  const answerIds: string[] = [];
+ 
+  for (const answer of answers) {
+    Iif (!answer?.questionId || !answer?.selectedAnswerId) {
+      return NextResponse.json(
+        { success: false, error: "Invalid answer payload" },
+        { status: 400 }
+      );
+    }
+ 
+    Iif (!questionIdSet.has(answer.questionId)) {
+      return NextResponse.json(
+        { success: false, error: "Invalid question in answers" },
+        { status: 400 }
+      );
+    }
+ 
+    Iif (seenQuestionIds.has(answer.questionId)) {
+      return NextResponse.json(
+        { success: false, error: "Duplicate answer for question" },
+        { status: 400 }
+      );
+    }
+ 
+    seenQuestionIds.add(answer.questionId);
+    answerIds.push(answer.selectedAnswerId);
+  }
+ 
+  const answerRows = await db
+    .select({
+      id: quizAnswers.id,
+      quizQuestionId: quizAnswers.quizQuestionId,
+      isCorrect: quizAnswers.isCorrect,
+    })
+    .from(quizAnswers)
+    .where(inArray(quizAnswers.id, answerIds));
+ 
+  if (answerRows.length !== answerIds.length) {
+    return NextResponse.json(
+      { success: false, error: "Invalid answer selection" },
+      { status: 400 }
+    );
+  }
+ 
+  const answerById = new Map(answerRows.map(r => [r.id, r]));
+  let correctAnswersCount = 0;
+ 
+  const now = new Date();
+  const attemptAnswers = [];
+ 
+  for (const answer of answers) {
+    const record = answerById.get(answer.selectedAnswerId);
+    Iif (!record || record.quizQuestionId !== answer.questionId) {
+      return NextResponse.json(
+        { success: false, error: "Answer does not match question" },
+        { status: 400 }
+      );
+    }
+ 
+    if (record.isCorrect) correctAnswersCount++;
+ 
+    attemptAnswers.push({
+      attemptId: "",
+      quizQuestionId: answer.questionId,
+      selectedAnswerId: answer.selectedAnswerId,
+      isCorrect: record.isCorrect,
+      answeredAt: now,
+    });
+  }
+ 
+  const totalQuestions = questionRows.length;
+  const percentage = ((correctAnswersCount / totalQuestions) * 100).toFixed(2);
+  const violationsArray = Array.isArray(violations) ? violations : [];
+  const integrityScore = Math.max(0, 100 - violationsArray.length * 10);
+  const safeTimeSpentSeconds = Math.max(0, Number(timeSpentSeconds) || 0);
+  const startedAt = new Date(now.getTime() - safeTimeSpentSeconds * 1000);
+ 
+  const pointsEarned = calculateQuizPoints({
+    score: correctAnswersCount,
+    integrityScore,
+  });
+ 
+  try {
+    const [attempt] = await db
+      .insert(quizAttempts)
+      .values({
+        userId,
+        quizId,
+        score: correctAnswersCount,
+        totalQuestions,
+        percentage,
+        timeSpentSeconds: safeTimeSpentSeconds,
+        integrityScore,
+        pointsEarned,
+        metadata: { violations: violationsArray, isGuestResult: true },
+        startedAt,
+        completedAt: now,
+      })
+      .returning({ id: quizAttempts.id });
+ 
+    await db.insert(quizAttemptAnswers).values(
+      attemptAnswers.map(a => ({
+        ...a,
+        attemptId: attempt.id,
+      }))
+    );
+ 
+    const pointsAwarded = await awardQuizPoints({
+      userId,
+      quizId,
+      attemptId: attempt.id,
+      score: correctAnswersCount,
+      integrityScore,
+    });
+ 
+    return NextResponse.json({
+      success: true,
+      attemptId: attempt.id,
+      score: correctAnswersCount,
+      totalQuestions,
+      percentage: parseFloat(percentage),
+      integrityScore,
+      pointsAwarded,
+    });
+  } catch (error) {
+    console.error("Failed to save guest quiz result:", error);
+    return NextResponse.json(
+      { success: false, error: "Failed to save result" },
+      { status: 500 }
+    );
+  }
+}
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage-quiz/app/api/quiz/verify-answer/index.html b/frontend/coverage-quiz/app/api/quiz/verify-answer/index.html new file mode 100644 index 00000000..15298bb2 --- /dev/null +++ b/frontend/coverage-quiz/app/api/quiz/verify-answer/index.html @@ -0,0 +1,116 @@ + + + + + + Code coverage report for app/api/quiz/verify-answer + + + + + + + + + +
+
+

All files app/api/quiz/verify-answer

+
+ +
+ 92.3% + Statements + 12/13 +
+ + +
+ 100% + Branches + 9/9 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 92.3% + Lines + 12/13 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
route.ts +
+
92.3%12/13100%9/9100%1/192.3%12/13
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage-quiz/app/api/quiz/verify-answer/route.ts.html b/frontend/coverage-quiz/app/api/quiz/verify-answer/route.ts.html new file mode 100644 index 00000000..46f20bcf --- /dev/null +++ b/frontend/coverage-quiz/app/api/quiz/verify-answer/route.ts.html @@ -0,0 +1,232 @@ + + + + + + Code coverage report for app/api/quiz/verify-answer/route.ts + + + + + + + + + +
+
+

All files / app/api/quiz/verify-answer route.ts

+
+ +
+ 92.3% + Statements + 12/13 +
+ + +
+ 100% + Branches + 9/9 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 92.3% + Lines + 12/13 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50  +  +  +  +  +  +  +  +  +  +8x +8x +8x +  +8x +3x +  +  +  +  +  +5x +  +5x +2x +  +  +  +  +  +3x +  +3x +1x +  +  +  +  +  +2x +  +  +  +  +  +  +  +  +  + 
import { NextRequest, NextResponse } from 'next/server';
+import { decryptAnswers } from '@/lib/quiz/quiz-crypto';
+ 
+interface VerifyRequest {
+  questionId: string;
+  answerId: string;
+  encryptedAnswers: string;
+}
+ 
+export async function POST(request: NextRequest) {
+  try {
+    const body: VerifyRequest = await request.json();
+    const { questionId, answerId, encryptedAnswers } = body;
+ 
+    if (!questionId || !answerId || !encryptedAnswers) {
+      return NextResponse.json(
+        { error: 'Missing required fields' },
+        { status: 400 }
+      );
+    }
+ 
+    const correctAnswersMap = decryptAnswers(encryptedAnswers);
+ 
+    if (!correctAnswersMap) {
+      return NextResponse.json(
+        { error: 'Invalid encrypted data' },
+        { status: 400 }
+      );
+    }
+ 
+    const correctAnswerId = correctAnswersMap[questionId];
+ 
+    if (!correctAnswerId) {
+      return NextResponse.json(
+        { error: 'Question not found' },
+        { status: 404 }
+      );
+    }
+ 
+    return NextResponse.json({
+      isCorrect: answerId === correctAnswerId,
+    });
+  } catch {
+    return NextResponse.json(
+      { error: 'Internal server error' },
+      { status: 500 }
+    );
+  }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage-quiz/base.css b/frontend/coverage-quiz/base.css new file mode 100644 index 00000000..f418035b --- /dev/null +++ b/frontend/coverage-quiz/base.css @@ -0,0 +1,224 @@ +body, html { + margin:0; padding: 0; + height: 100%; +} +body { + font-family: Helvetica Neue, Helvetica, Arial; + font-size: 14px; + color:#333; +} +.small { font-size: 12px; } +*, *:after, *:before { + -webkit-box-sizing:border-box; + -moz-box-sizing:border-box; + box-sizing:border-box; + } +h1 { font-size: 20px; margin: 0;} +h2 { font-size: 14px; } +pre { + font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; + margin: 0; + padding: 0; + -moz-tab-size: 2; + -o-tab-size: 2; + tab-size: 2; +} +a { color:#0074D9; text-decoration:none; } +a:hover { text-decoration:underline; } +.strong { font-weight: bold; } +.space-top1 { padding: 10px 0 0 0; } +.pad2y { padding: 20px 0; } +.pad1y { padding: 10px 0; } +.pad2x { padding: 0 20px; } +.pad2 { padding: 20px; } +.pad1 { padding: 10px; } +.space-left2 { padding-left:55px; } +.space-right2 { padding-right:20px; } +.center { text-align:center; } +.clearfix { display:block; } +.clearfix:after { + content:''; + display:block; + height:0; + clear:both; + visibility:hidden; + } +.fl { float: left; } +@media only screen and (max-width:640px) { + .col3 { width:100%; max-width:100%; } + .hide-mobile { display:none!important; } +} + +.quiet { + color: #7f7f7f; + color: rgba(0,0,0,0.5); +} +.quiet a { opacity: 0.7; } + +.fraction { + font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; + font-size: 10px; + color: #555; + background: #E8E8E8; + padding: 4px 5px; + border-radius: 3px; + vertical-align: middle; +} + +div.path a:link, div.path a:visited { color: #333; } +table.coverage { + border-collapse: collapse; + margin: 10px 0 0 0; + padding: 0; +} + +table.coverage td { + margin: 0; + padding: 0; + vertical-align: top; +} +table.coverage td.line-count { + text-align: right; + padding: 0 5px 0 20px; +} +table.coverage td.line-coverage { + text-align: right; + padding-right: 10px; + min-width:20px; +} + +table.coverage td span.cline-any { + display: inline-block; + padding: 0 5px; + width: 100%; +} +.missing-if-branch { + display: inline-block; + margin-right: 5px; + border-radius: 3px; + position: relative; + padding: 0 4px; + background: #333; + color: yellow; +} + +.skip-if-branch { + display: none; + margin-right: 10px; + position: relative; + padding: 0 4px; + background: #ccc; + color: white; +} +.missing-if-branch .typ, .skip-if-branch .typ { + color: inherit !important; +} +.coverage-summary { + border-collapse: collapse; + width: 100%; +} +.coverage-summary tr { border-bottom: 1px solid #bbb; } +.keyline-all { border: 1px solid #ddd; } +.coverage-summary td, .coverage-summary th { padding: 10px; } +.coverage-summary tbody { border: 1px solid #bbb; } +.coverage-summary td { border-right: 1px solid #bbb; } +.coverage-summary td:last-child { border-right: none; } +.coverage-summary th { + text-align: left; + font-weight: normal; + white-space: nowrap; +} +.coverage-summary th.file { border-right: none !important; } +.coverage-summary th.pct { } +.coverage-summary th.pic, +.coverage-summary th.abs, +.coverage-summary td.pct, +.coverage-summary td.abs { text-align: right; } +.coverage-summary td.file { white-space: nowrap; } +.coverage-summary td.pic { min-width: 120px !important; } +.coverage-summary tfoot td { } + +.coverage-summary .sorter { + height: 10px; + width: 7px; + display: inline-block; + margin-left: 0.5em; + background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; +} +.coverage-summary .sorted .sorter { + background-position: 0 -20px; +} +.coverage-summary .sorted-desc .sorter { + background-position: 0 -10px; +} +.status-line { height: 10px; } +/* yellow */ +.cbranch-no { background: yellow !important; color: #111; } +/* dark red */ +.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } +.low .chart { border:1px solid #C21F39 } +.highlighted, +.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ + background: #C21F39 !important; +} +/* medium red */ +.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } +/* light red */ +.low, .cline-no { background:#FCE1E5 } +/* light green */ +.high, .cline-yes { background:rgb(230,245,208) } +/* medium green */ +.cstat-yes { background:rgb(161,215,106) } +/* dark green */ +.status-line.high, .high .cover-fill { background:rgb(77,146,33) } +.high .chart { border:1px solid rgb(77,146,33) } +/* dark yellow (gold) */ +.status-line.medium, .medium .cover-fill { background: #f9cd0b; } +.medium .chart { border:1px solid #f9cd0b; } +/* light yellow */ +.medium { background: #fff4c2; } + +.cstat-skip { background: #ddd; color: #111; } +.fstat-skip { background: #ddd; color: #111 !important; } +.cbranch-skip { background: #ddd !important; color: #111; } + +span.cline-neutral { background: #eaeaea; } + +.coverage-summary td.empty { + opacity: .5; + padding-top: 4px; + padding-bottom: 4px; + line-height: 1; + color: #888; +} + +.cover-fill, .cover-empty { + display:inline-block; + height: 12px; +} +.chart { + line-height: 0; +} +.cover-empty { + background: white; +} +.cover-full { + border-right: none !important; +} +pre.prettyprint { + border: none !important; + padding: 0 !important; + margin: 0 !important; +} +.com { color: #999 !important; } +.ignore-none { color: #999; font-weight: normal; } + +.wrapper { + min-height: 100%; + height: auto !important; + height: 100%; + margin: 0 auto -48px; +} +.footer, .push { + height: 48px; +} diff --git a/frontend/coverage-quiz/block-navigation.js b/frontend/coverage-quiz/block-navigation.js new file mode 100644 index 00000000..530d1ed2 --- /dev/null +++ b/frontend/coverage-quiz/block-navigation.js @@ -0,0 +1,87 @@ +/* eslint-disable */ +var jumpToCode = (function init() { + // Classes of code we would like to highlight in the file view + var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; + + // Elements to highlight in the file listing view + var fileListingElements = ['td.pct.low']; + + // We don't want to select elements that are direct descendants of another match + var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` + + // Selector that finds elements on the page to which we can jump + var selector = + fileListingElements.join(', ') + + ', ' + + notSelector + + missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` + + // The NodeList of matching elements + var missingCoverageElements = document.querySelectorAll(selector); + + var currentIndex; + + function toggleClass(index) { + missingCoverageElements + .item(currentIndex) + .classList.remove('highlighted'); + missingCoverageElements.item(index).classList.add('highlighted'); + } + + function makeCurrent(index) { + toggleClass(index); + currentIndex = index; + missingCoverageElements.item(index).scrollIntoView({ + behavior: 'smooth', + block: 'center', + inline: 'center' + }); + } + + function goToPrevious() { + var nextIndex = 0; + if (typeof currentIndex !== 'number' || currentIndex === 0) { + nextIndex = missingCoverageElements.length - 1; + } else if (missingCoverageElements.length > 1) { + nextIndex = currentIndex - 1; + } + + makeCurrent(nextIndex); + } + + function goToNext() { + var nextIndex = 0; + + if ( + typeof currentIndex === 'number' && + currentIndex < missingCoverageElements.length - 1 + ) { + nextIndex = currentIndex + 1; + } + + makeCurrent(nextIndex); + } + + return function jump(event) { + if ( + document.getElementById('fileSearch') === document.activeElement && + document.activeElement != null + ) { + // if we're currently focused on the search input, we don't want to navigate + return; + } + + switch (event.which) { + case 78: // n + case 74: // j + goToNext(); + break; + case 66: // b + case 75: // k + case 80: // p + goToPrevious(); + break; + } + }; +})(); +window.addEventListener('keydown', jumpToCode); diff --git a/frontend/coverage-quiz/favicon.png b/frontend/coverage-quiz/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..c1525b811a167671e9de1fa78aab9f5c0b61cef7 GIT binary patch literal 445 zcmV;u0Yd(XP))rP{nL}Ln%S7`m{0DjX9TLF* zFCb$4Oi7vyLOydb!7n&^ItCzb-%BoB`=x@N2jll2Nj`kauio%aw_@fe&*}LqlFT43 z8doAAe))z_%=P%v^@JHp3Hjhj^6*Kr_h|g_Gr?ZAa&y>wxHE99Gk>A)2MplWz2xdG zy8VD2J|Uf#EAw*bo5O*PO_}X2Tob{%bUoO2G~T`@%S6qPyc}VkhV}UifBuRk>%5v( z)x7B{I~z*k<7dv#5tC+m{km(D087J4O%+<<;K|qwefb6@GSX45wCK}Sn*> + + + + Code coverage report for hooks + + + + + + + + + +
+
+

All files hooks

+
+ +
+ 88.42% + Statements + 84/95 +
+ + +
+ 70.45% + Branches + 31/44 +
+ + +
+ 90.9% + Functions + 20/22 +
+ + +
+ 92.77% + Lines + 77/83 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
useQuizGuards.ts +
+
85.33%64/7560%18/3088.88%16/1890.76%59/65
useQuizSession.ts +
+
100%20/2092.85%13/14100%4/4100%18/18
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage-quiz/hooks/useQuizGuards.ts.html b/frontend/coverage-quiz/hooks/useQuizGuards.ts.html new file mode 100644 index 00000000..8395532d --- /dev/null +++ b/frontend/coverage-quiz/hooks/useQuizGuards.ts.html @@ -0,0 +1,430 @@ + + + + + + Code coverage report for hooks/useQuizGuards.ts + + + + + + + + + +
+
+

All files / hooks useQuizGuards.ts

+
+ +
+ 85.33% + Statements + 64/75 +
+ + +
+ 60% + Branches + 18/30 +
+ + +
+ 88.88% + Functions + 16/18 +
+ + +
+ 90.76% + Lines + 59/65 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83 +84 +85 +86 +87 +88 +89 +90 +91 +92 +93 +94 +95 +96 +97 +98 +99 +100 +101 +102 +103 +104 +105 +106 +107 +108 +109 +110 +111 +112 +113 +114 +115 +116  +  +  +  +  +  +  +  +  +  +  +  +8x +8x +8x +8x +8x +1x +  +  +8x +8x +  +  +8x +8x +8x +  +  +8x +8x +  +7x +8x +7x +  +  +  +8x +8x +  +  +  +  +  +  +  +  +  +8x +8x +  +  +8x +8x +2x +1x +1x +1x +1x +  +  +  +8x +8x +  +  +8x +8x +2x +  +2x +2x +2x +  +2x +  +  +  +2x +1x +1x +1x +  +  +1x +1x +1x +  +  +8x +8x +  +  +8x +8x +2x +2x +  +2x +1x +1x +  +  +1x +1x +1x +1x +  +  +8x +8x +  +8x +  + 
import { useEffect, useRef } from 'react';
+import { clearQuizSession } from '@/lib/quiz/quiz-session';
+import { getQuizReloadKey } from '@/lib/quiz/quiz-storage-keys';
+ 
+type UseQuizGuardsParams = {
+  quizId: string;
+  status: 'rules' | 'in_progress' | 'completed';
+  onExit: () => void;
+  resetViolations: () => void;
+};
+ 
+export function useQuizGuards({ quizId, status, onExit, resetViolations }: UseQuizGuardsParams): { markQuitting: () => void } {
+  const isReloadingRef = useRef(false);
+  const statusRef = useRef(status);
+  const reloadKey = getQuizReloadKey(quizId);
+  const isQuittingRef = useRef(false);
+  const markQuitting = () => {
+  isQuittingRef.current = true;
+};
+ 
+  useEffect(() => {
+    statusRef.current = status;
+  }, [status]);
+ 
+  useEffect(() => {
+    isQuittingRef.current = false;
+    isReloadingRef.current = false;
+  }, []);
+ 
+  useEffect(() => {
+    if (status !== 'in_progress') return;
+ 
+    const hasGuard = window.history.state?.quizGuard;
+    if (!hasGuard) {
+      window.history.pushState({ quizGuard: true }, '');
+    }
+  }, [status]);
+ 
+  useEffect(() => {
+    const handleKeyDown = (e: KeyboardEvent) => {
+      const key = e.key.toLowerCase();
+      if (key === 'f5' || ((e.ctrlKey || e.metaKey) && key === 'r')) {
+        isReloadingRef.current = true;
+        window.setTimeout(() => {
+          isReloadingRef.current = false;
+        }, 1000);
+      }
+    };
+ 
+    window.addEventListener('keydown', handleKeyDown);
+    return () => window.removeEventListener('keydown', handleKeyDown);
+  }, []);
+ 
+  useEffect(() => {
+    const handleBeforeUnload = (e: BeforeUnloadEvent) => {
+      if (statusRef.current === 'in_progress' && !isQuittingRef.current) {
+        sessionStorage.setItem(reloadKey, '1');
+        Iif (isReloadingRef.current) return;
+        e.preventDefault();
+        e.returnValue = '';
+      }
+    };
+ 
+    window.addEventListener('beforeunload', handleBeforeUnload);
+    return () => window.removeEventListener('beforeunload', handleBeforeUnload);
+  }, [reloadKey]);
+ 
+  useEffect(() => {
+    const handleClick = (e: MouseEvent) => {
+      Iif (statusRef.current !== 'in_progress') return;
+ 
+      const target = e.target as HTMLElement;
+      const link = target.closest('a');
+      Iif (!link?.href) return;
+ 
+      Iif (link.href.includes(window.location.pathname.replace(/^\/(uk|en|pl)/, ''))) {
+        return;
+      }
+ 
+      if (!window.confirm('Exit quiz? Your progress will not be saved.')) {
+        e.preventDefault();
+        e.stopPropagation();
+        return;
+      }
+ 
+      isQuittingRef.current = true;
+      clearQuizSession(quizId);
+      resetViolations();
+    };
+ 
+    document.addEventListener('click', handleClick, true);
+    return () => document.removeEventListener('click', handleClick, true);
+  }, [quizId, resetViolations]);
+ 
+  useEffect(() => {
+    const handlePopState = () => {
+      Iif (statusRef.current !== 'in_progress') return;
+      Iif (isQuittingRef.current) return;
+ 
+      if (!window.confirm('Exit quiz? Your progress will not be saved.')) {
+        window.history.pushState({ quizGuard: true }, '');
+        return;
+      }
+ 
+      isQuittingRef.current = true;
+      clearQuizSession(quizId);
+      resetViolations();
+      onExit();
+    };
+ 
+    window.addEventListener('popstate', handlePopState);
+    return () => window.removeEventListener('popstate', handlePopState);
+  }, [quizId, resetViolations, onExit]);
+  return { markQuitting };
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage-quiz/hooks/useQuizSession.ts.html b/frontend/coverage-quiz/hooks/useQuizSession.ts.html new file mode 100644 index 00000000..29c7a20b --- /dev/null +++ b/frontend/coverage-quiz/hooks/useQuizSession.ts.html @@ -0,0 +1,313 @@ + + + + + + Code coverage report for hooks/useQuizSession.ts + + + + + + + + + +
+
+

All files / hooks useQuizSession.ts

+
+ +
+ 100% + Statements + 20/20 +
+ + +
+ 92.85% + Branches + 13/14 +
+ + +
+ 100% + Functions + 4/4 +
+ + +
+ 100% + Lines + 18/18 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +6x +  +6x +6x +6x +1x +  +  +6x +6x +1x +  +  +6x +6x +  +3x +2x +  +1x +  +  +  +6x +6x +  +1x +  +  +1x +  +  +  +  +  +  +  +  +  +  +  +6x +  +  + 
import { useEffect } from 'react';
+import {
+  saveQuizSession,
+  loadQuizSession,
+  clearQuizSession,
+  type QuizSessionData,
+} from '@/lib/quiz/quiz-session';
+import { QUIZ_ALLOW_RESTORE_KEY, getQuizReloadKey } from '@/lib/quiz/quiz-storage-keys';
+ 
+type Answer = {
+  questionId: string;
+  selectedAnswerId: string;
+  isCorrect: boolean;
+  answeredAt: Date;
+};
+ 
+type QuizState = {
+  status: 'rules' | 'in_progress' | 'completed';
+  currentIndex: number;
+  answers: Answer[];
+  questionStatus: 'answering' | 'revealed';
+  selectedAnswerId: string | null;
+  startedAt: Date | null;
+};
+ 
+type UseQuizSessionParams = {
+  quizId: string;
+  state: QuizState;
+  onRestore: (data: QuizSessionData) => void;
+};
+ 
+export function useQuizSession({ quizId, state, onRestore }: UseQuizSessionParams): void {
+  const reloadKey = getQuizReloadKey(quizId);
+ 
+  useEffect(() => {
+    const isReload = sessionStorage.getItem(reloadKey);
+    if (isReload) {
+      sessionStorage.removeItem(reloadKey);
+    }
+ 
+    const allowRestore = sessionStorage.getItem(QUIZ_ALLOW_RESTORE_KEY);
+    if (allowRestore) {
+      sessionStorage.removeItem(QUIZ_ALLOW_RESTORE_KEY);
+    }
+ 
+    const saved = loadQuizSession(quizId);
+    if (!saved) return;
+ 
+    if (isReload || allowRestore) {
+      onRestore(saved);
+    } else {
+      clearQuizSession(quizId);
+    }
+  }, [quizId, reloadKey, onRestore]);
+ 
+  useEffect(() => {
+    if (state.status !== 'in_progress') return;
+ 
+    const sessionData: QuizSessionData = {
+      status: state.status,
+      currentIndex: state.currentIndex,
+      answers: state.answers.map(a => ({
+        questionId: a.questionId,
+        selectedAnswerId: a.selectedAnswerId,
+        isCorrect: a.isCorrect,
+        answeredAt: a.answeredAt.getTime(),
+      })),
+      questionStatus: state.questionStatus,
+      selectedAnswerId: state.selectedAnswerId,
+      startedAt: state.startedAt?.getTime() ?? null,
+      savedAt: Date.now(),
+    };
+ 
+    saveQuizSession(quizId, sessionData);
+  }, [quizId, state]);
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage-quiz/index.html b/frontend/coverage-quiz/index.html new file mode 100644 index 00000000..551b2a35 --- /dev/null +++ b/frontend/coverage-quiz/index.html @@ -0,0 +1,176 @@ + + + + + + Code coverage report for All files + + + + + + + + + +
+
+

All files

+
+ +
+ 87.64% + Statements + 234/267 +
+ + +
+ 74.13% + Branches + 86/116 +
+ + +
+ 93.02% + Functions + 40/43 +
+ + +
+ 90.94% + Lines + 221/243 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
app/api/quiz/[slug] +
+
100%13/1375%3/4100%3/3100%12/12
app/api/quiz/guest-result +
+
85%51/6075.75%25/3380%4/585.71%48/56
app/api/quiz/verify-answer +
+
92.3%12/13100%9/9100%1/192.3%12/13
hooks +
+
88.42%84/9570.45%31/4490.9%20/2292.77%77/83
lib/quiz +
+
86.04%74/8669.23%18/26100%12/1291.13%72/79
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage-quiz/lib/quiz/guest-quiz.ts.html b/frontend/coverage-quiz/lib/quiz/guest-quiz.ts.html new file mode 100644 index 00000000..9966161e --- /dev/null +++ b/frontend/coverage-quiz/lib/quiz/guest-quiz.ts.html @@ -0,0 +1,244 @@ + + + + + + Code coverage report for lib/quiz/guest-quiz.ts + + + + + + + + + +
+
+

All files / lib/quiz guest-quiz.ts

+
+ +
+ 81.81% + Statements + 18/22 +
+ + +
+ 60% + Branches + 6/10 +
+ + +
+ 100% + Functions + 3/3 +
+ + +
+ 90% + Lines + 18/20 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +2x +2x +  +  +2x +  +  +2x +  +  +  +3x +  +  +  +3x +3x +  +3x +3x +3x +  +3x +1x +1x +  +  +1x +  +1x +1x +  +  +  +  +3x +3x + 
export interface PendingQuizResult {
+  quizId: string;
+  quizSlug: string;
+  answers: {
+    questionId: string;
+    selectedAnswerId: string;
+    isCorrect: boolean;
+  }[];
+  score: number;
+  totalQuestions: number;
+  percentage: number;
+  violations: { type: string; timestamp: number }[];
+  timeSpentSeconds: number;
+  savedAt: number;
+}
+ 
+const STORAGE_KEY = 'devlovers_pending_quiz';
+const EXPIRY_HOURS = 24;
+ 
+export function savePendingQuizResult(result: PendingQuizResult): void {
+  Iif (typeof window === 'undefined') {
+    return;
+  }
+  localStorage.setItem(STORAGE_KEY, JSON.stringify(result));
+}
+ 
+export function getPendingQuizResult(): PendingQuizResult | null {
+  Iif (typeof window === 'undefined') {
+    return null;
+  }
+  
+  const stored = localStorage.getItem(STORAGE_KEY);
+  Iif (!stored) return null;
+  
+  try {
+    const result: PendingQuizResult = JSON.parse(stored);
+    const expiryTime = result.savedAt + EXPIRY_HOURS * 60 * 60 * 1000;
+    
+    if (Date.now() > expiryTime) {
+      clearPendingQuizResult();
+      return null;
+    }
+    
+    return result;
+  } catch {
+    clearPendingQuizResult();
+    return null;
+  }
+}
+ 
+export function clearPendingQuizResult(): void {
+  Iif (typeof window === 'undefined') return;
+  localStorage.removeItem(STORAGE_KEY);
+}
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage-quiz/lib/quiz/index.html b/frontend/coverage-quiz/lib/quiz/index.html new file mode 100644 index 00000000..56441fc6 --- /dev/null +++ b/frontend/coverage-quiz/lib/quiz/index.html @@ -0,0 +1,161 @@ + + + + + + Code coverage report for lib/quiz + + + + + + + + + +
+
+

All files lib/quiz

+
+ +
+ 86.04% + Statements + 74/86 +
+ + +
+ 69.23% + Branches + 18/26 +
+ + +
+ 100% + Functions + 12/12 +
+ + +
+ 91.13% + Lines + 72/79 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FileStatementsBranchesFunctionsLines
guest-quiz.ts +
+
81.81%18/2260%6/10100%3/390%18/20
quiz-crypto.ts +
+
96.96%32/3375%3/4100%5/596.87%31/32
quiz-session.ts +
+
75.86%22/2975%9/12100%3/384%21/25
quiz-storage-keys.ts +
+
100%2/2100%0/0100%1/1100%2/2
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage-quiz/lib/quiz/quiz-crypto.ts.html b/frontend/coverage-quiz/lib/quiz/quiz-crypto.ts.html new file mode 100644 index 00000000..9accc795 --- /dev/null +++ b/frontend/coverage-quiz/lib/quiz/quiz-crypto.ts.html @@ -0,0 +1,331 @@ + + + + + + Code coverage report for lib/quiz/quiz-crypto.ts + + + + + + + + + +
+
+

All files / lib/quiz quiz-crypto.ts

+
+ +
+ 96.96% + Statements + 32/33 +
+ + +
+ 75% + Branches + 3/4 +
+ + +
+ 100% + Functions + 5/5 +
+ + +
+ 96.87% + Lines + 31/32 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +67 +68 +69 +70 +71 +72 +73 +74 +75 +76 +77 +78 +79 +80 +81 +82 +83  +  +2x +2x +2x +  +  +31x +31x +  +  +31x +  +  +  +  +  +  +  +  +  +  +18x +18x +18x +  +18x +18x +  +  +  +  +18x +  +18x +18x +  +  +  +  +  +  +13x +13x +13x +  +13x +13x +13x +  +13x +13x +  +13x +  +  +  +  +13x +  +  +6x +  +  +  +  +  +  +  +  +  +4x +  +4x +8x +7x +6x +  +  +  +4x +  + 
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';
+ 
+const ALGORITHM = 'aes-256-gcm';
+const IV_LENGTH = 16;
+const AUTH_TAG_LENGTH = 16;
+ 
+function getEncryptionKey(): Buffer {
+  const key = process.env.QUIZ_ENCRYPTION_KEY;
+  Iif (!key) {
+    throw new Error('QUIZ_ENCRYPTION_KEY environment variable is not set');
+  }
+  return Buffer.from(key, 'hex');
+}
+ 
+export interface CorrectAnswersMap {
+  [questionId: string]: string;
+}
+ 
+/**
+ * Encrypts answers map. Returns base64 string: IV (16b) + AuthTag (16b) + CipherText
+ */
+export function encryptAnswers(answers: CorrectAnswersMap): string {
+  const key = getEncryptionKey();
+  const iv = randomBytes(IV_LENGTH);
+  const cipher = createCipheriv(ALGORITHM, key, iv);
+ 
+  const plaintext = JSON.stringify(answers);
+  const encrypted = Buffer.concat([
+    cipher.update(plaintext, 'utf8'),
+    cipher.final(),
+  ]);
+ 
+  const authTag = cipher.getAuthTag();
+ 
+  const result = Buffer.concat([iv, authTag, encrypted]);
+  return result.toString('base64');
+}
+ 
+/**
+ * Decrypts blob. Returns null if tampered or wrong key.
+ */
+export function decryptAnswers(encryptedBlob: string): CorrectAnswersMap | null {
+  try {
+    const key = getEncryptionKey();
+    const data = Buffer.from(encryptedBlob, 'base64');
+ 
+    const iv = data.subarray(0, IV_LENGTH);
+    const authTag = data.subarray(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH);
+    const ciphertext = data.subarray(IV_LENGTH + AUTH_TAG_LENGTH);
+ 
+    const decipher = createDecipheriv(ALGORITHM, key, iv);
+    decipher.setAuthTag(authTag);
+ 
+    const decrypted = Buffer.concat([
+      decipher.update(ciphertext),
+      decipher.final(),
+    ]);
+ 
+    return JSON.parse(decrypted.toString('utf8'));
+  } catch {
+    // Decryption failed - tampered data or wrong key
+    return null;
+  }
+}
+ 
+/**
+ * Creates encrypted blob from quiz questions
+ */
+export function createEncryptedAnswersBlob(
+  questions: Array<{ id: string; answers: Array<{ id: string; isCorrect: boolean }> }>
+): string {
+  const answersMap: CorrectAnswersMap = {};
+ 
+  for (const question of questions) {
+    const correctAnswer = question.answers.find(a => a.isCorrect);
+    if (correctAnswer) {
+      answersMap[question.id] = correctAnswer.id;
+    }
+  }
+ 
+  return encryptAnswers(answersMap);
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage-quiz/lib/quiz/quiz-session.ts.html b/frontend/coverage-quiz/lib/quiz/quiz-session.ts.html new file mode 100644 index 00000000..1abb3170 --- /dev/null +++ b/frontend/coverage-quiz/lib/quiz/quiz-session.ts.html @@ -0,0 +1,283 @@ + + + + + + Code coverage report for lib/quiz/quiz-session.ts + + + + + + + + + +
+
+

All files / lib/quiz quiz-session.ts

+
+ +
+ 75.86% + Statements + 22/29 +
+ + +
+ 75% + Branches + 9/12 +
+ + +
+ 100% + Functions + 3/3 +
+ + +
+ 84% + Lines + 21/25 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 +21 +22 +23 +24 +25 +26 +27 +28 +29 +30 +31 +32 +33 +34 +35 +36 +37 +38 +39 +40 +41 +42 +43 +44 +45 +46 +47 +48 +49 +50 +51 +52 +53 +54 +55 +56 +57 +58 +59 +60 +61 +62 +63 +64 +65 +66 +672x +2x +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +  +6x +  +6x +6x +6x +  +  +  +  +  +  +7x +  +7x +7x +7x +  +6x +  +  +6x +2x +2x +  +  +  +4x +2x +2x +  +  +2x +  +  +  +  +  +  +  +7x +  +7x +7x +  +  +  +  + 
const STORAGE_KEY_PREFIX = 'quiz_session_';
+const SESSION_TTL_MS = 30 * 60 * 1000; // 30 minutes
+ 
+export interface QuizSessionData {
+  status: 'rules' | 'in_progress' | 'completed';
+  currentIndex: number;
+  answers: Array<{
+    questionId: string;
+    selectedAnswerId: string;
+    isCorrect: boolean;
+    answeredAt: number;
+  }>;
+  questionStatus: 'answering' | 'revealed';
+  selectedAnswerId: string | null;
+  startedAt: number | null;
+  savedAt: number;
+}
+ 
+export function saveQuizSession(quizId: string, state: QuizSessionData): void {
+  Iif (typeof window === 'undefined') return;
+ 
+  try {
+    const data = { ...state, savedAt: Date.now() };
+    localStorage.setItem(`${STORAGE_KEY_PREFIX}${quizId}`, JSON.stringify(data));
+  } catch (e) {
+    console.error('Failed to save quiz session:', e);
+  }
+}
+ 
+export function loadQuizSession(quizId: string): QuizSessionData | null {
+  Iif (typeof window === 'undefined') return null;
+ 
+  try {
+    const raw = localStorage.getItem(`${STORAGE_KEY_PREFIX}${quizId}`);
+    if (!raw) return null;
+ 
+    const data: QuizSessionData = JSON.parse(raw);
+ 
+    // Discard sessions older than 30 minutes
+    if (Date.now() - data.savedAt > SESSION_TTL_MS) {
+      clearQuizSession(quizId);
+      return null;
+    }
+ 
+    // Only restore in_progress sessions
+    if (data.status !== 'in_progress') {
+      clearQuizSession(quizId);
+      return null;
+    }
+ 
+    return data;
+  } catch (e) {
+    console.error('Failed to load quiz session:', e);
+    return null;
+  }
+}
+ 
+export function clearQuizSession(quizId: string): void {
+  Iif (typeof window === 'undefined') return;
+ 
+  try {
+    localStorage.removeItem(`${STORAGE_KEY_PREFIX}${quizId}`);
+  } catch (e) {
+    console.error('Failed to clear quiz session:', e);
+  }
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage-quiz/lib/quiz/quiz-storage-keys.ts.html b/frontend/coverage-quiz/lib/quiz/quiz-storage-keys.ts.html new file mode 100644 index 00000000..53270c9f --- /dev/null +++ b/frontend/coverage-quiz/lib/quiz/quiz-storage-keys.ts.html @@ -0,0 +1,100 @@ + + + + + + Code coverage report for lib/quiz/quiz-storage-keys.ts + + + + + + + + + +
+
+

All files / lib/quiz quiz-storage-keys.ts

+
+ +
+ 100% + Statements + 2/2 +
+ + +
+ 100% + Branches + 0/0 +
+ + +
+ 100% + Functions + 1/1 +
+ + +
+ 100% + Lines + 2/2 +
+ + +
+

+ Press n or j to go to the next uncovered block, b, p or k for the previous block. +

+ +
+
+

+
1 +2 +3 +4 +5 +62x +  +  +17x +  + 
export const QUIZ_ALLOW_RESTORE_KEY = 'quiz-allow-restore';
+ 
+export function getQuizReloadKey(quizId: string): string {
+  return `quiz-reload:${quizId}`;
+}
+ 
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/frontend/coverage-quiz/prettify.css b/frontend/coverage-quiz/prettify.css new file mode 100644 index 00000000..b317a7cd --- /dev/null +++ b/frontend/coverage-quiz/prettify.css @@ -0,0 +1 @@ +.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} diff --git a/frontend/coverage-quiz/prettify.js b/frontend/coverage-quiz/prettify.js new file mode 100644 index 00000000..b3225238 --- /dev/null +++ b/frontend/coverage-quiz/prettify.js @@ -0,0 +1,2 @@ +/* eslint-disable */ +window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); diff --git a/frontend/coverage-quiz/sort-arrow-sprite.png b/frontend/coverage-quiz/sort-arrow-sprite.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed68316eb3f65dec9063332d2f69bf3093bbfab GIT binary patch literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^>_9Bd!3HEZxJ@+%Qh}Z>jv*C{$p!i!8j}?a+@3A= zIAGwzjijN=FBi!|L1t?LM;Q;gkwn>2cAy-KV{dn nf0J1DIvEHQu*n~6U}x}qyky7vi4|9XhBJ7&`njxgN@xNA8m%nc literal 0 HcmV?d00001 diff --git a/frontend/coverage-quiz/sorter.js b/frontend/coverage-quiz/sorter.js new file mode 100644 index 00000000..4ed70ae5 --- /dev/null +++ b/frontend/coverage-quiz/sorter.js @@ -0,0 +1,210 @@ +/* eslint-disable */ +var addSorting = (function() { + 'use strict'; + var cols, + currentSort = { + index: 0, + desc: false + }; + + // returns the summary table element + function getTable() { + return document.querySelector('.coverage-summary'); + } + // returns the thead element of the summary table + function getTableHeader() { + return getTable().querySelector('thead tr'); + } + // returns the tbody element of the summary table + function getTableBody() { + return getTable().querySelector('tbody'); + } + // returns the th element for nth column + function getNthColumn(n) { + return getTableHeader().querySelectorAll('th')[n]; + } + + function onFilterInput() { + const searchValue = document.getElementById('fileSearch').value; + const rows = document.getElementsByTagName('tbody')[0].children; + + // Try to create a RegExp from the searchValue. If it fails (invalid regex), + // it will be treated as a plain text search + let searchRegex; + try { + searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive + } catch (error) { + searchRegex = null; + } + + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + let isMatch = false; + + if (searchRegex) { + // If a valid regex was created, use it for matching + isMatch = searchRegex.test(row.textContent); + } else { + // Otherwise, fall back to the original plain text search + isMatch = row.textContent + .toLowerCase() + .includes(searchValue.toLowerCase()); + } + + row.style.display = isMatch ? '' : 'none'; + } + } + + // loads the search box + function addSearchBox() { + var template = document.getElementById('filterTemplate'); + var templateClone = template.content.cloneNode(true); + templateClone.getElementById('fileSearch').oninput = onFilterInput; + template.parentElement.appendChild(templateClone); + } + + // loads all columns + function loadColumns() { + var colNodes = getTableHeader().querySelectorAll('th'), + colNode, + cols = [], + col, + i; + + for (i = 0; i < colNodes.length; i += 1) { + colNode = colNodes[i]; + col = { + key: colNode.getAttribute('data-col'), + sortable: !colNode.getAttribute('data-nosort'), + type: colNode.getAttribute('data-type') || 'string' + }; + cols.push(col); + if (col.sortable) { + col.defaultDescSort = col.type === 'number'; + colNode.innerHTML = + colNode.innerHTML + ''; + } + } + return cols; + } + // attaches a data attribute to every tr element with an object + // of data values keyed by column name + function loadRowData(tableRow) { + var tableCols = tableRow.querySelectorAll('td'), + colNode, + col, + data = {}, + i, + val; + for (i = 0; i < tableCols.length; i += 1) { + colNode = tableCols[i]; + col = cols[i]; + val = colNode.getAttribute('data-value'); + if (col.type === 'number') { + val = Number(val); + } + data[col.key] = val; + } + return data; + } + // loads all row data + function loadData() { + var rows = getTableBody().querySelectorAll('tr'), + i; + + for (i = 0; i < rows.length; i += 1) { + rows[i].data = loadRowData(rows[i]); + } + } + // sorts the table using the data for the ith column + function sortByIndex(index, desc) { + var key = cols[index].key, + sorter = function(a, b) { + a = a.data[key]; + b = b.data[key]; + return a < b ? -1 : a > b ? 1 : 0; + }, + finalSorter = sorter, + tableBody = document.querySelector('.coverage-summary tbody'), + rowNodes = tableBody.querySelectorAll('tr'), + rows = [], + i; + + if (desc) { + finalSorter = function(a, b) { + return -1 * sorter(a, b); + }; + } + + for (i = 0; i < rowNodes.length; i += 1) { + rows.push(rowNodes[i]); + tableBody.removeChild(rowNodes[i]); + } + + rows.sort(finalSorter); + + for (i = 0; i < rows.length; i += 1) { + tableBody.appendChild(rows[i]); + } + } + // removes sort indicators for current column being sorted + function removeSortIndicators() { + var col = getNthColumn(currentSort.index), + cls = col.className; + + cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); + col.className = cls; + } + // adds sort indicators for current column being sorted + function addSortIndicators() { + getNthColumn(currentSort.index).className += currentSort.desc + ? ' sorted-desc' + : ' sorted'; + } + // adds event listeners for all sorter widgets + function enableUI() { + var i, + el, + ithSorter = function ithSorter(i) { + var col = cols[i]; + + return function() { + var desc = col.defaultDescSort; + + if (currentSort.index === i) { + desc = !currentSort.desc; + } + sortByIndex(i, desc); + removeSortIndicators(); + currentSort.index = i; + currentSort.desc = desc; + addSortIndicators(); + }; + }; + for (i = 0; i < cols.length; i += 1) { + if (cols[i].sortable) { + // add the click event handler on the th so users + // dont have to click on those tiny arrows + el = getNthColumn(i).querySelector('.sorter').parentElement; + if (el.addEventListener) { + el.addEventListener('click', ithSorter(i)); + } else { + el.attachEvent('onclick', ithSorter(i)); + } + } + } + } + // adds sorting functionality to the UI + return function() { + if (!getTable()) { + return; + } + cols = loadColumns(); + loadData(); + addSearchBox(); + addSortIndicators(); + enableUI(); + }; +})(); + +window.addEventListener('load', addSorting); diff --git a/frontend/lib/tests/quiz/guest-quiz.test.ts b/frontend/lib/tests/quiz/guest-quiz.test.ts new file mode 100644 index 00000000..ae225595 --- /dev/null +++ b/frontend/lib/tests/quiz/guest-quiz.test.ts @@ -0,0 +1,101 @@ +// @vitest-environment jsdom +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { + savePendingQuizResult, + getPendingQuizResult, + clearPendingQuizResult, +} from '@/lib/quiz/guest-quiz'; + +describe('guest-quiz storage', () => { + beforeEach(() => { + localStorage.clear(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('saves pending quiz result to localStorage', () => { + const result = { + quizId: 'quiz-1', + quizSlug: 'react', + answers: [{ questionId: 'q1', selectedAnswerId: 'a1', isCorrect: true }], + score: 1, + totalQuestions: 1, + percentage: 100, + violations: [], + timeSpentSeconds: 10, + savedAt: Date.now(), + }; + + savePendingQuizResult(result); + + const stored = localStorage.getItem('devlovers_pending_quiz'); + expect(stored).not.toBeNull(); + }); + + it('returns null and clears storage for expired result', () => { + const now = Date.now(); + vi.spyOn(Date, 'now').mockReturnValue(now + 25 * 60 * 60 * 1000); + + localStorage.setItem( + 'devlovers_pending_quiz', + JSON.stringify({ + quizId: 'quiz-1', + quizSlug: 'react', + answers: [], + score: 0, + totalQuestions: 1, + percentage: 0, + violations: [], + timeSpentSeconds: 0, + savedAt: now, + }) + ); + + const result = getPendingQuizResult(); + + expect(result).toBeNull(); + expect(localStorage.getItem('devlovers_pending_quiz')).toBeNull(); + }); + + it('returns null and clears storage for invalid JSON', () => { + localStorage.setItem('devlovers_pending_quiz', '{bad json'); + + const result = getPendingQuizResult(); + + expect(result).toBeNull(); + expect(localStorage.getItem('devlovers_pending_quiz')).toBeNull(); + }); + + it('returns stored result when not expired', () => { + const now = Date.now(); + vi.spyOn(Date, 'now').mockReturnValue(now); + + const stored = { + quizId: 'quiz-1', + quizSlug: 'react', + answers: [], + score: 0, + totalQuestions: 1, + percentage: 0, + violations: [], + timeSpentSeconds: 0, + savedAt: now, + }; + + localStorage.setItem('devlovers_pending_quiz', JSON.stringify(stored)); + + const result = getPendingQuizResult(); + + expect(result?.quizId).toBe('quiz-1'); + }); + + it('clears pending quiz result', () => { + localStorage.setItem('devlovers_pending_quiz', JSON.stringify({ quizId: 'q1' })); + + clearPendingQuizResult(); + + expect(localStorage.getItem('devlovers_pending_quiz')).toBeNull(); + }); +}); diff --git a/frontend/lib/tests/quiz/guest-result-route.test.ts b/frontend/lib/tests/quiz/guest-result-route.test.ts new file mode 100644 index 00000000..45296717 --- /dev/null +++ b/frontend/lib/tests/quiz/guest-result-route.test.ts @@ -0,0 +1,179 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { POST } from '@/app/api/quiz/guest-result/route'; + +vi.mock('@/lib/auth', () => ({ + getCurrentUser: vi.fn(), +})); + +vi.mock('@/db/queries/points', () => ({ + calculateQuizPoints: vi.fn(), + awardQuizPoints: vi.fn(), +})); + +vi.mock('@/db', () => ({ + db: { + select: vi.fn(), + insert: vi.fn(), + }, +})); + +import { getCurrentUser } from '@/lib/auth'; +import { calculateQuizPoints, awardQuizPoints } from '@/db/queries/points'; +import { db } from '@/db'; + +const getCurrentUserMock = getCurrentUser as ReturnType; +const calculateQuizPointsMock = calculateQuizPoints as ReturnType; +const awardQuizPointsMock = awardQuizPoints as ReturnType; +const selectMock = db.select as ReturnType; +const insertMock = db.insert as ReturnType; + +const makeSelectChain = (result: unknown) => ({ + from: vi.fn().mockReturnValue({ + where: vi.fn().mockResolvedValue(result), + }), +}); + +describe('POST /api/quiz/guest-result', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('returns 401 when user is not authenticated', async () => { + getCurrentUserMock.mockResolvedValue(null); + + const request = new Request('http://localhost/api/quiz/guest-result', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + quizId: 'quiz-1', + answers: [{ questionId: 'q1', selectedAnswerId: 'a1' }], + violations: [], + timeSpentSeconds: 10, + }), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(401); + expect(data.error).toBe('Unauthorized'); + }); + + it('returns 404 when quiz has no questions', async () => { + getCurrentUserMock.mockResolvedValue({ id: 'user-1' }); + selectMock.mockImplementationOnce(() => makeSelectChain([])); + + const request = new Request('http://localhost/api/quiz/guest-result', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + quizId: 'quiz-1', + answers: [{ questionId: 'q1', selectedAnswerId: 'a1' }], + violations: [], + timeSpentSeconds: 10, + }), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(404); + expect(data.error).toBe('Quiz not found'); + }); + + it('returns 400 when answers count mismatches questions', async () => { + getCurrentUserMock.mockResolvedValue({ id: 'user-1' }); + selectMock.mockImplementationOnce(() => + makeSelectChain([{ id: 'q1' }, { id: 'q2' }]) + ); + + const request = new Request('http://localhost/api/quiz/guest-result', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + quizId: 'quiz-1', + answers: [{ questionId: 'q1', selectedAnswerId: 'a1' }], + violations: [], + timeSpentSeconds: 10, + }), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(400); + expect(data.error).toBe('Invalid input: answers count mismatch'); + }); + + it('returns 400 when answer selection is invalid', async () => { + getCurrentUserMock.mockResolvedValue({ id: 'user-1' }); + selectMock + .mockImplementationOnce(() => makeSelectChain([{ id: 'q1' }])) + .mockImplementationOnce(() => makeSelectChain([])); + + const request = new Request('http://localhost/api/quiz/guest-result', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + quizId: 'quiz-1', + answers: [{ questionId: 'q1', selectedAnswerId: 'a1' }], + violations: [], + timeSpentSeconds: 10, + }), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(400); + expect(data.error).toBe('Invalid answer selection'); + }); + + it('returns success and persists attempt', async () => { + getCurrentUserMock.mockResolvedValue({ id: 'user-1' }); + calculateQuizPointsMock.mockReturnValue(10); + awardQuizPointsMock.mockResolvedValue(7); + + selectMock + .mockImplementationOnce(() => makeSelectChain([{ id: 'q1' }, { id: 'q2' }])) + .mockImplementationOnce(() => + makeSelectChain([ + { id: 'a1', quizQuestionId: 'q1', isCorrect: true }, + { id: 'a2', quizQuestionId: 'q2', isCorrect: false }, + ]) + ); + + insertMock + .mockImplementationOnce(() => ({ + values: vi.fn().mockImplementation(() => ({ + returning: vi.fn().mockResolvedValue([{ id: 'attempt-1' }]), + })), + })) + .mockImplementationOnce(() => ({ + values: vi.fn().mockResolvedValue(undefined), + })); + + const request = new Request('http://localhost/api/quiz/guest-result', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + quizId: 'quiz-1', + answers: [ + { questionId: 'q1', selectedAnswerId: 'a1' }, + { questionId: 'q2', selectedAnswerId: 'a2' }, + ], + violations: [{ type: 'copy', timestamp: Date.now() }], + timeSpentSeconds: 30, + }), + }); + + const response = await POST(request); + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data.success).toBe(true); + expect(data.score).toBe(1); + expect(data.totalQuestions).toBe(2); + expect(data.pointsAwarded).toBe(7); + }); +}); diff --git a/frontend/lib/tests/quiz/quiz-slug-route.test.ts b/frontend/lib/tests/quiz/quiz-slug-route.test.ts new file mode 100644 index 00000000..23e814a7 --- /dev/null +++ b/frontend/lib/tests/quiz/quiz-slug-route.test.ts @@ -0,0 +1,95 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { NextRequest } from 'next/server'; + +vi.mock('@/db/queries/quiz', () => ({ + getQuizBySlug: vi.fn(), + getQuizQuestionsRandomized: vi.fn(), +})); + +import { GET } from '@/app/api/quiz/[slug]/route'; +import { getQuizBySlug, getQuizQuestionsRandomized } from '@/db/queries/quiz'; + +const getQuizBySlugMock = getQuizBySlug as ReturnType; +const getQuizQuestionsRandomizedMock = getQuizQuestionsRandomized as ReturnType; + +describe('GET /api/quiz/[slug]', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('returns 404 when quiz not found', async () => { + getQuizBySlugMock.mockResolvedValue(null); + + const request = new NextRequest('http://localhost/api/quiz/react?locale=en'); + const response = await GET(request, { params: Promise.resolve({ slug: 'react' }) }); + + const data = await response.json(); + + expect(response.status).toBe(404); + expect(data.error).toBe('Quiz not found'); + }); + + it('returns quiz with formatted questions', async () => { + getQuizBySlugMock.mockResolvedValue({ + id: 'quiz-1', + slug: 'react', + title: 'React Quiz', + description: 'Basics', + questionsCount: 1, + timeLimitSeconds: 60, + }); + + getQuizQuestionsRandomizedMock.mockResolvedValue([ + { + id: 'q1', + displayOrder: 1, + difficulty: null, + questionText: 'Question 1', + explanation: null, + answers: [ + { id: 'a1', displayOrder: 1, isCorrect: true, answerText: 'Answer 1' }, + ], + }, + ]); + + const request = new NextRequest('http://localhost/api/quiz/react?locale=en'); + const response = await GET(request, { params: Promise.resolve({ slug: 'react' }) }); + + const data = await response.json(); + + expect(response.status).toBe(200); + expect(data.quiz).toEqual({ + id: 'quiz-1', + slug: 'react', + title: 'React Quiz', + description: 'Basics', + questionsCount: 1, + timeLimitSeconds: 60, + }); + expect(data.questions[0]).toMatchObject({ + id: 'q1', + number: 1, + text: 'Question 1', + difficulty: null, + explanation: null, + }); + expect(data.questions[0].answers[0]).toEqual({ + id: 'a1', + text: 'Answer 1', + isCorrect: true, + }); + }); + + it('returns 500 on unexpected error', async () => { + getQuizBySlugMock.mockRejectedValue(new Error('db error')); + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + const request = new NextRequest('http://localhost/api/quiz/react?locale=en'); + const response = await GET(request, { params: Promise.resolve({ slug: 'react' }) }); + + const data = await response.json(); + + expect(response.status).toBe(500); + expect(data.error).toBe('Internal server error'); + consoleSpy.mockRestore(); + }); +}); diff --git a/frontend/lib/tests/quiz/use-quiz-guards.test.ts b/frontend/lib/tests/quiz/use-quiz-guards.test.ts new file mode 100644 index 00000000..0b4c396c --- /dev/null +++ b/frontend/lib/tests/quiz/use-quiz-guards.test.ts @@ -0,0 +1,161 @@ +// @vitest-environment jsdom +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { renderHook, act } from '@testing-library/react'; +import { useQuizGuards } from '@/hooks/useQuizGuards'; +import { getQuizReloadKey } from '@/lib/quiz/quiz-storage-keys'; + +vi.mock('@/lib/quiz/quiz-session', () => ({ + clearQuizSession: vi.fn(), +})); + +import { clearQuizSession } from '@/lib/quiz/quiz-session'; + +const createParams = (overrides: Partial<{ + quizId: string; + status: 'rules' | 'in_progress' | 'completed'; + onExit: () => void; + resetViolations: () => void; +}> = {}) => ({ + quizId: 'quiz-1', + status: 'in_progress' as const, + onExit: vi.fn(), + resetViolations: vi.fn(), + ...overrides, +}); + +describe('useQuizGuards', () => { + beforeEach(() => { + vi.clearAllMocks(); + sessionStorage.clear(); + window.history.replaceState({}, ''); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('pushes history guard when quiz is in progress', () => { + renderHook(() => useQuizGuards(createParams())); + + expect(window.history.state?.quizGuard).toBe(true); + }); + + it('does not push guard when status is not in_progress', () => { + renderHook(() => + useQuizGuards(createParams({ status: 'rules' })) + ); + + expect(window.history.state?.quizGuard).toBeUndefined(); + }); + + it('sets reload flag on beforeunload when in progress', () => { + const params = createParams(); + const reloadKey = getQuizReloadKey(params.quizId); + + renderHook(() => useQuizGuards(params)); + + act(() => { + window.dispatchEvent(new Event('beforeunload')); + }); + + expect(sessionStorage.getItem(reloadKey)).toBe('1'); + }); + + it('clears session and resets violations on external link click', () => { + const params = createParams(); + const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true); + + renderHook(() => useQuizGuards(params)); + + window.history.pushState({}, '', '/en/quizzes'); + const link = document.createElement('a'); + link.setAttribute('href', 'javascript:void(0)'); + document.body.appendChild(link); + + const event = new MouseEvent('click', { bubbles: true }); + link.dispatchEvent(event); + + expect(confirmSpy).toHaveBeenCalled(); + expect(clearQuizSession).toHaveBeenCalledWith(params.quizId); + expect(params.resetViolations).toHaveBeenCalled(); + + link.remove(); + }); + + it('prevents navigation when user cancels external link', () => { + const params = createParams(); + const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(false); + + renderHook(() => useQuizGuards(params)); + + const link = document.createElement('a'); + link.setAttribute('href', 'javascript:void(0)'); + document.body.appendChild(link); + + const preventDefault = vi.fn(); + const stopPropagation = vi.fn(); + + const event = new MouseEvent('click', { bubbles: true }) as MouseEvent & { + preventDefault: () => void; + stopPropagation: () => void; + }; + event.preventDefault = preventDefault; + event.stopPropagation = stopPropagation; + + link.dispatchEvent(event); + + expect(confirmSpy).toHaveBeenCalled(); + expect(preventDefault).toHaveBeenCalled(); + expect(stopPropagation).toHaveBeenCalled(); + expect(clearQuizSession).not.toHaveBeenCalled(); + + link.remove(); + }); + + it('handles back navigation with confirm', () => { + const params = createParams(); + const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true); + + renderHook(() => useQuizGuards(params)); + + act(() => { + window.dispatchEvent(new PopStateEvent('popstate')); + }); + + expect(confirmSpy).toHaveBeenCalled(); + expect(clearQuizSession).toHaveBeenCalledWith(params.quizId); + expect(params.resetViolations).toHaveBeenCalled(); + expect(params.onExit).toHaveBeenCalled(); + }); + + it('cancels back navigation when user declines', () => { + const params = createParams(); + const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(false); + const pushStateSpy = vi.spyOn(window.history, 'pushState'); + + renderHook(() => useQuizGuards(params)); + + act(() => { + window.dispatchEvent(new PopStateEvent('popstate')); + }); + + expect(confirmSpy).toHaveBeenCalled(); + expect(pushStateSpy).toHaveBeenCalled(); + expect(clearQuizSession).not.toHaveBeenCalled(); + expect(params.onExit).not.toHaveBeenCalled(); + }); + + it('markQuitting bypasses unload confirmation', () => { + const params = createParams(); + const reloadKey = getQuizReloadKey(params.quizId); + + const { result } = renderHook(() => useQuizGuards(params)); + + act(() => { + result.current.markQuitting(); + window.dispatchEvent(new Event('beforeunload')); + }); + + expect(sessionStorage.getItem(reloadKey)).toBeNull; + }); +}); diff --git a/frontend/lib/tests/quiz/use-quiz-session.test.ts b/frontend/lib/tests/quiz/use-quiz-session.test.ts new file mode 100644 index 00000000..9d91a904 --- /dev/null +++ b/frontend/lib/tests/quiz/use-quiz-session.test.ts @@ -0,0 +1,182 @@ +// @vitest-environment jsdom +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; +import { renderHook, waitFor } from '@testing-library/react'; +import { useQuizSession } from '@/hooks/useQuizSession'; +import { QUIZ_ALLOW_RESTORE_KEY, getQuizReloadKey } from '@/lib/quiz/quiz-storage-keys'; + +vi.mock('@/lib/quiz/quiz-session', () => ({ + saveQuizSession: vi.fn(), + loadQuizSession: vi.fn(), + clearQuizSession: vi.fn(), +})); + +import { saveQuizSession, loadQuizSession, clearQuizSession } from '@/lib/quiz/quiz-session'; + +type QuizState = { + status: 'rules' | 'in_progress' | 'completed'; + currentIndex: number; + answers: Array<{ + questionId: string; + selectedAnswerId: string; + isCorrect: boolean; + answeredAt: Date; + }>; + questionStatus: 'answering' | 'revealed'; + selectedAnswerId: string | null; + startedAt: Date | null; +}; + +const createState = (overrides: Partial = {}): QuizState => ({ + status: 'rules', + currentIndex: 0, + answers: [], + questionStatus: 'answering', + selectedAnswerId: null, + startedAt: null, + ...overrides, +}); + +describe('useQuizSession', () => { + const quizId = 'quiz-1'; + + const loadMock = loadQuizSession as ReturnType; + const saveMock = saveQuizSession as ReturnType; + const clearMock = clearQuizSession as ReturnType; + + beforeEach(() => { + sessionStorage.clear(); + vi.clearAllMocks(); + }); + + + it('restores session on reload', async () => { + const onRestore = vi.fn(); + const reloadKey = getQuizReloadKey(quizId); + + sessionStorage.setItem(reloadKey, '1'); + loadMock.mockReturnValue({ + status: 'in_progress', + currentIndex: 1, + answers: [], + questionStatus: 'answering', + selectedAnswerId: null, + startedAt: null, + savedAt: Date.now(), + }); + + renderHook(() => useQuizSession({ quizId, state: createState(), onRestore })); + + await waitFor(() => expect(onRestore).toHaveBeenCalledTimes(1)); + expect(clearMock).not.toHaveBeenCalled(); + expect(sessionStorage.getItem(reloadKey)).toBeNull(); + }); + + it('restores session when allow-restore flag is set', async () => { + const onRestore = vi.fn(); + + sessionStorage.setItem(QUIZ_ALLOW_RESTORE_KEY, '1'); + loadMock.mockReturnValue({ + status: 'in_progress', + currentIndex: 0, + answers: [], + questionStatus: 'answering', + selectedAnswerId: null, + startedAt: null, + savedAt: Date.now(), + }); + + renderHook(() => useQuizSession({ quizId, state: createState(), onRestore })); + + await waitFor(() => expect(onRestore).toHaveBeenCalledTimes(1)); + expect(clearMock).not.toHaveBeenCalled(); + expect(sessionStorage.getItem(QUIZ_ALLOW_RESTORE_KEY)).toBeNull(); + }); + + it('clears saved session when restore is not allowed', async () => { + const onRestore = vi.fn(); + + loadMock.mockReturnValue({ + status: 'in_progress', + currentIndex: 0, + answers: [], + questionStatus: 'answering', + selectedAnswerId: null, + startedAt: null, + savedAt: Date.now(), + }); + + renderHook(() => useQuizSession({ quizId, state: createState(), onRestore })); + + await waitFor(() => expect(clearMock).toHaveBeenCalledWith(quizId)); + expect(onRestore).not.toHaveBeenCalled(); + }); + + it('does nothing when no saved session exists', async () => { + const onRestore = vi.fn(); + + loadMock.mockReturnValue(null); + + renderHook(() => useQuizSession({ quizId, state: createState(), onRestore })); + + await waitFor(() => { + expect(onRestore).not.toHaveBeenCalled(); + expect(clearMock).not.toHaveBeenCalled(); + }); + }); + + it('saves session when status is in_progress', async () => { + const onRestore = vi.fn(); + const startedAt = new Date('2026-01-25T12:00:00Z'); + const answeredAt = new Date('2026-01-25T12:00:10Z'); + + const state = createState({ + status: 'in_progress', + currentIndex: 0, + questionStatus: 'revealed', + selectedAnswerId: 'a1', + startedAt, + answers: [ + { + questionId: 'q1', + selectedAnswerId: 'a1', + isCorrect: true, + answeredAt, + }, + ], + }); + + renderHook(() => useQuizSession({ quizId, state, onRestore })); + + await waitFor(() => expect(saveMock).toHaveBeenCalledTimes(1)); + + const [calledQuizId, payload] = saveMock.mock.calls[0]; + + expect(calledQuizId).toBe(quizId); + expect(payload).toEqual( + expect.objectContaining({ + status: 'in_progress', + currentIndex: 0, + questionStatus: 'revealed', + selectedAnswerId: 'a1', + startedAt: startedAt.getTime(), + savedAt: expect.any(Number), + }) + ); + expect(payload.answers).toEqual([ + { + questionId: 'q1', + selectedAnswerId: 'a1', + isCorrect: true, + answeredAt: answeredAt.getTime(), + }, + ]); + }); + + it('does not save session when status is not in_progress', async () => { + const onRestore = vi.fn(); + + renderHook(() => useQuizSession({ quizId, state: createState(), onRestore })); + + await waitFor(() => expect(saveMock).not.toHaveBeenCalled()); + }); +}); diff --git a/frontend/vitest.config.ts b/frontend/vitest.config.ts index 2fbd4ebf..161ceee9 100644 --- a/frontend/vitest.config.ts +++ b/frontend/vitest.config.ts @@ -13,7 +13,7 @@ export default defineConfig({ }, test: { environment: 'node', - include: ['lib/tests/**/*.test.ts', 'components/tests/**/*.test.tsx'], + include: ['lib/tests/**/*.test.ts', 'components/tests/**/*.test.tsx', 'components/quiz/tests/**/*.test.tsx'], globals: true, coverage: { provider: 'v8', From 7cc8f161e941081795a7ee47d247f82740374892 Mon Sep 17 00:00:00 2001 From: Lesia Soloviova Date: Sun, 25 Jan 2026 17:33:22 +0200 Subject: [PATCH 07/61] chore: remove coverage-quiz from git, add to .gitignore --- frontend/.gitignore | 1 + .../app/api/quiz/[slug]/index.html | 116 --- .../app/api/quiz/[slug]/route.ts.html | 238 ------- .../app/api/quiz/guest-result/index.html | 116 --- .../app/api/quiz/guest-result/route.ts.html | 670 ------------------ .../app/api/quiz/verify-answer/index.html | 116 --- .../app/api/quiz/verify-answer/route.ts.html | 232 ------ frontend/coverage-quiz/base.css | 224 ------ frontend/coverage-quiz/block-navigation.js | 87 --- frontend/coverage-quiz/favicon.png | Bin 445 -> 0 bytes frontend/coverage-quiz/hooks/index.html | 131 ---- .../coverage-quiz/hooks/useQuizGuards.ts.html | 430 ----------- .../hooks/useQuizSession.ts.html | 313 -------- frontend/coverage-quiz/index.html | 176 ----- .../coverage-quiz/lib/quiz/guest-quiz.ts.html | 244 ------- frontend/coverage-quiz/lib/quiz/index.html | 161 ----- .../lib/quiz/quiz-crypto.ts.html | 331 --------- .../lib/quiz/quiz-session.ts.html | 283 -------- .../lib/quiz/quiz-storage-keys.ts.html | 100 --- frontend/coverage-quiz/prettify.css | 1 - frontend/coverage-quiz/prettify.js | 2 - frontend/coverage-quiz/sort-arrow-sprite.png | Bin 138 -> 0 bytes frontend/coverage-quiz/sorter.js | 210 ------ 23 files changed, 1 insertion(+), 4181 deletions(-) delete mode 100644 frontend/coverage-quiz/app/api/quiz/[slug]/index.html delete mode 100644 frontend/coverage-quiz/app/api/quiz/[slug]/route.ts.html delete mode 100644 frontend/coverage-quiz/app/api/quiz/guest-result/index.html delete mode 100644 frontend/coverage-quiz/app/api/quiz/guest-result/route.ts.html delete mode 100644 frontend/coverage-quiz/app/api/quiz/verify-answer/index.html delete mode 100644 frontend/coverage-quiz/app/api/quiz/verify-answer/route.ts.html delete mode 100644 frontend/coverage-quiz/base.css delete mode 100644 frontend/coverage-quiz/block-navigation.js delete mode 100644 frontend/coverage-quiz/favicon.png delete mode 100644 frontend/coverage-quiz/hooks/index.html delete mode 100644 frontend/coverage-quiz/hooks/useQuizGuards.ts.html delete mode 100644 frontend/coverage-quiz/hooks/useQuizSession.ts.html delete mode 100644 frontend/coverage-quiz/index.html delete mode 100644 frontend/coverage-quiz/lib/quiz/guest-quiz.ts.html delete mode 100644 frontend/coverage-quiz/lib/quiz/index.html delete mode 100644 frontend/coverage-quiz/lib/quiz/quiz-crypto.ts.html delete mode 100644 frontend/coverage-quiz/lib/quiz/quiz-session.ts.html delete mode 100644 frontend/coverage-quiz/lib/quiz/quiz-storage-keys.ts.html delete mode 100644 frontend/coverage-quiz/prettify.css delete mode 100644 frontend/coverage-quiz/prettify.js delete mode 100644 frontend/coverage-quiz/sort-arrow-sprite.png delete mode 100644 frontend/coverage-quiz/sorter.js diff --git a/frontend/.gitignore b/frontend/.gitignore index 1f9b14cf..f5dd8f9e 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -12,6 +12,7 @@ # testing /coverage +/coverage-quiz # next.js /.next/ diff --git a/frontend/coverage-quiz/app/api/quiz/[slug]/index.html b/frontend/coverage-quiz/app/api/quiz/[slug]/index.html deleted file mode 100644 index 1e79676d..00000000 --- a/frontend/coverage-quiz/app/api/quiz/[slug]/index.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - Code coverage report for app/api/quiz/[slug] - - - - - - - - - -
-
-

All files app/api/quiz/[slug]

-
- -
- 100% - Statements - 13/13 -
- - -
- 75% - Branches - 3/4 -
- - -
- 100% - Functions - 3/3 -
- - -
- 100% - Lines - 12/12 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
route.ts -
-
100%13/1375%3/4100%3/3100%12/12
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage-quiz/app/api/quiz/[slug]/route.ts.html b/frontend/coverage-quiz/app/api/quiz/[slug]/route.ts.html deleted file mode 100644 index 6bb40b45..00000000 --- a/frontend/coverage-quiz/app/api/quiz/[slug]/route.ts.html +++ /dev/null @@ -1,238 +0,0 @@ - - - - - - Code coverage report for app/api/quiz/[slug]/route.ts - - - - - - - - - -
-
-

All files / app/api/quiz/[slug] route.ts

-
- -
- 100% - Statements - 13/13 -
- - -
- 75% - Branches - 3/4 -
- - -
- 100% - Functions - 3/3 -
- - -
- 100% - Lines - 12/12 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52  -  -  -  -  -  -  -3x -3x -  -3x -3x -  -2x -1x -  -  -1x -  -1x -  -  -  -  -1x -  -  -  -  -  -  -  -1x -  -  -  -  -  -  -  -  -  -  -  -1x -1x -  -  -  -  -  - 
import { NextRequest, NextResponse } from 'next/server';
-import { getQuizBySlug, getQuizQuestionsRandomized } from '@/db/queries/quiz';
- 
-export async function GET(
-  request: NextRequest,
-  { params }: { params: Promise<{ slug: string }> }
-) {
-  const { slug } = await params;
-  const locale = request.nextUrl.searchParams.get('locale') || 'uk';
- 
-  try {
-    const quiz = await getQuizBySlug(slug, locale);
- 
-    if (!quiz) {
-      return NextResponse.json({ error: 'Quiz not found' }, { status: 404 });
-    }
- 
-    const questions = await getQuizQuestionsRandomized(quiz.id, locale);
- 
-    const formattedQuestions = questions.map((q, index) => ({
-      id: q.id,
-      number: index + 1,
-      text: q.questionText,
-      difficulty: q.difficulty,
-      answers: q.answers.map(a => ({
-        id: a.id,
-        text: a.answerText,
-        isCorrect: a.isCorrect,
-      })),
-      explanation: q.explanation,
-    }));
- 
-    return NextResponse.json({
-      quiz: {
-        id: quiz.id,
-        slug: quiz.slug,
-        title: quiz.title,
-        description: quiz.description,
-        questionsCount: quiz.questionsCount,
-        timeLimitSeconds: quiz.timeLimitSeconds,
-      },
-      questions: formattedQuestions,
-    });
-  } catch (error) {
-    console.error('Error fetching quiz:', error);
-    return NextResponse.json(
-      { error: 'Internal server error' },
-      { status: 500 }
-    );
-  }
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage-quiz/app/api/quiz/guest-result/index.html b/frontend/coverage-quiz/app/api/quiz/guest-result/index.html deleted file mode 100644 index d89af522..00000000 --- a/frontend/coverage-quiz/app/api/quiz/guest-result/index.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - Code coverage report for app/api/quiz/guest-result - - - - - - - - - -
-
-

All files app/api/quiz/guest-result

-
- -
- 85% - Statements - 51/60 -
- - -
- 75.75% - Branches - 25/33 -
- - -
- 80% - Functions - 4/5 -
- - -
- 85.71% - Lines - 48/56 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
route.ts -
-
85%51/6075.75%25/3380%4/585.71%48/56
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage-quiz/app/api/quiz/guest-result/route.ts.html b/frontend/coverage-quiz/app/api/quiz/guest-result/route.ts.html deleted file mode 100644 index 463ce5b9..00000000 --- a/frontend/coverage-quiz/app/api/quiz/guest-result/route.ts.html +++ /dev/null @@ -1,670 +0,0 @@ - - - - - - Code coverage report for app/api/quiz/guest-result/route.ts - - - - - - - - - -
-
-

All files / app/api/quiz/guest-result route.ts

-
- -
- 85% - Statements - 51/60 -
- - -
- 75.75% - Branches - 25/33 -
- - -
- 80% - Functions - 4/5 -
- - -
- 85.71% - Lines - 48/56 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116 -117 -118 -119 -120 -121 -122 -123 -124 -125 -126 -127 -128 -129 -130 -131 -132 -133 -134 -135 -136 -137 -138 -139 -140 -141 -142 -143 -144 -145 -146 -147 -148 -149 -150 -151 -152 -153 -154 -155 -156 -157 -158 -159 -160 -161 -162 -163 -164 -165 -166 -167 -168 -169 -170 -171 -172 -173 -174 -175 -176 -177 -178 -179 -180 -181 -182 -183 -184 -185 -186 -187 -188 -189 -190 -191 -192 -193 -194 -195 -196  -  -  -  -  -  -  -  -  -  -  -  -1x -  -  -5x -  -5x -  -  -  -  -  -  -5x -  -5x -  -  -  -  -  -  -5x -5x -1x -  -  -  -  -  -4x -  -4x -  -  -  -  -4x -1x -  -  -  -  -  -3x -1x -  -  -  -  -  -3x -2x -2x -  -2x -3x -  -  -  -  -  -  -3x -  -  -  -  -  -  -3x -  -  -  -  -  -  -3x -3x -  -  -2x -  -  -  -  -  -  -  -  -2x -1x -  -  -  -  -  -2x -1x -  -1x -1x -  -1x -2x -2x -  -  -  -  -  -  -2x -  -2x -  -  -  -  -  -  -  -  -1x -1x -1x -5x -5x -5x -  -5x -  -  -  -  -5x -5x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -1x -2x -  -  -  -  -  -1x -  -  -  -  -  -  -  -1x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
import { NextResponse } from "next/server";
-import { getCurrentUser } from "@/lib/auth";
-import { db } from "@/db";
-import {
-  quizAttempts,
-  quizAttemptAnswers,
-  quizQuestions,
-  quizAnswers,
-} from "@/db/schema/quiz";
-import { awardQuizPoints, calculateQuizPoints } from "@/db/queries/points";
-import { eq, inArray } from "drizzle-orm";
- 
-export const runtime = "nodejs";
- 
-export async function POST(req: Request) {
-  const body = await req.json().catch(() => null);
- 
-  Iif (!body) {
-    return NextResponse.json(
-      { success: false, error: "Invalid input" },
-      { status: 400 }
-    );
-  }
- 
-  const { quizId, answers, violations, timeSpentSeconds } = body;
- 
-  Iif (!quizId || !Array.isArray(answers) || answers.length === 0) {
-    return NextResponse.json(
-      { success: false, error: "Invalid input" },
-      { status: 400 }
-    );
-  }
- 
-  const session = await getCurrentUser();
-  if (!session) {
-    return NextResponse.json(
-      { success: false, error: "Unauthorized" },
-      { status: 401 }
-    );
-  }
- 
-  const userId = session.id;
- 
-  const questionRows = await db
-    .select({ id: quizQuestions.id })
-    .from(quizQuestions)
-    .where(eq(quizQuestions.quizId, quizId));
- 
-  if (questionRows.length === 0) {
-    return NextResponse.json(
-      { success: false, error: "Quiz not found" },
-      { status: 404 }
-    );
-  }
- 
-  if (answers.length !== questionRows.length) {
-    return NextResponse.json(
-      { success: false, error: "Invalid input: answers count mismatch" },
-      { status: 400 }
-    );
-  }
- 
-  const questionIdSet = new Set(questionRows.map(r => r.id));
-  const seenQuestionIds = new Set<string>();
-  const answerIds: string[] = [];
- 
-  for (const answer of answers) {
-    Iif (!answer?.questionId || !answer?.selectedAnswerId) {
-      return NextResponse.json(
-        { success: false, error: "Invalid answer payload" },
-        { status: 400 }
-      );
-    }
- 
-    Iif (!questionIdSet.has(answer.questionId)) {
-      return NextResponse.json(
-        { success: false, error: "Invalid question in answers" },
-        { status: 400 }
-      );
-    }
- 
-    Iif (seenQuestionIds.has(answer.questionId)) {
-      return NextResponse.json(
-        { success: false, error: "Duplicate answer for question" },
-        { status: 400 }
-      );
-    }
- 
-    seenQuestionIds.add(answer.questionId);
-    answerIds.push(answer.selectedAnswerId);
-  }
- 
-  const answerRows = await db
-    .select({
-      id: quizAnswers.id,
-      quizQuestionId: quizAnswers.quizQuestionId,
-      isCorrect: quizAnswers.isCorrect,
-    })
-    .from(quizAnswers)
-    .where(inArray(quizAnswers.id, answerIds));
- 
-  if (answerRows.length !== answerIds.length) {
-    return NextResponse.json(
-      { success: false, error: "Invalid answer selection" },
-      { status: 400 }
-    );
-  }
- 
-  const answerById = new Map(answerRows.map(r => [r.id, r]));
-  let correctAnswersCount = 0;
- 
-  const now = new Date();
-  const attemptAnswers = [];
- 
-  for (const answer of answers) {
-    const record = answerById.get(answer.selectedAnswerId);
-    Iif (!record || record.quizQuestionId !== answer.questionId) {
-      return NextResponse.json(
-        { success: false, error: "Answer does not match question" },
-        { status: 400 }
-      );
-    }
- 
-    if (record.isCorrect) correctAnswersCount++;
- 
-    attemptAnswers.push({
-      attemptId: "",
-      quizQuestionId: answer.questionId,
-      selectedAnswerId: answer.selectedAnswerId,
-      isCorrect: record.isCorrect,
-      answeredAt: now,
-    });
-  }
- 
-  const totalQuestions = questionRows.length;
-  const percentage = ((correctAnswersCount / totalQuestions) * 100).toFixed(2);
-  const violationsArray = Array.isArray(violations) ? violations : [];
-  const integrityScore = Math.max(0, 100 - violationsArray.length * 10);
-  const safeTimeSpentSeconds = Math.max(0, Number(timeSpentSeconds) || 0);
-  const startedAt = new Date(now.getTime() - safeTimeSpentSeconds * 1000);
- 
-  const pointsEarned = calculateQuizPoints({
-    score: correctAnswersCount,
-    integrityScore,
-  });
- 
-  try {
-    const [attempt] = await db
-      .insert(quizAttempts)
-      .values({
-        userId,
-        quizId,
-        score: correctAnswersCount,
-        totalQuestions,
-        percentage,
-        timeSpentSeconds: safeTimeSpentSeconds,
-        integrityScore,
-        pointsEarned,
-        metadata: { violations: violationsArray, isGuestResult: true },
-        startedAt,
-        completedAt: now,
-      })
-      .returning({ id: quizAttempts.id });
- 
-    await db.insert(quizAttemptAnswers).values(
-      attemptAnswers.map(a => ({
-        ...a,
-        attemptId: attempt.id,
-      }))
-    );
- 
-    const pointsAwarded = await awardQuizPoints({
-      userId,
-      quizId,
-      attemptId: attempt.id,
-      score: correctAnswersCount,
-      integrityScore,
-    });
- 
-    return NextResponse.json({
-      success: true,
-      attemptId: attempt.id,
-      score: correctAnswersCount,
-      totalQuestions,
-      percentage: parseFloat(percentage),
-      integrityScore,
-      pointsAwarded,
-    });
-  } catch (error) {
-    console.error("Failed to save guest quiz result:", error);
-    return NextResponse.json(
-      { success: false, error: "Failed to save result" },
-      { status: 500 }
-    );
-  }
-}
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage-quiz/app/api/quiz/verify-answer/index.html b/frontend/coverage-quiz/app/api/quiz/verify-answer/index.html deleted file mode 100644 index 15298bb2..00000000 --- a/frontend/coverage-quiz/app/api/quiz/verify-answer/index.html +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - Code coverage report for app/api/quiz/verify-answer - - - - - - - - - -
-
-

All files app/api/quiz/verify-answer

-
- -
- 92.3% - Statements - 12/13 -
- - -
- 100% - Branches - 9/9 -
- - -
- 100% - Functions - 1/1 -
- - -
- 92.3% - Lines - 12/13 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
route.ts -
-
92.3%12/13100%9/9100%1/192.3%12/13
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage-quiz/app/api/quiz/verify-answer/route.ts.html b/frontend/coverage-quiz/app/api/quiz/verify-answer/route.ts.html deleted file mode 100644 index 46f20bcf..00000000 --- a/frontend/coverage-quiz/app/api/quiz/verify-answer/route.ts.html +++ /dev/null @@ -1,232 +0,0 @@ - - - - - - Code coverage report for app/api/quiz/verify-answer/route.ts - - - - - - - - - -
-
-

All files / app/api/quiz/verify-answer route.ts

-
- -
- 92.3% - Statements - 12/13 -
- - -
- 100% - Branches - 9/9 -
- - -
- 100% - Functions - 1/1 -
- - -
- 92.3% - Lines - 12/13 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50  -  -  -  -  -  -  -  -  -  -8x -8x -8x -  -8x -3x -  -  -  -  -  -5x -  -5x -2x -  -  -  -  -  -3x -  -3x -1x -  -  -  -  -  -2x -  -  -  -  -  -  -  -  -  - 
import { NextRequest, NextResponse } from 'next/server';
-import { decryptAnswers } from '@/lib/quiz/quiz-crypto';
- 
-interface VerifyRequest {
-  questionId: string;
-  answerId: string;
-  encryptedAnswers: string;
-}
- 
-export async function POST(request: NextRequest) {
-  try {
-    const body: VerifyRequest = await request.json();
-    const { questionId, answerId, encryptedAnswers } = body;
- 
-    if (!questionId || !answerId || !encryptedAnswers) {
-      return NextResponse.json(
-        { error: 'Missing required fields' },
-        { status: 400 }
-      );
-    }
- 
-    const correctAnswersMap = decryptAnswers(encryptedAnswers);
- 
-    if (!correctAnswersMap) {
-      return NextResponse.json(
-        { error: 'Invalid encrypted data' },
-        { status: 400 }
-      );
-    }
- 
-    const correctAnswerId = correctAnswersMap[questionId];
- 
-    if (!correctAnswerId) {
-      return NextResponse.json(
-        { error: 'Question not found' },
-        { status: 404 }
-      );
-    }
- 
-    return NextResponse.json({
-      isCorrect: answerId === correctAnswerId,
-    });
-  } catch {
-    return NextResponse.json(
-      { error: 'Internal server error' },
-      { status: 500 }
-    );
-  }
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage-quiz/base.css b/frontend/coverage-quiz/base.css deleted file mode 100644 index f418035b..00000000 --- a/frontend/coverage-quiz/base.css +++ /dev/null @@ -1,224 +0,0 @@ -body, html { - margin:0; padding: 0; - height: 100%; -} -body { - font-family: Helvetica Neue, Helvetica, Arial; - font-size: 14px; - color:#333; -} -.small { font-size: 12px; } -*, *:after, *:before { - -webkit-box-sizing:border-box; - -moz-box-sizing:border-box; - box-sizing:border-box; - } -h1 { font-size: 20px; margin: 0;} -h2 { font-size: 14px; } -pre { - font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; - margin: 0; - padding: 0; - -moz-tab-size: 2; - -o-tab-size: 2; - tab-size: 2; -} -a { color:#0074D9; text-decoration:none; } -a:hover { text-decoration:underline; } -.strong { font-weight: bold; } -.space-top1 { padding: 10px 0 0 0; } -.pad2y { padding: 20px 0; } -.pad1y { padding: 10px 0; } -.pad2x { padding: 0 20px; } -.pad2 { padding: 20px; } -.pad1 { padding: 10px; } -.space-left2 { padding-left:55px; } -.space-right2 { padding-right:20px; } -.center { text-align:center; } -.clearfix { display:block; } -.clearfix:after { - content:''; - display:block; - height:0; - clear:both; - visibility:hidden; - } -.fl { float: left; } -@media only screen and (max-width:640px) { - .col3 { width:100%; max-width:100%; } - .hide-mobile { display:none!important; } -} - -.quiet { - color: #7f7f7f; - color: rgba(0,0,0,0.5); -} -.quiet a { opacity: 0.7; } - -.fraction { - font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; - font-size: 10px; - color: #555; - background: #E8E8E8; - padding: 4px 5px; - border-radius: 3px; - vertical-align: middle; -} - -div.path a:link, div.path a:visited { color: #333; } -table.coverage { - border-collapse: collapse; - margin: 10px 0 0 0; - padding: 0; -} - -table.coverage td { - margin: 0; - padding: 0; - vertical-align: top; -} -table.coverage td.line-count { - text-align: right; - padding: 0 5px 0 20px; -} -table.coverage td.line-coverage { - text-align: right; - padding-right: 10px; - min-width:20px; -} - -table.coverage td span.cline-any { - display: inline-block; - padding: 0 5px; - width: 100%; -} -.missing-if-branch { - display: inline-block; - margin-right: 5px; - border-radius: 3px; - position: relative; - padding: 0 4px; - background: #333; - color: yellow; -} - -.skip-if-branch { - display: none; - margin-right: 10px; - position: relative; - padding: 0 4px; - background: #ccc; - color: white; -} -.missing-if-branch .typ, .skip-if-branch .typ { - color: inherit !important; -} -.coverage-summary { - border-collapse: collapse; - width: 100%; -} -.coverage-summary tr { border-bottom: 1px solid #bbb; } -.keyline-all { border: 1px solid #ddd; } -.coverage-summary td, .coverage-summary th { padding: 10px; } -.coverage-summary tbody { border: 1px solid #bbb; } -.coverage-summary td { border-right: 1px solid #bbb; } -.coverage-summary td:last-child { border-right: none; } -.coverage-summary th { - text-align: left; - font-weight: normal; - white-space: nowrap; -} -.coverage-summary th.file { border-right: none !important; } -.coverage-summary th.pct { } -.coverage-summary th.pic, -.coverage-summary th.abs, -.coverage-summary td.pct, -.coverage-summary td.abs { text-align: right; } -.coverage-summary td.file { white-space: nowrap; } -.coverage-summary td.pic { min-width: 120px !important; } -.coverage-summary tfoot td { } - -.coverage-summary .sorter { - height: 10px; - width: 7px; - display: inline-block; - margin-left: 0.5em; - background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; -} -.coverage-summary .sorted .sorter { - background-position: 0 -20px; -} -.coverage-summary .sorted-desc .sorter { - background-position: 0 -10px; -} -.status-line { height: 10px; } -/* yellow */ -.cbranch-no { background: yellow !important; color: #111; } -/* dark red */ -.red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } -.low .chart { border:1px solid #C21F39 } -.highlighted, -.highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ - background: #C21F39 !important; -} -/* medium red */ -.cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } -/* light red */ -.low, .cline-no { background:#FCE1E5 } -/* light green */ -.high, .cline-yes { background:rgb(230,245,208) } -/* medium green */ -.cstat-yes { background:rgb(161,215,106) } -/* dark green */ -.status-line.high, .high .cover-fill { background:rgb(77,146,33) } -.high .chart { border:1px solid rgb(77,146,33) } -/* dark yellow (gold) */ -.status-line.medium, .medium .cover-fill { background: #f9cd0b; } -.medium .chart { border:1px solid #f9cd0b; } -/* light yellow */ -.medium { background: #fff4c2; } - -.cstat-skip { background: #ddd; color: #111; } -.fstat-skip { background: #ddd; color: #111 !important; } -.cbranch-skip { background: #ddd !important; color: #111; } - -span.cline-neutral { background: #eaeaea; } - -.coverage-summary td.empty { - opacity: .5; - padding-top: 4px; - padding-bottom: 4px; - line-height: 1; - color: #888; -} - -.cover-fill, .cover-empty { - display:inline-block; - height: 12px; -} -.chart { - line-height: 0; -} -.cover-empty { - background: white; -} -.cover-full { - border-right: none !important; -} -pre.prettyprint { - border: none !important; - padding: 0 !important; - margin: 0 !important; -} -.com { color: #999 !important; } -.ignore-none { color: #999; font-weight: normal; } - -.wrapper { - min-height: 100%; - height: auto !important; - height: 100%; - margin: 0 auto -48px; -} -.footer, .push { - height: 48px; -} diff --git a/frontend/coverage-quiz/block-navigation.js b/frontend/coverage-quiz/block-navigation.js deleted file mode 100644 index 530d1ed2..00000000 --- a/frontend/coverage-quiz/block-navigation.js +++ /dev/null @@ -1,87 +0,0 @@ -/* eslint-disable */ -var jumpToCode = (function init() { - // Classes of code we would like to highlight in the file view - var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; - - // Elements to highlight in the file listing view - var fileListingElements = ['td.pct.low']; - - // We don't want to select elements that are direct descendants of another match - var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` - - // Selector that finds elements on the page to which we can jump - var selector = - fileListingElements.join(', ') + - ', ' + - notSelector + - missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` - - // The NodeList of matching elements - var missingCoverageElements = document.querySelectorAll(selector); - - var currentIndex; - - function toggleClass(index) { - missingCoverageElements - .item(currentIndex) - .classList.remove('highlighted'); - missingCoverageElements.item(index).classList.add('highlighted'); - } - - function makeCurrent(index) { - toggleClass(index); - currentIndex = index; - missingCoverageElements.item(index).scrollIntoView({ - behavior: 'smooth', - block: 'center', - inline: 'center' - }); - } - - function goToPrevious() { - var nextIndex = 0; - if (typeof currentIndex !== 'number' || currentIndex === 0) { - nextIndex = missingCoverageElements.length - 1; - } else if (missingCoverageElements.length > 1) { - nextIndex = currentIndex - 1; - } - - makeCurrent(nextIndex); - } - - function goToNext() { - var nextIndex = 0; - - if ( - typeof currentIndex === 'number' && - currentIndex < missingCoverageElements.length - 1 - ) { - nextIndex = currentIndex + 1; - } - - makeCurrent(nextIndex); - } - - return function jump(event) { - if ( - document.getElementById('fileSearch') === document.activeElement && - document.activeElement != null - ) { - // if we're currently focused on the search input, we don't want to navigate - return; - } - - switch (event.which) { - case 78: // n - case 74: // j - goToNext(); - break; - case 66: // b - case 75: // k - case 80: // p - goToPrevious(); - break; - } - }; -})(); -window.addEventListener('keydown', jumpToCode); diff --git a/frontend/coverage-quiz/favicon.png b/frontend/coverage-quiz/favicon.png deleted file mode 100644 index c1525b811a167671e9de1fa78aab9f5c0b61cef7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 445 zcmV;u0Yd(XP))rP{nL}Ln%S7`m{0DjX9TLF* zFCb$4Oi7vyLOydb!7n&^ItCzb-%BoB`=x@N2jll2Nj`kauio%aw_@fe&*}LqlFT43 z8doAAe))z_%=P%v^@JHp3Hjhj^6*Kr_h|g_Gr?ZAa&y>wxHE99Gk>A)2MplWz2xdG zy8VD2J|Uf#EAw*bo5O*PO_}X2Tob{%bUoO2G~T`@%S6qPyc}VkhV}UifBuRk>%5v( z)x7B{I~z*k<7dv#5tC+m{km(D087J4O%+<<;K|qwefb6@GSX45wCK}Sn*> - - - - Code coverage report for hooks - - - - - - - - - -
-
-

All files hooks

-
- -
- 88.42% - Statements - 84/95 -
- - -
- 70.45% - Branches - 31/44 -
- - -
- 90.9% - Functions - 20/22 -
- - -
- 92.77% - Lines - 77/83 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
useQuizGuards.ts -
-
85.33%64/7560%18/3088.88%16/1890.76%59/65
useQuizSession.ts -
-
100%20/2092.85%13/14100%4/4100%18/18
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage-quiz/hooks/useQuizGuards.ts.html b/frontend/coverage-quiz/hooks/useQuizGuards.ts.html deleted file mode 100644 index 8395532d..00000000 --- a/frontend/coverage-quiz/hooks/useQuizGuards.ts.html +++ /dev/null @@ -1,430 +0,0 @@ - - - - - - Code coverage report for hooks/useQuizGuards.ts - - - - - - - - - -
-
-

All files / hooks useQuizGuards.ts

-
- -
- 85.33% - Statements - 64/75 -
- - -
- 60% - Branches - 18/30 -
- - -
- 88.88% - Functions - 16/18 -
- - -
- 90.76% - Lines - 59/65 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83 -84 -85 -86 -87 -88 -89 -90 -91 -92 -93 -94 -95 -96 -97 -98 -99 -100 -101 -102 -103 -104 -105 -106 -107 -108 -109 -110 -111 -112 -113 -114 -115 -116  -  -  -  -  -  -  -  -  -  -  -  -8x -8x -8x -8x -8x -1x -  -  -8x -8x -  -  -8x -8x -8x -  -  -8x -8x -  -7x -8x -7x -  -  -  -8x -8x -  -  -  -  -  -  -  -  -  -8x -8x -  -  -8x -8x -2x -1x -1x -1x -1x -  -  -  -8x -8x -  -  -8x -8x -2x -  -2x -2x -2x -  -2x -  -  -  -2x -1x -1x -1x -  -  -1x -1x -1x -  -  -8x -8x -  -  -8x -8x -2x -2x -  -2x -1x -1x -  -  -1x -1x -1x -1x -  -  -8x -8x -  -8x -  - 
import { useEffect, useRef } from 'react';
-import { clearQuizSession } from '@/lib/quiz/quiz-session';
-import { getQuizReloadKey } from '@/lib/quiz/quiz-storage-keys';
- 
-type UseQuizGuardsParams = {
-  quizId: string;
-  status: 'rules' | 'in_progress' | 'completed';
-  onExit: () => void;
-  resetViolations: () => void;
-};
- 
-export function useQuizGuards({ quizId, status, onExit, resetViolations }: UseQuizGuardsParams): { markQuitting: () => void } {
-  const isReloadingRef = useRef(false);
-  const statusRef = useRef(status);
-  const reloadKey = getQuizReloadKey(quizId);
-  const isQuittingRef = useRef(false);
-  const markQuitting = () => {
-  isQuittingRef.current = true;
-};
- 
-  useEffect(() => {
-    statusRef.current = status;
-  }, [status]);
- 
-  useEffect(() => {
-    isQuittingRef.current = false;
-    isReloadingRef.current = false;
-  }, []);
- 
-  useEffect(() => {
-    if (status !== 'in_progress') return;
- 
-    const hasGuard = window.history.state?.quizGuard;
-    if (!hasGuard) {
-      window.history.pushState({ quizGuard: true }, '');
-    }
-  }, [status]);
- 
-  useEffect(() => {
-    const handleKeyDown = (e: KeyboardEvent) => {
-      const key = e.key.toLowerCase();
-      if (key === 'f5' || ((e.ctrlKey || e.metaKey) && key === 'r')) {
-        isReloadingRef.current = true;
-        window.setTimeout(() => {
-          isReloadingRef.current = false;
-        }, 1000);
-      }
-    };
- 
-    window.addEventListener('keydown', handleKeyDown);
-    return () => window.removeEventListener('keydown', handleKeyDown);
-  }, []);
- 
-  useEffect(() => {
-    const handleBeforeUnload = (e: BeforeUnloadEvent) => {
-      if (statusRef.current === 'in_progress' && !isQuittingRef.current) {
-        sessionStorage.setItem(reloadKey, '1');
-        Iif (isReloadingRef.current) return;
-        e.preventDefault();
-        e.returnValue = '';
-      }
-    };
- 
-    window.addEventListener('beforeunload', handleBeforeUnload);
-    return () => window.removeEventListener('beforeunload', handleBeforeUnload);
-  }, [reloadKey]);
- 
-  useEffect(() => {
-    const handleClick = (e: MouseEvent) => {
-      Iif (statusRef.current !== 'in_progress') return;
- 
-      const target = e.target as HTMLElement;
-      const link = target.closest('a');
-      Iif (!link?.href) return;
- 
-      Iif (link.href.includes(window.location.pathname.replace(/^\/(uk|en|pl)/, ''))) {
-        return;
-      }
- 
-      if (!window.confirm('Exit quiz? Your progress will not be saved.')) {
-        e.preventDefault();
-        e.stopPropagation();
-        return;
-      }
- 
-      isQuittingRef.current = true;
-      clearQuizSession(quizId);
-      resetViolations();
-    };
- 
-    document.addEventListener('click', handleClick, true);
-    return () => document.removeEventListener('click', handleClick, true);
-  }, [quizId, resetViolations]);
- 
-  useEffect(() => {
-    const handlePopState = () => {
-      Iif (statusRef.current !== 'in_progress') return;
-      Iif (isQuittingRef.current) return;
- 
-      if (!window.confirm('Exit quiz? Your progress will not be saved.')) {
-        window.history.pushState({ quizGuard: true }, '');
-        return;
-      }
- 
-      isQuittingRef.current = true;
-      clearQuizSession(quizId);
-      resetViolations();
-      onExit();
-    };
- 
-    window.addEventListener('popstate', handlePopState);
-    return () => window.removeEventListener('popstate', handlePopState);
-  }, [quizId, resetViolations, onExit]);
-  return { markQuitting };
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage-quiz/hooks/useQuizSession.ts.html b/frontend/coverage-quiz/hooks/useQuizSession.ts.html deleted file mode 100644 index 29c7a20b..00000000 --- a/frontend/coverage-quiz/hooks/useQuizSession.ts.html +++ /dev/null @@ -1,313 +0,0 @@ - - - - - - Code coverage report for hooks/useQuizSession.ts - - - - - - - - - -
-
-

All files / hooks useQuizSession.ts

-
- -
- 100% - Statements - 20/20 -
- - -
- 92.85% - Branches - 13/14 -
- - -
- 100% - Functions - 4/4 -
- - -
- 100% - Lines - 18/18 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -6x -  -6x -6x -6x -1x -  -  -6x -6x -1x -  -  -6x -6x -  -3x -2x -  -1x -  -  -  -6x -6x -  -1x -  -  -1x -  -  -  -  -  -  -  -  -  -  -  -6x -  -  - 
import { useEffect } from 'react';
-import {
-  saveQuizSession,
-  loadQuizSession,
-  clearQuizSession,
-  type QuizSessionData,
-} from '@/lib/quiz/quiz-session';
-import { QUIZ_ALLOW_RESTORE_KEY, getQuizReloadKey } from '@/lib/quiz/quiz-storage-keys';
- 
-type Answer = {
-  questionId: string;
-  selectedAnswerId: string;
-  isCorrect: boolean;
-  answeredAt: Date;
-};
- 
-type QuizState = {
-  status: 'rules' | 'in_progress' | 'completed';
-  currentIndex: number;
-  answers: Answer[];
-  questionStatus: 'answering' | 'revealed';
-  selectedAnswerId: string | null;
-  startedAt: Date | null;
-};
- 
-type UseQuizSessionParams = {
-  quizId: string;
-  state: QuizState;
-  onRestore: (data: QuizSessionData) => void;
-};
- 
-export function useQuizSession({ quizId, state, onRestore }: UseQuizSessionParams): void {
-  const reloadKey = getQuizReloadKey(quizId);
- 
-  useEffect(() => {
-    const isReload = sessionStorage.getItem(reloadKey);
-    if (isReload) {
-      sessionStorage.removeItem(reloadKey);
-    }
- 
-    const allowRestore = sessionStorage.getItem(QUIZ_ALLOW_RESTORE_KEY);
-    if (allowRestore) {
-      sessionStorage.removeItem(QUIZ_ALLOW_RESTORE_KEY);
-    }
- 
-    const saved = loadQuizSession(quizId);
-    if (!saved) return;
- 
-    if (isReload || allowRestore) {
-      onRestore(saved);
-    } else {
-      clearQuizSession(quizId);
-    }
-  }, [quizId, reloadKey, onRestore]);
- 
-  useEffect(() => {
-    if (state.status !== 'in_progress') return;
- 
-    const sessionData: QuizSessionData = {
-      status: state.status,
-      currentIndex: state.currentIndex,
-      answers: state.answers.map(a => ({
-        questionId: a.questionId,
-        selectedAnswerId: a.selectedAnswerId,
-        isCorrect: a.isCorrect,
-        answeredAt: a.answeredAt.getTime(),
-      })),
-      questionStatus: state.questionStatus,
-      selectedAnswerId: state.selectedAnswerId,
-      startedAt: state.startedAt?.getTime() ?? null,
-      savedAt: Date.now(),
-    };
- 
-    saveQuizSession(quizId, sessionData);
-  }, [quizId, state]);
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage-quiz/index.html b/frontend/coverage-quiz/index.html deleted file mode 100644 index 551b2a35..00000000 --- a/frontend/coverage-quiz/index.html +++ /dev/null @@ -1,176 +0,0 @@ - - - - - - Code coverage report for All files - - - - - - - - - -
-
-

All files

-
- -
- 87.64% - Statements - 234/267 -
- - -
- 74.13% - Branches - 86/116 -
- - -
- 93.02% - Functions - 40/43 -
- - -
- 90.94% - Lines - 221/243 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
app/api/quiz/[slug] -
-
100%13/1375%3/4100%3/3100%12/12
app/api/quiz/guest-result -
-
85%51/6075.75%25/3380%4/585.71%48/56
app/api/quiz/verify-answer -
-
92.3%12/13100%9/9100%1/192.3%12/13
hooks -
-
88.42%84/9570.45%31/4490.9%20/2292.77%77/83
lib/quiz -
-
86.04%74/8669.23%18/26100%12/1291.13%72/79
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage-quiz/lib/quiz/guest-quiz.ts.html b/frontend/coverage-quiz/lib/quiz/guest-quiz.ts.html deleted file mode 100644 index 9966161e..00000000 --- a/frontend/coverage-quiz/lib/quiz/guest-quiz.ts.html +++ /dev/null @@ -1,244 +0,0 @@ - - - - - - Code coverage report for lib/quiz/guest-quiz.ts - - - - - - - - - -
-
-

All files / lib/quiz guest-quiz.ts

-
- -
- 81.81% - Statements - 18/22 -
- - -
- 60% - Branches - 6/10 -
- - -
- 100% - Functions - 3/3 -
- - -
- 90% - Lines - 18/20 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -2x -2x -  -  -2x -  -  -2x -  -  -  -3x -  -  -  -3x -3x -  -3x -3x -3x -  -3x -1x -1x -  -  -1x -  -1x -1x -  -  -  -  -3x -3x - 
export interface PendingQuizResult {
-  quizId: string;
-  quizSlug: string;
-  answers: {
-    questionId: string;
-    selectedAnswerId: string;
-    isCorrect: boolean;
-  }[];
-  score: number;
-  totalQuestions: number;
-  percentage: number;
-  violations: { type: string; timestamp: number }[];
-  timeSpentSeconds: number;
-  savedAt: number;
-}
- 
-const STORAGE_KEY = 'devlovers_pending_quiz';
-const EXPIRY_HOURS = 24;
- 
-export function savePendingQuizResult(result: PendingQuizResult): void {
-  Iif (typeof window === 'undefined') {
-    return;
-  }
-  localStorage.setItem(STORAGE_KEY, JSON.stringify(result));
-}
- 
-export function getPendingQuizResult(): PendingQuizResult | null {
-  Iif (typeof window === 'undefined') {
-    return null;
-  }
-  
-  const stored = localStorage.getItem(STORAGE_KEY);
-  Iif (!stored) return null;
-  
-  try {
-    const result: PendingQuizResult = JSON.parse(stored);
-    const expiryTime = result.savedAt + EXPIRY_HOURS * 60 * 60 * 1000;
-    
-    if (Date.now() > expiryTime) {
-      clearPendingQuizResult();
-      return null;
-    }
-    
-    return result;
-  } catch {
-    clearPendingQuizResult();
-    return null;
-  }
-}
- 
-export function clearPendingQuizResult(): void {
-  Iif (typeof window === 'undefined') return;
-  localStorage.removeItem(STORAGE_KEY);
-}
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage-quiz/lib/quiz/index.html b/frontend/coverage-quiz/lib/quiz/index.html deleted file mode 100644 index 56441fc6..00000000 --- a/frontend/coverage-quiz/lib/quiz/index.html +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - Code coverage report for lib/quiz - - - - - - - - - -
-
-

All files lib/quiz

-
- -
- 86.04% - Statements - 74/86 -
- - -
- 69.23% - Branches - 18/26 -
- - -
- 100% - Functions - 12/12 -
- - -
- 91.13% - Lines - 72/79 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
FileStatementsBranchesFunctionsLines
guest-quiz.ts -
-
81.81%18/2260%6/10100%3/390%18/20
quiz-crypto.ts -
-
96.96%32/3375%3/4100%5/596.87%31/32
quiz-session.ts -
-
75.86%22/2975%9/12100%3/384%21/25
quiz-storage-keys.ts -
-
100%2/2100%0/0100%1/1100%2/2
-
-
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage-quiz/lib/quiz/quiz-crypto.ts.html b/frontend/coverage-quiz/lib/quiz/quiz-crypto.ts.html deleted file mode 100644 index 9accc795..00000000 --- a/frontend/coverage-quiz/lib/quiz/quiz-crypto.ts.html +++ /dev/null @@ -1,331 +0,0 @@ - - - - - - Code coverage report for lib/quiz/quiz-crypto.ts - - - - - - - - - -
-
-

All files / lib/quiz quiz-crypto.ts

-
- -
- 96.96% - Statements - 32/33 -
- - -
- 75% - Branches - 3/4 -
- - -
- 100% - Functions - 5/5 -
- - -
- 96.87% - Lines - 31/32 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -67 -68 -69 -70 -71 -72 -73 -74 -75 -76 -77 -78 -79 -80 -81 -82 -83  -  -2x -2x -2x -  -  -31x -31x -  -  -31x -  -  -  -  -  -  -  -  -  -  -18x -18x -18x -  -18x -18x -  -  -  -  -18x -  -18x -18x -  -  -  -  -  -  -13x -13x -13x -  -13x -13x -13x -  -13x -13x -  -13x -  -  -  -  -13x -  -  -6x -  -  -  -  -  -  -  -  -  -4x -  -4x -8x -7x -6x -  -  -  -4x -  - 
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';
- 
-const ALGORITHM = 'aes-256-gcm';
-const IV_LENGTH = 16;
-const AUTH_TAG_LENGTH = 16;
- 
-function getEncryptionKey(): Buffer {
-  const key = process.env.QUIZ_ENCRYPTION_KEY;
-  Iif (!key) {
-    throw new Error('QUIZ_ENCRYPTION_KEY environment variable is not set');
-  }
-  return Buffer.from(key, 'hex');
-}
- 
-export interface CorrectAnswersMap {
-  [questionId: string]: string;
-}
- 
-/**
- * Encrypts answers map. Returns base64 string: IV (16b) + AuthTag (16b) + CipherText
- */
-export function encryptAnswers(answers: CorrectAnswersMap): string {
-  const key = getEncryptionKey();
-  const iv = randomBytes(IV_LENGTH);
-  const cipher = createCipheriv(ALGORITHM, key, iv);
- 
-  const plaintext = JSON.stringify(answers);
-  const encrypted = Buffer.concat([
-    cipher.update(plaintext, 'utf8'),
-    cipher.final(),
-  ]);
- 
-  const authTag = cipher.getAuthTag();
- 
-  const result = Buffer.concat([iv, authTag, encrypted]);
-  return result.toString('base64');
-}
- 
-/**
- * Decrypts blob. Returns null if tampered or wrong key.
- */
-export function decryptAnswers(encryptedBlob: string): CorrectAnswersMap | null {
-  try {
-    const key = getEncryptionKey();
-    const data = Buffer.from(encryptedBlob, 'base64');
- 
-    const iv = data.subarray(0, IV_LENGTH);
-    const authTag = data.subarray(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH);
-    const ciphertext = data.subarray(IV_LENGTH + AUTH_TAG_LENGTH);
- 
-    const decipher = createDecipheriv(ALGORITHM, key, iv);
-    decipher.setAuthTag(authTag);
- 
-    const decrypted = Buffer.concat([
-      decipher.update(ciphertext),
-      decipher.final(),
-    ]);
- 
-    return JSON.parse(decrypted.toString('utf8'));
-  } catch {
-    // Decryption failed - tampered data or wrong key
-    return null;
-  }
-}
- 
-/**
- * Creates encrypted blob from quiz questions
- */
-export function createEncryptedAnswersBlob(
-  questions: Array<{ id: string; answers: Array<{ id: string; isCorrect: boolean }> }>
-): string {
-  const answersMap: CorrectAnswersMap = {};
- 
-  for (const question of questions) {
-    const correctAnswer = question.answers.find(a => a.isCorrect);
-    if (correctAnswer) {
-      answersMap[question.id] = correctAnswer.id;
-    }
-  }
- 
-  return encryptAnswers(answersMap);
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage-quiz/lib/quiz/quiz-session.ts.html b/frontend/coverage-quiz/lib/quiz/quiz-session.ts.html deleted file mode 100644 index 1abb3170..00000000 --- a/frontend/coverage-quiz/lib/quiz/quiz-session.ts.html +++ /dev/null @@ -1,283 +0,0 @@ - - - - - - Code coverage report for lib/quiz/quiz-session.ts - - - - - - - - - -
-
-

All files / lib/quiz quiz-session.ts

-
- -
- 75.86% - Statements - 22/29 -
- - -
- 75% - Branches - 9/12 -
- - -
- 100% - Functions - 3/3 -
- - -
- 84% - Lines - 21/25 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -6 -7 -8 -9 -10 -11 -12 -13 -14 -15 -16 -17 -18 -19 -20 -21 -22 -23 -24 -25 -26 -27 -28 -29 -30 -31 -32 -33 -34 -35 -36 -37 -38 -39 -40 -41 -42 -43 -44 -45 -46 -47 -48 -49 -50 -51 -52 -53 -54 -55 -56 -57 -58 -59 -60 -61 -62 -63 -64 -65 -66 -672x -2x -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -6x -  -6x -6x -6x -  -  -  -  -  -  -7x -  -7x -7x -7x -  -6x -  -  -6x -2x -2x -  -  -  -4x -2x -2x -  -  -2x -  -  -  -  -  -  -  -7x -  -7x -7x -  -  -  -  - 
const STORAGE_KEY_PREFIX = 'quiz_session_';
-const SESSION_TTL_MS = 30 * 60 * 1000; // 30 minutes
- 
-export interface QuizSessionData {
-  status: 'rules' | 'in_progress' | 'completed';
-  currentIndex: number;
-  answers: Array<{
-    questionId: string;
-    selectedAnswerId: string;
-    isCorrect: boolean;
-    answeredAt: number;
-  }>;
-  questionStatus: 'answering' | 'revealed';
-  selectedAnswerId: string | null;
-  startedAt: number | null;
-  savedAt: number;
-}
- 
-export function saveQuizSession(quizId: string, state: QuizSessionData): void {
-  Iif (typeof window === 'undefined') return;
- 
-  try {
-    const data = { ...state, savedAt: Date.now() };
-    localStorage.setItem(`${STORAGE_KEY_PREFIX}${quizId}`, JSON.stringify(data));
-  } catch (e) {
-    console.error('Failed to save quiz session:', e);
-  }
-}
- 
-export function loadQuizSession(quizId: string): QuizSessionData | null {
-  Iif (typeof window === 'undefined') return null;
- 
-  try {
-    const raw = localStorage.getItem(`${STORAGE_KEY_PREFIX}${quizId}`);
-    if (!raw) return null;
- 
-    const data: QuizSessionData = JSON.parse(raw);
- 
-    // Discard sessions older than 30 minutes
-    if (Date.now() - data.savedAt > SESSION_TTL_MS) {
-      clearQuizSession(quizId);
-      return null;
-    }
- 
-    // Only restore in_progress sessions
-    if (data.status !== 'in_progress') {
-      clearQuizSession(quizId);
-      return null;
-    }
- 
-    return data;
-  } catch (e) {
-    console.error('Failed to load quiz session:', e);
-    return null;
-  }
-}
- 
-export function clearQuizSession(quizId: string): void {
-  Iif (typeof window === 'undefined') return;
- 
-  try {
-    localStorage.removeItem(`${STORAGE_KEY_PREFIX}${quizId}`);
-  } catch (e) {
-    console.error('Failed to clear quiz session:', e);
-  }
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage-quiz/lib/quiz/quiz-storage-keys.ts.html b/frontend/coverage-quiz/lib/quiz/quiz-storage-keys.ts.html deleted file mode 100644 index 53270c9f..00000000 --- a/frontend/coverage-quiz/lib/quiz/quiz-storage-keys.ts.html +++ /dev/null @@ -1,100 +0,0 @@ - - - - - - Code coverage report for lib/quiz/quiz-storage-keys.ts - - - - - - - - - -
-
-

All files / lib/quiz quiz-storage-keys.ts

-
- -
- 100% - Statements - 2/2 -
- - -
- 100% - Branches - 0/0 -
- - -
- 100% - Functions - 1/1 -
- - -
- 100% - Lines - 2/2 -
- - -
-

- Press n or j to go to the next uncovered block, b, p or k for the previous block. -

- -
-
-

-
1 -2 -3 -4 -5 -62x -  -  -17x -  - 
export const QUIZ_ALLOW_RESTORE_KEY = 'quiz-allow-restore';
- 
-export function getQuizReloadKey(quizId: string): string {
-  return `quiz-reload:${quizId}`;
-}
- 
- -
-
- - - - - - - - \ No newline at end of file diff --git a/frontend/coverage-quiz/prettify.css b/frontend/coverage-quiz/prettify.css deleted file mode 100644 index b317a7cd..00000000 --- a/frontend/coverage-quiz/prettify.css +++ /dev/null @@ -1 +0,0 @@ -.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} diff --git a/frontend/coverage-quiz/prettify.js b/frontend/coverage-quiz/prettify.js deleted file mode 100644 index b3225238..00000000 --- a/frontend/coverage-quiz/prettify.js +++ /dev/null @@ -1,2 +0,0 @@ -/* eslint-disable */ -window.PR_SHOULD_USE_CONTINUATION=true;(function(){var h=["break,continue,do,else,for,if,return,while"];var u=[h,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var p=[u,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var l=[p,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var x=[p,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var R=[x,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"];var r="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes";var w=[p,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var s="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var I=[h,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var f=[h,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var H=[h,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var A=[l,R,w,s+I,f,H];var e=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/;var C="str";var z="kwd";var j="com";var O="typ";var G="lit";var L="pun";var F="pln";var m="tag";var E="dec";var J="src";var P="atn";var n="atv";var N="nocode";var M="(?:^^\\.?|[+-]|\\!|\\!=|\\!==|\\#|\\%|\\%=|&|&&|&&=|&=|\\(|\\*|\\*=|\\+=|\\,|\\-=|\\->|\\/|\\/=|:|::|\\;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|\\?|\\@|\\[|\\^|\\^=|\\^\\^|\\^\\^=|\\{|\\||\\|=|\\|\\||\\|\\|=|\\~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function k(Z){var ad=0;var S=false;var ac=false;for(var V=0,U=Z.length;V122)){if(!(al<65||ag>90)){af.push([Math.max(65,ag)|32,Math.min(al,90)|32])}if(!(al<97||ag>122)){af.push([Math.max(97,ag)&~32,Math.min(al,122)&~32])}}}}af.sort(function(av,au){return(av[0]-au[0])||(au[1]-av[1])});var ai=[];var ap=[NaN,NaN];for(var ar=0;arat[0]){if(at[1]+1>at[0]){an.push("-")}an.push(T(at[1]))}}an.push("]");return an.join("")}function W(al){var aj=al.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var ah=aj.length;var an=[];for(var ak=0,am=0;ak=2&&ai==="["){aj[ak]=X(ag)}else{if(ai!=="\\"){aj[ak]=ag.replace(/[a-zA-Z]/g,function(ao){var ap=ao.charCodeAt(0);return"["+String.fromCharCode(ap&~32,ap|32)+"]"})}}}}return aj.join("")}var aa=[];for(var V=0,U=Z.length;V=0;){S[ac.charAt(ae)]=Y}}var af=Y[1];var aa=""+af;if(!ag.hasOwnProperty(aa)){ah.push(af);ag[aa]=null}}ah.push(/[\0-\uffff]/);V=k(ah)})();var X=T.length;var W=function(ah){var Z=ah.sourceCode,Y=ah.basePos;var ad=[Y,F];var af=0;var an=Z.match(V)||[];var aj={};for(var ae=0,aq=an.length;ae=5&&"lang-"===ap.substring(0,5);if(am&&!(ai&&typeof ai[1]==="string")){am=false;ap=J}if(!am){aj[ag]=ap}}var ab=af;af+=ag.length;if(!am){ad.push(Y+ab,ap)}else{var al=ai[1];var ak=ag.indexOf(al);var ac=ak+al.length;if(ai[2]){ac=ag.length-ai[2].length;ak=ac-al.length}var ar=ap.substring(5);B(Y+ab,ag.substring(0,ak),W,ad);B(Y+ab+ak,al,q(ar,al),ad);B(Y+ab+ac,ag.substring(ac),W,ad)}}ah.decorations=ad};return W}function i(T){var W=[],S=[];if(T.tripleQuotedStrings){W.push([C,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(T.multiLineStrings){W.push([C,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{W.push([C,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(T.verbatimStrings){S.push([C,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var Y=T.hashComments;if(Y){if(T.cStyleComments){if(Y>1){W.push([j,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{W.push([j,/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}S.push([C,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,null])}else{W.push([j,/^#[^\r\n]*/,null,"#"])}}if(T.cStyleComments){S.push([j,/^\/\/[^\r\n]*/,null]);S.push([j,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(T.regexLiterals){var X=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");S.push(["lang-regex",new RegExp("^"+M+"("+X+")")])}var V=T.types;if(V){S.push([O,V])}var U=(""+T.keywords).replace(/^ | $/g,"");if(U.length){S.push([z,new RegExp("^(?:"+U.replace(/[\s,]+/g,"|")+")\\b"),null])}W.push([F,/^\s+/,null," \r\n\t\xA0"]);S.push([G,/^@[a-z_$][a-z_$@0-9]*/i,null],[O,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[F,/^[a-z_$][a-z_$@0-9]*/i,null],[G,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[F,/^\\[\s\S]?/,null],[L,/^.[^\s\w\.$@\'\"\`\/\#\\]*/,null]);return g(W,S)}var K=i({keywords:A,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function Q(V,ag){var U=/(?:^|\s)nocode(?:\s|$)/;var ab=/\r\n?|\n/;var ac=V.ownerDocument;var S;if(V.currentStyle){S=V.currentStyle.whiteSpace}else{if(window.getComputedStyle){S=ac.defaultView.getComputedStyle(V,null).getPropertyValue("white-space")}}var Z=S&&"pre"===S.substring(0,3);var af=ac.createElement("LI");while(V.firstChild){af.appendChild(V.firstChild)}var W=[af];function ae(al){switch(al.nodeType){case 1:if(U.test(al.className)){break}if("BR"===al.nodeName){ad(al);if(al.parentNode){al.parentNode.removeChild(al)}}else{for(var an=al.firstChild;an;an=an.nextSibling){ae(an)}}break;case 3:case 4:if(Z){var am=al.nodeValue;var aj=am.match(ab);if(aj){var ai=am.substring(0,aj.index);al.nodeValue=ai;var ah=am.substring(aj.index+aj[0].length);if(ah){var ak=al.parentNode;ak.insertBefore(ac.createTextNode(ah),al.nextSibling)}ad(al);if(!ai){al.parentNode.removeChild(al)}}}break}}function ad(ak){while(!ak.nextSibling){ak=ak.parentNode;if(!ak){return}}function ai(al,ar){var aq=ar?al.cloneNode(false):al;var ao=al.parentNode;if(ao){var ap=ai(ao,1);var an=al.nextSibling;ap.appendChild(aq);for(var am=an;am;am=an){an=am.nextSibling;ap.appendChild(am)}}return aq}var ah=ai(ak.nextSibling,0);for(var aj;(aj=ah.parentNode)&&aj.nodeType===1;){ah=aj}W.push(ah)}for(var Y=0;Y=S){ah+=2}if(V>=ap){Z+=2}}}var t={};function c(U,V){for(var S=V.length;--S>=0;){var T=V[S];if(!t.hasOwnProperty(T)){t[T]=U}else{if(window.console){console.warn("cannot override language handler %s",T)}}}}function q(T,S){if(!(T&&t.hasOwnProperty(T))){T=/^\s*]*(?:>|$)/],[j,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[L,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);c(g([[F,/^[\s]+/,null," \t\r\n"],[n,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[m,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[P,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[L,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);c(g([],[[n,/^[\s\S]+/]]),["uq.val"]);c(i({keywords:l,hashComments:true,cStyleComments:true,types:e}),["c","cc","cpp","cxx","cyc","m"]);c(i({keywords:"null,true,false"}),["json"]);c(i({keywords:R,hashComments:true,cStyleComments:true,verbatimStrings:true,types:e}),["cs"]);c(i({keywords:x,cStyleComments:true}),["java"]);c(i({keywords:H,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);c(i({keywords:I,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);c(i({keywords:s,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);c(i({keywords:f,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);c(i({keywords:w,cStyleComments:true,regexLiterals:true}),["js"]);c(i({keywords:r,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);c(g([],[[C,/^[\s\S]+/]]),["regex"]);function d(V){var U=V.langExtension;try{var S=a(V.sourceNode);var T=S.sourceCode;V.sourceCode=T;V.spans=S.spans;V.basePos=0;q(U,T)(V);D(V)}catch(W){if("console" in window){console.log(W&&W.stack?W.stack:W)}}}function y(W,V,U){var S=document.createElement("PRE");S.innerHTML=W;if(U){Q(S,U)}var T={langExtension:V,numberLines:U,sourceNode:S};d(T);return S.innerHTML}function b(ad){function Y(af){return document.getElementsByTagName(af)}var ac=[Y("pre"),Y("code"),Y("xmp")];var T=[];for(var aa=0;aa=0){var ah=ai.match(ab);var am;if(!ah&&(am=o(aj))&&"CODE"===am.tagName){ah=am.className.match(ab)}if(ah){ah=ah[1]}var al=false;for(var ak=aj.parentNode;ak;ak=ak.parentNode){if((ak.tagName==="pre"||ak.tagName==="code"||ak.tagName==="xmp")&&ak.className&&ak.className.indexOf("prettyprint")>=0){al=true;break}}if(!al){var af=aj.className.match(/\blinenums\b(?::(\d+))?/);af=af?af[1]&&af[1].length?+af[1]:true:false;if(af){Q(aj,af)}S={langExtension:ah,sourceNode:aj,numberLines:af};d(S)}}}if(X]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); diff --git a/frontend/coverage-quiz/sort-arrow-sprite.png b/frontend/coverage-quiz/sort-arrow-sprite.png deleted file mode 100644 index 6ed68316eb3f65dec9063332d2f69bf3093bbfab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 138 zcmeAS@N?(olHy`uVBq!ia0vp^>_9Bd!3HEZxJ@+%Qh}Z>jv*C{$p!i!8j}?a+@3A= zIAGwzjijN=FBi!|L1t?LM;Q;gkwn>2cAy-KV{dn nf0J1DIvEHQu*n~6U}x}qyky7vi4|9XhBJ7&`njxgN@xNA8m%nc diff --git a/frontend/coverage-quiz/sorter.js b/frontend/coverage-quiz/sorter.js deleted file mode 100644 index 4ed70ae5..00000000 --- a/frontend/coverage-quiz/sorter.js +++ /dev/null @@ -1,210 +0,0 @@ -/* eslint-disable */ -var addSorting = (function() { - 'use strict'; - var cols, - currentSort = { - index: 0, - desc: false - }; - - // returns the summary table element - function getTable() { - return document.querySelector('.coverage-summary'); - } - // returns the thead element of the summary table - function getTableHeader() { - return getTable().querySelector('thead tr'); - } - // returns the tbody element of the summary table - function getTableBody() { - return getTable().querySelector('tbody'); - } - // returns the th element for nth column - function getNthColumn(n) { - return getTableHeader().querySelectorAll('th')[n]; - } - - function onFilterInput() { - const searchValue = document.getElementById('fileSearch').value; - const rows = document.getElementsByTagName('tbody')[0].children; - - // Try to create a RegExp from the searchValue. If it fails (invalid regex), - // it will be treated as a plain text search - let searchRegex; - try { - searchRegex = new RegExp(searchValue, 'i'); // 'i' for case-insensitive - } catch (error) { - searchRegex = null; - } - - for (let i = 0; i < rows.length; i++) { - const row = rows[i]; - let isMatch = false; - - if (searchRegex) { - // If a valid regex was created, use it for matching - isMatch = searchRegex.test(row.textContent); - } else { - // Otherwise, fall back to the original plain text search - isMatch = row.textContent - .toLowerCase() - .includes(searchValue.toLowerCase()); - } - - row.style.display = isMatch ? '' : 'none'; - } - } - - // loads the search box - function addSearchBox() { - var template = document.getElementById('filterTemplate'); - var templateClone = template.content.cloneNode(true); - templateClone.getElementById('fileSearch').oninput = onFilterInput; - template.parentElement.appendChild(templateClone); - } - - // loads all columns - function loadColumns() { - var colNodes = getTableHeader().querySelectorAll('th'), - colNode, - cols = [], - col, - i; - - for (i = 0; i < colNodes.length; i += 1) { - colNode = colNodes[i]; - col = { - key: colNode.getAttribute('data-col'), - sortable: !colNode.getAttribute('data-nosort'), - type: colNode.getAttribute('data-type') || 'string' - }; - cols.push(col); - if (col.sortable) { - col.defaultDescSort = col.type === 'number'; - colNode.innerHTML = - colNode.innerHTML + ''; - } - } - return cols; - } - // attaches a data attribute to every tr element with an object - // of data values keyed by column name - function loadRowData(tableRow) { - var tableCols = tableRow.querySelectorAll('td'), - colNode, - col, - data = {}, - i, - val; - for (i = 0; i < tableCols.length; i += 1) { - colNode = tableCols[i]; - col = cols[i]; - val = colNode.getAttribute('data-value'); - if (col.type === 'number') { - val = Number(val); - } - data[col.key] = val; - } - return data; - } - // loads all row data - function loadData() { - var rows = getTableBody().querySelectorAll('tr'), - i; - - for (i = 0; i < rows.length; i += 1) { - rows[i].data = loadRowData(rows[i]); - } - } - // sorts the table using the data for the ith column - function sortByIndex(index, desc) { - var key = cols[index].key, - sorter = function(a, b) { - a = a.data[key]; - b = b.data[key]; - return a < b ? -1 : a > b ? 1 : 0; - }, - finalSorter = sorter, - tableBody = document.querySelector('.coverage-summary tbody'), - rowNodes = tableBody.querySelectorAll('tr'), - rows = [], - i; - - if (desc) { - finalSorter = function(a, b) { - return -1 * sorter(a, b); - }; - } - - for (i = 0; i < rowNodes.length; i += 1) { - rows.push(rowNodes[i]); - tableBody.removeChild(rowNodes[i]); - } - - rows.sort(finalSorter); - - for (i = 0; i < rows.length; i += 1) { - tableBody.appendChild(rows[i]); - } - } - // removes sort indicators for current column being sorted - function removeSortIndicators() { - var col = getNthColumn(currentSort.index), - cls = col.className; - - cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); - col.className = cls; - } - // adds sort indicators for current column being sorted - function addSortIndicators() { - getNthColumn(currentSort.index).className += currentSort.desc - ? ' sorted-desc' - : ' sorted'; - } - // adds event listeners for all sorter widgets - function enableUI() { - var i, - el, - ithSorter = function ithSorter(i) { - var col = cols[i]; - - return function() { - var desc = col.defaultDescSort; - - if (currentSort.index === i) { - desc = !currentSort.desc; - } - sortByIndex(i, desc); - removeSortIndicators(); - currentSort.index = i; - currentSort.desc = desc; - addSortIndicators(); - }; - }; - for (i = 0; i < cols.length; i += 1) { - if (cols[i].sortable) { - // add the click event handler on the th so users - // dont have to click on those tiny arrows - el = getNthColumn(i).querySelector('.sorter').parentElement; - if (el.addEventListener) { - el.addEventListener('click', ithSorter(i)); - } else { - el.attachEvent('onclick', ithSorter(i)); - } - } - } - } - // adds sorting functionality to the UI - return function() { - if (!getTable()) { - return; - } - cols = loadColumns(); - loadData(); - addSearchBox(); - addSortIndicators(); - enableUI(); - }; -})(); - -window.addEventListener('load', addSorting); From 786b33c7ba00b081eb878ad3aa84ecc5c6529ad3 Mon Sep 17 00:00:00 2001 From: Lesia Soloviova Date: Sun, 25 Jan 2026 17:40:52 +0200 Subject: [PATCH 08/61] chore: add coverage-quiz to .gitignore, fix quiz guards test --- .gitignore | 1 + frontend/lib/tests/quiz/use-quiz-guards.test.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 8705e153..65e2c957 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,7 @@ yarn-error.log* # Coverage directory /coverage +/coverage-quiz # Dotenv files *.local diff --git a/frontend/lib/tests/quiz/use-quiz-guards.test.ts b/frontend/lib/tests/quiz/use-quiz-guards.test.ts index 0b4c396c..62fe0763 100644 --- a/frontend/lib/tests/quiz/use-quiz-guards.test.ts +++ b/frontend/lib/tests/quiz/use-quiz-guards.test.ts @@ -156,6 +156,6 @@ describe('useQuizGuards', () => { window.dispatchEvent(new Event('beforeunload')); }); - expect(sessionStorage.getItem(reloadKey)).toBeNull; + expect(sessionStorage.getItem(reloadKey)).toBeNull(); }); }); From 82d01ea70430e2af83c9de2b42c35151a66fb4bf Mon Sep 17 00:00:00 2001 From: Viktor Svertoka Date: Sat, 31 Jan 2026 16:55:06 +0200 Subject: [PATCH 09/61] chore: bump Node.js to 20 for Netlify --- netlify.toml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 netlify.toml diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 00000000..7c529ca7 --- /dev/null +++ b/netlify.toml @@ -0,0 +1,13 @@ +[build] + base = "frontend" + command = "npm run build" + publish = ".next" + +[functions] + node_bundler = "esbuild" + +[build.environment] + NODE_VERSION = "20.11.1" + +[[plugins]] + package = "@netlify/plugin-nextjs" \ No newline at end of file From b7c49a559f79352fd4f778b84e4ed7d048600acb Mon Sep 17 00:00:00 2001 From: Viktor Svertoka Date: Sat, 31 Jan 2026 17:12:11 +0200 Subject: [PATCH 10/61] feat(md) add netlify status (#234) --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 987c65f9..7e3238e8 100644 --- a/README.md +++ b/README.md @@ -1 +1,4 @@ ## DevLovers - a platform for technical interview preparation in frontend, backend, and full-stack development. + +develop +[![Netlify Status](https://api.netlify.com/api/v1/badges/0d21e84e-ea55-47f0-b841-c8eb91f2c9a0/deploy-status)](https://app.netlify.com/projects/develop-devlovers/deploys) From 4041589eb492526adb58d19e488a741827fcb8ac Mon Sep 17 00:00:00 2001 From: liudmylasovetovs Date: Sat, 31 Jan 2026 13:53:29 -0800 Subject: [PATCH 11/61] (SP 2) [Shop UI] Unify storefront styles across components and interactions --- .../app/[locale]/shop/admin/products/page.tsx | 131 ++++++------- frontend/app/[locale]/shop/cart/page.tsx | 173 ++++++++++-------- .../app/[locale]/shop/checkout/error/page.tsx | 132 ++++++++++--- .../checkout/payment/StripePaymentClient.tsx | 148 ++++++++------- .../shop/checkout/payment/[orderId]/page.tsx | 129 +++++++++---- .../[locale]/shop/checkout/success/page.tsx | 95 +++++++--- .../app/[locale]/shop/orders/[id]/page.tsx | 88 ++++++--- frontend/app/[locale]/shop/orders/error.tsx | 36 ++-- frontend/app/[locale]/shop/orders/page.tsx | 43 +++-- frontend/app/[locale]/shop/page.tsx | 62 ++++++- .../[locale]/shop/products/[slug]/page.tsx | 132 ++++++------- frontend/app/globals.css | 89 ++++++++- frontend/components/shared/Footer.tsx | 48 +++-- frontend/components/shared/HeaderButton.tsx | 44 ++++- .../components/shop/add-to-cart-button.tsx | 115 +++++++++--- .../admin/admin-product-delete-button.tsx | 4 +- .../admin/admin-product-status-toggle.tsx | 15 +- .../shop/admin/shop-admin-topbar.tsx | 4 +- .../components/shop/catalog-load-more.tsx | 16 +- frontend/components/shop/category-tile.tsx | 4 +- .../components/shop/header/cart-button.tsx | 25 +-- frontend/components/shop/product-card.tsx | 16 +- frontend/components/shop/product-filters.tsx | 57 ++++-- frontend/components/shop/product-sort.tsx | 62 ++++--- frontend/components/shop/shop-hero.tsx | 63 ++++++- frontend/components/theme/ThemeToggle.tsx | 2 +- frontend/lib/shop/ui-classes.ts | 118 ++++++++++++ 27 files changed, 1293 insertions(+), 558 deletions(-) create mode 100644 frontend/lib/shop/ui-classes.ts diff --git a/frontend/app/[locale]/shop/admin/products/page.tsx b/frontend/app/[locale]/shop/admin/products/page.tsx index ee1fa46e..5bfb4d09 100644 --- a/frontend/app/[locale]/shop/admin/products/page.tsx +++ b/frontend/app/[locale]/shop/admin/products/page.tsx @@ -90,6 +90,8 @@ export default async function AdminProductsPage({ const csrfTokenStatus = issueCsrfToken('admin:products:status'); const csrfTokenDelete = issueCsrfToken('admin:products:delete'); + const TH_BASE = + 'px-3 py-2 text-left text-xs font-semibold text-foreground leading-tight whitespace-normal break-words'; return ( <> @@ -109,7 +111,7 @@ export default async function AdminProductsPage({ {t('newProduct')} @@ -159,7 +161,9 @@ export default async function AdminProductsPage({
-
{t('table.category')}
+
+ {t('table.category')} +
-
{t('table.type')}
+
+ {t('table.type')} +
-
{t('table.stock')}
+
+ {t('table.stock')} +
{row.stock}
-
{t('table.badge')}
+
+ {t('table.badge')} +
{badge}
-
{t('table.active')}
+
+ {t('table.active')} +
{row.isActive ? t('actions.yes') : t('actions.no')}
-
{t('table.featured')}
+
+ {t('table.featured')} +
- {row.isFeatured ? t('actions.yes') : t('actions.no')} + {row.isFeatured + ? t('actions.yes') + : t('actions.no')}
-
{t('table.created')}
+
+ {t('table.created')} +
{formatDate(row.createdAt, locale)}
@@ -214,7 +232,9 @@ export default async function AdminProductsPage({ {t('actions.view')} @@ -222,7 +242,9 @@ export default async function AdminProductsPage({ {t('actions.edit')} @@ -253,73 +275,52 @@ export default async function AdminProductsPage({
- + + + + + + + + + + + + + - - - - - - - - - - - @@ -379,7 +380,9 @@ export default async function AdminProductsPage({ @@ -388,19 +391,23 @@ export default async function AdminProductsPage({ \r\n\r" - } - ] - } - ] - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Приклад:" - } - ] - }, - { - "type": "code", - "language": "html", - "content": "
{t('listCaption')}
+ {t('table.title')} + {t('table.slug')} + {t('table.price')} + {t('table.category')} + {t('table.type')} + {t('table.stock')} + {t('table.badge')} + {t('table.active')} + {t('table.featured')} + {t('table.created')} + {t('table.actions')}
- {row.isFeatured ? t('actions.yes') : t('actions.no')} + {row.isFeatured + ? t('actions.yes') + : t('actions.no')} -
+
{t('actions.view')} {t('actions.edit')} diff --git a/frontend/app/[locale]/shop/cart/page.tsx b/frontend/app/[locale]/shop/cart/page.tsx index 5f22926d..658fe356 100644 --- a/frontend/app/[locale]/shop/cart/page.tsx +++ b/frontend/app/[locale]/shop/cart/page.tsx @@ -3,15 +3,56 @@ import { useState } from 'react'; import Image from 'next/image'; import { useParams } from 'next/navigation'; -import { useRouter } from '@/i18n/routing'; +import { useRouter, Link } from '@/i18n/routing'; import { useTranslations } from 'next-intl'; - +import { cn } from '@/lib/utils'; import { Minus, Plus, Trash2, ShoppingBag } from 'lucide-react'; -import { Link } from '@/i18n/routing'; import { useCart } from '@/components/shop/cart-provider'; import { generateIdempotencyKey } from '@/lib/shop/idempotency'; import { formatMoney } from '@/lib/shop/currency'; +import { + SHOP_FOCUS, + SHOP_LINK_BASE, + SHOP_LINK_MD, + SHOP_LINK_XS, + SHOP_DISABLED, + SHOP_CHIP_INTERACTIVE, + SHOP_CHIP_SHADOW_HOVER, + SHOP_CHIP_BORDER_HOVER, + SHOP_STEPPER_BUTTON_BASE, + SHOP_CTA_BASE, + SHOP_CTA_INTERACTIVE, + SHOP_CTA_INSET, + SHOP_CTA_WAVE, + shopCtaGradient, +} from '@/lib/shop/ui-classes'; + +const SHOP_PRODUCT_LINK = cn( + 'block truncate', + SHOP_LINK_BASE, + SHOP_LINK_MD, + SHOP_FOCUS +); + +const SHOP_STEPPER_BTN = cn( + SHOP_STEPPER_BUTTON_BASE, + 'h-8 w-8', + SHOP_CHIP_INTERACTIVE, + SHOP_CHIP_SHADOW_HOVER, + SHOP_CHIP_BORDER_HOVER, + SHOP_FOCUS, + SHOP_DISABLED +); + +const SHOP_HERO_CTA = cn( + SHOP_CTA_BASE, + SHOP_CTA_INTERACTIVE, + SHOP_FOCUS, + SHOP_DISABLED, + 'w-full justify-center gap-2 px-6 py-3 text-sm text-white', + 'shadow-[var(--shop-hero-btn-shadow)] hover:shadow-[var(--shop-hero-btn-shadow-hover)]' +); export default function CartPage() { const { cart, updateQuantity, removeFromCart } = useCart(); @@ -32,7 +73,7 @@ export default function CartPage() { try { return tColors(colorSlug); } catch { - return color; + return color; } }; @@ -67,9 +108,8 @@ export default function CartPage() { typeof data?.message === 'string' ? data.message : typeof data?.error === 'string' - ? data.error - : 'Unable to start checkout right now.'; - + ? data.error + : 'Unable to start checkout right now.'; setCheckoutError(message); return; } @@ -86,7 +126,7 @@ export default function CartPage() { ? data.clientSecret : null; - const orderId: string = String(data.orderId); + const orderId = String(data.orderId); setCreatedOrderId(orderId); if (paymentProvider === 'stripe' && clientSecret) { @@ -95,7 +135,6 @@ export default function CartPage() { orderId )}?clientSecret=${encodeURIComponent(clientSecret)}&clearCart=1` ); - return; } @@ -120,22 +159,28 @@ export default function CartPage() { return (
-
); @@ -162,9 +207,7 @@ export default function CartPage() { {cart.items.map(item => (
  • @@ -183,7 +226,7 @@ export default function CartPage() {
    {item.title} @@ -200,11 +243,7 @@ export default function CartPage() { {item.quantity >= item.stock && ( - + {t('actions.maxStock', { stock: item.stock })} )}
    - {formatMoney( - item.lineTotalMinor, - item.currency, - locale - )} + {formatMoney(item.lineTotalMinor, item.currency, locale)}
  • @@ -278,14 +310,8 @@ export default function CartPage() { -
    @@ -273,7 +286,9 @@ export default async function MyOrdersPage({
    - {t('table.orderId')}: + + {t('table.orderId')}:{' '} + {shortOrderId(o.id)} diff --git a/frontend/app/[locale]/shop/page.tsx b/frontend/app/[locale]/shop/page.tsx index c715c1c2..8a1c4468 100644 --- a/frontend/app/[locale]/shop/page.tsx +++ b/frontend/app/[locale]/shop/page.tsx @@ -4,6 +4,13 @@ import { Hero } from '@/components/shop/shop-hero'; import { CategoryTile } from '@/components/shop/category-tile'; import { getHomepageContent } from '@/lib/shop/data'; import { getTranslations } from 'next-intl/server'; +import { + SHOP_CTA_BASE, + SHOP_CTA_INSET, + SHOP_CTA_WAVE, + SHOP_FOCUS, + shopCtaGradient, +} from '@/lib/shop/ui-classes'; export default async function HomePage({ params, @@ -38,10 +45,27 @@ export default async function HomePage({ - {t('viewAll')} + {t('viewAll')} + @@ -98,10 +122,40 @@ export default async function HomePage({
    - {t('hero.cta')} + {/* base gradient */} +
    diff --git a/frontend/app/[locale]/shop/products/[slug]/page.tsx b/frontend/app/[locale]/shop/products/[slug]/page.tsx index 6f8b7eab..1b7fe732 100644 --- a/frontend/app/[locale]/shop/products/[slug]/page.tsx +++ b/frontend/app/[locale]/shop/products/[slug]/page.tsx @@ -4,12 +4,13 @@ import Image from 'next/image'; import { notFound } from 'next/navigation'; import { ArrowLeft } from 'lucide-react'; import { getTranslations } from 'next-intl/server'; - +import { cn } from '@/lib/utils'; import { AddToCartButton } from '@/components/shop/add-to-cart-button'; import { getProductPageData } from '@/lib/shop/data'; import { formatMoney, resolveCurrencyFromLocale } from '@/lib/shop/currency'; import { getPublicProductBySlug } from '@/db/queries/shop/products'; import { Link } from '@/i18n/routing'; +import { SHOP_FOCUS, SHOP_NAV_LINK_BASE } from '@/lib/shop/ui-classes'; export const dynamic = 'force-dynamic'; @@ -37,70 +38,24 @@ export default async function ProductPage({ if (result.kind === 'not_found') { notFound(); } - - if (result.kind === 'unavailable') { - const p = result.product; - - return ( -
    - - -
    -
    - {p.badge && p.badge !== 'NONE' && ( - - {p.badge} - - )} - {p.name} -
    - -
    -

    - {p.name} -

    - -
    - {t('notAvailable')} -
    - - {p.description && ( -

    {p.description}

    - )} -
    -
    -
    - ); - } - - const product = result.product; - + const isUnavailable = result.kind === 'unavailable'; + const product = result.product as any; // shape differs for unavailable vs available; guard reads below + const NAV_LINK = cn(SHOP_NAV_LINK_BASE, SHOP_FOCUS, 'text-lg', 'items-center gap-2'); + const badge = product?.badge as string | undefined; + const badgeLabel = + badge && badge !== 'NONE' + ? (() => { + try { + return tProduct(`badges.${badge}`); + } catch { + return badge; + } + })() + : null; return (
    diff --git a/frontend/app/globals.css b/frontend/app/globals.css index 702d39a7..2743a7c1 100644 --- a/frontend/app/globals.css +++ b/frontend/app/globals.css @@ -342,7 +342,7 @@ .animate-spin-slow { animation: spin 20s linear infinite; } - + .animate-spin-slower { animation: spin 30s linear infinite reverse; } @@ -354,7 +354,8 @@ } @keyframes float { - 0%, 100% { + 0%, + 100% { transform: translateY(0); } 50% { @@ -375,6 +376,10 @@ .shop-scope { /* keep shop rounding slightly tighter than platform */ --radius: calc(var(--radius-base) - 2px); + + /* Shop header/button accent in LIGHT: no platform blue */ + --accent-primary: var(--foreground); + --accent-hover: color-mix(in oklab, var(--foreground) 92%, white); /* light: shop accent = black */ --accent: var(--foreground); @@ -390,6 +395,43 @@ /* CTA button: light theme (section is black) -> pink bg + white text */ --shop-cta-bg: #ff2d55; --shop-cta-fg: oklch(0.985 0 0); + + /* Hero CTA (top section) */ + --shop-hero-btn-bg: var(--foreground); + --shop-hero-btn-bg-hover: color-mix(in oklab, var(--foreground) 92%, white); + --shop-hero-btn-shadow: 0 18px 45px rgba(0, 0, 0, 0.35); + --shop-hero-btn-shadow-hover: 0 22px 60px rgba(0, 0, 0, 0.45); + + /* Card aura (product + category tiles) */ + --shop-card-shadow-hover: + 0 0 0 1px rgba(0, 0, 0, 0.22), 0 18px 45px rgba(0, 0, 0, 0.12); + + /* Bottom CTA (inverted by theme) */ + --shop-cta-shadow: 0 22px 60px rgba(255, 45, 85, 0.45); + --shop-cta-shadow-hover: 0 28px 80px rgba(255, 45, 85, 0.6); + --shop-cta-bg-hover: #e0264b; + + /* Filters / chips (stronger than card aura) */ + --shop-chip-shadow-hover: + 0 0 0 1px rgba(0, 0, 0, 0.32), 0 0 0 5px rgba(0, 0, 0, 0.08), + 0 18px 45px rgba(0, 0, 0, 0.22); + + --shop-chip-shadow-selected: + 0 0 0 2px rgba(0, 0, 0, 0.4), 0 0 0 7px rgba(0, 0, 0, 0.1), + 0 22px 60px rgba(0, 0, 0, 0.28); + + --shop-hero-btn-success-bg: color-mix( + in oklab, + var(--shop-hero-btn-bg) 88%, + white + ); + --shop-hero-btn-success-bg-hover: color-mix( + in oklab, + var(--shop-hero-btn-bg) 80%, + white + ); + --shop-hero-btn-success-shadow: 0 22px 60px rgba(0, 0, 0, 0.25); + --shop-hero-btn-success-shadow-hover: 0 28px 80px rgba(0, 0, 0, 0.32); } .dark .shop-scope { @@ -402,15 +444,56 @@ --color-accent: var(--accent-primary); --color-accent-foreground: var(--foreground); --color-ring: var(--accent-primary); - --card: var(--background); + /* Shop accent in DARK: keep magenta */ + --accent-primary: #ff2d55; + --accent-hover: #e0264b; + /* keep borders closer to previous shop look, derived (no hex) */ --border: color-mix(in oklab, var(--foreground) 18%, var(--background)); --input: color-mix(in oklab, var(--foreground) 18%, var(--background)); + /* CTA button: dark theme (section becomes white) -> black bg + white text */ --shop-cta-bg: var(--background); --shop-cta-fg: var(--foreground); + + /* Hero CTA (top section) */ + --shop-hero-btn-bg: var(--accent-primary); + --shop-hero-btn-bg-hover: var(--accent-hover); + --shop-hero-btn-shadow: 0 22px 60px rgba(255, 45, 85, 0.45); + --shop-hero-btn-shadow-hover: 0 28px 80px rgba(255, 45, 85, 0.6); + + /* Card aura (product + category tiles) */ + --shop-card-shadow-hover: + 0 0 0 1px rgba(255, 45, 85, 0.28), 0 18px 45px rgba(255, 45, 85, 0.16); + + /* Bottom CTA (inverted by theme) */ + --shop-cta-shadow: 0 18px 45px rgba(0, 0, 0, 0.3); + --shop-cta-shadow-hover: 0 22px 60px rgba(0, 0, 0, 0.45); + --shop-cta-bg-hover: color-mix(in oklab, var(--background) 92%, white); + + /* Filters / chips (stronger than card aura) */ + --shop-chip-shadow-hover: + 0 0 0 1px rgba(255, 45, 85, 0.55), 0 0 0 5px rgba(255, 45, 85, 0.18), + 0 18px 50px rgba(255, 45, 85, 0.32); + + --shop-chip-shadow-selected: + 0 0 0 2px rgba(255, 45, 85, 0.7), 0 0 0 7px rgba(255, 45, 85, 0.22), + 0 22px 70px rgba(255, 45, 85, 0.38); + + --shop-hero-btn-success-bg: color-mix( + in oklab, + var(--accent-primary) 82%, + black + ); + --shop-hero-btn-success-bg-hover: color-mix( + in oklab, + var(--accent-primary) 72%, + black + ); + --shop-hero-btn-success-shadow: 0 22px 60px rgba(255, 45, 85, 0.45); + --shop-hero-btn-success-shadow-hover: 0 28px 80px rgba(255, 45, 85, 0.6); } /* about-community */ diff --git a/frontend/components/shared/Footer.tsx b/frontend/components/shared/Footer.tsx index a355efa4..d195df3e 100644 --- a/frontend/components/shared/Footer.tsx +++ b/frontend/components/shared/Footer.tsx @@ -5,6 +5,8 @@ import { Link } from '@/i18n/routing'; import { Github, Linkedin, Send } from 'lucide-react'; import { ThemeToggle } from '@/components/theme/ThemeToggle'; import { useTranslations } from 'next-intl'; +import { useSelectedLayoutSegments } from 'next/navigation'; +import { cn } from '@/lib/utils'; const SOCIAL = [ { label: 'GitHub', href: 'https://github.com/DevLoversTeam', Icon: Github }, @@ -18,18 +20,24 @@ const SOCIAL = [ export default function Footer() { const t = useTranslations('footer'); - + const segments = useSelectedLayoutSegments(); + const isShop = segments.includes('shop'); return ( -
    +

    {t('builtWith')}{' '} - + DevLovers {' '} {t('byCommunity')} @@ -39,10 +47,10 @@ export default function Footer() { {t('privacyPolicy')} @@ -51,10 +59,10 @@ focus-visible:[color:var(--accent-hover)] {t('termsOfService')} @@ -91,13 +99,13 @@ focus-visible:[color:var(--accent-hover)] dark:text-slate-300 transition-all -hover:-translate-y-0.5 -hover:!text-[var(--accent-hover)] -hover:!border-[var(--accent-hover)] + hover:-translate-y-0.5 + hover:!text-[var(--footer-hover)] + hover:!border-[var(--footer-hover)] -active:!text-[var(--accent-hover)] -active:!border-[var(--accent-hover)] -active:scale-95 + active:!text-[var(--footer-hover)] + active:!border-[var(--footer-hover)] + active:scale-95 " diff --git a/frontend/components/shared/HeaderButton.tsx b/frontend/components/shared/HeaderButton.tsx index 8bf54ce5..c9bd8de1 100644 --- a/frontend/components/shared/HeaderButton.tsx +++ b/frontend/components/shared/HeaderButton.tsx @@ -15,6 +15,14 @@ interface HeaderButtonProps { label?: string; + /** + * Optional badge rendered OUTSIDE the overflow-hidden button/link, + * so it will not be clipped by the shine/gradient container. + */ + badge?: React.ReactNode; + badgeClassName?: string; + badgeAriaLabel?: string; + className?: string; } @@ -26,6 +34,9 @@ export function HeaderButton({ variant = 'default', showArrow = false, label, + badge, + badgeClassName = '', + badgeAriaLabel, className = '', }: HeaderButtonProps) { const isIconOnly = variant === 'icon'; @@ -120,8 +131,37 @@ export function HeaderButton({ ${className} `; - if (!href) { + const wrapWithBadge = (node: React.ReactNode) => { + if (!badge) return node; + + const defaultBadgeClasses = ` + pointer-events-none + absolute -right-1 -top-1 + flex h-5 min-w-5 items-center justify-center + rounded-full px-1 + text-[11px] font-semibold leading-none tabular-nums + ring-2 ring-background + `; + + const ariaProps = badgeAriaLabel + ? { 'aria-label': badgeAriaLabel } + : { 'aria-hidden': true as const }; + return ( + + {node} + + {badge} + + + ); + }; + + if (!href) { + return wrapWithBadge( ); diff --git a/frontend/components/shop/admin/admin-product-delete-button.tsx b/frontend/components/shop/admin/admin-product-delete-button.tsx index 065b6b11..5ee73c69 100644 --- a/frontend/components/shop/admin/admin-product-delete-button.tsx +++ b/frontend/components/shop/admin/admin-product-delete-button.tsx @@ -85,14 +85,14 @@ export function AdminProductDeleteButton({ }; return ( -

    +
    diff --git a/frontend/components/shop/admin/admin-product-status-toggle.tsx b/frontend/components/shop/admin/admin-product-status-toggle.tsx index 62899c1c..48ab1edf 100644 --- a/frontend/components/shop/admin/admin-product-status-toggle.tsx +++ b/frontend/components/shop/admin/admin-product-status-toggle.tsx @@ -48,7 +48,10 @@ export function AdminProductStatusToggle({ // ignore } - if (response.status === 403 && (code === 'CSRF_MISSING' || code === 'CSRF_INVALID')) { + if ( + response.status === 403 && + (code === 'CSRF_MISSING' || code === 'CSRF_INVALID') + ) { setError(t('securityExpired')); return; } @@ -68,10 +71,14 @@ export function AdminProductStatusToggle({ } }; - const buttonLabel = isLoading ? t('updating') : isActive ? t('deactivate') : t('activate'); + const buttonLabel = isLoading + ? t('updating') + : isActive + ? t('deactivate') + : t('activate'); return ( -
    +
    diff --git a/frontend/components/shop/admin/shop-admin-topbar.tsx b/frontend/components/shop/admin/shop-admin-topbar.tsx index 21ca8c50..2b04f168 100644 --- a/frontend/components/shop/admin/shop-admin-topbar.tsx +++ b/frontend/components/shop/admin/shop-admin-topbar.tsx @@ -11,7 +11,7 @@ export async function ShopAdminTopbar() { aria-label={t('label')} className="flex flex-wrap items-center justify-between gap-3 py-3" > -
      +
      -
      +
      {isLoading ? tCommon('loading') : t('loadMore')} diff --git a/frontend/components/shop/category-tile.tsx b/frontend/components/shop/category-tile.tsx index 86028f62..3047f33b 100644 --- a/frontend/components/shop/category-tile.tsx +++ b/frontend/components/shop/category-tile.tsx @@ -20,10 +20,10 @@ export async function CategoryTile({ category }: CategoryTileProps) { href={href} aria-label={tCategoryTile('shopCategory', { name: categoryName })} className={[ - // IMPORTANT: make it block-level so aspect-ratio + Image fill work correctly 'group relative block w-full', 'aspect-[4/3] overflow-hidden rounded-lg bg-muted', - 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/30 focus-visible:ring-offset-2 focus-visible:ring-offset-background', + 'transition-[box-shadow] duration-500 hover:shadow-[var(--shop-card-shadow-hover)]', + 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--color-ring)] focus-visible:ring-offset-2 focus-visible:ring-offset-background', ].join(' ')} > 0; + const badgeText = itemCount > 99 ? '99+' : itemCount; + return ( - -
      ); diff --git a/frontend/components/shop/product-filters.tsx b/frontend/components/shop/product-filters.tsx index e8734097..0d8226f2 100644 --- a/frontend/components/shop/product-filters.tsx +++ b/frontend/components/shop/product-filters.tsx @@ -3,7 +3,16 @@ import { useId } from 'react'; import { usePathname, useRouter, useSearchParams } from 'next/navigation'; import { useTranslations } from 'next-intl'; - +import { + SHOP_FOCUS, + SHOP_CHIP_INTERACTIVE, + SHOP_CHIP_HOVER, + SHOP_CHIP_SELECTED, + SHOP_SWATCH_BASE, + SHOP_SIZE_CHIP_BASE, + SHOP_FILTER_ITEM_BASE, + SHOP_CHIP_BORDER_HOVER, +} from '@/lib/shop/ui-classes'; import { CATEGORIES, COLORS, PRODUCT_TYPES, SIZES } from '@/lib/config/catalog'; import { cn } from '@/lib/utils'; @@ -55,13 +64,20 @@ export function ProductFilters() { onClick={() => updateFilter('category', cat.slug)} aria-current={currentCategory === cat.slug ? 'true' : undefined} className={cn( - 'text-sm transition-colors', + SHOP_FILTER_ITEM_BASE, + SHOP_FOCUS, currentCategory === cat.slug - ? 'font-medium text-accent' - : 'text-muted-foreground hover:text-foreground' + ? 'text-accent' + : 'text-muted-foreground hover:text-accent' )} > - {tCategories(cat.slug === 'new-arrivals' ? 'newArrivals' : cat.slug === 'best-sellers' ? 'bestSellers' : cat.slug)} + {tCategories( + cat.slug === 'new-arrivals' + ? 'newArrivals' + : cat.slug === 'best-sellers' + ? 'bestSellers' + : cat.slug + )} ))} @@ -90,10 +106,11 @@ export function ProductFilters() { } aria-pressed={isSelected} className={cn( - 'text-sm transition-colors', + SHOP_FILTER_ITEM_BASE, + SHOP_FOCUS, isSelected - ? 'font-medium text-accent' - : 'text-muted-foreground hover:text-foreground' + ? 'text-accent' + : 'text-muted-foreground hover:text-accent' )} > {tTypes(type.slug)} @@ -127,10 +144,14 @@ export function ProductFilters() { } aria-pressed={isSelected} className={cn( - 'h-7 w-7 rounded-full border-2 transition-all', + SHOP_SWATCH_BASE, + // у фільтрах розмір 8x8, тому override: + 'h-8 w-8', + SHOP_CHIP_INTERACTIVE, + SHOP_FOCUS, isSelected - ? 'border-accent ring-2 ring-accent ring-offset-2 ring-offset-background' - : 'border-border hover:border-muted-foreground' + ? SHOP_CHIP_SELECTED + : cn(SHOP_CHIP_HOVER, SHOP_CHIP_BORDER_HOVER) )} style={{ background: color.hex }} title={colorLabel} @@ -161,10 +182,18 @@ export function ProductFilters() { onClick={() => updateFilter('size', isSelected ? null : size)} aria-pressed={isSelected} className={cn( - 'rounded-md border px-3 py-1.5 text-sm transition-colors', + SHOP_SIZE_CHIP_BASE, + // у фільтрах інші паддінги — override: + 'px-3 py-1.5 text-sm', + SHOP_CHIP_INTERACTIVE, + SHOP_FOCUS, isSelected - ? 'border-accent bg-accent text-accent-foreground' - : 'border-border text-muted-foreground hover:border-foreground hover:text-foreground' + ? cn('bg-accent text-accent-foreground', SHOP_CHIP_SELECTED) + : cn( + 'bg-transparent text-muted-foreground border-border hover:text-foreground', + SHOP_CHIP_HOVER, + SHOP_CHIP_BORDER_HOVER + ) )} > {size} diff --git a/frontend/components/shop/product-sort.tsx b/frontend/components/shop/product-sort.tsx index 5640c4aa..8aae175c 100644 --- a/frontend/components/shop/product-sort.tsx +++ b/frontend/components/shop/product-sort.tsx @@ -6,9 +6,15 @@ import { useId } from 'react'; import { useRouter } from '@/i18n/routing'; import { useSearchParams } from 'next/navigation'; import { useTranslations } from 'next-intl'; - +import { ChevronDown } from 'lucide-react'; import { SORT_OPTIONS } from '@/lib/config/catalog'; import { cn } from '@/lib/utils'; +import { + SHOP_DISABLED, + SHOP_FOCUS, + SHOP_SELECT_BASE, + SHOP_SELECT_INTERACTIVE, +} from '@/lib/shop/ui-classes'; type ProductSortProps = { className?: string; @@ -30,10 +36,10 @@ export function ProductSort({ className }: ProductSortProps) { const getOptionLabel = (value: string) => { const keyMap: Record = { - 'featured': 'featured', + featured: 'featured', 'price-asc': 'priceAsc', 'price-desc': 'priceDesc', - 'newest': 'newest', + newest: 'newest', }; return tOptions(keyMap[value] || 'featured'); }; @@ -64,26 +70,36 @@ export function ProductSort({ className }: ProductSortProps) { {t('sortBy')} - +
      + + +
      ); } diff --git a/frontend/components/shop/shop-hero.tsx b/frontend/components/shop/shop-hero.tsx index 6eebe123..c76cba58 100644 --- a/frontend/components/shop/shop-hero.tsx +++ b/frontend/components/shop/shop-hero.tsx @@ -5,6 +5,13 @@ interface HeroProps { ctaText: string; ctaLink: string; } +import { + SHOP_CTA_BASE, + SHOP_CTA_INSET, + SHOP_CTA_WAVE, + SHOP_FOCUS, + shopCtaGradient, +} from '@/lib/shop/ui-classes'; export function Hero({ headline, subheadline, ctaText, ctaLink }: HeroProps) { return ( @@ -16,22 +23,66 @@ export function Hero({ headline, subheadline, ctaText, ctaLink }: HeroProps) {

      - {headline} + + {headline} + + +

      -

      +

      {subheadline}

      - {ctaText} - + {/* base gradient */} +
      diff --git a/frontend/components/theme/ThemeToggle.tsx b/frontend/components/theme/ThemeToggle.tsx index 874d69fa..515f96d4 100644 --- a/frontend/components/theme/ThemeToggle.tsx +++ b/frontend/components/theme/ThemeToggle.tsx @@ -58,7 +58,7 @@ export function ThemeToggle() { .theme-toggle-btn:hover :global(svg), .theme-toggle-btn:focus-visible :global(svg), .theme-toggle-btn:active :global(svg) { - color: var(--accent-hover); + color: var(--theme-toggle-hover, var(--accent-hover)); } `}
      diff --git a/frontend/lib/shop/ui-classes.ts b/frontend/lib/shop/ui-classes.ts new file mode 100644 index 00000000..22c6c737 --- /dev/null +++ b/frontend/lib/shop/ui-classes.ts @@ -0,0 +1,118 @@ +export const SHOP_FOCUS = ` + focus-visible:outline-none + focus-visible:ring-2 + focus-visible:ring-[color:var(--color-ring)] + focus-visible:ring-offset-2 + focus-visible:ring-offset-background +`; + +export const SHOP_CTA_BASE = ` + group relative inline-flex items-center overflow-hidden rounded-2xl + text-xs md:text-sm font-semibold tracking-[0.25em] uppercase + active:scale-95 active:brightness-110 + transition-shadow duration-700 ease-out +`; + +export const SHOP_CTA_WAVE = ` + absolute inset-0 opacity-0 + group-hover:opacity-100 group-active:opacity-100 + group-hover:animate-wave-slide-up +`; + +export const SHOP_CTA_INSET = ` + pointer-events-none absolute inset-[2px] rounded-2xl + bg-gradient-to-r from-white/20 via-white/5 to-white/20 + opacity-40 supports-[hover:hover]:group-hover:opacity-60 transition-opacity +`; + +// Shared interaction for “chips” (swatches, size pills, +/- stepper) +export const SHOP_CHIP_INTERACTIVE = + 'transition-[box-shadow,border-color,color,background-color,filter] duration-500 ease-out hover:brightness-110'; + +// Optional “lift” (НЕ використовуй для size/+/- якщо хочеш без тремтіння) +export const SHOP_CHIP_LIFT = + 'transition-transform duration-500 ease-out hover:-translate-y-0.5'; + +export const SHOP_CHIP_HOVER = + 'hover:shadow-[var(--shop-chip-shadow-hover)] hover:border-accent/60'; + +export const SHOP_CHIP_SELECTED = + 'border-accent ring-2 ring-accent ring-offset-2 ring-offset-background shadow-[var(--shop-chip-shadow-selected)]'; + +// Optional: base shapes (so you don’t repeat layout primitives) +export const SHOP_SWATCH_BASE = + 'group relative h-9 w-9 rounded-full border border-border shadow-none'; + +export const SHOP_SIZE_CHIP_BASE = + 'group rounded-md border px-4 py-2 text-sm font-medium'; + +export const SHOP_STEPPER_BUTTON_BASE = + 'flex h-10 w-10 items-center justify-center rounded-md border border-border text-foreground bg-transparent'; + +/** + * Builds a horizontal gradient background from two CSS vars. + * Example: shopCtaGradient('--shop-cta-bg', '--shop-cta-bg-hover') + */ + +// Text-link-ish filter items (category/type lists) +export const SHOP_FILTER_ITEM_BASE = + 'inline-flex text-sm font-medium transition-[color,transform] duration-300 ease-out hover:-translate-y-[1px]'; + +// If you want the “chip hover border” to be consistent (used in filters + size) +export const SHOP_CHIP_BORDER_HOVER = 'hover:border-foreground/60'; + +export const SHOP_DISABLED = 'disabled:pointer-events-none disabled:opacity-60'; + +export const SHOP_CHIP_SHADOW_HOVER = + 'hover:shadow-[var(--shop-chip-shadow-hover)]'; + +/** + * Reusable “text link” for product names / order links / “go to order”, etc. + * Size додаєш окремо (text-xs, text-[15px], …). + */ +export const SHOP_LINK_BASE = + 'inline-flex font-medium text-foreground underline underline-offset-4 decoration-2 decoration-foreground/30 ' + + 'transition-[color,transform,text-decoration-color] duration-300 ease-out ' + + 'hover:-translate-y-[1px] hover:text-accent hover:decoration-[color:var(--accent-primary)]'; + +export const SHOP_LINK_MD = 'text-[15px]'; +export const SHOP_LINK_XS = 'text-xs'; + +/** + * CTA interaction (додатково до SHOP_CTA_BASE). + * SHOP_CTA_BASE залишаємо як layout+typography+active. + */ +export const SHOP_CTA_INTERACTIVE = + 'transition-[transform,filter,box-shadow] duration-700 ease-out'; + +// Outline button (inverted to CTA; used in error pages, secondary actions) +export const SHOP_OUTLINE_BTN_BASE = + 'inline-flex items-center justify-center rounded-xl border px-4 py-2 ' + + 'text-sm font-semibold uppercase tracking-[0.25em] ' + + 'border-border text-foreground bg-transparent'; + +export const SHOP_OUTLINE_BTN_INTERACTIVE = + 'transition-[transform,box-shadow,border-color,color,background-color,filter] duration-500 ease-out ' + + 'hover:-translate-y-[1px] hover:shadow-[var(--shop-chip-shadow-hover)] hover:brightness-110 ' + + 'hover:border-[color:var(--accent-primary)] hover:text-[color:var(--accent-primary)]'; + +// Nav/breadcrumb-ish links (e.g. "My orders", "Shop", "Back to ...") +export const SHOP_NAV_LINK_BASE = + 'inline-flex font-medium underline underline-offset-4 decoration-2 ' + + 'text-muted-foreground decoration-foreground/30 ' + + 'transition-[color,transform,text-decoration-color] duration-300 ease-out ' + + 'hover:-translate-y-[1px] hover:text-accent hover:decoration-[color:var(--accent-primary)]'; + +// Select / dropdown (e.g. sort) +export const SHOP_SELECT_BASE = + 'peer h-10 w-full appearance-none rounded-xl border border-border bg-background pl-3 pr-11 text-sm font-medium'; + +export const SHOP_SELECT_INTERACTIVE = + 'transition-[transform,box-shadow,border-color,color,background-color,filter] duration-500 ease-out ' + + 'hover:-translate-y-[1px] hover:shadow-[var(--shop-chip-shadow-hover)] hover:border-foreground/40 hover:brightness-110'; + +export function shopCtaGradient(baseVar: string, hoverVar: string) { + return { + background: `linear-gradient(90deg, var(${baseVar}) 0%, var(${hoverVar}) 100%)`, + } as const; +} From 124bc0472b9bcaea46493c17f022ca10dab2ea4b Mon Sep 17 00:00:00 2001 From: liudmylasovetovs <127711697+liudmylasovetovs@users.noreply.github.com> Date: Sat, 31 Jan 2026 14:01:12 -0800 Subject: [PATCH 12/61] (SP 2) [Shop UI] Unify storefront styles across components and interactions (#236) --- .../app/[locale]/shop/admin/products/page.tsx | 131 ++++++------- frontend/app/[locale]/shop/cart/page.tsx | 173 ++++++++++-------- .../app/[locale]/shop/checkout/error/page.tsx | 132 ++++++++++--- .../checkout/payment/StripePaymentClient.tsx | 148 ++++++++------- .../shop/checkout/payment/[orderId]/page.tsx | 129 +++++++++---- .../[locale]/shop/checkout/success/page.tsx | 95 +++++++--- .../app/[locale]/shop/orders/[id]/page.tsx | 88 ++++++--- frontend/app/[locale]/shop/orders/error.tsx | 36 ++-- frontend/app/[locale]/shop/orders/page.tsx | 43 +++-- frontend/app/[locale]/shop/page.tsx | 62 ++++++- .../[locale]/shop/products/[slug]/page.tsx | 132 ++++++------- frontend/app/globals.css | 89 ++++++++- frontend/components/shared/Footer.tsx | 48 +++-- frontend/components/shared/HeaderButton.tsx | 44 ++++- .../components/shop/add-to-cart-button.tsx | 115 +++++++++--- .../admin/admin-product-delete-button.tsx | 4 +- .../admin/admin-product-status-toggle.tsx | 15 +- .../shop/admin/shop-admin-topbar.tsx | 4 +- .../components/shop/catalog-load-more.tsx | 16 +- frontend/components/shop/category-tile.tsx | 4 +- .../components/shop/header/cart-button.tsx | 25 +-- frontend/components/shop/product-card.tsx | 16 +- frontend/components/shop/product-filters.tsx | 57 ++++-- frontend/components/shop/product-sort.tsx | 62 ++++--- frontend/components/shop/shop-hero.tsx | 63 ++++++- frontend/components/theme/ThemeToggle.tsx | 2 +- frontend/lib/shop/ui-classes.ts | 118 ++++++++++++ 27 files changed, 1293 insertions(+), 558 deletions(-) create mode 100644 frontend/lib/shop/ui-classes.ts diff --git a/frontend/app/[locale]/shop/admin/products/page.tsx b/frontend/app/[locale]/shop/admin/products/page.tsx index ee1fa46e..5bfb4d09 100644 --- a/frontend/app/[locale]/shop/admin/products/page.tsx +++ b/frontend/app/[locale]/shop/admin/products/page.tsx @@ -90,6 +90,8 @@ export default async function AdminProductsPage({ const csrfTokenStatus = issueCsrfToken('admin:products:status'); const csrfTokenDelete = issueCsrfToken('admin:products:delete'); + const TH_BASE = + 'px-3 py-2 text-left text-xs font-semibold text-foreground leading-tight whitespace-normal break-words'; return ( <> @@ -109,7 +111,7 @@ export default async function AdminProductsPage({ {t('newProduct')} @@ -159,7 +161,9 @@ export default async function AdminProductsPage({
      -
      {t('table.category')}
      +
      + {t('table.category')} +
      -
      {t('table.type')}
      +
      + {t('table.type')} +
      -
      {t('table.stock')}
      +
      + {t('table.stock')} +
      {row.stock}
      -
      {t('table.badge')}
      +
      + {t('table.badge')} +
      {badge}
      -
      {t('table.active')}
      +
      + {t('table.active')} +
      {row.isActive ? t('actions.yes') : t('actions.no')}
      -
      {t('table.featured')}
      +
      + {t('table.featured')} +
      - {row.isFeatured ? t('actions.yes') : t('actions.no')} + {row.isFeatured + ? t('actions.yes') + : t('actions.no')}
      -
      {t('table.created')}
      +
      + {t('table.created')} +
      {formatDate(row.createdAt, locale)}
      @@ -214,7 +232,9 @@ export default async function AdminProductsPage({ {t('actions.view')} @@ -222,7 +242,9 @@ export default async function AdminProductsPage({ {t('actions.edit')} @@ -253,73 +275,52 @@ export default async function AdminProductsPage({
      - + + + + + + + + + + + + + - - - - - - - - - - - @@ -379,7 +380,9 @@ export default async function AdminProductsPage({ @@ -388,19 +391,23 @@ export default async function AdminProductsPage({ \r\n \r\n \r\n\r" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Стан елементів:", - "bold": true - }, - { - "type": "code", - "language": "html", - "content": "
      \r\n \r\n
      \r" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "CSS стилізація за станом:", - "bold": true - }, - { - "type": "code", - "language": "css", - "content": "[data-status='active'] {\r\n color: green;\r\n}\r\n[data-status='inactive'] {\r\n color: gray;\r\n}\r" - } - ] - } - ] - }, - { - "type": "paragraph", - "children": [ - { - "text": "Переваги:", - "bold": true - }, - { - "text": " Валідний HTML, легкий доступ через JavaScript, не впливає на стилізацію." - } - ] - } - ] - }, - { - "question": "26. Як створити гіперпосилання в HTML?", - "category": "html", - "answerBlocks": [ - { - "type": "paragraph", - "children": [ - { - "text": "Використовуємо тег " - }, - { - "text": "", - "code": true - }, - { - "text": ":" - } - ] - }, - { - "type": "code", - "language": "html", - "content": "Текст посилання\r" - }, - { - "type": "bulletList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "href", - "code": true - }, - { - "text": " — адреса, куди веде посилання." - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Можна додати " - }, - { - "text": "target=\"_blank\"", - "code": true - }, - { - "text": " для відкриття в новій вкладці." - } - ] - } - ] - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Різні типи посилань:" - } - ] - }, - { - "type": "code", - "language": "html", - "content": "\r\nGoogle\r\n\r\n\r\nПро нас\r\n\r\n\r\nНаписати листа\r\n\r\n\r\nЗателефонувати\r\n\r\n\r\nЗавантажити PDF\r" - } - ] - }, - { - "question": "27. Що таке внутрішні та зовнішні гіперпосилання і які вони мають атрибути?", - "category": "html", - "answerBlocks": [ - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Зовнішні посилання:" - } - ] - }, - { - "type": "bulletList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "Ведуть на інші веб-сайти" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Починаються з " - }, - { - "text": "http://", - "code": true - }, - { - "text": " або " - }, - { - "text": "https://", - "code": true - } - ] - } - ] - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Внутрішні посилання:" - } - ] - }, - { - "type": "bulletList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "Ведуть на сторінки того самого сайту" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Використовують відносні або абсолютні шляхи" - } - ] - } - ] - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Основні атрибути:" - } - ] - }, - { - "type": "code", - "language": "html", - "content": "\r\n\r\n Зовнішній сайт\r\n\r\n\r\n\r\n Контакти \r\n\r\n\r\n Розділ 1 \r" - }, - { - "type": "paragraph", - "children": [ - { - "text": "Атрибути:", - "bold": true - } - ] - }, - { - "type": "bulletList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "href", - "code": true - }, - { - "text": " — URL адреса" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "target", - "code": true - }, - { - "text": " — де відкрити (\\_blank, \\_self, \\_parent, \\_top)" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "rel", - "code": true - }, - { - "text": " — відношення до цільової сторінки" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "title", - "code": true - }, - { - "text": " — підказка при наведенні" - } - ] - } - ] - } - ] - }, - { - "question": "28. Яка різниця між відносними та абсолютними URL-адресами?", - "category": "html", - "answerBlocks": [ - { - "type": "paragraph", - "children": [ - { - "text": "Абсолютний URL", - "bold": true - }, - { - "text": " — містить повний шлях із протоколом і доменом." - } - ] - }, - { - "type": "code", - "language": "html", - "content": "Посилання\r" - }, - { - "type": "paragraph", - "children": [ - { - "text": "Використовується для переходів на зовнішні ресурси або між доменами." - } - ] - }, - { - "type": "paragraph", - "children": [ - { - "text": "Відносний URL", - "bold": true - }, - { - "text": " — вказує шлях відносно поточного документа." - } - ] - }, - { - "type": "code", - "language": "html", - "content": "Посилання\r\nПосилання\r\nПосилання\r" - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Типи відносних URL:" - } - ] - }, - { - "type": "code", - "language": "html", - "content": "\r\nКоманда\r\n\r\n\r\nКонтакти\r\n\r\n\r\nГоловна\r\n\r\n\r\nІнструкція\r" - }, - { - "type": "paragraph", - "children": [ - { - "text": "Зручно для внутрішніх сторінок сайту, особливо при розробці й тестуванні." - } - ] - } - ] - }, - { - "question": "29. Як відкрити посилання в новій вкладці?", - "category": "html", - "answerBlocks": [ - { - "type": "bulletList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "Додати атрибут " - }, - { - "text": "target=\"_blank\"", - "code": true - }, - { - "text": " до тега " - }, - { - "text": "", - "code": true - }, - { - "text": ":" - }, - { - "type": "code", - "language": "html", - "content": "Відкрити в новій вкладці\r" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Рекомендовано також додати " - }, - { - "text": "rel=\"noopener noreferrer\"", - "code": true - }, - { - "text": " для безпеки:" - }, - { - "type": "code", - "language": "html", - "content": "\r\n Відкрити в новій вкладці\r\n\r" - } - ] - } - ] - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Інші значення target:" - } - ] - }, - { - "type": "code", - "language": "html", - "content": "\r\nПоточна вкладка\r\n\r\n\r\nБатьківський фрейм\r\n\r\n\r\nВерхній рівень\r\n\r\n\r\nКонкретне вікно\r" - }, - { - "type": "paragraph", - "children": [ - { - "text": "Безпека:", - "bold": true - }, - { - "text": " " - }, - { - "text": "rel=\"noopener noreferrer\"", - "code": true - }, - { - "text": " запобігає доступу нової сторінки до поточної через " - }, - { - "text": "window.opener", - "code": true - }, - { - "text": "." - } - ] - } - ] - }, - { - "question": "30. Як створити якір для переходу до певної частини сторінки?", - "category": "html", - "answerBlocks": [ - { - "type": "bulletList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "Додаємо ідентифікатор елементу:" - }, - { - "type": "code", - "language": "html", - "content": "

      Контакти

      \r" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Створюємо посилання на цей id:" - }, - { - "type": "code", - "language": "html", - "content": "Перейти до контактів\r" - } - ] - } - ] - }, - { - "type": "paragraph", - "children": [ - { - "text": "При кліку сторінка прокрутиться до елемента з таким id." - } - ] - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Повний приклад:" - } - ] - }, - { - "type": "code", - "language": "html", - "content": "\r\n\r\n \r\n Якірні посилання\r\n \r\n \r\n \r\n \r\n\r\n \r\n
      \r\n

      Про нас

      \r\n

      Інформація про компанію...

      \r\n
      \r\n\r\n
      \r\n

      Послуги

      \r\n

      Опис наших послуг...

      \r\n
      \r\n\r\n
      \r\n

      Контакти

      \r\n

      Контактна інформація...

      \r\n
      \r\n\r\n \r\n ↑ На верх\r\n \r\n\r" - }, - { - "type": "paragraph", - "children": [ - { - "text": "CSS для плавної прокрутки:", - "bold": true - } - ] - }, - { - "type": "code", - "language": "css", - "content": "html {\r\n scroll-behavior: smooth;\r\n}\r" - } - ] - }, - { - "question": "31. Як створити посилання на різні розділи на одній веб-сторінці HTML?", - "category": "html", - "answerBlocks": [ - { - "type": "paragraph", - "children": [ - { - "text": "Відповідь: Для створення гіперпосилання використовується тег " - }, - { - "text": "", - "code": true - }, - { - "text": ". Атрибут href вказує URL або шлях." - } - ] - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Повний приклад:" - } - ] - }, - { - "type": "code", - "language": "html", - "content": "\r\n\r\n \r\n Якірні посилання\r\n \r\n \r\n \r\n \r\n\r\n \r\n
      \r\n

      Про нас

      \r\n

      Інформація про компанію...

      \r\n
      \r\n\r\n
      \r\n

      Послуги

      \r\n

      Опис наших послуг...

      \r\n
      \r\n\r\n
      \r\n

      Контакти

      \r\n

      Контактна інформація...

      \r\n
      \r\n\r\n \r\n ↑ На верх\r\n \r\n\r" - }, - { - "type": "paragraph", - "children": [ - { - "text": "CSS для плавної прокрутки:", - "bold": true - } - ] - }, - { - "type": "code", - "language": "css", - "content": "html {\r\n scroll-behavior: smooth;\r\n}\r" - } - ] - }, - { - "question": "32. Як створити посилання на файл для завантаження в HTML?", - "category": "html", - "answerBlocks": [ - { - "type": "paragraph", - "children": [ - { - "text": "Використати " - }, - { - "text": "", - "code": true - }, - { - "text": " з атрибутом " - }, - { - "text": "download", - "code": true - }, - { - "text": ":" - } - ] - }, - { - "type": "code", - "language": "html", - "content": "Завантажити інструкцію\r" - }, - { - "type": "bulletList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "href — шлях до файлу." - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "download — каже браузеру зберегти файл замість відкриття." - } - ] - } - ] - }, - { - "type": "paragraph", - "children": [ - { - "text": "Можна вказати ім'я:" - } - ] - }, - { - "type": "code", - "language": "html", - "content": "Завантажити\r" - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Додаткові приклади:" - } - ] - }, - { - "type": "code", - "language": "html", - "content": "\r\nЗавантажити PDF\r\n\r\n\r\n\r\n Завантажити звіт\r\n\r\n\r\n\r\n Зберегти фото \r" - }, - { - "type": "paragraph", - "children": [ - { - "text": "Примітка:", - "bold": true - }, - { - "text": " Атрибут " - }, - { - "text": "download", - "code": true - }, - { - "text": " працює тільки для файлів з того самого домену (same-origin)." - } - ] - } - ] - }, - { - "question": "33. Яке призначення атрибута target у тегу a в HTML?", - "category": "html", - "answerBlocks": [ - { - "type": "paragraph", - "children": [ - { - "text": "Атрибут target визначає, де відкриється посилання:" - } - ] - }, - { - "type": "bulletList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "_self", - "code": true - }, - { - "text": " (за замовчуванням) — відкриває в тій самій вкладці." - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "_blank", - "code": true - }, - { - "text": " — відкриває у новій вкладці/вікні." - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "_parent", - "code": true - }, - { - "text": " — відкриває в батьківському фреймі (якщо є фрейми)." - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "_top", - "code": true - }, - { - "text": " — відкриває у всьому вікні, виходячи з фреймів." - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "customName", - "code": true - }, - { - "text": " — відкриває в конкретному вікні/фреймі з цим ім'ям." - } - ] - } - ] - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Приклади:" - } - ] - }, - { - "type": "code", - "language": "html", - "content": "\r\nВнутрішня сторінка\r\n\r\n\r\nЗовнішній сайт\r\n\r\n\r\n\r\n Безпечне зовнішнє посилання\r\n\r\n\r\n\r\nДовідка\r" - }, - { - "type": "paragraph", - "children": [ - { - "text": "Безпека:", - "bold": true - }, - { - "text": " Для " - }, - { - "text": "target=\"_blank\"", - "code": true - }, - { - "text": " рекомендується додавати " - }, - { - "text": "rel=\"noopener noreferrer\"", - "code": true - }, - { - "text": "." - } - ] - } - ] - }, - { - "question": "34. Як вставляти зображення на HTML-сторінку?", - "category": "html", - "answerBlocks": [ - { - "type": "paragraph", - "children": [ - { - "text": "Використовуємо тег " - }, - { - "text": "", - "code": true - }, - { - "text": ":" - } - ] - }, - { - "type": "code", - "language": "html", - "content": "\"Опис\r" - }, - { - "type": "bulletList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "src", - "code": true - }, - { - "text": " — шлях до зображення." - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "alt", - "code": true - }, - { - "text": " — текстовий опис для доступності та коли зображення не завантажилось." - } - ] - } - ] - }, - { - "type": "paragraph", - "children": [ - { - "text": "Опційно: " - }, - { - "text": "width", - "code": true - }, - { - "text": ", " - }, - { - "text": "height", - "code": true - }, - { - "text": ", " - }, - { - "text": "title", - "code": true - }, - { - "text": "." - } - ] - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Детальніші приклади:" - } - ] - }, - { - "type": "code", - "language": "html", - "content": "\r\n\"Фотографія\r\n\r\n\r\n\"Логотип\r\n\r\n\r\n\"Графік\r\n\r\n\r\n\"Банер\r\n\r\n\r\n\"Зовнішнє\r" - }, - { - "type": "paragraph", - "children": [ - { - "text": "Краща практика:", - "bold": true - }, - { - "text": " Завжди включайте атрибут " - }, - { - "text": "alt", - "code": true - }, - { - "text": " для доступності." - } - ] - } - ] - }, - { - "question": "35. Яке значення має атрибут alt для зображень?", - "category": "html", - "answerBlocks": [ - { - "type": "paragraph", - "children": [ - { - "text": "alt", - "code": true - }, - { - "text": " — альтернативний текст для зображення." - } - ] - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Призначення:" - } - ] - }, - { - "type": "bulletList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "Показується, якщо зображення не завантажилось." - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Допомагає скрінрідерам робити сайт доступним." - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Використовується для SEO." - } - ] - } - ] - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Приклади правильного використання:" - } - ] - }, - { - "type": "code", - "language": "html", - "content": "\r\n\r\n\r\n\r\n\"\"\r\n\r\n\r\n\r\n \"Повернутися\r\n\r\n\r\n\r\n\"Інфографіка:\r" - }, - { - "type": "paragraph", - "children": [ - { - "text": "Правила:", - "bold": true - } - ] - }, - { - "type": "bulletList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "Для декоративних зображень використовуйте " - }, - { - "text": "alt=\"\"", - "code": true - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Для інформативних - описуйте зміст" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Для посилань - описуйте призначення посилання" - } - ] - } - ] - } - ] - }, - { - "question": "36. Які формати зображень підтримуються веббраузерами?", - "category": "html", - "answerBlocks": [ - { - "type": "paragraph", - "children": [ - { - "text": "Основні формати, які підтримують сучасні браузери:", - "bold": true - } - ] - }, - { - "type": "bulletList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "JPEG", - "code": true - }, - { - "text": " / " - }, - { - "text": "JPG", - "code": true - }, - { - "text": " — фотографії з високою деталізацією, стиснення з втратою якості." - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "PNG", - "code": true - }, - { - "text": " — графіка з прозорістю, без втрати якості." - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "GIF", - "code": true - }, - { - "text": " — анімація та прості картинки з обмеженою палітрою." - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "WebP", - "code": true - }, - { - "text": " — сучасний формат з високим стисненням і прозорістю." - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "SVG", - "code": true - }, - { - "text": " — векторна графіка, масштабована без втрати якості." - } - ] - } - ] - }, - { - "type": "paragraph", - "children": [ - { - "text": "Менш поширені: BMP, ICO (для іконок)." - } - ] - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Коли використовувати:" - } - ] - }, - { - "type": "code", - "language": "html", - "content": "\r\n\"Пейзаж\"\r\n\r\n\r\n\"Логотип\"\r\n\r\n\r\n\"Завантаження\"\r\n\r\n\r\n\"Оптимізоване\r\n\r\n\r\n\"Іконка\"\r" - }, - { - "type": "paragraph", - "children": [ - { - "text": "Сучасні тенденції:", - "bold": true - }, - { - "text": " WebP та AVIF для кращого стиснення, SVG для іконок та простої графіки." - } - ] - } - ] - }, - { - "question": "37. Які різні типи списків доступні в HTML?", - "category": "html", - "answerBlocks": [ - { - "type": "paragraph", - "children": [ - { - "text": "В HTML є три основні типи списків:" - } - ] - }, - { - "type": "numberedList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "Нумерований список (`
        `)", - "bold": true - }, - { - "text": " — елементи пронумеровані:" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Маркований список (`
          `)", - "bold": true - }, - { - "text": " — елементи з маркерами (кульки, квадрати):" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Список визначень (`
          `)", - "bold": true - }, - { - "text": " — термін + опис:" - } - ] - } - ] - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Приклади:" - } - ] - }, - { - "type": "code", - "language": "html", - "content": "\r\n
            \r\n
          1. Перший крок
          2. \r\n
          3. Другий крок
          4. \r\n
          5. Третій крок
          6. \r\n
          \r\n\r\n\r\n
            \r\n
          • Молоко
          • \r\n
          • Хліб
          • \r\n
          • Яйця
          • \r\n
          \r\n\r\n\r\n
          \r\n
          HTML
          \r\n
          HyperText Markup Language
          \r\n
          CSS
          \r\n
          Cascading Style Sheets
          \r\n
          \r" - } - ] - }, - { - "question": "38. Як створювати впорядковані, невпорядковані списки та списки з описом у HTML?", - "category": "html", - "answerBlocks": [ - { - "type": "numberedList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "Впорядкований список (`
            `):", - "bold": true - }, - { - "type": "code", - "language": "html", - "content": "
              \r\n
            1. Перший
            2. \r\n
            3. Другий
            4. \r\n
            \r" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Невпорядкований список (`
              `):", - "bold": true - }, - { - "type": "code", - "language": "html", - "content": "
                \r\n
              • Пункт 1
              • \r\n
              • Пункт 2
              • \r\n
              \r" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Список з описом (`
              `):", - "bold": true - }, - { - "type": "code", - "language": "html", - "content": "
              \r\n
              HTML
              \r\n
              Мова розмітки
              \r\n
              JavaScript
              \r\n
              Мова програмування
              \r\n
              \r" - } - ] - } - ] - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Розширені приклади:" - } - ] - }, - { - "type": "code", - "language": "html", - "content": "\r\n
                \r\n
              1. Третій пункт (C)
              2. \r\n
              3. Четвертий пункт (D)
              4. \r\n
              \r\n\r\n\r\n
                \r\n
              • Квадратний маркер
              • \r\n
              • Ще один квадратний маркер
              • \r\n
              \r" - } - ] - }, - { - "question": "39. Чи можна вкладати списки в HTML? Якщо так, то як?", - "category": "html", - "answerBlocks": [ - { - "type": "paragraph", - "children": [ - { - "text": "Так, можна. Вкладені списки створюють всередині " - }, - { - "text": "
            • ", - "code": true - }, - { - "text": " елемента:" - } - ] - }, - { - "type": "code", - "language": "html", - "content": "
                \r\n
              • \r\n Пункт 1\r\n
                  \r\n
                • Вкладений пункт 1
                • \r\n
                • Вкладений пункт 2
                • \r\n
                \r\n
              • \r\n
              • Пункт 2
              • \r\n
              \r" - }, - { - "type": "paragraph", - "children": [ - { - "text": "Можна вкладати будь-які типи списків (" - }, - { - "text": "
                ", - "code": true - }, - { - "text": " всередині " - }, - { - "text": "
                  ", - "code": true - }, - { - "text": " і навпаки)." - } - ] - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Більш складний приклад:" - } - ] - }, - { - "type": "code", - "language": "html", - "content": "
                    \r\n
                  1. \r\n Веб-технології\r\n
                      \r\n
                    • \r\n Frontend\r\n
                        \r\n
                      • HTML
                      • \r\n
                      • CSS
                      • \r\n
                      • JavaScript
                      • \r\n
                      \r\n
                    • \r\n
                    • \r\n Backend\r\n
                        \r\n
                      • Node.js
                      • \r\n
                      • Python
                      • \r\n
                      • PHP
                      • \r\n
                      \r\n
                    • \r\n
                    \r\n
                  2. \r\n
                  3. Мобільна розробка
                  4. \r\n
                  \r" - }, - { - "type": "paragraph", - "children": [ - { - "text": "Правило:", - "bold": true - }, - { - "text": " Вкладений список завжди має бути всередині " - }, - { - "text": "
                1. ", - "code": true - }, - { - "text": ", а не між " - }, - { - "text": "
                2. ", - "code": true - }, - { - "text": " елементами." - } - ] - } - ] - }, - { - "question": "40. Як можна згрупувати опції всередині тегу select?", - "category": "html", - "answerBlocks": [ - { - "type": "paragraph", - "children": [ - { - "text": "Для групування опцій у випадаючому списку використовуйте тег " - }, - { - "text": "", - "code": true - }, - { - "text": ":" - } - ] - }, - { - "type": "code", - "language": "html", - "content": "\r" - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Переваги " - }, - { - "text": "", - "code": true - }, - { - "text": ":" - } - ] - }, - { - "type": "bulletList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "Візуально групує пов'язані опції" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Покращує юзабіліті для довгих списків" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Підтримує атрибут " - }, - { - "text": "label", - "code": true - }, - { - "text": " для назви групи" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Можна вимкнути групу через " - }, - { - "text": "disabled", - "code": true - } - ] - } - ] - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Приклад з вимкненою групою:" - } - ] - }, - { - "type": "code", - "language": "html", - "content": "\r" - } - ] - }, - { - "question": "41. Що таке HTML-форми та як їх створити?", - "category": "html", - "answerBlocks": [ - { - "type": "paragraph", - "children": [ - { - "text": "HTML-форма — це елемент, який збирає дані від користувача та відправляє їх на сервер або обробляє на клієнті." - } - ] - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Приклад створення:" - } - ] - }, - { - "type": "code", - "language": "html", - "content": "
                  \r\n \r\n \r\n\r\n \r\n \r\n\r\n \r\n\r" - }, - { - "type": "bulletList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "
                  ", - "code": true - }, - { - "text": " — контейнер форми." - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "action", - "code": true - }, - { - "text": " — URL, куди відправляються дані." - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "method", - "code": true - }, - { - "text": " — спосіб відправки (get або post)." - } - ] - } - ] - }, - { - "type": "paragraph", - "children": [ - { - "text": "Поля вводу", - "bold": true - }, - { - "text": " (" - }, - { - "text": "", - "code": true - }, - { - "text": ", " - }, - { - "text": "\r" - }, - { - "type": "paragraph", - "children": [ - { - "text": "Стилізація через CSS:", - "bold": true - } - ] - }, - { - "type": "code", - "language": "css", - "content": "input:required {\r\n border-left: 3px solid red;\r\n}\r\n\r\ninput:valid {\r\n border-left: 3px solid green;\r\n}\r" - } - ] - }, - { - "question": "44. Яке призначення елемента label у формах?", - "category": "html", - "answerBlocks": [ - { - "type": "paragraph", - "children": [ - { - "text": "
      {t('listCaption')}
      + {t('table.title')} + {t('table.slug')} + {t('table.price')} + {t('table.category')} + {t('table.type')} + {t('table.stock')} + {t('table.badge')} + {t('table.active')} + {t('table.featured')} + {t('table.created')} + {t('table.actions')}
      - {row.isFeatured ? t('actions.yes') : t('actions.no')} + {row.isFeatured + ? t('actions.yes') + : t('actions.no')} -
      +
      {t('actions.view')} {t('actions.edit')} diff --git a/frontend/app/[locale]/shop/cart/page.tsx b/frontend/app/[locale]/shop/cart/page.tsx index 5f22926d..658fe356 100644 --- a/frontend/app/[locale]/shop/cart/page.tsx +++ b/frontend/app/[locale]/shop/cart/page.tsx @@ -3,15 +3,56 @@ import { useState } from 'react'; import Image from 'next/image'; import { useParams } from 'next/navigation'; -import { useRouter } from '@/i18n/routing'; +import { useRouter, Link } from '@/i18n/routing'; import { useTranslations } from 'next-intl'; - +import { cn } from '@/lib/utils'; import { Minus, Plus, Trash2, ShoppingBag } from 'lucide-react'; -import { Link } from '@/i18n/routing'; import { useCart } from '@/components/shop/cart-provider'; import { generateIdempotencyKey } from '@/lib/shop/idempotency'; import { formatMoney } from '@/lib/shop/currency'; +import { + SHOP_FOCUS, + SHOP_LINK_BASE, + SHOP_LINK_MD, + SHOP_LINK_XS, + SHOP_DISABLED, + SHOP_CHIP_INTERACTIVE, + SHOP_CHIP_SHADOW_HOVER, + SHOP_CHIP_BORDER_HOVER, + SHOP_STEPPER_BUTTON_BASE, + SHOP_CTA_BASE, + SHOP_CTA_INTERACTIVE, + SHOP_CTA_INSET, + SHOP_CTA_WAVE, + shopCtaGradient, +} from '@/lib/shop/ui-classes'; + +const SHOP_PRODUCT_LINK = cn( + 'block truncate', + SHOP_LINK_BASE, + SHOP_LINK_MD, + SHOP_FOCUS +); + +const SHOP_STEPPER_BTN = cn( + SHOP_STEPPER_BUTTON_BASE, + 'h-8 w-8', + SHOP_CHIP_INTERACTIVE, + SHOP_CHIP_SHADOW_HOVER, + SHOP_CHIP_BORDER_HOVER, + SHOP_FOCUS, + SHOP_DISABLED +); + +const SHOP_HERO_CTA = cn( + SHOP_CTA_BASE, + SHOP_CTA_INTERACTIVE, + SHOP_FOCUS, + SHOP_DISABLED, + 'w-full justify-center gap-2 px-6 py-3 text-sm text-white', + 'shadow-[var(--shop-hero-btn-shadow)] hover:shadow-[var(--shop-hero-btn-shadow-hover)]' +); export default function CartPage() { const { cart, updateQuantity, removeFromCart } = useCart(); @@ -32,7 +73,7 @@ export default function CartPage() { try { return tColors(colorSlug); } catch { - return color; + return color; } }; @@ -67,9 +108,8 @@ export default function CartPage() { typeof data?.message === 'string' ? data.message : typeof data?.error === 'string' - ? data.error - : 'Unable to start checkout right now.'; - + ? data.error + : 'Unable to start checkout right now.'; setCheckoutError(message); return; } @@ -86,7 +126,7 @@ export default function CartPage() { ? data.clientSecret : null; - const orderId: string = String(data.orderId); + const orderId = String(data.orderId); setCreatedOrderId(orderId); if (paymentProvider === 'stripe' && clientSecret) { @@ -95,7 +135,6 @@ export default function CartPage() { orderId )}?clientSecret=${encodeURIComponent(clientSecret)}&clearCart=1` ); - return; } @@ -120,22 +159,28 @@ export default function CartPage() { return (
      -
      ); @@ -162,9 +207,7 @@ export default function CartPage() { {cart.items.map(item => (
    1. @@ -183,7 +226,7 @@ export default function CartPage() {
      {item.title} @@ -200,11 +243,7 @@ export default function CartPage() { {item.quantity >= item.stock && ( - + {t('actions.maxStock', { stock: item.stock })} )}
      - {formatMoney( - item.lineTotalMinor, - item.currency, - locale - )} + {formatMoney(item.lineTotalMinor, item.currency, locale)}
    2. @@ -278,14 +310,8 @@ export default function CartPage() { -
      @@ -273,7 +286,9 @@ export default async function MyOrdersPage({
      - {t('table.orderId')}: + + {t('table.orderId')}:{' '} + {shortOrderId(o.id)} diff --git a/frontend/app/[locale]/shop/page.tsx b/frontend/app/[locale]/shop/page.tsx index c715c1c2..8a1c4468 100644 --- a/frontend/app/[locale]/shop/page.tsx +++ b/frontend/app/[locale]/shop/page.tsx @@ -4,6 +4,13 @@ import { Hero } from '@/components/shop/shop-hero'; import { CategoryTile } from '@/components/shop/category-tile'; import { getHomepageContent } from '@/lib/shop/data'; import { getTranslations } from 'next-intl/server'; +import { + SHOP_CTA_BASE, + SHOP_CTA_INSET, + SHOP_CTA_WAVE, + SHOP_FOCUS, + shopCtaGradient, +} from '@/lib/shop/ui-classes'; export default async function HomePage({ params, @@ -38,10 +45,27 @@ export default async function HomePage({ - {t('viewAll')} + {t('viewAll')} + @@ -98,10 +122,40 @@ export default async function HomePage({
      - {t('hero.cta')} + {/* base gradient */} +
      diff --git a/frontend/app/[locale]/shop/products/[slug]/page.tsx b/frontend/app/[locale]/shop/products/[slug]/page.tsx index 6f8b7eab..1b7fe732 100644 --- a/frontend/app/[locale]/shop/products/[slug]/page.tsx +++ b/frontend/app/[locale]/shop/products/[slug]/page.tsx @@ -4,12 +4,13 @@ import Image from 'next/image'; import { notFound } from 'next/navigation'; import { ArrowLeft } from 'lucide-react'; import { getTranslations } from 'next-intl/server'; - +import { cn } from '@/lib/utils'; import { AddToCartButton } from '@/components/shop/add-to-cart-button'; import { getProductPageData } from '@/lib/shop/data'; import { formatMoney, resolveCurrencyFromLocale } from '@/lib/shop/currency'; import { getPublicProductBySlug } from '@/db/queries/shop/products'; import { Link } from '@/i18n/routing'; +import { SHOP_FOCUS, SHOP_NAV_LINK_BASE } from '@/lib/shop/ui-classes'; export const dynamic = 'force-dynamic'; @@ -37,70 +38,24 @@ export default async function ProductPage({ if (result.kind === 'not_found') { notFound(); } - - if (result.kind === 'unavailable') { - const p = result.product; - - return ( -
      - - -
      -
      - {p.badge && p.badge !== 'NONE' && ( - - {p.badge} - - )} - {p.name} -
      - -
      -

      - {p.name} -

      - -
      - {t('notAvailable')} -
      - - {p.description && ( -

      {p.description}

      - )} -
      -
      -
      - ); - } - - const product = result.product; - + const isUnavailable = result.kind === 'unavailable'; + const product = result.product as any; // shape differs for unavailable vs available; guard reads below + const NAV_LINK = cn(SHOP_NAV_LINK_BASE, SHOP_FOCUS, 'text-lg', 'items-center gap-2'); + const badge = product?.badge as string | undefined; + const badgeLabel = + badge && badge !== 'NONE' + ? (() => { + try { + return tProduct(`badges.${badge}`); + } catch { + return badge; + } + })() + : null; return (
      diff --git a/frontend/app/globals.css b/frontend/app/globals.css index 702d39a7..2743a7c1 100644 --- a/frontend/app/globals.css +++ b/frontend/app/globals.css @@ -342,7 +342,7 @@ .animate-spin-slow { animation: spin 20s linear infinite; } - + .animate-spin-slower { animation: spin 30s linear infinite reverse; } @@ -354,7 +354,8 @@ } @keyframes float { - 0%, 100% { + 0%, + 100% { transform: translateY(0); } 50% { @@ -375,6 +376,10 @@ .shop-scope { /* keep shop rounding slightly tighter than platform */ --radius: calc(var(--radius-base) - 2px); + + /* Shop header/button accent in LIGHT: no platform blue */ + --accent-primary: var(--foreground); + --accent-hover: color-mix(in oklab, var(--foreground) 92%, white); /* light: shop accent = black */ --accent: var(--foreground); @@ -390,6 +395,43 @@ /* CTA button: light theme (section is black) -> pink bg + white text */ --shop-cta-bg: #ff2d55; --shop-cta-fg: oklch(0.985 0 0); + + /* Hero CTA (top section) */ + --shop-hero-btn-bg: var(--foreground); + --shop-hero-btn-bg-hover: color-mix(in oklab, var(--foreground) 92%, white); + --shop-hero-btn-shadow: 0 18px 45px rgba(0, 0, 0, 0.35); + --shop-hero-btn-shadow-hover: 0 22px 60px rgba(0, 0, 0, 0.45); + + /* Card aura (product + category tiles) */ + --shop-card-shadow-hover: + 0 0 0 1px rgba(0, 0, 0, 0.22), 0 18px 45px rgba(0, 0, 0, 0.12); + + /* Bottom CTA (inverted by theme) */ + --shop-cta-shadow: 0 22px 60px rgba(255, 45, 85, 0.45); + --shop-cta-shadow-hover: 0 28px 80px rgba(255, 45, 85, 0.6); + --shop-cta-bg-hover: #e0264b; + + /* Filters / chips (stronger than card aura) */ + --shop-chip-shadow-hover: + 0 0 0 1px rgba(0, 0, 0, 0.32), 0 0 0 5px rgba(0, 0, 0, 0.08), + 0 18px 45px rgba(0, 0, 0, 0.22); + + --shop-chip-shadow-selected: + 0 0 0 2px rgba(0, 0, 0, 0.4), 0 0 0 7px rgba(0, 0, 0, 0.1), + 0 22px 60px rgba(0, 0, 0, 0.28); + + --shop-hero-btn-success-bg: color-mix( + in oklab, + var(--shop-hero-btn-bg) 88%, + white + ); + --shop-hero-btn-success-bg-hover: color-mix( + in oklab, + var(--shop-hero-btn-bg) 80%, + white + ); + --shop-hero-btn-success-shadow: 0 22px 60px rgba(0, 0, 0, 0.25); + --shop-hero-btn-success-shadow-hover: 0 28px 80px rgba(0, 0, 0, 0.32); } .dark .shop-scope { @@ -402,15 +444,56 @@ --color-accent: var(--accent-primary); --color-accent-foreground: var(--foreground); --color-ring: var(--accent-primary); - --card: var(--background); + /* Shop accent in DARK: keep magenta */ + --accent-primary: #ff2d55; + --accent-hover: #e0264b; + /* keep borders closer to previous shop look, derived (no hex) */ --border: color-mix(in oklab, var(--foreground) 18%, var(--background)); --input: color-mix(in oklab, var(--foreground) 18%, var(--background)); + /* CTA button: dark theme (section becomes white) -> black bg + white text */ --shop-cta-bg: var(--background); --shop-cta-fg: var(--foreground); + + /* Hero CTA (top section) */ + --shop-hero-btn-bg: var(--accent-primary); + --shop-hero-btn-bg-hover: var(--accent-hover); + --shop-hero-btn-shadow: 0 22px 60px rgba(255, 45, 85, 0.45); + --shop-hero-btn-shadow-hover: 0 28px 80px rgba(255, 45, 85, 0.6); + + /* Card aura (product + category tiles) */ + --shop-card-shadow-hover: + 0 0 0 1px rgba(255, 45, 85, 0.28), 0 18px 45px rgba(255, 45, 85, 0.16); + + /* Bottom CTA (inverted by theme) */ + --shop-cta-shadow: 0 18px 45px rgba(0, 0, 0, 0.3); + --shop-cta-shadow-hover: 0 22px 60px rgba(0, 0, 0, 0.45); + --shop-cta-bg-hover: color-mix(in oklab, var(--background) 92%, white); + + /* Filters / chips (stronger than card aura) */ + --shop-chip-shadow-hover: + 0 0 0 1px rgba(255, 45, 85, 0.55), 0 0 0 5px rgba(255, 45, 85, 0.18), + 0 18px 50px rgba(255, 45, 85, 0.32); + + --shop-chip-shadow-selected: + 0 0 0 2px rgba(255, 45, 85, 0.7), 0 0 0 7px rgba(255, 45, 85, 0.22), + 0 22px 70px rgba(255, 45, 85, 0.38); + + --shop-hero-btn-success-bg: color-mix( + in oklab, + var(--accent-primary) 82%, + black + ); + --shop-hero-btn-success-bg-hover: color-mix( + in oklab, + var(--accent-primary) 72%, + black + ); + --shop-hero-btn-success-shadow: 0 22px 60px rgba(255, 45, 85, 0.45); + --shop-hero-btn-success-shadow-hover: 0 28px 80px rgba(255, 45, 85, 0.6); } /* about-community */ diff --git a/frontend/components/shared/Footer.tsx b/frontend/components/shared/Footer.tsx index a355efa4..d195df3e 100644 --- a/frontend/components/shared/Footer.tsx +++ b/frontend/components/shared/Footer.tsx @@ -5,6 +5,8 @@ import { Link } from '@/i18n/routing'; import { Github, Linkedin, Send } from 'lucide-react'; import { ThemeToggle } from '@/components/theme/ThemeToggle'; import { useTranslations } from 'next-intl'; +import { useSelectedLayoutSegments } from 'next/navigation'; +import { cn } from '@/lib/utils'; const SOCIAL = [ { label: 'GitHub', href: 'https://github.com/DevLoversTeam', Icon: Github }, @@ -18,18 +20,24 @@ const SOCIAL = [ export default function Footer() { const t = useTranslations('footer'); - + const segments = useSelectedLayoutSegments(); + const isShop = segments.includes('shop'); return ( -
      +

      {t('builtWith')}{' '} - + DevLovers {' '} {t('byCommunity')} @@ -39,10 +47,10 @@ export default function Footer() { {t('privacyPolicy')} @@ -51,10 +59,10 @@ focus-visible:[color:var(--accent-hover)] {t('termsOfService')} @@ -91,13 +99,13 @@ focus-visible:[color:var(--accent-hover)] dark:text-slate-300 transition-all -hover:-translate-y-0.5 -hover:!text-[var(--accent-hover)] -hover:!border-[var(--accent-hover)] + hover:-translate-y-0.5 + hover:!text-[var(--footer-hover)] + hover:!border-[var(--footer-hover)] -active:!text-[var(--accent-hover)] -active:!border-[var(--accent-hover)] -active:scale-95 + active:!text-[var(--footer-hover)] + active:!border-[var(--footer-hover)] + active:scale-95 " diff --git a/frontend/components/shared/HeaderButton.tsx b/frontend/components/shared/HeaderButton.tsx index 8bf54ce5..c9bd8de1 100644 --- a/frontend/components/shared/HeaderButton.tsx +++ b/frontend/components/shared/HeaderButton.tsx @@ -15,6 +15,14 @@ interface HeaderButtonProps { label?: string; + /** + * Optional badge rendered OUTSIDE the overflow-hidden button/link, + * so it will not be clipped by the shine/gradient container. + */ + badge?: React.ReactNode; + badgeClassName?: string; + badgeAriaLabel?: string; + className?: string; } @@ -26,6 +34,9 @@ export function HeaderButton({ variant = 'default', showArrow = false, label, + badge, + badgeClassName = '', + badgeAriaLabel, className = '', }: HeaderButtonProps) { const isIconOnly = variant === 'icon'; @@ -120,8 +131,37 @@ export function HeaderButton({ ${className} `; - if (!href) { + const wrapWithBadge = (node: React.ReactNode) => { + if (!badge) return node; + + const defaultBadgeClasses = ` + pointer-events-none + absolute -right-1 -top-1 + flex h-5 min-w-5 items-center justify-center + rounded-full px-1 + text-[11px] font-semibold leading-none tabular-nums + ring-2 ring-background + `; + + const ariaProps = badgeAriaLabel + ? { 'aria-label': badgeAriaLabel } + : { 'aria-hidden': true as const }; + return ( + + {node} + + {badge} + + + ); + }; + + if (!href) { + return wrapWithBadge( ); diff --git a/frontend/components/shop/admin/admin-product-delete-button.tsx b/frontend/components/shop/admin/admin-product-delete-button.tsx index 065b6b11..5ee73c69 100644 --- a/frontend/components/shop/admin/admin-product-delete-button.tsx +++ b/frontend/components/shop/admin/admin-product-delete-button.tsx @@ -85,14 +85,14 @@ export function AdminProductDeleteButton({ }; return ( -

      +
      diff --git a/frontend/components/shop/admin/admin-product-status-toggle.tsx b/frontend/components/shop/admin/admin-product-status-toggle.tsx index 62899c1c..48ab1edf 100644 --- a/frontend/components/shop/admin/admin-product-status-toggle.tsx +++ b/frontend/components/shop/admin/admin-product-status-toggle.tsx @@ -48,7 +48,10 @@ export function AdminProductStatusToggle({ // ignore } - if (response.status === 403 && (code === 'CSRF_MISSING' || code === 'CSRF_INVALID')) { + if ( + response.status === 403 && + (code === 'CSRF_MISSING' || code === 'CSRF_INVALID') + ) { setError(t('securityExpired')); return; } @@ -68,10 +71,14 @@ export function AdminProductStatusToggle({ } }; - const buttonLabel = isLoading ? t('updating') : isActive ? t('deactivate') : t('activate'); + const buttonLabel = isLoading + ? t('updating') + : isActive + ? t('deactivate') + : t('activate'); return ( -
      +
      diff --git a/frontend/components/shop/admin/shop-admin-topbar.tsx b/frontend/components/shop/admin/shop-admin-topbar.tsx index 21ca8c50..2b04f168 100644 --- a/frontend/components/shop/admin/shop-admin-topbar.tsx +++ b/frontend/components/shop/admin/shop-admin-topbar.tsx @@ -11,7 +11,7 @@ export async function ShopAdminTopbar() { aria-label={t('label')} className="flex flex-wrap items-center justify-between gap-3 py-3" > -
        +
        -
        +
        {isLoading ? tCommon('loading') : t('loadMore')} diff --git a/frontend/components/shop/category-tile.tsx b/frontend/components/shop/category-tile.tsx index 86028f62..3047f33b 100644 --- a/frontend/components/shop/category-tile.tsx +++ b/frontend/components/shop/category-tile.tsx @@ -20,10 +20,10 @@ export async function CategoryTile({ category }: CategoryTileProps) { href={href} aria-label={tCategoryTile('shopCategory', { name: categoryName })} className={[ - // IMPORTANT: make it block-level so aspect-ratio + Image fill work correctly 'group relative block w-full', 'aspect-[4/3] overflow-hidden rounded-lg bg-muted', - 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground/30 focus-visible:ring-offset-2 focus-visible:ring-offset-background', + 'transition-[box-shadow] duration-500 hover:shadow-[var(--shop-card-shadow-hover)]', + 'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[color:var(--color-ring)] focus-visible:ring-offset-2 focus-visible:ring-offset-background', ].join(' ')} > 0; + const badgeText = itemCount > 99 ? '99+' : itemCount; + return ( - -
        ); diff --git a/frontend/components/shop/product-filters.tsx b/frontend/components/shop/product-filters.tsx index e8734097..0d8226f2 100644 --- a/frontend/components/shop/product-filters.tsx +++ b/frontend/components/shop/product-filters.tsx @@ -3,7 +3,16 @@ import { useId } from 'react'; import { usePathname, useRouter, useSearchParams } from 'next/navigation'; import { useTranslations } from 'next-intl'; - +import { + SHOP_FOCUS, + SHOP_CHIP_INTERACTIVE, + SHOP_CHIP_HOVER, + SHOP_CHIP_SELECTED, + SHOP_SWATCH_BASE, + SHOP_SIZE_CHIP_BASE, + SHOP_FILTER_ITEM_BASE, + SHOP_CHIP_BORDER_HOVER, +} from '@/lib/shop/ui-classes'; import { CATEGORIES, COLORS, PRODUCT_TYPES, SIZES } from '@/lib/config/catalog'; import { cn } from '@/lib/utils'; @@ -55,13 +64,20 @@ export function ProductFilters() { onClick={() => updateFilter('category', cat.slug)} aria-current={currentCategory === cat.slug ? 'true' : undefined} className={cn( - 'text-sm transition-colors', + SHOP_FILTER_ITEM_BASE, + SHOP_FOCUS, currentCategory === cat.slug - ? 'font-medium text-accent' - : 'text-muted-foreground hover:text-foreground' + ? 'text-accent' + : 'text-muted-foreground hover:text-accent' )} > - {tCategories(cat.slug === 'new-arrivals' ? 'newArrivals' : cat.slug === 'best-sellers' ? 'bestSellers' : cat.slug)} + {tCategories( + cat.slug === 'new-arrivals' + ? 'newArrivals' + : cat.slug === 'best-sellers' + ? 'bestSellers' + : cat.slug + )} ))} @@ -90,10 +106,11 @@ export function ProductFilters() { } aria-pressed={isSelected} className={cn( - 'text-sm transition-colors', + SHOP_FILTER_ITEM_BASE, + SHOP_FOCUS, isSelected - ? 'font-medium text-accent' - : 'text-muted-foreground hover:text-foreground' + ? 'text-accent' + : 'text-muted-foreground hover:text-accent' )} > {tTypes(type.slug)} @@ -127,10 +144,14 @@ export function ProductFilters() { } aria-pressed={isSelected} className={cn( - 'h-7 w-7 rounded-full border-2 transition-all', + SHOP_SWATCH_BASE, + // у фільтрах розмір 8x8, тому override: + 'h-8 w-8', + SHOP_CHIP_INTERACTIVE, + SHOP_FOCUS, isSelected - ? 'border-accent ring-2 ring-accent ring-offset-2 ring-offset-background' - : 'border-border hover:border-muted-foreground' + ? SHOP_CHIP_SELECTED + : cn(SHOP_CHIP_HOVER, SHOP_CHIP_BORDER_HOVER) )} style={{ background: color.hex }} title={colorLabel} @@ -161,10 +182,18 @@ export function ProductFilters() { onClick={() => updateFilter('size', isSelected ? null : size)} aria-pressed={isSelected} className={cn( - 'rounded-md border px-3 py-1.5 text-sm transition-colors', + SHOP_SIZE_CHIP_BASE, + // у фільтрах інші паддінги — override: + 'px-3 py-1.5 text-sm', + SHOP_CHIP_INTERACTIVE, + SHOP_FOCUS, isSelected - ? 'border-accent bg-accent text-accent-foreground' - : 'border-border text-muted-foreground hover:border-foreground hover:text-foreground' + ? cn('bg-accent text-accent-foreground', SHOP_CHIP_SELECTED) + : cn( + 'bg-transparent text-muted-foreground border-border hover:text-foreground', + SHOP_CHIP_HOVER, + SHOP_CHIP_BORDER_HOVER + ) )} > {size} diff --git a/frontend/components/shop/product-sort.tsx b/frontend/components/shop/product-sort.tsx index 5640c4aa..8aae175c 100644 --- a/frontend/components/shop/product-sort.tsx +++ b/frontend/components/shop/product-sort.tsx @@ -6,9 +6,15 @@ import { useId } from 'react'; import { useRouter } from '@/i18n/routing'; import { useSearchParams } from 'next/navigation'; import { useTranslations } from 'next-intl'; - +import { ChevronDown } from 'lucide-react'; import { SORT_OPTIONS } from '@/lib/config/catalog'; import { cn } from '@/lib/utils'; +import { + SHOP_DISABLED, + SHOP_FOCUS, + SHOP_SELECT_BASE, + SHOP_SELECT_INTERACTIVE, +} from '@/lib/shop/ui-classes'; type ProductSortProps = { className?: string; @@ -30,10 +36,10 @@ export function ProductSort({ className }: ProductSortProps) { const getOptionLabel = (value: string) => { const keyMap: Record = { - 'featured': 'featured', + featured: 'featured', 'price-asc': 'priceAsc', 'price-desc': 'priceDesc', - 'newest': 'newest', + newest: 'newest', }; return tOptions(keyMap[value] || 'featured'); }; @@ -64,26 +70,36 @@ export function ProductSort({ className }: ProductSortProps) { {t('sortBy')} - +
        + + +
        ); } diff --git a/frontend/components/shop/shop-hero.tsx b/frontend/components/shop/shop-hero.tsx index 6eebe123..c76cba58 100644 --- a/frontend/components/shop/shop-hero.tsx +++ b/frontend/components/shop/shop-hero.tsx @@ -5,6 +5,13 @@ interface HeroProps { ctaText: string; ctaLink: string; } +import { + SHOP_CTA_BASE, + SHOP_CTA_INSET, + SHOP_CTA_WAVE, + SHOP_FOCUS, + shopCtaGradient, +} from '@/lib/shop/ui-classes'; export function Hero({ headline, subheadline, ctaText, ctaLink }: HeroProps) { return ( @@ -16,22 +23,66 @@ export function Hero({ headline, subheadline, ctaText, ctaLink }: HeroProps) {

        - {headline} + + {headline} + + +

        -

        +

        {subheadline}

        - {ctaText} - + {/* base gradient */} +
        diff --git a/frontend/components/theme/ThemeToggle.tsx b/frontend/components/theme/ThemeToggle.tsx index 874d69fa..515f96d4 100644 --- a/frontend/components/theme/ThemeToggle.tsx +++ b/frontend/components/theme/ThemeToggle.tsx @@ -58,7 +58,7 @@ export function ThemeToggle() { .theme-toggle-btn:hover :global(svg), .theme-toggle-btn:focus-visible :global(svg), .theme-toggle-btn:active :global(svg) { - color: var(--accent-hover); + color: var(--theme-toggle-hover, var(--accent-hover)); } `}
        diff --git a/frontend/lib/shop/ui-classes.ts b/frontend/lib/shop/ui-classes.ts new file mode 100644 index 00000000..22c6c737 --- /dev/null +++ b/frontend/lib/shop/ui-classes.ts @@ -0,0 +1,118 @@ +export const SHOP_FOCUS = ` + focus-visible:outline-none + focus-visible:ring-2 + focus-visible:ring-[color:var(--color-ring)] + focus-visible:ring-offset-2 + focus-visible:ring-offset-background +`; + +export const SHOP_CTA_BASE = ` + group relative inline-flex items-center overflow-hidden rounded-2xl + text-xs md:text-sm font-semibold tracking-[0.25em] uppercase + active:scale-95 active:brightness-110 + transition-shadow duration-700 ease-out +`; + +export const SHOP_CTA_WAVE = ` + absolute inset-0 opacity-0 + group-hover:opacity-100 group-active:opacity-100 + group-hover:animate-wave-slide-up +`; + +export const SHOP_CTA_INSET = ` + pointer-events-none absolute inset-[2px] rounded-2xl + bg-gradient-to-r from-white/20 via-white/5 to-white/20 + opacity-40 supports-[hover:hover]:group-hover:opacity-60 transition-opacity +`; + +// Shared interaction for “chips” (swatches, size pills, +/- stepper) +export const SHOP_CHIP_INTERACTIVE = + 'transition-[box-shadow,border-color,color,background-color,filter] duration-500 ease-out hover:brightness-110'; + +// Optional “lift” (НЕ використовуй для size/+/- якщо хочеш без тремтіння) +export const SHOP_CHIP_LIFT = + 'transition-transform duration-500 ease-out hover:-translate-y-0.5'; + +export const SHOP_CHIP_HOVER = + 'hover:shadow-[var(--shop-chip-shadow-hover)] hover:border-accent/60'; + +export const SHOP_CHIP_SELECTED = + 'border-accent ring-2 ring-accent ring-offset-2 ring-offset-background shadow-[var(--shop-chip-shadow-selected)]'; + +// Optional: base shapes (so you don’t repeat layout primitives) +export const SHOP_SWATCH_BASE = + 'group relative h-9 w-9 rounded-full border border-border shadow-none'; + +export const SHOP_SIZE_CHIP_BASE = + 'group rounded-md border px-4 py-2 text-sm font-medium'; + +export const SHOP_STEPPER_BUTTON_BASE = + 'flex h-10 w-10 items-center justify-center rounded-md border border-border text-foreground bg-transparent'; + +/** + * Builds a horizontal gradient background from two CSS vars. + * Example: shopCtaGradient('--shop-cta-bg', '--shop-cta-bg-hover') + */ + +// Text-link-ish filter items (category/type lists) +export const SHOP_FILTER_ITEM_BASE = + 'inline-flex text-sm font-medium transition-[color,transform] duration-300 ease-out hover:-translate-y-[1px]'; + +// If you want the “chip hover border” to be consistent (used in filters + size) +export const SHOP_CHIP_BORDER_HOVER = 'hover:border-foreground/60'; + +export const SHOP_DISABLED = 'disabled:pointer-events-none disabled:opacity-60'; + +export const SHOP_CHIP_SHADOW_HOVER = + 'hover:shadow-[var(--shop-chip-shadow-hover)]'; + +/** + * Reusable “text link” for product names / order links / “go to order”, etc. + * Size додаєш окремо (text-xs, text-[15px], …). + */ +export const SHOP_LINK_BASE = + 'inline-flex font-medium text-foreground underline underline-offset-4 decoration-2 decoration-foreground/30 ' + + 'transition-[color,transform,text-decoration-color] duration-300 ease-out ' + + 'hover:-translate-y-[1px] hover:text-accent hover:decoration-[color:var(--accent-primary)]'; + +export const SHOP_LINK_MD = 'text-[15px]'; +export const SHOP_LINK_XS = 'text-xs'; + +/** + * CTA interaction (додатково до SHOP_CTA_BASE). + * SHOP_CTA_BASE залишаємо як layout+typography+active. + */ +export const SHOP_CTA_INTERACTIVE = + 'transition-[transform,filter,box-shadow] duration-700 ease-out'; + +// Outline button (inverted to CTA; used in error pages, secondary actions) +export const SHOP_OUTLINE_BTN_BASE = + 'inline-flex items-center justify-center rounded-xl border px-4 py-2 ' + + 'text-sm font-semibold uppercase tracking-[0.25em] ' + + 'border-border text-foreground bg-transparent'; + +export const SHOP_OUTLINE_BTN_INTERACTIVE = + 'transition-[transform,box-shadow,border-color,color,background-color,filter] duration-500 ease-out ' + + 'hover:-translate-y-[1px] hover:shadow-[var(--shop-chip-shadow-hover)] hover:brightness-110 ' + + 'hover:border-[color:var(--accent-primary)] hover:text-[color:var(--accent-primary)]'; + +// Nav/breadcrumb-ish links (e.g. "My orders", "Shop", "Back to ...") +export const SHOP_NAV_LINK_BASE = + 'inline-flex font-medium underline underline-offset-4 decoration-2 ' + + 'text-muted-foreground decoration-foreground/30 ' + + 'transition-[color,transform,text-decoration-color] duration-300 ease-out ' + + 'hover:-translate-y-[1px] hover:text-accent hover:decoration-[color:var(--accent-primary)]'; + +// Select / dropdown (e.g. sort) +export const SHOP_SELECT_BASE = + 'peer h-10 w-full appearance-none rounded-xl border border-border bg-background pl-3 pr-11 text-sm font-medium'; + +export const SHOP_SELECT_INTERACTIVE = + 'transition-[transform,box-shadow,border-color,color,background-color,filter] duration-500 ease-out ' + + 'hover:-translate-y-[1px] hover:shadow-[var(--shop-chip-shadow-hover)] hover:border-foreground/40 hover:brightness-110'; + +export function shopCtaGradient(baseVar: string, hoverVar: string) { + return { + background: `linear-gradient(90deg, var(${baseVar}) 0%, var(${hoverVar}) 100%)`, + } as const; +} From be2ddbbb279eee8c99ea116496e5ee60eaf2f0c9 Mon Sep 17 00:00:00 2001 From: Viktor Svertoka Date: Sun, 1 Feb 2026 00:23:20 +0200 Subject: [PATCH 13/61] Host (#237) * feat(md) add netlify status * feat(files): add packages * fix(auth): use currentTarget for email input validity --- .../components/auth/fields/EmailField.tsx | 6 +- frontend/package-lock.json | 6421 +++++++---------- frontend/package.json | 4 +- studio/package-lock.json | 620 +- studio/package.json | 2 +- 5 files changed, 2780 insertions(+), 4273 deletions(-) diff --git a/frontend/components/auth/fields/EmailField.tsx b/frontend/components/auth/fields/EmailField.tsx index 972b448e..5a3344f5 100644 --- a/frontend/components/auth/fields/EmailField.tsx +++ b/frontend/components/auth/fields/EmailField.tsx @@ -12,7 +12,7 @@ export function EmailField({ const t = useTranslations("auth.fields"); const handleInvalid = (e: React.InvalidEvent) => { - const input = e.target; + const input = e.currentTarget; if (input.validity.valueMissing) { input.setCustomValidity(t("validation.required")); } else if (input.validity.typeMismatch) { @@ -35,9 +35,9 @@ export function EmailField({ onInput={handleInput} onChange={ onChange - ? e => onChange(e.target.value) + ? e => onChange(e.currentTarget.value) : undefined } /> ); -} \ No newline at end of file +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 124f811a..1feeeba8 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "frontend", - "version": "0.5.0", + "version": "0.5.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "frontend", - "version": "0.5.0", + "version": "0.5.1", "dependencies": { "@neondatabase/serverless": "^1.0.2", "@portabletext/react": "^5.0.0", @@ -40,10 +40,11 @@ "react-dom": "^19.2.1", "sonner": "^2.0.7", "stripe": "20.0.0", - "tailwind-merge": "^3.4.0" + "tailwind-merge": "^3.4.0", + "zod": "^3.24.0" }, "devDependencies": { - "@netlify/plugin-nextjs": "^5.15.1", + "@netlify/plugin-nextjs": "^5.15.7", "@tailwindcss/postcss": "^4", "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^6.8.0", @@ -119,9 +120,9 @@ } }, "node_modules/@asamuzakjp/dom-selector": { - "version": "6.7.6", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.6.tgz", - "integrity": "sha512-hBaJER6A9MpdG3WgdlOolHmbOYvSk46y7IQN/1+iqiCuUu6iWdQrs9DGKF8ocqsEqWujWf/V7b7vaDgiUmIvUg==", + "version": "6.7.7", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.7.7.tgz", + "integrity": "sha512-8CO/UQ4tzDd7ula+/CVimJIVWez99UJlbMyIgk8xOnhAVPKLnBZmUFYVgugS441v2ZqUq5EnSh6B0Ua0liSFAA==", "dev": true, "license": "MIT", "dependencies": { @@ -129,7 +130,7 @@ "bidi-js": "^1.0.3", "css-tree": "^3.1.0", "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.2.4" + "lru-cache": "^11.2.5" } }, "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": { @@ -149,4552 +150,3106 @@ "dev": true, "license": "MIT" }, - "node_modules/@aws-crypto/sha256-browser": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-5.2.0.tgz", - "integrity": "sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-js": "^5.2.0", - "@aws-crypto/supports-web-crypto": "^5.2.0", - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" }, "engines": { - "node": ">=14.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" - }, + "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-crypto/sha256-browser/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" }, "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-crypto/sha256-js": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-5.2.0.tgz", - "integrity": "sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/util": "^5.2.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^2.6.2" + "node": ">=6.9.0" }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@aws-crypto/supports-web-crypto": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-5.2.0.tgz", - "integrity": "sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - } - }, - "node_modules/@aws-crypto/util": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-5.2.0.tgz", - "integrity": "sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@smithy/util-utf8": "^2.0.0", - "tslib": "^2.6.2" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "node_modules/@aws-crypto/util/node_modules/@smithy/is-array-buffer": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.2.0.tgz", - "integrity": "sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==", + "node_modules/@babel/generator": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.0.tgz", + "integrity": "sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" }, "engines": { - "node": ">=14.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-buffer-from": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.2.0.tgz", - "integrity": "sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==", + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@smithy/is-array-buffer": "^2.2.0", - "tslib": "^2.6.2" + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" }, "engines": { - "node": ">=14.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-crypto/util/node_modules/@smithy/util-utf8": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.3.0.tgz", - "integrity": "sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==", + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^2.2.0", - "tslib": "^2.6.2" - }, + "license": "MIT", "engines": { - "node": ">=14.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/client-sesv2": { - "version": "3.975.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sesv2/-/client-sesv2-3.975.0.tgz", - "integrity": "sha512-4R+hR6N2LbvTIf6Y2e9b9PQlVkAD5WmSRMAGslul5L/jCE0LzOYC+4RQ7u5EOv0mERozcYleLPK2Zc0jTn4gTg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/credential-provider-node": "^3.972.1", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.2", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/signature-v4-multi-region": "3.972.0", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.1", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.11", - "@smithy/middleware-retry": "^4.4.27", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.26", - "@smithy/util-defaults-mode-node": "^4.2.29", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/client-sso": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.974.0.tgz", - "integrity": "sha512-ci+GiM0c4ULo4D79UMcY06LcOLcfvUfiyt8PzNY0vbt5O8BfCPYf4QomwVgkNcLLCYmroO4ge2Yy1EsLUlcD6g==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.1", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.10", - "@smithy/middleware-retry": "^4.4.26", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.11", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.25", - "@smithy/util-defaults-mode-node": "^4.2.28", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/core": { - "version": "3.973.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.973.1.tgz", - "integrity": "sha512-Ocubx42QsMyVs9ANSmFpRm0S+hubWljpPLjOi9UFrtcnVJjrVJTzQ51sN0e5g4e8i8QZ7uY73zosLmgYL7kZTQ==", + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/xml-builder": "^3.972.1", - "@smithy/core": "^3.21.1", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.972.1.tgz", - "integrity": "sha512-/etNHqnx96phy/SjI0HRC588o4vKH5F0xfkZ13yAATV7aNrb+5gYGNE6ePWafP+FuZ3HkULSSlJFj0AxgrAqYw==", + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "node_modules/@aws-sdk/credential-provider-http": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-http/-/credential-provider-http-3.972.2.tgz", - "integrity": "sha512-mXgdaUfe5oM+tWKyeZ7Vh/iQ94FrkMky1uuzwTOmFADiRcSk5uHy/e3boEFedXiT/PRGzgBmqvJVK4F6lUISCg==", + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.10", - "tslib": "^2.6.2" - }, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.972.1.tgz", - "integrity": "sha512-OdbJA3v+XlNDsrYzNPRUwr8l7gw1r/nR8l4r96MDzSBDU8WEo8T6C06SvwaXR8SpzsjO3sq5KMP86wXWg7Rj4g==", + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/credential-provider-env": "^3.972.1", - "@aws-sdk/credential-provider-http": "^3.972.1", - "@aws-sdk/credential-provider-login": "^3.972.1", - "@aws-sdk/credential-provider-process": "^3.972.1", - "@aws-sdk/credential-provider-sso": "^3.972.1", - "@aws-sdk/credential-provider-web-identity": "^3.972.1", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/credential-provider-login": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-login/-/credential-provider-login-3.972.1.tgz", - "integrity": "sha512-CccqDGL6ZrF3/EFWZefvKW7QwwRdxlHUO8NVBKNVcNq6womrPDvqB6xc9icACtE0XB0a7PLoSTkAg8bQVkTO2w==", + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.972.1.tgz", - "integrity": "sha512-DwXPk9GfuU/xG9tmCyXFVkCr6X3W8ZCoL5Ptb0pbltEx1/LCcg7T+PBqDlPiiinNCD6ilIoMJDWsnJ8ikzZA7Q==", + "node_modules/@babel/helpers": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", + "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/credential-provider-env": "^3.972.1", - "@aws-sdk/credential-provider-http": "^3.972.1", - "@aws-sdk/credential-provider-ini": "^3.972.1", - "@aws-sdk/credential-provider-process": "^3.972.1", - "@aws-sdk/credential-provider-sso": "^3.972.1", - "@aws-sdk/credential-provider-web-identity": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@babel/template": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.972.1.tgz", - "integrity": "sha512-bi47Zigu3692SJwdBvo8y1dEwE6B61stCwCFnuRWJVTfiM84B+VTSCV661CSWJmIZzmcy7J5J3kWyxL02iHj0w==", + "node_modules/@babel/parser": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.0.0" } }, - "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.972.1.tgz", - "integrity": "sha512-dLZVNhM7wSgVUFsgVYgI5hb5Z/9PUkT46pk/SHrSmUqfx6YDvoV4YcPtaiRqviPpEGGiRtdQMEadyOKIRqulUQ==", + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/client-sso": "3.974.0", - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/token-providers": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.972.1.tgz", - "integrity": "sha512-YMDeYgi0u687Ay0dAq/pFPKuijrlKTgsaB/UATbxCs/FzZfMiG4If5ksywHmmW7MiYUF8VVv+uou3TczvLrN4w==", + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.972.1.tgz", - "integrity": "sha512-/R82lXLPmZ9JaUGSUdKtBp2k/5xQxvBT3zZWyKiBOhyulFotlfvdlrO8TnqstBimsl4lYEYySDL+W6ldFh6ALg==", + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/middleware-logger": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.972.1.tgz", - "integrity": "sha512-JGgFl6cHg9G2FHu4lyFIzmFN8KESBiRr84gLC3Aeni0Gt1nKm+KxWLBuha/RPcXxJygGXCcMM4AykkIwxor8RA==", + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { - "node": ">=20.0.0" + "node": ">=6.9.0" } }, - "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.972.1.tgz", - "integrity": "sha512-taGzNRe8vPHjnliqXIHp9kBgIemLE/xCaRTMH1NH0cncHeaPcjxtnCroAAM9aOlPuKvBe2CpZESyvM1+D8oI7Q==", + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@aws/lambda-invoke-store": "^0.2.2", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.972.0.tgz", - "integrity": "sha512-0bcKFXWx+NZ7tIlOo7KjQ+O2rydiHdIQahrq+fN6k9Osky29v17guy68urUKfhTobR6iY6KvxkroFWaFtTgS5w==", + "node_modules/@csstools/color-helpers": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", + "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@aws-sdk/util-arn-parser": "3.972.0", - "@smithy/core": "^3.20.6", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-stream": "^4.5.10", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", "engines": { - "node": ">=20.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@aws-sdk/core": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.972.0.tgz", - "integrity": "sha512-nEeUW2M9F+xdIaD98F5MBcQ4ITtykj3yKbgFZ6J0JtL3bq+Z90szQ6Yy8H/BLPYXTs3V4n9ifnBo8cprRDiE6A==", + "node_modules/@csstools/css-calc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", + "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.972.0", - "@aws-sdk/xml-builder": "3.972.0", - "@smithy/core": "^3.20.6", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/signature-v4": "^5.3.8", - "@smithy/smithy-client": "^4.10.8", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" } }, - "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@aws-sdk/types": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz", - "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==", + "node_modules/@csstools/css-color-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", + "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", "dev": true, - "license": "Apache-2.0", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@csstools/color-helpers": "^5.1.0", + "@csstools/css-calc": "^2.1.4" }, "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/middleware-sdk-s3/node_modules/@aws-sdk/xml-builder": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.0.tgz", - "integrity": "sha512-POaGMcXnozzqBUyJM3HLUZ9GR6OKJWPGJEmhtTnxZXt8B6JcJ/6K3xRJ5H/j8oovVLz8Wg6vFxAHv8lvuASxMg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "fast-xml-parser": "5.2.5", - "tslib": "^2.6.2" + "node": ">=18" }, - "engines": { - "node": ">=20.0.0" + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" } }, - "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.972.2", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.972.2.tgz", - "integrity": "sha512-d+Exq074wy0X6wvShg/kmZVtkah+28vMuqCtuY3cydg8LUZOJBtbAolCpEJizSyb8mJJZF9BjWaTANXL4OYnkg==", + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/core": "^3.973.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@smithy/core": "^3.21.1", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" } }, - "node_modules/@aws-sdk/nested-clients": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/nested-clients/-/nested-clients-3.974.0.tgz", - "integrity": "sha512-k3dwdo/vOiHMJc9gMnkPl1BA5aQfTrZbz+8fiDkWrPagqAioZgmo5oiaOaeX0grObfJQKDtcpPFR4iWf8cgl8Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-crypto/sha256-browser": "5.2.0", - "@aws-crypto/sha256-js": "5.2.0", - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/middleware-host-header": "^3.972.1", - "@aws-sdk/middleware-logger": "^3.972.1", - "@aws-sdk/middleware-recursion-detection": "^3.972.1", - "@aws-sdk/middleware-user-agent": "^3.972.1", - "@aws-sdk/region-config-resolver": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@aws-sdk/util-endpoints": "3.972.0", - "@aws-sdk/util-user-agent-browser": "^3.972.1", - "@aws-sdk/util-user-agent-node": "^3.972.1", - "@smithy/config-resolver": "^4.4.6", - "@smithy/core": "^3.21.0", - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/hash-node": "^4.2.8", - "@smithy/invalid-dependency": "^4.2.8", - "@smithy/middleware-content-length": "^4.2.8", - "@smithy/middleware-endpoint": "^4.4.10", - "@smithy/middleware-retry": "^4.4.26", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/smithy-client": "^4.10.11", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-body-length-node": "^4.2.1", - "@smithy/util-defaults-mode-browser": "^4.3.25", - "@smithy/util-defaults-mode-node": "^4.2.28", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.972.1.tgz", - "integrity": "sha512-voIY8RORpxLAEgEkYaTFnkaIuRwVBEc+RjVZYcSSllPV+ZEKAacai6kNhJeE3D70Le+JCfvRb52tng/AVHY+jQ==", + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.26.tgz", + "integrity": "sha512-6boXK0KkzT5u5xOgF6TKB+CLq9SOpEGmkZw0g5n9/7yg85wab3UzSxB8TxhLJ31L4SGJ6BCFRw/iftTha1CJXA==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/config-resolver": "^4.4.6", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" - } + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0" }, - "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.972.0.tgz", - "integrity": "sha512-2udiRijmjpN81Pvajje4TsjbXDZNP6K9bYUanBYH8hXa/tZG5qfGCySD+TyX0sgDxCQmEDMg3LaQdfjNHBDEgQ==", + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.972.0", - "@aws-sdk/types": "3.972.0", - "@smithy/protocol-http": "^5.3.8", - "@smithy/signature-v4": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/signature-v4-multi-region/node_modules/@aws-sdk/types": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz", - "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==", + "node_modules/@emnapi/core": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", + "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" } }, - "node_modules/@aws-sdk/token-providers": { - "version": "3.974.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.974.0.tgz", - "integrity": "sha512-cBykL0LiccKIgNhGWvQRTPvsBLPZxnmJU3pYxG538jpFX8lQtrCy1L7mmIHNEdxIdIGEPgAEHF8/JQxgBToqUQ==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "license": "MIT", + "optional": true, "dependencies": { - "@aws-sdk/core": "^3.973.0", - "@aws-sdk/nested-clients": "3.974.0", - "@aws-sdk/types": "^3.973.0", - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "tslib": "^2.4.0" } }, - "node_modules/@aws-sdk/types": { - "version": "3.973.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.973.0.tgz", - "integrity": "sha512-jYIdB7a7jhRTvyb378nsjyvJh1Si+zVduJ6urMNGpz8RjkmHZ+9vM2H07XaIB2Cfq0GhJRZYOfUCH8uqQhqBkQ==", + "node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", + "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=20.0.0" + "tslib": "^2.4.0" } }, - "node_modules/@aws-sdk/util-arn-parser": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.0.tgz", - "integrity": "sha512-RM5Mmo/KJ593iMSrALlHEOcc9YOIyOsDmS5x2NLOMdEmzv1o00fcpAkCQ02IGu1eFneBFT7uX0Mpag0HI+Cz2g==", + "node_modules/@esbuild/aix-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", + "cpu": [ + "ppc64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], "engines": { - "node": ">=20.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/util-endpoints": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.972.0.tgz", - "integrity": "sha512-6JHsl1V/a1ZW8D8AFfd4R52fwZPnZ5H4U6DS8m/bWT8qad72NvbOFAC7U2cDtFs2TShqUO3TEiX/EJibtY3ijg==", + "node_modules/@esbuild/android-arm": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", + "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", + "cpu": [ + "arm" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "3.972.0", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-endpoints": "^3.2.8", - "tslib": "^2.6.2" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=20.0.0" + "node": ">=12" } }, - "node_modules/@aws-sdk/util-endpoints/node_modules/@aws-sdk/types": { - "version": "3.972.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.972.0.tgz", - "integrity": "sha512-U7xBIbLSetONxb2bNzHyDgND3oKGoIfmknrEVnoEU4GUSs+0augUOIn9DIWGUO2ETcRFdsRUnmx9KhPT9Ojbug==", + "node_modules/@esbuild/android-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=20.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/util-locate-window": { - "version": "3.965.3", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.965.3.tgz", - "integrity": "sha512-FNUqAjlKAGA7GM05kywE99q8wiPHPZqrzhq3wXRga6PRD6A0kzT85Pb0AzYBVTBRpSrKyyr6M92Y6bnSBVp2BA==", + "node_modules/@esbuild/android-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=20.0.0" + "node": ">=18" } }, - "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.972.1.tgz", - "integrity": "sha512-IgF55NFmJX8d9Wql9M0nEpk2eYbuD8G4781FN4/fFgwTXBn86DvlZJuRWDCMcMqZymnBVX7HW9r+3r9ylqfW0w==", + "node_modules/@esbuild/darwin-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/types": "^3.973.0", - "@smithy/types": "^4.12.0", - "bowser": "^2.11.0", - "tslib": "^2.6.2" + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" } }, - "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.972.1.tgz", - "integrity": "sha512-oIs4JFcADzoZ0c915R83XvK2HltWupxNsXUIuZse2rgk7b97zTpkxaqXiH0h9ylh31qtgo/t8hp4tIqcsMrEbQ==", + "node_modules/@esbuild/darwin-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@aws-sdk/middleware-user-agent": "^3.972.1", - "@aws-sdk/types": "^3.973.0", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } + "node": ">=18" } }, - "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.1", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.1.tgz", - "integrity": "sha512-6zZGlPOqn7Xb+25MAXGb1JhgvaC5HjZj6GzszuVrnEgbhvzBRFGKYemuHBV4bho+dtqeYKPgaZUv7/e80hIGNg==", + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "fast-xml-parser": "5.2.5", - "tslib": "^2.6.2" - }, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=20.0.0" + "node": ">=18" } }, - "node_modules/@aws/lambda-invoke-store": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@aws/lambda-invoke-store/-/lambda-invoke-store-0.2.3.tgz", - "integrity": "sha512-oLvsaPMTBejkkmHhjf09xTgk71mOqyr/409NKhRIL08If7AhVfUsJhVsx386uJaqNd42v9kWamQ9lFbkoC2dYw==", + "node_modules/@esbuild/freebsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=18.0.0" + "node": ">=18" } }, - "node_modules/@babel/code-frame": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", - "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", - "dev": true, + "node_modules/@esbuild/linux-arm": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", + "cpu": [ + "arm" + ], + "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@babel/compat-data": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", - "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "node_modules/@esbuild/linux-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@babel/core": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", - "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "node_modules/@esbuild/linux-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" + "node": ">=18" } }, - "node_modules/@babel/generator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", - "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "node_modules/@esbuild/linux-loong64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", + "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", + "cpu": [ + "loong64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" + "node": ">=12" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "node_modules/@esbuild/linux-mips64el": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", + "cpu": [ + "mips64el" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "node_modules/@esbuild/linux-ppc64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "node_modules/@esbuild/linux-riscv64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", + "cpu": [ + "riscv64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "node_modules/@esbuild/linux-s390x": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", + "cpu": [ + "s390x" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=18" } }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "node_modules/@esbuild/linux-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "node_modules/@esbuild/netbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@babel/parser": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", - "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.6" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", - "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "node_modules/@esbuild/openbsd-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - }, + "optional": true, + "os": [ + "openbsd" + ], "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@babel/traverse": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", - "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.6", - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6", - "debug": "^4.3.1" - }, + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@babel/types": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", - "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "node_modules/@esbuild/sunos-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@bcoe/v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", - "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "node_modules/@esbuild/win32-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { "node": ">=18" } }, - "node_modules/@csstools/color-helpers": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz", - "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==", + "node_modules/@esbuild/win32-ia32": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "cpu": [ + "ia32" + ], "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" ], - "license": "MIT-0", "engines": { "node": ">=18" } }, - "node_modules/@csstools/css-calc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz", - "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } + "node_modules/@esbuild/win32-x64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "cpu": [ + "x64" ], + "dev": true, "license": "MIT", + "optional": true, + "os": [ + "win32" + ], "engines": { "node": ">=18" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" } }, - "node_modules/@csstools/css-color-parser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz", - "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], "license": "MIT", "dependencies": { - "@csstools/color-helpers": "^5.1.0", - "@csstools/css-calc": "^2.1.4" + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": ">=18" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.5", - "@csstools/css-tokenizer": "^3.0.4" + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", - "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": ">=18" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, - "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.4" + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.0.26", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.26.tgz", - "integrity": "sha512-6boXK0KkzT5u5xOgF6TKB+CLq9SOpEGmkZw0g5n9/7yg85wab3UzSxB8TxhLJ31L4SGJ6BCFRw/iftTha1CJXA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0" - }, - "node_modules/@csstools/css-tokenizer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", - "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], "license": "MIT", "engines": { - "node": ">=18" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@emnapi/core": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", - "integrity": "sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==", + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", "dev": true, - "license": "MIT", - "optional": true, + "license": "Apache-2.0", "dependencies": { - "@emnapi/wasi-threads": "1.1.0", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", - "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz", - "integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==", + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", - "optional": true, "dependencies": { - "tslib": "^2.4.0" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", - "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", - "cpu": [ - "ppc64" - ], + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, "engines": { - "node": ">=18" + "node": "*" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", - "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", - "cpu": [ - "arm" - ], + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, "engines": { - "node": ">=12" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", - "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", - "cpu": [ - "arm64" - ], + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, "engines": { - "node": ">=18" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", - "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", - "cpu": [ - "x64" - ], + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "android" - ], + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, "engines": { - "node": ">=18" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", - "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", - "cpu": [ - "arm64" - ], + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", - "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", - "cpu": [ - "x64" - ], + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, "engines": { - "node": ">=18" + "node": "*" } }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", - "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", - "cpu": [ - "arm64" - ], + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], "engines": { - "node": ">=18" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", - "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", - "cpu": [ - "x64" - ], + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], + "license": "Apache-2.0", "engines": { - "node": ">=18" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@esbuild/linux-arm": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", - "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", - "cpu": [ - "arm" - ], + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, "engines": { - "node": ">=18" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", - "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", - "cpu": [ - "arm64" - ], + "node_modules/@exodus/bytes": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.10.0.tgz", + "integrity": "sha512-tf8YdcbirXdPnJ+Nd4UN1EXnz+IP2DI45YVEr3vvzcVTOyrApkmIB4zvOQVd3XPr7RXnfBtAx+PXImXOIU0Ajg==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } } }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", - "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", - "cpu": [ - "ia32" - ], - "dev": true, + "node_modules/@formatjs/ecma402-abstract": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-3.1.0.tgz", + "integrity": "sha512-CjP1sUzM7XiQW6YluDreN+dMvcKZysO/J4ikvuDjDyd6nSOoSqAK9gvD1s75ZFaJVXtYOsz+y3CUXPZ1sKxcxw==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@formatjs/fast-memoize": "3.1.0", + "@formatjs/intl-localematcher": "0.8.0", + "decimal.js": "^10.6.0", + "tslib": "^2.8.1" } }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.15.18", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", - "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", - "cpu": [ - "loong64" - ], - "dev": true, + "node_modules/@formatjs/ecma402-abstract/node_modules/@formatjs/intl-localematcher": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.0.tgz", + "integrity": "sha512-zgMYWdUlmEZpX2Io+v3LHrfq9xZ6khpQVf9UAw2xYWhGerGgI9XgH1HvL/A34jWiruUJpYlP5pk4g8nIcaDrXQ==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" + "dependencies": { + "@formatjs/fast-memoize": "3.1.0", + "tslib": "^2.8.1" } }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", - "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", - "cpu": [ - "mips64el" - ], - "dev": true, + "node_modules/@formatjs/fast-memoize": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.0.tgz", + "integrity": "sha512-b5mvSWCI+XVKiz5WhnBCY3RJ4ZwfjAidU0yVlKa3d3MSgKmH1hC3tBGEAtYyN5mqL7N0G5x0BOUYyO8CEupWgg==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" + "dependencies": { + "tslib": "^2.8.1" } }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", - "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", - "cpu": [ - "ppc64" - ], - "dev": true, + "node_modules/@formatjs/icu-messageformat-parser": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.0.tgz", + "integrity": "sha512-Q01XuvtbDVCJQsG/E2MSfMZ+UdUoZV8v4Aex8tTH44SqKJZCeu5LjuclaKFUS0o1YoXndfEinJen5k1T1GR1vg==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "dependencies": { + "@formatjs/ecma402-abstract": "3.1.0", + "@formatjs/icu-skeleton-parser": "2.1.0", + "tslib": "^2.8.1" + } + }, + "node_modules/@formatjs/icu-skeleton-parser": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.0.tgz", + "integrity": "sha512-wNer4imHDFBVAJnMb2OGoSyM4wL/uuLnuo5mrenliqkDaNjRbG4jzlJcwTTDEBhai8iCjnzUsE7xwNJC29SfWw==", + "license": "MIT", + "dependencies": { + "@formatjs/ecma402-abstract": "3.1.0", + "tslib": "^2.8.1" + } + }, + "node_modules/@formatjs/intl-localematcher": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.10.tgz", + "integrity": "sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==", + "license": "MIT", + "dependencies": { + "tslib": "2" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=18" + "node": ">=18.18.0" } }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", - "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", - "cpu": [ - "riscv64" - ], + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, "engines": { - "node": ">=18" + "node": ">=18.18.0" } }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", - "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", - "cpu": [ - "s390x" - ], + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "Apache-2.0", "engines": { - "node": ">=18" + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@esbuild/linux-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", - "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", - "cpu": [ - "x64" - ], + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", "license": "MIT", "optional": true, - "os": [ - "linux" - ], "engines": { "node": ">=18" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", - "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", "cpu": [ "arm64" ], - "dev": true, - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ - "netbsd" + "darwin" ], "engines": { - "node": ">=18" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" } }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", - "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", "cpu": [ "x64" ], - "dev": true, - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ - "netbsd" + "darwin" ], "engines": { - "node": ">=18" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" } }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", - "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", "cpu": [ "arm64" ], - "dev": true, - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "openbsd" + "darwin" ], - "engines": { - "node": ">=18" + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", - "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", "cpu": [ "x64" ], - "dev": true, - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "openbsd" + "darwin" ], - "engines": { - "node": ">=18" + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", - "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", "cpu": [ - "arm64" + "arm" ], - "dev": true, - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "openharmony" + "linux" ], - "engines": { - "node": ">=18" + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", - "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", "cpu": [ "x64" ], - "dev": true, - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "sunos" + "linux" ], - "engines": { - "node": ">=18" + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", - "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", "cpu": [ "arm64" ], - "dev": true, - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "win32" + "linux" ], - "engines": { - "node": ">=18" + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", - "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", "cpu": [ - "ia32" + "x64" ], - "dev": true, - "license": "MIT", + "license": "LGPL-3.0-or-later", "optional": true, "os": [ - "win32" + "linux" ], - "engines": { - "node": ">=18" + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@esbuild/win32-x64": { - "version": "0.27.2", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", - "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", "cpu": [ - "x64" + "arm" ], - "dev": true, - "license": "MIT", + "license": "Apache-2.0", "optional": true, "os": [ - "win32" + "linux" ], "engines": { - "node": ">=18" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://opencollective.com/libvips" }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" } }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, - "license": "MIT", + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" } }, - "node_modules/@eslint/config-array": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", - "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", - "dev": true, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" } }, - "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "*" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" } }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", - "dev": true, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" } }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", - "dev": true, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" } }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", - "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", - "dev": true, - "license": "MIT", + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" + "@emnapi/runtime": "^1.7.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://opencollective.com/libvips" } }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "*" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, - "node_modules/@eslint/js": { - "version": "9.39.2", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", - "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", - "dev": true, - "license": "MIT", + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" }, "funding": { - "url": "https://eslint.org/donate" + "url": "https://opencollective.com/libvips" } }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@exodus/bytes": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.10.0.tgz", - "integrity": "sha512-tf8YdcbirXdPnJ+Nd4UN1EXnz+IP2DI45YVEr3vvzcVTOyrApkmIB4zvOQVd3XPr7RXnfBtAx+PXImXOIU0Ajg==", + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "dev": true, "license": "MIT", "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, - "peerDependencies": { - "@noble/hashes": "^1.8.0 || ^2.0.0" - }, - "peerDependenciesMeta": { - "@noble/hashes": { - "optional": true - } + "node": ">=6.0.0" } }, - "node_modules/@formatjs/ecma402-abstract": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-2.3.6.tgz", - "integrity": "sha512-HJnTFeRM2kVFVr5gr5kH1XP6K0JcJtE7Lzvtr3FS/so5f1kpsqqqxy5JF+FRaO6H2qmcMfAUIox7AJteieRtVw==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, "license": "MIT", "dependencies": { - "@formatjs/fast-memoize": "2.2.7", - "@formatjs/intl-localematcher": "0.6.2", - "decimal.js": "^10.4.3", - "tslib": "^2.8.0" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@formatjs/ecma402-abstract/node_modules/@formatjs/intl-localematcher": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.6.2.tgz", - "integrity": "sha512-XOMO2Hupl0wdd172Y06h6kLpBz6Dv+J4okPLl4LPtzbr8f66WbIoy4ev98EBuZ6ZK4h5ydTN6XneT4QVpD7cdA==", - "license": "MIT", - "dependencies": { - "tslib": "^2.8.0" - } - }, - "node_modules/@formatjs/fast-memoize": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.7.tgz", - "integrity": "sha512-Yabmi9nSvyOMrlSeGGWDiH7rf3a7sIwplbvo/dlz9WCIjzIQAfy1RMf4S0X3yG724n5Ghu2GmEl5NJIV6O9sZQ==", - "license": "MIT", - "dependencies": { - "tslib": "^2.8.0" - } - }, - "node_modules/@formatjs/icu-messageformat-parser": { - "version": "2.11.4", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-2.11.4.tgz", - "integrity": "sha512-7kR78cRrPNB4fjGFZg3Rmj5aah8rQj9KPzuLsmcSn4ipLXQvC04keycTI1F7kJYDwIXtT2+7IDEto842CfZBtw==", + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@formatjs/ecma402-abstract": "2.3.6", - "@formatjs/icu-skeleton-parser": "1.8.16", - "tslib": "^2.8.0" + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" } }, - "node_modules/@formatjs/icu-skeleton-parser": { - "version": "1.8.16", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-1.8.16.tgz", - "integrity": "sha512-H13E9Xl+PxBd8D5/6TVUluSpxGNvFSlN/b3coUp0e0JpuWXXnQDiavIpY3NnvSp4xhEMoXyyBvVfdFX8jglOHQ==", + "node_modules/@neondatabase/serverless": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@neondatabase/serverless/-/serverless-1.0.2.tgz", + "integrity": "sha512-I5sbpSIAHiB+b6UttofhrN/UJXII+4tZPAq1qugzwCwLIL8EZLV7F/JyHUrEIiGgQpEXzpnjlJ+zwcEhheGvCw==", "license": "MIT", "dependencies": { - "@formatjs/ecma402-abstract": "2.3.6", - "tslib": "^2.8.0" + "@types/node": "^22.15.30", + "@types/pg": "^8.8.0" + }, + "engines": { + "node": ">=19.0.0" } }, - "node_modules/@formatjs/intl-localematcher": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.10.tgz", - "integrity": "sha512-af3qATX+m4Rnd9+wHcjJ4w2ijq+rAVP3CCinJQvFv1kgSu1W6jypUmvleJxcewdxmutM8dmIRZFxO/IQBZmP2Q==", + "node_modules/@neondatabase/serverless/node_modules/@types/node": { + "version": "22.19.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz", + "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==", "license": "MIT", "dependencies": { - "tslib": "2" - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" + "undici-types": "~6.21.0" } }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "node_modules/@netlify/plugin-nextjs": { + "version": "5.15.7", + "resolved": "https://registry.npmjs.org/@netlify/plugin-nextjs/-/plugin-nextjs-5.15.7.tgz", + "integrity": "sha512-tSlGTwOR/dFZMrjhCxynlwc/NkOwY+bc0gurOranLtwASH5wat2WqZaPkyIdfVdNd/zNpmGxgbkH0uppg1Lsxg==", "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" - }, + "license": "MIT", "engines": { - "node": ">=18.18.0" + "node": ">=18.0.0" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } + "node_modules/@next/env": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.6.tgz", + "integrity": "sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==", + "license": "MIT" }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "node_modules/@next/eslint-plugin-next": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.0.1.tgz", + "integrity": "sha512-g4Cqmv/gyFEXNeVB2HkqDlYKfy+YrlM2k8AVIO/YQVEPfhVruH1VA99uT1zELLnPLIeOnx8IZ6Ddso0asfTIdw==", "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@img/colour": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", - "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", "license": "MIT", - "optional": true, - "engines": { - "node": ">=18" + "dependencies": { + "fast-glob": "3.3.1" } }, - "node_modules/@img/sharp-darwin-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", - "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "node_modules/@next/swc-darwin-arm64": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.6.tgz", + "integrity": "sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==", "cpu": [ "arm64" ], - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.2.4" + "node": ">= 10" } }, - "node_modules/@img/sharp-darwin-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", - "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "node_modules/@next/swc-darwin-x64": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.6.tgz", + "integrity": "sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==", "cpu": [ "x64" ], - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ "darwin" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.2.4" + "node": ">= 10" } }, - "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", - "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.6.tgz", + "integrity": "sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==", "cpu": [ "arm64" ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", - "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", - "cpu": [ - "x64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "darwin" - ], - "funding": { - "url": "https://opencollective.com/libvips" - } - }, - "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", - "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", - "cpu": [ - "arm" - ], - "license": "LGPL-3.0-or-later", + "license": "MIT", "optional": true, "os": [ "linux" ], - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">= 10" } }, - "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", - "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.6.tgz", + "integrity": "sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==", "cpu": [ "arm64" ], - "license": "LGPL-3.0-or-later", + "license": "MIT", "optional": true, "os": [ "linux" ], - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">= 10" } }, - "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", - "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.6.tgz", + "integrity": "sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==", "cpu": [ - "ppc64" + "x64" ], - "license": "LGPL-3.0-or-later", + "license": "MIT", "optional": true, "os": [ "linux" ], - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">= 10" } }, - "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", - "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.6.tgz", + "integrity": "sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==", "cpu": [ - "riscv64" + "x64" ], - "license": "LGPL-3.0-or-later", + "license": "MIT", "optional": true, "os": [ "linux" ], - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">= 10" } }, - "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", - "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.6.tgz", + "integrity": "sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==", "cpu": [ - "s390x" + "arm64" ], - "license": "LGPL-3.0-or-later", + "license": "MIT", "optional": true, "os": [ - "linux" + "win32" ], - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">= 10" } }, - "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", - "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.6.tgz", + "integrity": "sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==", "cpu": [ "x64" ], - "license": "LGPL-3.0-or-later", + "license": "MIT", "optional": true, "os": [ - "linux" + "win32" ], - "funding": { - "url": "https://opencollective.com/libvips" + "engines": { + "node": ">= 10" } }, - "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", - "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", - "cpu": [ - "arm64" - ], - "license": "LGPL-3.0-or-later", - "optional": true, - "os": [ - "linux" - ], - "funding": { - "url": "https://opencollective.com/libvips" + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" } }, - "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", - "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", + "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.3", + "is-glob": "^4.0.3", + "node-addon-api": "^7.0.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.6", + "@parcel/watcher-darwin-arm64": "2.5.6", + "@parcel/watcher-darwin-x64": "2.5.6", + "@parcel/watcher-freebsd-x64": "2.5.6", + "@parcel/watcher-linux-arm-glibc": "2.5.6", + "@parcel/watcher-linux-arm-musl": "2.5.6", + "@parcel/watcher-linux-arm64-glibc": "2.5.6", + "@parcel/watcher-linux-arm64-musl": "2.5.6", + "@parcel/watcher-linux-x64-glibc": "2.5.6", + "@parcel/watcher-linux-x64-musl": "2.5.6", + "@parcel/watcher-win32-arm64": "2.5.6", + "@parcel/watcher-win32-ia32": "2.5.6", + "@parcel/watcher-win32-x64": "2.5.6" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", + "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", "cpu": [ - "x64" + "arm64" ], - "license": "LGPL-3.0-or-later", + "license": "MIT", "optional": true, "os": [ - "linux" + "android" ], + "engines": { + "node": ">= 10.0.0" + }, "funding": { - "url": "https://opencollective.com/libvips" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@img/sharp-linux-arm": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", - "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", + "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", "cpu": [ - "arm" + "arm64" ], - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ - "linux" + "darwin" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">= 10.0.0" }, "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.2.4" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@img/sharp-linux-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", - "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", + "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", "cpu": [ - "arm64" + "x64" ], - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ - "linux" + "darwin" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">= 10.0.0" }, "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.2.4" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@img/sharp-linux-ppc64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", - "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", + "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", "cpu": [ - "ppc64" + "x64" ], - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ - "linux" + "freebsd" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">= 10.0.0" }, "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.2.4" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@img/sharp-linux-riscv64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", - "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", + "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", "cpu": [ - "riscv64" + "arm" ], - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">= 10.0.0" }, "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.2.4" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@img/sharp-linux-s390x": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", - "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", + "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", "cpu": [ - "s390x" + "arm" ], - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">= 10.0.0" }, "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.2.4" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@img/sharp-linux-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", - "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", + "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", "cpu": [ - "x64" + "arm64" ], - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">= 10.0.0" }, "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.2.4" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", - "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "node_modules/@parcel/watcher-linux-arm64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", + "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", "cpu": [ "arm64" ], - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">= 10.0.0" }, "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", - "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "node_modules/@parcel/watcher-linux-x64-glibc": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", + "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", "cpu": [ "x64" ], - "license": "Apache-2.0", + "license": "MIT", "optional": true, "os": [ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">= 10.0.0" }, "funding": { - "url": "https://opencollective.com/libvips" - }, - "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@img/sharp-wasm32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", - "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "node_modules/@parcel/watcher-linux-x64-musl": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", + "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", "cpu": [ - "wasm32" + "x64" ], - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "license": "MIT", "optional": true, - "dependencies": { - "@emnapi/runtime": "^1.7.0" - }, + "os": [ + "linux" + ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">= 10.0.0" }, "funding": { - "url": "https://opencollective.com/libvips" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@img/sharp-win32-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", - "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", + "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", "cpu": [ "arm64" ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">= 10.0.0" }, "funding": { - "url": "https://opencollective.com/libvips" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@img/sharp-win32-ia32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", - "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", + "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", "cpu": [ "ia32" ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">= 10.0.0" }, "funding": { - "url": "https://opencollective.com/libvips" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@img/sharp-win32-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", - "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.6", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", + "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", "cpu": [ "x64" ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", + "license": "MIT", "optional": true, "os": [ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">= 10.0.0" }, "funding": { - "url": "https://opencollective.com/libvips" + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, + "node_modules/@parcel/watcher/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, + "node_modules/@portabletext/react": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@portabletext/react/-/react-5.0.0.tgz", + "integrity": "sha512-ZEYhjsiUn2Dvrhyao3qAvi6C/re9ZBt2atp14dtwWtPruMNgp1uMf3p+URf0pEEhu+rMEh9JeK0A8FgNejEWCg==", "license": "MIT", "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" + "@portabletext/toolkit": "^4.0.0", + "@portabletext/types": "^3.0.0" + }, + "engines": { + "node": ">=20.19 <22 || >=22.12" + }, + "peerDependencies": { + "react": "^18.2 || ^19" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, + "node_modules/@portabletext/toolkit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@portabletext/toolkit/-/toolkit-4.0.0.tgz", + "integrity": "sha512-Jj/QIy3vzZCNcxiUGM7KjGhUhyVjch+9pOzotWRARPNe07R6nbF/cRsKL70q5Xizf+6PVtFYwks4CSXKInC+wg==", "license": "MIT", + "dependencies": { + "@portabletext/types": "^3.0.0" + }, "engines": { - "node": ">=6.0.0" + "node": ">=20.19 <22 || >=22.12" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, + "node_modules/@portabletext/types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@portabletext/types/-/types-3.0.1.tgz", + "integrity": "sha512-Axessc2mZ6tjbD2qIZdTZIEfBtzJBe/1pzKF372cUar6ccUCfgXU4vaKgIbqB33mB6rI75omx9rrm+SgR4rIAA==", "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "engines": { + "node": ">=20.19 <22 || >=22.12" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.12", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", - "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", - "dev": true, + "node_modules/@radix-ui/primitive": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", + "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", + "license": "MIT" + }, + "node_modules/@radix-ui/react-accordion": { + "version": "1.2.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz", + "integrity": "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==", "license": "MIT", - "optional": true, "dependencies": { - "@emnapi/core": "^1.4.3", - "@emnapi/runtime": "^1.4.3", - "@tybys/wasm-util": "^0.10.0" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collapsible": "1.1.12", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/@neondatabase/serverless": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@neondatabase/serverless/-/serverless-1.0.2.tgz", - "integrity": "sha512-I5sbpSIAHiB+b6UttofhrN/UJXII+4tZPAq1qugzwCwLIL8EZLV7F/JyHUrEIiGgQpEXzpnjlJ+zwcEhheGvCw==", + "node_modules/@radix-ui/react-collapsible": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", + "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==", "license": "MIT", "dependencies": { - "@types/node": "^22.15.30", - "@types/pg": "^8.8.0" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1" }, - "engines": { - "node": ">=19.0.0" + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/@neondatabase/serverless/node_modules/@types/node": { - "version": "22.19.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz", - "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==", + "node_modules/@radix-ui/react-collection": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", + "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/@netlify/plugin-nextjs": { - "version": "5.15.6", - "resolved": "https://registry.npmjs.org/@netlify/plugin-nextjs/-/plugin-nextjs-5.15.6.tgz", - "integrity": "sha512-SZa4a0nBllKNaVV19kIFBuZ6p8Lt3Hagrw5gxysm2qs5AMQJRQfJq6av+rnRwCk8w/zNn2FShF4Fckk4+L1Hzg==", - "dev": true, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", "license": "MIT", - "engines": { - "node": ">=18.0.0" - } - }, - "node_modules/@next/env": { - "version": "16.1.4", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.1.4.tgz", - "integrity": "sha512-gkrXnZyxPUy0Gg6SrPQPccbNVLSP3vmW8LU5dwEttEEC1RwDivk8w4O+sZIjFvPrSICXyhQDCG+y3VmjlJf+9A==", - "license": "MIT" - }, - "node_modules/@next/eslint-plugin-next": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.0.1.tgz", - "integrity": "sha512-g4Cqmv/gyFEXNeVB2HkqDlYKfy+YrlM2k8AVIO/YQVEPfhVruH1VA99uT1zELLnPLIeOnx8IZ6Ddso0asfTIdw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-glob": "3.3.1" - } - }, - "node_modules/@next/swc-darwin-arm64": { - "version": "16.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.1.4.tgz", - "integrity": "sha512-T8atLKuvk13XQUdVLCv1ZzMPgLPW0+DWWbHSQXs0/3TjPrKNxTmUIhOEaoEyl3Z82k8h/gEtqyuoZGv6+Ugawg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-darwin-x64": { - "version": "16.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.1.4.tgz", - "integrity": "sha512-AKC/qVjUGUQDSPI6gESTx0xOnOPQ5gttogNS3o6bA83yiaSZJek0Am5yXy82F1KcZCx3DdOwdGPZpQCluonuxg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.1.4.tgz", - "integrity": "sha512-POQ65+pnYOkZNdngWfMEt7r53bzWiKkVNbjpmCt1Zb3V6lxJNXSsjwRuTQ8P/kguxDC8LRkqaL3vvsFrce4dMQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.1.4.tgz", - "integrity": "sha512-3Wm0zGYVCs6qDFAiSSDL+Z+r46EdtCv/2l+UlIdMbAq9hPJBvGu/rZOeuvCaIUjbArkmXac8HnTyQPJFzFWA0Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.1.4.tgz", - "integrity": "sha512-lWAYAezFinaJiD5Gv8HDidtsZdT3CDaCeqoPoJjeB57OqzvMajpIhlZFce5sCAH6VuX4mdkxCRqecCJFwfm2nQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-linux-x64-musl": { - "version": "16.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.1.4.tgz", - "integrity": "sha512-fHaIpT7x4gA6VQbdEpYUXRGyge/YbRrkG6DXM60XiBqDM2g2NcrsQaIuj375egnGFkJow4RHacgBOEsHfGbiUw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.1.4.tgz", - "integrity": "sha512-MCrXxrTSE7jPN1NyXJr39E+aNFBrQZtO154LoCz7n99FuKqJDekgxipoodLNWdQP7/DZ5tKMc/efybx1l159hw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.1.4", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.1.4.tgz", - "integrity": "sha512-JSVlm9MDhmTXw/sO2PE/MRj+G6XOSMZB+BcZ0a7d6KwVFZVpkHcb2okyoYFBaco6LeiL53BBklRlOrDDbOeE5w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nolyfill/is-core-module": { - "version": "1.0.39", - "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", - "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.4.0" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@parcel/watcher": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", - "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", - "hasInstallScript": true, + "node_modules/@radix-ui/react-context": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", + "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", "license": "MIT", - "dependencies": { - "detect-libc": "^2.0.3", - "is-glob": "^4.0.3", - "node-addon-api": "^7.0.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, - "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.5.6", - "@parcel/watcher-darwin-arm64": "2.5.6", - "@parcel/watcher-darwin-x64": "2.5.6", - "@parcel/watcher-freebsd-x64": "2.5.6", - "@parcel/watcher-linux-arm-glibc": "2.5.6", - "@parcel/watcher-linux-arm-musl": "2.5.6", - "@parcel/watcher-linux-arm64-glibc": "2.5.6", - "@parcel/watcher-linux-arm64-musl": "2.5.6", - "@parcel/watcher-linux-x64-glibc": "2.5.6", - "@parcel/watcher-linux-x64-musl": "2.5.6", - "@parcel/watcher-win32-arm64": "2.5.6", - "@parcel/watcher-win32-ia32": "2.5.6", - "@parcel/watcher-win32-x64": "2.5.6" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@parcel/watcher-android-arm64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz", - "integrity": "sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A==", - "cpu": [ - "arm64" - ], + "node_modules/@radix-ui/react-direction": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", + "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10.0.0" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@parcel/watcher-darwin-arm64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz", - "integrity": "sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-darwin-x64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz", - "integrity": "sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-freebsd-x64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz", - "integrity": "sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-glibc": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz", - "integrity": "sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm-musl": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz", - "integrity": "sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-glibc": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz", - "integrity": "sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-arm64-musl": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz", - "integrity": "sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-glibc": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz", - "integrity": "sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-linux-x64-musl": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz", - "integrity": "sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-arm64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz", - "integrity": "sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-ia32": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz", - "integrity": "sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher-win32-x64": { - "version": "2.5.6", - "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz", - "integrity": "sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/@parcel/watcher/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@portabletext/react": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@portabletext/react/-/react-5.0.0.tgz", - "integrity": "sha512-ZEYhjsiUn2Dvrhyao3qAvi6C/re9ZBt2atp14dtwWtPruMNgp1uMf3p+URf0pEEhu+rMEh9JeK0A8FgNejEWCg==", - "license": "MIT", - "dependencies": { - "@portabletext/toolkit": "^4.0.0", - "@portabletext/types": "^3.0.0" - }, - "engines": { - "node": ">=20.19 <22 || >=22.12" - }, - "peerDependencies": { - "react": "^18.2 || ^19" - } - }, - "node_modules/@portabletext/toolkit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@portabletext/toolkit/-/toolkit-4.0.0.tgz", - "integrity": "sha512-Jj/QIy3vzZCNcxiUGM7KjGhUhyVjch+9pOzotWRARPNe07R6nbF/cRsKL70q5Xizf+6PVtFYwks4CSXKInC+wg==", - "license": "MIT", - "dependencies": { - "@portabletext/types": "^3.0.0" - }, - "engines": { - "node": ">=20.19 <22 || >=22.12" - } - }, - "node_modules/@portabletext/types": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@portabletext/types/-/types-3.0.1.tgz", - "integrity": "sha512-Axessc2mZ6tjbD2qIZdTZIEfBtzJBe/1pzKF372cUar6ccUCfgXU4vaKgIbqB33mB6rI75omx9rrm+SgR4rIAA==", - "license": "MIT", - "engines": { - "node": ">=20.19 <22 || >=22.12" - } - }, - "node_modules/@radix-ui/primitive": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", - "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", - "license": "MIT" - }, - "node_modules/@radix-ui/react-accordion": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.2.12.tgz", - "integrity": "sha512-T4nygeh9YE9dLRPhAHSeOZi7HBXo+0kYIPJXayZfvWOWA0+n3dESrZbjfDPUABkUNym6Hd+f2IR113To8D2GPA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collapsible": "1.1.12", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-collapsible": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.1.12.tgz", - "integrity": "sha512-Uu+mSh4agx2ib1uIGPP4/CKNULyajb3p92LsVXmH2EHVMTfZWpll88XJ0j4W0z3f8NK1eYl1+Mf/szHPmcHzyA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-collection": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", - "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-slot": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-compose-refs": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", - "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-context": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", - "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-direction": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", - "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-id": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", - "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-presence": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", - "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-primitive": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", - "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-slot": "1.2.3" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-radio-group": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", - "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-use-controllable-state": "1.2.2", - "@radix-ui/react-use-previous": "1.1.1", - "@radix-ui/react-use-size": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-roving-focus": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", - "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-collection": "1.1.7", - "@radix-ui/react-compose-refs": "1.1.2", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-use-callback-ref": "1.1.1", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-slot": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-compose-refs": "1.1.2" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-tabs": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", - "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", - "license": "MIT", - "dependencies": { - "@radix-ui/primitive": "1.1.3", - "@radix-ui/react-context": "1.1.2", - "@radix-ui/react-direction": "1.1.1", - "@radix-ui/react-id": "1.1.1", - "@radix-ui/react-presence": "1.1.5", - "@radix-ui/react-primitive": "2.1.3", - "@radix-ui/react-roving-focus": "1.1.11", - "@radix-ui/react-use-controllable-state": "1.2.2" - }, - "peerDependencies": { - "@types/react": "*", - "@types/react-dom": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", - "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-callback-ref": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", - "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-controllable-state": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", - "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-effect-event": "0.0.2", - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-effect-event": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", - "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-layout-effect": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", - "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-previous": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", - "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", - "license": "MIT", - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@radix-ui/react-use-size": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", - "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", - "license": "MIT", - "dependencies": { - "@radix-ui/react-use-layout-effect": "1.1.1" - }, - "peerDependencies": { - "@types/react": "*", - "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - } - } - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.56.0.tgz", - "integrity": "sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.56.0.tgz", - "integrity": "sha512-lfbVUbelYqXlYiU/HApNMJzT1E87UPGvzveGg2h0ktUNlOCxKlWuJ9jtfvs1sKHdwU4fzY7Pl8sAl49/XaEk6Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.56.0.tgz", - "integrity": "sha512-EgxD1ocWfhoD6xSOeEEwyE7tDvwTgZc8Bss7wCWe+uc7wO8G34HHCUH+Q6cHqJubxIAnQzAsyUsClt0yFLu06w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.56.0.tgz", - "integrity": "sha512-1vXe1vcMOssb/hOF8iv52A7feWW2xnu+c8BV4t1F//m9QVLTfNVpEdja5ia762j/UEJe2Z1jAmEqZAK42tVW3g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.56.0.tgz", - "integrity": "sha512-bof7fbIlvqsyv/DtaXSck4VYQ9lPtoWNFCB/JY4snlFuJREXfZnm+Ej6yaCHfQvofJDXLDMTVxWscVSuQvVWUQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.56.0.tgz", - "integrity": "sha512-KNa6lYHloW+7lTEkYGa37fpvPq+NKG/EHKM8+G/g9WDU7ls4sMqbVRV78J6LdNuVaeeK5WB9/9VAFbKxcbXKYg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.56.0.tgz", - "integrity": "sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.56.0.tgz", - "integrity": "sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.56.0.tgz", - "integrity": "sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.56.0.tgz", - "integrity": "sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.56.0.tgz", - "integrity": "sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.56.0.tgz", - "integrity": "sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.56.0.tgz", - "integrity": "sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.56.0.tgz", - "integrity": "sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.56.0.tgz", - "integrity": "sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.56.0.tgz", - "integrity": "sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.56.0.tgz", - "integrity": "sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.56.0.tgz", - "integrity": "sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.56.0.tgz", - "integrity": "sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.56.0.tgz", - "integrity": "sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] - }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.56.0.tgz", - "integrity": "sha512-LhN/Reh+7F3RCgQIRbgw8ZMwUwyqJM+8pXNT6IIJAqm2IdKkzpCh/V9EdgOMBKuebIrzswqy4ATlrDgiOwbRcQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ] - }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.56.0.tgz", - "integrity": "sha512-kbFsOObXp3LBULg1d3JIUQMa9Kv4UitDmpS+k0tinPBz3watcUiV2/LUDMMucA6pZO3WGE27P7DsfaN54l9ing==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.56.0.tgz", - "integrity": "sha512-vSSgny54D6P4vf2izbtFm/TcWYedw7f8eBrOiGGecyHyQB9q4Kqentjaj8hToe+995nob/Wv48pDqL5a62EWtg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.56.0.tgz", - "integrity": "sha512-FeCnkPCTHQJFbiGG49KjV5YGW/8b9rrXAM2Mz2kiIoktq2qsJxRD5giEMEOD2lPdgs72upzefaUvS+nc8E3UzQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.56.0.tgz", - "integrity": "sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rtsao/scc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", - "dev": true, - "license": "MIT" - }, - "node_modules/@sanity/client": { - "version": "7.14.1", - "resolved": "https://registry.npmjs.org/@sanity/client/-/client-7.14.1.tgz", - "integrity": "sha512-lCDx0vuNUgg9FL2E5Hiv1q7u6iHd1Z+jjMZAaYtfzzFPoBKOiyZlaFwqyLVHRyTbwnXQdIGDPmx2AvfBTbGQsQ==", - "license": "MIT", - "dependencies": { - "@sanity/eventsource": "^5.0.2", - "get-it": "^8.7.0", - "nanoid": "^3.3.11", - "rxjs": "^7.0.0" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/@sanity/eventsource": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@sanity/eventsource/-/eventsource-5.0.2.tgz", - "integrity": "sha512-/B9PMkUvAlUrpRq0y+NzXgRv5lYCLxZNsBJD2WXVnqZYOfByL9oQBV7KiTaARuObp5hcQYuPfOAVjgXe3hrixA==", - "license": "MIT", - "dependencies": { - "@types/event-source-polyfill": "1.0.5", - "@types/eventsource": "1.1.15", - "event-source-polyfill": "1.0.31", - "eventsource": "2.0.2" - } - }, - "node_modules/@sanity/image-url": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@sanity/image-url/-/image-url-1.2.0.tgz", - "integrity": "sha512-pYRhti+lDi22it+npWXkEGuYyzbXJLF+d0TYLiyWbKu46JHhYhTDKkp6zmGu4YKF5cXUjT6pnUjFsaf2vlB9nQ==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@schummar/icu-type-parser": { - "version": "1.21.5", - "resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz", - "integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==", - "license": "MIT" - }, - "node_modules/@smithy/abort-controller": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-4.2.8.tgz", - "integrity": "sha512-peuVfkYHAmS5ybKxWcfraK7WBBP0J+rkfUcbHJJKQ4ir3UAUNQI+Y4Vt/PqSzGqgloJ5O1dk7+WzNL8wcCSXbw==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@radix-ui/react-id": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", + "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", + "license": "MIT", "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@radix-ui/react-use-layout-effect": "1.1.1" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@smithy/config-resolver": { - "version": "4.4.6", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-4.4.6.tgz", - "integrity": "sha512-qJpzYC64kaj3S0fueiu3kXm8xPrR3PcXDPEgnaNMRn0EjNSZFoFjvbUp0YUDsRhN1CB90EnHJtbxWKevnH99UQ==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@radix-ui/react-presence": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", + "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", + "license": "MIT", "dependencies": { - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-config-provider": "^4.2.0", - "@smithy/util-endpoints": "^3.2.8", - "@smithy/util-middleware": "^4.2.8", - "tslib": "^2.6.2" + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-use-layout-effect": "1.1.1" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/@smithy/core": { - "version": "3.21.1", - "resolved": "https://registry.npmjs.org/@smithy/core/-/core-3.21.1.tgz", - "integrity": "sha512-NUH8R4O6FkN8HKMojzbGg/5pNjsfTjlMmeFclyPfPaXXUrbr5TzhWgbf7t92wfrpCHRgpjyz7ffASIS3wX28aA==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@radix-ui/react-primitive": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", + "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", + "license": "MIT", "dependencies": { - "@smithy/middleware-serde": "^4.2.9", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-body-length-browser": "^4.2.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-stream": "^4.5.10", - "@smithy/util-utf8": "^4.2.0", - "@smithy/uuid": "^1.1.0", - "tslib": "^2.6.2" + "@radix-ui/react-slot": "1.2.3" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/@smithy/credential-provider-imds": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-4.2.8.tgz", - "integrity": "sha512-FNT0xHS1c/CPN8upqbMFP83+ul5YgdisfCfkZ86Jh2NSmnqw/AJ6x5pEogVCTVvSm7j9MopRU89bmDelxuDMYw==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@radix-ui/react-radio-group": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", + "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==", + "license": "MIT", "dependencies": { - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "tslib": "^2.6.2" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/@smithy/fetch-http-handler": { - "version": "5.3.9", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.9.tgz", - "integrity": "sha512-I4UhmcTYXBrct03rwzQX1Y/iqQlzVQaPxWjCjula++5EmWq9YGBrx6bbGqluGc1f0XEfhSkiY4jhLgbsJUMKRA==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", + "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", + "license": "MIT", "dependencies": { - "@smithy/protocol-http": "^5.3.8", - "@smithy/querystring-builder": "^4.2.8", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "tslib": "^2.6.2" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/@smithy/hash-node": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-4.2.8.tgz", - "integrity": "sha512-7ZIlPbmaDGxVoxErDZnuFG18WekhbA/g2/i97wGj+wUBeS6pcUeAym8u4BXh/75RXWhgIJhyC11hBzig6MljwA==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", "dependencies": { - "@smithy/types": "^4.12.0", - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" + "@radix-ui/react-compose-refs": "1.1.2" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@smithy/invalid-dependency": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-4.2.8.tgz", - "integrity": "sha512-N9iozRybwAQ2dn9Fot9kI6/w9vos2oTXLhtK7ovGqwZjlOcxu6XhPlpLpC+INsxktqHinn5gS2DXDjDF2kG5sQ==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "license": "MIT", "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } } }, - "node_modules/@smithy/is-array-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-4.2.0.tgz", - "integrity": "sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", + "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, - "engines": { - "node": ">=18.0.0" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@smithy/middleware-content-length": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-4.2.8.tgz", - "integrity": "sha512-RO0jeoaYAB1qBRhfVyq0pMgBoUK34YEJxVxyjOWYZiOKOq2yMZ4MnVXMZCUDenpozHue207+9P5ilTV1zeda0A==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", + "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", + "license": "MIT", "dependencies": { - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@radix-ui/react-use-effect-event": "0.0.2", + "@radix-ui/react-use-layout-effect": "1.1.1" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@smithy/middleware-endpoint": { - "version": "4.4.11", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.11.tgz", - "integrity": "sha512-/WqsrycweGGfb9sSzME4CrsuayjJF6BueBmkKlcbeU5q18OhxRrvvKlmfw3tpDsK5ilx2XUJvoukwxHB0nHs/Q==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@radix-ui/react-use-effect-event": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", + "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", + "license": "MIT", "dependencies": { - "@smithy/core": "^3.21.1", - "@smithy/middleware-serde": "^4.2.9", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "@smithy/url-parser": "^4.2.8", - "@smithy/util-middleware": "^4.2.8", - "tslib": "^2.6.2" + "@radix-ui/react-use-layout-effect": "1.1.1" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@smithy/middleware-retry": { - "version": "4.4.27", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-4.4.27.tgz", - "integrity": "sha512-xFUYCGRVsfgiN5EjsJJSzih9+yjStgMTCLANPlf0LVQkPDYCe0hz97qbdTZosFOiYlGBlHYityGRxrQ/hxhfVQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/service-error-classification": "^4.2.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-retry": "^4.2.8", - "@smithy/uuid": "^1.1.0", - "tslib": "^2.6.2" + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", + "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, - "engines": { - "node": ">=18.0.0" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@smithy/middleware-serde": { - "version": "4.2.9", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-4.2.9.tgz", - "integrity": "sha512-eMNiej0u/snzDvlqRGSN3Vl0ESn3838+nKyVfF2FKNXFbi4SERYT6PR392D39iczngbqqGG0Jl1DlCnp7tBbXQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, - "engines": { - "node": ">=18.0.0" + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@smithy/middleware-stack": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-4.2.8.tgz", - "integrity": "sha512-w6LCfOviTYQjBctOKSwy6A8FIkQy7ICvglrZFl6Bw4FmcQ1Z420fUtIhxaUZZshRe0VCq4kvDiPiXrPZAe8oRA==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@radix-ui/react-use-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz", + "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==", + "license": "MIT", "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" + "@radix-ui/react-use-layout-effect": "1.1.1" }, - "engines": { - "node": ">=18.0.0" + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "node_modules/@smithy/node-config-provider": { - "version": "4.3.8", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-4.3.8.tgz", - "integrity": "sha512-aFP1ai4lrbVlWjfpAfRSL8KFcnJQYfTl5QxLJXY32vghJrDuFyPZ6LtUL+JEGYiFRG1PfPLHLoxj107ulncLIg==", + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/property-provider": "^4.2.8", - "@smithy/shared-ini-file-loader": "^4.4.3", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@smithy/node-http-handler": { - "version": "4.4.8", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-4.4.8.tgz", - "integrity": "sha512-q9u+MSbJVIJ1QmJ4+1u+cERXkrhuILCBDsJUBAW1MPE6sFonbCNaegFuwW9ll8kh5UdyY3jOkoOGlc7BesoLpg==", + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/abort-controller": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/querystring-builder": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@smithy/property-provider": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-4.2.8.tgz", - "integrity": "sha512-EtCTbyIveCKeOXDSWSdze3k612yCPq1YbXsbqX3UHhkOSW8zKsM9NOJG5gTIya0vbY2DIaieG8pKo1rITHYL0w==", + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@smithy/protocol-http": { - "version": "5.3.8", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-5.3.8.tgz", - "integrity": "sha512-QNINVDhxpZ5QnP3aviNHQFlRogQZDfYlCkQT+7tJnErPQbDhysondEjhikuANxgMsZrkGeiAxXy4jguEGsDrWQ==", + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@smithy/querystring-builder": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-4.2.8.tgz", - "integrity": "sha512-Xr83r31+DrE8CP3MqPgMJl+pQlLLmOfiEUnoyAlGzzJIrEsbKsPy1hqH0qySaQm4oWrCBlUqRt+idEgunKB+iw==", + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "@smithy/util-uri-escape": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/@smithy/querystring-parser": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-4.2.8.tgz", - "integrity": "sha512-vUurovluVy50CUlazOiXkPq40KGvGWSdmusa3130MwrR1UNnNgKAlj58wlOe61XSHRpUfIIh6cE0zZ8mzKaDPA==", + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/@smithy/service-error-classification": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-4.2.8.tgz", - "integrity": "sha512-mZ5xddodpJhEt3RkCjbmUQuXUOaPNTkbMGR0bcS8FE0bJDLMZlhmpgrvPNCYglVw5rsYTpSnv19womw9WWXKQQ==", + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@smithy/shared-ini-file-loader": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.3.tgz", - "integrity": "sha512-DfQjxXQnzC5UbCUPeC3Ie8u+rIWZTvuDPAGU/BxzrOGhRvgUanaP68kDZA+jaT3ZI+djOf+4dERGlm9mWfFDrg==", + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@smithy/signature-v4": { - "version": "5.3.8", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-5.3.8.tgz", - "integrity": "sha512-6A4vdGj7qKNRF16UIcO8HhHjKW27thsxYci+5r/uVRkdcBEkOEiY8OMPuydLX4QHSrJqGHPJzPRwwVTqbLZJhg==", + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^4.2.0", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-middleware": "^4.2.8", - "@smithy/util-uri-escape": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@smithy/smithy-client": { - "version": "4.10.12", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-4.10.12.tgz", - "integrity": "sha512-VKO/HKoQ5OrSHW6AJUmEnUKeXI1/5LfCwO9cwyao7CmLvGnZeM1i36Lyful3LK1XU7HwTVieTqO1y2C/6t3qtA==", + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/core": "^3.21.1", - "@smithy/middleware-endpoint": "^4.4.11", - "@smithy/middleware-stack": "^4.2.8", - "@smithy/protocol-http": "^5.3.8", - "@smithy/types": "^4.12.0", - "@smithy/util-stream": "^4.5.10", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@smithy/types": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.12.0.tgz", - "integrity": "sha512-9YcuJVTOBDjg9LWo23Qp0lTQ3D7fQsQtwle0jVfpbUHy9qBwCEgKuVH4FqFB3VYu0nwdHKiEMA+oXz7oV8X1kw==", + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@smithy/url-parser": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-4.2.8.tgz", - "integrity": "sha512-NQho9U68TGMEU639YkXnVMV3GEFFULmmaWdlu1E9qzyIePOHsoSnagTGSDv1Zi8DCNN6btxOSdgmy5E/hsZwhA==", + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/querystring-parser": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@smithy/util-base64": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-4.3.0.tgz", - "integrity": "sha512-GkXZ59JfyxsIwNTWFnjmFEI8kZpRNIBfxKjv09+nkAWPt/4aGaEWMM04m4sxgNVWkbt2MdSvE3KF/PfX4nFedQ==", + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@smithy/util-body-length-browser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz", - "integrity": "sha512-Fkoh/I76szMKJnBXWPdFkQJl2r9SjPt3cMzLdOB6eJ4Pnpas8hVoWPYemX/peO0yrrvldgCUVJqOAjUrOLjbxg==", + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@smithy/util-body-length-node": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz", - "integrity": "sha512-h53dz/pISVrVrfxV1iqXlx5pRg3V2YWFcSQyPyXZRrZoZj4R4DeWRDo1a7dd3CPTcFi3kE+98tuNyD2axyZReA==", + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@smithy/util-buffer-from": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-4.2.0.tgz", - "integrity": "sha512-kAY9hTKulTNevM2nlRtxAG2FQ3B2OR6QIrPY3zE5LqJy1oxzmgBGsHLWTcNhWXKchgA0WHW+mZkQrng/pgcCew==", + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/is-array-buffer": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@smithy/util-config-provider": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz", - "integrity": "sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==", + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@smithy/util-defaults-mode-browser": { - "version": "4.3.26", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-4.3.26.tgz", - "integrity": "sha512-vva0dzYUTgn7DdE0uaha10uEdAgmdLnNFowKFjpMm6p2R0XDk5FHPX3CBJLzWQkQXuEprsb0hGz9YwbicNWhjw==", + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/property-provider": "^4.2.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@smithy/util-defaults-mode-node": { - "version": "4.2.29", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-4.2.29.tgz", - "integrity": "sha512-c6D7IUBsZt/aNnTBHMTf+OVh+h/JcxUUgfTcIJaWRe6zhOum1X+pNKSZtZ+7fbOn5I99XVFtmrnXKv8yHHErTQ==", + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/config-resolver": "^4.4.6", - "@smithy/credential-provider-imds": "^4.2.8", - "@smithy/node-config-provider": "^4.3.8", - "@smithy/property-provider": "^4.2.8", - "@smithy/smithy-client": "^4.10.12", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] }, - "node_modules/@smithy/util-endpoints": { - "version": "3.2.8", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-3.2.8.tgz", - "integrity": "sha512-8JaVTn3pBDkhZgHQ8R0epwWt+BqPSLCjdjXXusK1onwJlRuN69fbvSK66aIKKO7SwVFM6x2J2ox5X8pOaWcUEw==", + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/node-config-provider": "^4.3.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] }, - "node_modules/@smithy/util-hex-encoding": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.0.tgz", - "integrity": "sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==", + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@smithy/util-middleware": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-4.2.8.tgz", - "integrity": "sha512-PMqfeJxLcNPMDgvPbbLl/2Vpin+luxqTGPpW3NAQVLbRrFRzTa4rNAASYeIGjRV9Ytuhzny39SpyU04EQreF+A==", + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@smithy/util-retry": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-4.2.8.tgz", - "integrity": "sha512-CfJqwvoRY0kTGe5AkQokpURNCT1u/MkRzMTASWMPPo2hNSnKtF1D45dQl3DE2LKLr4m+PW9mCeBMJr5mCAVThg==", + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/service-error-classification": "^4.2.8", - "@smithy/types": "^4.12.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@smithy/util-stream": { - "version": "4.5.10", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-4.5.10.tgz", - "integrity": "sha512-jbqemy51UFSZSp2y0ZmRfckmrzuKww95zT9BYMmuJ8v3altGcqjwoV1tzpOwuHaKrwQrCjIzOib499ymr2f98g==", + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@smithy/fetch-http-handler": "^5.3.9", - "@smithy/node-http-handler": "^4.4.8", - "@smithy/types": "^4.12.0", - "@smithy/util-base64": "^4.3.0", - "@smithy/util-buffer-from": "^4.2.0", - "@smithy/util-hex-encoding": "^4.2.0", - "@smithy/util-utf8": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" - } + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@smithy/util-uri-escape": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz", - "integrity": "sha512-igZpCKV9+E/Mzrpq6YacdTQ0qTiLm85gD6N/IrmyDvQFA4UnU3d5g3m8tMT/6zG/vVkWSU+VxeUyGonL62DuxA==", + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", "dev": true, - "license": "Apache-2.0", + "license": "MIT" + }, + "node_modules/@sanity/client": { + "version": "7.14.1", + "resolved": "https://registry.npmjs.org/@sanity/client/-/client-7.14.1.tgz", + "integrity": "sha512-lCDx0vuNUgg9FL2E5Hiv1q7u6iHd1Z+jjMZAaYtfzzFPoBKOiyZlaFwqyLVHRyTbwnXQdIGDPmx2AvfBTbGQsQ==", + "license": "MIT", "dependencies": { - "tslib": "^2.6.2" + "@sanity/eventsource": "^5.0.2", + "get-it": "^8.7.0", + "nanoid": "^3.3.11", + "rxjs": "^7.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=20" } }, - "node_modules/@smithy/util-utf8": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-4.2.0.tgz", - "integrity": "sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@sanity/eventsource": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@sanity/eventsource/-/eventsource-5.0.2.tgz", + "integrity": "sha512-/B9PMkUvAlUrpRq0y+NzXgRv5lYCLxZNsBJD2WXVnqZYOfByL9oQBV7KiTaARuObp5hcQYuPfOAVjgXe3hrixA==", + "license": "MIT", "dependencies": { - "@smithy/util-buffer-from": "^4.2.0", - "tslib": "^2.6.2" - }, - "engines": { - "node": ">=18.0.0" + "@types/event-source-polyfill": "1.0.5", + "@types/eventsource": "1.1.15", + "event-source-polyfill": "1.0.31", + "eventsource": "2.0.2" } }, - "node_modules/@smithy/uuid": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@smithy/uuid/-/uuid-1.1.0.tgz", - "integrity": "sha512-4aUIteuyxtBUhVdiQqcDhKFitwfd9hqoSDYY2KRXiWtgoWJ9Bmise+KfEPDiVHWeJepvF8xJO9/9+WDIciMFFw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.6.2" - }, + "node_modules/@sanity/image-url": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@sanity/image-url/-/image-url-1.2.0.tgz", + "integrity": "sha512-pYRhti+lDi22it+npWXkEGuYyzbXJLF+d0TYLiyWbKu46JHhYhTDKkp6zmGu4YKF5cXUjT6pnUjFsaf2vlB9nQ==", + "license": "MIT", "engines": { - "node": ">=18.0.0" + "node": ">=10.0.0" } }, + "node_modules/@schummar/icu-type-parser": { + "version": "1.21.5", + "resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz", + "integrity": "sha512-bXHSaW5jRTmke9Vd0h5P7BtWZG9Znqb8gSDxZnxaGSJnGwPLDPfS+3g0BKzeWqzgZPsIVZkM7m2tbo18cm5HBw==", + "license": "MIT" + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -4703,9 +3258,9 @@ "license": "MIT" }, "node_modules/@stripe/react-stripe-js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-5.4.1.tgz", - "integrity": "sha512-ipeYcAHa4EPmjwfv0lFE+YDVkOQ0TMKkFWamW+BqmnSkEln/hO8rmxGPPWcd9WjqABx6Ro8Xg4pAS7evCcR9cw==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@stripe/react-stripe-js/-/react-stripe-js-5.6.0.tgz", + "integrity": "sha512-tucu/vTGc+5NXbo2pUiaVjA4ENdRBET8qGS00BM4BAU8J4Pi3eY6BHollsP2+VSuzzlvXwMg0it3ZLhbCj2fPg==", "license": "MIT", "dependencies": { "prop-types": "^15.7.2" @@ -4717,18 +3272,18 @@ } }, "node_modules/@stripe/stripe-js": { - "version": "8.6.4", - "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-8.6.4.tgz", - "integrity": "sha512-ipBq9+YEt5i4FxotkwNlQDqXvt//9ts23XQ4zgwm7yOkU/k3ZHnNRnm7dR418r5nC455dVYEzP7eERUHLrIYhA==", + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-8.7.0.tgz", + "integrity": "sha512-tNUerSstwNC1KuHgX4CASGO0Md3CB26IJzSXmVlSuFvhsBP4ZaEPpY4jxWOn9tfdDscuVT4Kqb8cZ2o9nLCgRQ==", "license": "MIT", "engines": { "node": ">=12.16" } }, "node_modules/@swc/core-darwin-arm64": { - "version": "1.15.10", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.10.tgz", - "integrity": "sha512-U72pGqmJYbjrLhMndIemZ7u9Q9owcJczGxwtfJlz/WwMaGYAV/g4nkGiUVk/+QSX8sFCAjanovcU1IUsP2YulA==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.15.11.tgz", + "integrity": "sha512-QoIupRWVH8AF1TgxYyeA5nS18dtqMuxNwchjBIwJo3RdwLEFiJq6onOx9JAxHtuPwUkIVuU2Xbp+jCJ7Vzmgtg==", "cpu": [ "arm64" ], @@ -4742,9 +3297,9 @@ } }, "node_modules/@swc/core-darwin-x64": { - "version": "1.15.10", - "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.10.tgz", - "integrity": "sha512-NZpDXtwHH083L40xdyj1sY31MIwLgOxKfZEAGCI8xHXdHa+GWvEiVdGiu4qhkJctoHFzAEc7ZX3GN5phuJcPuQ==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.15.11.tgz", + "integrity": "sha512-S52Gu1QtPSfBYDiejlcfp9GlN+NjTZBRRNsz8PNwBgSE626/FUf2PcllVUix7jqkoMC+t0rS8t+2/aSWlMuQtA==", "cpu": [ "x64" ], @@ -4758,9 +3313,9 @@ } }, "node_modules/@swc/core-linux-arm-gnueabihf": { - "version": "1.15.10", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.10.tgz", - "integrity": "sha512-ioieF5iuRziUF1HkH1gg1r93e055dAdeBAPGAk40VjqpL5/igPJ/WxFHGvc6WMLhUubSJI4S0AiZAAhEAp1jDg==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.15.11.tgz", + "integrity": "sha512-lXJs8oXo6Z4yCpimpQ8vPeCjkgoHu5NoMvmJZ8qxDyU99KVdg6KwU9H79vzrmB+HfH+dCZ7JGMqMF//f8Cfvdg==", "cpu": [ "arm" ], @@ -4774,9 +3329,9 @@ } }, "node_modules/@swc/core-linux-arm64-gnu": { - "version": "1.15.10", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.10.tgz", - "integrity": "sha512-tD6BClOrxSsNus9cJL7Gxdv7z7Y2hlyvZd9l0NQz+YXzmTWqnfzLpg16ovEI7gknH2AgDBB5ywOsqu8hUgSeEQ==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.15.11.tgz", + "integrity": "sha512-chRsz1K52/vj8Mfq/QOugVphlKPWlMh10V99qfH41hbGvwAU6xSPd681upO4bKiOr9+mRIZZW+EfJqY42ZzRyA==", "cpu": [ "arm64" ], @@ -4790,9 +3345,9 @@ } }, "node_modules/@swc/core-linux-arm64-musl": { - "version": "1.15.10", - "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.10.tgz", - "integrity": "sha512-4uAHO3nbfbrTcmO/9YcVweTQdx5fN3l7ewwl5AEK4yoC4wXmoBTEPHAVdKNe4r9+xrTgd4BgyPsy0409OjjlMw==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.15.11.tgz", + "integrity": "sha512-PYftgsTaGnfDK4m6/dty9ryK1FbLk+LosDJ/RJR2nkXGc8rd+WenXIlvHjWULiBVnS1RsjHHOXmTS4nDhe0v0w==", "cpu": [ "arm64" ], @@ -4806,9 +3361,9 @@ } }, "node_modules/@swc/core-linux-x64-gnu": { - "version": "1.15.10", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.10.tgz", - "integrity": "sha512-W0h9ONNw1pVIA0cN7wtboOSTl4Jk3tHq+w2cMPQudu9/+3xoCxpFb9ZdehwCAk29IsvdWzGzY6P7dDVTyFwoqg==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.15.11.tgz", + "integrity": "sha512-DKtnJKIHiZdARyTKiX7zdRjiDS1KihkQWatQiCHMv+zc2sfwb4Glrodx2VLOX4rsa92NLR0Sw8WLcPEMFY1szQ==", "cpu": [ "x64" ], @@ -4822,9 +3377,9 @@ } }, "node_modules/@swc/core-linux-x64-musl": { - "version": "1.15.10", - "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.10.tgz", - "integrity": "sha512-XQNZlLZB62S8nAbw7pqoqwy91Ldy2RpaMRqdRN3T+tAg6Xg6FywXRKCsLh6IQOadr4p1+lGnqM/Wn35z5a/0Vw==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.15.11.tgz", + "integrity": "sha512-mUjjntHj4+8WBaiDe5UwRNHuEzLjIWBTSGTw0JT9+C9/Yyuh4KQqlcEQ3ro6GkHmBGXBFpGIj/o5VMyRWfVfWw==", "cpu": [ "x64" ], @@ -4838,9 +3393,9 @@ } }, "node_modules/@swc/core-win32-arm64-msvc": { - "version": "1.15.10", - "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.10.tgz", - "integrity": "sha512-qnAGrRv5Nj/DATxAmCnJQRXXQqnJwR0trxLndhoHoxGci9MuguNIjWahS0gw8YZFjgTinbTxOwzatkoySihnmw==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.15.11.tgz", + "integrity": "sha512-ZkNNG5zL49YpaFzfl6fskNOSxtcZ5uOYmWBkY4wVAvgbSAQzLRVBp+xArGWh2oXlY/WgL99zQSGTv7RI5E6nzA==", "cpu": [ "arm64" ], @@ -4854,9 +3409,9 @@ } }, "node_modules/@swc/core-win32-ia32-msvc": { - "version": "1.15.10", - "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.10.tgz", - "integrity": "sha512-i4X/q8QSvzVlaRtv1xfnfl+hVKpCfiJ+9th484rh937fiEZKxZGf51C+uO0lfKDP1FfnT6C1yBYwHy7FLBVXFw==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.15.11.tgz", + "integrity": "sha512-6XnzORkZCQzvTQ6cPrU7iaT9+i145oLwnin8JrfsLG41wl26+5cNQ2XV3zcbrnFEV6esjOceom9YO1w9mGJByw==", "cpu": [ "ia32" ], @@ -4870,9 +3425,9 @@ } }, "node_modules/@swc/core-win32-x64-msvc": { - "version": "1.15.10", - "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.10.tgz", - "integrity": "sha512-HvY8XUFuoTXn6lSccDLYFlXv1SU/PzYi4PyUqGT++WfTnbw/68N/7BdUZqglGRwiSqr0qhYt/EhmBpULj0J9rA==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.15.11.tgz", + "integrity": "sha512-IQ2n6af7XKLL6P1gIeZACskSxK8jWtoKpJWLZmdXTDj1MGzktUy4i+FvpdtxFmJWNavRWH1VmTr6kAubRDHeKw==", "cpu": [ "x64" ], @@ -5385,13 +3940,12 @@ } }, "node_modules/@types/nodemailer": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.5.tgz", - "integrity": "sha512-7WtR4MFJUNN2UFy0NIowBRJswj5KXjXDhlZY43Hmots5eGu5q/dTeFd/I6GgJA/qj3RqO6dDy4SvfcV3fOVeIA==", + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-7.0.9.tgz", + "integrity": "sha512-vI8oF1M+8JvQhsId0Pc38BdUP2evenIIys7c7p+9OZXSPOH5c1dyINP1jT8xQ2xPuBUXmIC87s+91IZMDjH8Ow==", "dev": true, "license": "MIT", "dependencies": { - "@aws-sdk/client-sesv2": "^3.839.0", "@types/node": "*" } }, @@ -5413,9 +3967,9 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.2.9", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.9.tgz", - "integrity": "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==", + "version": "19.2.10", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz", + "integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==", "devOptional": true, "license": "MIT", "dependencies": { @@ -5433,17 +3987,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.1.tgz", - "integrity": "sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz", + "integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.53.1", - "@typescript-eslint/type-utils": "8.53.1", - "@typescript-eslint/utils": "8.53.1", - "@typescript-eslint/visitor-keys": "8.53.1", + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/type-utils": "8.54.0", + "@typescript-eslint/utils": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" @@ -5456,7 +4010,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.53.1", + "@typescript-eslint/parser": "^8.54.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -5472,16 +4026,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.1.tgz", - "integrity": "sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz", + "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.53.1", - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/typescript-estree": "8.53.1", - "@typescript-eslint/visitor-keys": "8.53.1", + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", "debug": "^4.4.3" }, "engines": { @@ -5497,14 +4051,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.53.1.tgz", - "integrity": "sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz", + "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.53.1", - "@typescript-eslint/types": "^8.53.1", + "@typescript-eslint/tsconfig-utils": "^8.54.0", + "@typescript-eslint/types": "^8.54.0", "debug": "^4.4.3" }, "engines": { @@ -5519,14 +4073,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.1.tgz", - "integrity": "sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz", + "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/visitor-keys": "8.53.1" + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5537,9 +4091,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.1.tgz", - "integrity": "sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz", + "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==", "dev": true, "license": "MIT", "engines": { @@ -5554,15 +4108,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.53.1.tgz", - "integrity": "sha512-MOrdtNvyhy0rHyv0ENzub1d4wQYKb2NmIqG7qEqPWFW7Mpy2jzFC3pQ2yKDvirZB7jypm5uGjF2Qqs6OIqu47w==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz", + "integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/typescript-estree": "8.53.1", - "@typescript-eslint/utils": "8.53.1", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/utils": "8.54.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, @@ -5579,9 +4133,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.1.tgz", - "integrity": "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz", + "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==", "dev": true, "license": "MIT", "engines": { @@ -5593,16 +4147,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.1.tgz", - "integrity": "sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz", + "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.53.1", - "@typescript-eslint/tsconfig-utils": "8.53.1", - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/visitor-keys": "8.53.1", + "@typescript-eslint/project-service": "8.54.0", + "@typescript-eslint/tsconfig-utils": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", @@ -5650,16 +4204,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.53.1.tgz", - "integrity": "sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz", + "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.53.1", - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/typescript-estree": "8.53.1" + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -5674,13 +4228,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.1.tgz", - "integrity": "sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz", + "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -6449,21 +5003,21 @@ "license": "MIT" }, "node_modules/ast-v8-to-istanbul": { - "version": "0.3.10", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.10.tgz", - "integrity": "sha512-p4K7vMz2ZSk3wN8l5o3y2bJAoZXT3VuJI5OLTATY/01CYWumWvwkUw0SqDBnNq6IiTO3qDa1eSQDibAV8g7XOQ==", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.11.tgz", + "integrity": "sha512-Qya9fkoofMjCBNVdWINMjB5KZvkYfaO9/anwkWnjxibpWUxo5iHl2sOdP7/uAqaRuUYuoo8rDwnbaaKVFxoUvw==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", - "js-tokens": "^9.0.1" + "js-tokens": "^10.0.0" } }, "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", - "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", "dev": true, "license": "MIT" }, @@ -6527,9 +5081,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.9.18", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.18.tgz", - "integrity": "sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA==", + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" @@ -6554,13 +5108,6 @@ "require-from-string": "^2.0.2" } }, - "node_modules/bowser": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.13.1.tgz", - "integrity": "sha512-OHawaAbjwx6rqICCKgSG0SAnT05bzd7ppyKLVUITZpANBaaMFBAsaNkto3LoQ31tyFP5kNujE8Cdx85G9VzOkw==", - "dev": true, - "license": "MIT" - }, "node_modules/brace-expansion": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", @@ -7129,9 +5676,9 @@ } }, "node_modules/diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.1.tgz", + "integrity": "sha512-Z3u54A8qGyqFOSr2pk0ijYs8mOE9Qz8kTvtKeBI+upoG9j04Sq+oI7W8zAJiQybDcESET8/uIdHzs0p3k4fZlw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -7365,9 +5912,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.278", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.278.tgz", - "integrity": "sha512-dQ0tM1svDRQOwxnXxm+twlGTjr9Upvt8UFWAgmLsxEzFQxhbti4VwxmMjsDxVC51Zo84swW7FVCXEV+VAkhuPw==", + "version": "1.5.283", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.283.tgz", + "integrity": "sha512-3vifjt1HgrGW/h76UEeny+adYApveS9dH2h3p57JYzBSXJIKUJAvtmIytDKjcSCt9xHfrNCFJ7gts6vkhuq++w==", "dev": true, "license": "ISC" }, @@ -8760,25 +7307,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-xml-parser": { - "version": "5.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.2.5.tgz", - "integrity": "sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT", - "dependencies": { - "strnum": "^2.1.0" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, "node_modules/fastq": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", @@ -9106,9 +7634,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz", - "integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.1.tgz", + "integrity": "sha512-EoY1N2xCn44xU6750Sx7OjOIT59FkmstNc3X6y5xpz7D5cBtZRe/3pSlTkDJgqsOk3WwZPkWfonhhUJfttQo3w==", "dev": true, "license": "MIT", "dependencies": { @@ -9459,6 +7987,21 @@ "ms": "^2.0.0" } }, + "node_modules/icu-minify": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.8.1.tgz", + "integrity": "sha512-RhXrTA19/PUt5/xs3B/eyx3DCjnMmcfdgqNYBE5vdrEqM4PQK2GWjHa1zdArTxPGgEc4jfuDMH1LkKhmIxwLFw==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], + "license": "MIT", + "dependencies": { + "@formatjs/icu-messageformat-parser": "^3.4.0" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -9540,15 +8083,15 @@ } }, "node_modules/intl-messageformat": { - "version": "10.7.18", - "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.7.18.tgz", - "integrity": "sha512-m3Ofv/X/tV8Y3tHXLohcuVuhWKo7BBq62cqY15etqmLxg2DZ34AGGgQDeR+SCta2+zICb1NX83af0GJmbQ1++g==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.1.1.tgz", + "integrity": "sha512-vnrF2f4vfsdaFY6tuLZfzGcx1GZFMFAq6c7QdK3HSXNcGXEIQncNgbeAcnpjAOszQzq3Jbol2SwgshIGY08WyA==", "license": "BSD-3-Clause", "dependencies": { - "@formatjs/ecma402-abstract": "2.3.6", - "@formatjs/fast-memoize": "2.2.7", - "@formatjs/icu-messageformat-parser": "2.11.4", - "tslib": "^2.8.0" + "@formatjs/ecma402-abstract": "3.1.0", + "@formatjs/fast-memoize": "3.1.0", + "@formatjs/icu-messageformat-parser": "3.5.0", + "tslib": "^2.8.1" } }, "node_modules/is-array-buffer": { @@ -10986,12 +9529,12 @@ } }, "node_modules/next": { - "version": "16.1.4", - "resolved": "https://registry.npmjs.org/next/-/next-16.1.4.tgz", - "integrity": "sha512-gKSecROqisnV7Buen5BfjmXAm7Xlpx9o2ueVQRo5DxQcjC8d330dOM1xiGWc2k3Dcnz0In3VybyRPOsudwgiqQ==", + "version": "16.1.6", + "resolved": "https://registry.npmjs.org/next/-/next-16.1.6.tgz", + "integrity": "sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==", "license": "MIT", "dependencies": { - "@next/env": "16.1.4", + "@next/env": "16.1.6", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.8.3", "caniuse-lite": "^1.0.30001579", @@ -11005,14 +9548,14 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.1.4", - "@next/swc-darwin-x64": "16.1.4", - "@next/swc-linux-arm64-gnu": "16.1.4", - "@next/swc-linux-arm64-musl": "16.1.4", - "@next/swc-linux-x64-gnu": "16.1.4", - "@next/swc-linux-x64-musl": "16.1.4", - "@next/swc-win32-arm64-msvc": "16.1.4", - "@next/swc-win32-x64-msvc": "16.1.4", + "@next/swc-darwin-arm64": "16.1.6", + "@next/swc-darwin-x64": "16.1.6", + "@next/swc-linux-arm64-gnu": "16.1.6", + "@next/swc-linux-arm64-musl": "16.1.6", + "@next/swc-linux-x64-gnu": "16.1.6", + "@next/swc-linux-x64-musl": "16.1.6", + "@next/swc-win32-arm64-msvc": "16.1.6", + "@next/swc-win32-x64-msvc": "16.1.6", "sharp": "^0.34.4" }, "peerDependencies": { @@ -11039,9 +9582,9 @@ } }, "node_modules/next-intl": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.7.0.tgz", - "integrity": "sha512-gvROzcNr/HM0jTzQlKWQxUNk8jrZ0bREz+bht3wNbv+uzlZ5Kn3J+m+viosub18QJ72S08UJnVK50PXWcUvwpQ==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.8.1.tgz", + "integrity": "sha512-7V0Ewm6bP12Lgn6cjcKi6NbVy0JeIp1K9EzHfTIsuLAUn4NuH8dhon2WYJywHCyWnxKepm34xF6G1fR2dsNoqA==", "funding": [ { "type": "individual", @@ -11053,10 +9596,11 @@ "@formatjs/intl-localematcher": "^0.5.4", "@parcel/watcher": "^2.4.1", "@swc/core": "^1.15.2", + "icu-minify": "^4.8.1", "negotiator": "^1.0.0", - "next-intl-swc-plugin-extractor": "^4.7.0", + "next-intl-swc-plugin-extractor": "^4.8.1", "po-parser": "^2.1.1", - "use-intl": "^4.7.0" + "use-intl": "^4.8.1" }, "peerDependencies": { "next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0", @@ -11070,15 +9614,15 @@ } }, "node_modules/next-intl-swc-plugin-extractor": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.7.0.tgz", - "integrity": "sha512-iAqflu2FWdQMWhwB0B2z52X7LmEpvnMNJXqVERZQ7bK5p9iqQLu70ur6Ka6NfiXLxfb+AeAkUX5qIciQOg+87A==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.8.1.tgz", + "integrity": "sha512-4RXyce0ZrQl/6lkfwvDpP8r5B7wbAQakCW9BuMPh+7/DgXFyE+cBXxcmfDkxFDkpXvBFqBZ2HsN4pav49reAZQ==", "license": "MIT" }, "node_modules/next-intl/node_modules/@swc/core": { - "version": "1.15.10", - "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.10.tgz", - "integrity": "sha512-udNofxftduMUEv7nqahl2nvodCiCDQ4Ge0ebzsEm6P8s0RC2tBM0Hqx0nNF5J/6t9uagFJyWIDjXy3IIWMHDJw==", + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.15.11.tgz", + "integrity": "sha512-iLmLTodbYxU39HhMPaMUooPwO/zqJWvsqkrXv1ZI38rMb048p6N7qtAtTp37sw9NzSrvH6oli8EdDygo09IZ/w==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { @@ -11093,16 +9637,16 @@ "url": "https://opencollective.com/swc" }, "optionalDependencies": { - "@swc/core-darwin-arm64": "1.15.10", - "@swc/core-darwin-x64": "1.15.10", - "@swc/core-linux-arm-gnueabihf": "1.15.10", - "@swc/core-linux-arm64-gnu": "1.15.10", - "@swc/core-linux-arm64-musl": "1.15.10", - "@swc/core-linux-x64-gnu": "1.15.10", - "@swc/core-linux-x64-musl": "1.15.10", - "@swc/core-win32-arm64-msvc": "1.15.10", - "@swc/core-win32-ia32-msvc": "1.15.10", - "@swc/core-win32-x64-msvc": "1.15.10" + "@swc/core-darwin-arm64": "1.15.11", + "@swc/core-darwin-x64": "1.15.11", + "@swc/core-linux-arm-gnueabihf": "1.15.11", + "@swc/core-linux-arm64-gnu": "1.15.11", + "@swc/core-linux-arm64-musl": "1.15.11", + "@swc/core-linux-x64-gnu": "1.15.11", + "@swc/core-linux-x64-musl": "1.15.11", + "@swc/core-win32-arm64-msvc": "1.15.11", + "@swc/core-win32-ia32-msvc": "1.15.11", + "@swc/core-win32-x64-msvc": "1.15.11" }, "peerDependencies": { "@swc/helpers": ">=0.5.17" @@ -11117,8 +9661,8 @@ "version": "0.5.18", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.18.tgz", "integrity": "sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==", - "optional": true, - "peer": true, + "extraneous": true, + "license": "Apache-2.0", "dependencies": { "tslib": "^2.8.0" } @@ -11244,9 +9788,9 @@ "license": "MIT" }, "node_modules/nodemailer": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.12.tgz", - "integrity": "sha512-H+rnK5bX2Pi/6ms3sN4/jRQvYSMltV6vqup/0SFOrxYYY/qoNvhXPlYq3e+Pm9RFJRwrMGbMIwi81M4dxpomhA==", + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.13.tgz", + "integrity": "sha512-PNDFSJdP+KFgdsG3ZzMXCgquO7I6McjY2vlqILjtJd0hy8wEvtugS9xKRF2NWlPNGxvLCXlTNIae4serI7dinw==", "license": "MIT-0", "engines": { "node": ">=6.0.0" @@ -11536,12 +10080,12 @@ "license": "MIT" }, "node_modules/pg": { - "version": "8.17.2", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.17.2.tgz", - "integrity": "sha512-vjbKdiBJRqzcYw1fNU5KuHyYvdJ1qpcQg1CeBrHFqV1pWgHeVR6j/+kX0E1AAXfyuLUGY1ICrN2ELKA/z2HWzw==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz", + "integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==", "license": "MIT", "dependencies": { - "pg-connection-string": "^2.10.1", + "pg-connection-string": "^2.11.0", "pg-pool": "^3.11.0", "pg-protocol": "^1.11.0", "pg-types": "2.2.0", @@ -11570,9 +10114,9 @@ "optional": true }, "node_modules/pg-connection-string": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.10.1.tgz", - "integrity": "sha512-iNzslsoeSH2/gmDDKiyMqF64DATUCWj3YJ0wP14kqcsf2TUklwimd+66yYojKwZCA7h2yRNLGug71hCBA2a4sw==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.11.0.tgz", + "integrity": "sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ==", "license": "MIT" }, "node_modules/pg-int8": { @@ -11852,24 +10396,24 @@ "license": "MIT" }, "node_modules/react": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", - "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.3" + "react": "^19.2.4" } }, "node_modules/react-is": { @@ -12014,9 +10558,9 @@ } }, "node_modules/rollup": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.56.0.tgz", - "integrity": "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", "dev": true, "license": "MIT", "dependencies": { @@ -12030,31 +10574,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.56.0", - "@rollup/rollup-android-arm64": "4.56.0", - "@rollup/rollup-darwin-arm64": "4.56.0", - "@rollup/rollup-darwin-x64": "4.56.0", - "@rollup/rollup-freebsd-arm64": "4.56.0", - "@rollup/rollup-freebsd-x64": "4.56.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.56.0", - "@rollup/rollup-linux-arm-musleabihf": "4.56.0", - "@rollup/rollup-linux-arm64-gnu": "4.56.0", - "@rollup/rollup-linux-arm64-musl": "4.56.0", - "@rollup/rollup-linux-loong64-gnu": "4.56.0", - "@rollup/rollup-linux-loong64-musl": "4.56.0", - "@rollup/rollup-linux-ppc64-gnu": "4.56.0", - "@rollup/rollup-linux-ppc64-musl": "4.56.0", - "@rollup/rollup-linux-riscv64-gnu": "4.56.0", - "@rollup/rollup-linux-riscv64-musl": "4.56.0", - "@rollup/rollup-linux-s390x-gnu": "4.56.0", - "@rollup/rollup-linux-x64-gnu": "4.56.0", - "@rollup/rollup-linux-x64-musl": "4.56.0", - "@rollup/rollup-openbsd-x64": "4.56.0", - "@rollup/rollup-openharmony-arm64": "4.56.0", - "@rollup/rollup-win32-arm64-msvc": "4.56.0", - "@rollup/rollup-win32-ia32-msvc": "4.56.0", - "@rollup/rollup-win32-x64-gnu": "4.56.0", - "@rollup/rollup-win32-x64-msvc": "4.56.0", + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" } }, @@ -12698,19 +11242,6 @@ } } }, - "node_modules/strnum": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.1.2.tgz", - "integrity": "sha512-l63NF9y/cLROq/yqKXSLtcMeeyOfnSQlfMSlzFt/K73oIaD8DGaQWd7Z34X9GPiKqP5rbSh84Hl4bOlLcjiSrQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "license": "MIT" - }, "node_modules/styled-jsx": { "version": "5.1.6", "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", @@ -13335,16 +11866,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.53.1.tgz", - "integrity": "sha512-gB+EVQfP5RDElh9ittfXlhZJdjSU4jUSTyE2+ia8CYyNvet4ElfaLlAIqDvQV9JPknKx0jQH1racTYe/4LaLSg==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.54.0.tgz", + "integrity": "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.53.1", - "@typescript-eslint/parser": "8.53.1", - "@typescript-eslint/typescript-estree": "8.53.1", - "@typescript-eslint/utils": "8.53.1" + "@typescript-eslint/eslint-plugin": "8.54.0", + "@typescript-eslint/parser": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/utils": "8.54.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -13460,14 +11991,21 @@ } }, "node_modules/use-intl": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.7.0.tgz", - "integrity": "sha512-jyd8nSErVRRsSlUa+SDobKHo9IiWs5fjcPl9VBUnzUyEQpVM5mwJCgw8eUiylhvBpLQzUGox1KN0XlRivSID9A==", + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.8.1.tgz", + "integrity": "sha512-wH85ro36JAw9NrtogpEsqHghD21Ul6/zAKxTqHktnO6WTQzqhbcfPkO5iXu3YOnJBA8Anm7IvWSTqSzRE+2PsA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/amannn" + } + ], "license": "MIT", "dependencies": { - "@formatjs/fast-memoize": "^2.2.0", + "@formatjs/fast-memoize": "^3.1.0", "@schummar/icu-type-parser": "1.21.5", - "intl-messageformat": "^10.5.14" + "icu-minify": "^4.8.1", + "intl-messageformat": "^11.1.0" }, "peerDependencies": { "react": "^17.0.0 || ^18.0.0 || >=19.0.0-rc <19.0.0 || ^19.0.0" @@ -14064,7 +12602,6 @@ "version": "3.25.76", "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", - "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" diff --git a/frontend/package.json b/frontend/package.json index 16826716..356af09c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "frontend", - "version": "0.5.0", + "version": "0.5.1", "private": true, "scripts": { "dev": "next dev", @@ -57,7 +57,7 @@ "zod": "^3.24.0" }, "devDependencies": { - "@netlify/plugin-nextjs": "^5.15.1", + "@netlify/plugin-nextjs": "^5.15.7", "@tailwindcss/postcss": "^4", "@testing-library/dom": "^10.4.1", "@testing-library/jest-dom": "^6.8.0", diff --git a/studio/package-lock.json b/studio/package-lock.json index 0fefa5d7..88d15a89 100644 --- a/studio/package-lock.json +++ b/studio/package-lock.json @@ -1,12 +1,12 @@ { "name": "devlovers", - "version": "0.5.0", + "version": "0.5.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "devlovers", - "version": "0.5.0", + "version": "0.5.1", "license": "UNLICENSED", "dependencies": { "@sanity/orderable-document-list": "^1.4.2", @@ -289,9 +289,9 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", - "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", @@ -303,30 +303,29 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", - "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", - "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", "license": "MIT", - "peer": true, "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.28.6", + "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -343,13 +342,13 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", - "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.0.tgz", + "integrity": "sha512-vSH118/wwM/pLR38g/Sgk05sNtro6TlTJKuiMXDaZqPUfjTFcudpCOt00IhOfj+1BFAX+UFAlzCU+6WXr3GLFQ==", "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -635,12 +634,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", - "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", + "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", "license": "MIT", "dependencies": { - "@babel/types": "^7.28.6" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -832,14 +831,14 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.6.tgz", - "integrity": "sha512-9knsChgsMzBV5Yh3kkhrZNxH3oCYAfMBkNNaVN4cP2RVlFPe8wYdwwcnOsAbkdDoV9UjFtOXWrWB52M8W4jNeA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", + "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.28.6" + "@babel/traverse": "^7.29.0" }, "engines": { "node": ">=6.9.0" @@ -1011,9 +1010,9 @@ } }, "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.28.6.tgz", - "integrity": "sha512-5suVoXjC14lUN6ZL9OLKIHCNVWCrqGqlmEp/ixdXjvgnEl/kauLvvMO/Xw9NyMc95Joj1AeLVPVMvibBgSoFlA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", "license": "MIT", "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.28.5", @@ -1213,15 +1212,15 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", - "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.0.tgz", + "integrity": "sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==", "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.28.3", - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.5" + "@babel/traverse": "^7.29.0" }, "engines": { "node": ">=6.9.0" @@ -1247,13 +1246,13 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1532,9 +1531,9 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.6.tgz", - "integrity": "sha512-eZhoEZHYQLL5uc1gS5e9/oTknS0sSSAtd5TkKMUp3J+S/CaUjagc0kOUPsEbDmMeva0nC3WWl4SxVY6+OBuxfw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", + "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.28.6" @@ -1736,12 +1735,12 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.6.tgz", - "integrity": "sha512-GaTI4nXDrs7l0qaJ6Rg06dtOXTBCG6TMDB44zbqofCIC4PqC7SEvmFFtpxzCDw9W5aJ7RKVshgXTLvLdBFV/qw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.0.tgz", + "integrity": "sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w==", "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", + "@babel/compat-data": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", @@ -1755,7 +1754,7 @@ "@babel/plugin-syntax-import-attributes": "^7.28.6", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.28.6", + "@babel/plugin-transform-async-generator-functions": "^7.29.0", "@babel/plugin-transform-async-to-generator": "^7.28.6", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", "@babel/plugin-transform-block-scoping": "^7.28.6", @@ -1766,7 +1765,7 @@ "@babel/plugin-transform-destructuring": "^7.28.5", "@babel/plugin-transform-dotall-regex": "^7.28.6", "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.28.6", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-dynamic-import": "^7.27.1", "@babel/plugin-transform-explicit-resource-management": "^7.28.6", "@babel/plugin-transform-exponentiation-operator": "^7.28.6", @@ -1779,9 +1778,9 @@ "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.28.6", - "@babel/plugin-transform-modules-systemjs": "^7.28.5", + "@babel/plugin-transform-modules-systemjs": "^7.29.0", "@babel/plugin-transform-modules-umd": "^7.27.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-new-target": "^7.27.1", "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", "@babel/plugin-transform-numeric-separator": "^7.28.6", @@ -1793,7 +1792,7 @@ "@babel/plugin-transform-private-methods": "^7.28.6", "@babel/plugin-transform-private-property-in-object": "^7.28.6", "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.28.6", + "@babel/plugin-transform-regenerator": "^7.29.0", "@babel/plugin-transform-regexp-modifiers": "^7.28.6", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", @@ -1806,10 +1805,10 @@ "@babel/plugin-transform-unicode-regex": "^7.27.1", "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.14", - "babel-plugin-polyfill-corejs3": "^0.13.0", - "babel-plugin-polyfill-regenerator": "^0.6.5", - "core-js-compat": "^3.43.0", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "core-js-compat": "^3.48.0", "semver": "^6.3.1" }, "engines": { @@ -1915,17 +1914,17 @@ } }, "node_modules/@babel/traverse": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", - "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.6", + "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/types": "^7.29.0", "debug": "^4.3.1" }, "engines": { @@ -1933,9 +1932,9 @@ } }, "node_modules/@babel/types": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", - "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -1999,9 +1998,9 @@ } }, "node_modules/@codemirror/lint": { - "version": "6.9.2", - "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.2.tgz", - "integrity": "sha512-sv3DylBiIyi+xKwRCJAAsBZZZWo82shJ/RTMymLabAdtbkV5cSKwWDeCgtUq3v8flTaXS2y1kKkICuRYtUswyQ==", + "version": "6.9.3", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.9.3.tgz", + "integrity": "sha512-y3YkYhdnhjDBAe0VIA0c4wVoFOvnp8CnAvfLqi0TqotIv92wIlAAP7HELOpLBsKwjAX6W92rSflA6an/2zBvXw==", "license": "MIT", "dependencies": { "@codemirror/state": "^6.0.0", @@ -2042,11 +2041,10 @@ } }, "node_modules/@codemirror/view": { - "version": "6.39.11", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.11.tgz", - "integrity": "sha512-bWdeR8gWM87l4DB/kYSF9A+dVackzDb/V56Tq7QVrQ7rn86W0rgZFtlL3g3pem6AeGcb9NQNoy3ao4WpW4h5tQ==", + "version": "6.39.12", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.39.12.tgz", + "integrity": "sha512-f+/VsHVn/kOA9lltk/GFzuYwVVAKmOnNjxbrhkk3tPHntFqjWeI2TbIXx006YkBkqC10wZ4NsnWXCQiFPeAISQ==", "license": "MIT", - "peer": true, "dependencies": { "@codemirror/state": "^6.5.0", "crelt": "^1.0.6", @@ -2138,7 +2136,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -2161,7 +2158,6 @@ } ], "license": "MIT", - "peer": true, "engines": { "node": ">=18" } @@ -2195,7 +2191,6 @@ "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.3.1.tgz", "integrity": "sha512-xkGBRQQab4RLwgXxoqETICr6S5JlogafbhNsidmrkVv2YRs5MLwpjoF2qpiGjQt8S9AoxtIV603s0GIUpY5eYQ==", "license": "MIT", - "peer": true, "dependencies": { "@dnd-kit/accessibility": "^3.1.1", "@dnd-kit/utilities": "^3.2.2", @@ -2251,7 +2246,6 @@ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz", "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==", "license": "MIT", - "peer": true, "dependencies": { "@emotion/memoize": "^0.9.0" } @@ -2838,31 +2832,31 @@ } }, "node_modules/@floating-ui/core": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz", - "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.4.tgz", + "integrity": "sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==", "license": "MIT", "dependencies": { "@floating-ui/utils": "^0.2.10" } }, "node_modules/@floating-ui/dom": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz", - "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==", + "version": "1.7.5", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.5.tgz", + "integrity": "sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==", "license": "MIT", "dependencies": { - "@floating-ui/core": "^1.7.3", + "@floating-ui/core": "^1.7.4", "@floating-ui/utils": "^0.2.10" } }, "node_modules/@floating-ui/react-dom": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz", - "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.7.tgz", + "integrity": "sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==", "license": "MIT", "dependencies": { - "@floating-ui/dom": "^1.7.4" + "@floating-ui/dom": "^1.7.5" }, "peerDependencies": { "react": ">=16.8.0", @@ -3750,7 +3744,6 @@ "resolved": "https://registry.npmjs.org/@octokit/core/-/core-5.2.2.tgz", "integrity": "sha512-/g2d4sW9nUDJOMz3mabVQvOGhVa4e/BN/Um7yca9Bb2XTzPPnfTWHWQg+IsEYO7M3Vx+EXvaM/I2pJWIMun1bg==", "license": "MIT", - "peer": true, "dependencies": { "@octokit/auth-token": "^4.0.0", "@octokit/graphql": "^7.1.0", @@ -3961,11 +3954,10 @@ } }, "node_modules/@portabletext/editor": { - "version": "3.3.16", - "resolved": "https://registry.npmjs.org/@portabletext/editor/-/editor-3.3.16.tgz", - "integrity": "sha512-Z+/HXuOf1zwDpwp54uR9lsQtJ0tak5rr/9FgwObjUB1LAPNZm8IUezfQO56U3gvoQSZ7Rhj5/+BC+6hnnAKfyw==", + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/@portabletext/editor/-/editor-3.3.18.tgz", + "integrity": "sha512-K+504jYuodi4b4C+U1SibeHELIBsTDwF30pUZd2IAKp4+avfzpvgX5qKxtPg3aW8z++L3U5Y33ggC9IbJDIVOg==", "license": "MIT", - "peer": true, "dependencies": { "@portabletext/block-tools": "^4.1.11", "@portabletext/keyboard-shortcuts": "^2.1.1", @@ -4218,9 +4210,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.56.0.tgz", - "integrity": "sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", "cpu": [ "arm" ], @@ -4231,9 +4223,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.56.0.tgz", - "integrity": "sha512-lfbVUbelYqXlYiU/HApNMJzT1E87UPGvzveGg2h0ktUNlOCxKlWuJ9jtfvs1sKHdwU4fzY7Pl8sAl49/XaEk6Q==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", "cpu": [ "arm64" ], @@ -4244,9 +4236,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.56.0.tgz", - "integrity": "sha512-EgxD1ocWfhoD6xSOeEEwyE7tDvwTgZc8Bss7wCWe+uc7wO8G34HHCUH+Q6cHqJubxIAnQzAsyUsClt0yFLu06w==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", "cpu": [ "arm64" ], @@ -4257,9 +4249,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.56.0.tgz", - "integrity": "sha512-1vXe1vcMOssb/hOF8iv52A7feWW2xnu+c8BV4t1F//m9QVLTfNVpEdja5ia762j/UEJe2Z1jAmEqZAK42tVW3g==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", "cpu": [ "x64" ], @@ -4270,9 +4262,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.56.0.tgz", - "integrity": "sha512-bof7fbIlvqsyv/DtaXSck4VYQ9lPtoWNFCB/JY4snlFuJREXfZnm+Ej6yaCHfQvofJDXLDMTVxWscVSuQvVWUQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", "cpu": [ "arm64" ], @@ -4283,9 +4275,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.56.0.tgz", - "integrity": "sha512-KNa6lYHloW+7lTEkYGa37fpvPq+NKG/EHKM8+G/g9WDU7ls4sMqbVRV78J6LdNuVaeeK5WB9/9VAFbKxcbXKYg==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", "cpu": [ "x64" ], @@ -4296,9 +4288,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.56.0.tgz", - "integrity": "sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", "cpu": [ "arm" ], @@ -4309,9 +4301,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.56.0.tgz", - "integrity": "sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", "cpu": [ "arm" ], @@ -4322,9 +4314,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.56.0.tgz", - "integrity": "sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", "cpu": [ "arm64" ], @@ -4335,9 +4327,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.56.0.tgz", - "integrity": "sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", "cpu": [ "arm64" ], @@ -4348,9 +4340,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.56.0.tgz", - "integrity": "sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", "cpu": [ "loong64" ], @@ -4361,9 +4353,9 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.56.0.tgz", - "integrity": "sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", "cpu": [ "loong64" ], @@ -4374,9 +4366,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.56.0.tgz", - "integrity": "sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", "cpu": [ "ppc64" ], @@ -4387,9 +4379,9 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.56.0.tgz", - "integrity": "sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", "cpu": [ "ppc64" ], @@ -4400,9 +4392,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.56.0.tgz", - "integrity": "sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", "cpu": [ "riscv64" ], @@ -4413,9 +4405,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.56.0.tgz", - "integrity": "sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", "cpu": [ "riscv64" ], @@ -4426,9 +4418,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.56.0.tgz", - "integrity": "sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", "cpu": [ "s390x" ], @@ -4439,9 +4431,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.56.0.tgz", - "integrity": "sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", "cpu": [ "x64" ], @@ -4452,9 +4444,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.56.0.tgz", - "integrity": "sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", "cpu": [ "x64" ], @@ -4465,9 +4457,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.56.0.tgz", - "integrity": "sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", "cpu": [ "x64" ], @@ -4478,9 +4470,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.56.0.tgz", - "integrity": "sha512-LhN/Reh+7F3RCgQIRbgw8ZMwUwyqJM+8pXNT6IIJAqm2IdKkzpCh/V9EdgOMBKuebIrzswqy4ATlrDgiOwbRcQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", "cpu": [ "arm64" ], @@ -4491,9 +4483,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.56.0.tgz", - "integrity": "sha512-kbFsOObXp3LBULg1d3JIUQMa9Kv4UitDmpS+k0tinPBz3watcUiV2/LUDMMucA6pZO3WGE27P7DsfaN54l9ing==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", "cpu": [ "arm64" ], @@ -4504,9 +4496,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.56.0.tgz", - "integrity": "sha512-vSSgny54D6P4vf2izbtFm/TcWYedw7f8eBrOiGGecyHyQB9q4Kqentjaj8hToe+995nob/Wv48pDqL5a62EWtg==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", "cpu": [ "ia32" ], @@ -4517,9 +4509,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.56.0.tgz", - "integrity": "sha512-FeCnkPCTHQJFbiGG49KjV5YGW/8b9rrXAM2Mz2kiIoktq2qsJxRD5giEMEOD2lPdgs72upzefaUvS+nc8E3UzQ==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", "cpu": [ "x64" ], @@ -4530,9 +4522,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.56.0.tgz", - "integrity": "sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", "cpu": [ "x64" ], @@ -4635,7 +4627,6 @@ "resolved": "https://registry.npmjs.org/@sanity/client/-/client-7.14.1.tgz", "integrity": "sha512-lCDx0vuNUgg9FL2E5Hiv1q7u6iHd1Z+jjMZAaYtfzzFPoBKOiyZlaFwqyLVHRyTbwnXQdIGDPmx2AvfBTbGQsQ==", "license": "MIT", - "peer": true, "dependencies": { "@sanity/eventsource": "^5.0.2", "get-it": "^8.7.0", @@ -5405,9 +5396,9 @@ } }, "node_modules/@sanity/orderable-document-list": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@sanity/orderable-document-list/-/orderable-document-list-1.4.2.tgz", - "integrity": "sha512-tnomvVsUElB70ZY7kh5LYoA3cZe/oCUu49bLper+CZm9QxoIF5336KeEw95Y/YL0oipiTkdaqiiIjgCwx+emgg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@sanity/orderable-document-list/-/orderable-document-list-1.5.0.tgz", + "integrity": "sha512-Ed1cykbRW/UrW4JC+KRVWAvm6TN7Z80DBUkf9qbNzFUnykFR8Yxtmmdcbx5Q1HwzTzA7uUR6agWYuw4COqt7YQ==", "license": "MIT", "dependencies": { "@hello-pangea/dnd": "^18.0.1", @@ -5710,9 +5701,9 @@ } }, "node_modules/@sanity/runtime-cli/node_modules/string-width": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", - "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.1.tgz", + "integrity": "sha512-KpqHIdDL9KwYk22wEOg/VIqYbrnLeSApsKT/bSj6Ez7pn3CftUiLAv2Lccpq1ALcpLV9UX1Ppn92npZWu2w/aw==", "license": "MIT", "dependencies": { "get-east-asian-width": "^1.3.0", @@ -5953,7 +5944,6 @@ "resolved": "https://registry.npmjs.org/@sanity/types/-/types-4.22.0.tgz", "integrity": "sha512-VWAUc8Xtj4IipQt99SzudRldJWHfBVnde+g5qnLZ/Nc1MpFjGiRWu/3smRN5mPOqlrtUfrr0ho/fBZnjE1CEMg==", "license": "MIT", - "peer": true, "dependencies": { "@sanity/client": "^7.13.2", "@sanity/media-library-types": "^1.0.1" @@ -6582,11 +6572,10 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz", - "integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==", + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.1.0.tgz", + "integrity": "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -6604,11 +6593,10 @@ "license": "MIT" }, "node_modules/@types/react": { - "version": "19.2.9", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.9.tgz", - "integrity": "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==", + "version": "19.2.10", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.10.tgz", + "integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==", "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -6681,17 +6669,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.1.tgz", - "integrity": "sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz", + "integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.53.1", - "@typescript-eslint/type-utils": "8.53.1", - "@typescript-eslint/utils": "8.53.1", - "@typescript-eslint/visitor-keys": "8.53.1", + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/type-utils": "8.54.0", + "@typescript-eslint/utils": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.4.0" @@ -6704,7 +6692,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.53.1", + "@typescript-eslint/parser": "^8.54.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <6.0.0" } @@ -6720,17 +6708,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.1.tgz", - "integrity": "sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz", + "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { - "@typescript-eslint/scope-manager": "8.53.1", - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/typescript-estree": "8.53.1", - "@typescript-eslint/visitor-keys": "8.53.1", + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", "debug": "^4.4.3" }, "engines": { @@ -6746,14 +6733,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.53.1.tgz", - "integrity": "sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz", + "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.53.1", - "@typescript-eslint/types": "^8.53.1", + "@typescript-eslint/tsconfig-utils": "^8.54.0", + "@typescript-eslint/types": "^8.54.0", "debug": "^4.4.3" }, "engines": { @@ -6768,14 +6755,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.1.tgz", - "integrity": "sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz", + "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/visitor-keys": "8.53.1" + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6786,9 +6773,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.1.tgz", - "integrity": "sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz", + "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==", "dev": true, "license": "MIT", "engines": { @@ -6803,15 +6790,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.53.1.tgz", - "integrity": "sha512-MOrdtNvyhy0rHyv0ENzub1d4wQYKb2NmIqG7qEqPWFW7Mpy2jzFC3pQ2yKDvirZB7jypm5uGjF2Qqs6OIqu47w==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz", + "integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/typescript-estree": "8.53.1", - "@typescript-eslint/utils": "8.53.1", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/utils": "8.54.0", "debug": "^4.4.3", "ts-api-utils": "^2.4.0" }, @@ -6828,9 +6815,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.1.tgz", - "integrity": "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz", + "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==", "dev": true, "license": "MIT", "engines": { @@ -6842,16 +6829,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.1.tgz", - "integrity": "sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz", + "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.53.1", - "@typescript-eslint/tsconfig-utils": "8.53.1", - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/visitor-keys": "8.53.1", + "@typescript-eslint/project-service": "8.54.0", + "@typescript-eslint/tsconfig-utils": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", "debug": "^4.4.3", "minimatch": "^9.0.5", "semver": "^7.7.3", @@ -6909,16 +6896,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.53.1.tgz", - "integrity": "sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz", + "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.53.1", - "@typescript-eslint/types": "8.53.1", - "@typescript-eslint/typescript-estree": "8.53.1" + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6933,13 +6920,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.1.tgz", - "integrity": "sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz", + "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.53.1", + "@typescript-eslint/types": "8.54.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -7065,7 +7052,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -7523,13 +7509,13 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", - "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.0.tgz", + "integrity": "sha512-AvDcMxJ34W4Wgy4KBIIePQTAOP1Ie2WFwkQp3dB7FQ/f0lI5+nM96zUnYEOE1P9sEg0es5VCP0HxiWu5fUHZAQ==", "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.5", - "core-js-compat": "^3.43.0" + "@babel/helper-define-polyfill-provider": "^0.6.6", + "core-js-compat": "^3.48.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" @@ -7588,9 +7574,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.9.18", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.18.tgz", - "integrity": "sha512-e23vBV1ZLfjb9apvfPk4rHVu2ry6RIr2Wfs+O324okSidrX7pTAnEJPCh/O5BtRlr7QtZI7ktOP3vsqr7Z5XoA==", + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.js" @@ -7696,7 +7682,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -8963,9 +8948,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.278", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.278.tgz", - "integrity": "sha512-dQ0tM1svDRQOwxnXxm+twlGTjr9Upvt8UFWAgmLsxEzFQxhbti4VwxmMjsDxVC51Zo84swW7FVCXEV+VAkhuPw==", + "version": "1.5.283", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.283.tgz", + "integrity": "sha512-3vifjt1HgrGW/h76UEeny+adYApveS9dH2h3p57JYzBSXJIKUJAvtmIytDKjcSCt9xHfrNCFJ7gts6vkhuq++w==", "license": "ISC" }, "node_modules/emoji-regex": { @@ -9183,7 +9168,6 @@ "integrity": "sha512-jd0f4NHbD6cALCyGElNpGAOtWxSq46l9X/sWB0Nzd5er4Kz2YTm+Vl0qKFT9KUJvD8+fiO8AvoHhFvEatfVixA==", "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -9258,7 +9242,6 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -10607,9 +10590,9 @@ } }, "node_modules/groq-js": { - "version": "1.25.0", - "resolved": "https://registry.npmjs.org/groq-js/-/groq-js-1.25.0.tgz", - "integrity": "sha512-ShVrNkIg/IRxyyNQoh1orC/UD8FIUSKwwV5BKF4opIaoPCPeI0V/iFQL4jgB8ve67f5LZkDlmd4PCMT7I+3swA==", + "version": "1.26.0", + "resolved": "https://registry.npmjs.org/groq-js/-/groq-js-1.26.0.tgz", + "integrity": "sha512-1WxWfmeownBbB2UhvFGyLT3yl/NFGF2qUoev+650Or2qDnoyXjvL83lwspUFuG4piWdDRh2iETljXKoDxacH+w==", "license": "MIT", "dependencies": { "debug": "^4.3.4" @@ -10954,7 +10937,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.23.2" } @@ -12196,7 +12178,6 @@ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-26.1.0.tgz", "integrity": "sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==", "license": "MIT", - "peer": true, "dependencies": { "cssstyle": "^4.2.1", "data-urls": "^5.0.0", @@ -14241,9 +14222,9 @@ } }, "node_modules/react": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", - "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -14271,16 +14252,15 @@ } }, "node_modules/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.3" + "react": "^19.2.4" } }, "node_modules/react-fast-compare": { @@ -14339,11 +14319,10 @@ } }, "node_modules/react-is": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.3.tgz", - "integrity": "sha512-qJNJfu81ByyabuG7hPFEbXqNcWSU3+eVus+KJs+0ncpGfMyYdvSmxiJxbWR65lYi1I+/0HBcliO029gc4F+PnA==", - "license": "MIT", - "peer": true + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz", + "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==", + "license": "MIT" }, "node_modules/react-redux": { "version": "9.2.0", @@ -14663,8 +14642,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", @@ -14907,9 +14885,9 @@ } }, "node_modules/rollup": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.56.0.tgz", - "integrity": "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg==", + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", "license": "MIT", "dependencies": { "@types/estree": "1.0.8" @@ -14922,31 +14900,31 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.56.0", - "@rollup/rollup-android-arm64": "4.56.0", - "@rollup/rollup-darwin-arm64": "4.56.0", - "@rollup/rollup-darwin-x64": "4.56.0", - "@rollup/rollup-freebsd-arm64": "4.56.0", - "@rollup/rollup-freebsd-x64": "4.56.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.56.0", - "@rollup/rollup-linux-arm-musleabihf": "4.56.0", - "@rollup/rollup-linux-arm64-gnu": "4.56.0", - "@rollup/rollup-linux-arm64-musl": "4.56.0", - "@rollup/rollup-linux-loong64-gnu": "4.56.0", - "@rollup/rollup-linux-loong64-musl": "4.56.0", - "@rollup/rollup-linux-ppc64-gnu": "4.56.0", - "@rollup/rollup-linux-ppc64-musl": "4.56.0", - "@rollup/rollup-linux-riscv64-gnu": "4.56.0", - "@rollup/rollup-linux-riscv64-musl": "4.56.0", - "@rollup/rollup-linux-s390x-gnu": "4.56.0", - "@rollup/rollup-linux-x64-gnu": "4.56.0", - "@rollup/rollup-linux-x64-musl": "4.56.0", - "@rollup/rollup-openbsd-x64": "4.56.0", - "@rollup/rollup-openharmony-arm64": "4.56.0", - "@rollup/rollup-win32-arm64-msvc": "4.56.0", - "@rollup/rollup-win32-ia32-msvc": "4.56.0", - "@rollup/rollup-win32-x64-gnu": "4.56.0", - "@rollup/rollup-win32-x64-msvc": "4.56.0", + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" } }, @@ -14993,7 +14971,6 @@ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", "license": "Apache-2.0", - "peer": true, "dependencies": { "tslib": "^2.1.0" } @@ -15105,7 +15082,6 @@ "resolved": "https://registry.npmjs.org/sanity/-/sanity-4.22.0.tgz", "integrity": "sha512-wBmr/euVC6Kvni1gKP2qwkEsyxGQlfnPOhjowT7tjm0a0eOvBwS6uHnHtjr24wjhf8PeongurQzMewx2tTP8Wg==", "license": "MIT", - "peer": true, "dependencies": { "@date-fns/tz": "^1.4.1", "@dnd-kit/core": "^6.3.1", @@ -16145,8 +16121,7 @@ "version": "0.120.0", "resolved": "https://registry.npmjs.org/slate/-/slate-0.120.0.tgz", "integrity": "sha512-CXK/DADGgMZb4z9RTtXylzIDOxvmNJEF9bXV2bAGkLWhQ3rm7GORY9q0H/W41YJvAGZsLbH7nnrhMYr550hWDQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/slate-dom": { "version": "0.119.0", @@ -16650,7 +16625,6 @@ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.3.8.tgz", "integrity": "sha512-Kq/W41AKQloOqKM39zfaMdJ4BcYDw/N5CIq4/GTI0YjU6pKcZ1KKhk6b4du0a+6RA9pIfOP/eu94Ge7cu+PDCA==", "license": "MIT", - "peer": true, "dependencies": { "@emotion/is-prop-valid": "1.4.0", "@emotion/unitless": "0.10.0", @@ -16844,7 +16818,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -17128,7 +17101,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -17138,16 +17110,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.53.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.53.1.tgz", - "integrity": "sha512-gB+EVQfP5RDElh9ittfXlhZJdjSU4jUSTyE2+ia8CYyNvet4ElfaLlAIqDvQV9JPknKx0jQH1racTYe/4LaLSg==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.54.0.tgz", + "integrity": "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.53.1", - "@typescript-eslint/parser": "8.53.1", - "@typescript-eslint/typescript-estree": "8.53.1", - "@typescript-eslint/utils": "8.53.1" + "@typescript-eslint/eslint-plugin": "8.54.0", + "@typescript-eslint/parser": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/utils": "8.54.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -17485,7 +17457,6 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "license": "MIT", - "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -17596,7 +17567,6 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -18041,9 +18011,9 @@ } }, "node_modules/xstate": { - "version": "5.25.1", - "resolved": "https://registry.npmjs.org/xstate/-/xstate-5.25.1.tgz", - "integrity": "sha512-oyvsNH5pF2qkHmiHEMdWqc3OjDtoZOH2MTAI35r01f/ZQWOD+VLOiYqo65UgQET0XMA5s9eRm8fnsIo+82biEw==", + "version": "5.26.0", + "resolved": "https://registry.npmjs.org/xstate/-/xstate-5.26.0.tgz", + "integrity": "sha512-Fvi9VBoqHgsGYLU2NTag8xDTWtKqUC0+ue7EAhBNBb06wf620QEy05upBaEI1VLMzIn63zugLV8nHb69ZUWYAA==", "license": "MIT", "funding": { "type": "opencollective", diff --git a/studio/package.json b/studio/package.json index 0137d201..576dfaa3 100644 --- a/studio/package.json +++ b/studio/package.json @@ -1,7 +1,7 @@ { "name": "devlovers", "private": true, - "version": "0.5.0", + "version": "0.5.1", "main": "package.json", "license": "UNLICENSED", "scripts": { From 0b726bf73b6d23c5ddd819f2355f7136f9bfebbc Mon Sep 17 00:00:00 2001 From: Viktor Svertoka Date: Sun, 1 Feb 2026 00:44:07 +0200 Subject: [PATCH 14/61] Host (#238) * feat(md) add netlify status * feat(files): add packages * fix(auth): use currentTarget for email input validity * fix(auth): use currentTarget for email input validity * fix(auth): use currentTarget in password field --- frontend/components/auth/fields/NameField.tsx | 4 ++-- frontend/components/auth/fields/PasswordField.tsx | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/components/auth/fields/NameField.tsx b/frontend/components/auth/fields/NameField.tsx index b143b0e0..f2ddcee7 100644 --- a/frontend/components/auth/fields/NameField.tsx +++ b/frontend/components/auth/fields/NameField.tsx @@ -12,7 +12,7 @@ export function NameField({ const t = useTranslations("auth.fields"); const handleInvalid = (e: React.InvalidEvent) => { - const input = e.target; + const input = e.currentTarget; if (input.validity.valueMissing) { input.setCustomValidity(t("validation.required")); } @@ -33,4 +33,4 @@ export function NameField({ onInput={handleInput} /> ); -} \ No newline at end of file +} diff --git a/frontend/components/auth/fields/PasswordField.tsx b/frontend/components/auth/fields/PasswordField.tsx index cbc36d87..4f78d772 100644 --- a/frontend/components/auth/fields/PasswordField.tsx +++ b/frontend/components/auth/fields/PasswordField.tsx @@ -16,7 +16,7 @@ export function PasswordField({ const [visible, setVisible] = useState(false); const handleInvalid = (e: React.InvalidEvent) => { - const input = e.target; + const input = e.currentTarget; if (input.validity.valueMissing) { input.setCustomValidity(t("validation.required")); } else if (input.validity.tooShort && minLength) { @@ -53,4 +53,4 @@ export function PasswordField({
      ); -} \ No newline at end of file +} From 7edf42b3541986a621c2d1031d381edcec8f2e99 Mon Sep 17 00:00:00 2001 From: liudmylasovetovs Date: Sat, 31 Jan 2026 14:51:15 -0800 Subject: [PATCH 15/61] (SP 1) [Shop UI] Add page metadata across shop routes --- .../[locale]/shop/admin/orders/[id]/page.tsx | 6 + .../app/[locale]/shop/admin/orders/page.tsx | 7 + frontend/app/[locale]/shop/admin/page.tsx | 7 +- .../shop/admin/products/[id]/edit/page.tsx | 8 +- .../[locale]/shop/admin/products/new/page.tsx | 7 +- .../app/[locale]/shop/admin/products/page.tsx | 6 +- .../app/[locale]/shop/cart/CartPageClient.tsx | 463 ++++++++++++++++++ frontend/app/[locale]/shop/cart/page.tsx | 410 +--------------- .../app/[locale]/shop/checkout/error/page.tsx | 8 +- .../shop/checkout/payment/[orderId]/page.tsx | 6 + .../success/OrderStatusAutoRefresh.tsx | 1 - .../[locale]/shop/checkout/success/page.tsx | 18 +- .../app/[locale]/shop/orders/[id]/page.tsx | 5 + frontend/app/[locale]/shop/orders/page.tsx | 7 + frontend/app/[locale]/shop/page.tsx | 6 + .../[locale]/shop/products/[slug]/page.tsx | 7 +- frontend/app/[locale]/shop/products/page.tsx | 6 + 17 files changed, 564 insertions(+), 414 deletions(-) create mode 100644 frontend/app/[locale]/shop/cart/CartPageClient.tsx diff --git a/frontend/app/[locale]/shop/admin/orders/[id]/page.tsx b/frontend/app/[locale]/shop/admin/orders/[id]/page.tsx index 007b076f..af75a718 100644 --- a/frontend/app/[locale]/shop/admin/orders/[id]/page.tsx +++ b/frontend/app/[locale]/shop/admin/orders/[id]/page.tsx @@ -12,6 +12,12 @@ import { import { fromDbMoney } from '@/lib/shop/money'; import { ShopAdminTopbar } from '@/components/shop/admin/shop-admin-topbar'; import { guardShopAdminPage } from '@/lib/auth/guard-shop-admin-page'; +import { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'Admin Order | DevLovers', + description: 'Review and manage order, including refunds and status checks.', +}; export const dynamic = 'force-dynamic'; diff --git a/frontend/app/[locale]/shop/admin/orders/page.tsx b/frontend/app/[locale]/shop/admin/orders/page.tsx index 5cf475e6..7979aa7f 100644 --- a/frontend/app/[locale]/shop/admin/orders/page.tsx +++ b/frontend/app/[locale]/shop/admin/orders/page.tsx @@ -13,6 +13,13 @@ import { AdminPagination } from '@/components/shop/admin/admin-pagination'; import { guardShopAdminPage } from '@/lib/auth/guard-shop-admin-page'; import { CSRF_FORM_FIELD, issueCsrfToken } from '@/lib/security/csrf'; import { parsePage } from '@/lib/pagination'; +import { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'Admin Orders | DevLovers', + description: 'View and manage orders in the DevLovers shop catalog.', +}; + export const dynamic = 'force-dynamic'; diff --git a/frontend/app/[locale]/shop/admin/page.tsx b/frontend/app/[locale]/shop/admin/page.tsx index 1f980916..4c8a3fb9 100644 --- a/frontend/app/[locale]/shop/admin/page.tsx +++ b/frontend/app/[locale]/shop/admin/page.tsx @@ -1,9 +1,14 @@ -// frontend/app/[locale]/shop/admin/page.tsx import { Link } from '@/i18n/routing'; import { getTranslations } from 'next-intl/server'; import { ShopAdminTopbar } from '@/components/shop/admin/shop-admin-topbar'; import { guardShopAdminPage } from '@/lib/auth/guard-shop-admin-page'; +import { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'Shop Admin | DevLovers', + description: 'Manage products, orders, and settings for your shop.', +}; export const dynamic = 'force-dynamic'; diff --git a/frontend/app/[locale]/shop/admin/products/[id]/edit/page.tsx b/frontend/app/[locale]/shop/admin/products/[id]/edit/page.tsx index c117697d..32c71ae8 100644 --- a/frontend/app/[locale]/shop/admin/products/[id]/edit/page.tsx +++ b/frontend/app/[locale]/shop/admin/products/[id]/edit/page.tsx @@ -1,4 +1,3 @@ -// frontend/app/[locale]/shop/admin/products/[id]/edit/page.tsx import { notFound } from 'next/navigation'; import { eq } from 'drizzle-orm'; import { z } from 'zod'; @@ -12,6 +11,13 @@ import { products, productPrices } from '@/db/schema'; import type { CurrencyCode } from '@/lib/shop/currency'; import { currencyValues } from '@/lib/shop/currency'; import { issueCsrfToken } from '@/lib/security/csrf'; +import { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'Edit Product | DevLovers', + description: 'Edit an existing product in the DevLovers shop catalog.', +}; + export const dynamic = 'force-dynamic'; diff --git a/frontend/app/[locale]/shop/admin/products/new/page.tsx b/frontend/app/[locale]/shop/admin/products/new/page.tsx index b40a26d0..89cc4de4 100644 --- a/frontend/app/[locale]/shop/admin/products/new/page.tsx +++ b/frontend/app/[locale]/shop/admin/products/new/page.tsx @@ -1,9 +1,14 @@ -// frontend/app/[locale]/shop/admin/products/new/page.tsx import { ShopAdminTopbar } from '@/components/shop/admin/shop-admin-topbar'; import { guardShopAdminPage } from '@/lib/auth/guard-shop-admin-page'; import { issueCsrfToken } from '@/lib/security/csrf'; import { ProductForm } from '../_components/product-form'; +import { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'New Product | DevLovers', + description: 'Create a new product for the DevLovers shop catalog.', +}; export const dynamic = 'force-dynamic'; diff --git a/frontend/app/[locale]/shop/admin/products/page.tsx b/frontend/app/[locale]/shop/admin/products/page.tsx index 5bfb4d09..ed1dccf1 100644 --- a/frontend/app/[locale]/shop/admin/products/page.tsx +++ b/frontend/app/[locale]/shop/admin/products/page.tsx @@ -1,4 +1,3 @@ -// frontend/app/[locale]/shop/admin/products/page.tsx import { Link } from '@/i18n/routing'; import { and, desc, eq, sql } from 'drizzle-orm'; import { issueCsrfToken } from '@/lib/security/csrf'; @@ -17,7 +16,12 @@ import { import { formatMoney, resolveCurrencyFromLocale } from '@/lib/shop/currency'; import { parsePage } from '@/lib/pagination'; import { getTranslations } from 'next-intl/server'; +import { Metadata } from 'next'; +export const metadata: Metadata = { + title: 'Admin Products | DevLovers', + description: 'Create, edit, activate, and manage product catalog.', +}; export const dynamic = 'force-dynamic'; const PAGE_SIZE = 25; diff --git a/frontend/app/[locale]/shop/cart/CartPageClient.tsx b/frontend/app/[locale]/shop/cart/CartPageClient.tsx new file mode 100644 index 00000000..acf8dc50 --- /dev/null +++ b/frontend/app/[locale]/shop/cart/CartPageClient.tsx @@ -0,0 +1,463 @@ +'use client'; + +import { useState } from 'react'; +import Image from 'next/image'; +import { useParams } from 'next/navigation'; +import { useRouter, Link } from '@/i18n/routing'; +import { useTranslations } from 'next-intl'; +import { cn } from '@/lib/utils'; +import { Minus, Plus, Trash2, ShoppingBag } from 'lucide-react'; + +import { useCart } from '@/components/shop/cart-provider'; +import { generateIdempotencyKey } from '@/lib/shop/idempotency'; +import { formatMoney } from '@/lib/shop/currency'; +import { + SHOP_FOCUS, + SHOP_LINK_BASE, + SHOP_LINK_MD, + SHOP_LINK_XS, + SHOP_DISABLED, + SHOP_CHIP_INTERACTIVE, + SHOP_CHIP_SHADOW_HOVER, + SHOP_CHIP_BORDER_HOVER, + SHOP_STEPPER_BUTTON_BASE, + SHOP_CTA_BASE, + SHOP_CTA_INTERACTIVE, + SHOP_CTA_INSET, + SHOP_CTA_WAVE, + shopCtaGradient, +} from '@/lib/shop/ui-classes'; + +const SHOP_PRODUCT_LINK = cn( + 'block truncate', + SHOP_LINK_BASE, + SHOP_LINK_MD, + SHOP_FOCUS +); + +const SHOP_STEPPER_BTN = cn( + SHOP_STEPPER_BUTTON_BASE, + 'h-8 w-8', + SHOP_CHIP_INTERACTIVE, + SHOP_CHIP_SHADOW_HOVER, + SHOP_CHIP_BORDER_HOVER, + SHOP_FOCUS, + SHOP_DISABLED +); + +const SHOP_HERO_CTA = cn( + SHOP_CTA_BASE, + SHOP_CTA_INTERACTIVE, + SHOP_FOCUS, + SHOP_DISABLED, + 'w-full justify-center gap-2 px-6 py-3 text-sm text-white', + 'shadow-[var(--shop-hero-btn-shadow)] hover:shadow-[var(--shop-hero-btn-shadow-hover)]' +); + +export default function CartPage() { + const { cart, updateQuantity, removeFromCart } = useCart(); + const router = useRouter(); + const t = useTranslations('shop.cart'); + const tColors = useTranslations('shop.catalog.colors'); + const [isCheckingOut, setIsCheckingOut] = useState(false); + const [checkoutError, setCheckoutError] = useState(null); + const [createdOrderId, setCreatedOrderId] = useState(null); + + const params = useParams<{ locale?: string }>(); + const locale = params.locale ?? 'en'; + const shopBase = '/shop'; + + const translateColor = (color: string | null | undefined): string | null => { + if (!color) return null; + const colorSlug = color.toLowerCase(); + try { + return tColors(colorSlug); + } catch { + return color; + } + }; + + async function handleCheckout() { + setCheckoutError(null); + setCreatedOrderId(null); + setIsCheckingOut(true); + + try { + const idempotencyKey = generateIdempotencyKey(); + + const response = await fetch('/api/shop/checkout', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Idempotency-Key': idempotencyKey, + }, + body: JSON.stringify({ + items: cart.items.map(item => ({ + productId: item.productId, + quantity: item.quantity, + selectedSize: item.selectedSize, + selectedColor: item.selectedColor, + })), + }), + }); + + const data = await response.json(); + + if (!response.ok) { + const message = + typeof data?.message === 'string' + ? data.message + : typeof data?.error === 'string' + ? data.error + : 'Unable to start checkout right now.'; + setCheckoutError(message); + return; + } + + if (!data?.orderId) { + setCheckoutError('Unexpected checkout response.'); + return; + } + + const paymentProvider: string = data.paymentProvider ?? 'none'; + const clientSecret: string | null = + typeof data.clientSecret === 'string' && + data.clientSecret.trim().length > 0 + ? data.clientSecret + : null; + + const orderId = String(data.orderId); + setCreatedOrderId(orderId); + + if (paymentProvider === 'stripe' && clientSecret) { + router.push( + `${shopBase}/checkout/payment/${encodeURIComponent( + orderId + )}?clientSecret=${encodeURIComponent(clientSecret)}&clearCart=1` + ); + return; + } + + const paymentsDisabledFlag = + paymentProvider !== 'stripe' || !clientSecret + ? '&paymentsDisabled=true' + : ''; + + router.push( + `${shopBase}/checkout/success?orderId=${encodeURIComponent( + orderId + )}&clearCart=1${paymentsDisabledFlag}` + ); + } catch { + setCheckoutError('Unable to start checkout right now.'); + } finally { + setIsCheckingOut(false); + } + } + + if (cart.items.length === 0) { + return ( +
      +
      +
      +
      + ); + } + + return ( +
      +

      + {t('title')} +

      + +
      +
      +
        + {cart.removed.length > 0 && ( +
      • + {t('alerts.itemsRemoved')} +
      • + )} + + {cart.items.map(item => ( +
      • +
        +
        + {item.title} +
        + +
        +
        +
        + + {item.title} + + + {(item.selectedSize || item.selectedColor) && ( +

        + {[ + translateColor(item.selectedColor), + item.selectedSize, + ] + .filter(Boolean) + .join(' / ')} +

        + )} +
        + + +
        + +
        +
        + + + + {item.quantity} + + + + + {item.quantity >= item.stock && ( + + {t('actions.maxStock', { stock: item.stock })} + + )} +
        + + + {formatMoney( + item.lineTotalMinor, + item.currency, + locale + )} + +
        +
        +
        +
      • + ))} +
      +
      + + +
      +
      + ); +} diff --git a/frontend/app/[locale]/shop/cart/page.tsx b/frontend/app/[locale]/shop/cart/page.tsx index 658fe356..dd15d65e 100644 --- a/frontend/app/[locale]/shop/cart/page.tsx +++ b/frontend/app/[locale]/shop/cart/page.tsx @@ -1,407 +1,11 @@ -'use client'; +import type { Metadata } from 'next'; +import CartPageClient from './CartPageClient'; -import { useState } from 'react'; -import Image from 'next/image'; -import { useParams } from 'next/navigation'; -import { useRouter, Link } from '@/i18n/routing'; -import { useTranslations } from 'next-intl'; -import { cn } from '@/lib/utils'; -import { Minus, Plus, Trash2, ShoppingBag } from 'lucide-react'; - -import { useCart } from '@/components/shop/cart-provider'; -import { generateIdempotencyKey } from '@/lib/shop/idempotency'; -import { formatMoney } from '@/lib/shop/currency'; -import { - SHOP_FOCUS, - SHOP_LINK_BASE, - SHOP_LINK_MD, - SHOP_LINK_XS, - SHOP_DISABLED, - SHOP_CHIP_INTERACTIVE, - SHOP_CHIP_SHADOW_HOVER, - SHOP_CHIP_BORDER_HOVER, - SHOP_STEPPER_BUTTON_BASE, - SHOP_CTA_BASE, - SHOP_CTA_INTERACTIVE, - SHOP_CTA_INSET, - SHOP_CTA_WAVE, - shopCtaGradient, -} from '@/lib/shop/ui-classes'; - -const SHOP_PRODUCT_LINK = cn( - 'block truncate', - SHOP_LINK_BASE, - SHOP_LINK_MD, - SHOP_FOCUS -); - -const SHOP_STEPPER_BTN = cn( - SHOP_STEPPER_BUTTON_BASE, - 'h-8 w-8', - SHOP_CHIP_INTERACTIVE, - SHOP_CHIP_SHADOW_HOVER, - SHOP_CHIP_BORDER_HOVER, - SHOP_FOCUS, - SHOP_DISABLED -); - -const SHOP_HERO_CTA = cn( - SHOP_CTA_BASE, - SHOP_CTA_INTERACTIVE, - SHOP_FOCUS, - SHOP_DISABLED, - 'w-full justify-center gap-2 px-6 py-3 text-sm text-white', - 'shadow-[var(--shop-hero-btn-shadow)] hover:shadow-[var(--shop-hero-btn-shadow-hover)]' -); +export const metadata: Metadata = { + title: 'Cart | DevLovers', + description: 'Review items in your cart and proceed to checkout.', +}; export default function CartPage() { - const { cart, updateQuantity, removeFromCart } = useCart(); - const router = useRouter(); - const t = useTranslations('shop.cart'); - const tColors = useTranslations('shop.catalog.colors'); - const [isCheckingOut, setIsCheckingOut] = useState(false); - const [checkoutError, setCheckoutError] = useState(null); - const [createdOrderId, setCreatedOrderId] = useState(null); - - const params = useParams<{ locale?: string }>(); - const locale = params.locale ?? 'en'; - const shopBase = '/shop'; - - const translateColor = (color: string | null | undefined): string | null => { - if (!color) return null; - const colorSlug = color.toLowerCase(); - try { - return tColors(colorSlug); - } catch { - return color; - } - }; - - async function handleCheckout() { - setCheckoutError(null); - setCreatedOrderId(null); - setIsCheckingOut(true); - - try { - const idempotencyKey = generateIdempotencyKey(); - - const response = await fetch('/api/shop/checkout', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Idempotency-Key': idempotencyKey, - }, - body: JSON.stringify({ - items: cart.items.map(item => ({ - productId: item.productId, - quantity: item.quantity, - selectedSize: item.selectedSize, - selectedColor: item.selectedColor, - })), - }), - }); - - const data = await response.json(); - - if (!response.ok) { - const message = - typeof data?.message === 'string' - ? data.message - : typeof data?.error === 'string' - ? data.error - : 'Unable to start checkout right now.'; - setCheckoutError(message); - return; - } - - if (!data?.orderId) { - setCheckoutError('Unexpected checkout response.'); - return; - } - - const paymentProvider: string = data.paymentProvider ?? 'none'; - const clientSecret: string | null = - typeof data.clientSecret === 'string' && - data.clientSecret.trim().length > 0 - ? data.clientSecret - : null; - - const orderId = String(data.orderId); - setCreatedOrderId(orderId); - - if (paymentProvider === 'stripe' && clientSecret) { - router.push( - `${shopBase}/checkout/payment/${encodeURIComponent( - orderId - )}?clientSecret=${encodeURIComponent(clientSecret)}&clearCart=1` - ); - return; - } - - const paymentsDisabledFlag = - paymentProvider !== 'stripe' || !clientSecret - ? '&paymentsDisabled=true' - : ''; - - router.push( - `${shopBase}/checkout/success?orderId=${encodeURIComponent( - orderId - )}&clearCart=1${paymentsDisabledFlag}` - ); - } catch { - setCheckoutError('Unable to start checkout right now.'); - } finally { - setIsCheckingOut(false); - } - } - - if (cart.items.length === 0) { - return ( -
      -
      -
      -
      - ); - } - - return ( -
      -

      - {t('title')} -

      - -
      -
      -
        - {cart.removed.length > 0 && ( -
      • - {t('alerts.itemsRemoved')} -
      • - )} - - {cart.items.map(item => ( -
      • -
        -
        - {item.title} -
        - -
        -
        -
        - - {item.title} - - - {(item.selectedSize || item.selectedColor) && ( -

        - {[translateColor(item.selectedColor), item.selectedSize] - .filter(Boolean) - .join(' / ')} -

        - )} -
        - - -
        - -
        -
        - - - - {item.quantity} - - - - - {item.quantity >= item.stock && ( - - {t('actions.maxStock', { stock: item.stock })} - - )} -
        - - - {formatMoney(item.lineTotalMinor, item.currency, locale)} - -
        -
        -
        -
      • - ))} -
      -
      - - -
      -
      - ); + return ; } diff --git a/frontend/app/[locale]/shop/checkout/error/page.tsx b/frontend/app/[locale]/shop/checkout/error/page.tsx index b06d402c..86a4d5a0 100644 --- a/frontend/app/[locale]/shop/checkout/error/page.tsx +++ b/frontend/app/[locale]/shop/checkout/error/page.tsx @@ -1,4 +1,3 @@ -// frontend/app/[locale]/shop/checkout/error/page.tsx import { Link } from '@/i18n/routing'; import { getTranslations } from 'next-intl/server'; @@ -19,6 +18,13 @@ import { SHOP_OUTLINE_BTN_BASE, SHOP_OUTLINE_BTN_INTERACTIVE, } from '@/lib/shop/ui-classes'; +import { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'Checkout Error | DevLovers', + description: + 'We couldn’t complete the checkout. Try again or contact support.', +}; export const dynamic = 'force-dynamic'; export const revalidate = 0; diff --git a/frontend/app/[locale]/shop/checkout/payment/[orderId]/page.tsx b/frontend/app/[locale]/shop/checkout/payment/[orderId]/page.tsx index 0a76aec6..182dc50d 100644 --- a/frontend/app/[locale]/shop/checkout/payment/[orderId]/page.tsx +++ b/frontend/app/[locale]/shop/checkout/payment/[orderId]/page.tsx @@ -23,6 +23,12 @@ import { SHOP_OUTLINE_BTN_BASE, SHOP_OUTLINE_BTN_INTERACTIVE, } from '@/lib/shop/ui-classes'; +import { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'Checkout | DevLovers', + description: 'Complete payment securely for order', +}; export const dynamic = 'force-dynamic'; export const revalidate = 0; diff --git a/frontend/app/[locale]/shop/checkout/success/OrderStatusAutoRefresh.tsx b/frontend/app/[locale]/shop/checkout/success/OrderStatusAutoRefresh.tsx index 1d1ff451..909bd0f2 100644 --- a/frontend/app/[locale]/shop/checkout/success/OrderStatusAutoRefresh.tsx +++ b/frontend/app/[locale]/shop/checkout/success/OrderStatusAutoRefresh.tsx @@ -1,4 +1,3 @@ -// frontend/app/[locale]/shop/checkout/success/OrderStatusAutoRefresh.tsx 'use client'; import { useEffect, useRef } from 'react'; diff --git a/frontend/app/[locale]/shop/checkout/success/page.tsx b/frontend/app/[locale]/shop/checkout/success/page.tsx index 686b3447..06813c66 100644 --- a/frontend/app/[locale]/shop/checkout/success/page.tsx +++ b/frontend/app/[locale]/shop/checkout/success/page.tsx @@ -1,4 +1,3 @@ -// frontend/app/[locale]/shop/checkout/success/page.tsx import { Link } from '@/i18n/routing'; import { getTranslations } from 'next-intl/server'; @@ -20,6 +19,13 @@ import { SHOP_OUTLINE_BTN_INTERACTIVE, shopCtaGradient, } from '@/lib/shop/ui-classes'; +import { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'Order Confirmed| DevLovers', + description: + 'Your order has been placed. You can track its status on the order page.', +}; export const dynamic = 'force-dynamic'; export const revalidate = 0; @@ -74,13 +80,19 @@ function HeroCtaInner({ children }: { children: React.ReactNode }) { {/* base gradient */}
      John Doe
      ", - "code": true - }, - { - "text": ". Рядки — " - }, - { - "text": "", - "code": true - }, - { - "text": ", заголовки стовпців — " - }, - { - "text": "", - "code": true - }, - { - "text": " — групує рядки заголовків таблиці." - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "", - "code": true - }, - { - "text": " — основна частина таблиці з даними." - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "", - "code": true - }, - { - "text": " — групує рядки підсумків (footer таблиці), відображається після tbody, але може рендеритися браузером перед ним для оптимізації." - } - ] - } - ] - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Приклад:" - } - ] - }, - { - "type": "code", - "language": "html", - "content": "
      ", - "code": true - }, - { - "text": ", комірки — " - }, - { - "text": "", - "code": true - }, - { - "text": "." - } - ] - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Приклад:" - } - ] - }, - { - "type": "code", - "language": "html", - "content": "\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
      Ім'яВік
      Іван25
      \r" - } - ] - }, - { - "question": "60. Що таке thead, tbody та tfoot у HTML-таблиці і для чого вони потрібні?", - "category": "html", - "answerBlocks": [ - { - "type": "bulletList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "
      \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
      НазваЦіна
      Товар 1100 грн
      Товар 2200 грн
      Загалом300 грн
      \r" - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Переваги:" - } - ] - }, - { - "type": "bulletList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "Покращує семантику та доступність" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Дозволяє стилізувати різні частини таблиці окремо" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Браузери можуть фіксувати заголовки при прокрутці" - } - ] - } - ] - } - ] - }, - { - "question": "61. Що таке атрибути colspan і rowspan у HTML-таблицях?", - "category": "html", - "answerBlocks": [ - { - "type": "bulletList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "colspan", - "code": true - }, - { - "text": " — об'єднує комірку по кількох стовпцях." - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "rowspan", - "code": true - }, - { - "text": " — об'єднує комірку по кількох рядках." - }, - { - "type": "code", - "language": "html", - "content": "
    Об'єднано 2 стовпціОб'єднано 3 рядки
    \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
    Заголовок на 2 колонкиВертикальний заголовок
    Комірка 1Комірка 2
    Комірка 3Комірка 4Комірка 5
    \r" - } - ] - }, - { - "question": "62. Як зробити HTML-таблицю доступною для користувачів з екранними рідерами?", - "category": "html", - "answerBlocks": [ - { - "type": "bulletList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "Використовувати семантичні теги: " - }, - { - "text": "", - "code": true - }, - { - "text": ", " - }, - { - "text": "", - "code": true - }, - { - "text": ", " - }, - { - "text": "", - "code": true - }, - { - "text": ", " - }, - { - "text": "", - "code": true - }, - { - "text": ", " - }, - { - "text": "
    ", - "code": true - }, - { - "text": "." - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Додавати атрибут scope у " - }, - { - "text": "", - "code": true - }, - { - "text": " (scope=\"col\" або scope=\"row\") для зв'язку заголовків і комірок." - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Додавати " - }, - { - "text": "
    ", - "code": true - }, - { - "text": " для опису таблиці." - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Уникати зайвого " - }, - { - "text": "rowspan/colspan", - "code": true - }, - { - "text": ", якщо вони ускладнюють навігацію." - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Забезпечити достатній контраст та видимі фокуси при навігації з клавіатури." - } - ] - } - ] - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Приклад доступної таблиці:" - } - ] - }, - { - "type": "code", - "language": "html", - "content": "\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n
    \r\n Продажі за 2024 рік\r\n
    МісяцьПродажіПрибуток
    Січень1000200
    \r" - } - ] - }, - { - "question": "63. Що таке елемент canvas? І для чого він використовується?", - "category": "html", - "answerBlocks": [ - { - "type": "paragraph", - "children": [ - { - "text": "", - "code": true - }, - { - "text": " — растровий холст для малювання графіки через JavaScript API." - } - ] - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Використання:" - } - ] - }, - { - "type": "bulletList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "Ігри та інтерактивна графіка" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Складні візуалізації даних" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Обробка зображень" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Анімації та ефекти" - } - ] - } - ] - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Приклад:" - } - ] - }, - { - "type": "code", - "language": "html", - "content": "\r\n\r\n\r" - } - ] - }, - { - "question": "64. Різниця між canvas та svg? У яких випадках краще використовувати canvas, а в яких svg?", - "category": "html", - "answerBlocks": [ - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "", - "code": true - }, - { - "text": ":" - } - ] - }, - { - "type": "bulletList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "Растрова графіка (пікселі)" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Малювання через JavaScript API" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Підходить для ігор, складної анімації, обробки зображень" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Масштабування може погіршити якість" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Неможливо стилізувати окремі елементи CSS" - } - ] - } - ] - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "", - "code": true - }, - { - "text": ":" - } - ] - }, - { - "type": "bulletList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "Векторна графіка (математичні об'єкти)" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Описується XML-розміткою" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Підходить для іконок, логотипів, простих діаграм" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Безкінечне масштабування без втрати якості" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Можна стилізувати CSS та анімувати" - } - ] - } - ] - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Коли використовувати:" - } - ] - }, - { - "type": "paragraph", - "children": [ - { - "text": "Canvas", - "bold": true - }, - { - "text": " → ігри, складна графіка, обробка фото, реал-тайм анімації" - } - ] - }, - { - "type": "paragraph", - "children": [ - { - "text": "SVG", - "bold": true - }, - { - "text": " → іконки, логотипи, прості діаграми, масштабована графіка" - } - ] - } - ] - }, - { - "question": "65. Яка різниця між елементами svg та canvas?", - "category": "html", - "answerBlocks": [ - { - "type": "bulletList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "", - "code": true - }, - { - "text": " — векторна графіка, кожен елемент DOM-структурований, масштабований без втрати якості, легкий для анімації та стилізації через CSS/JS." - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "", - "code": true - }, - { - "text": " — растровий холст, малювання відбувається через JS API, не має внутрішньої семантики елементів, масштабування може знизити якість, підходить для ігор і складної графіки." - } - ] - } - ] - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Порівняння:" - } - ] - }, - { - "type": "table", - "header": [ - [ - { - "text": "Характеристика" - } - ], - [ - { - "text": "SVG" - } - ], - [ - { - "text": "Canvas" - } - ] - ], - "rows": [ - [ - [ - { - "text": "Тип графіки" - } - ], - [ - { - "text": "Векторна" - } - ], - [ - { - "text": "Растрова" - } - ] - ], - [ - [ - { - "text": "DOM-структура" - } - ], - [ - { - "text": "Так" - } - ], - [ - { - "text": "Ні" - } - ] - ], - [ - [ - { - "text": "Масштабування" - } - ], - [ - { - "text": "Без втрат" - } - ], - [ - { - "text": "З втратами" - } - ] - ], - [ - [ - { - "text": "Стилізація CSS" - } - ], - [ - { - "text": "Так" - } - ], - [ - { - "text": "Обмежено" - } - ] - ], - [ - [ - { - "text": "Продуктивність" - } - ], - [ - { - "text": "Повільніше при багатьох елементах" - } - ], - [ - { - "text": "Швидше для складної графіки" - } - ] - ] - ] - } - ] - }, - { - "question": "66. Що ви знаєте про SVG? Які є варіанти додавання SVG на сторінки сайту? Чим вони відрізняються?", - "category": "html", - "answerBlocks": [ - { - "type": "paragraph", - "children": [ - { - "text": "SVG (Scalable Vector Graphics)", - "bold": true - }, - { - "text": " — векторний формат для графіки, що масштабується без втрати якості." - } - ] - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Способи додавання SVG:" - } - ] - }, - { - "type": "numberedList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "Inline SVG", - "bold": true - }, - { - "text": " — безпосередньо в HTML:" - }, - { - "type": "code", - "language": "html", - "content": "\r\n \r\n\r" - }, - { - "type": "bulletList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "Переваги:", - "bold": true - }, - { - "text": " повний контроль CSS/JS, доступність" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Недоліки:", - "bold": true - }, - { - "text": " збільшує розмір HTML" - } - ] - } - ] - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Як зображення", - "bold": true - }, - { - "text": " — через " - }, - { - "text": "", - "code": true - }, - { - "text": ":" - }, - { - "type": "code", - "language": "html", - "content": "\"Іконка\"\r" - } - ] - } - ] - }, - { - "type": "bulletList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "Переваги:", - "bold": true - }, - { - "text": " кешується, компактний HTML" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Недоліки:", - "bold": true - }, - { - "text": " не можна стилізувати CSS" - } - ] - } - ] - }, - { - "type": "numberedList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "CSS background", - "bold": true - }, - { - "text": ":" - }, - { - "type": "code", - "language": "css", - "content": ".icon {\r\n background-image: url('icon.svg');\r\n}\r" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "Через `` або ``", - "bold": true - }, - { - "text": ":" - }, - { - "type": "code", - "language": "html", - "content": "\r" - } - ] - } - ] - }, - { - "type": "paragraph", - "children": [ - { - "text": "Найкращий вибір:", - "bold": true - }, - { - "text": " inline для іконок, що потребують стилізації; " - }, - { - "text": "", - "code": true - }, - { - "text": " для статичної графіки." - } - ] - } - ] - }, - { - "question": "67. Як створювати карти зображень у HTML?", - "category": "html", - "answerBlocks": [ - { - "type": "paragraph", - "children": [ - { - "text": "HTML-карта зображень (image map) дозволяє зробити частини зображення клікабельними." - } - ] - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Приклад:" - } - ] - }, - { - "type": "code", - "language": "html", - "content": "\"План\r\n\r\n\r\n \"Кімната\r\n \"Кімната\r\n \r\n\r" - }, - { - "type": "heading", - "level": 4, - "children": [ - { - "text": "Пояснення:" - } - ] - }, - { - "type": "bulletList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "usemap=\"#map1\"", - "code": true - }, - { - "text": " — прив'язка картинки до карти" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "", - "code": true - }, - { - "text": " — задає клікабельну область:" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "shape=\"rect\"", - "code": true - }, - { - "text": " — прямокутник (coords: x1,y1,x2,y2)" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "shape=\"circle\"", - "code": true - }, - { - "text": " — коло (coords: x,y,radius)" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "shape=\"poly\"", - "code": true - }, - { - "text": " — багатокутник (coords: x1,y1,x2,y2,x3,y3...)" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "href", - "code": true - }, - { - "text": " — посилання при кліку" - } - ] - }, - { - "type": "listItem", - "children": [ - { - "text": "alt", - "code": true - }, - { - "text": " — альтернативний текст для доступності" - } - ] - } - ] - } - ] - }, - { - "question": "68. Як додати аудіо та відео в HTML-документ за допомогою вбудованих тегів?", - "category": "html", - "answerBlocks": [ - { - "type": "bulletList", - "children": [ - { - "type": "listItem", - "children": [ - { - "text": "Аудіо", - "bold": true - }, - { - "text": " — тег " - }, - { - "text": "