@aphrody/m3-reader 3.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/CollectionReader.d.ts +26 -0
- package/dist/CollectionReader.d.ts.map +1 -0
- package/dist/DatabookReader.d.ts +39 -0
- package/dist/DatabookReader.d.ts.map +1 -0
- package/dist/MangaReader.d.ts +36 -0
- package/dist/MangaReader.d.ts.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1478 -0
- package/dist/index.js.map +14 -0
- package/dist/reader-lifecycle.test.d.ts +2 -0
- package/dist/reader-lifecycle.test.d.ts.map +1 -0
- package/dist/use-focus-trap.d.ts +9 -0
- package/dist/use-focus-trap.d.ts.map +1 -0
- package/dist/use-reader-core.d.ts +33 -0
- package/dist/use-reader-core.d.ts.map +1 -0
- package/dragon-ball.css +60 -0
- package/package.json +59 -0
- package/src/CollectionReader.tsx +82 -0
- package/src/DatabookReader.tsx +804 -0
- package/src/MangaReader.tsx +560 -0
- package/src/index.ts +7 -0
- package/src/reader-lifecycle.test.tsx +428 -0
- package/src/use-focus-trap.ts +129 -0
- package/src/use-reader-core.ts +138 -0
- package/styles.css +61 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/MangaReader.tsx", "../src/use-reader-core.ts", "../src/DatabookReader.tsx", "../src/use-focus-trap.ts", "../src/CollectionReader.tsx"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"\"use client\";\n\n/**\n * Lecteur de scan manga complet, 100 % client.\n *\n * Deux modes togglables :\n * - Paginé : Swiper (Keyboard, Zoom, Virtual, Mousewheel), RTL réel,\n * double-page optionnelle sur grand écran.\n * - Vertical : défilement webtoon virtualisé via `@tanstack/react-virtual`\n * (mesure dynamique des planches, overscan raisonnable).\n *\n * URL resolution and chapter persistence are supplied by the host.\n */\n\nimport {\n type ReactElement,\n type ReactNode,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport { useVirtualizer } from \"@tanstack/react-virtual\";\nimport {\n BookOpen,\n ChevronLeft,\n ChevronRight,\n Columns2,\n Maximize2,\n Minimize2,\n MoveHorizontal,\n ScrollText,\n Square,\n} from \"lucide-react\";\nimport { Swiper, SwiperSlide } from \"swiper/react\";\nimport { Keyboard, Mousewheel, Zoom } from \"swiper/modules\";\n\n// eslint-disable-next-line import/no-unassigned-import -- Swiper layout and interactions require this stylesheet.\nimport \"swiper/css\";\n// eslint-disable-next-line import/no-unassigned-import -- Swiper layout and interactions require this stylesheet.\nimport \"swiper/css/zoom\";\n\nimport {\n SHOW_STRIP_MAX,\n type ViewMode,\n useFullscreen,\n useSwiperSync,\n useVerticalScrollSync,\n virtualRowStyle,\n} from \"./use-reader-core\";\n\nexport type MangaReaderProps = {\n pages: string[];\n title: string;\n direction?: \"rtl\" | \"ltr\";\n prevHref?: string | null;\n nextHref?: string | null;\n prevPages?: string[] | null;\n nextPages?: string[] | null;\n initialPage?: number;\n onPageChange?: (page: number) => void;\n apiPageHref?: (page: number) => string | null;\n resolveAssetUrl?: (path: string) => string;\n resolveMirrorUrl?: (url: string) => string | null;\n renderChapterLink?: (props: {\n href: string;\n children: ReactNode;\n className: string;\n ariaLabel: string;\n }) => ReactNode;\n};\n\nconst LG_BREAKPOINT = 1024; // Tailwind `lg`\n\n/** Détecte le passage sous/au-delà du breakpoint `lg` pour la double-page. */\nfunction useIsLargeScreen(): boolean {\n const [isLarge, setIsLarge] = useState(false);\n useEffect(() => {\n const mql = window.matchMedia(`(min-width: ${LG_BREAKPOINT}px)`);\n const sync = () => setIsLarge(mql.matches);\n sync();\n mql.addEventListener(\"change\", sync);\n return () => mql.removeEventListener(\"change\", sync);\n }, []);\n return isLarge;\n}\n\nexport function MangaReader({\n pages,\n title,\n direction = \"rtl\",\n prevHref = null,\n nextHref = null,\n prevPages = null,\n nextPages = null,\n initialPage = 1,\n onPageChange,\n apiPageHref,\n resolveAssetUrl = (path) => path,\n resolveMirrorUrl,\n renderChapterLink,\n}: MangaReaderProps): ReactElement {\n const containerRef = useRef<HTMLDivElement>(null);\n\n const [mode, setMode] = useState<ViewMode>(\"paged\");\n const [dir, setDir] = useState<\"rtl\" | \"ltr\">(direction);\n const [spread, setSpread] = useState(false);\n const [current, setCurrent] = useState(() =>\n Math.max(0, Math.min(pages.length - 1, initialPage - 1)),\n );\n const { isFullscreen, toggleFullscreen } = useFullscreen(containerRef);\n const { swiperRef, handleSwiper, handleSlideChange } = useSwiperSync(setCurrent);\n\n const isLarge = useIsLargeScreen();\n // Double-page seulement effective sur grand écran (lisibilité mobile).\n const useSpread = spread && isLarge && mode === \"paged\";\n\n const activeThumbRef = useRef<HTMLButtonElement | null>(null);\n\n // Défilement automatique fluide de la bande de vignettes vers la planche active\n useEffect(() => {\n if (mode === \"paged\" && activeThumbRef.current) {\n activeThumbRef.current.scrollIntoView({\n behavior: \"smooth\",\n block: \"nearest\",\n inline: \"center\",\n });\n }\n }, [current, mode]);\n\n // Résolution des URL absolues une seule fois (les chemins sont stables).\n const urls = useMemo(() => pages.map(resolveAssetUrl), [pages, resolveAssetUrl]);\n const total = urls.length;\n const baseId = useId();\n\n // Préchargement intelligent des planches en arrière-plan\n useEffect(() => {\n if (urls.length === 0) return;\n // 1. Précharge la page courante, la précédente (retour arrière) et les 3 suivantes\n const indices = [current, current - 1, current + 1, current + 2, current + 3];\n for (const idx of indices) {\n if (idx >= 0 && idx < urls.length) {\n const img = new Image();\n img.src = urls[idx];\n }\n }\n\n // 2. Précharge les 3 premières pages du chapitre suivant en arrière-plan\n if (nextPages && nextPages.length > 0) {\n nextPages.slice(0, 3).forEach((page) => {\n const img = new Image();\n img.src = resolveAssetUrl(page);\n });\n }\n\n // 3. Précharge les 3 dernières pages du chapitre précédent en arrière-plan (si l'utilisateur veut revenir en arrière)\n if (prevPages && prevPages.length > 0) {\n prevPages.slice(-3).forEach((page) => {\n const img = new Image();\n img.src = resolveAssetUrl(page);\n });\n }\n }, [current, urls, prevPages, nextPages, resolveAssetUrl]);\n\n // Repli automatique CDN <-> Bot en cas d'erreur réseau sur une planche\n const handleImageError = useCallback(\n (e: React.SyntheticEvent<HTMLImageElement, Event>) => {\n const target = e.currentTarget;\n if (target.dataset.fallbackApplied) return;\n target.dataset.fallbackApplied = \"true\";\n try {\n const mirror = resolveMirrorUrl?.(target.src);\n if (mirror) target.src = mirror;\n } catch {\n // URL invalide, aucun repli possible\n }\n },\n [resolveMirrorUrl],\n );\n\n /* ------------------------------ Vertical ------------------------------ */\n\n const scrollParentRef = useRef<HTMLDivElement>(null);\n const virtualizer = useVirtualizer({\n count: total,\n getScrollElement: () => scrollParentRef.current,\n // Estimation ~ hauteur viewport ; remplacée par la mesure réelle.\n estimateSize: () => (typeof window === \"undefined\" ? 900 : window.innerHeight),\n overscan: 3,\n });\n\n // Synchronise le compteur en mode vertical sur la page la plus visible.\n useVerticalScrollSync(mode, scrollParentRef, virtualizer, setCurrent);\n\n useEffect(() => {\n onPageChange?.(current + 1);\n }, [current, onPageChange]);\n const apiPlanche = apiPageHref?.(current + 1) ?? null;\n\n /* ------------------------------ Contrôles ----------------------------- */\n\n const goPrevPage = useCallback(() => {\n if (mode === \"paged\") swiperRef.current?.slidePrev();\n else virtualizer.scrollToIndex(Math.max(0, current - 1), { align: \"start\" });\n }, [mode, current, virtualizer]);\n\n const goNextPage = useCallback(() => {\n if (mode === \"paged\") swiperRef.current?.slideNext();\n else\n virtualizer.scrollToIndex(Math.min(total - 1, current + 1), {\n align: \"start\",\n });\n }, [mode, current, total, virtualizer]);\n\n const counter = total ? `${Math.min(current + 1, total)} / ${total}` : \"0 / 0\";\n\n return (\n <div\n ref={containerRef}\n className=\"m3-reader m3-reader-container m3-reader-scroll flex h-[85vh] min-h-[460px] w-full min-w-0 max-w-full flex-col overflow-hidden rounded-xl bg-[var(--reader-surface)] [&:fullscreen]:h-screen [&:fullscreen]:min-h-screen [&:fullscreen]:rounded-none\"\n data-manga-reader\n data-m3-component=\"MangaReader\"\n data-testid=\"manga-reader\"\n role=\"region\"\n aria-label={title}\n >\n {/* Barre de contrôles Toriyama HUD */}\n <div className=\"m3-reader-hud z-10 flex flex-wrap items-center justify-between gap-2 px-3.5 py-2.5\">\n <div className=\"flex items-center gap-2.5 min-w-0\">\n <span className=\"min-w-0 max-w-[32ch] md:max-w-[45ch] truncate font-sans text-[18px] font-extrabold uppercase tracking-wide text-[var(--reader-on-surface)] drop-shadow-[1px_1px_0px_#000]\">\n {title}\n </span>\n <span className=\"m3-reader-badge bg-[var(--reader-accent)] text-[var(--reader-on-accent)] px-2 py-0.5 text-[18px] font-black\">\n <span>{counter}</span>\n </span>\n {/* L'adresse de LA planche affichée, en un clic : la même que celle\n\t\t\t\t\t que rendra `curl`. C'est le seul endroit où la correspondance\n\t\t\t\t\t entre ce qu'on voit et ce qu'on peut récupérer est évidente. */}\n {apiPlanche && (\n <a\n href={apiPlanche}\n className=\"hidden shrink-0 rounded border border-white/15 px-1.5 py-0.5 font-mono text-[18px] font-bold uppercase tracking-wider text-[var(--reader-on-surface)]/50 transition-colors hover:border-[var(--reader-accent)] hover:text-[var(--reader-accent)] sm:inline-block\"\n title=\"Cette planche en JSON (image + transcription)\"\n >\n API\n </a>\n )}\n </div>\n\n <div className=\"flex flex-wrap items-center gap-1.5 ml-auto\">\n {/* Toggle mode lecture */}\n <button\n type=\"button\"\n className={mode === \"paged\" ? \"m3-reader-btn-active\" : \"m3-reader-btn\"}\n onClick={() => setMode(\"paged\")}\n aria-pressed={mode === \"paged\"}\n aria-label=\"Mode de lecture paginé\"\n title=\"Mode paginé\"\n >\n <BookOpen size={13} aria-hidden /> Paginé\n </button>\n <button\n type=\"button\"\n className={mode === \"vertical\" ? \"m3-reader-btn-active\" : \"m3-reader-btn\"}\n onClick={() => setMode(\"vertical\")}\n aria-pressed={mode === \"vertical\"}\n aria-label=\"Mode de lecture vertical (webtoon)\"\n title=\"Mode vertical (webtoon)\"\n >\n <ScrollText size={13} aria-hidden /> Défilement\n </button>\n\n {/* Toggle sens de lecture */}\n <button\n type=\"button\"\n className={dir === \"rtl\" ? \"m3-reader-btn-active\" : \"m3-reader-btn\"}\n onClick={() => setDir((d) => (d === \"rtl\" ? \"ltr\" : \"rtl\"))}\n aria-label={\n dir === \"rtl\"\n ? \"Sens de lecture manga (droite vers gauche)\"\n : \"Sens de lecture BD (gauche vers droite)\"\n }\n title={dir === \"rtl\" ? \"Sens manga (droite→gauche)\" : \"Sens BD (gauche→droite)\"}\n >\n <MoveHorizontal size={13} aria-hidden />\n {dir === \"rtl\" ? \"RTL\" : \"LTR\"}\n </button>\n\n {/* Toggle double-page (paginé + grand écran) */}\n <button\n type=\"button\"\n className={useSpread ? \"m3-reader-btn-active\" : \"m3-reader-btn\"}\n onClick={() => setSpread((s) => !s)}\n disabled={mode !== \"paged\" || !isLarge}\n aria-pressed={useSpread}\n aria-label=\"Affichage double-page (grand écran)\"\n title=\"Double-page (grand écran)\"\n >\n {useSpread ? <Columns2 size={13} aria-hidden /> : <Square size={13} aria-hidden />}\n Double\n </button>\n\n {/* Plein écran */}\n <button\n type=\"button\"\n className=\"m3-reader-btn\"\n onClick={toggleFullscreen}\n aria-label={isFullscreen ? \"Quitter le plein écran\" : \"Activer le plein écran\"}\n title={isFullscreen ? \"Quitter le plein écran\" : \"Plein écran\"}\n >\n {isFullscreen ? (\n <Minimize2 size={13} aria-hidden />\n ) : (\n <Maximize2 size={13} aria-hidden />\n )}\n </button>\n\n {/* Navigation chapitres */}\n {prevHref ? (\n renderChapterLink ? (\n renderChapterLink({\n href: prevHref,\n children: (\n <>\n <ChevronLeft size={13} aria-hidden /> Préc.\n </>\n ),\n className: \"m3-reader-btn\",\n ariaLabel: \"Chapitre précédent\",\n })\n ) : (\n <a\n href={prevHref}\n className=\"m3-reader-btn\"\n aria-label=\"Chapitre précédent\"\n title=\"Chapitre précédent\"\n >\n <ChevronLeft size={13} aria-hidden /> Préc.\n </a>\n )\n ) : (\n <span className=\"m3-reader-btn pointer-events-none opacity-40\">\n <ChevronLeft size={13} aria-hidden /> Préc.\n </span>\n )}\n {nextHref ? (\n renderChapterLink ? (\n renderChapterLink({\n href: nextHref,\n children: (\n <>\n Suiv. <ChevronRight size={13} aria-hidden />\n </>\n ),\n className: \"m3-reader-btn\",\n ariaLabel: \"Chapitre suivant\",\n })\n ) : (\n <a\n href={nextHref}\n className=\"m3-reader-btn\"\n aria-label=\"Chapitre suivant\"\n title=\"Chapitre suivant\"\n >\n Suiv. <ChevronRight size={13} aria-hidden />\n </a>\n )\n ) : (\n <span className=\"m3-reader-btn pointer-events-none opacity-40\">\n Suiv. <ChevronRight size={13} aria-hidden />\n </span>\n )}\n </div>\n </div>\n\n {/* Zone de lecture */}\n <div className=\"relative min-h-0 min-w-0 flex-1 overflow-hidden bg-[var(--reader-surface)]\">\n {total === 0 ? (\n <div className=\"flex h-full items-center justify-center text-[18px] text-[var(--reader-on-surface)]\">\n Aucune planche disponible.\n </div>\n ) : mode === \"paged\" ? (\n <>\n <Swiper\n key={`${dir}-${useSpread ? \"spread\" : \"single\"}`}\n // Le `key` force un remount à chaque changement de sens/double-page :\n // on repart de la planche courante plutôt que de la page 1.\n initialSlide={current}\n dir={dir}\n modules={[Keyboard, Zoom, Mousewheel]}\n className=\"h-full w-full max-w-full\"\n slidesPerView={useSpread ? 2 : 1}\n spaceBetween={useSpread ? 8 : 0}\n keyboard={{ enabled: true }}\n mousewheel={{ forceToAxis: true }}\n zoom={{ maxRatio: 3, toggle: true }}\n onSwiper={handleSwiper}\n onSlideChange={handleSlideChange}\n >\n {urls.map((url, i) => (\n <SwiperSlide key={url + i} zoom className=\"flex items-center justify-center\">\n {/* `swiper-zoom-container` requis par le module Zoom (pan/pinch). */}\n <div className=\"swiper-zoom-container flex h-full w-full max-w-full items-center justify-center\">\n <img\n src={url}\n alt={`${title} — page ${i + 1}`}\n loading={Math.abs(i - current) <= 1 ? \"eager\" : \"lazy\"}\n draggable={false}\n className=\"max-h-full max-w-full object-contain pointer-events-none select-none\"\n onError={handleImageError}\n />\n </div>\n </SwiperSlide>\n ))}\n </Swiper>\n\n {/* Flèches de navigation (respectent le sens RTL/LTR). */}\n <button\n type=\"button\"\n aria-label=\"Page précédente\"\n onClick={goPrevPage}\n className=\"absolute top-1/2 left-3 z-10 -translate-y-1/2 rounded-lg border-2 border-[var(--reader-outline)] bg-[var(--reader-surface)]/85 p-2.5 text-[var(--reader-accent)] shadow-[3px_3px_0px_#000] transition-all hover:bg-[var(--reader-accent)] hover:text-[var(--reader-on-accent)] hover:scale-105 active:translate-y-[calc(-50%+1px)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--reader-accent)]\"\n >\n {dir === \"rtl\" ? (\n <ChevronRight size={22} aria-hidden />\n ) : (\n <ChevronLeft size={22} aria-hidden />\n )}\n </button>\n <button\n type=\"button\"\n aria-label=\"Page suivante\"\n onClick={goNextPage}\n className=\"absolute top-1/2 right-3 z-10 -translate-y-1/2 rounded-lg border-2 border-[var(--reader-outline)] bg-[var(--reader-surface)]/85 p-2.5 text-[var(--reader-accent)] shadow-[3px_3px_0px_#000] transition-all hover:bg-[var(--reader-accent)] hover:text-[var(--reader-on-accent)] hover:scale-105 active:translate-y-[calc(-50%+1px)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--reader-accent)]\"\n >\n {dir === \"rtl\" ? (\n <ChevronLeft size={22} aria-hidden />\n ) : (\n <ChevronRight size={22} aria-hidden />\n )}\n </button>\n </>\n ) : (\n <div\n ref={scrollParentRef}\n // `tabIndex` requis pour rendre le conteneur défilable au clavier\n // (flèches/PgUp/PgDn) — sinon la lecture verticale est inatteignable.\n tabIndex={0}\n className=\"h-full w-full overflow-y-auto overflow-x-hidden m3-reader-scroll focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--reader-accent)]\"\n aria-label={`${title} — lecture verticale`}\n >\n <div\n style={{\n height: virtualizer.getTotalSize(),\n width: \"100%\",\n position: \"relative\",\n }}\n >\n {virtualizer.getVirtualItems().map((vi) => (\n <div\n key={`${baseId}-${vi.key}`}\n data-index={vi.index}\n ref={virtualizer.measureElement}\n style={virtualRowStyle(vi.start)}\n className=\"flex justify-center py-2\"\n >\n <img\n src={urls[vi.index]}\n alt={`${title} — page ${vi.index + 1}`}\n loading={Math.abs(vi.index - current) <= 1 ? \"eager\" : \"lazy\"}\n draggable={false}\n className=\"h-auto w-full max-w-3xl object-contain border-y md:border-2 border-[var(--reader-outline)]/40 shadow-[0_4px_12px_rgba(0,0,0,0.8)]\"\n onError={handleImageError}\n />\n </div>\n ))}\n </div>\n </div>\n )}\n </div>\n\n {/* Bande de vignettes (chapitres courts uniquement — cf. SHOW_STRIP_MAX). */}\n {mode === \"paged\" && total > 0 && total <= SHOW_STRIP_MAX && (\n <div className=\"z-10 w-full min-w-0 max-w-full bg-[var(--reader-surface)] border-t-2 border-[var(--reader-outline)] px-4 py-3 flex items-center overflow-x-auto m3-reader-scroll\">\n <div className=\"flex gap-2.5 min-w-max mx-auto px-2\">\n {urls.map((url, idx) => (\n <button\n key={url + \"-thumb-\" + idx}\n ref={current === idx ? activeThumbRef : undefined}\n type=\"button\"\n onClick={() => {\n setCurrent(idx);\n swiperRef.current?.slideTo(idx);\n }}\n className={`relative shrink-0 w-12 h-16 rounded-md overflow-hidden border-2 transition-all ${\n current === idx\n ? \"border-[var(--reader-accent)] scale-105 shadow-[0_0_12px_var(--reader-accent)]\"\n : \"border-[var(--reader-outline)] shadow-[2px_2px_0px_#000] hover:border-white/50\"\n }`}\n >\n <img\n src={url}\n alt={`Page ${idx + 1}`}\n className=\"w-full h-full object-cover select-none pointer-events-none\"\n loading=\"lazy\"\n />\n <span className=\"absolute bottom-0 inset-x-0 bg-[var(--reader-surface)]/85 text-[18px] font-extrabold text-[var(--reader-on-surface)] text-center font-mono py-0.5 border-t border-[var(--reader-outline)]/60\">\n {idx + 1}\n </span>\n </button>\n ))}\n </div>\n </div>\n )}\n\n {/* Tomes longs : la bande de vignettes serait trop lourde (jusqu'à ~282 nœuds)\n\t\t\t → saut direct à une planche par saisie du numéro. */}\n {mode === \"paged\" && total > SHOW_STRIP_MAX && (\n <form\n className=\"z-10 flex items-center justify-center gap-3 border-t-2 border-[var(--reader-outline)] bg-[var(--reader-surface)] px-4 py-3\"\n onSubmit={(e) => {\n e.preventDefault();\n const field = e.currentTarget.elements.namedItem(\"goto\") as HTMLInputElement | null;\n const val = Number(field?.value);\n if (!Number.isFinite(val)) return;\n const i = Math.min(Math.max(val - 1, 0), total - 1);\n setCurrent(i);\n swiperRef.current?.slideTo(i);\n }}\n >\n <label\n htmlFor={`${baseId}-goto`}\n className=\"text-[18px] font-black uppercase tracking-wider text-[var(--reader-on-surface)]\"\n >\n Aller à la planche\n </label>\n <input\n id={`${baseId}-goto`}\n name=\"goto\"\n type=\"number\"\n min={1}\n max={total}\n key={current}\n defaultValue={current + 1}\n className=\"w-20 rounded-md border-2 border-[var(--reader-outline)] bg-[var(--reader-surface)] px-2.5 py-1 text-center text-[18px] font-extrabold tabular-nums text-[var(--reader-accent)] shadow-[2px_2px_0px_#000] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--reader-accent)]\"\n />\n <span className=\"text-[18px] font-bold tabular-nums text-[var(--reader-on-surface)]/70\">\n / {total}\n </span>\n <button type=\"submit\" className=\"m3-reader-btn-active !py-1 !px-3.5\">\n OK\n </button>\n </form>\n )}\n </div>\n );\n}\n",
|
|
6
|
+
"// SPDX-License-Identifier: Apache-2.0\n\"use client\";\n\n/**\n * Behaviour shared by `MangaReader` and `DatabookReader`: reading mode, fullscreen, the Swiper\n * handle and the scroll-position sync of the virtualized vertical mode.\n */\n\nimport {\n type CSSProperties,\n type RefObject,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport type { Virtualizer } from \"@tanstack/react-virtual\";\nimport type { SwiperClass } from \"swiper/react\";\n\nexport type ViewMode = \"paged\" | \"vertical\";\n\n/**\n * Above this many pages the thumbnail strip (one DOM node per page, up to ~282 for a whole volume)\n * is too heavy: a compact \"go to page\" control replaces it.\n */\nexport const SHOW_STRIP_MAX = 40;\n\n/**\n * Fullscreen state of this container. The toggle resolves false on native failure;\n * `fullscreenError` exposes that failure without rejecting a button's event handler.\n */\nexport function useFullscreen(containerRef: RefObject<HTMLElement | null>) {\n const [isFullscreen, setIsFullscreen] = useState(false);\n const [fullscreenError, setFullscreenError] = useState<Error | null>(null);\n const mounted = useRef(false);\n const operation = useRef(0);\n\n const toggleFullscreen = useCallback(async (): Promise<boolean> => {\n const el = containerRef.current;\n if (!el) return false;\n const doc = el.ownerDocument;\n const current = ++operation.current;\n if (mounted.current) setFullscreenError(null);\n try {\n if (doc.fullscreenElement === el) {\n if (typeof doc.exitFullscreen !== \"function\")\n throw new Error(\"Fullscreen exit is unavailable\");\n await doc.exitFullscreen();\n } else {\n // Request this container directly; never exit another component's fullscreen.\n if (typeof el.requestFullscreen !== \"function\")\n throw new Error(\"Fullscreen is unavailable\");\n await el.requestFullscreen();\n }\n if (mounted.current && current === operation.current) {\n setIsFullscreen(\n Boolean(containerRef.current && doc.fullscreenElement === containerRef.current),\n );\n }\n return true;\n } catch (reason) {\n if (mounted.current && current === operation.current) {\n setFullscreenError(reason instanceof Error ? reason : new Error(String(reason)));\n }\n return false;\n }\n }, [containerRef]);\n\n useEffect(() => {\n const doc = containerRef.current?.ownerDocument;\n if (!doc) return;\n mounted.current = true;\n const onChange = () =>\n setIsFullscreen(\n Boolean(containerRef.current && doc.fullscreenElement === containerRef.current),\n );\n onChange();\n doc.addEventListener(\"fullscreenchange\", onChange);\n return () => {\n mounted.current = false;\n operation.current++;\n doc.removeEventListener(\"fullscreenchange\", onChange);\n };\n }, [containerRef]);\n\n return { isFullscreen, fullscreenError, toggleFullscreen };\n}\n\n/** The Swiper instance and the two callbacks that keep `current` in step with it. */\nexport function useSwiperSync(setCurrent: (index: number) => void) {\n const swiperRef = useRef<SwiperClass | null>(null);\n\n const handleSwiper = useCallback(\n (sw: SwiperClass) => {\n swiperRef.current = sw;\n setCurrent(sw.realIndex ?? sw.activeIndex ?? 0);\n },\n [setCurrent],\n );\n\n const handleSlideChange = useCallback(\n (sw: SwiperClass) => setCurrent(sw.realIndex ?? sw.activeIndex ?? 0),\n [setCurrent],\n );\n\n return { swiperRef, handleSwiper, handleSlideChange };\n}\n\n/** In vertical mode, `current` follows the page at the middle of the scroll viewport. */\nexport function useVerticalScrollSync(\n mode: ViewMode,\n scrollParentRef: RefObject<HTMLElement | null>,\n virtualizer: Virtualizer<HTMLDivElement, Element>,\n setCurrent: (index: number) => void,\n) {\n useEffect(() => {\n if (mode !== \"vertical\") return;\n const el = scrollParentRef.current;\n if (!el) return;\n const onScroll = () => {\n const mid = el.scrollTop + el.clientHeight / 2;\n const item = virtualizer.getVirtualItems().find((vi) => vi.start <= mid && vi.end >= mid);\n if (item) setCurrent(item.index);\n };\n el.addEventListener(\"scroll\", onScroll, { passive: true });\n onScroll();\n return () => el.removeEventListener(\"scroll\", onScroll);\n }, [mode, scrollParentRef, virtualizer, setCurrent]);\n}\n\n/** Absolute placement of one virtual row. */\nexport const virtualRowStyle = (start: number): CSSProperties => ({\n position: \"absolute\",\n top: 0,\n left: 0,\n width: \"100%\",\n transform: `translateY(${start}px)`,\n});\n",
|
|
7
|
+
"\"use client\";\n\n/**\n * Lecteur de databook / interview — format manga + légende.\n *\n * Deux modes :\n * - Vertical (défaut) : défilement webtoon, image puis texte en dessous.\n * - Paginé : une planche à la fois (Swiper). Sur grand écran, layout\n * planche | texte côte à côte pour que les légendes longues ne mangent\n * plus l'image. Clic sur la planche → lightbox plein écran.\n *\n * Les pages sans image ni texte sont filtrées côté public (slots admin vides).\n * LTR par défaut. Affiche le `number` éditorial (pas seulement l'index).\n */\n\nimport type React from \"react\";\nimport type { ReactNode } from \"react\";\nimport { type ReactElement, useCallback, useEffect, useId, useMemo, useRef, useState } from \"react\";\nimport { useVirtualizer } from \"@tanstack/react-virtual\";\nimport { useFocusTrap } from \"./use-focus-trap\";\nimport {\n BookOpen,\n ChevronLeft,\n ChevronRight,\n Maximize2,\n Minimize2,\n ScrollText,\n X,\n ZoomIn,\n} from \"lucide-react\";\nimport { Swiper, SwiperSlide } from \"swiper/react\";\nimport { Mousewheel, Virtual, Zoom } from \"swiper/modules\";\n\n// eslint-disable-next-line import/no-unassigned-import -- Swiper layout and interactions require this stylesheet.\nimport \"swiper/css\";\n// eslint-disable-next-line import/no-unassigned-import -- Swiper layout and interactions require this stylesheet.\nimport \"swiper/css/zoom\";\n// eslint-disable-next-line import/no-unassigned-import -- Swiper layout and interactions require this stylesheet.\nimport \"swiper/css/virtual\";\n\nimport {\n SHOW_STRIP_MAX,\n type ViewMode,\n useFullscreen,\n useSwiperSync,\n useVerticalScrollSync,\n virtualRowStyle,\n} from \"./use-reader-core\";\n\nexport type DatabookReaderPage = {\n /** Numéro affiché (auto 1…N, modifiable côté admin). */\n number?: number | null;\n image?: string | null;\n /**\n * Transcription — présente pour les premières planches seulement.\n * Au-delà, la fiche envoie `text: null` et `aDuTexte: true`, et le lecteur\n * demande le texte au fournisseur de transcriptions au fil du défilement :\n * embarquer les 313 transcriptions d'un Daizenshuu pesait 412 Ko de charge\n * RSC pour une planche lue.\n */\n text?: string | null;\n /** La planche PORTE une transcription, même si elle n'est pas encore chargée. */\n aDuTexte?: boolean;\n /** Transcription relue à l'image : pas d'avertissement de lecture douteuse. */\n verifiee?: boolean;\n};\n\nexport type ReaderTranscription = { text: string; page: number; verified: boolean };\n\nexport type DatabookReaderProps = {\n pages: DatabookReaderPage[];\n title: string;\n resolveAssetUrl?: (path: string) => string;\n imageSrc?: (url: string, width: number) => string;\n imageSrcSet?: (url: string, widths: number[]) => string | null;\n loadTranscriptions?: (pages: number[], signal: AbortSignal) => Promise<Record<string, string>>;\n renderTranscription?: (transcription: ReaderTranscription) => ReactNode;\n preloadCount?: number;\n};\n\ntype ResolvedPage = {\n /** Numéro affiché (édition admin) — pas forcément = position dans le lecteur. */\n number: number;\n imageUrl: string | null;\n text: string | null;\n /** Une transcription existe côté serveur, chargée ou non. */\n aDuTexte: boolean;\n /** Transcription acquittée à la main (drapeau porté par la fiche). */\n verifiee: boolean;\n};\n\nfunction pageNumber(p: DatabookReaderPage, fallback: number): number {\n const raw = p.number as unknown;\n if (typeof raw === \"number\" && Number.isFinite(raw)) return Math.trunc(raw);\n if (typeof raw === \"string\" && String(raw).trim() && Number.isFinite(Number(raw))) {\n return Math.trunc(Number(raw));\n }\n return fallback;\n}\n\nfunction resolvePages(\n pages: DatabookReaderPage[],\n resolveAssetUrl: (path: string) => string,\n): ResolvedPage[] {\n const out: ResolvedPage[] = [];\n for (let i = 0; i < pages.length; i++) {\n const p = pages[i];\n const img = typeof p.image === \"string\" && p.image.trim() ? p.image.trim() : null;\n const text = typeof p.text === \"string\" && p.text.trim() ? p.text.trim() : null;\n const aDuTexte = p.aDuTexte === true || text !== null;\n // Une planche sans image ET sans transcription est un emplacement vide\n // réservé côté studio : elle ne se montre pas au public.\n if (!img && !aDuTexte) continue;\n out.push({\n number: pageNumber(p, i + 1),\n imageUrl: img ? resolveAssetUrl(img) : null,\n text,\n aDuTexte,\n verifiee: p.verifiee === true,\n });\n }\n return out;\n}\n\n/** Combien de planches autour de la courante on charge d'avance. */\nconst FENETRE_TEXTE = 6;\n\n/**\n * Transcriptions chargées à la demande.\n *\n * La fiche n'embarque que les premières ; celles qu'on approche sont demandées\n * par fenêtres au fournisseur transmis par l'hôte. Une planche déjà connue n'est\n * jamais redemandée, et un échec réseau laisse simplement la planche sans\n * texte — jamais d'erreur jetée à la figure d'un lecteur qui feuillette.\n */\nfunction useTextesPlanches(\n loadTranscriptions: DatabookReaderProps[\"loadTranscriptions\"],\n items: ResolvedPage[],\n current: number,\n): Map<number, string> {\n const [charges, setCharges] = useState<Map<number, string>>(() => new Map());\n // Les numéros déjà demandés (succès ou échec) : sans cette mémoire, une\n // planche sans transcription serait redemandée à chaque défilement.\n const demandes = useRef<Set<number>>(new Set());\n\n const manquants = useMemo(() => {\n if (!loadTranscriptions) return [];\n const de = Math.max(0, current - FENETRE_TEXTE);\n const a = Math.min(items.length, current + FENETRE_TEXTE + 1);\n const out: number[] = [];\n for (let i = de; i < a; i++) {\n const it = items[i];\n if (!it || !it.aDuTexte || it.text !== null) continue;\n if (charges.has(it.number) || demandes.current.has(it.number)) continue;\n out.push(it.number);\n }\n return out;\n }, [loadTranscriptions, items, current, charges]);\n\n useEffect(() => {\n if (manquants.length === 0) return;\n for (const n of manquants) demandes.current.add(n);\n const stop = new AbortController();\n loadTranscriptions!(manquants, stop.signal)\n .then((textes) => ({ textes }))\n .then((d: { textes?: Record<string, string> }) => {\n const recus = Object.entries(d.textes ?? {});\n if (recus.length === 0) return undefined;\n setCharges((prec) => {\n const suiv = new Map(prec);\n for (const [n, t] of recus) suiv.set(Number(n), t);\n return suiv;\n });\n return undefined;\n })\n .catch(() => {\n /* hors ligne ou navigation : la planche reste sans texte */\n });\n return () => stop.abort();\n }, [manquants, loadTranscriptions]);\n\n return charges;\n}\n\nexport function DatabookReader({\n pages,\n title,\n resolveAssetUrl = (path) => path,\n imageSrc = (url) => url,\n imageSrcSet = () => null,\n loadTranscriptions,\n renderTranscription,\n preloadCount = 4,\n}: DatabookReaderProps): ReactElement {\n const containerRef = useRef<HTMLDivElement>(null);\n const lightboxRef = useRef<HTMLDivElement>(null);\n const baseId = useId();\n\n const items = useMemo(() => resolvePages(pages, resolveAssetUrl), [pages, resolveAssetUrl]);\n const total = items.length;\n\n const [mode, setMode] = useState<ViewMode>(\"vertical\");\n const [current, setCurrent] = useState(0);\n const { isFullscreen, toggleFullscreen } = useFullscreen(containerRef);\n const { swiperRef, handleSwiper, handleSlideChange } = useSwiperSync(setCurrent);\n /** Lightbox plein écran sur la planche courante (indépendant du mode). */\n const [lightbox, setLightbox] = useState(false);\n\n const textesCharges = useTextesPlanches(loadTranscriptions, items, current);\n /** Le texte d'une planche : celui de la fiche, sinon celui chargé après coup. */\n const texteDe = useCallback(\n (p: ResolvedPage | undefined): string | null =>\n p ? (p.text ?? textesCharges.get(p.number) ?? null) : null,\n [textesCharges],\n );\n useFocusTrap(lightboxRef, lightbox, () => setLightbox(false));\n\n // Clamp l'index si le nombre de pages change (édition live / revalidation).\n useEffect(() => {\n if (current >= total && total > 0) setCurrent(total - 1);\n if (total === 0) setCurrent(0);\n }, [total, current]);\n\n // Précharge planche courante ± voisines.\n useEffect(() => {\n if (total === 0) return;\n for (const idx of [current - 1, current, current + 1, current + 2]) {\n const url = items[idx]?.imageUrl;\n if (!url) continue;\n const img = new Image();\n img.src = url;\n }\n }, [current, items, total]);\n\n // Détection côté client uniquement (`document` absent au rendu serveur).\n const [fullscreenDispo, setFullscreenDispo] = useState(false);\n useEffect(() => {\n setFullscreenDispo(\n typeof document !== \"undefined\" &&\n document.fullscreenEnabled &&\n typeof containerRef.current?.requestFullscreen === \"function\",\n );\n }, []);\n\n /* ------------------------------ Vertical ------------------------------ */\n\n const scrollParentRef = useRef<HTMLDivElement>(null);\n const virtualizer = useVirtualizer({\n count: total,\n getScrollElement: () => scrollParentRef.current,\n estimateSize: () =>\n typeof window === \"undefined\" ? 1100 : Math.round(window.innerHeight * 0.95),\n // Marge de préchargement réglée sur les capacités : 4 planches rendent le\n // défilement continu sur un scan de 1 à 2 Mo, mais les précharger sur un\n // mobile en 2G dépense des données pour des pages qu'on ne verra\n // peut-être jamais. `prechargement` vaut 1 sur réseau lent, 6 sur une\n // machine confortable.\n overscan: Math.max(2, preloadCount),\n });\n\n const goTo = useCallback(\n (idx: number) => {\n const i = Math.min(Math.max(idx, 0), Math.max(0, total - 1));\n setCurrent(i);\n if (mode === \"paged\") swiperRef.current?.slideTo(i);\n else virtualizer.scrollToIndex(i, { align: \"start\" });\n },\n [mode, total, virtualizer],\n );\n\n useVerticalScrollSync(mode, scrollParentRef, virtualizer, setCurrent);\n\n // Navigation clavier des planches.\n //\n // Une SEULE source de vérité : ce handler, qui passe par `goTo` (donc\n // `slideTo` en mode paginé). Le module `Keyboard` de Swiper est\n // volontairement absent : il écoute lui aussi `keydown`, sur `document`,\n // et faisait AVANCER DEUX FOIS — Swiper glissait d'une planche et émettait\n // `slideChange` (`setCurrent(index)`), puis ce handler ajoutait +1 par-dessus.\n // `current` se décalait durablement de la planche affichée, si bien que la\n // transcription montrée à côté du scan était celle d'une AUTRE page (mesuré\n // sur /wiki/databooks/305 : compteur « n°3 · 3/20 » sur la planche 2).\n useEffect(() => {\n const actif = lightbox || mode === \"paged\";\n if (!actif) return;\n const onKey = (e: KeyboardEvent) => {\n const cible = e.target as HTMLElement | null;\n const tag = cible?.tagName;\n if (tag === \"INPUT\" || tag === \"TEXTAREA\" || cible?.isContentEditable) return;\n switch (e.key) {\n case \"Escape\":\n if (lightbox) setLightbox(false);\n return;\n case \"ArrowLeft\":\n e.preventDefault();\n goTo(current - 1);\n return;\n case \"ArrowRight\":\n e.preventDefault();\n goTo(current + 1);\n return;\n case \"Home\":\n e.preventDefault();\n goTo(0);\n return;\n case \"End\":\n e.preventDefault();\n goTo(total - 1);\n return;\n }\n };\n window.addEventListener(\"keydown\", onKey);\n return () => window.removeEventListener(\"keydown\", onKey);\n }, [lightbox, mode, total, current, goTo]);\n\n const goPrevPage = useCallback(() => {\n goTo(current - 1);\n }, [current, goTo]);\n\n const goNextPage = useCallback(() => {\n goTo(current + 1);\n }, [current, goTo]);\n\n const openLightbox = useCallback(\n (idx?: number) => {\n if (typeof idx === \"number\") setCurrent(idx);\n const item = items[typeof idx === \"number\" ? idx : current];\n if (item?.imageUrl) setLightbox(true);\n },\n [items, current],\n );\n\n // `min-h-10`/`min-w-10` (40 px) : les commandes du lecteur mesuraient 28 à\n // 30 px de haut, sous le seuil tactile. Le padding reste serré à partir de\n // `sm`, où l'on pointe à la souris.\n // Balayage horizontal dans la visionneuse : sur mobile elle n'offrait que deux\n // flèches de 44 px posées sur l'image — le geste naturel (faire glisser la\n // planche) ne faisait rien. Seuil de 48 px, et on ignore les gestes\n // majoritairement verticaux pour ne pas voler un défilement.\n const toucheRef = useRef<{ x: number; y: number } | null>(null);\n const onTouchStart = useCallback((e: React.TouchEvent) => {\n const t = e.touches[0];\n toucheRef.current = t ? { x: t.clientX, y: t.clientY } : null;\n }, []);\n const onTouchEnd = useCallback(\n (e: React.TouchEvent) => {\n const depart = toucheRef.current;\n toucheRef.current = null;\n const t = e.changedTouches[0];\n if (!depart || !t) return;\n const dx = t.clientX - depart.x;\n const dy = t.clientY - depart.y;\n if (Math.abs(dx) < 48 || Math.abs(dx) < Math.abs(dy)) return;\n // `goTo` et non `setCurrent` : il fait suivre le Swiper sous-jacent,\n // sans quoi refermer la visionneuse laisserait la planche paginée sur\n // l'ancienne page.\n goTo(dx < 0 ? current + 1 : current - 1);\n },\n [current, goTo],\n );\n\n const currentItem = items[current];\n const currentNum = currentItem?.number ?? current + 1;\n const counter = total ? `n°${currentNum} · ${Math.min(current + 1, total)}/${total}` : \"0 / 0\";\n const currentText = texteDe(currentItem);\n const currentImage = currentItem?.imageUrl ?? null;\n\n if (total === 0) {\n return (\n <div className=\"m3-reader-frame flex min-h-[200px] items-center justify-center rounded-xl p-8 text-[18px] text-[var(--reader-on-surface)]/50\">\n Aucune page à afficher pour le moment.\n </div>\n );\n }\n\n const legendBlock = (\n <div className=\"flex h-full min-h-0 flex-col p-4 bg-[var(--reader-surface)] border-t-2 lg:border-t-0 lg:border-l-2 border-[var(--reader-outline)]\">\n <div className=\"mb-2 shrink-0 flex items-center justify-between\">\n <span className=\"m3-reader-badge bg-[var(--reader-accent)] text-[var(--reader-on-accent)] px-2 py-0.5 text-[18px] font-black\">\n <span>PLANCHE {currentNum}</span>\n </span>\n <span className=\"text-[18px] font-bold tabular-nums text-[var(--reader-on-surface)]/60\">\n {current + 1} / {total}\n </span>\n </div>\n <div className=\"min-h-0 flex-1 overflow-y-auto overscroll-contain pr-1 m3-reader-scroll\">\n {currentText ? (\n (renderTranscription?.({\n text: currentText,\n page: currentNum,\n verified: currentItem?.verifiee ?? false,\n }) ?? currentText)\n ) : currentItem?.aDuTexte ? (\n // La planche PORTE une transcription : dire « aucune description »\n // pendant qu'elle arrive ferait croire à une lacune du corpus.\n <p className=\"text-[18px] italic text-[var(--reader-on-surface)]/50\">\n Chargement de la transcription…\n </p>\n ) : (\n <p className=\"text-[18px] italic text-[var(--reader-on-surface)]/50\">\n Aucune description pour cette page.\n </p>\n )}\n </div>\n </div>\n );\n\n return (\n <div\n ref={containerRef}\n className=\"m3-reader m3-reader-container m3-reader-scroll flex h-[84svh] min-h-[460px] w-full min-w-0 flex-col overflow-hidden rounded-xl bg-[var(--reader-surface)] [contain:inline-size] [&:fullscreen]:h-screen [&:fullscreen]:min-h-screen [&:fullscreen]:rounded-none\"\n data-databook-reader\n data-m3-component=\"DatabookReader\"\n data-testid=\"databook-reader\"\n role=\"region\"\n aria-label={title}\n >\n {/* Barre de contrôles Toriyama HUD */}\n <div className=\"m3-reader-hud z-10 flex flex-nowrap items-center gap-2 px-3 py-2.5 sm:flex-wrap\">\n <span className=\"mr-1 hidden min-w-0 max-w-[40ch] truncate font-sans text-[18px] font-extrabold uppercase tracking-wide text-[var(--reader-on-surface)] drop-shadow-[1px_1px_0px_#000] sm:block\">\n {title}\n </span>\n <span className=\"m3-reader-badge bg-[var(--reader-accent)] text-[var(--reader-on-accent)] px-2 py-0.5 text-[18px] font-black sm:ml-auto\">\n <span>{counter}</span>\n </span>\n\n <div className=\"ml-auto flex shrink-0 items-center gap-1.5 sm:ml-0 sm:gap-2\">\n <button\n type=\"button\"\n className={mode === \"vertical\" ? \"m3-reader-btn-active\" : \"m3-reader-btn\"}\n onClick={() => setMode(\"vertical\")}\n aria-pressed={mode === \"vertical\"}\n aria-label=\"Mode vertical (planche + texte)\"\n title=\"Mode vertical (planche + texte)\"\n >\n <ScrollText size={14} aria-hidden />\n <span className=\"hidden sm:inline\">Défilement</span>\n </button>\n <button\n type=\"button\"\n className={mode === \"paged\" ? \"m3-reader-btn-active\" : \"m3-reader-btn\"}\n onClick={() => setMode(\"paged\")}\n aria-pressed={mode === \"paged\"}\n aria-label=\"Mode paginé\"\n title=\"Mode paginé\"\n >\n <BookOpen size={14} aria-hidden />\n <span className=\"hidden sm:inline\">Paginé</span>\n </button>\n <button\n type=\"button\"\n className=\"m3-reader-btn\"\n onClick={() => openLightbox()}\n disabled={!currentImage}\n aria-label=\"Agrandir la planche\"\n title=\"Agrandir la planche\"\n >\n <ZoomIn size={14} aria-hidden />\n </button>\n {fullscreenDispo && (\n <button\n type=\"button\"\n className=\"m3-reader-btn\"\n onClick={toggleFullscreen}\n aria-label={isFullscreen ? \"Quitter le plein écran\" : \"Activer le plein écran\"}\n title={isFullscreen ? \"Quitter le plein écran\" : \"Plein écran\"}\n >\n {isFullscreen ? (\n <Minimize2 size={14} aria-hidden />\n ) : (\n <Maximize2 size={14} aria-hidden />\n )}\n </button>\n )}\n </div>\n </div>\n\n {/* Zone de lecture */}\n <div className=\"relative min-h-0 min-w-0 flex-1 overflow-hidden bg-[var(--reader-surface)]\">\n {mode === \"paged\" ? (\n /* Layout côte à côte ≥ lg : planche fixe à gauche, texte scrollable\n\t\t\t\t\t à droite — les légendes longues ne réduisent plus l'image. */\n <div className=\"flex h-full min-h-0 flex-col lg:flex-row\">\n {/* Zone planche */}\n <div className=\"relative min-h-[52%] min-w-0 flex-[1.6] lg:h-full lg:min-h-0 lg:flex-[1.15]\">\n <Swiper\n key=\"databook-ltr\"\n initialSlide={current}\n dir=\"ltr\"\n modules={[Zoom, Virtual, Mousewheel]}\n className=\"h-full w-full\"\n slidesPerView={1}\n mousewheel={{ forceToAxis: true }}\n zoom={{ maxRatio: 3, toggle: true }}\n virtual\n onSwiper={handleSwiper}\n onSlideChange={handleSlideChange}\n >\n {items.map((item, i) => (\n <SwiperSlide\n key={`db-page-${item.number}-${i}`}\n virtualIndex={i}\n zoom={Boolean(item.imageUrl)}\n className=\"flex items-center justify-center\"\n >\n <div className=\"swiper-zoom-container flex h-full w-full items-center justify-center bg-[var(--reader-surface)]\">\n {item.imageUrl ? (\n <button\n type=\"button\"\n className=\"group relative flex h-full w-full cursor-zoom-in items-center justify-center border-0 bg-transparent p-0\"\n onClick={() => openLightbox(i)}\n aria-label={`Agrandir la page ${item.number}`}\n >\n <img\n src={imageSrc(item.imageUrl, 1080)}\n srcSet={imageSrcSet(item.imageUrl, [828, 1080, 1920]) || undefined}\n sizes=\"(min-width: 1024px) 900px, 100vw\"\n alt={`${title} — page ${item.number}`}\n loading={Math.abs(i - current) <= 1 ? \"eager\" : \"lazy\"}\n draggable={false}\n className=\"max-h-full max-w-full object-contain transition-opacity group-hover:opacity-95\"\n />\n <span className=\"pointer-events-none absolute right-3 bottom-3 rounded-md border border-white/15 bg-[var(--reader-surface)]/60 px-2 py-1 text-[18px] font-bold uppercase tracking-wider text-[var(--reader-on-surface)]/70 opacity-0 transition-opacity group-hover:opacity-100\">\n Agrandir\n </span>\n </button>\n ) : (\n <div className=\"px-6 text-center text-[18px] text-[var(--reader-on-surface)]/50\">\n Page {item.number} — pas d'image\n </div>\n )}\n </div>\n </SwiperSlide>\n ))}\n </Swiper>\n\n <button\n type=\"button\"\n aria-label=\"Page précédente\"\n onClick={goPrevPage}\n disabled={current <= 0}\n className=\"absolute top-1/2 left-2 z-10 -translate-y-1/2 rounded-full border border-[var(--reader-outline)] bg-[var(--reader-surface)]/60 p-3 text-[var(--reader-on-surface)] transition-colors hover:border-[var(--reader-accent)] hover:text-[var(--reader-accent)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--reader-accent)] disabled:opacity-30\"\n >\n <ChevronLeft size={20} aria-hidden />\n </button>\n <button\n type=\"button\"\n aria-label=\"Page suivante\"\n onClick={goNextPage}\n disabled={current >= total - 1}\n className=\"absolute top-1/2 right-2 z-10 -translate-y-1/2 rounded-full border border-[var(--reader-outline)] bg-[var(--reader-surface)]/60 p-3 text-[var(--reader-on-surface)] transition-colors hover:border-[var(--reader-accent)] hover:text-[var(--reader-accent)] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--reader-accent)] disabled:opacity-30\"\n >\n <ChevronRight size={20} aria-hidden />\n </button>\n </div>\n\n {/* Légende — panneau scrollable indépendant (ne compresse plus l'image). */}\n <div className=\"min-h-0 max-h-[38%] shrink-0 border-t border-[var(--reader-outline)] bg-[var(--reader-surface)]/90 px-3 py-2.5 sm:max-h-[48%] sm:px-5 sm:py-4 lg:max-h-none lg:w-[min(420px,38%)] lg:overflow-hidden lg:border-l lg:border-t-0\">\n {legendBlock}\n </div>\n </div>\n ) : (\n <div\n ref={scrollParentRef}\n tabIndex={0}\n // `overscroll-y-auto` et non `contain` : arrivé en bout de lecteur,\n // la molette doit rendre la main au document. Avec `contain`, le\n // lecteur avalait le geste et la page restait bloquée — on ne\n // pouvait plus descendre vers la suite de la fiche sans sortir la\n // souris de la zone. Le risque d'origine (l'inertie mobile qui\n // éjectait le lecteur en pleine lecture) est traité autrement : les\n // planches s'accrochent au défilement, donc le mouvement s'arrête\n // franchement sur une page au lieu de filer.\n //\n // `snap-y snap-proximity` : le « vrai effet de page » — le\n // défilement s'arrête franchement sur une planche au lieu de\n // s'immobiliser entre deux images. `proximity` et non `mandatory` :\n // la liste est virtualisée (éléments montés/démontés en cours de\n // route, hauteurs mesurées après coup), et une accroche\n // obligatoire se bat avec ces remesures — elle produit des sauts au\n // moment où une planche entre dans la fenêtre.\n className=\"h-full w-full snap-y snap-proximity overflow-y-auto overflow-x-hidden overscroll-y-auto focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-[var(--reader-accent)]\"\n aria-label={`${title} — lecture verticale`}\n >\n <div\n style={{\n height: virtualizer.getTotalSize(),\n width: \"100%\",\n position: \"relative\",\n }}\n >\n {virtualizer.getVirtualItems().map((vi) => {\n const item = items[vi.index];\n if (!item) return null;\n return (\n <div\n key={`${baseId}-${vi.key}`}\n data-index={vi.index}\n ref={virtualizer.measureElement}\n style={virtualRowStyle(vi.start)}\n className=\"snap-start snap-always border-b border-white/5 px-2 py-4 sm:px-4 sm:py-6\"\n >\n <div className=\"group/planche mx-auto flex w-full max-w-3xl flex-col gap-3\">\n <span className=\"text-[18px] font-bold uppercase tracking-[0.2em] text-[var(--reader-accent)]/70\">\n Page {item.number}\n </span>\n {item.imageUrl ? (\n <button\n type=\"button\"\n className=\"group relative cursor-zoom-in border-0 bg-transparent p-0 text-left focus-visible:outline-none\"\n onClick={() => openLightbox(vi.index)}\n aria-label={`Ouvrir la page ${item.number} en plein écran`}\n >\n <img\n src={imageSrc(item.imageUrl, 1080)}\n srcSet={imageSrcSet(item.imageUrl, [828, 1080, 1920]) || undefined}\n sizes=\"(min-width: 768px) 768px, 100vw\"\n alt={`${title} — page ${item.number}`}\n // Trois planches de part et d'autre chargées d'avance :\n // avec une seule, tourner la page laissait un rectangle\n // vide le temps du téléchargement d'un scan de 1 à 2 Mo.\n loading={Math.abs(vi.index - current) <= 3 ? \"eager\" : \"lazy\"}\n // `high` sur la planche courante : le navigateur la sert\n // avant les voisines préchargées.\n fetchPriority={vi.index === current ? \"high\" : \"auto\"}\n decoding=\"async\"\n draggable={false}\n className=\"h-auto w-full rounded-sm bg-[var(--reader-surface)] object-contain shadow-[0_0_24px_rgba(0,0,0,0.45)] transition-[transform,box-shadow] duration-300 ease-out will-change-transform group-hover:scale-[1.015] group-hover:shadow-[0_0_60px_rgba(0,0,0,0.75)] group-focus-visible:scale-[1.015] motion-reduce:transform-none motion-reduce:transition-none\"\n />\n {/* Appel au plein écran, révélé au survol de la planche. */}\n <span className=\"pointer-events-none absolute right-3 bottom-3 inline-flex items-center gap-1.5 rounded-md border border-white/15 bg-[var(--reader-surface)]/70 px-2.5 py-1.5 text-[18px] font-bold uppercase tracking-wider text-[var(--reader-on-surface)]/80 opacity-0 backdrop-blur-sm transition-opacity duration-200 group-hover:opacity-100 group-focus-visible:opacity-100\">\n <ZoomIn size={12} aria-hidden />\n Plein écran\n </span>\n </button>\n ) : (\n <div className=\"flex min-h-[120px] items-center justify-center rounded border border-dashed border-white/10 bg-[var(--reader-surface)]/40 text-[18px] text-[var(--reader-on-surface)]/50\">\n Pas d'image pour cette page\n </div>\n )}\n {texteDe(item) ? (\n <div className=\"rounded-md border border-[var(--reader-outline)]/40 bg-[var(--reader-surface)]/50 px-4 py-3\">\n {renderTranscription?.({\n text: texteDe(item) as string,\n page: item.number,\n verified: item.verifiee,\n }) ?? texteDe(item)}\n </div>\n ) : null}\n </div>\n </div>\n );\n })}\n </div>\n </div>\n )}\n </div>\n\n {/* Bande de vignettes (databooks courts). */}\n {mode === \"paged\" && total > 0 && total <= SHOW_STRIP_MAX && (\n <div className=\"z-10 flex items-center gap-4 overflow-x-auto border-t border-[var(--reader-outline)] bg-[var(--reader-surface)]/90 px-4 py-3 scrollbar-thin\">\n <div className=\"mx-auto flex gap-2\">\n {items.map((item, idx) => (\n <button\n key={`thumb-${item.number}-${idx}`}\n type=\"button\"\n onClick={() => goTo(idx)}\n className={`relative h-16 w-12 flex-shrink-0 overflow-hidden rounded border-2 transition-all ${\n current === idx\n ? \"scale-105 border-[var(--reader-accent)] shadow-[0_0_10px_rgba(255,178,0,0.4)]\"\n : \"border-white/10 hover:border-white/30\"\n }`}\n title={`Page ${item.number}`}\n aria-label={`Aller à la page ${item.number}`}\n aria-current={current === idx ? \"true\" : undefined}\n >\n {item.imageUrl ? (\n <img\n // Vignette de 48×64 px : elle chargeait le scan source (jusqu'à\n // 5 Mio) pour un timbre-poste, jusqu'à 40 fois par fiche.\n src={imageSrc(item.imageUrl, 96)}\n alt=\"\"\n className=\"h-full w-full object-cover\"\n loading=\"lazy\"\n />\n ) : (\n <span className=\"flex h-full w-full items-center justify-center bg-zinc-900 text-[18px] text-[var(--reader-on-surface)]/50\">\n txt\n </span>\n )}\n <span className=\"absolute inset-x-0 bottom-0 bg-[var(--reader-surface)]/70 text-center font-mono text-[18px] font-bold text-[var(--reader-on-surface)]\">\n {item.number}\n </span>\n </button>\n ))}\n </div>\n </div>\n )}\n\n {/* Saut direct pour les longs databooks. */}\n {mode === \"paged\" && total > SHOW_STRIP_MAX && (\n <form\n className=\"z-10 flex items-center justify-center gap-3 border-t-2 border-[var(--reader-outline)] bg-[var(--reader-surface)] px-4 py-3\"\n onSubmit={(e) => {\n e.preventDefault();\n const field = e.currentTarget.elements.namedItem(\"goto\") as HTMLInputElement | null;\n const wanted = Number(field?.value);\n if (!Number.isFinite(wanted)) return;\n // Cherche d'abord par numéro éditorial, sinon par position 1-based.\n const byNumber = items.findIndex((it) => it.number === Math.trunc(wanted));\n if (byNumber >= 0) goTo(byNumber);\n else goTo(Math.trunc(wanted) - 1);\n }}\n >\n <label\n htmlFor={`${baseId}-goto`}\n className=\"text-[18px] font-black uppercase tracking-wider text-[var(--reader-on-surface)]\"\n >\n Aller à n°\n </label>\n <input\n id={`${baseId}-goto`}\n name=\"goto\"\n type=\"number\"\n key={currentNum}\n defaultValue={currentNum}\n className=\"w-20 rounded-md border-2 border-[var(--reader-outline)] bg-[var(--reader-surface)] px-2.5 py-1 text-center text-[18px] font-extrabold tabular-nums text-[var(--reader-accent)] shadow-[2px_2px_0px_#000] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[var(--reader-accent)]\"\n />\n <button type=\"submit\" className=\"m3-reader-btn-active !py-1 !px-3.5\">\n OK\n </button>\n </form>\n )}\n\n {/* Lightbox agrandissement planche */}\n {lightbox && currentImage && (\n <div\n ref={lightboxRef}\n // `tabIndex={-1}` : requis par `useFocusTrap`, qui donne le focus au\n // panneau à l'ouverture. Sans piège, la tabulation continuait DERRIÈRE\n // la visionneuse plein écran — le focus disparaissait pour l'utilisateur\n // clavier et pour le lecteur d'écran.\n tabIndex={-1}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={`${title} — page ${currentNum} agrandie`}\n className=\"fixed inset-0 z-[100] flex flex-col bg-[var(--reader-surface)]/95\"\n onClick={() => setLightbox(false)}\n >\n <div className=\"flex items-center gap-3 border-b-2 border-[var(--reader-outline)] bg-[var(--reader-surface)] px-4 py-3\">\n <span className=\"min-w-0 flex-1 truncate font-sans text-[18px] font-extrabold uppercase tracking-wide text-[var(--reader-on-surface)]\">\n {title}\n </span>\n <span className=\"m3-reader-badge bg-[var(--reader-accent)] text-[var(--reader-on-accent)] px-2 py-0.5 text-[18px] font-black\">\n <span>n°{currentNum}</span>\n </span>\n <button\n type=\"button\"\n className=\"m3-reader-btn ml-auto\"\n onClick={(e) => {\n e.stopPropagation();\n setLightbox(false);\n }}\n aria-label=\"Fermer\"\n >\n <X size={16} aria-hidden />\n </button>\n </div>\n <div\n className=\"relative flex min-h-0 flex-1 items-center justify-center p-2 sm:p-4\"\n onClick={(e) => e.stopPropagation()}\n onTouchStart={onTouchStart}\n onTouchEnd={onTouchEnd}\n >\n <img\n src={currentImage}\n alt={`${title} — page ${currentNum}`}\n className=\"max-h-full max-w-full object-contain\"\n draggable={false}\n />\n <button\n type=\"button\"\n aria-label=\"Page précédente\"\n onClick={() => goTo(current - 1)}\n disabled={current <= 0}\n className=\"absolute top-1/2 left-4 -translate-y-1/2 rounded-lg border-2 border-[var(--reader-outline)] bg-[var(--reader-surface)]/90 p-3 text-[var(--reader-accent)] shadow-[3px_3px_0px_#000] hover:bg-[var(--reader-accent)] hover:text-[var(--reader-on-accent)] transition-all disabled:opacity-30\"\n >\n <ChevronLeft size={22} aria-hidden />\n </button>\n <button\n type=\"button\"\n aria-label=\"Page suivante\"\n onClick={() => goTo(current + 1)}\n disabled={current >= total - 1}\n className=\"absolute top-1/2 right-4 -translate-y-1/2 rounded-lg border-2 border-[var(--reader-outline)] bg-[var(--reader-surface)]/90 p-3 text-[var(--reader-accent)] shadow-[3px_3px_0px_#000] hover:bg-[var(--reader-accent)] hover:text-[var(--reader-on-accent)] transition-all disabled:opacity-30\"\n >\n <ChevronRight size={22} aria-hidden />\n </button>\n </div>\n </div>\n )}\n </div>\n );\n}\n",
|
|
8
|
+
"// SPDX-License-Identifier: Apache-2.0\n\"use client\";\n\n/**\n * Piège de focus pour une boîte de dialogue modale (WCAG 2.4.3 / 2.1.2).\n *\n * Keeps Tab inside the panel, closes on Escape, and restores focus on exit.\n * A click on non-focusable text moves focus back into the panel on the next Tab.\n */\nimport { useEffect, useRef, type RefObject } from \"react\";\n\nconst FOCUSABLE =\n 'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex=\"-1\"])';\n\ninterface FocusLease {\n panel: HTMLElement;\n previousFocused: HTMLElement | null;\n previousTrap?: FocusLease;\n}\n\n// The document scroll lock is shared; only the last activated modal handles keys.\nconst activeTraps = new WeakMap<Document, { stack: FocusLease[]; previousOverflow: string }>();\n\n/** Native tab stops, excluding controls hidden or disabled through their ancestors. */\nfunction isTabbable(element: HTMLElement): boolean {\n if (element.tabIndex < 0 || element.matches(\":disabled, input[type='hidden']\")) return false;\n if (element.closest(\"[hidden], [inert]\")) return false;\n const view = element.ownerDocument.defaultView;\n const visibility = view?.getComputedStyle(element).visibility;\n if (visibility === \"hidden\" || visibility === \"collapse\") return false;\n for (let ancestor: HTMLElement | null = element; ancestor; ancestor = ancestor.parentElement) {\n if (view?.getComputedStyle(ancestor).display === \"none\") return false;\n }\n return true;\n}\n\nexport function useFocusTrap(\n panelRef: RefObject<HTMLElement | null>,\n active: boolean,\n onEscape?: () => void,\n): void {\n // `onEscape` est presque toujours une lambda écrite dans le JSX de l'appelant\n // (`() => setLightbox(false)`), donc une NOUVELLE fonction à chaque rendu. En\n // dépendance d'effet, elle rejouait tout le montage du piège à chaque rendu\n // de la modale : le nettoyage rendait le focus au déclencheur, l'effet\n // suivant le reprenait sur le panneau — l'utilisateur au clavier était donc\n // ramené en tête du panneau dès qu'un état changeait, et `prevFocused`\n // mémorisait le panneau au lieu du bouton d'ouverture, si bien que fermer ne\n // rendait plus le focus au bon endroit. On garde la fonction dans une ref :\n // l'effet ne dépend plus que de ce qui doit réellement le rejouer.\n const onEscapeRef = useRef(onEscape);\n onEscapeRef.current = onEscape;\n\n useEffect(() => {\n if (!active) return;\n const panel = panelRef.current;\n if (!panel) return;\n const doc = panel.ownerDocument;\n let state = activeTraps.get(doc);\n if (!state) {\n state = { stack: [], previousOverflow: doc.body.style.overflow };\n activeTraps.set(doc, state);\n }\n const lease: FocusLease = {\n panel,\n previousFocused: doc.activeElement as HTMLElement | null,\n previousTrap: state.stack.at(-1),\n };\n state.stack.push(lease);\n // Le panneau doit porter `tabIndex={-1}` pour pouvoir recevoir le focus.\n panelRef.current?.focus();\n\n const onKey = (e: KeyboardEvent) => {\n if (state.stack.at(-1) !== lease) return;\n if (e.key === \"Escape\") {\n onEscapeRef.current?.();\n return;\n }\n if (e.key !== \"Tab\") return;\n const currentPanel = panelRef.current;\n if (!currentPanel) return;\n const items = [...currentPanel.querySelectorAll<HTMLElement>(FOCUSABLE)].filter(isTabbable);\n if (items.length === 0) {\n e.preventDefault();\n currentPanel.focus();\n return;\n }\n const first = items[0]!;\n const last = items[items.length - 1]!;\n const current = doc.activeElement as HTMLElement | null;\n if (!current || !items.includes(current)) {\n e.preventDefault();\n (e.shiftKey ? last : first).focus();\n } else if (e.shiftKey && current === first) {\n e.preventDefault();\n last.focus();\n } else if (!e.shiftKey && current === last) {\n e.preventDefault();\n first.focus();\n }\n };\n\n doc.addEventListener(\"keydown\", onKey);\n doc.body.style.overflow = \"hidden\";\n\n return () => {\n doc.removeEventListener(\"keydown\", onKey);\n const wasTop = state.stack.at(-1) === lease;\n state.stack.splice(state.stack.indexOf(lease), 1);\n // Preserve the opener chain even when a lower modal is removed first.\n for (const remaining of state.stack) {\n if (remaining.previousTrap === lease) {\n remaining.previousTrap = lease.previousTrap;\n remaining.previousFocused = lease.previousFocused;\n }\n }\n const top = state.stack.at(-1);\n if (!top) {\n doc.body.style.overflow = state.previousOverflow;\n activeTraps.delete(doc);\n }\n if (wasTop) {\n const previous = lease.previousFocused;\n if (previous?.isConnected && (!top || top.panel.contains(previous))) previous.focus();\n else top?.panel.focus();\n }\n };\n }, [panelRef, active]);\n}\n",
|
|
9
|
+
"\"use client\";\n\nimport { type ReactNode, useEffect, useId, useMemo, useState } from \"react\";\n\nexport interface ReaderCollectionChapter {\n id: string | number;\n label: string;\n pageCount: number;\n}\n\nexport interface ReaderCollectionEdition {\n id: string;\n label: string;\n chapters: ReaderCollectionChapter[];\n}\n\nexport interface CollectionReaderProps {\n title: string;\n editions: ReaderCollectionEdition[];\n initialEdition?: string;\n initialChapter?: string | number;\n emptyLabel: string;\n contentsLabel: string;\n previousLabel: string;\n nextLabel: string;\n onSelectionChange?: (edition: ReaderCollectionEdition, chapter: ReaderCollectionChapter) => void;\n renderChapter: (edition: ReaderCollectionEdition, chapter: ReaderCollectionChapter, index: number) => ReactNode;\n}\n\n/** Edition and chapter navigation shared by manga volumes and illustrated collections. */\nexport function CollectionReader({ title, editions, initialEdition, initialChapter, emptyLabel, contentsLabel, previousLabel, nextLabel, onSelectionChange, renderChapter }: CollectionReaderProps) {\n const contentsId = useId();\n const available = useMemo(() => editions.map((edition) => ({ ...edition, chapters: edition.chapters.filter((chapter) => chapter.pageCount > 0) })).filter((edition) => edition.chapters.length > 0), [editions]);\n const [editionId, setEditionId] = useState(() => available.find((edition) => edition.id === initialEdition)?.id ?? available[0]?.id ?? \"\");\n const edition = available.find((item) => item.id === editionId) ?? available[0];\n const [chapterIndex, setChapterIndex] = useState(() => Math.max(0, edition?.chapters.findIndex((chapter) => String(chapter.id) === String(initialChapter)) ?? 0));\n const [contentsOpen, setContentsOpen] = useState(false);\n const safeIndex = Math.min(chapterIndex, Math.max(0, (edition?.chapters.length ?? 0) - 1));\n const chapter = edition?.chapters[safeIndex];\n\n useEffect(() => {\n if (edition && chapter) onSelectionChange?.(edition, chapter);\n }, [edition, chapter, onSelectionChange]);\n\n if (!edition || !chapter) {\n return <p className=\"m3-reader-collection-empty\" data-m3-component=\"CollectionReader\" data-testid=\"collection-reader-empty\">{emptyLabel}</p>;\n }\n\n return (\n <section className=\"m3-reader m3-reader-collection\" aria-label={title} data-m3-component=\"CollectionReader\" data-testid=\"collection-reader\">\n <div className=\"m3-reader-collection-toolbar\">\n <div className=\"m3-reader-collection-editions\" role=\"group\" aria-label={title}>\n {available.map((item) => (\n <button key={item.id} type=\"button\" className={item.id === edition.id ? \"m3-reader-btn-active\" : \"m3-reader-btn\"}\n aria-pressed={item.id === edition.id} onClick={() => { setEditionId(item.id); setChapterIndex(0); setContentsOpen(false); }}>\n {item.label}\n </button>\n ))}\n </div>\n <button type=\"button\" className=\"m3-reader-btn\" aria-expanded={contentsOpen} aria-controls={contentsOpen ? contentsId : undefined}\n onClick={() => setContentsOpen((value) => !value)}>{contentsLabel} ({edition.chapters.length})</button>\n </div>\n {contentsOpen && (\n <div id={contentsId} className=\"m3-reader-collection-contents\" role=\"group\" aria-label={contentsLabel}>\n {edition.chapters.map((item, index) => (\n <button key={item.id} type=\"button\" className={index === safeIndex ? \"m3-reader-btn-active\" : \"m3-reader-btn\"}\n aria-current={index === safeIndex ? \"page\" : undefined} disabled={item.pageCount === 0}\n onClick={() => { setChapterIndex(index); setContentsOpen(false); }}>\n {item.label} · {item.pageCount}\n </button>\n ))}\n </div>\n )}\n {renderChapter(edition, chapter, safeIndex)}\n <nav className=\"m3-reader-collection-navigation\" aria-label={contentsLabel}>\n <button type=\"button\" className=\"m3-reader-btn\" disabled={safeIndex === 0} onClick={() => setChapterIndex(safeIndex - 1)}>{previousLabel}</button>\n <span aria-live=\"polite\">{chapter.label} ({safeIndex + 1}/{edition.chapters.length})</span>\n <button type=\"button\" className=\"m3-reader-btn\" disabled={safeIndex >= edition.chapters.length - 1} onClick={() => setChapterIndex(safeIndex + 1)}>{nextLabel}</button>\n </nav>\n </section>\n );\n}\n"
|
|
10
|
+
],
|
|
11
|
+
"mappings": ";AAcA;AAAA,iBAGE;AAAA,eACA;AAAA;AAAA;AAAA,YAGA;AAAA,cACA;AAAA;AAGF;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWA;AACA;AAGA;AAEA;;;ACnCA;AAAA;AAAA;AAAA;AAAA;AAAA;AAPA;AAwBO,IAAM,iBAAiB;AAMvB,SAAS,aAAa,CAAC,cAA6C;AAAA,EACzE,OAAO,cAAc,mBAAmB,SAAS,KAAK;AAAA,EACtD,OAAO,iBAAiB,sBAAsB,SAAuB,IAAI;AAAA,EACzE,MAAM,UAAU,OAAO,KAAK;AAAA,EAC5B,MAAM,YAAY,OAAO,CAAC;AAAA,EAE1B,MAAM,mBAAmB,YAAY,YAA8B;AAAA,IACjE,MAAM,KAAK,aAAa;AAAA,IACxB,IAAI,CAAC;AAAA,MAAI,OAAO;AAAA,IAChB,MAAM,MAAM,GAAG;AAAA,IACf,MAAM,UAAU,EAAE,UAAU;AAAA,IAC5B,IAAI,QAAQ;AAAA,MAAS,mBAAmB,IAAI;AAAA,IAC5C,IAAI;AAAA,MACF,IAAI,IAAI,sBAAsB,IAAI;AAAA,QAChC,IAAI,OAAO,IAAI,mBAAmB;AAAA,UAChC,MAAM,IAAI,MAAM,gCAAgC;AAAA,QAClD,MAAM,IAAI,eAAe;AAAA,MAC3B,EAAO;AAAA,QAEL,IAAI,OAAO,GAAG,sBAAsB;AAAA,UAClC,MAAM,IAAI,MAAM,2BAA2B;AAAA,QAC7C,MAAM,GAAG,kBAAkB;AAAA;AAAA,MAE7B,IAAI,QAAQ,WAAW,YAAY,UAAU,SAAS;AAAA,QACpD,gBACE,QAAQ,aAAa,WAAW,IAAI,sBAAsB,aAAa,OAAO,CAChF;AAAA,MACF;AAAA,MACA,OAAO;AAAA,MACP,OAAO,QAAQ;AAAA,MACf,IAAI,QAAQ,WAAW,YAAY,UAAU,SAAS;AAAA,QACpD,mBAAmB,kBAAkB,QAAQ,SAAS,IAAI,MAAM,OAAO,MAAM,CAAC,CAAC;AAAA,MACjF;AAAA,MACA,OAAO;AAAA;AAAA,KAER,CAAC,YAAY,CAAC;AAAA,EAEjB,UAAU,MAAM;AAAA,IACd,MAAM,MAAM,aAAa,SAAS;AAAA,IAClC,IAAI,CAAC;AAAA,MAAK;AAAA,IACV,QAAQ,UAAU;AAAA,IAClB,MAAM,WAAW,MACf,gBACE,QAAQ,aAAa,WAAW,IAAI,sBAAsB,aAAa,OAAO,CAChF;AAAA,IACF,SAAS;AAAA,IACT,IAAI,iBAAiB,oBAAoB,QAAQ;AAAA,IACjD,OAAO,MAAM;AAAA,MACX,QAAQ,UAAU;AAAA,MAClB,UAAU;AAAA,MACV,IAAI,oBAAoB,oBAAoB,QAAQ;AAAA;AAAA,KAErD,CAAC,YAAY,CAAC;AAAA,EAEjB,OAAO,EAAE,cAAc,iBAAiB,iBAAiB;AAAA;AAIpD,SAAS,aAAa,CAAC,YAAqC;AAAA,EACjE,MAAM,YAAY,OAA2B,IAAI;AAAA,EAEjD,MAAM,eAAe,YACnB,CAAC,OAAoB;AAAA,IACnB,UAAU,UAAU;AAAA,IACpB,WAAW,GAAG,aAAa,GAAG,eAAe,CAAC;AAAA,KAEhD,CAAC,UAAU,CACb;AAAA,EAEA,MAAM,oBAAoB,YACxB,CAAC,OAAoB,WAAW,GAAG,aAAa,GAAG,eAAe,CAAC,GACnE,CAAC,UAAU,CACb;AAAA,EAEA,OAAO,EAAE,WAAW,cAAc,kBAAkB;AAAA;AAI/C,SAAS,qBAAqB,CACnC,MACA,iBACA,aACA,YACA;AAAA,EACA,UAAU,MAAM;AAAA,IACd,IAAI,SAAS;AAAA,MAAY;AAAA,IACzB,MAAM,KAAK,gBAAgB;AAAA,IAC3B,IAAI,CAAC;AAAA,MAAI;AAAA,IACT,MAAM,WAAW,MAAM;AAAA,MACrB,MAAM,MAAM,GAAG,YAAY,GAAG,eAAe;AAAA,MAC7C,MAAM,OAAO,YAAY,gBAAgB,EAAE,KAAK,CAAC,OAAO,GAAG,SAAS,OAAO,GAAG,OAAO,GAAG;AAAA,MACxF,IAAI;AAAA,QAAM,WAAW,KAAK,KAAK;AAAA;AAAA,IAEjC,GAAG,iBAAiB,UAAU,UAAU,EAAE,SAAS,KAAK,CAAC;AAAA,IACzD,SAAS;AAAA,IACT,OAAO,MAAM,GAAG,oBAAoB,UAAU,QAAQ;AAAA,KACrD,CAAC,MAAM,iBAAiB,aAAa,UAAU,CAAC;AAAA;AAI9C,IAAM,kBAAkB,CAAC,WAAkC;AAAA,EAChE,UAAU;AAAA,EACV,KAAK;AAAA,EACL,MAAM;AAAA,EACN,OAAO;AAAA,EACP,WAAW,cAAc;AAC3B;;;;ADzIA;AA2EA,IAAM,gBAAgB;AAGtB,SAAS,gBAAgB,GAAY;AAAA,EACnC,OAAO,SAAS,cAAc,UAAS,KAAK;AAAA,EAC5C,WAAU,MAAM;AAAA,IACd,MAAM,MAAM,OAAO,WAAW,eAAe,kBAAkB;AAAA,IAC/D,MAAM,OAAO,MAAM,WAAW,IAAI,OAAO;AAAA,IACzC,KAAK;AAAA,IACL,IAAI,iBAAiB,UAAU,IAAI;AAAA,IACnC,OAAO,MAAM,IAAI,oBAAoB,UAAU,IAAI;AAAA,KAClD,CAAC,CAAC;AAAA,EACL,OAAO;AAAA;AAGF,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,YAAY;AAAA,EACZ,cAAc;AAAA,EACd;AAAA,EACA;AAAA,EACA,kBAAkB,CAAC,SAAS;AAAA,EAC5B;AAAA,EACA;AAAA,GACiC;AAAA,EACjC,MAAM,eAAe,QAAuB,IAAI;AAAA,EAEhD,OAAO,MAAM,WAAW,UAAmB,OAAO;AAAA,EAClD,OAAO,KAAK,UAAU,UAAwB,SAAS;AAAA,EACvD,OAAO,QAAQ,aAAa,UAAS,KAAK;AAAA,EAC1C,OAAO,SAAS,cAAc,UAAS,MACrC,KAAK,IAAI,GAAG,KAAK,IAAI,MAAM,SAAS,GAAG,cAAc,CAAC,CAAC,CACzD;AAAA,EACA,QAAQ,cAAc,qBAAqB,cAAc,YAAY;AAAA,EACrE,QAAQ,WAAW,cAAc,sBAAsB,cAAc,UAAU;AAAA,EAE/E,MAAM,UAAU,iBAAiB;AAAA,EAEjC,MAAM,YAAY,UAAU,WAAW,SAAS;AAAA,EAEhD,MAAM,iBAAiB,QAAiC,IAAI;AAAA,EAG5D,WAAU,MAAM;AAAA,IACd,IAAI,SAAS,WAAW,eAAe,SAAS;AAAA,MAC9C,eAAe,QAAQ,eAAe;AAAA,QACpC,UAAU;AAAA,QACV,OAAO;AAAA,QACP,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AAAA,KACC,CAAC,SAAS,IAAI,CAAC;AAAA,EAGlB,MAAM,OAAO,QAAQ,MAAM,MAAM,IAAI,eAAe,GAAG,CAAC,OAAO,eAAe,CAAC;AAAA,EAC/E,MAAM,QAAQ,KAAK;AAAA,EACnB,MAAM,SAAS,MAAM;AAAA,EAGrB,WAAU,MAAM;AAAA,IACd,IAAI,KAAK,WAAW;AAAA,MAAG;AAAA,IAEvB,MAAM,UAAU,CAAC,SAAS,UAAU,GAAG,UAAU,GAAG,UAAU,GAAG,UAAU,CAAC;AAAA,IAC5E,WAAW,OAAO,SAAS;AAAA,MACzB,IAAI,OAAO,KAAK,MAAM,KAAK,QAAQ;AAAA,QACjC,MAAM,MAAM,IAAI;AAAA,QAChB,IAAI,MAAM,KAAK;AAAA,MACjB;AAAA,IACF;AAAA,IAGA,IAAI,aAAa,UAAU,SAAS,GAAG;AAAA,MACrC,UAAU,MAAM,GAAG,CAAC,EAAE,QAAQ,CAAC,SAAS;AAAA,QACtC,MAAM,MAAM,IAAI;AAAA,QAChB,IAAI,MAAM,gBAAgB,IAAI;AAAA,OAC/B;AAAA,IACH;AAAA,IAGA,IAAI,aAAa,UAAU,SAAS,GAAG;AAAA,MACrC,UAAU,MAAM,EAAE,EAAE,QAAQ,CAAC,SAAS;AAAA,QACpC,MAAM,MAAM,IAAI;AAAA,QAChB,IAAI,MAAM,gBAAgB,IAAI;AAAA,OAC/B;AAAA,IACH;AAAA,KACC,CAAC,SAAS,MAAM,WAAW,WAAW,eAAe,CAAC;AAAA,EAGzD,MAAM,mBAAmB,aACvB,CAAC,MAAqD;AAAA,IACpD,MAAM,SAAS,EAAE;AAAA,IACjB,IAAI,OAAO,QAAQ;AAAA,MAAiB;AAAA,IACpC,OAAO,QAAQ,kBAAkB;AAAA,IACjC,IAAI;AAAA,MACF,MAAM,SAAS,mBAAmB,OAAO,GAAG;AAAA,MAC5C,IAAI;AAAA,QAAQ,OAAO,MAAM;AAAA,MACzB,MAAM;AAAA,KAIV,CAAC,gBAAgB,CACnB;AAAA,EAIA,MAAM,kBAAkB,QAAuB,IAAI;AAAA,EACnD,MAAM,cAAc,eAAe;AAAA,IACjC,OAAO;AAAA,IACP,kBAAkB,MAAM,gBAAgB;AAAA,IAExC,cAAc,MAAO,OAAO,WAAW,cAAc,MAAM,OAAO;AAAA,IAClE,UAAU;AAAA,EACZ,CAAC;AAAA,EAGD,sBAAsB,MAAM,iBAAiB,aAAa,UAAU;AAAA,EAEpE,WAAU,MAAM;AAAA,IACd,eAAe,UAAU,CAAC;AAAA,KACzB,CAAC,SAAS,YAAY,CAAC;AAAA,EAC1B,MAAM,aAAa,cAAc,UAAU,CAAC,KAAK;AAAA,EAIjD,MAAM,aAAa,aAAY,MAAM;AAAA,IACnC,IAAI,SAAS;AAAA,MAAS,UAAU,SAAS,UAAU;AAAA,IAC9C;AAAA,kBAAY,cAAc,KAAK,IAAI,GAAG,UAAU,CAAC,GAAG,EAAE,OAAO,QAAQ,CAAC;AAAA,KAC1E,CAAC,MAAM,SAAS,WAAW,CAAC;AAAA,EAE/B,MAAM,aAAa,aAAY,MAAM;AAAA,IACnC,IAAI,SAAS;AAAA,MAAS,UAAU,SAAS,UAAU;AAAA,IAEjD;AAAA,kBAAY,cAAc,KAAK,IAAI,QAAQ,GAAG,UAAU,CAAC,GAAG;AAAA,QAC1D,OAAO;AAAA,MACT,CAAC;AAAA,KACF,CAAC,MAAM,SAAS,OAAO,WAAW,CAAC;AAAA,EAEtC,MAAM,UAAU,QAAQ,GAAG,KAAK,IAAI,UAAU,GAAG,KAAK,OAAO,UAAU;AAAA,EAEvE,uBACE,KAiVE,OAjVF;AAAA,IACE,KAAK;AAAA,IACL,WAAU;AAAA,IACV,qBAAiB;AAAA,IACjB,qBAAkB;AAAA,IAClB,eAAY;AAAA,IACZ,MAAK;AAAA,IACL,cAAY;AAAA,IAPd,UAiVE;AAAA,sBAvUA,KAkJE,OAlJF;AAAA,QAAK,WAAU;AAAA,QAAf,UAkJE;AAAA,0BAjJA,KAmBE,OAnBF;AAAA,YAAK,WAAU;AAAA,YAAf,UAmBE;AAAA,8BAlBA,IAEE,QAFF;AAAA,gBAAM,WAAU;AAAA,gBAAhB,UACG;AAAA,eACD;AAAA,8BACF,IAEE,QAFF;AAAA,gBAAM,WAAU;AAAA,gBAAhB,0BACE,IAAiB,QAAjB;AAAA,4BAAO;AAAA,iBAAU;AAAA,eACjB;AAAA,cAID,8BACC,IAME,KANF;AAAA,gBACE,MAAM;AAAA,gBACN,WAAU;AAAA,gBACV,OAAM;AAAA,gBAHR;AAAA,eAME;AAAA;AAAA,WAEJ;AAAA,0BAEF,KA2HE,OA3HF;AAAA,YAAK,WAAU;AAAA,YAAf,UA2HE;AAAA,8BAzHA,KASE,UATF;AAAA,gBACE,MAAK;AAAA,gBACL,WAAW,SAAS,UAAU,yBAAyB;AAAA,gBACvD,SAAS,MAAM,QAAQ,OAAO;AAAA,gBAC9B,gBAAc,SAAS;AAAA,gBACvB,cAAW;AAAA,gBACX,OAAM;AAAA,gBANR,UASE;AAAA,kCADA,IAAC,UAAD;AAAA,oBAAU,MAAM;AAAA,oBAAI,eAAW;AAAA,mBAAC;AAAA,kBARlC;AAAA;AAAA,eASE;AAAA,8BACF,KASE,UATF;AAAA,gBACE,MAAK;AAAA,gBACL,WAAW,SAAS,aAAa,yBAAyB;AAAA,gBAC1D,SAAS,MAAM,QAAQ,UAAU;AAAA,gBACjC,gBAAc,SAAS;AAAA,gBACvB,cAAW;AAAA,gBACX,OAAM;AAAA,gBANR,UASE;AAAA,kCADA,IAAC,YAAD;AAAA,oBAAY,MAAM;AAAA,oBAAI,eAAW;AAAA,mBAAC;AAAA,kBARpC;AAAA;AAAA,eASE;AAAA,8BAGF,KAaE,UAbF;AAAA,gBACE,MAAK;AAAA,gBACL,WAAW,QAAQ,QAAQ,yBAAyB;AAAA,gBACpD,SAAS,MAAM,OAAO,CAAC,MAAO,MAAM,QAAQ,QAAQ,KAAM;AAAA,gBAC1D,cACE,QAAQ,QACJ,+CACA;AAAA,gBAEN,OAAO,QAAQ,QAAQ,+BAA+B;AAAA,gBATxD,UAaE;AAAA,kCAFA,IAAC,gBAAD;AAAA,oBAAgB,MAAM;AAAA,oBAAI,eAAW;AAAA,mBAAC;AAAA,kBACrC,QAAQ,QAAQ,QAAQ;AAAA;AAAA,eACzB;AAAA,8BAGF,KAWE,UAXF;AAAA,gBACE,MAAK;AAAA,gBACL,WAAW,YAAY,yBAAyB;AAAA,gBAChD,SAAS,MAAM,UAAU,CAAC,MAAM,CAAC,CAAC;AAAA,gBAClC,UAAU,SAAS,WAAW,CAAC;AAAA,gBAC/B,gBAAc;AAAA,gBACd,cAAW;AAAA,gBACX,OAAM;AAAA,gBAPR,UAWE;AAAA,kBAFC,4BAAY,IAAC,UAAD;AAAA,oBAAU,MAAM;AAAA,oBAAI,eAAW;AAAA,mBAAC,oBAAK,IAAC,QAAD;AAAA,oBAAQ,MAAM;AAAA,oBAAI,eAAW;AAAA,mBAAC;AAAA,kBATlF;AAAA;AAAA,eAWE;AAAA,8BAGF,IAYE,UAZF;AAAA,gBACE,MAAK;AAAA,gBACL,WAAU;AAAA,gBACV,SAAS;AAAA,gBACT,cAAY,eAAe,2BAA2B;AAAA,gBACtD,OAAO,eAAe,2BAA2B;AAAA,gBALnD,UAOG,+BACC,IAAC,WAAD;AAAA,kBAAW,MAAM;AAAA,kBAAI,eAAW;AAAA,iBAAC,oBAEjC,IAAC,WAAD;AAAA,kBAAW,MAAM;AAAA,kBAAI,eAAW;AAAA,iBAAC;AAAA,eAEnC;AAAA,cAGD,WACC,oBACE,kBAAkB;AAAA,gBAChB,MAAM;AAAA,gBACN,0BACE;AAAA,4BAEE;AAAA,oCADA,IAAC,aAAD;AAAA,sBAAa,MAAM;AAAA,sBAAI,eAAW;AAAA,qBAAC;AAAA,oBADrC;AAAA;AAAA,iBAEE;AAAA,gBAEJ,WAAW;AAAA,gBACX,WAAW;AAAA,cACb,CAAC,oBAED,KAOE,KAPF;AAAA,gBACE,MAAM;AAAA,gBACN,WAAU;AAAA,gBACV,cAAW;AAAA,gBACX,OAAM;AAAA,gBAJR,UAOE;AAAA,kCADA,IAAC,aAAD;AAAA,oBAAa,MAAM;AAAA,oBAAI,eAAW;AAAA,mBAAC;AAAA,kBANrC;AAAA;AAAA,eAOE,oBAGJ,KAEE,QAFF;AAAA,gBAAM,WAAU;AAAA,gBAAhB,UAEE;AAAA,kCADA,IAAC,aAAD;AAAA,oBAAa,MAAM;AAAA,oBAAI,eAAW;AAAA,mBAAC;AAAA,kBADrC;AAAA;AAAA,eAEE;AAAA,cAEH,WACC,oBACE,kBAAkB;AAAA,gBAChB,MAAM;AAAA,gBACN,0BACE;AAAA,4BAEE;AAAA,oBAFF;AAAA,oCACQ,IAAC,cAAD;AAAA,sBAAc,MAAM;AAAA,sBAAI,eAAW;AAAA,qBAAC;AAAA;AAAA,iBAC1C;AAAA,gBAEJ,WAAW;AAAA,gBACX,WAAW;AAAA,cACb,CAAC,oBAED,KAOE,KAPF;AAAA,gBACE,MAAM;AAAA,gBACN,WAAU;AAAA,gBACV,cAAW;AAAA,gBACX,OAAM;AAAA,gBAJR,UAOE;AAAA,kBAPF;AAAA,kCAMQ,IAAC,cAAD;AAAA,oBAAc,MAAM;AAAA,oBAAI,eAAW;AAAA,mBAAC;AAAA;AAAA,eAC1C,oBAGJ,KAEE,QAFF;AAAA,gBAAM,WAAU;AAAA,gBAAhB,UAEE;AAAA,kBAFF;AAAA,kCACQ,IAAC,cAAD;AAAA,oBAAc,MAAM;AAAA,oBAAI,eAAW;AAAA,mBAAC;AAAA;AAAA,eAC1C;AAAA;AAAA,WAEJ;AAAA;AAAA,OACF;AAAA,sBAGF,IAuGE,OAvGF;AAAA,QAAK,WAAU;AAAA,QAAf,UACG,UAAU,oBACT,IAEE,OAFF;AAAA,UAAK,WAAU;AAAA,UAAf;AAAA,SAEE,IACA,SAAS,0BACX;AAAA,oBA2DE;AAAA,4BA1DA,IA+BE,QA/BF;AAAA,cAIE,cAAc;AAAA,cACd;AAAA,cACA,SAAS,CAAC,UAAU,MAAM,UAAU;AAAA,cACpC,WAAU;AAAA,cACV,eAAe,YAAY,IAAI;AAAA,cAC/B,cAAc,YAAY,IAAI;AAAA,cAC9B,UAAU,EAAE,SAAS,KAAK;AAAA,cAC1B,YAAY,EAAE,aAAa,KAAK;AAAA,cAChC,MAAM,EAAE,UAAU,GAAG,QAAQ,KAAK;AAAA,cAClC,UAAU;AAAA,cACV,eAAe;AAAA,cAdjB,UAgBG,KAAK,IAAI,CAAC,KAAK,sBACd,IAYE,aAZF;AAAA,gBAA2B,MAAI;AAAA,gBAAC,WAAU;AAAA,gBAA1C,0BAEE,IASE,OATF;AAAA,kBAAK,WAAU;AAAA,kBAAf,0BACE,IAAC,OAAD;AAAA,oBACE,KAAK;AAAA,oBACL,KAAK,GAAG,gBAAgB,IAAI;AAAA,oBAC5B,SAAS,KAAK,IAAI,IAAI,OAAO,KAAK,IAAI,UAAU;AAAA,oBAChD,WAAW;AAAA,oBACX,WAAU;AAAA,oBACV,SAAS;AAAA,mBACX;AAAA,iBACA;AAAA,iBAXc,MAAM,CAYtB,CACH;AAAA,eA7BI,GAAG,OAAO,YAAY,WAAW,UA8BtC;AAAA,4BAGF,IAWE,UAXF;AAAA,cACE,MAAK;AAAA,cACL,cAAW;AAAA,cACX,SAAS;AAAA,cACT,WAAU;AAAA,cAJZ,UAMG,QAAQ,wBACP,IAAC,cAAD;AAAA,gBAAc,MAAM;AAAA,gBAAI,eAAW;AAAA,eAAC,oBAEpC,IAAC,aAAD;AAAA,gBAAa,MAAM;AAAA,gBAAI,eAAW;AAAA,eAAC;AAAA,aAErC;AAAA,4BACF,IAWE,UAXF;AAAA,cACE,MAAK;AAAA,cACL,cAAW;AAAA,cACX,SAAS;AAAA,cACT,WAAU;AAAA,cAJZ,UAMG,QAAQ,wBACP,IAAC,aAAD;AAAA,gBAAa,MAAM;AAAA,gBAAI,eAAW;AAAA,eAAC,oBAEnC,IAAC,cAAD;AAAA,gBAAc,MAAM;AAAA,gBAAI,eAAW;AAAA,eAAC;AAAA,aAEtC;AAAA;AAAA,SACF,oBAEF,IAkCE,OAlCF;AAAA,UACE,KAAK;AAAA,UAGL,UAAU;AAAA,UACV,WAAU;AAAA,UACV,cAAY,GAAG;AAAA,UANjB,0BAQE,IAyBE,OAzBF;AAAA,YACE,OAAO;AAAA,cACL,QAAQ,YAAY,aAAa;AAAA,cACjC,OAAO;AAAA,cACP,UAAU;AAAA,YACZ;AAAA,YALF,UAOG,YAAY,gBAAgB,EAAE,IAAI,CAAC,uBAClC,IAeE,OAfF;AAAA,cAEE,cAAY,GAAG;AAAA,cACf,KAAK,YAAY;AAAA,cACjB,OAAO,gBAAgB,GAAG,KAAK;AAAA,cAC/B,WAAU;AAAA,cALZ,0BAOE,IAAC,OAAD;AAAA,gBACE,KAAK,KAAK,GAAG;AAAA,gBACb,KAAK,GAAG,gBAAgB,GAAG,QAAQ;AAAA,gBACnC,SAAS,KAAK,IAAI,GAAG,QAAQ,OAAO,KAAK,IAAI,UAAU;AAAA,gBACvD,WAAW;AAAA,gBACX,WAAU;AAAA,gBACV,SAAS;AAAA,eACX;AAAA,eAbK,GAAG,UAAU,GAAG,KAcrB,CACH;AAAA,WACD;AAAA,SACF;AAAA,OAEJ;AAAA,MAGD,SAAS,WAAW,QAAQ,KAAK,SAAS,kCACzC,IA6BE,OA7BF;AAAA,QAAK,WAAU;AAAA,QAAf,0BACE,IA2BE,OA3BF;AAAA,UAAK,WAAU;AAAA,UAAf,UACG,KAAK,IAAI,CAAC,KAAK,wBACd,KAuBE,UAvBF;AAAA,YAEE,KAAK,YAAY,MAAM,iBAAiB;AAAA,YACxC,MAAK;AAAA,YACL,SAAS,MAAM;AAAA,cACb,WAAW,GAAG;AAAA,cACd,UAAU,SAAS,QAAQ,GAAG;AAAA;AAAA,YAEhC,WAAW,kFACT,YAAY,MACR,mFACA;AAAA,YAXR,UAuBE;AAAA,8BATA,IAAC,OAAD;AAAA,gBACE,KAAK;AAAA,gBACL,KAAK,QAAQ,MAAM;AAAA,gBACnB,WAAU;AAAA,gBACV,SAAQ;AAAA,eACV;AAAA,8BACA,IAEE,QAFF;AAAA,gBAAM,WAAU;AAAA,gBAAhB,UACG,MAAM;AAAA,eACP;AAAA;AAAA,aArBG,MAAM,YAAY,GAsBvB,CACH;AAAA,SACD;AAAA,OACF;AAAA,MAKH,SAAS,WAAW,QAAQ,kCAC3B,KAkCE,QAlCF;AAAA,QACE,WAAU;AAAA,QACV,UAAU,CAAC,MAAM;AAAA,UACf,EAAE,eAAe;AAAA,UACjB,MAAM,QAAQ,EAAE,cAAc,SAAS,UAAU,MAAM;AAAA,UACvD,MAAM,MAAM,OAAO,OAAO,KAAK;AAAA,UAC/B,IAAI,CAAC,OAAO,SAAS,GAAG;AAAA,YAAG;AAAA,UAC3B,MAAM,IAAI,KAAK,IAAI,KAAK,IAAI,MAAM,GAAG,CAAC,GAAG,QAAQ,CAAC;AAAA,UAClD,WAAW,CAAC;AAAA,UACZ,UAAU,SAAS,QAAQ,CAAC;AAAA;AAAA,QAThC,UAkCE;AAAA,0BAtBA,IAKE,SALF;AAAA,YACE,SAAS,GAAG;AAAA,YACZ,WAAU;AAAA,YAFZ;AAAA,WAKE;AAAA,0BACF,IAAC,SAAD;AAAA,YACE,IAAI,GAAG;AAAA,YACP,MAAK;AAAA,YACL,MAAK;AAAA,YACL,KAAK;AAAA,YACL,KAAK;AAAA,YAEL,cAAc,UAAU;AAAA,YACxB,WAAU;AAAA,aAFL,OAGP;AAAA,0BACA,KAEE,QAFF;AAAA,YAAM,WAAU;AAAA,YAAhB,UAEE;AAAA,cAFF;AAAA,cACK;AAAA;AAAA,WACH;AAAA,0BACF,IAEE,UAFF;AAAA,YAAQ,MAAK;AAAA,YAAS,WAAU;AAAA,YAAhC;AAAA,WAEE;AAAA;AAAA,OACF;AAAA;AAAA,GAEJ;AAAA;;AE5hBN,wBAA4B,2BAAa,qBAAW,mBAAO,oBAAS,qBAAQ;AAC5E,2BAAS;;;ACTT,sBAAS,sBAAW;AARpB;AAUA,IAAM,YACJ;AASF,IAAM,cAAc,IAAI;AAGxB,SAAS,UAAU,CAAC,SAA+B;AAAA,EACjD,IAAI,QAAQ,WAAW,KAAK,QAAQ,QAAQ,iCAAiC;AAAA,IAAG,OAAO;AAAA,EACvF,IAAI,QAAQ,QAAQ,mBAAmB;AAAA,IAAG,OAAO;AAAA,EACjD,MAAM,OAAO,QAAQ,cAAc;AAAA,EACnC,MAAM,aAAa,MAAM,iBAAiB,OAAO,EAAE;AAAA,EACnD,IAAI,eAAe,YAAY,eAAe;AAAA,IAAY,OAAO;AAAA,EACjE,SAAS,WAA+B,QAAS,UAAU,WAAW,SAAS,eAAe;AAAA,IAC5F,IAAI,MAAM,iBAAiB,QAAQ,EAAE,YAAY;AAAA,MAAQ,OAAO;AAAA,EAClE;AAAA,EACA,OAAO;AAAA;AAGF,SAAS,YAAY,CAC1B,UACA,QACA,UACM;AAAA,EAUN,MAAM,cAAc,QAAO,QAAQ;AAAA,EACnC,YAAY,UAAU;AAAA,EAEtB,WAAU,MAAM;AAAA,IACd,IAAI,CAAC;AAAA,MAAQ;AAAA,IACb,MAAM,QAAQ,SAAS;AAAA,IACvB,IAAI,CAAC;AAAA,MAAO;AAAA,IACZ,MAAM,MAAM,MAAM;AAAA,IAClB,IAAI,QAAQ,YAAY,IAAI,GAAG;AAAA,IAC/B,IAAI,CAAC,OAAO;AAAA,MACV,QAAQ,EAAE,OAAO,CAAC,GAAG,kBAAkB,IAAI,KAAK,MAAM,SAAS;AAAA,MAC/D,YAAY,IAAI,KAAK,KAAK;AAAA,IAC5B;AAAA,IACA,MAAM,QAAoB;AAAA,MACxB;AAAA,MACA,iBAAiB,IAAI;AAAA,MACrB,cAAc,MAAM,MAAM,GAAG,EAAE;AAAA,IACjC;AAAA,IACA,MAAM,MAAM,KAAK,KAAK;AAAA,IAEtB,SAAS,SAAS,MAAM;AAAA,IAExB,MAAM,QAAQ,CAAC,MAAqB;AAAA,MAClC,IAAI,MAAM,MAAM,GAAG,EAAE,MAAM;AAAA,QAAO;AAAA,MAClC,IAAI,EAAE,QAAQ,UAAU;AAAA,QACtB,YAAY,UAAU;AAAA,QACtB;AAAA,MACF;AAAA,MACA,IAAI,EAAE,QAAQ;AAAA,QAAO;AAAA,MACrB,MAAM,eAAe,SAAS;AAAA,MAC9B,IAAI,CAAC;AAAA,QAAc;AAAA,MACnB,MAAM,QAAQ,CAAC,GAAG,aAAa,iBAA8B,SAAS,CAAC,EAAE,OAAO,UAAU;AAAA,MAC1F,IAAI,MAAM,WAAW,GAAG;AAAA,QACtB,EAAE,eAAe;AAAA,QACjB,aAAa,MAAM;AAAA,QACnB;AAAA,MACF;AAAA,MACA,MAAM,QAAQ,MAAM;AAAA,MACpB,MAAM,OAAO,MAAM,MAAM,SAAS;AAAA,MAClC,MAAM,UAAU,IAAI;AAAA,MACpB,IAAI,CAAC,WAAW,CAAC,MAAM,SAAS,OAAO,GAAG;AAAA,QACxC,EAAE,eAAe;AAAA,SAChB,EAAE,WAAW,OAAO,OAAO,MAAM;AAAA,MACpC,EAAO,SAAI,EAAE,YAAY,YAAY,OAAO;AAAA,QAC1C,EAAE,eAAe;AAAA,QACjB,KAAK,MAAM;AAAA,MACb,EAAO,SAAI,CAAC,EAAE,YAAY,YAAY,MAAM;AAAA,QAC1C,EAAE,eAAe;AAAA,QACjB,MAAM,MAAM;AAAA,MACd;AAAA;AAAA,IAGF,IAAI,iBAAiB,WAAW,KAAK;AAAA,IACrC,IAAI,KAAK,MAAM,WAAW;AAAA,IAE1B,OAAO,MAAM;AAAA,MACX,IAAI,oBAAoB,WAAW,KAAK;AAAA,MACxC,MAAM,SAAS,MAAM,MAAM,GAAG,EAAE,MAAM;AAAA,MACtC,MAAM,MAAM,OAAO,MAAM,MAAM,QAAQ,KAAK,GAAG,CAAC;AAAA,MAEhD,WAAW,aAAa,MAAM,OAAO;AAAA,QACnC,IAAI,UAAU,iBAAiB,OAAO;AAAA,UACpC,UAAU,eAAe,MAAM;AAAA,UAC/B,UAAU,kBAAkB,MAAM;AAAA,QACpC;AAAA,MACF;AAAA,MACA,MAAM,MAAM,MAAM,MAAM,GAAG,EAAE;AAAA,MAC7B,IAAI,CAAC,KAAK;AAAA,QACR,IAAI,KAAK,MAAM,WAAW,MAAM;AAAA,QAChC,YAAY,OAAO,GAAG;AAAA,MACxB;AAAA,MACA,IAAI,QAAQ;AAAA,QACV,MAAM,WAAW,MAAM;AAAA,QACvB,IAAI,UAAU,gBAAgB,CAAC,OAAO,IAAI,MAAM,SAAS,QAAQ;AAAA,UAAI,SAAS,MAAM;AAAA,QAC/E;AAAA,eAAK,MAAM,MAAM;AAAA,MACxB;AAAA;AAAA,KAED,CAAC,UAAU,MAAM,CAAC;AAAA;;;AD3GvB;AAAA,cACE;AAAA,iBACA;AAAA,kBACA;AAAA,eACA;AAAA,eACA;AAAA,gBACA;AAAA;AAAA;AAAA;AAIF,mBAAS,wBAAQ;AACjB,uBAAS,8BAAqB;AAG9B;AAEA;AAEA;;AAtCA;AA2FA,SAAS,UAAU,CAAC,GAAuB,UAA0B;AAAA,EACnE,MAAM,MAAM,EAAE;AAAA,EACd,IAAI,OAAO,QAAQ,YAAY,OAAO,SAAS,GAAG;AAAA,IAAG,OAAO,KAAK,MAAM,GAAG;AAAA,EAC1E,IAAI,OAAO,QAAQ,YAAY,OAAO,GAAG,EAAE,KAAK,KAAK,OAAO,SAAS,OAAO,GAAG,CAAC,GAAG;AAAA,IACjF,OAAO,KAAK,MAAM,OAAO,GAAG,CAAC;AAAA,EAC/B;AAAA,EACA,OAAO;AAAA;AAGT,SAAS,YAAY,CACnB,OACA,iBACgB;AAAA,EAChB,MAAM,MAAsB,CAAC;AAAA,EAC7B,SAAS,IAAI,EAAG,IAAI,MAAM,QAAQ,KAAK;AAAA,IACrC,MAAM,IAAI,MAAM;AAAA,IAChB,MAAM,MAAM,OAAO,EAAE,UAAU,YAAY,EAAE,MAAM,KAAK,IAAI,EAAE,MAAM,KAAK,IAAI;AAAA,IAC7E,MAAM,OAAO,OAAO,EAAE,SAAS,YAAY,EAAE,KAAK,KAAK,IAAI,EAAE,KAAK,KAAK,IAAI;AAAA,IAC3E,MAAM,WAAW,EAAE,aAAa,QAAQ,SAAS;AAAA,IAGjD,IAAI,CAAC,OAAO,CAAC;AAAA,MAAU;AAAA,IACvB,IAAI,KAAK;AAAA,MACP,QAAQ,WAAW,GAAG,IAAI,CAAC;AAAA,MAC3B,UAAU,MAAM,gBAAgB,GAAG,IAAI;AAAA,MACvC;AAAA,MACA;AAAA,MACA,UAAU,EAAE,aAAa;AAAA,IAC3B,CAAC;AAAA,EACH;AAAA,EACA,OAAO;AAAA;AAIT,IAAM,gBAAgB;AAUtB,SAAS,iBAAiB,CACxB,oBACA,OACA,SACqB;AAAA,EACrB,OAAO,SAAS,cAAc,UAA8B,MAAM,IAAI,GAAK;AAAA,EAG3E,MAAM,WAAW,QAAoB,IAAI,GAAK;AAAA,EAE9C,MAAM,YAAY,SAAQ,MAAM;AAAA,IAC9B,IAAI,CAAC;AAAA,MAAoB,OAAO,CAAC;AAAA,IACjC,MAAM,KAAK,KAAK,IAAI,GAAG,UAAU,aAAa;AAAA,IAC9C,MAAM,IAAI,KAAK,IAAI,MAAM,QAAQ,UAAU,gBAAgB,CAAC;AAAA,IAC5D,MAAM,MAAgB,CAAC;AAAA,IACvB,SAAS,IAAI,GAAI,IAAI,GAAG,KAAK;AAAA,MAC3B,MAAM,KAAK,MAAM;AAAA,MACjB,IAAI,CAAC,MAAM,CAAC,GAAG,YAAY,GAAG,SAAS;AAAA,QAAM;AAAA,MAC7C,IAAI,QAAQ,IAAI,GAAG,MAAM,KAAK,SAAS,QAAQ,IAAI,GAAG,MAAM;AAAA,QAAG;AAAA,MAC/D,IAAI,KAAK,GAAG,MAAM;AAAA,IACpB;AAAA,IACA,OAAO;AAAA,KACN,CAAC,oBAAoB,OAAO,SAAS,OAAO,CAAC;AAAA,EAEhD,WAAU,MAAM;AAAA,IACd,IAAI,UAAU,WAAW;AAAA,MAAG;AAAA,IAC5B,WAAW,KAAK;AAAA,MAAW,SAAS,QAAQ,IAAI,CAAC;AAAA,IACjD,MAAM,OAAO,IAAI;AAAA,IACjB,mBAAoB,WAAW,KAAK,MAAM,EACvC,KAAK,CAAC,YAAY,EAAE,OAAO,EAAE,EAC7B,KAAK,CAAC,MAA2C;AAAA,MAChD,MAAM,QAAQ,OAAO,QAAQ,EAAE,UAAU,CAAC,CAAC;AAAA,MAC3C,IAAI,MAAM,WAAW;AAAA,QAAG;AAAA,MACxB,WAAW,CAAC,SAAS;AAAA,QACnB,MAAM,OAAO,IAAI,IAAI,IAAI;AAAA,QACzB,YAAY,GAAG,MAAM;AAAA,UAAO,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC;AAAA,QACjD,OAAO;AAAA,OACR;AAAA,MACD;AAAA,KACD,EACA,MAAM,MAAM,EAEZ;AAAA,IACH,OAAO,MAAM,KAAK,MAAM;AAAA,KACvB,CAAC,WAAW,kBAAkB,CAAC;AAAA,EAElC,OAAO;AAAA;AAGF,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA,kBAAkB,CAAC,SAAS;AAAA,EAC5B,WAAW,CAAC,QAAQ;AAAA,EACpB,cAAc,MAAM;AAAA,EACpB;AAAA,EACA;AAAA,EACA,eAAe;AAAA,GACqB;AAAA,EACpC,MAAM,eAAe,QAAuB,IAAI;AAAA,EAChD,MAAM,cAAc,QAAuB,IAAI;AAAA,EAC/C,MAAM,SAAS,OAAM;AAAA,EAErB,MAAM,QAAQ,SAAQ,MAAM,aAAa,OAAO,eAAe,GAAG,CAAC,OAAO,eAAe,CAAC;AAAA,EAC1F,MAAM,QAAQ,MAAM;AAAA,EAEpB,OAAO,MAAM,WAAW,UAAmB,UAAU;AAAA,EACrD,OAAO,SAAS,cAAc,UAAS,CAAC;AAAA,EACxC,QAAQ,cAAc,qBAAqB,cAAc,YAAY;AAAA,EACrE,QAAQ,WAAW,cAAc,sBAAsB,cAAc,UAAU;AAAA,EAE/E,OAAO,UAAU,eAAe,UAAS,KAAK;AAAA,EAE9C,MAAM,gBAAgB,kBAAkB,oBAAoB,OAAO,OAAO;AAAA,EAE1E,MAAM,UAAU,aACd,CAAC,MACC,IAAK,EAAE,QAAQ,cAAc,IAAI,EAAE,MAAM,KAAK,OAAQ,MACxD,CAAC,aAAa,CAChB;AAAA,EACA,aAAa,aAAa,UAAU,MAAM,YAAY,KAAK,CAAC;AAAA,EAG5D,WAAU,MAAM;AAAA,IACd,IAAI,WAAW,SAAS,QAAQ;AAAA,MAAG,WAAW,QAAQ,CAAC;AAAA,IACvD,IAAI,UAAU;AAAA,MAAG,WAAW,CAAC;AAAA,KAC5B,CAAC,OAAO,OAAO,CAAC;AAAA,EAGnB,WAAU,MAAM;AAAA,IACd,IAAI,UAAU;AAAA,MAAG;AAAA,IACjB,WAAW,OAAO,CAAC,UAAU,GAAG,SAAS,UAAU,GAAG,UAAU,CAAC,GAAG;AAAA,MAClE,MAAM,MAAM,MAAM,MAAM;AAAA,MACxB,IAAI,CAAC;AAAA,QAAK;AAAA,MACV,MAAM,MAAM,IAAI;AAAA,MAChB,IAAI,MAAM;AAAA,IACZ;AAAA,KACC,CAAC,SAAS,OAAO,KAAK,CAAC;AAAA,EAG1B,OAAO,iBAAiB,sBAAsB,UAAS,KAAK;AAAA,EAC5D,WAAU,MAAM;AAAA,IACd,mBACE,OAAO,aAAa,eAClB,SAAS,qBACT,OAAO,aAAa,SAAS,sBAAsB,UACvD;AAAA,KACC,CAAC,CAAC;AAAA,EAIL,MAAM,kBAAkB,QAAuB,IAAI;AAAA,EACnD,MAAM,cAAc,gBAAe;AAAA,IACjC,OAAO;AAAA,IACP,kBAAkB,MAAM,gBAAgB;AAAA,IACxC,cAAc,MACZ,OAAO,WAAW,cAAc,OAAO,KAAK,MAAM,OAAO,cAAc,IAAI;AAAA,IAM7E,UAAU,KAAK,IAAI,GAAG,YAAY;AAAA,EACpC,CAAC;AAAA,EAED,MAAM,OAAO,aACX,CAAC,QAAgB;AAAA,IACf,MAAM,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,CAAC,GAAG,KAAK,IAAI,GAAG,QAAQ,CAAC,CAAC;AAAA,IAC3D,WAAW,CAAC;AAAA,IACZ,IAAI,SAAS;AAAA,MAAS,UAAU,SAAS,QAAQ,CAAC;AAAA,IAC7C;AAAA,kBAAY,cAAc,GAAG,EAAE,OAAO,QAAQ,CAAC;AAAA,KAEtD,CAAC,MAAM,OAAO,WAAW,CAC3B;AAAA,EAEA,sBAAsB,MAAM,iBAAiB,aAAa,UAAU;AAAA,EAYpE,WAAU,MAAM;AAAA,IACd,MAAM,QAAQ,YAAY,SAAS;AAAA,IACnC,IAAI,CAAC;AAAA,MAAO;AAAA,IACZ,MAAM,QAAQ,CAAC,MAAqB;AAAA,MAClC,MAAM,QAAQ,EAAE;AAAA,MAChB,MAAM,MAAM,OAAO;AAAA,MACnB,IAAI,QAAQ,WAAW,QAAQ,cAAc,OAAO;AAAA,QAAmB;AAAA,MACvE,QAAQ,EAAE;AAAA,aACH;AAAA,UACH,IAAI;AAAA,YAAU,YAAY,KAAK;AAAA,UAC/B;AAAA,aACG;AAAA,UACH,EAAE,eAAe;AAAA,UACjB,KAAK,UAAU,CAAC;AAAA,UAChB;AAAA,aACG;AAAA,UACH,EAAE,eAAe;AAAA,UACjB,KAAK,UAAU,CAAC;AAAA,UAChB;AAAA,aACG;AAAA,UACH,EAAE,eAAe;AAAA,UACjB,KAAK,CAAC;AAAA,UACN;AAAA,aACG;AAAA,UACH,EAAE,eAAe;AAAA,UACjB,KAAK,QAAQ,CAAC;AAAA,UACd;AAAA;AAAA;AAAA,IAGN,OAAO,iBAAiB,WAAW,KAAK;AAAA,IACxC,OAAO,MAAM,OAAO,oBAAoB,WAAW,KAAK;AAAA,KACvD,CAAC,UAAU,MAAM,OAAO,SAAS,IAAI,CAAC;AAAA,EAEzC,MAAM,aAAa,aAAY,MAAM;AAAA,IACnC,KAAK,UAAU,CAAC;AAAA,KACf,CAAC,SAAS,IAAI,CAAC;AAAA,EAElB,MAAM,aAAa,aAAY,MAAM;AAAA,IACnC,KAAK,UAAU,CAAC;AAAA,KACf,CAAC,SAAS,IAAI,CAAC;AAAA,EAElB,MAAM,eAAe,aACnB,CAAC,QAAiB;AAAA,IAChB,IAAI,OAAO,QAAQ;AAAA,MAAU,WAAW,GAAG;AAAA,IAC3C,MAAM,OAAO,MAAM,OAAO,QAAQ,WAAW,MAAM;AAAA,IACnD,IAAI,MAAM;AAAA,MAAU,YAAY,IAAI;AAAA,KAEtC,CAAC,OAAO,OAAO,CACjB;AAAA,EASA,MAAM,YAAY,QAAwC,IAAI;AAAA,EAC9D,MAAM,eAAe,aAAY,CAAC,MAAwB;AAAA,IACxD,MAAM,IAAI,EAAE,QAAQ;AAAA,IACpB,UAAU,UAAU,IAAI,EAAE,GAAG,EAAE,SAAS,GAAG,EAAE,QAAQ,IAAI;AAAA,KACxD,CAAC,CAAC;AAAA,EACL,MAAM,aAAa,aACjB,CAAC,MAAwB;AAAA,IACvB,MAAM,SAAS,UAAU;AAAA,IACzB,UAAU,UAAU;AAAA,IACpB,MAAM,IAAI,EAAE,eAAe;AAAA,IAC3B,IAAI,CAAC,UAAU,CAAC;AAAA,MAAG;AAAA,IACnB,MAAM,KAAK,EAAE,UAAU,OAAO;AAAA,IAC9B,MAAM,KAAK,EAAE,UAAU,OAAO;AAAA,IAC9B,IAAI,KAAK,IAAI,EAAE,IAAI,MAAM,KAAK,IAAI,EAAE,IAAI,KAAK,IAAI,EAAE;AAAA,MAAG;AAAA,IAItD,KAAK,KAAK,IAAI,UAAU,IAAI,UAAU,CAAC;AAAA,KAEzC,CAAC,SAAS,IAAI,CAChB;AAAA,EAEA,MAAM,cAAc,MAAM;AAAA,EAC1B,MAAM,aAAa,aAAa,UAAU,UAAU;AAAA,EACpD,MAAM,UAAU,QAAQ,KAAK,gBAAgB,KAAK,IAAI,UAAU,GAAG,KAAK,KAAK,UAAU;AAAA,EACvF,MAAM,cAAc,QAAQ,WAAW;AAAA,EACvC,MAAM,eAAe,aAAa,YAAY;AAAA,EAE9C,IAAI,UAAU,GAAG;AAAA,IACf,uBACE,KAEE,OAFF;AAAA,MAAK,WAAU;AAAA,MAAf;AAAA,KAEE;AAAA,EAEN;AAAA,EAEA,MAAM,8BACJ,MA4BE,OA5BF;AAAA,IAAK,WAAU;AAAA,IAAf,UA4BE;AAAA,sBA3BA,MAOE,OAPF;AAAA,QAAK,WAAU;AAAA,QAAf,UAOE;AAAA,0BANA,KAEE,QAFF;AAAA,YAAM,WAAU;AAAA,YAAhB,0BACE,MAA4B,QAA5B;AAAA,wBAA4B;AAAA,gBAA5B;AAAA,gBAAe;AAAA;AAAA,aAAa;AAAA,WAC5B;AAAA,0BACF,MAEE,QAFF;AAAA,YAAM,WAAU;AAAA,YAAhB,UAEE;AAAA,cADC,UAAU;AAAA,cADb;AAAA,cACmB;AAAA;AAAA,WACjB;AAAA;AAAA,OACF;AAAA,sBACF,KAkBE,OAlBF;AAAA,QAAK,WAAU;AAAA,QAAf,UACG,cACE,sBAAsB;AAAA,UACrB,MAAM;AAAA,UACN,MAAM;AAAA,UACN,UAAU,aAAa,YAAY;AAAA,QACrC,CAAC,KAAK,cACJ,aAAa,2BAGf,KAEE,KAFF;AAAA,UAAG,WAAU;AAAA,UAAb;AAAA,SAEE,oBAEF,KAEE,KAFF;AAAA,UAAG,WAAU;AAAA,UAAb;AAAA,SAEE;AAAA,OAEJ;AAAA;AAAA,GACF;AAAA,EAGJ,uBACE,MAyYE,OAzYF;AAAA,IACE,KAAK;AAAA,IACL,WAAU;AAAA,IACV,wBAAoB;AAAA,IACpB,qBAAkB;AAAA,IAClB,eAAY;AAAA,IACZ,MAAK;AAAA,IACL,cAAY;AAAA,IAPd,UAyYE;AAAA,sBA/XA,MAyDE,OAzDF;AAAA,QAAK,WAAU;AAAA,QAAf,UAyDE;AAAA,0BAxDA,KAEE,QAFF;AAAA,YAAM,WAAU;AAAA,YAAhB,UACG;AAAA,WACD;AAAA,0BACF,KAEE,QAFF;AAAA,YAAM,WAAU;AAAA,YAAhB,0BACE,KAAiB,QAAjB;AAAA,wBAAO;AAAA,aAAU;AAAA,WACjB;AAAA,0BAEF,MAgDE,OAhDF;AAAA,YAAK,WAAU;AAAA,YAAf,UAgDE;AAAA,8BA/CA,MAUE,UAVF;AAAA,gBACE,MAAK;AAAA,gBACL,WAAW,SAAS,aAAa,yBAAyB;AAAA,gBAC1D,SAAS,MAAM,QAAQ,UAAU;AAAA,gBACjC,gBAAc,SAAS;AAAA,gBACvB,cAAW;AAAA,gBACX,OAAM;AAAA,gBANR,UAUE;AAAA,kCAFA,KAAC,aAAD;AAAA,oBAAY,MAAM;AAAA,oBAAI,eAAW;AAAA,mBAAC;AAAA,kCAClC,KAA+C,QAA/C;AAAA,oBAAM,WAAU;AAAA,oBAAhB;AAAA,mBAA+C;AAAA;AAAA,eAC/C;AAAA,8BACF,MAUE,UAVF;AAAA,gBACE,MAAK;AAAA,gBACL,WAAW,SAAS,UAAU,yBAAyB;AAAA,gBACvD,SAAS,MAAM,QAAQ,OAAO;AAAA,gBAC9B,gBAAc,SAAS;AAAA,gBACvB,cAAW;AAAA,gBACX,OAAM;AAAA,gBANR,UAUE;AAAA,kCAFA,KAAC,WAAD;AAAA,oBAAU,MAAM;AAAA,oBAAI,eAAW;AAAA,mBAAC;AAAA,kCAChC,KAA2C,QAA3C;AAAA,oBAAM,WAAU;AAAA,oBAAhB;AAAA,mBAA2C;AAAA;AAAA,eAC3C;AAAA,8BACF,KASE,UATF;AAAA,gBACE,MAAK;AAAA,gBACL,WAAU;AAAA,gBACV,SAAS,MAAM,aAAa;AAAA,gBAC5B,UAAU,CAAC;AAAA,gBACX,cAAW;AAAA,gBACX,OAAM;AAAA,gBANR,0BAQE,KAAC,QAAD;AAAA,kBAAQ,MAAM;AAAA,kBAAI,eAAW;AAAA,iBAAC;AAAA,eAC9B;AAAA,cACD,mCACC,KAYE,UAZF;AAAA,gBACE,MAAK;AAAA,gBACL,WAAU;AAAA,gBACV,SAAS;AAAA,gBACT,cAAY,eAAe,2BAA2B;AAAA,gBACtD,OAAO,eAAe,2BAA2B;AAAA,gBALnD,UAOG,+BACC,KAAC,YAAD;AAAA,kBAAW,MAAM;AAAA,kBAAI,eAAW;AAAA,iBAAC,oBAEjC,KAAC,YAAD;AAAA,kBAAW,MAAM;AAAA,kBAAI,eAAW;AAAA,iBAAC;AAAA,eAEnC;AAAA;AAAA,WAEJ;AAAA;AAAA,OACF;AAAA,sBAGF,KAkLE,OAlLF;AAAA,QAAK,WAAU;AAAA,QAAf,UACG,SAAS,0BAGR,MA8EE,OA9EF;AAAA,UAAK,WAAU;AAAA,UAAf,UA8EE;AAAA,4BA5EA,MAsEE,OAtEF;AAAA,cAAK,WAAU;AAAA,cAAf,UAsEE;AAAA,gCArEA,KAiDE,SAjDF;AAAA,kBAEE,cAAc;AAAA,kBACd,KAAI;AAAA,kBACJ,SAAS,CAAC,OAAM,SAAS,WAAU;AAAA,kBACnC,WAAU;AAAA,kBACV,eAAe;AAAA,kBACf,YAAY,EAAE,aAAa,KAAK;AAAA,kBAChC,MAAM,EAAE,UAAU,GAAG,QAAQ,KAAK;AAAA,kBAClC,SAAO;AAAA,kBACP,UAAU;AAAA,kBACV,eAAe;AAAA,kBAXjB,UAaG,MAAM,IAAI,CAAC,MAAM,sBAChB,KAiCE,cAjCF;AAAA,oBAEE,cAAc;AAAA,oBACd,MAAM,QAAQ,KAAK,QAAQ;AAAA,oBAC3B,WAAU;AAAA,oBAJZ,0BAME,KA0BE,OA1BF;AAAA,sBAAK,WAAU;AAAA,sBAAf,UACG,KAAK,2BACJ,MAkBE,UAlBF;AAAA,wBACE,MAAK;AAAA,wBACL,WAAU;AAAA,wBACV,SAAS,MAAM,aAAa,CAAC;AAAA,wBAC7B,cAAY,oBAAoB,KAAK;AAAA,wBAJvC,UAkBE;AAAA,0CAZA,KAAC,OAAD;AAAA,4BACE,KAAK,SAAS,KAAK,UAAU,IAAI;AAAA,4BACjC,QAAQ,YAAY,KAAK,UAAU,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK;AAAA,4BACzD,OAAM;AAAA,4BACN,KAAK,GAAG,gBAAgB,KAAK;AAAA,4BAC7B,SAAS,KAAK,IAAI,IAAI,OAAO,KAAK,IAAI,UAAU;AAAA,4BAChD,WAAW;AAAA,4BACX,WAAU;AAAA,2BACZ;AAAA,0CACA,KAEE,QAFF;AAAA,4BAAM,WAAU;AAAA,4BAAhB;AAAA,2BAEE;AAAA;AAAA,uBACF,oBAEF,MAEE,OAFF;AAAA,wBAAK,WAAU;AAAA,wBAAf,UAEE;AAAA,0BAFF;AAAA,0BACQ,KAAK;AAAA,0BADb;AAAA;AAAA,uBAEE;AAAA,qBAEJ;AAAA,qBA/BG,WAAW,KAAK,UAAU,GAgC/B,CACH;AAAA,mBA/CG,cAgDJ;AAAA,gCAEF,KAQE,UARF;AAAA,kBACE,MAAK;AAAA,kBACL,cAAW;AAAA,kBACX,SAAS;AAAA,kBACT,UAAU,WAAW;AAAA,kBACrB,WAAU;AAAA,kBALZ,0BAOE,KAAC,cAAD;AAAA,oBAAa,MAAM;AAAA,oBAAI,eAAW;AAAA,mBAAC;AAAA,iBACnC;AAAA,gCACF,KAQE,UARF;AAAA,kBACE,MAAK;AAAA,kBACL,cAAW;AAAA,kBACX,SAAS;AAAA,kBACT,UAAU,WAAW,QAAQ;AAAA,kBAC7B,WAAU;AAAA,kBALZ,0BAOE,KAAC,eAAD;AAAA,oBAAc,MAAM;AAAA,oBAAI,eAAW;AAAA,mBAAC;AAAA,iBACpC;AAAA;AAAA,aACF;AAAA,4BAGF,KAEE,OAFF;AAAA,cAAK,WAAU;AAAA,cAAf,UACG;AAAA,aACD;AAAA;AAAA,SACF,oBAEF,KA4FE,OA5FF;AAAA,UACE,KAAK;AAAA,UACL,UAAU;AAAA,UAiBV,WAAU;AAAA,UACV,cAAY,GAAG;AAAA,UApBjB,0BAsBE,KAqEE,OArEF;AAAA,YACE,OAAO;AAAA,cACL,QAAQ,YAAY,aAAa;AAAA,cACjC,OAAO;AAAA,cACP,UAAU;AAAA,YACZ;AAAA,YALF,UAOG,YAAY,gBAAgB,EAAE,IAAI,CAAC,OAAO;AAAA,cACzC,MAAM,OAAO,MAAM,GAAG;AAAA,cACtB,IAAI,CAAC;AAAA,gBAAM,OAAO;AAAA,cAClB,uBACE,KAuDE,OAvDF;AAAA,gBAEE,cAAY,GAAG;AAAA,gBACf,KAAK,YAAY;AAAA,gBACjB,OAAO,gBAAgB,GAAG,KAAK;AAAA,gBAC/B,WAAU;AAAA,gBALZ,0BAOE,MA+CE,OA/CF;AAAA,kBAAK,WAAU;AAAA,kBAAf,UA+CE;AAAA,oCA9CA,MAEE,QAFF;AAAA,sBAAM,WAAU;AAAA,sBAAhB,UAEE;AAAA,wBAFF;AAAA,wBACQ,KAAK;AAAA;AAAA,qBACX;AAAA,oBACD,KAAK,2BACJ,MA2BE,UA3BF;AAAA,sBACE,MAAK;AAAA,sBACL,WAAU;AAAA,sBACV,SAAS,MAAM,aAAa,GAAG,KAAK;AAAA,sBACpC,cAAY,kBAAkB,KAAK;AAAA,sBAJrC,UA2BE;AAAA,wCArBA,KAAC,OAAD;AAAA,0BACE,KAAK,SAAS,KAAK,UAAU,IAAI;AAAA,0BACjC,QAAQ,YAAY,KAAK,UAAU,CAAC,KAAK,MAAM,IAAI,CAAC,KAAK;AAAA,0BACzD,OAAM;AAAA,0BACN,KAAK,GAAG,gBAAgB,KAAK;AAAA,0BAI7B,SAAS,KAAK,IAAI,GAAG,QAAQ,OAAO,KAAK,IAAI,UAAU;AAAA,0BAGvD,eAAe,GAAG,UAAU,UAAU,SAAS;AAAA,0BAC/C,UAAS;AAAA,0BACT,WAAW;AAAA,0BACX,WAAU;AAAA,yBACZ;AAAA,wCAEA,MAGE,QAHF;AAAA,0BAAM,WAAU;AAAA,0BAAhB,UAGE;AAAA,4CAFA,KAAC,QAAD;AAAA,8BAAQ,MAAM;AAAA,8BAAI,eAAW;AAAA,6BAAC;AAAA,4BADhC;AAAA;AAAA,yBAGE;AAAA;AAAA,qBACF,oBAEF,KAEE,OAFF;AAAA,sBAAK,WAAU;AAAA,sBAAf;AAAA,qBAEE;AAAA,oBAEH,QAAQ,IAAI,oBACX,KAME,OANF;AAAA,sBAAK,WAAU;AAAA,sBAAf,UACG,sBAAsB;AAAA,wBACrB,MAAM,QAAQ,IAAI;AAAA,wBAClB,MAAM,KAAK;AAAA,wBACX,UAAU,KAAK;AAAA,sBACjB,CAAC,KAAK,QAAQ,IAAI;AAAA,qBAClB,IACA;AAAA;AAAA,iBACJ;AAAA,iBArDG,GAAG,UAAU,GAAG,KAsDrB;AAAA,aAEL;AAAA,WACD;AAAA,SACF;AAAA,OAEJ;AAAA,MAGD,SAAS,WAAW,QAAQ,KAAK,SAAS,kCACzC,KAoCE,OApCF;AAAA,QAAK,WAAU;AAAA,QAAf,0BACE,KAkCE,OAlCF;AAAA,UAAK,WAAU;AAAA,UAAf,UACG,MAAM,IAAI,CAAC,MAAM,wBAChB,MA8BE,UA9BF;AAAA,YAEE,MAAK;AAAA,YACL,SAAS,MAAM,KAAK,GAAG;AAAA,YACvB,WAAW,oFACT,YAAY,MACR,kFACA;AAAA,YAEN,OAAO,QAAQ,KAAK;AAAA,YACpB,cAAY,mBAAmB,KAAK;AAAA,YACpC,gBAAc,YAAY,MAAM,SAAS;AAAA,YAX3C,UA8BE;AAAA,cAjBC,KAAK,2BACJ,KAAC,OAAD;AAAA,gBAGE,KAAK,SAAS,KAAK,UAAU,EAAE;AAAA,gBAC/B,KAAI;AAAA,gBACJ,WAAU;AAAA,gBACV,SAAQ;AAAA,eACV,oBAEA,KAEE,QAFF;AAAA,gBAAM,WAAU;AAAA,gBAAhB;AAAA,eAEE;AAAA,8BAEJ,KAEE,QAFF;AAAA,gBAAM,WAAU;AAAA,gBAAhB,UACG,KAAK;AAAA,eACN;AAAA;AAAA,aA5BG,SAAS,KAAK,UAAU,KA6B7B,CACH;AAAA,SACD;AAAA,OACF;AAAA,MAIH,SAAS,WAAW,QAAQ,kCAC3B,MA8BE,QA9BF;AAAA,QACE,WAAU;AAAA,QACV,UAAU,CAAC,MAAM;AAAA,UACf,EAAE,eAAe;AAAA,UACjB,MAAM,QAAQ,EAAE,cAAc,SAAS,UAAU,MAAM;AAAA,UACvD,MAAM,SAAS,OAAO,OAAO,KAAK;AAAA,UAClC,IAAI,CAAC,OAAO,SAAS,MAAM;AAAA,YAAG;AAAA,UAE9B,MAAM,WAAW,MAAM,UAAU,CAAC,OAAO,GAAG,WAAW,KAAK,MAAM,MAAM,CAAC;AAAA,UACzE,IAAI,YAAY;AAAA,YAAG,KAAK,QAAQ;AAAA,UAC3B;AAAA,iBAAK,KAAK,MAAM,MAAM,IAAI,CAAC;AAAA;AAAA,QAVpC,UA8BE;AAAA,0BAjBA,KAKE,SALF;AAAA,YACE,SAAS,GAAG;AAAA,YACZ,WAAU;AAAA,YAFZ;AAAA,WAKE;AAAA,0BACF,KAAC,SAAD;AAAA,YACE,IAAI,GAAG;AAAA,YACP,MAAK;AAAA,YACL,MAAK;AAAA,YAEL,cAAc;AAAA,YACd,WAAU;AAAA,aAFL,UAGP;AAAA,0BACA,KAEE,UAFF;AAAA,YAAQ,MAAK;AAAA,YAAS,WAAU;AAAA,YAAhC;AAAA,WAEE;AAAA;AAAA,OACF;AAAA,MAIH,YAAY,gCACX,MA+DE,OA/DF;AAAA,QACE,KAAK;AAAA,QAKL,UAAU;AAAA,QACV,MAAK;AAAA,QACL,cAAW;AAAA,QACX,cAAY,GAAG,gBAAgB;AAAA,QAC/B,WAAU;AAAA,QACV,SAAS,MAAM,YAAY,KAAK;AAAA,QAXlC,UA+DE;AAAA,0BAlDA,MAkBE,OAlBF;AAAA,YAAK,WAAU;AAAA,YAAf,UAkBE;AAAA,8BAjBA,KAEE,QAFF;AAAA,gBAAM,WAAU;AAAA,gBAAhB,UACG;AAAA,eACD;AAAA,8BACF,KAEE,QAFF;AAAA,gBAAM,WAAU;AAAA,gBAAhB,0BACE,MAAsB,QAAtB;AAAA,4BAAsB;AAAA,oBAAtB;AAAA,oBAAS;AAAA;AAAA,iBAAa;AAAA,eACtB;AAAA,8BACF,KAUE,UAVF;AAAA,gBACE,MAAK;AAAA,gBACL,WAAU;AAAA,gBACV,SAAS,CAAC,MAAM;AAAA,kBACd,EAAE,gBAAgB;AAAA,kBAClB,YAAY,KAAK;AAAA;AAAA,gBAEnB,cAAW;AAAA,gBAPb,0BASE,KAAC,GAAD;AAAA,kBAAG,MAAM;AAAA,kBAAI,eAAW;AAAA,iBAAC;AAAA,eACzB;AAAA;AAAA,WACF;AAAA,0BACF,MA8BE,OA9BF;AAAA,YACE,WAAU;AAAA,YACV,SAAS,CAAC,MAAM,EAAE,gBAAgB;AAAA,YAClC;AAAA,YACA;AAAA,YAJF,UA8BE;AAAA,8BAxBA,KAAC,OAAD;AAAA,gBACE,KAAK;AAAA,gBACL,KAAK,GAAG,gBAAgB;AAAA,gBACxB,WAAU;AAAA,gBACV,WAAW;AAAA,eACb;AAAA,8BACA,KAQE,UARF;AAAA,gBACE,MAAK;AAAA,gBACL,cAAW;AAAA,gBACX,SAAS,MAAM,KAAK,UAAU,CAAC;AAAA,gBAC/B,UAAU,WAAW;AAAA,gBACrB,WAAU;AAAA,gBALZ,0BAOE,KAAC,cAAD;AAAA,kBAAa,MAAM;AAAA,kBAAI,eAAW;AAAA,iBAAC;AAAA,eACnC;AAAA,8BACF,KAQE,UARF;AAAA,gBACE,MAAK;AAAA,gBACL,cAAW;AAAA,gBACX,SAAS,MAAM,KAAK,UAAU,CAAC;AAAA,gBAC/B,UAAU,WAAW,QAAQ;AAAA,gBAC7B,WAAU;AAAA,gBALZ,0BAOE,KAAC,eAAD;AAAA,kBAAc,MAAM;AAAA,kBAAI,eAAW;AAAA,iBAAC;AAAA,eACpC;AAAA;AAAA,WACF;AAAA;AAAA,OACF;AAAA;AAAA,GAEJ;AAAA;;AE/xBN,sBAAyB,qBAAW,mBAAO,sBAAS;AAAA;AAFpD;AA8BO,SAAS,gBAAgB,GAAG,OAAO,UAAU,gBAAgB,gBAAgB,YAAY,eAAe,eAAe,WAAW,mBAAmB,iBAAwC;AAAA,EAClM,MAAM,aAAa,OAAM;AAAA,EACzB,MAAM,YAAY,SAAQ,MAAM,SAAS,IAAI,CAAC,aAAa,KAAK,SAAS,UAAU,QAAQ,SAAS,OAAO,CAAC,YAAY,QAAQ,YAAY,CAAC,EAAE,EAAE,EAAE,OAAO,CAAC,YAAY,QAAQ,SAAS,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC;AAAA,EAC/M,OAAO,WAAW,gBAAgB,UAAS,MAAM,UAAU,KAAK,CAAC,YAAY,QAAQ,OAAO,cAAc,GAAG,MAAM,UAAU,IAAI,MAAM,EAAE;AAAA,EACzI,MAAM,UAAU,UAAU,KAAK,CAAC,SAAS,KAAK,OAAO,SAAS,KAAK,UAAU;AAAA,EAC7E,OAAO,cAAc,mBAAmB,UAAS,MAAM,KAAK,IAAI,GAAG,SAAS,SAAS,UAAU,CAAC,YAAY,OAAO,QAAQ,EAAE,MAAM,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC;AAAA,EAChK,OAAO,cAAc,mBAAmB,UAAS,KAAK;AAAA,EACtD,MAAM,YAAY,KAAK,IAAI,cAAc,KAAK,IAAI,IAAI,SAAS,SAAS,UAAU,KAAK,CAAC,CAAC;AAAA,EACzF,MAAM,UAAU,SAAS,SAAS;AAAA,EAElC,WAAU,MAAM;AAAA,IACd,IAAI,WAAW;AAAA,MAAS,oBAAoB,SAAS,OAAO;AAAA,KAC3D,CAAC,SAAS,SAAS,iBAAiB,CAAC;AAAA,EAExC,IAAI,CAAC,WAAW,CAAC,SAAS;AAAA,IACxB,uBAAO,KAAmI,KAAnI;AAAA,MAAG,WAAU;AAAA,MAA6B,qBAAkB;AAAA,MAAmB,eAAY;AAAA,MAA3F,UAAsH;AAAA,KAAa;AAAA,EAC5I;AAAA,EAEA,uBACE,MA8BE,WA9BF;AAAA,IAAS,WAAU;AAAA,IAAiC,cAAY;AAAA,IAAO,qBAAkB;AAAA,IAAmB,eAAY;AAAA,IAAxH,UA8BE;AAAA,sBA7BA,MAWE,OAXF;AAAA,QAAK,WAAU;AAAA,QAAf,UAWE;AAAA,0BAVA,KAOE,OAPF;AAAA,YAAK,WAAU;AAAA,YAAgC,MAAK;AAAA,YAAQ,cAAY;AAAA,YAAxE,UACG,UAAU,IAAI,CAAC,yBACd,KAGE,UAHF;AAAA,cAAsB,MAAK;AAAA,cAAS,WAAW,KAAK,OAAO,QAAQ,KAAK,yBAAyB;AAAA,cAC/F,gBAAc,KAAK,OAAO,QAAQ;AAAA,cAAI,SAAS,MAAM;AAAA,gBAAE,aAAa,KAAK,EAAE;AAAA,gBAAG,gBAAgB,CAAC;AAAA,gBAAG,gBAAgB,KAAK;AAAA;AAAA,cADzH,UAEG,KAAK;AAAA,eAFK,KAAK,EAGhB,CACH;AAAA,WACD;AAAA,0BACF,MACkG,UADlG;AAAA,YAAQ,MAAK;AAAA,YAAS,WAAU;AAAA,YAAgB,iBAAe;AAAA,YAAc,iBAAe,eAAe,aAAa;AAAA,YACtH,SAAS,MAAM,gBAAgB,CAAC,UAAU,CAAC,KAAK;AAAA,YADlD,UACkG;AAAA,cAA5C;AAAA,cADtD;AAAA,cACuE,QAAQ,SAAS;AAAA,cADxF;AAAA;AAAA,WACkG;AAAA;AAAA,OAClG;AAAA,MACD,gCACC,KAQE,OARF;AAAA,QAAK,IAAI;AAAA,QAAY,WAAU;AAAA,QAAgC,MAAK;AAAA,QAAQ,cAAY;AAAA,QAAxF,UACG,QAAQ,SAAS,IAAI,CAAC,MAAM,0BAC3B,MAIE,UAJF;AAAA,UAAsB,MAAK;AAAA,UAAS,WAAW,UAAU,YAAY,yBAAyB;AAAA,UAC5F,gBAAc,UAAU,YAAY,SAAS;AAAA,UAAW,UAAU,KAAK,cAAc;AAAA,UACrF,SAAS,MAAM;AAAA,YAAE,gBAAgB,KAAK;AAAA,YAAG,gBAAgB,KAAK;AAAA;AAAA,UAFhE,UAIE;AAAA,YADC,KAAK;AAAA,YAHR;AAAA,YAGkB,KAAK;AAAA;AAAA,WAHV,KAAK,EAIhB,CACH;AAAA,OACD;AAAA,MAEH,cAAc,SAAS,SAAS,SAAS;AAAA,sBAC1C,MAIE,OAJF;AAAA,QAAK,WAAU;AAAA,QAAkC,cAAY;AAAA,QAA7D,UAIE;AAAA,0BAHA,KAA2I,UAA3I;AAAA,YAAQ,MAAK;AAAA,YAAS,WAAU;AAAA,YAAgB,UAAU,cAAc;AAAA,YAAG,SAAS,MAAM,gBAAgB,YAAY,CAAC;AAAA,YAAvH,UAA2H;AAAA,WAAgB;AAAA,0BAC3I,MAAsF,QAAtF;AAAA,YAAM,aAAU;AAAA,YAAhB,UAAsF;AAAA,cAA5D,QAAQ;AAAA,cAAlC;AAAA,cAA2C,YAAY;AAAA,cAAvD;AAAA,cAA2D,QAAQ,SAAS;AAAA,cAA5E;AAAA;AAAA,WAAsF;AAAA,0BACtF,KAAgK,UAAhK;AAAA,YAAQ,MAAK;AAAA,YAAS,WAAU;AAAA,YAAgB,UAAU,aAAa,QAAQ,SAAS,SAAS;AAAA,YAAG,SAAS,MAAM,gBAAgB,YAAY,CAAC;AAAA,YAAhJ,UAAoJ;AAAA,WAAY;AAAA;AAAA,OAChK;AAAA;AAAA,GACF;AAAA;",
|
|
12
|
+
"debugId": "F914B781AD4BAB2F64756E2164756E21",
|
|
13
|
+
"names": []
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reader-lifecycle.test.d.ts","sourceRoot":"","sources":["../src/reader-lifecycle.test.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Piège de focus pour une boîte de dialogue modale (WCAG 2.4.3 / 2.1.2).
|
|
3
|
+
*
|
|
4
|
+
* Keeps Tab inside the panel, closes on Escape, and restores focus on exit.
|
|
5
|
+
* A click on non-focusable text moves focus back into the panel on the next Tab.
|
|
6
|
+
*/
|
|
7
|
+
import { type RefObject } from "react";
|
|
8
|
+
export declare function useFocusTrap(panelRef: RefObject<HTMLElement | null>, active: boolean, onEscape?: () => void): void;
|
|
9
|
+
//# sourceMappingURL=use-focus-trap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-focus-trap.d.ts","sourceRoot":"","sources":["../src/use-focus-trap.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AA2B1D,wBAAgB,YAAY,CAC1B,QAAQ,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,EACvC,MAAM,EAAE,OAAO,EACf,QAAQ,CAAC,EAAE,MAAM,IAAI,GACpB,IAAI,CAwFN"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Behaviour shared by `MangaReader` and `DatabookReader`: reading mode, fullscreen, the Swiper
|
|
3
|
+
* handle and the scroll-position sync of the virtualized vertical mode.
|
|
4
|
+
*/
|
|
5
|
+
import { type CSSProperties, type RefObject } from "react";
|
|
6
|
+
import type { Virtualizer } from "@tanstack/react-virtual";
|
|
7
|
+
import type { SwiperClass } from "swiper/react";
|
|
8
|
+
export type ViewMode = "paged" | "vertical";
|
|
9
|
+
/**
|
|
10
|
+
* Above this many pages the thumbnail strip (one DOM node per page, up to ~282 for a whole volume)
|
|
11
|
+
* is too heavy: a compact "go to page" control replaces it.
|
|
12
|
+
*/
|
|
13
|
+
export declare const SHOW_STRIP_MAX = 40;
|
|
14
|
+
/**
|
|
15
|
+
* Fullscreen state of this container. The toggle resolves false on native failure;
|
|
16
|
+
* `fullscreenError` exposes that failure without rejecting a button's event handler.
|
|
17
|
+
*/
|
|
18
|
+
export declare function useFullscreen(containerRef: RefObject<HTMLElement | null>): {
|
|
19
|
+
isFullscreen: boolean;
|
|
20
|
+
fullscreenError: Error | null;
|
|
21
|
+
toggleFullscreen: () => Promise<boolean>;
|
|
22
|
+
};
|
|
23
|
+
/** The Swiper instance and the two callbacks that keep `current` in step with it. */
|
|
24
|
+
export declare function useSwiperSync(setCurrent: (index: number) => void): {
|
|
25
|
+
swiperRef: RefObject<SwiperClass | null>;
|
|
26
|
+
handleSwiper: (sw: SwiperClass) => void;
|
|
27
|
+
handleSlideChange: (sw: SwiperClass) => void;
|
|
28
|
+
};
|
|
29
|
+
/** In vertical mode, `current` follows the page at the middle of the scroll viewport. */
|
|
30
|
+
export declare function useVerticalScrollSync(mode: ViewMode, scrollParentRef: RefObject<HTMLElement | null>, virtualizer: Virtualizer<HTMLDivElement, Element>, setCurrent: (index: number) => void): void;
|
|
31
|
+
/** Absolute placement of one virtual row. */
|
|
32
|
+
export declare const virtualRowStyle: (start: number) => CSSProperties;
|
|
33
|
+
//# sourceMappingURL=use-reader-core.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-reader-core.d.ts","sourceRoot":"","sources":["../src/use-reader-core.ts"],"names":[],"mappings":"AAGA;;;GAGG;AAEH,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,SAAS,EAKf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,UAAU,CAAC;AAE5C;;;GAGG;AACH,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC;;;GAGG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC;;;4BAMxB,OAAO,CAAC,OAAO,CAAC;EAiDhE;AAED,qFAAqF;AACrF,wBAAgB,aAAa,CAAC,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI;;uBAIxD,WAAW;4BAQX,WAAW;EAKnB;AAED,yFAAyF;AACzF,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,QAAQ,EACd,eAAe,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,EAC9C,WAAW,EAAE,WAAW,CAAC,cAAc,EAAE,OAAO,CAAC,EACjD,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,QAepC;AAED,6CAA6C;AAC7C,eAAO,MAAM,eAAe,UAAW,MAAM,KAAG,aAM9C,CAAC"}
|
package/dragon-ball.css
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: Apache-2.0 */
|
|
2
|
+
/* Shenron's live --dbz-* palette drives the reader, including /admin/design overrides. */
|
|
3
|
+
.m3-reader--dragon-ball.m3-reader,
|
|
4
|
+
.m3-reader--dragon-ball .m3-reader {
|
|
5
|
+
--reader-surface: var(--dbz-bg, #0a0a0a);
|
|
6
|
+
--reader-on-surface: var(--dbz-blue-light, #cdcdcd);
|
|
7
|
+
--reader-accent: var(--dbz-orange, #e78220);
|
|
8
|
+
--reader-on-accent: #0a0a0a;
|
|
9
|
+
--reader-outline: var(--dbz-border, #2a2a26);
|
|
10
|
+
--reader-card: var(--dbz-card, #141410);
|
|
11
|
+
--reader-accent-pressed: var(--dbz-orange-dark, #c46f1b);
|
|
12
|
+
--reader-blue: var(--dbz-blue, #1e244d);
|
|
13
|
+
--reader-yellow: var(--dbz-yellow, #fefd03);
|
|
14
|
+
--reader-red: var(--dbz-red, #f10c3b);
|
|
15
|
+
--reader-ember: var(--dbz-ember, var(--reader-accent));
|
|
16
|
+
--reader-ki: var(--dbz-ki, #4ba8ff);
|
|
17
|
+
--reader-amber: var(--dbz-amber, #ffd23f);
|
|
18
|
+
color-scheme: dark;
|
|
19
|
+
font-family: var(--font-sans, system-ui, sans-serif);
|
|
20
|
+
}
|
|
21
|
+
.m3-reader--dragon-ball .m3-reader-container,
|
|
22
|
+
.m3-reader--dragon-ball .m3-reader-collection {
|
|
23
|
+
border: 2px solid var(--reader-outline);
|
|
24
|
+
box-shadow: 4px 4px 0 #000;
|
|
25
|
+
}
|
|
26
|
+
.m3-reader--dragon-ball .m3-reader-hud {
|
|
27
|
+
text-transform: uppercase;
|
|
28
|
+
letter-spacing: .04em;
|
|
29
|
+
box-shadow: 0 2px 0 #000;
|
|
30
|
+
}
|
|
31
|
+
.m3-reader--dragon-ball .m3-reader-btn,
|
|
32
|
+
.m3-reader--dragon-ball .m3-reader-btn-active {
|
|
33
|
+
border-radius: var(--radius, .5rem);
|
|
34
|
+
box-shadow: 2px 2px 0 #000;
|
|
35
|
+
text-transform: uppercase;
|
|
36
|
+
}
|
|
37
|
+
.m3-reader--dragon-ball .m3-reader-btn:active,
|
|
38
|
+
.m3-reader--dragon-ball .m3-reader-btn-active:active { transform: translate(1px, 1px); box-shadow: 1px 1px 0 #000; }
|
|
39
|
+
.m3-reader--dragon-ball .m3-reader-btn-active:hover { background: var(--reader-accent-pressed); }
|
|
40
|
+
.m3-reader--dragon-ball .m3-reader-badge {
|
|
41
|
+
display: inline-flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
justify-content: center;
|
|
44
|
+
transform: skewX(-12.3deg);
|
|
45
|
+
border: 2px solid #000;
|
|
46
|
+
border-radius: .25rem;
|
|
47
|
+
color: var(--reader-on-accent);
|
|
48
|
+
box-shadow: 2px 2px 0 #000;
|
|
49
|
+
font-weight: 800;
|
|
50
|
+
text-transform: uppercase;
|
|
51
|
+
letter-spacing: .08em;
|
|
52
|
+
}
|
|
53
|
+
.m3-reader--dragon-ball .m3-reader-badge > * { transform: skewX(12.3deg); }
|
|
54
|
+
.m3-reader--dragon-ball .m3-reader-scroll {
|
|
55
|
+
scrollbar-width: thin;
|
|
56
|
+
scrollbar-color: var(--reader-accent) #050505;
|
|
57
|
+
}
|
|
58
|
+
.m3-reader--dragon-ball .m3-reader-scroll::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
59
|
+
.m3-reader--dragon-ball .m3-reader-scroll::-webkit-scrollbar-thumb { background: var(--reader-accent); border: 1px solid #000; border-radius: 2px; }
|
|
60
|
+
.m3-reader--dragon-ball .m3-reader-scroll::-webkit-scrollbar-thumb:hover { background: var(--reader-yellow); }
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aphrody/m3-reader",
|
|
3
|
+
"version": "3.3.3",
|
|
4
|
+
"description": "Material 3 manga and illustrated book readers for React",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"src",
|
|
9
|
+
"styles.css",
|
|
10
|
+
"dragon-ball.css"
|
|
11
|
+
],
|
|
12
|
+
"type": "module",
|
|
13
|
+
"sideEffects": [
|
|
14
|
+
"*.css"
|
|
15
|
+
],
|
|
16
|
+
"main": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"default": "./dist/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./source": {
|
|
24
|
+
"types": "./src/index.ts",
|
|
25
|
+
"default": "./src/index.ts"
|
|
26
|
+
},
|
|
27
|
+
"./styles.css": "./styles.css",
|
|
28
|
+
"./dragon-ball.css": "./dragon-ball.css",
|
|
29
|
+
"./use-focus-trap": {
|
|
30
|
+
"types": "./src/use-focus-trap.ts",
|
|
31
|
+
"default": "./src/use-focus-trap.ts"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"publishConfig": {
|
|
35
|
+
"access": "public"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "bun ../../scripts/build.ts build --only $npm_package_name",
|
|
39
|
+
"typecheck": "tsc --noEmit",
|
|
40
|
+
"test": "bun test",
|
|
41
|
+
"typecheck:fast": "tsc --noEmit"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@tanstack/react-virtual": "^3.14.13",
|
|
45
|
+
"lucide-react": "^1.48.0",
|
|
46
|
+
"swiper": "^14.3.0"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@aphrody/m3-config": "3.3.3"
|
|
50
|
+
},
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"react": ">=18.0.0",
|
|
53
|
+
"react-dom": ">=18.0.0",
|
|
54
|
+
"tailwindcss": ">=4.1.0"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"bun": ">=1.4.2"
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { type ReactNode, useEffect, useId, useMemo, useState } from "react";
|
|
4
|
+
|
|
5
|
+
export interface ReaderCollectionChapter {
|
|
6
|
+
id: string | number;
|
|
7
|
+
label: string;
|
|
8
|
+
pageCount: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ReaderCollectionEdition {
|
|
12
|
+
id: string;
|
|
13
|
+
label: string;
|
|
14
|
+
chapters: ReaderCollectionChapter[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface CollectionReaderProps {
|
|
18
|
+
title: string;
|
|
19
|
+
editions: ReaderCollectionEdition[];
|
|
20
|
+
initialEdition?: string;
|
|
21
|
+
initialChapter?: string | number;
|
|
22
|
+
emptyLabel: string;
|
|
23
|
+
contentsLabel: string;
|
|
24
|
+
previousLabel: string;
|
|
25
|
+
nextLabel: string;
|
|
26
|
+
onSelectionChange?: (edition: ReaderCollectionEdition, chapter: ReaderCollectionChapter) => void;
|
|
27
|
+
renderChapter: (edition: ReaderCollectionEdition, chapter: ReaderCollectionChapter, index: number) => ReactNode;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** Edition and chapter navigation shared by manga volumes and illustrated collections. */
|
|
31
|
+
export function CollectionReader({ title, editions, initialEdition, initialChapter, emptyLabel, contentsLabel, previousLabel, nextLabel, onSelectionChange, renderChapter }: CollectionReaderProps) {
|
|
32
|
+
const contentsId = useId();
|
|
33
|
+
const available = useMemo(() => editions.map((edition) => ({ ...edition, chapters: edition.chapters.filter((chapter) => chapter.pageCount > 0) })).filter((edition) => edition.chapters.length > 0), [editions]);
|
|
34
|
+
const [editionId, setEditionId] = useState(() => available.find((edition) => edition.id === initialEdition)?.id ?? available[0]?.id ?? "");
|
|
35
|
+
const edition = available.find((item) => item.id === editionId) ?? available[0];
|
|
36
|
+
const [chapterIndex, setChapterIndex] = useState(() => Math.max(0, edition?.chapters.findIndex((chapter) => String(chapter.id) === String(initialChapter)) ?? 0));
|
|
37
|
+
const [contentsOpen, setContentsOpen] = useState(false);
|
|
38
|
+
const safeIndex = Math.min(chapterIndex, Math.max(0, (edition?.chapters.length ?? 0) - 1));
|
|
39
|
+
const chapter = edition?.chapters[safeIndex];
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (edition && chapter) onSelectionChange?.(edition, chapter);
|
|
43
|
+
}, [edition, chapter, onSelectionChange]);
|
|
44
|
+
|
|
45
|
+
if (!edition || !chapter) {
|
|
46
|
+
return <p className="m3-reader-collection-empty" data-m3-component="CollectionReader" data-testid="collection-reader-empty">{emptyLabel}</p>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return (
|
|
50
|
+
<section className="m3-reader m3-reader-collection" aria-label={title} data-m3-component="CollectionReader" data-testid="collection-reader">
|
|
51
|
+
<div className="m3-reader-collection-toolbar">
|
|
52
|
+
<div className="m3-reader-collection-editions" role="group" aria-label={title}>
|
|
53
|
+
{available.map((item) => (
|
|
54
|
+
<button key={item.id} type="button" className={item.id === edition.id ? "m3-reader-btn-active" : "m3-reader-btn"}
|
|
55
|
+
aria-pressed={item.id === edition.id} onClick={() => { setEditionId(item.id); setChapterIndex(0); setContentsOpen(false); }}>
|
|
56
|
+
{item.label}
|
|
57
|
+
</button>
|
|
58
|
+
))}
|
|
59
|
+
</div>
|
|
60
|
+
<button type="button" className="m3-reader-btn" aria-expanded={contentsOpen} aria-controls={contentsOpen ? contentsId : undefined}
|
|
61
|
+
onClick={() => setContentsOpen((value) => !value)}>{contentsLabel} ({edition.chapters.length})</button>
|
|
62
|
+
</div>
|
|
63
|
+
{contentsOpen && (
|
|
64
|
+
<div id={contentsId} className="m3-reader-collection-contents" role="group" aria-label={contentsLabel}>
|
|
65
|
+
{edition.chapters.map((item, index) => (
|
|
66
|
+
<button key={item.id} type="button" className={index === safeIndex ? "m3-reader-btn-active" : "m3-reader-btn"}
|
|
67
|
+
aria-current={index === safeIndex ? "page" : undefined} disabled={item.pageCount === 0}
|
|
68
|
+
onClick={() => { setChapterIndex(index); setContentsOpen(false); }}>
|
|
69
|
+
{item.label} · {item.pageCount}
|
|
70
|
+
</button>
|
|
71
|
+
))}
|
|
72
|
+
</div>
|
|
73
|
+
)}
|
|
74
|
+
{renderChapter(edition, chapter, safeIndex)}
|
|
75
|
+
<nav className="m3-reader-collection-navigation" aria-label={contentsLabel}>
|
|
76
|
+
<button type="button" className="m3-reader-btn" disabled={safeIndex === 0} onClick={() => setChapterIndex(safeIndex - 1)}>{previousLabel}</button>
|
|
77
|
+
<span aria-live="polite">{chapter.label} ({safeIndex + 1}/{edition.chapters.length})</span>
|
|
78
|
+
<button type="button" className="m3-reader-btn" disabled={safeIndex >= edition.chapters.length - 1} onClick={() => setChapterIndex(safeIndex + 1)}>{nextLabel}</button>
|
|
79
|
+
</nav>
|
|
80
|
+
</section>
|
|
81
|
+
);
|
|
82
|
+
}
|