From caffb6d8b4795b95d14b41c466a6ca5738026654 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20And=C4=9Bl?= Date: Fri, 16 Oct 2026 12:55:32 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20export=20+=20detail=20recept=C5=AF=20fe?= =?UTF-8?q?at:=20losov=C3=A1n=C3=AD=20j=C3=ADdel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 25 +- client/package.json | 5 + client/src/App.scss | 6 + client/src/Utils.tsx | 6 + client/src/components/Header.tsx | 13 +- client/src/components/MealGroupRecipe.tsx | 5 +- client/src/components/MealTemplateCard.tsx | 58 +- client/src/components/MealTemplateForm.tsx | 50 +- client/src/components/RecipeNote.tsx | 20 + client/src/components/SpinWheel.tsx | 99 ++ client/src/components/WheelConfetti.scss | 154 ++++ client/src/components/WheelConfetti.tsx | 170 ++++ client/src/components/WheelRecipe.tsx | 41 + .../src/components/modals/SpinWheelModal.scss | 361 ++++++++ .../src/components/modals/SpinWheelModal.tsx | 388 ++++++++ client/src/enums.ts | 11 + client/src/markdownBreaks.ts | 37 + client/src/markdownPdf.ts | 85 ++ client/src/pages/MealGroupsPage.tsx | 28 +- client/src/pages/templates.scss | 87 +- client/src/recipePdf.ts | 186 ++++ client/src/useRecipeExport.ts | 23 + client/yarn.lock | 859 +++++++++++++++++- 23 files changed, 2674 insertions(+), 43 deletions(-) create mode 100644 client/src/components/RecipeNote.tsx create mode 100644 client/src/components/SpinWheel.tsx create mode 100644 client/src/components/WheelConfetti.scss create mode 100644 client/src/components/WheelConfetti.tsx create mode 100644 client/src/components/WheelRecipe.tsx create mode 100644 client/src/components/modals/SpinWheelModal.scss create mode 100644 client/src/components/modals/SpinWheelModal.tsx create mode 100644 client/src/markdownBreaks.ts create mode 100644 client/src/markdownPdf.ts create mode 100644 client/src/recipePdf.ts create mode 100644 client/src/useRecipeExport.ts diff --git a/CLAUDE.md b/CLAUDE.md index de667d4..a7089f2 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -82,7 +82,7 @@ cd client && yarn build # tsc --noEmit + vite build → client/dist `IntakeStatsPanel`, `ActivityStatsPanel`, `SpendingStatsPanel`) - **Components:** `components/` (Header, CalorieLookup, MealSuggestions, NutrientsFieldset) a `components/modals/` (MealModal, ActivityModal, ApplyWorkoutModal, ApplyMealModal — - výběr z historie a receptů, SettingsModal, ImportModal) + výběr z historie a receptů, SettingsModal, ImportModal, SpinWheelModal — kolo štěstí) - **Klientské enumy** patří do `enums.ts` jako TypeScript `enum` s mapou popisků, ne jako inline union u komponenty (`DayTab`, `StatsTab`, `StatsRange`) - **Context:** `context/auth.tsx` (JWT), `context/settings.tsx` (světlý/tmavý motiv) @@ -343,6 +343,29 @@ ji při čtení doplní. Recepty se nabízejí v našeptávání i ve výběru z historie (`ApplyMealModal`) a jídlo stejného jména z historie se vedle receptu nenabízí podruhé. +**Postup receptu je Markdown.** V aplikaci ho vykresluje `RecipeNote` +(react-markdown, syrové HTML se nevykreslí), do PDF ho převádí `markdownPdf.ts` +nad stejným parserem (mdast). Obě cesty používají `markdownBreaks.ts`, který +z jednoduchého zalomení řádku dělá tvrdé — starší postupy jsou prostý text po +řádcích a jinak by se slily do jednoho odstavce. **Když se změní zobrazení +Markdownu v jedné cestě, je potřeba upravit i druhou.** + +**Export do PDF** (`recipePdf.ts`, pdfmake v prohlížeči, načte se líně až při +exportu) jde u jednoho receptu (ikona na kartě receptu) i hromadně tlačítkem +na stránce Jídla — exportují se recepty, které seznam po filtrech ukazuje, bez +filtru všechny. Víc receptů dostane titulní stranu s klikacím obsahem, každý +recept začíná na nové stránce. PDF nese suroviny, hodnoty porce, postup a fotky +jídla (první přes šířku, ostatní menší); pdfmake umí jen JPEG a PNG, jiné +formáty se vynechají. Server se exportu neúčastní. + +**Kolo štěstí** (`SpinWheelModal`, tlačítko v hlavičce) je převzaté z Luncheru. +Losuje ze všech jídel (skupin ze seznamu jídel — i objednávaných), nebo jen +z receptů, volitelně zúžených chutí a typem. U všech jídel se nejdřív vybere +nebo vylosuje zdroj a točí se až z jeho jídel — všech jídel jsou stovky +a kolo by s nimi bylo nepoužitelné. Padlý recept jde rovnou otevřít +(`WheelRecipe`, jen ke čtení) a stáhnout jako PDF. Nic se neukládá — kolo jen +rozhoduje. Konfety (`WheelConfetti`) mají vzhled jednoduchého stylu a Hell Girl. + Dřív pod stejným klíčem ležely i šablony hotových jídel (`kind: HOTOVE`, nebo bez `kind`). Ty zanikly: `loadRecipes` je při čtení vynechá (poznají se podle toho, že nemají suroviny) a první zápis receptu je z úložiště odklidí. **Jídla diff --git a/client/package.json b/client/package.json index af03ec3..3b48e52 100644 --- a/client/package.json +++ b/client/package.json @@ -10,15 +10,20 @@ "@fortawesome/free-regular-svg-icons": "^7.1.0", "@fortawesome/free-solid-svg-icons": "^7.1.0", "@fortawesome/react-fontawesome": "^3.1.0", + "@types/mdast": "4.0.4", "@types/node": "^24.10.0", + "@types/pdfmake": "0.3.3", "@types/react": "^19.2.2", "@types/react-dom": "^19.2.2", "@vitejs/plugin-react": "^5.1.0", "bootstrap": "^5.3.8", + "mdast-util-from-markdown": "2.0.3", + "pdfmake": "0.3.11", "react": "^19.2.0", "react-bootstrap": "^2.10.10", "react-dom": "^19.2.0", "react-jwt": "^1.3.0", + "react-markdown": "10.1.0", "react-router": "^7.9.5", "react-router-dom": "^7.9.5", "react-toastify": "^11.0.5", diff --git a/client/src/App.scss b/client/src/App.scss index 5a60594..a1b43bf 100644 --- a/client/src/App.scss +++ b/client/src/App.scss @@ -268,6 +268,12 @@ body { color: var(--ft-text-secondary); font-size: 0.9rem; } + + .page-header-actions { + display: flex; + flex-wrap: wrap; + gap: 8px; + } } // ============================================ diff --git a/client/src/Utils.tsx b/client/src/Utils.tsx index b02517b..569f17f 100644 --- a/client/src/Utils.tsx +++ b/client/src/Utils.tsx @@ -410,3 +410,9 @@ export const parseNutrientInput = (value: string): number | undefined => { /** Převede gramy živiny na text pro editační pole. */ export const formatNutrientInput = (grams?: number): string => grams == null ? '' : String(grams).replace('.', ','); + +/** Počet se správným tvarem slova: 1 porce, 2–4 porce, 5 porcí. */ +export const formatCount = (count: number, one: string, few: string, many: string): string => { + if (count === 1) return `${count} ${one}`; + return `${count} ${count >= 2 && count <= 4 ? few : many}`; +}; diff --git a/client/src/components/Header.tsx b/client/src/components/Header.tsx index 562ccb1..58da311 100644 --- a/client/src/components/Header.tsx +++ b/client/src/components/Header.tsx @@ -2,11 +2,12 @@ import { useState } from "react"; import { Navbar, Nav, NavDropdown } from "react-bootstrap"; import { Link, useLocation } from "react-router"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faSun, faMoon, faGear } from "@fortawesome/free-solid-svg-icons"; +import { faSun, faMoon, faGear, faDharmachakra } from "@fortawesome/free-solid-svg-icons"; import { useAuth } from "../context/auth"; import { useSettings, ThemePreference } from "../context/settings"; import { DEN_URL, JIDLA_URL, PROFIL_URL, STATISTIKY_URL, TRENINKY_URL } from "../routes"; import SettingsModal from "./modals/SettingsModal"; +import SpinWheelModal from "./modals/SpinWheelModal"; /** Položky hlavní navigace. */ const NAV_ITEMS = [ @@ -19,6 +20,7 @@ const NAV_ITEMS = [ export default function Header() { const [settingsOpen, setSettingsOpen] = useState(false); + const [wheelOpen, setWheelOpen] = useState(false); const auth = useAuth(); const settings = useSettings(); const location = useLocation(); @@ -49,6 +51,14 @@ export default function Header() { {item.label} ))} + - + {onExport && ( + + )} + {onEdit && ( + + )} + {onDelete && ( + + )} {!!template.ingredients.length && ( @@ -74,7 +92,7 @@ const MealTemplateCard = ({ template, busy, onEdit, onDelete }: Readonly) {recipe.totalPrice != null && <> · {formatPrice(recipe.totalPrice)}} - {template.note &&

{template.note}

} + {template.note && } ); }; diff --git a/client/src/components/MealTemplateForm.tsx b/client/src/components/MealTemplateForm.tsx index b5f1089..f0b04c6 100644 --- a/client/src/components/MealTemplateForm.tsx +++ b/client/src/components/MealTemplateForm.tsx @@ -5,6 +5,7 @@ import { RECIPE_FLAVOR_NAMES } from "../enums"; import { deriveRecipe, formatCalories, formatPrice, formatWeight, parseIntInput } from "../Utils"; import SourceInput from "./SourceInput"; import RecipeCategoryInput from "./RecipeCategoryInput"; +import RecipeNote from "./RecipeNote"; import RecipeIngredientsEditor, { DraftIngredient, emptyIngredient, toDraftIngredient, toIngredient, } from "./RecipeIngredientsEditor"; @@ -21,6 +22,9 @@ type Props = { onCancel: () => void, }; +/** Ukázka Markdownu, dokud je postup prázdný. */ +const NOTE_PLACEHOLDER = 'Jak se jídlo připravuje…\n\n## Příprava\n1. Cibuli nakrájejte nadrobno\n2. **Orestujte** dozlatova'; + /** Naplní formulář z receptu, nebo ho nechá prázdný pro nový. */ const createForm = (template?: MealTemplate, meal?: Props['meal']) => ({ name: meal?.name ?? template?.name ?? '', @@ -42,6 +46,7 @@ const MealTemplateForm = ({ template, meal, onSaved, onCancel }: Readonly template?.ingredients?.length ? template.ingredients.map(toDraftIngredient) : [emptyIngredient()]); const [error, setError] = useState(); const [busy, setBusy] = useState(false); + const [notePreview, setNotePreview] = useState(false); const flavorId = useId(); const update = (changes: Partial>) => @@ -178,14 +183,43 @@ const MealTemplateForm = ({ template, meal, onSaved, onCancel }: Readonly - Postup - update({ note: event.target.value })} - /> +
+ Postup + setNotePreview(value === 'preview')} + > + + Psát + + + Náhled + + +
+ {notePreview && form.note.trim() + ?
+ : ( + update({ note: event.target.value })} + /> + )} + + Markdown: ## nadpis, - odrážka, 1. krok, + {' '}**tučně**, *kurzíva* +
diff --git a/client/src/components/RecipeNote.tsx b/client/src/components/RecipeNote.tsx new file mode 100644 index 0000000..f1cd62e --- /dev/null +++ b/client/src/components/RecipeNote.tsx @@ -0,0 +1,20 @@ +import Markdown from "react-markdown"; +import { remarkLineBreaks } from "../markdownBreaks"; + +type Props = { + note: string, +}; + +const REMARK_PLUGINS = [remarkLineBreaks]; + +/** + * Postup receptu zapsaný v Markdownu. Syrové HTML react-markdown nevykreslí, + * takže postup nic nepodstrčí. Jednoduché zalomení řádku zůstává zalomením. + */ +const RecipeNote = ({ note }: Readonly) => ( +
+ {note} +
+); + +export default RecipeNote; diff --git a/client/src/components/SpinWheel.tsx b/client/src/components/SpinWheel.tsx new file mode 100644 index 0000000..fa4429e --- /dev/null +++ b/client/src/components/SpinWheel.tsx @@ -0,0 +1,99 @@ +/** Délka roztočení kola; po ní se ukáže výsledek. */ +export const SPIN_MS = 4800; +/** Počet barev výsečí, které se střídají dokola. */ +export const SEGMENT_COLORS = 6; + +const WHEEL_SIZE = 360; +const RADIUS = WHEEL_SIZE / 2 - 6; +const CENTER = WHEEL_SIZE / 2; +/** U víc výsečí jsou popisky nečitelné — názvy jsou v seznamu vedle kola. */ +const MAX_LABELED_SEGMENTS = 40; + +type Props = { + labels: string[], + /** Natočení kola ve stupních; kolo se k němu dotočí animací, když je `spinning`. */ + rotation: number, + spinning: boolean, + onSpinEnd: () => void, +}; + +const truncate = (text: string, max: number): string => + text.length > max ? `${text.slice(0, max - 1)}…` : text; + +/** Bod na kružnici; úhel ve stupních měřený od „12 hodin" po směru hodinových ručiček. */ +const polar = (angle: number, radius: number) => { + const rad = (angle - 90) * Math.PI / 180; + return { x: CENTER + radius * Math.cos(rad), y: CENTER + radius * Math.sin(rad) }; +}; + +const createSegmentPath = (start: number, end: number): string => { + if (end - start >= 360) { + return `M ${CENTER} ${CENTER - RADIUS} A ${RADIUS} ${RADIUS} 0 1 1 ${CENTER - 0.01} ${CENTER - RADIUS} Z`; + } + const a = polar(start, RADIUS); + const b = polar(end, RADIUS); + const large = end - start > 180 ? 1 : 0; + return `M ${CENTER} ${CENTER} L ${a.x} ${a.y} A ${RADIUS} ${RADIUS} 0 ${large} 1 ${b.x} ${b.y} Z`; +}; + +const getFontSize = (count: number): number => { + if (count > 24) return 8.5; + if (count > 14) return 10; + if (count > 8) return 12; + return 14; +}; + +/** Kolo štěstí s výsečí pro každou možnost — převzaté z Luncheru. */ +const SpinWheel = ({ labels, rotation, spinning, onSpinEnd }: Readonly) => { + const count = labels.length; + const angle = 360 / Math.max(count, 1); + const fontSize = getFontSize(count); + const maxChars = count > 14 ? 24 : 20; + const showLabels = count <= MAX_LABELED_SEGMENTS; + return ( +
+
+ { if (event.propertyName === 'transform') onSpinEnd(); }} + role="img" + aria-label={`Kolo s ${count} možnostmi`} + > + + {labels.map((label, index) => { + const start = index * angle; + const middle = start + angle / 2; + return ( + // Názvy se můžou opakovat (stejné jídlo z jiného zdroje), proto i index + + + {showLabels && ( + + {truncate(label, maxChars)} + + )} + + ); + })} + + +
+ ); +}; + +export default SpinWheel; diff --git a/client/src/components/WheelConfetti.scss b/client/src/components/WheelConfetti.scss new file mode 100644 index 0000000..daf95ae --- /dev/null +++ b/client/src/components/WheelConfetti.scss @@ -0,0 +1,154 @@ +// Konfety po výsledku kola štěstí, převzaté z Luncheru +.wheel-confetti { + position: fixed; + inset: 0; + z-index: 2000; // nad modalem (bootstrap 1055) + pointer-events: none; + overflow: hidden; + + > span { + position: absolute; + will-change: transform, opacity; + animation-fill-mode: both; + } +} + +// Vrh: rychle vzhůru do vrcholu (zpomalení), pak volný pád (zrychlení) +@keyframes confetti-throw { + 0% { transform: translate3d(0, 0, 0) rotate(0deg) scale(0.4); opacity: 0; animation-timing-function: cubic-bezier(0.2, 0.7, 0.4, 1); } + 6% { opacity: 1; } + 38% { transform: translate3d(var(--x1), var(--y1), 0) rotate(var(--r1)) scale(1); animation-timing-function: cubic-bezier(0.5, 0, 0.9, 0.6); } + 88% { opacity: 1; } + 100% { transform: translate3d(var(--x2), var(--y2), 0) rotate(var(--r2)) scale(1); opacity: 0; } +} + +// Déšť: plynulý pád shora s mírným houpáním +@keyframes confetti-rain { + 0% { transform: translate3d(0, 0, 0) rotate(0deg); opacity: 0; animation-timing-function: ease-in-out; } + 8% { opacity: 1; } + 40% { transform: translate3d(var(--x1), var(--y1), 0) rotate(var(--r1)); animation-timing-function: ease-in; } + 88% { opacity: 1; } + 100% { transform: translate3d(var(--x2), var(--y2), 0) rotate(var(--r2)); opacity: 0; } +} + +.confetti-emoji { + line-height: 1; +} + +// ============================================ +// Konfety ve stylu Hell Girl +// ============================================ +.confetti-hg { + filter: drop-shadow(0 0 6px rgb(200 16 46 / 0.55)) drop-shadow(0 3px 3px rgb(0 0 0 / 0.3)); +} + +.confetti-hg-badge { + padding: 4px 14px; + border: 1px solid #c8102e; + outline: 1px solid rgb(200 16 46 / 0.4); + outline-offset: 2px; + color: #fff4ee; + font-family: 'Shippori Mincho', 'Noto Serif JP', serif; + font-weight: 800; + font-size: 1.1rem; + letter-spacing: 1px; + white-space: nowrap; + text-shadow: 0 0 8px rgb(232 52 79 / 0.7); +} + +// Nápis „poslání do pekla" — černá deska s rudým lemem, vynoří se z mlhy +.confetti-hg-banner { + position: absolute; + top: 38%; + left: 50%; + transform: translate(-50%, -50%); + padding: 14px 48px; + font-family: 'Shippori Mincho', 'Noto Serif JP', serif; + font-weight: 800; + font-size: clamp(2rem, 7vw, 4rem); + letter-spacing: 0.3em; + white-space: nowrap; + color: #fff4ee; + background: linear-gradient(90deg, transparent, #12070a 15%, #12070a 85%, transparent); + border-top: 2px solid #c8102e; + border-bottom: 2px solid #c8102e; + text-shadow: 0 0 18px rgb(232 52 79 / 0.9), 0 0 4px #c8102e; + animation: hg-banner 2.6s ease both; +} + +@keyframes hg-banner { + 0% { transform: translate(-50%, -50%) scale(1.15); opacity: 0; filter: blur(12px); } + 25% { transform: translate(-50%, -50%) scale(1); opacity: 1; filter: blur(0); } + 75% { transform: translate(-50%, -50%) scale(1); opacity: 1; filter: blur(0); } + 100% { transform: translate(-50%, -50%) scale(0.96); opacity: 0; filter: blur(8px); } +} + +// Postava s hláškou — tmavý odznak s rudou září a bublina jako svitek +.confetti-voice { + position: absolute; + left: 16px; + bottom: 16px; + display: flex; + align-items: flex-end; + gap: 8px; + max-width: min(460px, calc(100vw - 32px)); + animation: confetti-voice 4.6s ease both; +} + +.confetti-avatar { + flex: none; + display: grid; + place-items: center; + width: 84px; + height: 84px; + margin-bottom: 8px; + font-size: 46px; + border-radius: 50%; + border: 4px solid #12070a; + background: radial-gradient(circle at 50% 60%, #3a0d16, #12070a); + box-shadow: 0 0 0 3px var(--speaker, #c8102e), 0 0 18px rgb(200 16 46 / 0.6); +} + +.confetti-bubble { + position: relative; + margin-bottom: 40px; + padding: 12px 16px; + background: #170c0f; + color: #f3e6e2; + border: 1px solid var(--speaker, #c8102e); + border-radius: 3px; + box-shadow: 0 0 16px rgb(0 0 0 / 0.6), 0 0 10px rgb(200 16 46 / 0.3); + font-family: 'Shippori Mincho', 'Noto Serif JP', serif; + + p { + margin: 0; + font-weight: 600; + font-size: 1rem; + line-height: 1.35; + } + + small { + display: block; + margin-top: 4px; + color: var(--speaker, #c8102e); + font-weight: 700; + } + + // ocásek bubliny směrem k postavě + &::before { + content: ''; + position: absolute; + left: -14px; + bottom: 14px; + border: 8px solid transparent; + border-right: 10px solid var(--speaker, #c8102e); + } +} + +@keyframes confetti-voice { + 0% { opacity: 0; transform: translateY(40px) scale(0.8); } + 10% { opacity: 1; transform: translateY(0) scale(1.03); } + 15% { transform: translateY(0) scale(1); } + 85% { opacity: 1; transform: translateY(0); } + 100% { opacity: 0; transform: translateY(20px); } +} diff --git a/client/src/components/WheelConfetti.tsx b/client/src/components/WheelConfetti.tsx new file mode 100644 index 0000000..2b5b452 --- /dev/null +++ b/client/src/components/WheelConfetti.tsx @@ -0,0 +1,170 @@ +import { CSSProperties, ReactNode, useEffect, useMemo, useState } from "react"; +import { createPortal } from "react-dom"; +import { StyleTheme } from "../enums"; +import "./WheelConfetti.scss"; + +type Props = { + theme: StyleTheme, + /** Mění se s každým výsledkem — nová hodnota spustí novou salvu. */ + burstKey: number, +}; + +type Speaker = { name: string, emoji: string, color: string, quote: string }; + +const SIMPLE_COLORS = ['#16a34a', '#0ea5e9', '#f59e0b', '#8b5cf6', '#ec4899', '#14b8a6', '#ef4444']; +const HG_EMOJI = ['🥀', '🏮', '⛩️', '🔥', '🕯️', '🌑', '🦋', '👁️', '🕸️', '🌸']; +// Hlášky z Jigoku Shōjo (Pekelná korespondence, „Ippen, shinde miru?", poslání do pekla...) +const HG_BADGES = ['いっぺん、死んでみる?', '地獄通信', '地獄流し', 'Ippen… shinde miru?', 'Pomsta vykonána', '0:00', 'Uvaž to dobře…', 'Smlouva uzavřena']; +const HG_BADGE_COLORS = ['#12070a', '#7a0c1f', '#2e0d24']; +/** Ai Enma a její služebníci s „hláškou" po výsledku — místo obrázků emoji. */ +const HG_SPEAKERS: Speaker[] = [ + { name: 'Ai Enma', emoji: '🥀', color: '#c8102e', quote: 'Smlouva je uzavřena. Tohle jídlo tě bude provázet až na věčnost.' }, + { name: 'Ichimoku Ren', emoji: '👁️', color: '#6b5a8a', quote: 'Prohlédl jsem svým okem všechna jídla. Tohle jediné obstálo.' }, + { name: 'Hone Onna', emoji: '💀', color: '#b8862b', quote: 'Dobré až na kost, zlato. Mně z toho stejně nic nezůstane.' }, + { name: 'Wanyūdō', emoji: '🔥', color: '#d9582b', quote: 'Já jsem kolo. Kolo rozhodlo. Víc k tomu nemám.' }, + { name: 'Kikuri', emoji: '🎀', color: '#d4407a', quote: 'Hihi! Tohle bych do pekla poslala jako první!' }, + { name: 'Babička', emoji: '🕸️', color: '#8c7672', quote: 'Ai, jídlo je na stole… jako každý den už čtyři sta let.' }, +]; +const DURATION_MS = 6400; + +const rand = (min: number, max: number) => min + Math.random() * (max - min); +const pick = (list: T[]): T => list[Math.floor(Math.random() * list.length)]; + +type Piece = { id: number, style: CSSProperties, content: ReactNode, className: string }; + +type Launch = 'cannon-left' | 'cannon-right' | 'burst' | 'rain'; + +const LAUNCHES: Launch[] = ['cannon-left', 'cannon-left', 'cannon-right', 'cannon-right', 'burst', 'rain']; + +/** Vlastní CSS proměnné dráhy kousku; React je v `style` přijme, typ CSSProperties je nezná. */ +const withVars = (style: CSSProperties, vars: Record): CSSProperties => + ({ ...style, ...vars }) as CSSProperties; + +/** + * Výchozí pozice a dráha jednoho kousku: děla z dolních rohů střílí šikmo vzhůru, + * „výbuch" se rozletí ze středu a déšť padá opožděně shora. Kousek nejprve vyletí + * do vrcholu (--x1/--y1) a pak dopadne mimo obrazovku (--x2/--y2). + */ +const createLaunchStyle = (launch: Launch): CSSProperties => { + let left: number, top: number, x1: number, y1: number, x2: number, y2: number, delay: number; + switch (launch) { + case 'cannon-left': + case 'cannon-right': { + const direction = launch === 'cannon-left' ? 1 : -1; + left = launch === 'cannon-left' ? rand(-3, 3) : rand(97, 103); + top = rand(88, 100); + x1 = direction * rand(12, 62); + y1 = -rand(45, 88); + x2 = x1 + direction * rand(6, 22); + y2 = rand(12, 25); + delay = rand(0, 450); + break; + } + case 'burst': { + const angle = rand(0, Math.PI * 2); + const radius = rand(18, 48); + left = 50; + top = 42; + x1 = Math.cos(angle) * radius; + y1 = Math.sin(angle) * radius * 0.8 - rand(5, 15); + x2 = x1 * 1.35; + y2 = rand(70, 85); + delay = rand(0, 250); + break; + } + default: { + left = rand(0, 100); + top = -12; + x1 = rand(-8, 8); + y1 = rand(35, 55); + x2 = x1 + rand(-10, 10); + y2 = rand(115, 125); + delay = rand(400, 1800); + } + } + const spin = rand(180, 720) * (Math.random() < 0.5 ? -1 : 1); + return withVars({ + left: `${left}vw`, + top: `${top}vh`, + animationName: launch === 'rain' ? 'confetti-rain' : 'confetti-throw', + animationDelay: `${delay}ms`, + animationDuration: `${rand(2600, 3900)}ms`, + }, { + '--x1': `${x1}vw`, + '--y1': `${y1}vh`, + '--x2': `${x2}vw`, + '--y2': `${y2}vh`, + '--r1': `${spin * 0.4}deg`, + '--r2': `${spin}deg`, + }); +}; + +const createPieces = (theme: StyleTheme): Piece[] => { + if (theme !== StyleTheme.HELL_GIRL) { + return Array.from({ length: 170 }, (_, id) => ({ + id, + className: 'confetti-paper', + content: null, + style: { + ...createLaunchStyle(pick(LAUNCHES)), + width: `${rand(6, 11)}px`, + height: `${rand(10, 18)}px`, + background: pick(SIMPLE_COLORS), + borderRadius: Math.random() < 0.3 ? '50%' : '2px', + }, + })); + } + return Array.from({ length: 50 }, (_, id) => { + // Samolepky se tolik netočí, ať jsou čitelné + const base = withVars(createLaunchStyle(pick(LAUNCHES)), { + '--r1': `${rand(-25, 25)}deg`, + '--r2': `${rand(-40, 40)}deg`, + }); + if (Math.random() < 0.35) { + return { id, className: 'confetti-hg confetti-hg-badge', content: pick(HG_BADGES), style: { ...base, background: pick(HG_BADGE_COLORS) } }; + } + return { id, className: 'confetti-hg confetti-emoji', content: pick(HG_EMOJI), style: { ...base, fontSize: `${rand(28, 52)}px` } }; + }); +}; + +/** Salva konfet po výsledku kola štěstí, ve vzhledu podle zvoleného stylu. Převzato z Luncheru. */ +const WheelConfetti = ({ theme, burstKey }: Readonly) => { + const [active, setActive] = useState(false); + + useEffect(() => { + if (!burstKey) return; + setActive(true); + const timeout = setTimeout(() => setActive(false), DURATION_MS + 900); + return () => clearTimeout(timeout); + }, [burstKey]); + + // Kousky i postava se losují jednou za salvu, ne při každém překreslení + // eslint-disable-next-line react-hooks/exhaustive-deps + const pieces = useMemo(() => createPieces(theme), [burstKey]); + const speaker = useMemo( + () => theme === StyleTheme.HELL_GIRL ? pick(HG_SPEAKERS) : undefined, + // eslint-disable-next-line react-hooks/exhaustive-deps + [burstKey], + ); + + if (!active || globalThis.matchMedia?.('(prefers-reduced-motion: reduce)').matches) return null; + + return createPortal( +
+ {theme === StyleTheme.HELL_GIRL &&
🏮 地獄流し 🏮
} + {pieces.map(piece => {piece.content})} + {speaker && ( +
+ {speaker.emoji} +
+

„{speaker.quote}"

+ — {speaker.name} +
+
+ )} +
, + document.body, + ); +}; + +export default WheelConfetti; diff --git a/client/src/components/WheelRecipe.tsx b/client/src/components/WheelRecipe.tsx new file mode 100644 index 0000000..5d89719 --- /dev/null +++ b/client/src/components/WheelRecipe.tsx @@ -0,0 +1,41 @@ +import { useEffect, useState } from "react"; +import { Spinner } from "react-bootstrap"; +import { MealGroup, MealPhoto, MealTemplate, getMealPhotos } from "../../../types"; +import { useRecipeExport } from "../useRecipeExport"; +import MealTemplateCard from "./MealTemplateCard"; + +type Props = { + group: MealGroup & { recipe: MealTemplate }, +}; + +/** + * Recept jídla, které padlo na kole štěstí — jen k nahlédnutí, ať se podle + * něj dá rovnou vařit. Upravuje se v seznamu jídel. + */ +const WheelRecipe = ({ group }: Readonly) => { + const [photo, setPhoto] = useState(); + const [loading, setLoading] = useState(group.photoCount > 0); + const { exporting, exportRecipes } = useRecipeExport(); + + useEffect(() => { + if (!group.photoCount) return; + getMealPhotos({ query: { name: group.name, source: group.source } }).then(response => { + setPhoto(response.data?.[0]); + setLoading(false); + }); + }, [group.name, group.source, group.photoCount]); + + return ( +
+ {loading &&
} + {photo && {group.name}} + exportRecipes([{ recipe: group.recipe, photoCount: group.photoCount }])} + /> +
+ ); +}; + +export default WheelRecipe; diff --git a/client/src/components/modals/SpinWheelModal.scss b/client/src/components/modals/SpinWheelModal.scss new file mode 100644 index 0000000..030f066 --- /dev/null +++ b/client/src/components/modals/SpinWheelModal.scss @@ -0,0 +1,361 @@ +// Kolo štěstí převzaté z Luncheru. Barvy výsečí jednoduchý styl odvozuje od +// primární barvy, styl Hell Girl je přebíjí vlastní paletou (viz níže) +:root { + --wheel-c0: var(--ft-primary); + --wheel-c1: #0ea5e9; + --wheel-c2: #f59e0b; + --wheel-c3: #8b5cf6; + --wheel-c4: #ec4899; + --wheel-c5: #14b8a6; + --wheel-label: #ffffff; + --wheel-rim: var(--ft-text); + --wheel-hub: var(--ft-bg-card); +} + +.spin-wheel-modal { + .spin-filters { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px 12px; + margin-bottom: 16px; + } + + .spin-category { + width: auto; + min-width: 180px; + } + + // Přepínače jako segmentovaná „pilulka" + .spin-source { + display: inline-flex; + gap: 2px; + padding: 3px; + border-radius: 999px; + background: var(--ft-bg-hover); + + > .btn { + padding: 6px 16px; + font-size: 0.9rem; + border: none !important; + border-radius: 999px !important; + box-shadow: none !important; + margin: 0 !important; + } + + > .btn-outline-primary { + background: transparent; + color: var(--ft-text-secondary); + + &:hover { + background: var(--ft-bg-card); + color: var(--ft-primary); + } + } + } + + .spin-layout { + display: grid; + grid-template-columns: minmax(0, 1fr) 240px; + gap: 20px; + align-items: start; + + @media (max-width: 768px) { + grid-template-columns: minmax(0, 1fr); + } + } + + .spin-wheel-column { + display: flex; + flex-direction: column; + align-items: center; + gap: 12px; + min-width: 0; + } + + .spin-empty { + width: 100%; + max-width: 380px; + aspect-ratio: 1; + display: flex; + align-items: center; + justify-content: center; + text-align: center; + padding: 24px; + border: 2px dashed var(--ft-border); + border-radius: 50%; + color: var(--ft-text-muted); + } + + .spin-result { + min-height: 1.6em; + font-size: 1.15rem; + text-align: center; + opacity: 0; + transform: translateY(6px); + transition: opacity 0.3s ease, transform 0.3s ease; + + &.visible { + opacity: 1; + transform: none; + } + + strong { + color: var(--ft-primary); + } + + small { + color: var(--ft-text-muted); + } + } + + .spin-actions { + display: flex; + flex-wrap: wrap; + gap: 8px; + justify-content: center; + } + + .spin-button { + min-width: 160px; + } + + .spin-options { + border: 1px solid var(--ft-border); + border-radius: var(--ft-radius); + background: var(--ft-bg); + max-height: 440px; + display: flex; + flex-direction: column; + min-width: 0; + + ul { + list-style: none; + margin: 0; + padding: 4px; + overflow-y: auto; + } + + li { + display: flex; + align-items: center; + gap: 8px; + padding: 4px 6px; + border-radius: var(--ft-radius-sm); + font-size: 0.85rem; + + &:hover { + background: var(--ft-bg-hover); + } + } + } + + .spin-options-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 8px 10px; + border-bottom: 1px solid var(--ft-border); + font-weight: 700; + font-size: 0.85rem; + } + + .spin-restore { + background: none; + border: none; + color: var(--ft-primary); + font-size: 0.8rem; + font-weight: 600; + padding: 0; + } + + .spin-dot { + flex: none; + width: 10px; + height: 10px; + border-radius: 50%; + } + + .spin-option-label { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .spin-option-kind { + flex: none; + max-width: 80px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: var(--ft-text-muted); + font-size: 0.7rem; + } + + .spin-remove { + flex: none; + background: none; + border: none; + color: var(--ft-text-muted); + padding: 2px 6px; + border-radius: 4px; + + &:hover:not(:disabled) { + color: var(--ft-danger); + background: var(--ft-bg-hover); + } + } + + // Recept, který padl — fotka nad kartou receptu + .wheel-recipe { + .template-card { + margin-bottom: 0; + } + } + + .wheel-recipe-photo { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + max-height: 280px; + min-height: 60px; + margin-bottom: 12px; + object-fit: cover; + border-radius: var(--ft-radius); + } +} + +.spin-wheel { + position: relative; + width: 100%; + max-width: 380px; + aspect-ratio: 1; +} + +.spin-wheel-svg { + width: 100%; + height: 100%; + display: block; + filter: drop-shadow(0 6px 12px rgb(0 0 0 / 0.18)); +} + +.spin-wheel-pointer { + position: absolute; + top: -6px; + left: 50%; + transform: translateX(-50%); + z-index: 2; + width: 0; + height: 0; + border-left: 14px solid transparent; + border-right: 14px solid transparent; + border-top: 26px solid var(--ft-danger); + filter: drop-shadow(0 2px 2px rgb(0 0 0 / 0.3)); +} + +.spin-wheel-rim { + fill: var(--wheel-rim); +} + +.spin-wheel-hub { + fill: var(--wheel-hub); + stroke: var(--wheel-rim); + stroke-width: 4; +} + +.spin-seg { + stroke: var(--wheel-hub); + stroke-width: 1.5; +} + +.spin-seg-label { + fill: var(--wheel-label); + font-weight: 700; + pointer-events: none; + paint-order: stroke; + stroke: rgb(0 0 0 / 0.25); + stroke-width: 2px; +} + +@for $i from 0 through 5 { + .spin-seg-#{$i} { + fill: var(--wheel-c#{$i}); + background: var(--wheel-c#{$i}); + } +} + +@media (prefers-reduced-motion: reduce) { + .spin-wheel-svg { + transition-duration: 0.6s !important; + } +} + +// ============================================ +// STYL HELL GIRL — výseče v barvách soumraku a pekla, rudá nit jako lem. +// Dialog, tlačítka a písmo už řeší themes/hellgirl.scss, tady je jen kolo. +// ============================================ +html[data-ft-style="HELL_GIRL"] { + --wheel-c0: #a3122a; + --wheel-c1: #1f1416; + --wheel-c2: #c8452a; + --wheel-c3: #4b1a4f; + --wheel-c4: #6e0b1a; + --wheel-c5: #9a6b1f; + --wheel-rim: #c8102e; + --wheel-hub: #12070a; + + &[data-bs-theme="dark"] { + --wheel-c1: #2e1a1e; + } + + .spin-wheel-modal { + .spin-source { + border-radius: 2px; + } + + .spin-source > .btn { + border-radius: 2px !important; + } + + // Tlačítko roztočení — černá deska s rudou září + .spin-button { + background: linear-gradient(180deg, #2a1216, #12070a); + border: 1px solid #c8102e; + color: #fff4ee; + box-shadow: 0 0 0 3px rgb(200 16 46 / 0.25), 0 0 18px rgb(200 16 46 / 0.45); + letter-spacing: 1px; + font-size: 1.15rem; + padding: 12px 36px; + + &:hover:not(:disabled) { + background: linear-gradient(180deg, #3a1219, #1c0a10); + box-shadow: 0 0 0 3px rgb(200 16 46 / 0.4), 0 0 24px rgb(200 16 46 / 0.65); + } + } + + .spin-options { + border-top: 3px solid var(--ft-primary); + } + + .spin-wheel-pointer { + border-top-color: #c8102e; + filter: drop-shadow(0 0 4px rgb(200 16 46 / 0.7)); + } + + .spin-wheel-rim { + filter: drop-shadow(0 0 6px rgb(200 16 46 / 0.6)); + } + + .spin-seg { + stroke: #12070a; + stroke-width: 2; + } + + .spin-seg-label { + font-family: var(--hg-font); + } + } +} diff --git a/client/src/components/modals/SpinWheelModal.tsx b/client/src/components/modals/SpinWheelModal.tsx new file mode 100644 index 0000000..3020a33 --- /dev/null +++ b/client/src/components/modals/SpinWheelModal.tsx @@ -0,0 +1,388 @@ +import { useEffect, useMemo, useRef, useState } from "react"; +import { Button, ButtonGroup, Form, Modal, Spinner } from "react-bootstrap"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faBookOpen, faRotateLeft, faXmark } from "@fortawesome/free-solid-svg-icons"; +import { MealGroup, MealTemplate, RecipeFlavor, getMealGroups } from "../../../../types"; +import { RECIPE_FLAVOR_NAMES, StyleTheme, WHEEL_POOL_NAMES, WheelPool } from "../../enums"; +import { useSettings } from "../../context/settings"; +import { getMealKey, normalizeSearch } from "../../Utils"; +import SpinWheel, { SEGMENT_COLORS } from "../SpinWheel"; +import WheelConfetti from "../WheelConfetti"; +import WheelRecipe from "../WheelRecipe"; +import "./SpinWheelModal.scss"; + +type Props = { + isOpen: boolean, + onClose: () => void, +}; + +/** Točit má smysl až od dvou možností — jediná zbylá se vybere rovnou. */ +const MIN_SPIN_ITEMS = 2; + +type RecipeGroup = MealGroup & { recipe: MealTemplate }; + +/** Výseč kola — jídlo, nebo u všech jídel nejdřív zdroj, ze kterého se pak losuje. */ +type WheelItem = { + key: string, + label: string, + note?: string, + group?: MealGroup, +}; + +/** Klíče zdrojů se nesmí potkat s klíči jídel — vyřazené možnosti obou kol leží v jedné množině. */ +const SOURCE_KEY_PREFIX = 'zdroj:'; + +const getSourceKey = (group: MealGroup): string => `${SOURCE_KEY_PREFIX}${normalizeSearch(group.source ?? '')}`; + +/** Zdroje jídel pro první kolo, každý jednou, seřazené podle abecedy; jídla bez zdroje jako „Bez zdroje". */ +const listSources = (groupList: MealGroup[]): WheelItem[] => { + const sourceMap = new Map(); + for (const group of groupList) { + const key = getSourceKey(group); + const current = sourceMap.get(key); + if (current) current.count++; + else sourceMap.set(key, { label: group.source?.trim() || 'Bez zdroje', count: 1 }); + } + return [...sourceMap.entries()] + .map(([key, { label, count }]) => ({ key, label, note: `${count}×` })) + .sort((a, b) => a.label.localeCompare(b.label, 'cs')); +}; + +const createMealItem = (group: MealGroup, withSource: boolean): WheelItem => + ({ key: getMealKey(group), label: group.name, note: withSource ? group.source : undefined, group }); + +const getResultCaption = (sourceStage: boolean, onlyOne: boolean): string => { + if (sourceStage) return onlyOne ? 'Zbývá jediný zdroj' : 'Padl zdroj'; + return onlyOne ? 'Zbývá jediná možnost' : 'Padlo'; +}; + +const hasRecipe = (group: MealGroup): group is RecipeGroup => !!group.recipe; + +/** Kryptograficky náhodný index v rozsahu 0..n-1 (modulo zkreslení u malých n nevadí). */ +const getRandomIndex = (count: number): number => { + const buffer = new Uint32Array(1); + crypto.getRandomValues(buffer); + return buffer[0] % count; +}; + +/** Typy receptů pro výběr, každý jednou (porovnává se bez diakritiky), seřazené podle abecedy. */ +const listCategories = (groupList: RecipeGroup[]): { value: string, label: string }[] => { + const categoryMap = new Map(); + for (const group of groupList) { + const category = group.recipe.category?.trim(); + if (category && !categoryMap.has(normalizeSearch(category))) categoryMap.set(normalizeSearch(category), category); + } + return [...categoryMap.entries()] + .map(([value, label]) => ({ value, label })) + .sort((a, b) => a.label.localeCompare(b.label, 'cs')); +}; + +/** + * Kolo štěstí pro nerozhodnuté, převzaté z Luncheru. Losuje buď ze všech jídel + * (i objednávaných — pak stačí, že kolo rozhodne), nebo jen z receptů, volitelně + * zúžených chutí a typem; padlý recept jde rovnou otevřít a vařit podle něj. + * Všech jídel jsou stovky, takže se u nich nejdřív vybere (nebo vylosuje) zdroj + * a teprve z jeho jídel se točí. Možnosti lze z kola průběžně vyřazovat. + */ +const SpinWheelModal = ({ isOpen, onClose }: Readonly) => { + const [groupList, setGroupList] = useState(); + const [pool, setPool] = useState(WheelPool.VSE); + const [flavor, setFlavor] = useState(); + const [category, setCategory] = useState(''); + const [sourceKey, setSourceKey] = useState(); + const [excludedKeys, setExcludedKeys] = useState>(new Set()); + const [picked, setPicked] = useState(); + const [recipeShown, setRecipeShown] = useState(false); + const [rotation, setRotation] = useState(0); + const [spinning, setSpinning] = useState(false); + const [respinRequested, setRespinRequested] = useState(false); + const [confettiKey, setConfettiKey] = useState(0); + const pendingIndex = useRef(undefined); + const theme = useSettings()?.styleTheme ?? StyleTheme.JEDNODUCHY; + + useEffect(() => { + if (isOpen && !groupList) { + getMealGroups().then(response => setGroupList(response.data ?? [])); + } + }, [isOpen, groupList]); + + const flavorRecipeList = useMemo( + () => (groupList ?? []).filter(hasRecipe).filter(group => !flavor || group.recipe.flavor === flavor), + [groupList, flavor], + ); + const categoryList = useMemo(() => listCategories(flavorRecipeList), [flavorRecipeList]); + + const sourceList = useMemo(() => listSources(groupList ?? []), [groupList]); + const sourceStage = pool === WheelPool.VSE && !sourceKey; + + const candidateList = useMemo(() => { + if (pool === WheelPool.VSE) { + if (!sourceKey) return sourceList; + return (groupList ?? []) + .filter(group => getSourceKey(group) === sourceKey) + .map(group => createMealItem(group, false)); + } + return flavorRecipeList + .filter(group => !category || normalizeSearch(group.recipe.category ?? '') === category) + .map(group => createMealItem(group, true)); + }, [pool, sourceKey, sourceList, groupList, flavorRecipeList, category]); + + const wheelList = useMemo( + () => candidateList.filter(item => !excludedKeys.has(item.key)), + [candidateList, excludedKeys], + ); + const excludedCount = candidateList.length - wheelList.length; + const onlyOne = wheelList.length === 1; + + const clearResult = () => { + setPicked(undefined); + setRecipeShown(false); + }; + + const changePool = (value: WheelPool) => { + setPool(value); + clearResult(); + }; + + const changeFlavor = (value?: RecipeFlavor) => { + setFlavor(value); + // Typ, který s novou chutí žádný recept nemá, by kolo vyprázdnil + const stillExists = !category || (groupList ?? []).filter(hasRecipe).some(group => + (!value || group.recipe.flavor === value) && normalizeSearch(group.recipe.category ?? '') === category); + if (!stillExists) setCategory(''); + clearResult(); + }; + + const changeSource = (value?: string) => { + setSourceKey(value); + clearResult(); + }; + + // Zbyla jediná možnost — není co losovat, vybere se rovnou + useEffect(() => { + if (spinning || !onlyOne) return; + if (picked?.key !== wheelList[0].key) setPicked(wheelList[0]); + }, [onlyOne, spinning, wheelList, picked]); + + const spin = () => { + const count = wheelList.length; + if (count < MIN_SPIN_ITEMS || spinning) return; + const index = getRandomIndex(count); + const angle = 360 / count; + const jitter = (Math.random() - 0.5) * angle * 0.6; + const target = 360 - (index * angle + angle / 2) + jitter; + const turns = 5 + getRandomIndex(3); + pendingIndex.current = index; + clearResult(); + setSpinning(true); + setRotation(current => current - (current % 360) + turns * 360 + ((target % 360) + 360) % 360); + }; + + const handleSpinEnd = () => { + const index = pendingIndex.current; + if (index == null) return; + pendingIndex.current = undefined; + setSpinning(false); + setPicked(wheelList[index]); + // Padlý zdroj je jen mezikrok, oslavuje se až jídlo + if (!sourceStage) setConfettiKey(key => key + 1); + }; + + const removeOption = (item: WheelItem) => { + if (spinning) return; + setExcludedKeys(current => new Set(current).add(item.key)); + if (picked?.key === item.key) clearResult(); + }; + + const removeResultAndRespin = () => { + if (!picked) return; + removeOption(picked); + setRespinRequested(true); + }; + + // Po vyřazení se kolo překreslí s menším počtem výsečí — roztočí se až nad novými daty + useEffect(() => { + if (respinRequested) { + setRespinRequested(false); + spin(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [respinRequested]); + + const restoreAll = () => { + const candidateKeys = new Set(candidateList.map(item => item.key)); + setExcludedKeys(current => new Set([...current].filter(key => !candidateKeys.has(key)))); + }; + + const pickedRecipe = picked?.group && hasRecipe(picked.group) ? picked.group : undefined; + const pickedSource = sourceStage ? picked : undefined; + + const renderWheel = () => { + if (!groupList) return
; + if (!wheelList.length) { + return ( +
+ {candidateList.length + ? 'Na kole nezbyla žádná možnost.' + : pool === WheelPool.RECEPTY ? 'Takový recept zatím nemáte.' : 'Zatím nemáte zapsané žádné jídlo.'} +
+ ); + } + return item.label)} rotation={rotation} spinning={spinning} onSpinEnd={() => handleSpinEnd()} />; + }; + + return ( + + + + {recipeShown && pickedRecipe ? pickedRecipe.name : 'Kolo štěstí: co dnes?'} + + + + + {recipeShown && pickedRecipe ? : ( + <> +
+ + {Object.values(WheelPool).map(value => ( + + ))} + + {pool === WheelPool.VSE && ( + changeSource(event.target.value || undefined)} + > + + {sourceList.map(item => )} + + )} + {pool === WheelPool.RECEPTY && ( + <> + + {[undefined, ...Object.values(RecipeFlavor)].map(value => ( + + ))} + + { setCategory(event.target.value); clearResult(); }} + > + + {categoryList.map(item => )} + + + )} +
+
+
+ {renderWheel()} +
+ {picked && ( + <> + {getResultCaption(sourceStage, onlyOne)}: {picked.label} + {picked.group?.source && ({picked.group.source})} + + )} +
+
+ {pickedSource && !spinning && ( + + )} + + {picked && !spinning && wheelList.length > 1 && ( + + )} +
+ {onlyOne && ( + + Na kole musí být aspoň 2 možnosti — vraťte nějaké vyřazené, nebo berte tuto. + + )} +
+
+
+ Na kole ({wheelList.length}) + {excludedCount > 0 && ( + + )} +
+
    + {wheelList.map((item, index) => ( +
  • + + {item.label} + {item.group?.recipe && } + {item.note && {item.note}} + +
  • + ))} +
+
+
+ + )} +
+ + {recipeShown && ( + + )} + {!recipeShown && pickedRecipe && !spinning && ( + + )} + + +
+ ); +}; + +export default SpinWheelModal; diff --git a/client/src/enums.ts b/client/src/enums.ts index 3c35535..06367e8 100644 --- a/client/src/enums.ts +++ b/client/src/enums.ts @@ -212,3 +212,14 @@ export const STYLE_THEME_NAMES: Record = { JEDNODUCHY: 'Jednoduchý', HELL_GIRL: 'Hell Girl', }; + +/** Z čeho kolo štěstí losuje — ze všech jídel (i objednávaných), nebo jen z receptů. */ +export enum WheelPool { + VSE = 'VSE', + RECEPTY = 'RECEPTY', +} + +export const WHEEL_POOL_NAMES: Record = { + VSE: 'Všechna jídla', + RECEPTY: 'Recepty', +}; diff --git a/client/src/markdownBreaks.ts b/client/src/markdownBreaks.ts new file mode 100644 index 0000000..95c1abd --- /dev/null +++ b/client/src/markdownBreaks.ts @@ -0,0 +1,37 @@ +import type { Break, Nodes, Parent, PhrasingContent, Root, Text } from "mdast"; + +/** + * Jednoduché zalomení řádku v Markdownu je jen mezera, nový řádek dělá až prázdný + * řádek. Postupy receptů se ale píšou i jako prostý text po řádcích (starší + * záznamy vznikly dřív, než postup uměl Markdown), proto se tady každé zalomení + * uvnitř odstavce převede na tvrdé — jako v komentářích na GitHubu. + */ +const splitBreaks = (node: Text): PhrasingContent[] => { + const partList = node.value.split('\n'); + return partList.flatMap((value, index): PhrasingContent[] => { + const text: Text = { type: 'text', value }; + if (index === partList.length - 1) return value ? [text] : []; + const lineBreak: Break = { type: 'break' }; + return value ? [text, lineBreak] : [lineBreak]; + }); +}; + +const isParent = (node: Nodes): node is Parent & Nodes => 'children' in node; + +const transform = (node: Nodes) => { + if (!isParent(node)) return; + node.children = node.children.flatMap(child => + child.type === 'text' && child.value.includes('\n') ? splitBreaks(child) : [child]) as typeof node.children; + node.children.forEach(child => transform(child as Nodes)); +}; + +/** Převede zalomení řádků v textu na tvrdá zalomení (`break`). */ +export const applyLineBreaks = (tree: Root): Root => { + transform(tree); + return tree; +}; + +/** Plugin pro react-markdown se stejným chováním. */ +export const remarkLineBreaks = () => (tree: Root) => { + applyLineBreaks(tree); +}; diff --git a/client/src/markdownPdf.ts b/client/src/markdownPdf.ts new file mode 100644 index 0000000..69bf0fc --- /dev/null +++ b/client/src/markdownPdf.ts @@ -0,0 +1,85 @@ +import { fromMarkdown } from "mdast-util-from-markdown"; +import type { BlockContent, DefinitionContent, ListItem, PhrasingContent, RootContent } from "mdast"; +import type { Content } from "pdfmake/interfaces"; +import { applyLineBreaks } from "./markdownBreaks"; + +/** Barva odkazů a citací — stejná jako text vedle, jen tlumenější. */ +const MUTED_COLOR = '#6b7280'; +const LINK_COLOR = '#15803d'; +const CODE_BACKGROUND = '#f3f4f6'; + +const convertInline = (nodeList: PhrasingContent[]): Content[] => nodeList.map(convertPhrasing); + +const convertPhrasing = (node: PhrasingContent): Content => { + switch (node.type) { + case 'text': + return node.value; + case 'strong': + return { text: convertInline(node.children), bold: true }; + case 'emphasis': + return { text: convertInline(node.children), italics: true }; + case 'delete': + return { text: convertInline(node.children), decoration: 'lineThrough' }; + case 'inlineCode': + return { text: node.value, background: CODE_BACKGROUND }; + case 'break': + return '\n'; + case 'link': + return { text: convertInline(node.children), link: node.url, color: LINK_COLOR, decoration: 'underline' }; + // Obrázky z internetu se do PDF nestahují, zůstane popisek + case 'image': + return node.alt ?? ''; + case 'html': + return node.value; + default: + return 'children' in node ? convertInline(node.children as PhrasingContent[]) : ''; + } +}; + +/** Položka seznamu s jediným odstavcem je jen text, jinak se bloky skládají pod sebe. */ +const convertListItem = (item: ListItem): Content => { + const [first, ...rest] = item.children; + if (first?.type === 'paragraph' && !rest.length) return { text: convertInline(first.children) }; + return { stack: convertBlocks(item.children) }; +}; + +const convertBlock = (node: RootContent | BlockContent | DefinitionContent): Content | undefined => { + switch (node.type) { + case 'heading': + return { text: convertInline(node.children), style: node.depth <= 2 ? 'noteHeading' : 'noteSubheading' }; + case 'paragraph': + return { text: convertInline(node.children), style: 'noteParagraph' }; + case 'list': + return node.ordered + ? { ol: node.children.map(convertListItem), start: node.start ?? 1, style: 'noteList' } + : { ul: node.children.map(convertListItem), style: 'noteList' }; + case 'blockquote': + return { stack: convertBlocks(node.children), italics: true, color: MUTED_COLOR, margin: [12, 0, 0, 6] }; + case 'code': + return { + table: { widths: ['*'], body: [[{ text: node.value, fillColor: CODE_BACKGROUND, fontSize: 9 }]] }, + layout: 'noBorders', + style: 'noteParagraph', + }; + case 'thematicBreak': + return { + canvas: [{ type: 'line', x1: 0, y1: 0, x2: 515, y2: 0, lineWidth: 0.5, lineColor: '#d1d5db' }], + margin: [0, 4, 0, 10], + }; + case 'html': + return { text: node.value, style: 'noteParagraph' }; + default: + return undefined; + } +}; + +const convertBlocks = (nodeList: (RootContent | BlockContent | DefinitionContent)[]): Content[] => + nodeList.map(convertBlock).filter((item): item is Content => item !== undefined); + +/** + * Převede postup receptu z Markdownu na obsah pdfmake. Parser je stejný jako + * u zobrazení v aplikaci (react-markdown stojí na mdast), včetně zalomení řádků, + * takže PDF vypadá jako karta receptu. + */ +export const convertMarkdownToPdf = (markdown: string): Content[] => + convertBlocks(applyLineBreaks(fromMarkdown(markdown)).children); diff --git a/client/src/pages/MealGroupsPage.tsx b/client/src/pages/MealGroupsPage.tsx index cf4b490..5fbbe67 100644 --- a/client/src/pages/MealGroupsPage.tsx +++ b/client/src/pages/MealGroupsPage.tsx @@ -2,12 +2,13 @@ import { useCallback, useEffect, useMemo, useState } from "react"; import { Button, Form, Spinner } from "react-bootstrap"; import { useLocation } from "react-router"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faBookOpen, faCircleCheck } from "@fortawesome/free-solid-svg-icons"; -import { MealGroup, getMealGroups } from "../../../types"; +import { faBookOpen, faCircleCheck, faFilePdf } from "@fortawesome/free-solid-svg-icons"; +import { MealGroup, MealTemplate, getMealGroups } from "../../../types"; import { MealGroupAction, RECIPE_FILTER_NAMES, RecipeFilter, SourceFilter } from "../enums"; import { getMealKey, normalizeSearch } from "../Utils"; import MealGroupRow from "../components/MealGroupRow"; import MealTemplateForm from "../components/MealTemplateForm"; +import { useRecipeExport } from "../useRecipeExport"; import "./templates.scss"; import "./MealGroupsPage.scss"; @@ -73,6 +74,7 @@ const MealGroupsPage = () => { (location.state as LocationState)?.recipeFilter ?? RecipeFilter.VSE); const [opened, setOpened] = useState<{ key: string, action: MealGroupAction } | undefined>(); const [newRecipeOpen, setNewRecipeOpen] = useState(false); + const { exporting, exportRecipes } = useRecipeExport(); const load = useCallback(async () => { const response = await getMealGroups(); @@ -93,6 +95,11 @@ const MealGroupsPage = () => { && (!search.length || matchesSearch(group, search))); }, [groupList, query, sourceFilter, recipeFilter, onlyIncomplete]); + // Exportují se recepty, které seznam právě ukazuje — bez filtru tedy všechny + const visibleRecipeList = useMemo(() => visibleList + .filter((group): group is MealGroup & { recipe: MealTemplate } => !!group.recipe) + .map(group => ({ recipe: group.recipe, photoCount: group.photoCount })), [visibleList]); + const filtered = !!query.trim().length || sourceFilter !== SourceFilter.VSE || recipeFilter !== RecipeFilter.VSE; return ( @@ -104,9 +111,20 @@ const MealGroupsPage = () => { Všechna jídla podle názvu a zdroje — údaje doplníte nebo název změníte ve všech dnech najednou

- +
+ + +
{newRecipeOpen && ( diff --git a/client/src/pages/templates.scss b/client/src/pages/templates.scss index b4980e7..015b193 100644 --- a/client/src/pages/templates.scss +++ b/client/src/pages/templates.scss @@ -92,14 +92,68 @@ white-space: nowrap; } -// Postup přípravy si drží zalomení řádků tak, jak ho uživatel zapsal -.meal-template-note { +// Postup přípravy v Markdownu +.recipe-note { margin: 10px 0 0; padding-top: 10px; border-top: 1px solid var(--ft-border-light); font-size: 0.83rem; color: var(--ft-text-secondary); - white-space: pre-wrap; + + p, ul, ol, blockquote, pre { + margin: 0 0 0.6em; + } + + > :last-child { + margin-bottom: 0; + } + + h1, h2, h3, h4, h5, h6 { + margin: 0.8em 0 0.35em; + font-size: 0.95rem; + font-weight: 700; + color: var(--ft-text); + } + + h1 { + font-size: 1.05rem; + } + + > :first-child { + margin-top: 0; + } + + ul, ol { + padding-left: 1.4em; + } + + blockquote { + padding-left: 10px; + border-left: 3px solid var(--ft-border); + } + + code { + padding: 1px 4px; + border-radius: var(--ft-radius-sm); + background: var(--ft-bg-hover); + color: var(--ft-text); + font-size: 0.95em; + } + + pre code { + display: block; + padding: 8px 10px; + white-space: pre-wrap; + } + + hr { + margin: 0.8em 0; + border-color: var(--ft-border); + } + + img { + max-width: 100%; + } } // Řádek suroviny receptu — název se roztáhne, číselná pole mají pevnou šířku @@ -153,3 +207,30 @@ color: var(--ft-primary); } } + +// Popisek postupu s přepínačem psaní a náhledu +.recipe-note-label { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + margin-bottom: 0.5rem; + + .form-label { + margin-bottom: 0; + } +} + +.recipe-note-preview { + min-height: 120px; + padding: 8px 12px; + border: 1px solid var(--ft-border); + border-radius: var(--ft-radius-sm); + background: var(--ft-bg-card); + + .recipe-note { + margin: 0; + padding: 0; + border: none; + } +} diff --git a/client/src/recipePdf.ts b/client/src/recipePdf.ts new file mode 100644 index 0000000..7c9cc88 --- /dev/null +++ b/client/src/recipePdf.ts @@ -0,0 +1,186 @@ +import type { Content, TDocumentDefinitions } from "pdfmake/interfaces"; +import { MealGroup, MealPhoto, MealTemplate, getMealPhotos } from "../../types"; +import { RECIPE_FLAVOR_NAMES } from "./enums"; +import { convertMarkdownToPdf } from "./markdownPdf"; +import { + deriveRecipe, formatCalories, formatCount, formatDate, formatDateString, formatPrice, formatWeight, getIngredientCalories, +} from "./Utils"; + +/** Recept s počtem fotek jídla — fotky se stahují jen tam, kde nějaké jsou. */ +type RecipeItem = Pick & { recipe: MealTemplate }; + +const PRIMARY_COLOR = '#15803d'; +const MUTED_COLOR = '#6b7280'; +const BORDER_COLOR = '#e5e7eb'; +/** Šířka tisknutelné plochy A4 při okrajích 40 pt. */ +const CONTENT_WIDTH = 515; +const PHOTO_GAP = 8; +const SMALL_PHOTOS_PER_ROW = 3; + +/** pdfmake umí jen JPEG a PNG; fotky se sice ukládají jako JPEG, API ale přijímá i WebP. */ +const isSupportedImage = (photo: MealPhoto): boolean => /^data:image\/(jpeg|png)/.test(photo.image); + +/** Název souboru bez znaků, které systémy v názvu nepovolí. */ +const toFileName = (name: string): string => `${name.replace(/[\\/:*?"<>|]+/g, '').trim() || 'recept'}.pdf`; + +const getRecipeMeta = (recipe: MealTemplate): string => { + return [ + recipe.category, + RECIPE_FLAVOR_NAMES[recipe.flavor].toLowerCase(), + formatCount(recipe.servings ?? 1, 'porce', 'porce', 'porcí'), + recipe.weight != null ? `porce ${formatWeight(recipe.weight)}` : undefined, + recipe.calories != null ? `${formatCalories(recipe.calories)} na porci` : undefined, + recipe.price != null ? `${formatPrice(recipe.price)} za porci` : undefined, + ].filter(item => !!item).join(' · '); +}; + +/** První fotka přes celou šířku, ostatní menší v řadách po třech. */ +const createPhotos = (photoList: MealPhoto[]): Content[] => { + const [first, ...rest] = photoList; + if (!first) return []; + const result: Content[] = [{ image: first.image, fit: [CONTENT_WIDTH, 240], alignment: 'center', margin: [0, 0, 0, PHOTO_GAP] }]; + const smallWidth = (CONTENT_WIDTH - PHOTO_GAP * (SMALL_PHOTOS_PER_ROW - 1)) / SMALL_PHOTOS_PER_ROW; + for (let start = 0; start < rest.length; start += SMALL_PHOTOS_PER_ROW) { + const rowList = rest.slice(start, start + SMALL_PHOTOS_PER_ROW); + result.push({ + columns: [ + ...rowList.map(photo => ({ image: photo.image, fit: [smallWidth, 130] as [number, number], width: smallWidth })), + // Nedoplněná řada si drží šířku sloupců + ...Array.from({ length: SMALL_PHOTOS_PER_ROW - rowList.length }, () => ({ text: '', width: smallWidth })), + ], + columnGap: PHOTO_GAP, + margin: [0, 0, 0, PHOTO_GAP], + }); + } + return result; +}; + +const createIngredients = (recipe: MealTemplate): Content[] => { + if (!recipe.ingredients.length) return []; + const summary = deriveRecipe(recipe.ingredients, recipe.servings, recipe.cookedWeight); + const hasPrice = recipe.ingredients.some(item => item.price != null); + const header = ['Surovina', 'Množství', 'Energie', ...(hasPrice ? ['Cena'] : [])] + .map((text, index) => ({ text, style: 'tableHeader', alignment: index ? 'right' as const : 'left' as const })); + const rowList = recipe.ingredients.map(item => [ + item.name, + { text: item.weight != null ? formatWeight(item.weight) : '', alignment: 'right' as const }, + { text: formatCalories(getIngredientCalories(item)), alignment: 'right' as const }, + ...(hasPrice ? [{ text: item.price != null ? formatPrice(item.price) : '', alignment: 'right' as const }] : []), + ]); + const totalList = [ + `Celkem ${formatCalories(summary.totalCalories)}`, + summary.totalWeight != null ? formatWeight(summary.totalWeight) : undefined, + summary.caloriesPer100g != null ? `${summary.caloriesPer100g} kcal/100 g` : undefined, + summary.totalPrice != null ? formatPrice(summary.totalPrice) : undefined, + ].filter(item => !!item); + return [ + { text: 'Suroviny', style: 'sectionHeading' }, + { + table: { + headerRows: 1, + widths: ['*', 70, 70, ...(hasPrice ? [70] : [])], + body: [header, ...rowList], + }, + layout: { + hLineWidth: index => (index === 1 ? 0.8 : 0.4), + vLineWidth: () => 0, + hLineColor: () => BORDER_COLOR, + paddingTop: () => 3, + paddingBottom: () => 3, + }, + }, + { text: totalList.join(' · '), style: 'summary' }, + ]; +}; + +const createRecipe = (recipe: MealTemplate, photoList: MealPhoto[], index: number, withToc: boolean): Content => ({ + stack: [ + { + text: recipe.name, + style: 'recipeTitle', + id: `recept-${index}`, + tocItem: withToc, + }, + ...(recipe.source ? [{ text: recipe.source, style: 'recipeSource' }] : []), + { text: getRecipeMeta(recipe), style: 'recipeMeta' }, + ...createPhotos(photoList), + ...createIngredients(recipe), + ...(recipe.note?.trim() + ? [{ text: 'Postup', style: 'sectionHeading' }, ...convertMarkdownToPdf(recipe.note)] + : []), + ], + // Každý recept začíná na nové stránce, první hned za obsahem + ...(index > 0 || withToc ? { pageBreak: 'before' as const } : {}), +}); + +const loadPhotos = async (item: RecipeItem): Promise => { + if (!item.photoCount) return []; + const response = await getMealPhotos({ query: { name: item.recipe.name, source: item.recipe.source } }); + return (response.data ?? []).filter(isSupportedImage); +}; + +/** pdfmake je velký (písma), načte se až při prvním exportu. */ +const loadPdfMake = async () => { + const [pdfMakeModule, vfsModule] = await Promise.all([ + import("pdfmake/build/pdfmake"), + import("pdfmake/build/vfs_fonts"), + ]); + // Oba soubory jsou CommonJS/UMD, bundler je vystaví jako default + const pdfMake = pdfMakeModule.default; + const vfs = vfsModule.default; + pdfMake.addVirtualFileSystem(vfs); + return pdfMake; +}; + +/** + * Stáhne recepty jako PDF — suroviny, hodnoty porce, postup a fotky jídla. + * Víc receptů dostane titulní stránku s klikacím obsahem a každý recept + * začíná na nové stránce. + */ +export const exportRecipesPdf = async (itemList: RecipeItem[]): Promise => { + const sortedList = [...itemList].sort((a, b) => a.recipe.name.localeCompare(b.recipe.name, 'cs')); + const [pdfMake, photoLists] = await Promise.all([loadPdfMake(), Promise.all(sortedList.map(loadPhotos))]); + const withToc = sortedList.length > 1; + const title = withToc ? 'Recepty' : sortedList[0]?.recipe.name ?? 'Recept'; + + const cover: Content[] = withToc ? [ + { text: 'Recepty', style: 'coverTitle' }, + { text: `${formatCount(sortedList.length, 'recept', 'recepty', 'receptů')} · ${formatDateString(formatDate(new Date()))}`, style: 'recipeMeta' }, + { + toc: { + title: { text: 'Obsah', style: 'sectionHeading' }, + textMargin: [0, 2, 0, 2], + numberStyle: { color: MUTED_COLOR }, + }, + }, + ] : []; + + const definition: TDocumentDefinitions = { + info: { title }, + pageSize: 'A4', + pageMargins: [40, 40, 40, 50], + content: [...cover, ...sortedList.map((item, index) => createRecipe(item.recipe, photoLists[index], index, withToc))], + footer: (currentPage, pageCount) => ({ + text: `${currentPage} / ${pageCount}`, + alignment: 'center', + fontSize: 8, + color: MUTED_COLOR, + }), + defaultStyle: { font: 'Roboto', fontSize: 10, lineHeight: 1.25 }, + styles: { + coverTitle: { fontSize: 28, bold: true, color: PRIMARY_COLOR, margin: [0, 0, 0, 4] }, + recipeTitle: { fontSize: 20, bold: true, color: PRIMARY_COLOR }, + recipeSource: { fontSize: 11, color: MUTED_COLOR }, + recipeMeta: { fontSize: 9, color: MUTED_COLOR, margin: [0, 4, 0, 12] }, + sectionHeading: { fontSize: 13, bold: true, margin: [0, 10, 0, 6] }, + tableHeader: { bold: true, fontSize: 9, color: MUTED_COLOR }, + summary: { bold: true, fontSize: 9, color: MUTED_COLOR, margin: [0, 6, 0, 0] }, + noteHeading: { fontSize: 12, bold: true, margin: [0, 8, 0, 4] }, + noteSubheading: { fontSize: 10.5, bold: true, margin: [0, 6, 0, 3] }, + noteParagraph: { margin: [0, 0, 0, 6] }, + noteList: { margin: [0, 0, 0, 6] }, + }, + }; + + await pdfMake.createPdf(definition).download(withToc ? 'recepty.pdf' : toFileName(title)); +}; diff --git a/client/src/useRecipeExport.ts b/client/src/useRecipeExport.ts new file mode 100644 index 0000000..2c98c0c --- /dev/null +++ b/client/src/useRecipeExport.ts @@ -0,0 +1,23 @@ +import { useState } from "react"; +import { toast } from "react-toastify"; +import { exportRecipesPdf } from "./recipePdf"; + +/** Export receptů do PDF s příznakem probíhajícího exportu a hláškou při chybě. */ +export const useRecipeExport = () => { + const [exporting, setExporting] = useState(false); + + const exportRecipes = async (itemList: Parameters[0]) => { + if (exporting || !itemList.length) return; + setExporting(true); + try { + await exportRecipesPdf(itemList); + } catch (error) { + console.error('Export receptů do PDF selhal', error); + toast.error('PDF se nepodařilo vytvořit.'); + } finally { + setExporting(false); + } + }; + + return { exporting, exportRecipes }; +}; diff --git a/client/yarn.lock b/client/yarn.lock index a239dbc..e723785 100644 --- a/client/yarn.lock +++ b/client/yarn.lock @@ -386,6 +386,16 @@ resolved "https://registry.yarnpkg.com/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz#e57d4306966078662038094fb38eb9146dc3aea9" integrity sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ== +"@noble/ciphers@^1.0.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@noble/ciphers/-/ciphers-1.3.0.tgz#f64b8ff886c240e644e5573c097f86e5b43676dc" + integrity sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw== + +"@noble/hashes@^1.6.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.8.0.tgz#cee43d801fcef9644b11b8194857695acd5f815a" + integrity sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A== + "@parcel/watcher-android-arm64@2.6.0": version "2.6.0" resolved "https://registry.yarnpkg.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.6.0.tgz#99aaa3223d43807c9340af439cad7e9b6d26ada6" @@ -668,7 +678,7 @@ resolved "https://registry.yarnpkg.com/@standard-schema/utils/-/utils-0.3.0.tgz#3d5e608f16c2390c10528e98e59aef6bf73cae7b" integrity sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g== -"@swc/helpers@^0.5.0": +"@swc/helpers@^0.5.0", "@swc/helpers@^0.5.12": version "0.5.23" resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.23.tgz#19287d0d86d962b111376039a50c792902c9a86a" integrity sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw== @@ -759,11 +769,51 @@ resolved "https://registry.yarnpkg.com/@types/d3-timer/-/d3-timer-3.0.2.tgz#70bbda77dc23aa727413e22e214afa3f0e852f70" integrity sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw== -"@types/estree@1.0.9": +"@types/debug@^4.0.0": + version "4.1.13" + resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.13.tgz#22d1cc9d542d3593caea764f974306ab36286ee7" + integrity sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw== + dependencies: + "@types/ms" "*" + +"@types/estree-jsx@^1.0.0": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree-jsx/-/estree-jsx-1.0.5.tgz#858a88ea20f34fe65111f005a689fa1ebf70dc18" + integrity sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg== + dependencies: + "@types/estree" "*" + +"@types/estree@*", "@types/estree@1.0.9", "@types/estree@^1.0.0": version "1.0.9" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.9.tgz#cf3f0e876d7bee15a93ab925b82bf570a3904a24" integrity sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg== +"@types/hast@^3.0.0": + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.5.tgz#48020de4c0e63492f4ca9db42068c108f68b7f8f" + integrity sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g== + dependencies: + "@types/unist" "*" + +"@types/mdast@4.0.4", "@types/mdast@^4.0.0": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-4.0.4.tgz#7ccf72edd2f1aa7dd3437e180c64373585804dd6" + integrity sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA== + dependencies: + "@types/unist" "*" + +"@types/ms@*": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78" + integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== + +"@types/node@*": + version "26.6.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-26.6.5.tgz#cb35209e7767c503551b5fa42a48231c7441d979" + integrity sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg== + dependencies: + undici-types "~8.9.0" + "@types/node@^24.10.0": version "24.13.3" resolved "https://registry.yarnpkg.com/@types/node/-/node-24.13.3.tgz#49f18bd3c647866dcda51a0756c145e14590ce16" @@ -771,6 +821,21 @@ dependencies: undici-types "~7.18.0" +"@types/pdfkit@*": + version "0.17.6" + resolved "https://registry.yarnpkg.com/@types/pdfkit/-/pdfkit-0.17.6.tgz#b0a4b728b1d0bfb7ec6e574efebba7dc6bf4cca1" + integrity sha512-tIwzxk2uWKp0Cq9JIluQXJid77lYhF52EsIOwhsMF4iWLA6YneoBR1xVKYYdAysHuepUB0OX4tdwMiUDdGKmig== + dependencies: + "@types/node" "*" + +"@types/pdfmake@0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@types/pdfmake/-/pdfmake-0.3.3.tgz#595a7ea9d6007203333b38e030c9cbb32807f443" + integrity sha512-ufbceB4Q3dKpmFUMKEbZvJcfLi8+C5b1+rTFVG6urtkS3FHwaTQH6NCh/iEEDINlApCxH/W3/rsq99pP5E9/JA== + dependencies: + "@types/node" "*" + "@types/pdfkit" "*" + "@types/prop-types@^15.7.12": version "15.7.15" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.15.tgz#e6e5a86d602beaca71ce5163fadf5f95d70931c7" @@ -793,6 +858,16 @@ dependencies: csstype "^3.2.2" +"@types/unist@*", "@types/unist@^3.0.0": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.3.tgz#acaab0f919ce69cce629c2d4ed2eb4adc1b6c20c" + integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q== + +"@types/unist@^2.0.0": + version "2.0.11" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.11.tgz#11af57b127e32487774841f7a4e54eab166d03c4" + integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA== + "@types/use-sync-external-store@^0.0.6": version "0.0.6" resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz#60be8d21baab8c305132eb9cb912ed497852aadc" @@ -803,6 +878,11 @@ resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.4.tgz#ebc0c83180dc83994d902bbd51ab0af8a445b1f9" integrity sha512-CqN8MnISMwQbLJXO3doBAV4Yw9hx9/Pyr2rZ78+NfaCnhyRA/nKrpyk6E7mKw17ZOaQdLpK9GiUjrqLzBlN3sg== +"@ungap/structured-clone@^1.0.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.4.0.tgz#5e2e1374c0a30b5a42e8b083523a225c6945f88a" + integrity sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ== + "@vitejs/plugin-react@^5.1.0": version "5.2.0" resolved "https://registry.yarnpkg.com/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz#108bd0f566f288ce3566982df4eff137ded7b15f" @@ -822,6 +902,21 @@ aria-hidden@^1.2.3: dependencies: tslib "^2.0.0" +bail@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/bail/-/bail-2.0.2.tgz#d26f5cd8fe5d6f832a31517b9f7c356040ba6d5d" + integrity sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw== + +base64-js@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978" + integrity sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw== + +base64-js@^1.1.2, base64-js@^1.3.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + baseline-browser-mapping@^2.11.20: version "2.11.21" resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.11.21.tgz#99af73cb8e54007e4f5345e132278e26c2662f2c" @@ -832,6 +927,20 @@ bootstrap@^5.3.8: resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-5.3.8.tgz#6401a10057a22752d21f4e19055508980656aeed" integrity sha512-HP1SZDqaLDPwsNiqRqi5NcP0SSXciX2s9E+RyqJIIqGo+vJeN5AJVM98CXmW/Wux0nQ5L7jeWUdplCEf0Ee+tg== +brotli@^1.3.2: + version "1.3.3" + resolved "https://registry.yarnpkg.com/brotli/-/brotli-1.3.3.tgz#7365d8cc00f12cf765d2b2c898716bcf4b604d48" + integrity sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg== + dependencies: + base64-js "^1.1.2" + +browserify-zlib@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" + integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== + dependencies: + pako "~1.0.5" + browserslist@^4.24.0: version "4.28.9" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.9.tgz#07ce6b449b90af880eb9bfb7cd39372cc4f71c8c" @@ -848,6 +957,31 @@ caniuse-lite@^1.0.30001810: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz#4970b477dea3278374de9bc43aa8f5d39fc3cda2" integrity sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg== +ccount@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" + integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== + +character-entities-html4@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b" + integrity sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA== + +character-entities-legacy@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b" + integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ== + +character-entities@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22" + integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ== + +character-reference-invalid@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9" + integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw== + chokidar@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-5.0.0.tgz#949c126a9238a80792be9a0265934f098af369a5" @@ -860,11 +994,21 @@ classnames@^2.3.2: resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b" integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow== +clone@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" + integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w== + clsx@^2.0.0, clsx@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.1.1.tgz#eed397c9fd8bd882bfb18deab7102049a2f32999" integrity sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA== +comma-separated-tokens@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee" + integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== + convert-source-map@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" @@ -951,7 +1095,7 @@ d3-timer@^3.0.1: resolved "https://registry.yarnpkg.com/d3-timer/-/d3-timer-3.0.1.tgz#6284d2a2708285b1abb7e201eda4380af35e63b0" integrity sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA== -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: +debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: version "4.4.3" resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a" integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA== @@ -963,7 +1107,14 @@ decimal.js-light@^2.5.1: resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.1.tgz#134fd32508f19e208f4fb2f8dac0d2626a867934" integrity sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg== -dequal@^2.0.3: +decode-named-character-reference@^1.0.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.3.0.tgz#3e40603760874c2e5867691b599d73a7da25b53f" + integrity sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q== + dependencies: + character-entities "^2.0.0" + +dequal@^2.0.0, dequal@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== @@ -973,6 +1124,18 @@ detect-libc@^2.0.3: resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad" integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== +devlop@^1.0.0, devlop@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/devlop/-/devlop-1.1.0.tgz#4db7c2ca4dc6e0e834c30be70c94bbc976dc7018" + integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA== + dependencies: + dequal "^2.0.0" + +dfa@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/dfa/-/dfa-1.2.0.tgz#96ac3204e2d29c49ea5b57af8d92c2ae12790657" + integrity sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q== + dom-helpers@^5.0.1, dom-helpers@^5.2.0, dom-helpers@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" @@ -1028,16 +1191,46 @@ escalade@^3.2.0: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== +estree-util-is-identifier-name@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/estree-util-is-identifier-name/-/estree-util-is-identifier-name-3.0.0.tgz#0b5ef4c4ff13508b34dcd01ecfa945f61fce5dbd" + integrity sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg== + eventemitter3@^5.0.1: version "5.0.4" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.4.tgz#a86d66170433712dde814707ac52b5271ceb1feb" integrity sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw== +extend@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + +fast-deep-equal@^3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + fdir@^6.5.0: version "6.5.0" resolved "https://registry.yarnpkg.com/fdir/-/fdir-6.5.0.tgz#ed2ab967a331ade62f18d077dae192684d50d350" integrity sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg== +fontkit@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/fontkit/-/fontkit-2.0.4.tgz#4765d664c68b49b5d6feb6bd1051ee49d8ec5ab0" + integrity sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g== + dependencies: + "@swc/helpers" "^0.5.12" + brotli "^1.3.2" + clone "^2.1.2" + dfa "^1.2.0" + fast-deep-equal "^3.1.3" + restructure "^3.0.0" + tiny-inflate "^1.0.3" + unicode-properties "^1.4.0" + unicode-trie "^2.0.0" + fsevents@^2.3.2, fsevents@~2.3.2, fsevents@~2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" @@ -1053,6 +1246,39 @@ globrex@^0.1.2: resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098" integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg== +hast-util-to-jsx-runtime@^2.0.0: + version "2.3.6" + resolved "https://registry.yarnpkg.com/hast-util-to-jsx-runtime/-/hast-util-to-jsx-runtime-2.3.6.tgz#ff31897aae59f62232e21594eac7ef6b63333e98" + integrity sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg== + dependencies: + "@types/estree" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + comma-separated-tokens "^2.0.0" + devlop "^1.0.0" + estree-util-is-identifier-name "^3.0.0" + hast-util-whitespace "^3.0.0" + mdast-util-mdx-expression "^2.0.0" + mdast-util-mdx-jsx "^3.0.0" + mdast-util-mdxjs-esm "^2.0.0" + property-information "^7.0.0" + space-separated-tokens "^2.0.0" + style-to-js "^1.0.0" + unist-util-position "^5.0.0" + vfile-message "^4.0.0" + +hast-util-whitespace@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz#7778ed9d3c92dd9e8c5c8f648a49c21fc51cb621" + integrity sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw== + dependencies: + "@types/hast" "^3.0.0" + +html-url-attributes@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/html-url-attributes/-/html-url-attributes-3.0.1.tgz#83b052cd5e437071b756cd74ae70f708870c2d87" + integrity sha512-ol6UPyBWqsrO6EJySPz2O7ZSr856WDrEzM5zMqp+FJJLGMW35cLYmmZnl0vztAZxRUoNZJFTCohfjuIJ8I4QBQ== + immer@^11.0.0, immer@^11.1.8: version "11.1.18" resolved "https://registry.yarnpkg.com/immer/-/immer-11.1.18.tgz#87d9bced1e25157dc23bced66811de89460ec8f3" @@ -1063,6 +1289,11 @@ immutable@^5.1.5: resolved "https://registry.yarnpkg.com/immutable/-/immutable-5.1.9.tgz#ac23c3a01992ab665e14ac9ffff298f28cd74a0c" integrity sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg== +inline-style-parser@0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/inline-style-parser/-/inline-style-parser-0.2.7.tgz#b1fc68bfc0313b8685745e4464e37f9376b9c909" + integrity sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA== + "internmap@1 - 2": version "2.0.3" resolved "https://registry.yarnpkg.com/internmap/-/internmap-2.0.3.tgz#6685f23755e43c524e251d29cbc97248e3061009" @@ -1075,6 +1306,24 @@ invariant@^2.2.4: dependencies: loose-envify "^1.0.0" +is-alphabetical@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz#01072053ea7c1036df3c7d19a6daaec7f19e789b" + integrity sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ== + +is-alphanumerical@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz#7c03fbe96e3e931113e57f964b0a368cc2dfd875" + integrity sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw== + dependencies: + is-alphabetical "^2.0.0" + is-decimal "^2.0.0" + +is-decimal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-2.0.1.tgz#9469d2dc190d0214fd87d78b78caecc0cc14eef7" + integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A== + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -1087,6 +1336,21 @@ is-glob@^4.0.3: dependencies: is-extglob "^2.1.1" +is-hexadecimal@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz#86b5bf668fca307498d319dfc03289d781a90027" + integrity sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg== + +is-plain-obj@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-4.1.0.tgz#d65025edec3657ce032fd7db63c97883eaed71f0" + integrity sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg== + +js-md5@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/js-md5/-/js-md5-0.8.3.tgz#921bab7efa95bfc9d62b87ee08a57f8fe4305b69" + integrity sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -1102,6 +1366,19 @@ json5@^2.2.3: resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== +linebreak@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/linebreak/-/linebreak-1.1.0.tgz#831cf378d98bced381d8ab118f852bd50d81e46b" + integrity sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ== + dependencies: + base64-js "0.0.8" + unicode-trie "^2.0.0" + +longest-streak@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.1.0.tgz#62fa67cd958742a1574af9f39866364102d90cd4" + integrity sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g== + loose-envify@^1.0.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" @@ -1116,6 +1393,334 @@ lru-cache@^5.1.1: dependencies: yallist "^3.0.2" +mdast-util-from-markdown@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz#c95822b91aab75f18a4cbe8b2f51b873ed2cf0c7" + integrity sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q== + dependencies: + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + mdast-util-to-string "^4.0.0" + micromark "^4.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-decode-string "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + unist-util-stringify-position "^4.0.0" + +mdast-util-from-markdown@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-from-markdown/-/mdast-util-from-markdown-2.1.0.tgz#a92a9130c95e0e137e7cffaf85588a7da9e4bda6" + integrity sha512-KRLgmPaP2H1ul2bpoJkOS+9pAcDAAj9Nu1mqLnh8NLeJG5tlvj42TH80PIGBpROWlRXLk0lDrWNuZCFy/tsB4A== + dependencies: + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + mdast-util-to-string "^4.0.0" + micromark "^4.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-decode-string "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + unist-util-stringify-position "^4.0.0" + +mdast-util-mdx-expression@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.1.tgz#43f0abac9adc756e2086f63822a38c8d3c3a5096" + integrity sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-mdx-jsx@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/mdast-util-mdx-jsx/-/mdast-util-mdx-jsx-3.2.0.tgz#fd04c67a2a7499efb905a8a5c578dddc9fdada0d" + integrity sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + ccount "^2.0.0" + devlop "^1.1.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + parse-entities "^4.0.0" + stringify-entities "^4.0.0" + unist-util-stringify-position "^4.0.0" + vfile-message "^4.0.0" + +mdast-util-mdxjs-esm@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/mdast-util-mdxjs-esm/-/mdast-util-mdxjs-esm-2.0.1.tgz#019cfbe757ad62dd557db35a695e7314bcc9fa97" + integrity sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg== + dependencies: + "@types/estree-jsx" "^1.0.0" + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + mdast-util-from-markdown "^2.0.0" + mdast-util-to-markdown "^2.0.0" + +mdast-util-phrasing@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz#7cc0a8dec30eaf04b7b1a9661a92adb3382aa6e3" + integrity sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w== + dependencies: + "@types/mdast" "^4.0.0" + unist-util-is "^6.0.0" + +mdast-util-to-hast@^13.0.0: + version "13.2.1" + resolved "https://registry.yarnpkg.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz#d7ff84ca499a57e2c060ae67548ad950e689a053" + integrity sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + "@ungap/structured-clone" "^1.0.0" + devlop "^1.0.0" + micromark-util-sanitize-uri "^2.0.0" + trim-lines "^3.0.0" + unist-util-position "^5.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + +mdast-util-to-markdown@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-markdown/-/mdast-util-to-markdown-2.2.0.tgz#40e9db55768d7dd11fae4462531cf0223a2b4f35" + integrity sha512-2Jn/ADrHFFyyniBMi0bncTbJqU78K3/TEYvzvwrWWzD9MYPUa/Gm4r3nOeKFQg/PDM5BsbDYdc8ackW4NTY6NA== + dependencies: + "@types/mdast" "^4.0.0" + "@types/unist" "^3.0.0" + longest-streak "^3.0.0" + mdast-util-phrasing "^4.0.0" + mdast-util-to-string "^4.0.0" + micromark-util-character "^2.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-decode-string "^2.0.0" + micromark-util-html-tag-name "^2.0.0" + unist-util-visit "^5.0.0" + zwitch "^2.0.0" + +mdast-util-to-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/mdast-util-to-string/-/mdast-util-to-string-4.0.0.tgz#7a5121475556a04e7eddeb67b264aae79d312814" + integrity sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg== + dependencies: + "@types/mdast" "^4.0.0" + +micromark-core-commonmark@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/micromark-core-commonmark/-/micromark-core-commonmark-2.0.4.tgz#ff748f2327717b3675f0864e5aabc4f2a0028da3" + integrity sha512-wxEeE8v8XVvOrxn1TZj74qYhAtTQsSqufHVS3uNVyT1MupXxhyaHk8/9xDNPh9BmL77QNDgWTkZ3RsYHB2ry7w== + dependencies: + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + micromark-factory-destination "^2.0.0" + micromark-factory-label "^2.0.0" + micromark-factory-space "^2.1.0" + micromark-factory-title "^2.0.0" + micromark-factory-whitespace "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-classify-character "^2.0.0" + micromark-util-edit-map "^1.0.0" + micromark-util-html-tag-name "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-subtokenize "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.3" + +micromark-factory-destination@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-destination/-/micromark-factory-destination-2.0.1.tgz#8fef8e0f7081f0474fbdd92deb50c990a0264639" + integrity sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-label@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-label/-/micromark-factory-label-2.0.1.tgz#5267efa97f1e5254efc7f20b459a38cb21058ba1" + integrity sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg== + dependencies: + devlop "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-space@^2.0.0, micromark-factory-space@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-factory-space/-/micromark-factory-space-2.1.0.tgz#afc208b018c330f17f438731c2014fdff1cd154f" + integrity sha512-fS8hnLIjnjvdQIj39Geug8wWsR0HrYZ43KShKxNfwT7t2LOHo/LbWZEzEaSOjwtjdCsjoE6syHhonEzW0zv0+Q== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-title@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-title/-/micromark-factory-title-2.0.1.tgz#237e4aa5d58a95863f01032d9ee9b090f1de6e94" + integrity sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw== + dependencies: + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-factory-whitespace@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-factory-whitespace/-/micromark-factory-whitespace-2.0.1.tgz#06b26b2983c4d27bfcc657b33e25134d4868b0b1" + integrity sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ== + dependencies: + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-character@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz#2f987831a40d4c510ac261e89852c4e9703ccda6" + integrity sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q== + dependencies: + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-chunked@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-chunked/-/micromark-util-chunked-2.0.1.tgz#47fbcd93471a3fccab86cff03847fc3552db1051" + integrity sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-classify-character@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-classify-character/-/micromark-util-classify-character-2.0.1.tgz#d399faf9c45ca14c8b4be98b1ea481bced87b629" + integrity sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-combine-extensions@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-combine-extensions/-/micromark-util-combine-extensions-2.0.1.tgz#2a0f490ab08bff5cc2fd5eec6dd0ca04f89b30a9" + integrity sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg== + dependencies: + micromark-util-chunked "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-decode-numeric-character-reference@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/micromark-util-decode-numeric-character-reference/-/micromark-util-decode-numeric-character-reference-2.0.2.tgz#fcf15b660979388e6f118cdb6bf7d79d73d26fe5" + integrity sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-decode-string@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-decode-string/-/micromark-util-decode-string-2.0.1.tgz#6cb99582e5d271e84efca8e61a807994d7161eb2" + integrity sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ== + dependencies: + decode-named-character-reference "^1.0.0" + micromark-util-character "^2.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-symbol "^2.0.0" + +micromark-util-edit-map@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/micromark-util-edit-map/-/micromark-util-edit-map-1.0.0.tgz#0b5dd9ab4295f7389472c5fee2b61ffd45774cb2" + integrity sha512-Pa2ljlsEL6sVwFaYeyrOLSYbQt73JvGbPOYFq+9AElXiSnfI5Q4465RRZ1sHv7lDjDOnGRDDaqPXqClqWK/q1Q== + dependencies: + micromark-util-types "^2.0.0" + +micromark-util-encode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz#0d51d1c095551cfaac368326963cf55f15f540b8" + integrity sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw== + +micromark-util-html-tag-name@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-html-tag-name/-/micromark-util-html-tag-name-2.0.1.tgz#e40403096481986b41c106627f98f72d4d10b825" + integrity sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA== + +micromark-util-normalize-identifier@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-normalize-identifier/-/micromark-util-normalize-identifier-2.0.1.tgz#c30d77b2e832acf6526f8bf1aa47bc9c9438c16d" + integrity sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q== + dependencies: + micromark-util-symbol "^2.0.0" + +micromark-util-resolve-all@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-resolve-all/-/micromark-util-resolve-all-2.0.1.tgz#e1a2d62cdd237230a2ae11839027b19381e31e8b" + integrity sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg== + dependencies: + micromark-util-types "^2.0.0" + +micromark-util-sanitize-uri@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz#ab89789b818a58752b73d6b55238621b7faa8fd7" + integrity sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-symbol "^2.0.0" + +micromark-util-subtokenize@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/micromark-util-subtokenize/-/micromark-util-subtokenize-2.1.0.tgz#d8ade5ba0f3197a1cf6a2999fbbfe6357a1a19ee" + integrity sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA== + dependencies: + devlop "^1.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-symbol@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz#e5da494e8eb2b071a0d08fb34f6cefec6c0a19b8" + integrity sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q== + +micromark-util-types@^2.0.0, micromark-util-types@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/micromark-util-types/-/micromark-util-types-2.0.3.tgz#e7b04fa58589199c7ef58b37cd696fcb2bce44e7" + integrity sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg== + +micromark@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/micromark/-/micromark-4.0.3.tgz#6d29c0a5383c1dda7975a6c897e0e18d5694f10d" + integrity sha512-oGYfQzHSG5dOMovQcJ3fyTmZlWAWpi0XA0sJwJs+i6OT88o1+Jtw/8z0CmdowSLxGhhFK89rQ/oXph/wN02PNw== + dependencies: + "@types/debug" "^4.0.0" + debug "^4.0.0" + decode-named-character-reference "^1.0.0" + devlop "^1.0.0" + micromark-core-commonmark "^2.0.0" + micromark-factory-space "^2.0.0" + micromark-util-character "^2.0.0" + micromark-util-chunked "^2.0.0" + micromark-util-combine-extensions "^2.0.0" + micromark-util-decode-numeric-character-reference "^2.0.0" + micromark-util-edit-map "^1.0.0" + micromark-util-encode "^2.0.0" + micromark-util-normalize-identifier "^2.0.0" + micromark-util-resolve-all "^2.0.0" + micromark-util-sanitize-uri "^2.0.0" + micromark-util-subtokenize "^2.0.0" + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" @@ -1141,6 +1746,50 @@ object-assign@^4.1.1: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== +pako@^0.2.5: + version "0.2.9" + resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" + integrity sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA== + +pako@~1.0.5: + version "1.0.11" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" + integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== + +parse-entities@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-4.0.2.tgz#61d46f5ed28e4ee62e9ddc43d6b010188443f159" + integrity sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw== + dependencies: + "@types/unist" "^2.0.0" + character-entities-legacy "^3.0.0" + character-reference-invalid "^2.0.0" + decode-named-character-reference "^1.0.0" + is-alphanumerical "^2.0.0" + is-decimal "^2.0.0" + is-hexadecimal "^2.0.0" + +pdfkit@^0.19.1: + version "0.19.1" + resolved "https://registry.yarnpkg.com/pdfkit/-/pdfkit-0.19.1.tgz#633d5f031ce6f1ba6ce141325c6cf87fa6acb0a2" + integrity sha512-6Gzk+wDwTs4VSxsR5rCMTnIl5nlmkye1oWB0l2hDB1EX6ZNSIBroKQEv+2+fPPn+stVjyqzmsqRJVDfB9fo5DA== + dependencies: + "@noble/ciphers" "^1.0.0" + "@noble/hashes" "^1.6.0" + fontkit "^2.0.4" + js-md5 "^0.8.3" + linebreak "^1.1.0" + png-js "^1.1.0" + +pdfmake@0.3.11: + version "0.3.11" + resolved "https://registry.yarnpkg.com/pdfmake/-/pdfmake-0.3.11.tgz#b4504d19b8f31fa5063dc1b847b060faa4f7c5bb" + integrity sha512-Uc49J9hUMyuqJk+U+PxlpBpPr96A4HOOfesGx609EPr2ue82+5/Smq/KTAkEqh0/jUGSi1fumvqZ5yAWijJTJg== + dependencies: + linebreak "^1.1.0" + pdfkit "^0.19.1" + xmldoc "^2.0.3" + picocolors@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" @@ -1151,6 +1800,13 @@ picomatch@^4.0.3, picomatch@^4.0.4: resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.7.tgz#6313360034ccb36b3dc61ecbdff78121f90fe21f" integrity sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA== +png-js@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/png-js/-/png-js-1.1.0.tgz#60a135216601f807b88a6d61ac93bd42a32c5ee1" + integrity sha512-PM/uYGzGdNSzqeOgly68+6wKQDL1SY0a/N+OEa/+br6LnHWOAJB0Npiamnodfq3jd2LS/i2fMeOKSAILjA+m5Q== + dependencies: + browserify-zlib "^0.2.0" + postcss@^8.5.6: version "8.5.28" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.5.28.tgz#da4563a99a06e62d6c1cd1acae363224bcaed6e9" @@ -1182,6 +1838,11 @@ prop-types@^15.6.2, prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" +property-information@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/property-information/-/property-information-7.2.0.tgz#0809b34264e995c0bfcd3227028a1e35210af80a" + integrity sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg== + react-aria@^3.48.0: version "3.52.1" resolved "https://registry.yarnpkg.com/react-aria/-/react-aria-3.52.1.tgz#2dfc540b1f97c56c7ba5c47a7ff56ba5646ef589" @@ -1240,6 +1901,23 @@ react-lifecycles-compat@^3.0.4: resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== +react-markdown@10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-10.1.0.tgz#e22bc20faddbc07605c15284255653c0f3bad5ca" + integrity sha512-qKxVopLT/TyA6BX3Ue5NwabOsAzm0Q7kAPwq6L+wWDwisYs7R8vZ0nRXqq6rkueboxpkjvLGU9fWifiX/ZZFxQ== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + devlop "^1.0.0" + hast-util-to-jsx-runtime "^2.0.0" + html-url-attributes "^3.0.0" + mdast-util-to-hast "^13.0.0" + remark-parse "^11.0.0" + remark-rehype "^11.0.0" + unified "^11.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + "react-redux@8.x.x || 9.x.x": version "9.3.0" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-9.3.0.tgz#a30113bb6d95c0a715d54dda4308d450fca6ce09" @@ -1334,6 +2012,27 @@ redux@^5.0.1: resolved "https://registry.yarnpkg.com/redux/-/redux-5.0.1.tgz#97fa26881ce5746500125585d5642c77b6e9447b" integrity sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w== +remark-parse@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/remark-parse/-/remark-parse-11.0.0.tgz#aa60743fcb37ebf6b069204eb4da304e40db45a1" + integrity sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA== + dependencies: + "@types/mdast" "^4.0.0" + mdast-util-from-markdown "^2.0.0" + micromark-util-types "^2.0.0" + unified "^11.0.0" + +remark-rehype@^11.0.0: + version "11.1.2" + resolved "https://registry.yarnpkg.com/remark-rehype/-/remark-rehype-11.1.2.tgz#2addaadda80ca9bd9aa0da763e74d16327683b37" + integrity sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + mdast-util-to-hast "^13.0.0" + unified "^11.0.0" + vfile "^6.0.0" + reselect@5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/reselect/-/reselect-5.2.0.tgz#f380ef7664332d26ea06c1cba04bdbbdcaa955f1" @@ -1344,6 +2043,11 @@ reselect@^5.1.0: resolved "https://registry.yarnpkg.com/reselect/-/reselect-5.3.0.tgz#0a3e3ed4436bdf2ab7c5e0f392dab2c062595d61" integrity sha512-XGoLeRAVzUTcJ1qkxPQhDJyIZ5d6zzZD9nT7AEZOaaU9UbWclhycElmhO+VD5bFeLuzhPBaOV2oXC8uG35ZSpg== +restructure@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/restructure/-/restructure-3.0.2.tgz#e6b2fad214f78edee21797fa8160fef50eb9b49a" + integrity sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw== + rollup@^4.43.0: version "4.63.1" resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.63.1.tgz#a9b96d5b2558d034babb12ad8b67a043bc870ac4" @@ -1390,6 +2094,11 @@ sass@^1.93.3: optionalDependencies: "@parcel/watcher" "^2.4.1" +sax@^1.4.3: + version "1.6.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.6.1.tgz#4c23cf608c0b693ab54b4b5888e92cfe977b9843" + integrity sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q== + scheduler@^0.27.0: version "0.27.0" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.27.0.tgz#0c4ef82d67d1e5c1e359e8fc76d3a87f045fe5bd" @@ -1410,6 +2119,38 @@ set-cookie-parser@^2.6.0: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== +space-separated-tokens@^2.0.0: + version "2.0.2" + resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz#1ecd9d2350a3844572c3f4a312bceb018348859f" + integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q== + +stringify-entities@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/stringify-entities/-/stringify-entities-4.0.4.tgz#b3b79ef5f277cc4ac73caeb0236c5ba939b3a4f3" + integrity sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg== + dependencies: + character-entities-html4 "^2.0.0" + character-entities-legacy "^3.0.0" + +style-to-js@^1.0.0: + version "1.1.21" + resolved "https://registry.yarnpkg.com/style-to-js/-/style-to-js-1.1.21.tgz#2908941187f857e79e28e9cd78008b9a0b3e0e8d" + integrity sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ== + dependencies: + style-to-object "1.0.14" + +style-to-object@1.0.14: + version "1.0.14" + resolved "https://registry.yarnpkg.com/style-to-object/-/style-to-object-1.0.14.tgz#1d22f0e7266bb8c6d8cae5caf4ec4f005e08f611" + integrity sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw== + dependencies: + inline-style-parser "0.2.7" + +tiny-inflate@^1.0.0, tiny-inflate@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4" + integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw== + tiny-invariant@^1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" @@ -1423,6 +2164,16 @@ tinyglobby@^0.2.15: fdir "^6.5.0" picomatch "^4.0.4" +trim-lines@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338" + integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg== + +trough@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/trough/-/trough-2.2.0.tgz#94a60bd6bd375c152c1df911a4b11d5b0256f50f" + integrity sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw== + tsconfck@^3.0.3: version "3.1.6" resolved "https://registry.yarnpkg.com/tsconfck/-/tsconfck-3.1.6.tgz#da1f0b10d82237ac23422374b3fce1edb23c3ead" @@ -1458,6 +2209,78 @@ undici-types@~7.18.0: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-7.18.2.tgz#29357a89e7b7ca4aef3bf0fd3fd0cd73884229e9" integrity sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w== +undici-types@~8.9.0: + version "8.9.0" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-8.9.0.tgz#e240d97c8b5d85e5347ce73d25865c7906c1ec9f" + integrity sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg== + +unicode-properties@^1.4.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/unicode-properties/-/unicode-properties-1.4.1.tgz#96a9cffb7e619a0dc7368c28da27e05fc8f9be5f" + integrity sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg== + dependencies: + base64-js "^1.3.0" + unicode-trie "^2.0.0" + +unicode-trie@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-trie/-/unicode-trie-2.0.0.tgz#8fd8845696e2e14a8b67d78fa9e0dd2cad62fec8" + integrity sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ== + dependencies: + pako "^0.2.5" + tiny-inflate "^1.0.0" + +unified@^11.0.0: + version "11.0.5" + resolved "https://registry.yarnpkg.com/unified/-/unified-11.0.5.tgz#f66677610a5c0a9ee90cab2b8d4d66037026d9e1" + integrity sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA== + dependencies: + "@types/unist" "^3.0.0" + bail "^2.0.0" + devlop "^1.0.0" + extend "^3.0.0" + is-plain-obj "^4.0.0" + trough "^2.0.0" + vfile "^6.0.0" + +unist-util-is@^6.0.0: + version "6.0.1" + resolved "https://registry.yarnpkg.com/unist-util-is/-/unist-util-is-6.0.1.tgz#d0a3f86f2dd0db7acd7d8c2478080b5c67f9c6a9" + integrity sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-position@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/unist-util-position/-/unist-util-position-5.0.0.tgz#678f20ab5ca1207a97d7ea8a388373c9cf896be4" + integrity sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-stringify-position@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz#449c6e21a880e0855bf5aabadeb3a740314abac2" + integrity sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-visit-parents@^6.0.0: + version "6.0.2" + resolved "https://registry.yarnpkg.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz#777df7fb98652ce16b4b7cd999d0a1a40efa3a02" + integrity sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + +unist-util-visit@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/unist-util-visit/-/unist-util-visit-5.1.0.tgz#9a2a28b0aa76a15e0da70a08a5863a2f060e2468" + integrity sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + unist-util-visit-parents "^6.0.0" + update-browserslist-db@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.3.2.tgz#9d99fbff56c50bb11ba5fd35cece5916da595836" @@ -1471,6 +2294,22 @@ use-sync-external-store@^1.2.2, use-sync-external-store@^1.4.0, use-sync-externa resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz#b174bfa65cb2b526732d9f2ac0a408027876f32d" integrity sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w== +vfile-message@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/vfile-message/-/vfile-message-4.0.3.tgz#87b44dddd7b70f0641c2e3ed0864ba73e2ea8df4" + integrity sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-stringify-position "^4.0.0" + +vfile@^6.0.0: + version "6.0.3" + resolved "https://registry.yarnpkg.com/vfile/-/vfile-6.0.3.tgz#3652ab1c496531852bf55a6bac57af981ebc38ab" + integrity sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q== + dependencies: + "@types/unist" "^3.0.0" + vfile-message "^4.0.0" + victory-vendor@^37.0.2: version "37.3.6" resolved "https://registry.yarnpkg.com/victory-vendor/-/victory-vendor-37.3.6.tgz#401ac4b029a0b3d33e0cba8e8a1d765c487254da" @@ -1521,7 +2360,19 @@ warning@^4.0.0, warning@^4.0.3: dependencies: loose-envify "^1.0.0" +xmldoc@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/xmldoc/-/xmldoc-2.0.3.tgz#65b4226b753ea6cd4601f3f56d52338941d38380" + integrity sha512-6gRk4NY/Jvg67xn7OzJuxLRsGgiXBaPUQplVJ/9l99uIugxh4FTOewYz5ic8WScj7Xx/2WvhENiQKwkK9RpE4w== + dependencies: + sax "^1.4.3" + yallist@^3.0.2: version "3.1.1" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== + +zwitch@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7" + integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==