@atom63/resume 0.2.0 → 0.2.2

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/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/viewer/use-is-mobile.ts","../src/viewer/print.ts","../src/viewer/viewport.ts","../src/viewer/use-resume-viewport.ts","../src/viewer/resume-viewer.tsx"],"names":["useState","useEffect","useRef"],"mappings":"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","file":"index.js","sourcesContent":["import { useEffect, useState } from 'react'\n\n/** True when the viewport is narrow (<= 768px). SSR-safe (defaults false). */\nexport function useIsMobile(breakpointPx = 768): boolean {\n const [isMobile, setIsMobile] = useState(false)\n useEffect(() => {\n const mql = window.matchMedia(`(max-width: ${breakpointPx}px)`)\n const update = () => setIsMobile(mql.matches)\n update()\n mql.addEventListener('change', update)\n return () => mql.removeEventListener('change', update)\n }, [breakpointPx])\n return isMobile\n}\n","/**\n * Build a print-ready clone of the resume content element. The pagination\n * engine already renders real 8.5x11 `[data-resume-page]` frames, so the clone\n * is a plain wrapper holding those frames (no extra page sizing/padding); the\n * print stylesheet maps each frame to a physical page. The hidden measuring\n * layer is dropped so only the real pages print.\n */\nexport function createPrintClone(source: HTMLElement): HTMLElement {\n const clone = source.cloneNode(true) as HTMLElement\n clone.id = 'resume-print-clone'\n clone.style.cssText = 'background:white;color:black'\n clone.querySelector('[data-resume-measure]')?.remove()\n return clone\n}\n\n/** Hide all non-essential body children for printing; returns the hidden elements. */\nexport function hideBodyChildren(): HTMLElement[] {\n const hidden: HTMLElement[] = []\n const preserveTags = new Set(['STYLE', 'LINK', 'SCRIPT'])\n for (const child of Array.from(document.body.children) as HTMLElement[]) {\n if (!preserveTags.has(child.tagName)) {\n child.style.setProperty('display', 'none', 'important')\n hidden.push(child)\n }\n }\n return hidden\n}\n\nexport interface PrintControllerOptions {\n /** The element holding the rendered resume page frames (contentRef.current). */\n getTarget: () => HTMLElement | null\n /** Suggested PDF filename — becomes document.title during print. */\n getFilename: () => string\n /** Whether THIS viewer should own the print (e.g. it requested print, or it's the active/focused view). */\n shouldOwnPrint: () => boolean\n /** Optional: called after print is torn down (e.g. to force a re-render). */\n onAfterPrint?: () => void\n}\n\n/**\n * Register beforeprint/afterprint handlers implementing the clone-and-hide\n * print strategy. Returns an unsubscribe function that removes the listeners.\n */\nexport function registerPrintController(opts: PrintControllerOptions): () => void {\n let hiddenElements: HTMLElement[] = []\n let savedTitle = ''\n\n const beforePrint = () => {\n // Own the print only when this viewer should (its Save-to-PDF button /\n // shortcut requested it, or it's the active/focused view). Otherwise leave\n // it alone so another app isn't cloned/printed underneath, and so this\n // viewer isn't skipped when focus hasn't flushed yet.\n if (!opts.shouldOwnPrint()) return\n const target = opts.getTarget()\n if (!target) return\n savedTitle = document.title\n document.title = opts.getFilename()\n const clone = createPrintClone(target)\n hiddenElements = hideBodyChildren()\n document.body.appendChild(clone)\n }\n\n const afterPrint = () => {\n const clone = document.getElementById('resume-print-clone')\n if (clone) clone.remove()\n document.title = savedTitle\n for (const el of hiddenElements) {\n el.style.removeProperty('display')\n }\n hiddenElements = []\n opts.onAfterPrint?.()\n }\n\n window.addEventListener('beforeprint', beforePrint)\n window.addEventListener('afterprint', afterPrint)\n return () => {\n window.removeEventListener('beforeprint', beforePrint)\n window.removeEventListener('afterprint', afterPrint)\n }\n}\n","export const INTERACTIVE_PAN_SELECTOR = 'a, button, [role=\"button\"], input, textarea, select'\n\nexport const PAN_THRESHOLD_PX = 6\n\nexport function clampZoom(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(value, max))\n}\n\nexport function isInteractivePanTarget(target: EventTarget | null): boolean {\n return target instanceof HTMLElement && Boolean(target.closest(INTERACTIVE_PAN_SELECTOR))\n}\n\nexport function getTouchDistance(touches: TouchList): number {\n if (touches.length < 2) {\n return 0\n }\n\n const first = touches[0]\n const second = touches[1]\n if (!first || !second) {\n return 0\n }\n\n const deltaX = first.clientX - second.clientX\n const deltaY = first.clientY - second.clientY\n return Math.hypot(deltaX, deltaY)\n}\n\nexport function clampScrollPosition(element: HTMLElement): void {\n const maxScrollLeft = Math.max(0, element.scrollWidth - element.clientWidth)\n const maxScrollTop = Math.max(0, element.scrollHeight - element.clientHeight)\n element.scrollLeft = Math.min(Math.max(0, element.scrollLeft), maxScrollLeft)\n element.scrollTop = Math.min(Math.max(0, element.scrollTop), maxScrollTop)\n}\n\nexport function hasExceededPanThreshold(\n startX: number,\n startY: number,\n currentX: number,\n currentY: number,\n threshold = PAN_THRESHOLD_PX\n): boolean {\n return Math.abs(currentX - startX) > threshold || Math.abs(currentY - startY) > threshold\n}\n","// packages/resume/src/viewer/use-resume-viewport.ts\n//\n// Headless viewport engine for a paginated resume: zoom (buttons / Ctrl+wheel /\n// pinch), pan (mouse drag / touch), fit-to-width, focus-aware print + keyboard\n// shortcuts. The single source of truth for this behaviour — both the built-in\n// `ResumeViewer` and bespoke hosts (e.g. an OS window with its own toolbar\n// chrome) consume this hook so the logic is never duplicated.\n//\n// The host owns the markup and supplies the scroll + content refs; the hook\n// wires every listener to them and returns the derived scale, page count, and\n// the toolbar action handlers.\nimport { type RefObject, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'\nimport { getPageGeometry, type PageGeometry, type PageSize, WRAPPER_PADDING_X } from '../geometry'\nimport { registerPrintController } from './print'\nimport {\n clampScrollPosition,\n clampZoom,\n getTouchDistance,\n hasExceededPanThreshold,\n isInteractivePanTarget,\n} from './viewport'\n\nexport const SCALE_STEP = 0.1\nexport const MIN_SCALE = 0.25\nexport const MAX_SCALE = 2\nconst WHEEL_ZOOM_SENSITIVITY = 0.01\nconst WHEEL_ZOOM_CAP = 0.03\n\nconst clampResumeZoom = (value: number) => clampZoom(value, MIN_SCALE, MAX_SCALE)\n\nfunction isInputFocused(): boolean {\n const tag = document.activeElement?.tagName\n return tag === 'INPUT' || tag === 'TEXTAREA'\n}\n\nexport interface UseResumeViewportOptions {\n /** Scroll viewport element (the host attaches this to its scrollable region). */\n scrollRef: RefObject<HTMLDivElement | null>\n /** Element holding the rendered page frames (the print/copy target). */\n contentRef: RefObject<HTMLDivElement | null>\n /** Narrow-viewport flag — drives touch pan/pinch vs mouse drag. */\n isMobile: boolean\n /** Whether this viewport owns global print + keyboard shortcuts. */\n isActive: boolean\n /** Paper size driving the page dimensions + fit-to-width. Default 'letter'. */\n pageSize?: PageSize\n /** Suggested PDF filename (becomes document.title during print). */\n pdfFilename?: string\n /** Called after a successful copy-to-clipboard. */\n onCopy?: () => void\n /** Called when copy-to-clipboard fails. */\n onError?: (error: unknown) => void\n}\n\nexport interface UseResumeViewport {\n /** Current page count (reported by the pagination engine via setPageCount). */\n pageCount: number\n /** Reporter wired into PaginationReportContext. */\n setPageCount: (n: number) => void\n /** The scale actually applied to the page stage (fit-to-width or manual zoom). */\n effectiveScale: number\n /** Whether fit-to-width mode is active. */\n isFitToWidth: boolean\n /** Total stacked page height (unscaled px) — for sizing the page box. */\n totalHeight: number\n /** Resolved geometry for the active page size — for sizing the scaled box. */\n geo: PageGeometry\n handleZoomIn: () => void\n handleZoomOut: () => void\n handleActualSize: () => void\n handleFitToWidth: () => void\n requestPrint: () => void\n handleCopyText: () => Promise<void>\n /** Lower/upper zoom bounds (for disabling +/- controls). */\n minScale: number\n maxScale: number\n}\n\n/**\n * Wire the resume viewport behaviour to host-supplied refs and return the\n * derived state + action handlers. The host renders the scaled page stage; the\n * pagination engine inside it reports its page count through `setPageCount`.\n */\nexport function useResumeViewport({\n scrollRef,\n contentRef,\n isMobile,\n isActive,\n pageSize = 'letter',\n pdfFilename,\n onCopy,\n onError,\n}: UseResumeViewportOptions): UseResumeViewport {\n const geo = getPageGeometry(pageSize)\n\n // Keep the latest active state readable from global listeners (print/keyboard)\n // without re-registering them, so this viewport only hijacks printing when\n // it's the active view (avoids clashing with other apps' print handlers).\n const isActiveRef = useRef(isActive)\n isActiveRef.current = isActive\n\n // Set synchronously right before window.print() so the global beforeprint\n // listener knows THIS viewport initiated the print (active state can lag a click).\n const printRequestedRef = useRef(false)\n const requestPrint = useCallback(() => {\n printRequestedRef.current = true\n window.print()\n }, [])\n\n const zoomAnchorRef = useRef<{\n pointX: number\n pointY: number\n viewportX: number\n viewportY: number\n prevScale: number\n } | null>(null)\n const [pageCount, setPageCount] = useState(1)\n const [zoom, setZoom] = useState(1)\n const [fitScale, setFitScale] = useState(1)\n const [isFitToWidth, setIsFitToWidth] = useState(true)\n const [, forceRender] = useState(0)\n const dragRef = useRef<{\n startX: number\n startY: number\n scrollX: number\n scrollY: number\n } | null>(null)\n const touchPanRef = useRef<{\n scrollX: number\n scrollY: number\n startX: number\n startY: number\n } | null>(null)\n const pinchRef = useRef<{\n startDistance: number\n startScale: number\n } | null>(null)\n\n const effectiveScale = isFitToWidth ? fitScale : zoom\n\n // Refs for stable closures — avoids tearing down/re-registering listeners on every zoom tick\n const effectiveScaleRef = useRef(effectiveScale)\n const isFitToWidthRef = useRef(isFitToWidth)\n const fitScaleRef = useRef(fitScale)\n effectiveScaleRef.current = effectiveScale\n isFitToWidthRef.current = isFitToWidth\n fitScaleRef.current = fitScale\n\n // Keep zoom synced to fitScale while in fit-to-width mode\n useEffect(() => {\n if (isFitToWidth) setZoom(fitScale)\n }, [isFitToWidth, fitScale])\n\n // Responsive scaling — compute fit-to-width from the scroll viewport.\n useEffect(() => {\n const viewport = scrollRef.current\n if (!viewport) return\n\n const updateFitScale = () => {\n const available = Math.max(0, viewport.clientWidth - WRAPPER_PADDING_X)\n setFitScale(clampResumeZoom(Math.min(1, available / geo.widthPx)))\n }\n\n updateFitScale()\n const observer = new ResizeObserver(updateFitScale)\n observer.observe(viewport)\n window.visualViewport?.addEventListener('resize', updateFitScale)\n\n return () => {\n observer.disconnect()\n window.visualViewport?.removeEventListener('resize', updateFitScale)\n }\n }, [scrollRef, geo.widthPx])\n\n const captureViewportCenter = useCallback(() => {\n const el = scrollRef.current\n if (!el) return\n zoomAnchorRef.current = {\n pointX: el.scrollLeft + el.clientWidth / 2,\n pointY: el.scrollTop + el.clientHeight / 2,\n viewportX: el.clientWidth / 2,\n viewportY: el.clientHeight / 2,\n prevScale: effectiveScaleRef.current,\n }\n }, [scrollRef])\n\n const handleZoomIn = useCallback(() => {\n captureViewportCenter()\n setIsFitToWidth(false)\n setZoom(prev => clampResumeZoom(prev + SCALE_STEP))\n }, [captureViewportCenter])\n\n const handleZoomOut = useCallback(() => {\n captureViewportCenter()\n setIsFitToWidth(false)\n setZoom(prev => clampResumeZoom(prev - SCALE_STEP))\n }, [captureViewportCenter])\n\n const handleActualSize = useCallback(() => {\n captureViewportCenter()\n setIsFitToWidth(false)\n setZoom(1)\n }, [captureViewportCenter])\n\n const handleFitToWidth = useCallback(() => {\n setIsFitToWidth(true)\n }, [])\n\n // Ctrl+wheel zoom — rAF-batched for smooth trackpad pinch\n useEffect(() => {\n const el = scrollRef.current\n if (!el) return\n\n let pendingDelta = 0\n let rafId = 0\n let lastCursorX = 0\n let lastCursorY = 0\n\n const flush = () => {\n rafId = 0\n const delta = pendingDelta\n pendingDelta = 0\n const cursorX = lastCursorX\n const cursorY = lastCursorY\n\n setZoom(prev => {\n const next = clampResumeZoom(prev + delta)\n if (next === prev) return prev\n zoomAnchorRef.current = {\n pointX: el.scrollLeft + cursorX,\n pointY: el.scrollTop + cursorY,\n viewportX: cursorX,\n viewportY: cursorY,\n prevScale: isFitToWidthRef.current ? fitScaleRef.current : prev,\n }\n return next\n })\n setIsFitToWidth(false)\n }\n\n const onWheel = (e: WheelEvent) => {\n if (!e.ctrlKey) return\n e.preventDefault()\n\n pendingDelta = Math.max(\n -WHEEL_ZOOM_CAP,\n Math.min(pendingDelta + -e.deltaY * WHEEL_ZOOM_SENSITIVITY, WHEEL_ZOOM_CAP)\n )\n const rect = el.getBoundingClientRect()\n lastCursorX = e.clientX - rect.left\n lastCursorY = e.clientY - rect.top\n\n if (rafId === 0) {\n rafId = requestAnimationFrame(flush)\n }\n }\n\n el.addEventListener('wheel', onWheel, { passive: false })\n return () => {\n el.removeEventListener('wheel', onWheel)\n if (rafId !== 0) cancelAnimationFrame(rafId)\n }\n }, [scrollRef])\n\n // Scroll anchoring — adjust scroll position after zoom so the focal point stays put\n useLayoutEffect(() => {\n const anchor = zoomAnchorRef.current\n if (!anchor) return\n const el = scrollRef.current\n if (!el) return\n\n const ratio = effectiveScale / anchor.prevScale\n el.scrollLeft = anchor.pointX * ratio - anchor.viewportX\n el.scrollTop = anchor.pointY * ratio - anchor.viewportY\n zoomAnchorRef.current = null\n }, [effectiveScale, scrollRef])\n\n // Print handling — clone-and-hide strategy, gated on this viewport owning the print.\n useEffect(\n () =>\n registerPrintController({\n getTarget: () => contentRef.current,\n getFilename: () => pdfFilename ?? 'resume',\n shouldOwnPrint: () => printRequestedRef.current || isActiveRef.current,\n onAfterPrint: () => {\n printRequestedRef.current = false\n forceRender(n => n + 1)\n },\n }),\n [pdfFilename, contentRef]\n )\n\n // Keyboard shortcuts\n useEffect(() => {\n const keyActions: Record<string, () => void> = {\n '+': handleZoomIn,\n '=': handleZoomIn,\n '-': handleZoomOut,\n _: handleZoomOut,\n }\n const modKeyActions: Record<string, () => void> = {\n '0': handleFitToWidth,\n '1': handleActualSize,\n p: requestPrint,\n }\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (!isActiveRef.current || isInputFocused()) return\n const mod = e.ctrlKey || e.metaKey\n const action = mod ? modKeyActions[e.key] : keyActions[e.key]\n if (action) {\n e.preventDefault()\n action()\n }\n }\n\n globalThis.addEventListener('keydown', handleKeyDown)\n return () => globalThis.removeEventListener('keydown', handleKeyDown)\n }, [handleZoomIn, handleZoomOut, handleFitToWidth, handleActualSize, requestPrint])\n\n // Click-and-drag panning (hand tool). Mobile uses touch pan + pinch-to-zoom.\n useEffect(() => {\n const el = scrollRef.current\n if (!el) return\n\n const setPanActive = (active: boolean) => {\n if (isMobile) {\n el.dataset.panActive = active ? '' : undefined\n }\n }\n\n const moveDrag = (clientX: number, clientY: number) => {\n const drag = dragRef.current\n if (!drag) return\n el.scrollLeft = drag.scrollX - (clientX - drag.startX)\n el.scrollTop = drag.scrollY - (clientY - drag.startY)\n clampScrollPosition(el)\n }\n\n const beginDrag = (clientX: number, clientY: number) => {\n dragRef.current = {\n startX: clientX,\n startY: clientY,\n scrollX: el.scrollLeft,\n scrollY: el.scrollTop,\n }\n setPanActive(true)\n if (!isMobile) {\n el.classList.replace('resume-viewer-scroll-grab', 'resume-viewer-scroll-grabbing')\n el.classList.replace('cursor-grab', 'cursor-grabbing')\n }\n }\n\n const endDrag = () => {\n touchPanRef.current = null\n pinchRef.current = null\n if (!dragRef.current) return\n dragRef.current = null\n setPanActive(false)\n if (!isMobile) {\n el.classList.replace('resume-viewer-scroll-grabbing', 'resume-viewer-scroll-grab')\n el.classList.replace('cursor-grabbing', 'cursor-grab')\n }\n }\n\n const captureZoomAnchor = (clientX: number, clientY: number) => {\n const rect = el.getBoundingClientRect()\n zoomAnchorRef.current = {\n pointX: el.scrollLeft + clientX - rect.left,\n pointY: el.scrollTop + clientY - rect.top,\n viewportX: clientX - rect.left,\n viewportY: clientY - rect.top,\n prevScale: effectiveScaleRef.current,\n }\n }\n\n const onMouseDown = (e: MouseEvent) => {\n if (isMobile || e.button !== 0) return\n if (isInteractivePanTarget(e.target)) return\n\n beginDrag(e.clientX, e.clientY)\n e.preventDefault()\n }\n\n const onMouseMove = (e: MouseEvent) => {\n if (isMobile) return\n moveDrag(e.clientX, e.clientY)\n }\n\n const onTouchStart = (e: TouchEvent) => {\n if (!isMobile) return\n\n if (e.touches.length === 2) {\n touchPanRef.current = null\n dragRef.current = null\n const distance = getTouchDistance(e.touches)\n if (distance <= 0) return\n pinchRef.current = {\n startDistance: distance,\n startScale: effectiveScaleRef.current,\n }\n setIsFitToWidth(false)\n return\n }\n\n if (e.touches.length !== 1 || pinchRef.current) return\n if (isInteractivePanTarget(e.target)) return\n\n const touch = e.touches[0]\n if (!touch) return\n touchPanRef.current = {\n startX: touch.clientX,\n startY: touch.clientY,\n scrollX: el.scrollLeft,\n scrollY: el.scrollTop,\n }\n }\n\n const onTouchMove = (e: TouchEvent) => {\n if (!isMobile) return\n\n if (pinchRef.current && e.touches.length === 2) {\n const distance = getTouchDistance(e.touches)\n if (distance <= 0 || pinchRef.current.startDistance <= 0) return\n\n const first = e.touches[0]\n const second = e.touches[1]\n if (!first || !second) return\n\n const centerX = (first.clientX + second.clientX) / 2\n const centerY = (first.clientY + second.clientY) / 2\n const nextScale = clampResumeZoom(\n pinchRef.current.startScale * (distance / pinchRef.current.startDistance)\n )\n\n captureZoomAnchor(centerX, centerY)\n setZoom(nextScale)\n setIsFitToWidth(false)\n e.preventDefault()\n return\n }\n\n const pendingPan = touchPanRef.current\n const touch = e.touches[0]\n if (!pendingPan || !touch) return\n\n if (!dragRef.current) {\n if (\n !hasExceededPanThreshold(\n pendingPan.startX,\n pendingPan.startY,\n touch.clientX,\n touch.clientY\n )\n ) {\n return\n }\n\n dragRef.current = {\n startX: pendingPan.startX,\n startY: pendingPan.startY,\n scrollX: pendingPan.scrollX,\n scrollY: pendingPan.scrollY,\n }\n setPanActive(true)\n }\n\n moveDrag(touch.clientX, touch.clientY)\n e.preventDefault()\n }\n\n el.addEventListener('mousedown', onMouseDown)\n window.addEventListener('mousemove', onMouseMove)\n window.addEventListener('mouseup', endDrag)\n\n if (isMobile) {\n el.addEventListener('touchstart', onTouchStart, { passive: true })\n el.addEventListener('touchmove', onTouchMove, { passive: false })\n el.addEventListener('touchend', endDrag)\n el.addEventListener('touchcancel', endDrag)\n }\n\n return () => {\n el.removeEventListener('mousedown', onMouseDown)\n window.removeEventListener('mousemove', onMouseMove)\n window.removeEventListener('mouseup', endDrag)\n delete el.dataset.panActive\n if (isMobile) {\n el.removeEventListener('touchstart', onTouchStart)\n el.removeEventListener('touchmove', onTouchMove)\n el.removeEventListener('touchend', endDrag)\n el.removeEventListener('touchcancel', endDrag)\n }\n }\n }, [isMobile, scrollRef])\n\n const handleCopyText = useCallback(async () => {\n const content = contentRef.current\n if (!content) return\n try {\n await navigator.clipboard.writeText(content.innerText)\n onCopy?.()\n } catch (error) {\n onError?.(error)\n }\n }, [contentRef, onCopy, onError])\n\n const totalHeight = pageCount * geo.heightPx + Math.max(0, pageCount - 1) * geo.gapPx\n\n return {\n pageCount,\n setPageCount,\n effectiveScale,\n isFitToWidth,\n totalHeight,\n geo,\n handleZoomIn,\n handleZoomOut,\n handleActualSize,\n handleFitToWidth,\n requestPrint,\n handleCopyText,\n minScale: MIN_SCALE,\n maxScale: MAX_SCALE,\n }\n}\n","import { MDXProvider } from '@mdx-js/react'\nimport clsx from 'clsx'\nimport { ClipboardCopy, Maximize2, Minus, Plus } from 'lucide-react'\nimport { useRef } from 'react'\nimport { resumeMdxComponents } from '../document/components'\nimport {\n PaginationReportContext,\n ResumeFontFamilyContext,\n ResumePageSizeContext,\n} from '../document/pagination-context'\nimport '../styles/tokens.css'\nimport '../styles/document.css'\nimport '../styles/viewer.css'\nimport type { ResumeViewerProps } from './resume-viewer.types'\nimport { useIsMobile } from './use-is-mobile'\nimport { useResumeViewport } from './use-resume-viewport'\n\nexport function ResumeViewer({\n Content,\n components,\n fontFamily,\n isActive = true,\n pageSize = 'letter',\n pdfFilename,\n onCopy,\n onError,\n className,\n toolbarStart,\n toolbarEnd,\n}: ResumeViewerProps) {\n const isMobile = useIsMobile()\n const scrollRef = useRef<HTMLDivElement>(null)\n const contentRef = useRef<HTMLDivElement>(null)\n\n const {\n pageCount,\n setPageCount,\n effectiveScale,\n isFitToWidth,\n totalHeight,\n geo,\n handleZoomIn,\n handleZoomOut,\n handleActualSize,\n handleFitToWidth,\n requestPrint,\n handleCopyText,\n minScale,\n maxScale,\n } = useResumeViewport({\n scrollRef,\n contentRef,\n isMobile,\n isActive,\n pageSize,\n pdfFilename,\n onCopy,\n onError,\n })\n\n return (\n <div className=\"resume-viewer\">\n {/* Drives the printed paper size; @page can't read a CSS custom property. */}\n <style>{`@page { size: ${geo.cssPageSize}; margin: 0; }`}</style>\n <div className=\"resume-viewer-toolbar\">\n {toolbarStart}\n <span className=\"resume-viewer-pagecount\">\n {pageCount} page{pageCount > 1 ? 's' : ''}\n </span>\n <div className=\"resume-viewer-controls\">\n <button\n aria-label=\"Fit resume to width\"\n aria-pressed={isFitToWidth}\n className={clsx('resume-viewer-btn', isFitToWidth && 'resume-viewer-btn-active')}\n onClick={handleFitToWidth}\n title=\"Fit to width\"\n type=\"button\"\n >\n <Maximize2 className=\"resume-viewer-icon\" />\n </button>\n <div className=\"resume-viewer-btn-group\">\n <button\n aria-label=\"Zoom out\"\n className=\"resume-viewer-btn\"\n disabled={!isFitToWidth && effectiveScale <= minScale}\n onClick={handleZoomOut}\n title=\"Zoom out\"\n type=\"button\"\n >\n <Minus className=\"resume-viewer-icon\" />\n </button>\n <button\n className=\"resume-viewer-zoom-label\"\n onClick={handleActualSize}\n title=\"Actual size (100%)\"\n type=\"button\"\n >\n {Math.round(effectiveScale * 100)}%\n </button>\n <button\n aria-label=\"Zoom in\"\n className=\"resume-viewer-btn\"\n disabled={effectiveScale >= maxScale}\n onClick={handleZoomIn}\n title=\"Zoom in\"\n type=\"button\"\n >\n <Plus className=\"resume-viewer-icon\" />\n </button>\n </div>\n <button\n aria-label=\"Copy resume text\"\n className=\"resume-viewer-btn\"\n onClick={handleCopyText}\n title=\"Copy resume text\"\n type=\"button\"\n >\n <ClipboardCopy className=\"resume-viewer-icon\" />\n </button>\n <button\n className=\"resume-viewer-btn resume-viewer-btn-text\"\n onClick={requestPrint}\n type=\"button\"\n >\n Save to PDF\n </button>\n {toolbarEnd}\n </div>\n </div>\n\n <div\n className={clsx(\n 'resume-viewer-scroll',\n isMobile ? 'resume-viewer-scroll-mobile' : 'resume-viewer-scroll-grab',\n className\n )}\n ref={scrollRef}\n >\n <section\n aria-label={\n isMobile\n ? 'Resume preview. Drag with one finger to pan, pinch to zoom.'\n : 'Resume preview. Drag to pan, Ctrl+scroll to zoom.'\n }\n className=\"resume-viewer-stage\"\n >\n <div\n className=\"resume-viewer-page-box\"\n style={{\n width: geo.widthPx * effectiveScale,\n height: totalHeight * effectiveScale,\n }}\n >\n <div\n className=\"resume-viewer-scale-root\"\n data-resume-scale-root=\"\"\n style={{\n width: geo.widthPx,\n height: totalHeight,\n transform: effectiveScale === 1 ? undefined : `scale(${effectiveScale})`,\n }}\n >\n <ResumePageSizeContext.Provider value={pageSize}>\n <ResumeFontFamilyContext.Provider value={fontFamily}>\n <PaginationReportContext.Provider value={setPageCount}>\n <MDXProvider components={components ?? resumeMdxComponents}>\n <div\n className=\"resume-viewer-content\"\n id=\"resume-print-target\"\n ref={contentRef}\n style={{ zIndex: 1 }}\n >\n <Content />\n </div>\n </MDXProvider>\n </PaginationReportContext.Provider>\n </ResumeFontFamilyContext.Provider>\n </ResumePageSizeContext.Provider>\n </div>\n </div>\n </section>\n </div>\n </div>\n )\n}\n"]}
1
+ {"version":3,"sources":["../src/viewer/use-is-mobile.ts","../src/viewer/print.ts","../src/viewer/viewport.ts","../src/viewer/use-resume-viewport.ts","../src/viewer/resume-viewer.tsx"],"names":["useState","useEffect","useRef"],"mappings":";;;;;;;;AAGO,SAAS,WAAA,CAAY,eAAe,GAAA,EAAc;AACvD,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAI,SAAS,KAAK,CAAA;AAC9C,EAAA,SAAA,CAAU,MAAM;AACd,IAAA,MAAM,GAAA,GAAM,MAAA,CAAO,UAAA,CAAW,CAAA,YAAA,EAAe,YAAY,CAAA,GAAA,CAAK,CAAA;AAC9D,IAAA,MAAM,MAAA,GAAS,MAAM,WAAA,CAAY,GAAA,CAAI,OAAO,CAAA;AAC5C,IAAA,MAAA,EAAO;AACP,IAAA,GAAA,CAAI,gBAAA,CAAiB,UAAU,MAAM,CAAA;AACrC,IAAA,OAAO,MAAM,GAAA,CAAI,mBAAA,CAAoB,QAAA,EAAU,MAAM,CAAA;AAAA,EACvD,CAAA,EAAG,CAAC,YAAY,CAAC,CAAA;AACjB,EAAA,OAAO,QAAA;AACT;;;ACNO,SAAS,iBAAiB,MAAA,EAAkC;AACjE,EAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,SAAA,CAAU,IAAI,CAAA;AACnC,EAAA,KAAA,CAAM,EAAA,GAAK,oBAAA;AACX,EAAA,KAAA,CAAM,MAAM,OAAA,GAAU,8BAAA;AACtB,EAAA,KAAA,CAAM,aAAA,CAAc,uBAAuB,CAAA,EAAG,MAAA,EAAO;AACrD,EAAA,OAAO,KAAA;AACT;AAGO,SAAS,gBAAA,GAAkC;AAChD,EAAA,MAAM,SAAwB,EAAC;AAC/B,EAAA,MAAM,+BAAe,IAAI,GAAA,CAAI,CAAC,OAAA,EAAS,MAAA,EAAQ,QAAQ,CAAC,CAAA;AACxD,EAAA,KAAA,MAAW,SAAS,KAAA,CAAM,IAAA,CAAK,QAAA,CAAS,IAAA,CAAK,QAAQ,CAAA,EAAoB;AACvE,IAAA,IAAI,CAAC,YAAA,CAAa,GAAA,CAAI,KAAA,CAAM,OAAO,CAAA,EAAG;AACpC,MAAA,KAAA,CAAM,KAAA,CAAM,WAAA,CAAY,SAAA,EAAW,MAAA,EAAQ,WAAW,CAAA;AACtD,MAAA,MAAA,CAAO,KAAK,KAAK,CAAA;AAAA,IACnB;AAAA,EACF;AACA,EAAA,OAAO,MAAA;AACT;AAiBO,SAAS,wBAAwB,IAAA,EAA0C;AAChF,EAAA,IAAI,iBAAgC,EAAC;AACrC,EAAA,IAAI,UAAA,GAAa,EAAA;AAEjB,EAAA,MAAM,cAAc,MAAM;AAKxB,IAAA,IAAI,CAAC,IAAA,CAAK,cAAA,EAAe,EAAG;AAC5B,IAAA,MAAM,MAAA,GAAS,KAAK,SAAA,EAAU;AAC9B,IAAA,IAAI,CAAC,MAAA,EAAQ;AACb,IAAA,UAAA,GAAa,QAAA,CAAS,KAAA;AACtB,IAAA,QAAA,CAAS,KAAA,GAAQ,KAAK,WAAA,EAAY;AAClC,IAAA,MAAM,KAAA,GAAQ,iBAAiB,MAAM,CAAA;AACrC,IAAA,cAAA,GAAiB,gBAAA,EAAiB;AAClC,IAAA,QAAA,CAAS,IAAA,CAAK,YAAY,KAAK,CAAA;AAAA,EACjC,CAAA;AAEA,EAAA,MAAM,aAAa,MAAM;AACvB,IAAA,MAAM,KAAA,GAAQ,QAAA,CAAS,cAAA,CAAe,oBAAoB,CAAA;AAC1D,IAAA,IAAI,KAAA,QAAa,MAAA,EAAO;AACxB,IAAA,QAAA,CAAS,KAAA,GAAQ,UAAA;AACjB,IAAA,KAAA,MAAW,MAAM,cAAA,EAAgB;AAC/B,MAAA,EAAA,CAAG,KAAA,CAAM,eAAe,SAAS,CAAA;AAAA,IACnC;AACA,IAAA,cAAA,GAAiB,EAAC;AAClB,IAAA,IAAA,CAAK,YAAA,IAAe;AAAA,EACtB,CAAA;AAEA,EAAA,MAAA,CAAO,gBAAA,CAAiB,eAAe,WAAW,CAAA;AAClD,EAAA,MAAA,CAAO,gBAAA,CAAiB,cAAc,UAAU,CAAA;AAChD,EAAA,OAAO,MAAM;AACX,IAAA,MAAA,CAAO,mBAAA,CAAoB,eAAe,WAAW,CAAA;AACrD,IAAA,MAAA,CAAO,mBAAA,CAAoB,cAAc,UAAU,CAAA;AAAA,EACrD,CAAA;AACF;;;AC/EO,IAAM,wBAAA,GAA2B,qDAAA;AAEjC,IAAM,gBAAA,GAAmB,CAAA;AAEzB,SAAS,SAAA,CAAU,KAAA,EAAe,GAAA,EAAa,GAAA,EAAqB;AACzE,EAAA,OAAO,KAAK,GAAA,CAAI,GAAA,EAAK,KAAK,GAAA,CAAI,KAAA,EAAO,GAAG,CAAC,CAAA;AAC3C;AAEO,SAAS,uBAAuB,MAAA,EAAqC;AAC1E,EAAA,OAAO,kBAAkB,WAAA,IAAe,OAAA,CAAQ,MAAA,CAAO,OAAA,CAAQ,wBAAwB,CAAC,CAAA;AAC1F;AAEO,SAAS,iBAAiB,OAAA,EAA4B;AAC3D,EAAA,IAAI,OAAA,CAAQ,SAAS,CAAA,EAAG;AACtB,IAAA,OAAO,CAAA;AAAA,EACT;AAEA,EAAA,MAAM,KAAA,GAAQ,QAAQ,CAAC,CAAA;AACvB,EAAA,MAAM,MAAA,GAAS,QAAQ,CAAC,CAAA;AACxB,EAAA,IAAI,CAAC,KAAA,IAAS,CAAC,MAAA,EAAQ;AACrB,IAAA,OAAO,CAAA;AAAA,EACT;AAEA,EAAA,MAAM,MAAA,GAAS,KAAA,CAAM,OAAA,GAAU,MAAA,CAAO,OAAA;AACtC,EAAA,MAAM,MAAA,GAAS,KAAA,CAAM,OAAA,GAAU,MAAA,CAAO,OAAA;AACtC,EAAA,OAAO,IAAA,CAAK,KAAA,CAAM,MAAA,EAAQ,MAAM,CAAA;AAClC;AAEO,SAAS,oBAAoB,OAAA,EAA4B;AAC9D,EAAA,MAAM,gBAAgB,IAAA,CAAK,GAAA,CAAI,GAAG,OAAA,CAAQ,WAAA,GAAc,QAAQ,WAAW,CAAA;AAC3E,EAAA,MAAM,eAAe,IAAA,CAAK,GAAA,CAAI,GAAG,OAAA,CAAQ,YAAA,GAAe,QAAQ,YAAY,CAAA;AAC5E,EAAA,OAAA,CAAQ,UAAA,GAAa,KAAK,GAAA,CAAI,IAAA,CAAK,IAAI,CAAA,EAAG,OAAA,CAAQ,UAAU,CAAA,EAAG,aAAa,CAAA;AAC5E,EAAA,OAAA,CAAQ,SAAA,GAAY,KAAK,GAAA,CAAI,IAAA,CAAK,IAAI,CAAA,EAAG,OAAA,CAAQ,SAAS,CAAA,EAAG,YAAY,CAAA;AAC3E;AAEO,SAAS,wBACd,MAAA,EACA,MAAA,EACA,QAAA,EACA,QAAA,EACA,YAAY,gBAAA,EACH;AACT,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,QAAA,GAAW,MAAM,CAAA,GAAI,aAAa,IAAA,CAAK,GAAA,CAAI,QAAA,GAAW,MAAM,CAAA,GAAI,SAAA;AAClF;;;ACrBO,IAAM,UAAA,GAAa;AACnB,IAAM,SAAA,GAAY;AAClB,IAAM,SAAA,GAAY;AACzB,IAAM,sBAAA,GAAyB,IAAA;AAC/B,IAAM,cAAA,GAAiB,IAAA;AAEvB,IAAM,kBAAkB,CAAC,KAAA,KAAkB,SAAA,CAAU,KAAA,EAAO,WAAW,SAAS,CAAA;AAEhF,SAAS,cAAA,GAA0B;AACjC,EAAA,MAAM,GAAA,GAAM,SAAS,aAAA,EAAe,OAAA;AACpC,EAAA,OAAO,GAAA,KAAQ,WAAW,GAAA,KAAQ,UAAA;AACpC;AAkDO,SAAS,iBAAA,CAAkB;AAAA,EAChC,SAAA;AAAA,EACA,UAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA;AAAA,EACA,QAAA,GAAW,QAAA;AAAA,EACX,WAAA;AAAA,EACA,MAAA;AAAA,EACA;AACF,CAAA,EAAgD;AAC9C,EAAA,MAAM,GAAA,GAAM,gBAAgB,QAAQ,CAAA;AAKpC,EAAA,MAAM,WAAA,GAAc,OAAO,QAAQ,CAAA;AACnC,EAAA,WAAA,CAAY,OAAA,GAAU,QAAA;AAItB,EAAA,MAAM,iBAAA,GAAoB,OAAO,KAAK,CAAA;AACtC,EAAA,MAAM,YAAA,GAAe,YAAY,MAAM;AACrC,IAAA,iBAAA,CAAkB,OAAA,GAAU,IAAA;AAC5B,IAAA,MAAA,CAAO,KAAA,EAAM;AAAA,EACf,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,aAAA,GAAgB,OAMZ,IAAI,CAAA;AACd,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAIA,SAAS,CAAC,CAAA;AAC5C,EAAA,MAAM,CAAC,IAAA,EAAM,OAAO,CAAA,GAAIA,SAAS,CAAC,CAAA;AAClC,EAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAIA,SAAS,CAAC,CAAA;AAC1C,EAAA,MAAM,CAAC,YAAA,EAAc,eAAe,CAAA,GAAIA,SAAS,IAAI,CAAA;AACrD,EAAA,MAAM,GAAG,WAAW,CAAA,GAAIA,SAAS,CAAC,CAAA;AAClC,EAAA,MAAM,OAAA,GAAU,OAKN,IAAI,CAAA;AACd,EAAA,MAAM,WAAA,GAAc,OAKV,IAAI,CAAA;AACd,EAAA,MAAM,QAAA,GAAW,OAGP,IAAI,CAAA;AAEd,EAAA,MAAM,cAAA,GAAiB,eAAe,QAAA,GAAW,IAAA;AAGjD,EAAA,MAAM,iBAAA,GAAoB,OAAO,cAAc,CAAA;AAC/C,EAAA,MAAM,eAAA,GAAkB,OAAO,YAAY,CAAA;AAC3C,EAAA,MAAM,WAAA,GAAc,OAAO,QAAQ,CAAA;AACnC,EAAA,iBAAA,CAAkB,OAAA,GAAU,cAAA;AAC5B,EAAA,eAAA,CAAgB,OAAA,GAAU,YAAA;AAC1B,EAAA,WAAA,CAAY,OAAA,GAAU,QAAA;AAGtB,EAAAC,UAAU,MAAM;AACd,IAAA,IAAI,YAAA,UAAsB,QAAQ,CAAA;AAAA,EACpC,CAAA,EAAG,CAAC,YAAA,EAAc,QAAQ,CAAC,CAAA;AAG3B,EAAAA,UAAU,MAAM;AACd,IAAA,MAAM,WAAW,SAAA,CAAU,OAAA;AAC3B,IAAA,IAAI,CAAC,QAAA,EAAU;AAEf,IAAA,MAAM,iBAAiB,MAAM;AAC3B,MAAA,MAAM,YAAY,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,QAAA,CAAS,cAAc,iBAAiB,CAAA;AACtE,MAAA,WAAA,CAAY,eAAA,CAAgB,KAAK,GAAA,CAAI,CAAA,EAAG,YAAY,GAAA,CAAI,OAAO,CAAC,CAAC,CAAA;AAAA,IACnE,CAAA;AAEA,IAAA,cAAA,EAAe;AACf,IAAA,MAAM,QAAA,GAAW,IAAI,cAAA,CAAe,cAAc,CAAA;AAClD,IAAA,QAAA,CAAS,QAAQ,QAAQ,CAAA;AACzB,IAAA,MAAA,CAAO,cAAA,EAAgB,gBAAA,CAAiB,QAAA,EAAU,cAAc,CAAA;AAEhE,IAAA,OAAO,MAAM;AACX,MAAA,QAAA,CAAS,UAAA,EAAW;AACpB,MAAA,MAAA,CAAO,cAAA,EAAgB,mBAAA,CAAoB,QAAA,EAAU,cAAc,CAAA;AAAA,IACrE,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,SAAA,EAAW,GAAA,CAAI,OAAO,CAAC,CAAA;AAE3B,EAAA,MAAM,qBAAA,GAAwB,YAAY,MAAM;AAC9C,IAAA,MAAM,KAAK,SAAA,CAAU,OAAA;AACrB,IAAA,IAAI,CAAC,EAAA,EAAI;AACT,IAAA,aAAA,CAAc,OAAA,GAAU;AAAA,MACtB,MAAA,EAAQ,EAAA,CAAG,UAAA,GAAa,EAAA,CAAG,WAAA,GAAc,CAAA;AAAA,MACzC,MAAA,EAAQ,EAAA,CAAG,SAAA,GAAY,EAAA,CAAG,YAAA,GAAe,CAAA;AAAA,MACzC,SAAA,EAAW,GAAG,WAAA,GAAc,CAAA;AAAA,MAC5B,SAAA,EAAW,GAAG,YAAA,GAAe,CAAA;AAAA,MAC7B,WAAW,iBAAA,CAAkB;AAAA,KAC/B;AAAA,EACF,CAAA,EAAG,CAAC,SAAS,CAAC,CAAA;AAEd,EAAA,MAAM,YAAA,GAAe,YAAY,MAAM;AACrC,IAAA,qBAAA,EAAsB;AACtB,IAAA,eAAA,CAAgB,KAAK,CAAA;AACrB,IAAA,OAAA,CAAQ,CAAA,IAAA,KAAQ,eAAA,CAAgB,IAAA,GAAO,UAAU,CAAC,CAAA;AAAA,EACpD,CAAA,EAAG,CAAC,qBAAqB,CAAC,CAAA;AAE1B,EAAA,MAAM,aAAA,GAAgB,YAAY,MAAM;AACtC,IAAA,qBAAA,EAAsB;AACtB,IAAA,eAAA,CAAgB,KAAK,CAAA;AACrB,IAAA,OAAA,CAAQ,CAAA,IAAA,KAAQ,eAAA,CAAgB,IAAA,GAAO,UAAU,CAAC,CAAA;AAAA,EACpD,CAAA,EAAG,CAAC,qBAAqB,CAAC,CAAA;AAE1B,EAAA,MAAM,gBAAA,GAAmB,YAAY,MAAM;AACzC,IAAA,qBAAA,EAAsB;AACtB,IAAA,eAAA,CAAgB,KAAK,CAAA;AACrB,IAAA,OAAA,CAAQ,CAAC,CAAA;AAAA,EACX,CAAA,EAAG,CAAC,qBAAqB,CAAC,CAAA;AAE1B,EAAA,MAAM,gBAAA,GAAmB,YAAY,MAAM;AACzC,IAAA,eAAA,CAAgB,IAAI,CAAA;AAAA,EACtB,CAAA,EAAG,EAAE,CAAA;AAGL,EAAAA,UAAU,MAAM;AACd,IAAA,MAAM,KAAK,SAAA,CAAU,OAAA;AACrB,IAAA,IAAI,CAAC,EAAA,EAAI;AAET,IAAA,IAAI,YAAA,GAAe,CAAA;AACnB,IAAA,IAAI,KAAA,GAAQ,CAAA;AACZ,IAAA,IAAI,WAAA,GAAc,CAAA;AAClB,IAAA,IAAI,WAAA,GAAc,CAAA;AAElB,IAAA,MAAM,QAAQ,MAAM;AAClB,MAAA,KAAA,GAAQ,CAAA;AACR,MAAA,MAAM,KAAA,GAAQ,YAAA;AACd,MAAA,YAAA,GAAe,CAAA;AACf,MAAA,MAAM,OAAA,GAAU,WAAA;AAChB,MAAA,MAAM,OAAA,GAAU,WAAA;AAEhB,MAAA,OAAA,CAAQ,CAAA,IAAA,KAAQ;AACd,QAAA,MAAM,IAAA,GAAO,eAAA,CAAgB,IAAA,GAAO,KAAK,CAAA;AACzC,QAAA,IAAI,IAAA,KAAS,MAAM,OAAO,IAAA;AAC1B,QAAA,aAAA,CAAc,OAAA,GAAU;AAAA,UACtB,MAAA,EAAQ,GAAG,UAAA,GAAa,OAAA;AAAA,UACxB,MAAA,EAAQ,GAAG,SAAA,GAAY,OAAA;AAAA,UACvB,SAAA,EAAW,OAAA;AAAA,UACX,SAAA,EAAW,OAAA;AAAA,UACX,SAAA,EAAW,eAAA,CAAgB,OAAA,GAAU,WAAA,CAAY,OAAA,GAAU;AAAA,SAC7D;AACA,QAAA,OAAO,IAAA;AAAA,MACT,CAAC,CAAA;AACD,MAAA,eAAA,CAAgB,KAAK,CAAA;AAAA,IACvB,CAAA;AAEA,IAAA,MAAM,OAAA,GAAU,CAAC,CAAA,KAAkB;AACjC,MAAA,IAAI,CAAC,EAAE,OAAA,EAAS;AAChB,MAAA,CAAA,CAAE,cAAA,EAAe;AAEjB,MAAA,YAAA,GAAe,IAAA,CAAK,GAAA;AAAA,QAClB,CAAC,cAAA;AAAA,QACD,KAAK,GAAA,CAAI,YAAA,GAAe,CAAC,CAAA,CAAE,MAAA,GAAS,wBAAwB,cAAc;AAAA,OAC5E;AACA,MAAA,MAAM,IAAA,GAAO,GAAG,qBAAA,EAAsB;AACtC,MAAA,WAAA,GAAc,CAAA,CAAE,UAAU,IAAA,CAAK,IAAA;AAC/B,MAAA,WAAA,GAAc,CAAA,CAAE,UAAU,IAAA,CAAK,GAAA;AAE/B,MAAA,IAAI,UAAU,CAAA,EAAG;AACf,QAAA,KAAA,GAAQ,sBAAsB,KAAK,CAAA;AAAA,MACrC;AAAA,IACF,CAAA;AAEA,IAAA,EAAA,CAAG,iBAAiB,OAAA,EAAS,OAAA,EAAS,EAAE,OAAA,EAAS,OAAO,CAAA;AACxD,IAAA,OAAO,MAAM;AACX,MAAA,EAAA,CAAG,mBAAA,CAAoB,SAAS,OAAO,CAAA;AACvC,MAAA,IAAI,KAAA,KAAU,CAAA,EAAG,oBAAA,CAAqB,KAAK,CAAA;AAAA,IAC7C,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,SAAS,CAAC,CAAA;AAGd,EAAA,eAAA,CAAgB,MAAM;AACpB,IAAA,MAAM,SAAS,aAAA,CAAc,OAAA;AAC7B,IAAA,IAAI,CAAC,MAAA,EAAQ;AACb,IAAA,MAAM,KAAK,SAAA,CAAU,OAAA;AACrB,IAAA,IAAI,CAAC,EAAA,EAAI;AAET,IAAA,MAAM,KAAA,GAAQ,iBAAiB,MAAA,CAAO,SAAA;AACtC,IAAA,EAAA,CAAG,UAAA,GAAa,MAAA,CAAO,MAAA,GAAS,KAAA,GAAQ,MAAA,CAAO,SAAA;AAC/C,IAAA,EAAA,CAAG,SAAA,GAAY,MAAA,CAAO,MAAA,GAAS,KAAA,GAAQ,MAAA,CAAO,SAAA;AAC9C,IAAA,aAAA,CAAc,OAAA,GAAU,IAAA;AAAA,EAC1B,CAAA,EAAG,CAAC,cAAA,EAAgB,SAAS,CAAC,CAAA;AAG9B,EAAAA,SAAAA;AAAA,IACE,MACE,uBAAA,CAAwB;AAAA,MACtB,SAAA,EAAW,MAAM,UAAA,CAAW,OAAA;AAAA,MAC5B,WAAA,EAAa,MAAM,WAAA,IAAe,QAAA;AAAA,MAClC,cAAA,EAAgB,MAAM,iBAAA,CAAkB,OAAA,IAAW,WAAA,CAAY,OAAA;AAAA,MAC/D,cAAc,MAAM;AAClB,QAAA,iBAAA,CAAkB,OAAA,GAAU,KAAA;AAC5B,QAAA,WAAA,CAAY,CAAA,CAAA,KAAK,IAAI,CAAC,CAAA;AAAA,MACxB;AAAA,KACD,CAAA;AAAA,IACH,CAAC,aAAa,UAAU;AAAA,GAC1B;AAGA,EAAAA,UAAU,MAAM;AACd,IAAA,MAAM,UAAA,GAAyC;AAAA,MAC7C,GAAA,EAAK,YAAA;AAAA,MACL,GAAA,EAAK,YAAA;AAAA,MACL,GAAA,EAAK,aAAA;AAAA,MACL,CAAA,EAAG;AAAA,KACL;AACA,IAAA,MAAM,aAAA,GAA4C;AAAA,MAChD,GAAA,EAAK,gBAAA;AAAA,MACL,GAAA,EAAK,gBAAA;AAAA,MACL,CAAA,EAAG;AAAA,KACL;AAEA,IAAA,MAAM,aAAA,GAAgB,CAAC,CAAA,KAAqB;AAC1C,MAAA,IAAI,CAAC,WAAA,CAAY,OAAA,IAAW,cAAA,EAAe,EAAG;AAC9C,MAAA,MAAM,GAAA,GAAM,CAAA,CAAE,OAAA,IAAW,CAAA,CAAE,OAAA;AAC3B,MAAA,MAAM,MAAA,GAAS,MAAM,aAAA,CAAc,CAAA,CAAE,GAAG,CAAA,GAAI,UAAA,CAAW,EAAE,GAAG,CAAA;AAC5D,MAAA,IAAI,MAAA,EAAQ;AACV,QAAA,CAAA,CAAE,cAAA,EAAe;AACjB,QAAA,MAAA,EAAO;AAAA,MACT;AAAA,IACF,CAAA;AAEA,IAAA,UAAA,CAAW,gBAAA,CAAiB,WAAW,aAAa,CAAA;AACpD,IAAA,OAAO,MAAM,UAAA,CAAW,mBAAA,CAAoB,SAAA,EAAW,aAAa,CAAA;AAAA,EACtE,GAAG,CAAC,YAAA,EAAc,eAAe,gBAAA,EAAkB,gBAAA,EAAkB,YAAY,CAAC,CAAA;AAGlF,EAAAA,UAAU,MAAM;AACd,IAAA,MAAM,KAAK,SAAA,CAAU,OAAA;AACrB,IAAA,IAAI,CAAC,EAAA,EAAI;AAET,IAAA,MAAM,YAAA,GAAe,CAAC,MAAA,KAAoB;AACxC,MAAA,IAAI,QAAA,EAAU;AACZ,QAAA,EAAA,CAAG,OAAA,CAAQ,SAAA,GAAY,MAAA,GAAS,EAAA,GAAK,MAAA;AAAA,MACvC;AAAA,IACF,CAAA;AAEA,IAAA,MAAM,QAAA,GAAW,CAAC,OAAA,EAAiB,OAAA,KAAoB;AACrD,MAAA,MAAM,OAAO,OAAA,CAAQ,OAAA;AACrB,MAAA,IAAI,CAAC,IAAA,EAAM;AACX,MAAA,EAAA,CAAG,UAAA,GAAa,IAAA,CAAK,OAAA,IAAW,OAAA,GAAU,IAAA,CAAK,MAAA,CAAA;AAC/C,MAAA,EAAA,CAAG,SAAA,GAAY,IAAA,CAAK,OAAA,IAAW,OAAA,GAAU,IAAA,CAAK,MAAA,CAAA;AAC9C,MAAA,mBAAA,CAAoB,EAAE,CAAA;AAAA,IACxB,CAAA;AAEA,IAAA,MAAM,SAAA,GAAY,CAAC,OAAA,EAAiB,OAAA,KAAoB;AACtD,MAAA,OAAA,CAAQ,OAAA,GAAU;AAAA,QAChB,MAAA,EAAQ,OAAA;AAAA,QACR,MAAA,EAAQ,OAAA;AAAA,QACR,SAAS,EAAA,CAAG,UAAA;AAAA,QACZ,SAAS,EAAA,CAAG;AAAA,OACd;AACA,MAAA,YAAA,CAAa,IAAI,CAAA;AACjB,MAAA,IAAI,CAAC,QAAA,EAAU;AACb,QAAA,EAAA,CAAG,SAAA,CAAU,OAAA,CAAQ,2BAAA,EAA6B,+BAA+B,CAAA;AACjF,QAAA,EAAA,CAAG,SAAA,CAAU,OAAA,CAAQ,aAAA,EAAe,iBAAiB,CAAA;AAAA,MACvD;AAAA,IACF,CAAA;AAEA,IAAA,MAAM,UAAU,MAAM;AACpB,MAAA,WAAA,CAAY,OAAA,GAAU,IAAA;AACtB,MAAA,QAAA,CAAS,OAAA,GAAU,IAAA;AACnB,MAAA,IAAI,CAAC,QAAQ,OAAA,EAAS;AACtB,MAAA,OAAA,CAAQ,OAAA,GAAU,IAAA;AAClB,MAAA,YAAA,CAAa,KAAK,CAAA;AAClB,MAAA,IAAI,CAAC,QAAA,EAAU;AACb,QAAA,EAAA,CAAG,SAAA,CAAU,OAAA,CAAQ,+BAAA,EAAiC,2BAA2B,CAAA;AACjF,QAAA,EAAA,CAAG,SAAA,CAAU,OAAA,CAAQ,iBAAA,EAAmB,aAAa,CAAA;AAAA,MACvD;AAAA,IACF,CAAA;AAEA,IAAA,MAAM,iBAAA,GAAoB,CAAC,OAAA,EAAiB,OAAA,KAAoB;AAC9D,MAAA,MAAM,IAAA,GAAO,GAAG,qBAAA,EAAsB;AACtC,MAAA,aAAA,CAAc,OAAA,GAAU;AAAA,QACtB,MAAA,EAAQ,EAAA,CAAG,UAAA,GAAa,OAAA,GAAU,IAAA,CAAK,IAAA;AAAA,QACvC,MAAA,EAAQ,EAAA,CAAG,SAAA,GAAY,OAAA,GAAU,IAAA,CAAK,GAAA;AAAA,QACtC,SAAA,EAAW,UAAU,IAAA,CAAK,IAAA;AAAA,QAC1B,SAAA,EAAW,UAAU,IAAA,CAAK,GAAA;AAAA,QAC1B,WAAW,iBAAA,CAAkB;AAAA,OAC/B;AAAA,IACF,CAAA;AAEA,IAAA,MAAM,WAAA,GAAc,CAAC,CAAA,KAAkB;AACrC,MAAA,IAAI,QAAA,IAAY,CAAA,CAAE,MAAA,KAAW,CAAA,EAAG;AAChC,MAAA,IAAI,sBAAA,CAAuB,CAAA,CAAE,MAAM,CAAA,EAAG;AAEtC,MAAA,SAAA,CAAU,CAAA,CAAE,OAAA,EAAS,CAAA,CAAE,OAAO,CAAA;AAC9B,MAAA,CAAA,CAAE,cAAA,EAAe;AAAA,IACnB,CAAA;AAEA,IAAA,MAAM,WAAA,GAAc,CAAC,CAAA,KAAkB;AACrC,MAAA,IAAI,QAAA,EAAU;AACd,MAAA,QAAA,CAAS,CAAA,CAAE,OAAA,EAAS,CAAA,CAAE,OAAO,CAAA;AAAA,IAC/B,CAAA;AAEA,IAAA,MAAM,YAAA,GAAe,CAAC,CAAA,KAAkB;AACtC,MAAA,IAAI,CAAC,QAAA,EAAU;AAEf,MAAA,IAAI,CAAA,CAAE,OAAA,CAAQ,MAAA,KAAW,CAAA,EAAG;AAC1B,QAAA,WAAA,CAAY,OAAA,GAAU,IAAA;AACtB,QAAA,OAAA,CAAQ,OAAA,GAAU,IAAA;AAClB,QAAA,MAAM,QAAA,GAAW,gBAAA,CAAiB,CAAA,CAAE,OAAO,CAAA;AAC3C,QAAA,IAAI,YAAY,CAAA,EAAG;AACnB,QAAA,QAAA,CAAS,OAAA,GAAU;AAAA,UACjB,aAAA,EAAe,QAAA;AAAA,UACf,YAAY,iBAAA,CAAkB;AAAA,SAChC;AACA,QAAA,eAAA,CAAgB,KAAK,CAAA;AACrB,QAAA;AAAA,MACF;AAEA,MAAA,IAAI,CAAA,CAAE,OAAA,CAAQ,MAAA,KAAW,CAAA,IAAK,SAAS,OAAA,EAAS;AAChD,MAAA,IAAI,sBAAA,CAAuB,CAAA,CAAE,MAAM,CAAA,EAAG;AAEtC,MAAA,MAAM,KAAA,GAAQ,CAAA,CAAE,OAAA,CAAQ,CAAC,CAAA;AACzB,MAAA,IAAI,CAAC,KAAA,EAAO;AACZ,MAAA,WAAA,CAAY,OAAA,GAAU;AAAA,QACpB,QAAQ,KAAA,CAAM,OAAA;AAAA,QACd,QAAQ,KAAA,CAAM,OAAA;AAAA,QACd,SAAS,EAAA,CAAG,UAAA;AAAA,QACZ,SAAS,EAAA,CAAG;AAAA,OACd;AAAA,IACF,CAAA;AAEA,IAAA,MAAM,WAAA,GAAc,CAAC,CAAA,KAAkB;AACrC,MAAA,IAAI,CAAC,QAAA,EAAU;AAEf,MAAA,IAAI,QAAA,CAAS,OAAA,IAAW,CAAA,CAAE,OAAA,CAAQ,WAAW,CAAA,EAAG;AAC9C,QAAA,MAAM,QAAA,GAAW,gBAAA,CAAiB,CAAA,CAAE,OAAO,CAAA;AAC3C,QAAA,IAAI,QAAA,IAAY,CAAA,IAAK,QAAA,CAAS,OAAA,CAAQ,iBAAiB,CAAA,EAAG;AAE1D,QAAA,MAAM,KAAA,GAAQ,CAAA,CAAE,OAAA,CAAQ,CAAC,CAAA;AACzB,QAAA,MAAM,MAAA,GAAS,CAAA,CAAE,OAAA,CAAQ,CAAC,CAAA;AAC1B,QAAA,IAAI,CAAC,KAAA,IAAS,CAAC,MAAA,EAAQ;AAEvB,QAAA,MAAM,OAAA,GAAA,CAAW,KAAA,CAAM,OAAA,GAAU,MAAA,CAAO,OAAA,IAAW,CAAA;AACnD,QAAA,MAAM,OAAA,GAAA,CAAW,KAAA,CAAM,OAAA,GAAU,MAAA,CAAO,OAAA,IAAW,CAAA;AACnD,QAAA,MAAM,SAAA,GAAY,eAAA;AAAA,UAChB,QAAA,CAAS,OAAA,CAAQ,UAAA,IAAc,QAAA,GAAW,SAAS,OAAA,CAAQ,aAAA;AAAA,SAC7D;AAEA,QAAA,iBAAA,CAAkB,SAAS,OAAO,CAAA;AAClC,QAAA,OAAA,CAAQ,SAAS,CAAA;AACjB,QAAA,eAAA,CAAgB,KAAK,CAAA;AACrB,QAAA,CAAA,CAAE,cAAA,EAAe;AACjB,QAAA;AAAA,MACF;AAEA,MAAA,MAAM,aAAa,WAAA,CAAY,OAAA;AAC/B,MAAA,MAAM,KAAA,GAAQ,CAAA,CAAE,OAAA,CAAQ,CAAC,CAAA;AACzB,MAAA,IAAI,CAAC,UAAA,IAAc,CAAC,KAAA,EAAO;AAE3B,MAAA,IAAI,CAAC,QAAQ,OAAA,EAAS;AACpB,QAAA,IACE,CAAC,uBAAA;AAAA,UACC,UAAA,CAAW,MAAA;AAAA,UACX,UAAA,CAAW,MAAA;AAAA,UACX,KAAA,CAAM,OAAA;AAAA,UACN,KAAA,CAAM;AAAA,SACR,EACA;AACA,UAAA;AAAA,QACF;AAEA,QAAA,OAAA,CAAQ,OAAA,GAAU;AAAA,UAChB,QAAQ,UAAA,CAAW,MAAA;AAAA,UACnB,QAAQ,UAAA,CAAW,MAAA;AAAA,UACnB,SAAS,UAAA,CAAW,OAAA;AAAA,UACpB,SAAS,UAAA,CAAW;AAAA,SACtB;AACA,QAAA,YAAA,CAAa,IAAI,CAAA;AAAA,MACnB;AAEA,MAAA,QAAA,CAAS,KAAA,CAAM,OAAA,EAAS,KAAA,CAAM,OAAO,CAAA;AACrC,MAAA,CAAA,CAAE,cAAA,EAAe;AAAA,IACnB,CAAA;AAEA,IAAA,EAAA,CAAG,gBAAA,CAAiB,aAAa,WAAW,CAAA;AAC5C,IAAA,MAAA,CAAO,gBAAA,CAAiB,aAAa,WAAW,CAAA;AAChD,IAAA,MAAA,CAAO,gBAAA,CAAiB,WAAW,OAAO,CAAA;AAE1C,IAAA,IAAI,QAAA,EAAU;AACZ,MAAA,EAAA,CAAG,iBAAiB,YAAA,EAAc,YAAA,EAAc,EAAE,OAAA,EAAS,MAAM,CAAA;AACjE,MAAA,EAAA,CAAG,iBAAiB,WAAA,EAAa,WAAA,EAAa,EAAE,OAAA,EAAS,OAAO,CAAA;AAChE,MAAA,EAAA,CAAG,gBAAA,CAAiB,YAAY,OAAO,CAAA;AACvC,MAAA,EAAA,CAAG,gBAAA,CAAiB,eAAe,OAAO,CAAA;AAAA,IAC5C;AAEA,IAAA,OAAO,MAAM;AACX,MAAA,EAAA,CAAG,mBAAA,CAAoB,aAAa,WAAW,CAAA;AAC/C,MAAA,MAAA,CAAO,mBAAA,CAAoB,aAAa,WAAW,CAAA;AACnD,MAAA,MAAA,CAAO,mBAAA,CAAoB,WAAW,OAAO,CAAA;AAC7C,MAAA,OAAO,GAAG,OAAA,CAAQ,SAAA;AAClB,MAAA,IAAI,QAAA,EAAU;AACZ,QAAA,EAAA,CAAG,mBAAA,CAAoB,cAAc,YAAY,CAAA;AACjD,QAAA,EAAA,CAAG,mBAAA,CAAoB,aAAa,WAAW,CAAA;AAC/C,QAAA,EAAA,CAAG,mBAAA,CAAoB,YAAY,OAAO,CAAA;AAC1C,QAAA,EAAA,CAAG,mBAAA,CAAoB,eAAe,OAAO,CAAA;AAAA,MAC/C;AAAA,IACF,CAAA;AAAA,EACF,CAAA,EAAG,CAAC,QAAA,EAAU,SAAS,CAAC,CAAA;AAExB,EAAA,MAAM,cAAA,GAAiB,YAAY,YAAY;AAC7C,IAAA,MAAM,UAAU,UAAA,CAAW,OAAA;AAC3B,IAAA,IAAI,CAAC,OAAA,EAAS;AACd,IAAA,IAAI;AACF,MAAA,MAAM,SAAA,CAAU,SAAA,CAAU,SAAA,CAAU,OAAA,CAAQ,SAAS,CAAA;AACrD,MAAA,MAAA,IAAS;AAAA,IACX,SAAS,KAAA,EAAO;AACd,MAAA,OAAA,GAAU,KAAK,CAAA;AAAA,IACjB;AAAA,EACF,CAAA,EAAG,CAAC,UAAA,EAAY,MAAA,EAAQ,OAAO,CAAC,CAAA;AAEhC,EAAA,MAAM,WAAA,GAAc,SAAA,GAAY,GAAA,CAAI,QAAA,GAAW,IAAA,CAAK,IAAI,CAAA,EAAG,SAAA,GAAY,CAAC,CAAA,GAAI,GAAA,CAAI,KAAA;AAEhF,EAAA,OAAO;AAAA,IACL,SAAA;AAAA,IACA,YAAA;AAAA,IACA,cAAA;AAAA,IACA,YAAA;AAAA,IACA,WAAA;AAAA,IACA,GAAA;AAAA,IACA,YAAA;AAAA,IACA,aAAA;AAAA,IACA,gBAAA;AAAA,IACA,gBAAA;AAAA,IACA,YAAA;AAAA,IACA,cAAA;AAAA,IACA,QAAA,EAAU,SAAA;AAAA,IACV,QAAA,EAAU;AAAA,GACZ;AACF;AC5fO,SAAS,YAAA,CAAa;AAAA,EAC3B,OAAA;AAAA,EACA,UAAA;AAAA,EACA,UAAA;AAAA,EACA,QAAA,GAAW,IAAA;AAAA,EACX,QAAA,GAAW,QAAA;AAAA,EACX,WAAA;AAAA,EACA,MAAA;AAAA,EACA,OAAA;AAAA,EACA,SAAA;AAAA,EACA,YAAA;AAAA,EACA;AACF,CAAA,EAAsB;AACpB,EAAA,MAAM,WAAW,WAAA,EAAY;AAC7B,EAAA,MAAM,SAAA,GAAYC,OAAuB,IAAI,CAAA;AAC7C,EAAA,MAAM,UAAA,GAAaA,OAAuB,IAAI,CAAA;AAC9C,EAAA,MAAM,aAAA,GAAgB,EAAE,GAAG,mBAAA,EAAqB,GAAG,UAAA,EAAW;AAE9D,EAAA,MAAM;AAAA,IACJ,SAAA;AAAA,IACA,YAAA;AAAA,IACA,cAAA;AAAA,IACA,YAAA;AAAA,IACA,WAAA;AAAA,IACA,GAAA;AAAA,IACA,YAAA;AAAA,IACA,aAAA;AAAA,IACA,gBAAA;AAAA,IACA,gBAAA;AAAA,IACA,YAAA;AAAA,IACA,cAAA;AAAA,IACA,QAAA;AAAA,IACA;AAAA,MACE,iBAAA,CAAkB;AAAA,IACpB,SAAA;AAAA,IACA,UAAA;AAAA,IACA,QAAA;AAAA,IACA,QAAA;AAAA,IACA,QAAA;AAAA,IACA,WAAA;AAAA,IACA,MAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAA,uBACE,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,eAAA,EAEb,QAAA,EAAA;AAAA,oBAAA,GAAA,CAAC,OAAA,EAAA,EAAO,QAAA,EAAA,CAAA,cAAA,EAAiB,GAAA,CAAI,WAAW,CAAA,cAAA,CAAA,EAAiB,CAAA;AAAA,oBACzD,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,uBAAA,EACZ,QAAA,EAAA;AAAA,MAAA,YAAA;AAAA,sBACD,IAAA,CAAC,MAAA,EAAA,EAAK,SAAA,EAAU,yBAAA,EACb,QAAA,EAAA;AAAA,QAAA,SAAA;AAAA,QAAU,OAAA;AAAA,QAAM,SAAA,GAAY,IAAI,GAAA,GAAM;AAAA,OAAA,EACzC,CAAA;AAAA,sBACA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,wBAAA,EACb,QAAA,EAAA;AAAA,wBAAA,GAAA;AAAA,UAAC,QAAA;AAAA,UAAA;AAAA,YACC,YAAA,EAAW,qBAAA;AAAA,YACX,cAAA,EAAc,YAAA;AAAA,YACd,SAAA,EAAW,IAAA,CAAK,mBAAA,EAAqB,YAAA,IAAgB,0BAA0B,CAAA;AAAA,YAC/E,OAAA,EAAS,gBAAA;AAAA,YACT,KAAA,EAAM,cAAA;AAAA,YACN,IAAA,EAAK,QAAA;AAAA,YAEL,QAAA,kBAAA,GAAA,CAAC,SAAA,EAAA,EAAU,SAAA,EAAU,oBAAA,EAAqB;AAAA;AAAA,SAC5C;AAAA,wBACA,IAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,yBAAA,EACb,QAAA,EAAA;AAAA,0BAAA,GAAA;AAAA,YAAC,QAAA;AAAA,YAAA;AAAA,cACC,YAAA,EAAW,UAAA;AAAA,cACX,SAAA,EAAU,mBAAA;AAAA,cACV,QAAA,EAAU,CAAC,YAAA,IAAgB,cAAA,IAAkB,QAAA;AAAA,cAC7C,OAAA,EAAS,aAAA;AAAA,cACT,KAAA,EAAM,UAAA;AAAA,cACN,IAAA,EAAK,QAAA;AAAA,cAEL,QAAA,kBAAA,GAAA,CAAC,KAAA,EAAA,EAAM,SAAA,EAAU,oBAAA,EAAqB;AAAA;AAAA,WACxC;AAAA,0BACA,IAAA;AAAA,YAAC,QAAA;AAAA,YAAA;AAAA,cACC,SAAA,EAAU,0BAAA;AAAA,cACV,OAAA,EAAS,gBAAA;AAAA,cACT,KAAA,EAAM,oBAAA;AAAA,cACN,IAAA,EAAK,QAAA;AAAA,cAEJ,QAAA,EAAA;AAAA,gBAAA,IAAA,CAAK,KAAA,CAAM,iBAAiB,GAAG,CAAA;AAAA,gBAAE;AAAA;AAAA;AAAA,WACpC;AAAA,0BACA,GAAA;AAAA,YAAC,QAAA;AAAA,YAAA;AAAA,cACC,YAAA,EAAW,SAAA;AAAA,cACX,SAAA,EAAU,mBAAA;AAAA,cACV,UAAU,cAAA,IAAkB,QAAA;AAAA,cAC5B,OAAA,EAAS,YAAA;AAAA,cACT,KAAA,EAAM,SAAA;AAAA,cACN,IAAA,EAAK,QAAA;AAAA,cAEL,QAAA,kBAAA,GAAA,CAAC,IAAA,EAAA,EAAK,SAAA,EAAU,oBAAA,EAAqB;AAAA;AAAA;AACvC,SAAA,EACF,CAAA;AAAA,wBACA,GAAA;AAAA,UAAC,QAAA;AAAA,UAAA;AAAA,YACC,YAAA,EAAW,kBAAA;AAAA,YACX,SAAA,EAAU,mBAAA;AAAA,YACV,OAAA,EAAS,cAAA;AAAA,YACT,KAAA,EAAM,kBAAA;AAAA,YACN,IAAA,EAAK,QAAA;AAAA,YAEL,QAAA,kBAAA,GAAA,CAAC,aAAA,EAAA,EAAc,SAAA,EAAU,oBAAA,EAAqB;AAAA;AAAA,SAChD;AAAA,wBACA,GAAA;AAAA,UAAC,QAAA;AAAA,UAAA;AAAA,YACC,SAAA,EAAU,6CAAA;AAAA,YACV,OAAA,EAAS,YAAA;AAAA,YACT,IAAA,EAAK,QAAA;AAAA,YACN,QAAA,EAAA;AAAA;AAAA,SAED;AAAA,QACC;AAAA,OAAA,EACH;AAAA,KAAA,EACF,CAAA;AAAA,oBAEA,GAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAW,IAAA;AAAA,UACT,sBAAA;AAAA,UACA,WAAW,6BAAA,GAAgC,2BAAA;AAAA,UAC3C;AAAA,SACF;AAAA,QACA,GAAA,EAAK,SAAA;AAAA,QAEL,QAAA,kBAAA,GAAA;AAAA,UAAC,SAAA;AAAA,UAAA;AAAA,YACC,YAAA,EACE,WACI,6DAAA,GACA,mDAAA;AAAA,YAEN,SAAA,EAAU,qBAAA;AAAA,YAEV,QAAA,kBAAA,GAAA;AAAA,cAAC,KAAA;AAAA,cAAA;AAAA,gBACC,SAAA,EAAU,wBAAA;AAAA,gBACV,KAAA,EAAO;AAAA,kBACL,KAAA,EAAO,IAAI,OAAA,GAAU,cAAA;AAAA,kBACrB,QAAQ,WAAA,GAAc;AAAA,iBACxB;AAAA,gBAEA,QAAA,kBAAA,GAAA;AAAA,kBAAC,KAAA;AAAA,kBAAA;AAAA,oBACC,SAAA,EAAU,0BAAA;AAAA,oBACV,wBAAA,EAAuB,EAAA;AAAA,oBACvB,KAAA,EAAO;AAAA,sBACL,OAAO,GAAA,CAAI,OAAA;AAAA,sBACX,MAAA,EAAQ,WAAA;AAAA,sBACR,SAAA,EAAW,cAAA,KAAmB,CAAA,GAAI,MAAA,GAAY,SAAS,cAAc,CAAA,CAAA;AAAA,qBACvE;AAAA,oBAEA,QAAA,kBAAA,GAAA,CAAC,sBAAsB,QAAA,EAAtB,EAA+B,OAAO,QAAA,EACrC,QAAA,kBAAA,GAAA,CAAC,wBAAwB,QAAA,EAAxB,EAAiC,OAAO,UAAA,EACvC,QAAA,kBAAA,GAAA,CAAC,wBAAwB,QAAA,EAAxB,EAAiC,OAAO,YAAA,EACvC,QAAA,kBAAA,GAAA,CAAC,WAAA,EAAA,EAAY,UAAA,EAAY,aAAA,EACvB,QAAA,kBAAA,GAAA;AAAA,sBAAC,KAAA;AAAA,sBAAA;AAAA,wBACC,SAAA,EAAU,uBAAA;AAAA,wBACV,EAAA,EAAG,qBAAA;AAAA,wBACH,GAAA,EAAK,UAAA;AAAA,wBACL,KAAA,EAAO,EAAE,MAAA,EAAQ,CAAA,EAAE;AAAA,wBAEnB,8BAAC,OAAA,EAAA,EAAQ;AAAA;AAAA,qBACX,EACF,CAAA,EACF,CAAA,EACF,CAAA,EACF;AAAA;AAAA;AACF;AAAA;AACF;AAAA;AACF;AAAA;AACF,GAAA,EACF,CAAA;AAEJ","file":"index.js","sourcesContent":["import { useEffect, useState } from 'react'\n\n/** True when the viewport is narrow (<= 768px). SSR-safe (defaults false). */\nexport function useIsMobile(breakpointPx = 768): boolean {\n const [isMobile, setIsMobile] = useState(false)\n useEffect(() => {\n const mql = window.matchMedia(`(max-width: ${breakpointPx}px)`)\n const update = () => setIsMobile(mql.matches)\n update()\n mql.addEventListener('change', update)\n return () => mql.removeEventListener('change', update)\n }, [breakpointPx])\n return isMobile\n}\n","/**\n * Build a print-ready clone of the resume content element. The pagination\n * engine already renders real 8.5x11 `[data-resume-page]` frames, so the clone\n * is a plain wrapper holding those frames (no extra page sizing/padding); the\n * print stylesheet maps each frame to a physical page. The hidden measuring\n * layer is dropped so only the real pages print.\n */\nexport function createPrintClone(source: HTMLElement): HTMLElement {\n const clone = source.cloneNode(true) as HTMLElement\n clone.id = 'resume-print-clone'\n clone.style.cssText = 'background:white;color:black'\n clone.querySelector('[data-resume-measure]')?.remove()\n return clone\n}\n\n/** Hide all non-essential body children for printing; returns the hidden elements. */\nexport function hideBodyChildren(): HTMLElement[] {\n const hidden: HTMLElement[] = []\n const preserveTags = new Set(['STYLE', 'LINK', 'SCRIPT'])\n for (const child of Array.from(document.body.children) as HTMLElement[]) {\n if (!preserveTags.has(child.tagName)) {\n child.style.setProperty('display', 'none', 'important')\n hidden.push(child)\n }\n }\n return hidden\n}\n\nexport interface PrintControllerOptions {\n /** The element holding the rendered resume page frames (contentRef.current). */\n getTarget: () => HTMLElement | null\n /** Suggested PDF filename — becomes document.title during print. */\n getFilename: () => string\n /** Whether THIS viewer should own the print (e.g. it requested print, or it's the active/focused view). */\n shouldOwnPrint: () => boolean\n /** Optional: called after print is torn down (e.g. to force a re-render). */\n onAfterPrint?: () => void\n}\n\n/**\n * Register beforeprint/afterprint handlers implementing the clone-and-hide\n * print strategy. Returns an unsubscribe function that removes the listeners.\n */\nexport function registerPrintController(opts: PrintControllerOptions): () => void {\n let hiddenElements: HTMLElement[] = []\n let savedTitle = ''\n\n const beforePrint = () => {\n // Own the print only when this viewer should (its Save-to-PDF button /\n // shortcut requested it, or it's the active/focused view). Otherwise leave\n // it alone so another app isn't cloned/printed underneath, and so this\n // viewer isn't skipped when focus hasn't flushed yet.\n if (!opts.shouldOwnPrint()) return\n const target = opts.getTarget()\n if (!target) return\n savedTitle = document.title\n document.title = opts.getFilename()\n const clone = createPrintClone(target)\n hiddenElements = hideBodyChildren()\n document.body.appendChild(clone)\n }\n\n const afterPrint = () => {\n const clone = document.getElementById('resume-print-clone')\n if (clone) clone.remove()\n document.title = savedTitle\n for (const el of hiddenElements) {\n el.style.removeProperty('display')\n }\n hiddenElements = []\n opts.onAfterPrint?.()\n }\n\n window.addEventListener('beforeprint', beforePrint)\n window.addEventListener('afterprint', afterPrint)\n return () => {\n window.removeEventListener('beforeprint', beforePrint)\n window.removeEventListener('afterprint', afterPrint)\n }\n}\n","export const INTERACTIVE_PAN_SELECTOR = 'a, button, [role=\"button\"], input, textarea, select'\n\nexport const PAN_THRESHOLD_PX = 6\n\nexport function clampZoom(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(value, max))\n}\n\nexport function isInteractivePanTarget(target: EventTarget | null): boolean {\n return target instanceof HTMLElement && Boolean(target.closest(INTERACTIVE_PAN_SELECTOR))\n}\n\nexport function getTouchDistance(touches: TouchList): number {\n if (touches.length < 2) {\n return 0\n }\n\n const first = touches[0]\n const second = touches[1]\n if (!first || !second) {\n return 0\n }\n\n const deltaX = first.clientX - second.clientX\n const deltaY = first.clientY - second.clientY\n return Math.hypot(deltaX, deltaY)\n}\n\nexport function clampScrollPosition(element: HTMLElement): void {\n const maxScrollLeft = Math.max(0, element.scrollWidth - element.clientWidth)\n const maxScrollTop = Math.max(0, element.scrollHeight - element.clientHeight)\n element.scrollLeft = Math.min(Math.max(0, element.scrollLeft), maxScrollLeft)\n element.scrollTop = Math.min(Math.max(0, element.scrollTop), maxScrollTop)\n}\n\nexport function hasExceededPanThreshold(\n startX: number,\n startY: number,\n currentX: number,\n currentY: number,\n threshold = PAN_THRESHOLD_PX\n): boolean {\n return Math.abs(currentX - startX) > threshold || Math.abs(currentY - startY) > threshold\n}\n","// packages/resume/src/viewer/use-resume-viewport.ts\n//\n// Headless viewport engine for a paginated resume: zoom (buttons / Ctrl+wheel /\n// pinch), pan (mouse drag / touch), fit-to-width, focus-aware print + keyboard\n// shortcuts. The single source of truth for this behaviour — both the built-in\n// `ResumeViewer` and bespoke hosts (e.g. an OS window with its own toolbar\n// chrome) consume this hook so the logic is never duplicated.\n//\n// The host owns the markup and supplies the scroll + content refs; the hook\n// wires every listener to them and returns the derived scale, page count, and\n// the toolbar action handlers.\nimport { type RefObject, useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react'\nimport { getPageGeometry, type PageGeometry, type PageSize, WRAPPER_PADDING_X } from '../geometry'\nimport { registerPrintController } from './print'\nimport {\n clampScrollPosition,\n clampZoom,\n getTouchDistance,\n hasExceededPanThreshold,\n isInteractivePanTarget,\n} from './viewport'\n\nexport const SCALE_STEP = 0.1\nexport const MIN_SCALE = 0.25\nexport const MAX_SCALE = 2\nconst WHEEL_ZOOM_SENSITIVITY = 0.01\nconst WHEEL_ZOOM_CAP = 0.03\n\nconst clampResumeZoom = (value: number) => clampZoom(value, MIN_SCALE, MAX_SCALE)\n\nfunction isInputFocused(): boolean {\n const tag = document.activeElement?.tagName\n return tag === 'INPUT' || tag === 'TEXTAREA'\n}\n\nexport interface UseResumeViewportOptions {\n /** Scroll viewport element (the host attaches this to its scrollable region). */\n scrollRef: RefObject<HTMLDivElement | null>\n /** Element holding the rendered page frames (the print/copy target). */\n contentRef: RefObject<HTMLDivElement | null>\n /** Narrow-viewport flag — drives touch pan/pinch vs mouse drag. */\n isMobile: boolean\n /** Whether this viewport owns global print + keyboard shortcuts. */\n isActive: boolean\n /** Paper size driving the page dimensions + fit-to-width. Default 'letter'. */\n pageSize?: PageSize\n /** Suggested PDF filename (becomes document.title during print). */\n pdfFilename?: string\n /** Called after a successful copy-to-clipboard. */\n onCopy?: () => void\n /** Called when copy-to-clipboard fails. */\n onError?: (error: unknown) => void\n}\n\nexport interface UseResumeViewport {\n /** Current page count (reported by the pagination engine via setPageCount). */\n pageCount: number\n /** Reporter wired into PaginationReportContext. */\n setPageCount: (n: number) => void\n /** The scale actually applied to the page stage (fit-to-width or manual zoom). */\n effectiveScale: number\n /** Whether fit-to-width mode is active. */\n isFitToWidth: boolean\n /** Total stacked page height (unscaled px) — for sizing the page box. */\n totalHeight: number\n /** Resolved geometry for the active page size — for sizing the scaled box. */\n geo: PageGeometry\n handleZoomIn: () => void\n handleZoomOut: () => void\n handleActualSize: () => void\n handleFitToWidth: () => void\n requestPrint: () => void\n handleCopyText: () => Promise<void>\n /** Lower/upper zoom bounds (for disabling +/- controls). */\n minScale: number\n maxScale: number\n}\n\n/**\n * Wire the resume viewport behaviour to host-supplied refs and return the\n * derived state + action handlers. The host renders the scaled page stage; the\n * pagination engine inside it reports its page count through `setPageCount`.\n */\nexport function useResumeViewport({\n scrollRef,\n contentRef,\n isMobile,\n isActive,\n pageSize = 'letter',\n pdfFilename,\n onCopy,\n onError,\n}: UseResumeViewportOptions): UseResumeViewport {\n const geo = getPageGeometry(pageSize)\n\n // Keep the latest active state readable from global listeners (print/keyboard)\n // without re-registering them, so this viewport only hijacks printing when\n // it's the active view (avoids clashing with other apps' print handlers).\n const isActiveRef = useRef(isActive)\n isActiveRef.current = isActive\n\n // Set synchronously right before window.print() so the global beforeprint\n // listener knows THIS viewport initiated the print (active state can lag a click).\n const printRequestedRef = useRef(false)\n const requestPrint = useCallback(() => {\n printRequestedRef.current = true\n window.print()\n }, [])\n\n const zoomAnchorRef = useRef<{\n pointX: number\n pointY: number\n viewportX: number\n viewportY: number\n prevScale: number\n } | null>(null)\n const [pageCount, setPageCount] = useState(1)\n const [zoom, setZoom] = useState(1)\n const [fitScale, setFitScale] = useState(1)\n const [isFitToWidth, setIsFitToWidth] = useState(true)\n const [, forceRender] = useState(0)\n const dragRef = useRef<{\n startX: number\n startY: number\n scrollX: number\n scrollY: number\n } | null>(null)\n const touchPanRef = useRef<{\n scrollX: number\n scrollY: number\n startX: number\n startY: number\n } | null>(null)\n const pinchRef = useRef<{\n startDistance: number\n startScale: number\n } | null>(null)\n\n const effectiveScale = isFitToWidth ? fitScale : zoom\n\n // Refs for stable closures — avoids tearing down/re-registering listeners on every zoom tick\n const effectiveScaleRef = useRef(effectiveScale)\n const isFitToWidthRef = useRef(isFitToWidth)\n const fitScaleRef = useRef(fitScale)\n effectiveScaleRef.current = effectiveScale\n isFitToWidthRef.current = isFitToWidth\n fitScaleRef.current = fitScale\n\n // Keep zoom synced to fitScale while in fit-to-width mode\n useEffect(() => {\n if (isFitToWidth) setZoom(fitScale)\n }, [isFitToWidth, fitScale])\n\n // Responsive scaling — compute fit-to-width from the scroll viewport.\n useEffect(() => {\n const viewport = scrollRef.current\n if (!viewport) return\n\n const updateFitScale = () => {\n const available = Math.max(0, viewport.clientWidth - WRAPPER_PADDING_X)\n setFitScale(clampResumeZoom(Math.min(1, available / geo.widthPx)))\n }\n\n updateFitScale()\n const observer = new ResizeObserver(updateFitScale)\n observer.observe(viewport)\n window.visualViewport?.addEventListener('resize', updateFitScale)\n\n return () => {\n observer.disconnect()\n window.visualViewport?.removeEventListener('resize', updateFitScale)\n }\n }, [scrollRef, geo.widthPx])\n\n const captureViewportCenter = useCallback(() => {\n const el = scrollRef.current\n if (!el) return\n zoomAnchorRef.current = {\n pointX: el.scrollLeft + el.clientWidth / 2,\n pointY: el.scrollTop + el.clientHeight / 2,\n viewportX: el.clientWidth / 2,\n viewportY: el.clientHeight / 2,\n prevScale: effectiveScaleRef.current,\n }\n }, [scrollRef])\n\n const handleZoomIn = useCallback(() => {\n captureViewportCenter()\n setIsFitToWidth(false)\n setZoom(prev => clampResumeZoom(prev + SCALE_STEP))\n }, [captureViewportCenter])\n\n const handleZoomOut = useCallback(() => {\n captureViewportCenter()\n setIsFitToWidth(false)\n setZoom(prev => clampResumeZoom(prev - SCALE_STEP))\n }, [captureViewportCenter])\n\n const handleActualSize = useCallback(() => {\n captureViewportCenter()\n setIsFitToWidth(false)\n setZoom(1)\n }, [captureViewportCenter])\n\n const handleFitToWidth = useCallback(() => {\n setIsFitToWidth(true)\n }, [])\n\n // Ctrl+wheel zoom — rAF-batched for smooth trackpad pinch\n useEffect(() => {\n const el = scrollRef.current\n if (!el) return\n\n let pendingDelta = 0\n let rafId = 0\n let lastCursorX = 0\n let lastCursorY = 0\n\n const flush = () => {\n rafId = 0\n const delta = pendingDelta\n pendingDelta = 0\n const cursorX = lastCursorX\n const cursorY = lastCursorY\n\n setZoom(prev => {\n const next = clampResumeZoom(prev + delta)\n if (next === prev) return prev\n zoomAnchorRef.current = {\n pointX: el.scrollLeft + cursorX,\n pointY: el.scrollTop + cursorY,\n viewportX: cursorX,\n viewportY: cursorY,\n prevScale: isFitToWidthRef.current ? fitScaleRef.current : prev,\n }\n return next\n })\n setIsFitToWidth(false)\n }\n\n const onWheel = (e: WheelEvent) => {\n if (!e.ctrlKey) return\n e.preventDefault()\n\n pendingDelta = Math.max(\n -WHEEL_ZOOM_CAP,\n Math.min(pendingDelta + -e.deltaY * WHEEL_ZOOM_SENSITIVITY, WHEEL_ZOOM_CAP)\n )\n const rect = el.getBoundingClientRect()\n lastCursorX = e.clientX - rect.left\n lastCursorY = e.clientY - rect.top\n\n if (rafId === 0) {\n rafId = requestAnimationFrame(flush)\n }\n }\n\n el.addEventListener('wheel', onWheel, { passive: false })\n return () => {\n el.removeEventListener('wheel', onWheel)\n if (rafId !== 0) cancelAnimationFrame(rafId)\n }\n }, [scrollRef])\n\n // Scroll anchoring — adjust scroll position after zoom so the focal point stays put\n useLayoutEffect(() => {\n const anchor = zoomAnchorRef.current\n if (!anchor) return\n const el = scrollRef.current\n if (!el) return\n\n const ratio = effectiveScale / anchor.prevScale\n el.scrollLeft = anchor.pointX * ratio - anchor.viewportX\n el.scrollTop = anchor.pointY * ratio - anchor.viewportY\n zoomAnchorRef.current = null\n }, [effectiveScale, scrollRef])\n\n // Print handling — clone-and-hide strategy, gated on this viewport owning the print.\n useEffect(\n () =>\n registerPrintController({\n getTarget: () => contentRef.current,\n getFilename: () => pdfFilename ?? 'resume',\n shouldOwnPrint: () => printRequestedRef.current || isActiveRef.current,\n onAfterPrint: () => {\n printRequestedRef.current = false\n forceRender(n => n + 1)\n },\n }),\n [pdfFilename, contentRef]\n )\n\n // Keyboard shortcuts\n useEffect(() => {\n const keyActions: Record<string, () => void> = {\n '+': handleZoomIn,\n '=': handleZoomIn,\n '-': handleZoomOut,\n _: handleZoomOut,\n }\n const modKeyActions: Record<string, () => void> = {\n '0': handleFitToWidth,\n '1': handleActualSize,\n p: requestPrint,\n }\n\n const handleKeyDown = (e: KeyboardEvent) => {\n if (!isActiveRef.current || isInputFocused()) return\n const mod = e.ctrlKey || e.metaKey\n const action = mod ? modKeyActions[e.key] : keyActions[e.key]\n if (action) {\n e.preventDefault()\n action()\n }\n }\n\n globalThis.addEventListener('keydown', handleKeyDown)\n return () => globalThis.removeEventListener('keydown', handleKeyDown)\n }, [handleZoomIn, handleZoomOut, handleFitToWidth, handleActualSize, requestPrint])\n\n // Click-and-drag panning (hand tool). Mobile uses touch pan + pinch-to-zoom.\n useEffect(() => {\n const el = scrollRef.current\n if (!el) return\n\n const setPanActive = (active: boolean) => {\n if (isMobile) {\n el.dataset.panActive = active ? '' : undefined\n }\n }\n\n const moveDrag = (clientX: number, clientY: number) => {\n const drag = dragRef.current\n if (!drag) return\n el.scrollLeft = drag.scrollX - (clientX - drag.startX)\n el.scrollTop = drag.scrollY - (clientY - drag.startY)\n clampScrollPosition(el)\n }\n\n const beginDrag = (clientX: number, clientY: number) => {\n dragRef.current = {\n startX: clientX,\n startY: clientY,\n scrollX: el.scrollLeft,\n scrollY: el.scrollTop,\n }\n setPanActive(true)\n if (!isMobile) {\n el.classList.replace('resume-viewer-scroll-grab', 'resume-viewer-scroll-grabbing')\n el.classList.replace('cursor-grab', 'cursor-grabbing')\n }\n }\n\n const endDrag = () => {\n touchPanRef.current = null\n pinchRef.current = null\n if (!dragRef.current) return\n dragRef.current = null\n setPanActive(false)\n if (!isMobile) {\n el.classList.replace('resume-viewer-scroll-grabbing', 'resume-viewer-scroll-grab')\n el.classList.replace('cursor-grabbing', 'cursor-grab')\n }\n }\n\n const captureZoomAnchor = (clientX: number, clientY: number) => {\n const rect = el.getBoundingClientRect()\n zoomAnchorRef.current = {\n pointX: el.scrollLeft + clientX - rect.left,\n pointY: el.scrollTop + clientY - rect.top,\n viewportX: clientX - rect.left,\n viewportY: clientY - rect.top,\n prevScale: effectiveScaleRef.current,\n }\n }\n\n const onMouseDown = (e: MouseEvent) => {\n if (isMobile || e.button !== 0) return\n if (isInteractivePanTarget(e.target)) return\n\n beginDrag(e.clientX, e.clientY)\n e.preventDefault()\n }\n\n const onMouseMove = (e: MouseEvent) => {\n if (isMobile) return\n moveDrag(e.clientX, e.clientY)\n }\n\n const onTouchStart = (e: TouchEvent) => {\n if (!isMobile) return\n\n if (e.touches.length === 2) {\n touchPanRef.current = null\n dragRef.current = null\n const distance = getTouchDistance(e.touches)\n if (distance <= 0) return\n pinchRef.current = {\n startDistance: distance,\n startScale: effectiveScaleRef.current,\n }\n setIsFitToWidth(false)\n return\n }\n\n if (e.touches.length !== 1 || pinchRef.current) return\n if (isInteractivePanTarget(e.target)) return\n\n const touch = e.touches[0]\n if (!touch) return\n touchPanRef.current = {\n startX: touch.clientX,\n startY: touch.clientY,\n scrollX: el.scrollLeft,\n scrollY: el.scrollTop,\n }\n }\n\n const onTouchMove = (e: TouchEvent) => {\n if (!isMobile) return\n\n if (pinchRef.current && e.touches.length === 2) {\n const distance = getTouchDistance(e.touches)\n if (distance <= 0 || pinchRef.current.startDistance <= 0) return\n\n const first = e.touches[0]\n const second = e.touches[1]\n if (!first || !second) return\n\n const centerX = (first.clientX + second.clientX) / 2\n const centerY = (first.clientY + second.clientY) / 2\n const nextScale = clampResumeZoom(\n pinchRef.current.startScale * (distance / pinchRef.current.startDistance)\n )\n\n captureZoomAnchor(centerX, centerY)\n setZoom(nextScale)\n setIsFitToWidth(false)\n e.preventDefault()\n return\n }\n\n const pendingPan = touchPanRef.current\n const touch = e.touches[0]\n if (!pendingPan || !touch) return\n\n if (!dragRef.current) {\n if (\n !hasExceededPanThreshold(\n pendingPan.startX,\n pendingPan.startY,\n touch.clientX,\n touch.clientY\n )\n ) {\n return\n }\n\n dragRef.current = {\n startX: pendingPan.startX,\n startY: pendingPan.startY,\n scrollX: pendingPan.scrollX,\n scrollY: pendingPan.scrollY,\n }\n setPanActive(true)\n }\n\n moveDrag(touch.clientX, touch.clientY)\n e.preventDefault()\n }\n\n el.addEventListener('mousedown', onMouseDown)\n window.addEventListener('mousemove', onMouseMove)\n window.addEventListener('mouseup', endDrag)\n\n if (isMobile) {\n el.addEventListener('touchstart', onTouchStart, { passive: true })\n el.addEventListener('touchmove', onTouchMove, { passive: false })\n el.addEventListener('touchend', endDrag)\n el.addEventListener('touchcancel', endDrag)\n }\n\n return () => {\n el.removeEventListener('mousedown', onMouseDown)\n window.removeEventListener('mousemove', onMouseMove)\n window.removeEventListener('mouseup', endDrag)\n delete el.dataset.panActive\n if (isMobile) {\n el.removeEventListener('touchstart', onTouchStart)\n el.removeEventListener('touchmove', onTouchMove)\n el.removeEventListener('touchend', endDrag)\n el.removeEventListener('touchcancel', endDrag)\n }\n }\n }, [isMobile, scrollRef])\n\n const handleCopyText = useCallback(async () => {\n const content = contentRef.current\n if (!content) return\n try {\n await navigator.clipboard.writeText(content.innerText)\n onCopy?.()\n } catch (error) {\n onError?.(error)\n }\n }, [contentRef, onCopy, onError])\n\n const totalHeight = pageCount * geo.heightPx + Math.max(0, pageCount - 1) * geo.gapPx\n\n return {\n pageCount,\n setPageCount,\n effectiveScale,\n isFitToWidth,\n totalHeight,\n geo,\n handleZoomIn,\n handleZoomOut,\n handleActualSize,\n handleFitToWidth,\n requestPrint,\n handleCopyText,\n minScale: MIN_SCALE,\n maxScale: MAX_SCALE,\n }\n}\n","import { MDXProvider } from '@mdx-js/react'\nimport clsx from 'clsx'\nimport { ClipboardCopy, Maximize2, Minus, Plus } from 'lucide-react'\nimport { useRef } from 'react'\nimport { resumeMdxComponents } from '../document/components'\nimport {\n PaginationReportContext,\n ResumeFontFamilyContext,\n ResumePageSizeContext,\n} from '../document/pagination-context'\n// Styles ship via the `@atom63/resume/styles` entry (import it once in your app)\n// — NOT self-imported here, so a consumer's token overrides reliably win the\n// cascade instead of racing a re-injected copy of the package CSS.\nimport type { ResumeViewerProps } from './resume-viewer.types'\nimport { useIsMobile } from './use-is-mobile'\nimport { useResumeViewport } from './use-resume-viewport'\n\nexport function ResumeViewer({\n Content,\n components,\n fontFamily,\n isActive = true,\n pageSize = 'letter',\n pdfFilename,\n onCopy,\n onError,\n className,\n toolbarStart,\n toolbarEnd,\n}: ResumeViewerProps) {\n const isMobile = useIsMobile()\n const scrollRef = useRef<HTMLDivElement>(null)\n const contentRef = useRef<HTMLDivElement>(null)\n const mdxComponents = { ...resumeMdxComponents, ...components }\n\n const {\n pageCount,\n setPageCount,\n effectiveScale,\n isFitToWidth,\n totalHeight,\n geo,\n handleZoomIn,\n handleZoomOut,\n handleActualSize,\n handleFitToWidth,\n requestPrint,\n handleCopyText,\n minScale,\n maxScale,\n } = useResumeViewport({\n scrollRef,\n contentRef,\n isMobile,\n isActive,\n pageSize,\n pdfFilename,\n onCopy,\n onError,\n })\n\n return (\n <div className=\"resume-viewer\">\n {/* Drives the printed paper size; @page can't read a CSS custom property. */}\n <style>{`@page { size: ${geo.cssPageSize}; margin: 0; }`}</style>\n <div className=\"resume-viewer-toolbar\">\n {toolbarStart}\n <span className=\"resume-viewer-pagecount\">\n {pageCount} page{pageCount > 1 ? 's' : ''}\n </span>\n <div className=\"resume-viewer-controls\">\n <button\n aria-label=\"Fit resume to width\"\n aria-pressed={isFitToWidth}\n className={clsx('resume-viewer-btn', isFitToWidth && 'resume-viewer-btn-active')}\n onClick={handleFitToWidth}\n title=\"Fit to width\"\n type=\"button\"\n >\n <Maximize2 className=\"resume-viewer-icon\" />\n </button>\n <div className=\"resume-viewer-btn-group\">\n <button\n aria-label=\"Zoom out\"\n className=\"resume-viewer-btn\"\n disabled={!isFitToWidth && effectiveScale <= minScale}\n onClick={handleZoomOut}\n title=\"Zoom out\"\n type=\"button\"\n >\n <Minus className=\"resume-viewer-icon\" />\n </button>\n <button\n className=\"resume-viewer-zoom-label\"\n onClick={handleActualSize}\n title=\"Actual size (100%)\"\n type=\"button\"\n >\n {Math.round(effectiveScale * 100)}%\n </button>\n <button\n aria-label=\"Zoom in\"\n className=\"resume-viewer-btn\"\n disabled={effectiveScale >= maxScale}\n onClick={handleZoomIn}\n title=\"Zoom in\"\n type=\"button\"\n >\n <Plus className=\"resume-viewer-icon\" />\n </button>\n </div>\n <button\n aria-label=\"Copy resume text\"\n className=\"resume-viewer-btn\"\n onClick={handleCopyText}\n title=\"Copy resume text\"\n type=\"button\"\n >\n <ClipboardCopy className=\"resume-viewer-icon\" />\n </button>\n <button\n className=\"resume-viewer-btn resume-viewer-btn-primary\"\n onClick={requestPrint}\n type=\"button\"\n >\n Save to PDF\n </button>\n {toolbarEnd}\n </div>\n </div>\n\n <div\n className={clsx(\n 'resume-viewer-scroll',\n isMobile ? 'resume-viewer-scroll-mobile' : 'resume-viewer-scroll-grab',\n className\n )}\n ref={scrollRef}\n >\n <section\n aria-label={\n isMobile\n ? 'Resume preview. Drag with one finger to pan, pinch to zoom.'\n : 'Resume preview. Drag to pan, Ctrl+scroll to zoom.'\n }\n className=\"resume-viewer-stage\"\n >\n <div\n className=\"resume-viewer-page-box\"\n style={{\n width: geo.widthPx * effectiveScale,\n height: totalHeight * effectiveScale,\n }}\n >\n <div\n className=\"resume-viewer-scale-root\"\n data-resume-scale-root=\"\"\n style={{\n width: geo.widthPx,\n height: totalHeight,\n transform: effectiveScale === 1 ? undefined : `scale(${effectiveScale})`,\n }}\n >\n <ResumePageSizeContext.Provider value={pageSize}>\n <ResumeFontFamilyContext.Provider value={fontFamily}>\n <PaginationReportContext.Provider value={setPageCount}>\n <MDXProvider components={mdxComponents}>\n <div\n className=\"resume-viewer-content\"\n id=\"resume-print-target\"\n ref={contentRef}\n style={{ zIndex: 1 }}\n >\n <Content />\n </div>\n </MDXProvider>\n </PaginationReportContext.Provider>\n </ResumeFontFamilyContext.Provider>\n </ResumePageSizeContext.Provider>\n </div>\n </div>\n </section>\n </div>\n </div>\n )\n}\n"]}
@@ -58,11 +58,23 @@ function resumeWriteBackPlugin(options = {}) {
58
58
  }
59
59
  }
60
60
  server.watcher.unwatch(resumeAbs);
61
- const result = await handleWriteBack({ resumePath, root: server.config.root }, body);
62
- setTimeout(() => server.watcher.add(resumeAbs), 250);
63
- res.statusCode = result.ok ? 200 : 400;
64
- res.setHeader("content-type", "application/json");
65
- res.end(JSON.stringify(result));
61
+ try {
62
+ const result = await handleWriteBack({ resumePath, root: server.config.root }, body);
63
+ res.statusCode = result.ok ? 200 : 400;
64
+ res.setHeader("content-type", "application/json");
65
+ res.end(JSON.stringify(result));
66
+ } catch (error) {
67
+ res.statusCode = 500;
68
+ res.setHeader("content-type", "application/json");
69
+ res.end(
70
+ JSON.stringify({
71
+ ok: false,
72
+ error: error instanceof Error ? error.message : "Failed to write resume"
73
+ })
74
+ );
75
+ } finally {
76
+ setTimeout(() => server.watcher.add(resumeAbs), 250);
77
+ }
66
78
  });
67
79
  }
68
80
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/vite/mdx-raw-plugin.ts","../../src/vite/write-back.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import fs from 'node:fs/promises'\nimport type { Plugin } from 'vite'\n\nconst RAW_MDX = /\\.mdx\\?raw$/\nconst VIRTUAL_PREFIX = '\\0mdx-raw:'\nconst VIRTUAL_SUFFIX = '.raw'\n\n/**\n * Serve raw `.mdx?raw` imports as plain strings.\n *\n * Vite's built-in `?raw` works for most files, but `@mdx-js/rollup` strips the\n * query and compiles every `.mdx` to a component — so `?raw` would otherwise\n * yield the compiled module, not the source. This `pre` plugin resolves\n * `*.mdx?raw` to a virtual id ending in `.raw` (not `.mdx`), which MDX's\n * extension check skips, then loads the file text for it.\n *\n * Standalone resume consumers need this so `import source from './resume.mdx?raw'`\n * returns the raw MDX text (e.g. to feed the in-app editor / source view).\n */\nexport function mdxRawPlugin(): Plugin {\n return {\n name: 'mdx-raw',\n enforce: 'pre',\n async resolveId(id, importer) {\n if (!RAW_MDX.test(id)) {\n return null\n }\n const clean = id.replace(/\\?raw$/, '')\n const resolved = await this.resolve(clean, importer, { skipSelf: true })\n const filePath = resolved?.id ?? clean\n return `${VIRTUAL_PREFIX}${filePath}${VIRTUAL_SUFFIX}`\n },\n async load(id) {\n if (!id.startsWith(VIRTUAL_PREFIX)) {\n return null\n }\n const filePath = id.slice(VIRTUAL_PREFIX.length, id.length - VIRTUAL_SUFFIX.length)\n const code = await fs.readFile(filePath, 'utf-8')\n return { code: `export default ${JSON.stringify(code)}`, map: null }\n },\n }\n}\n","import { writeFile } from 'node:fs/promises'\nimport { isAbsolute, relative, resolve } from 'node:path'\nimport type { Plugin } from 'vite'\n\nexport interface ResumeWriteBackOptions {\n /** Path (absolute or relative to Vite root) of the resume file to persist to. */\n resumePath?: string\n /** POST endpoint. Default '/__write-resume'. */\n endpoint?: string\n}\n\n/** @internal Not part of the public API — use `resumeWriteBackPlugin` instead. */\nexport async function handleWriteBack(\n cfg: { resumePath: string; root: string },\n body: string\n): Promise<{ ok: boolean; error?: string }> {\n const abs = isAbsolute(cfg.resumePath) ? cfg.resumePath : resolve(cfg.root, cfg.resumePath)\n const rel = relative(cfg.root, abs)\n if (rel.startsWith('..') || isAbsolute(rel)) {\n return { ok: false, error: 'resume path escapes project root' }\n }\n await writeFile(abs, body, 'utf8')\n return { ok: true }\n}\n\nconst MAX_BODY = 5_000_000\n\n/** DEV-ONLY plugin: POST <endpoint> writes the request body to the resume file. */\nexport function resumeWriteBackPlugin(options: ResumeWriteBackOptions = {}): Plugin {\n const endpoint = options.endpoint ?? '/__write-resume'\n const resumePath = options.resumePath ?? 'src/resume.mdx'\n\n return {\n name: '@atom63/resume:resume-write-back',\n apply: 'serve',\n configureServer(server) {\n const resumeAbs = isAbsolute(resumePath)\n ? resumePath\n : resolve(server.config.root, resumePath)\n server.middlewares.use(async (req, res, next) => {\n if (req.method !== 'POST' || req.url?.split('?')[0] !== endpoint) return next()\n let body = ''\n for await (const chunk of req) {\n body += chunk\n if (body.length > MAX_BODY) {\n res.statusCode = 413\n res.end()\n return\n }\n }\n // A GUI Save writes the resume file, which Vite would otherwise full-reload\n // — dropping you out of the editor (the editor already holds this content\n // in state, so the reload is pure disruption). Unwatch the resume file\n // around our own write so the change event never fires, then re-watch\n // shortly after so EXTERNAL edits (you / your agent editing resume.mdx)\n // still hot-reload normally.\n server.watcher.unwatch(resumeAbs)\n const result = await handleWriteBack({ resumePath, root: server.config.root }, body)\n setTimeout(() => server.watcher.add(resumeAbs), 250)\n res.statusCode = result.ok ? 200 : 400\n res.setHeader('content-type', 'application/json')\n res.end(JSON.stringify(result))\n })\n },\n }\n}\n"]}
1
+ {"version":3,"sources":["../../src/vite/mdx-raw-plugin.ts","../../src/vite/write-back.ts"],"names":[],"mappings":";;;;AAGA,IAAM,OAAA,GAAU,aAAA;AAChB,IAAM,cAAA,GAAiB,YAAA;AACvB,IAAM,cAAA,GAAiB,MAAA;AAchB,SAAS,YAAA,GAAuB;AACrC,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,SAAA;AAAA,IACN,OAAA,EAAS,KAAA;AAAA,IACT,MAAM,SAAA,CAAU,EAAA,EAAI,QAAA,EAAU;AAC5B,MAAA,IAAI,CAAC,OAAA,CAAQ,IAAA,CAAK,EAAE,CAAA,EAAG;AACrB,QAAA,OAAO,IAAA;AAAA,MACT;AACA,MAAA,MAAM,KAAA,GAAQ,EAAA,CAAG,OAAA,CAAQ,QAAA,EAAU,EAAE,CAAA;AACrC,MAAA,MAAM,QAAA,GAAW,MAAM,IAAA,CAAK,OAAA,CAAQ,OAAO,QAAA,EAAU,EAAE,QAAA,EAAU,IAAA,EAAM,CAAA;AACvE,MAAA,MAAM,QAAA,GAAW,UAAU,EAAA,IAAM,KAAA;AACjC,MAAA,OAAO,CAAA,EAAG,cAAc,CAAA,EAAG,QAAQ,GAAG,cAAc,CAAA,CAAA;AAAA,IACtD,CAAA;AAAA,IACA,MAAM,KAAK,EAAA,EAAI;AACb,MAAA,IAAI,CAAC,EAAA,CAAG,UAAA,CAAW,cAAc,CAAA,EAAG;AAClC,QAAA,OAAO,IAAA;AAAA,MACT;AACA,MAAA,MAAM,QAAA,GAAW,GAAG,KAAA,CAAM,cAAA,CAAe,QAAQ,EAAA,CAAG,MAAA,GAAS,eAAe,MAAM,CAAA;AAClF,MAAA,MAAM,IAAA,GAAO,MAAM,EAAA,CAAG,QAAA,CAAS,UAAU,OAAO,CAAA;AAChD,MAAA,OAAO,EAAE,MAAM,CAAA,eAAA,EAAkB,IAAA,CAAK,UAAU,IAAI,CAAC,CAAA,CAAA,EAAI,GAAA,EAAK,IAAA,EAAK;AAAA,IACrE;AAAA,GACF;AACF;AC7BA,eAAsB,eAAA,CACpB,KACA,IAAA,EAC0C;AAC1C,EAAA,MAAM,GAAA,GAAM,UAAA,CAAW,GAAA,CAAI,UAAU,CAAA,GAAI,GAAA,CAAI,UAAA,GAAa,OAAA,CAAQ,GAAA,CAAI,IAAA,EAAM,GAAA,CAAI,UAAU,CAAA;AAC1F,EAAA,MAAM,GAAA,GAAM,QAAA,CAAS,GAAA,CAAI,IAAA,EAAM,GAAG,CAAA;AAClC,EAAA,IAAI,IAAI,UAAA,CAAW,IAAI,CAAA,IAAK,UAAA,CAAW,GAAG,CAAA,EAAG;AAC3C,IAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,KAAA,EAAO,kCAAA,EAAmC;AAAA,EAChE;AACA,EAAA,MAAM,SAAA,CAAU,GAAA,EAAK,IAAA,EAAM,MAAM,CAAA;AACjC,EAAA,OAAO,EAAE,IAAI,IAAA,EAAK;AACpB;AAEA,IAAM,QAAA,GAAW,GAAA;AAGV,SAAS,qBAAA,CAAsB,OAAA,GAAkC,EAAC,EAAW;AAClF,EAAA,MAAM,QAAA,GAAW,QAAQ,QAAA,IAAY,iBAAA;AACrC,EAAA,MAAM,UAAA,GAAa,QAAQ,UAAA,IAAc,gBAAA;AAEzC,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,kCAAA;AAAA,IACN,KAAA,EAAO,OAAA;AAAA,IACP,gBAAgB,MAAA,EAAQ;AACtB,MAAA,MAAM,SAAA,GAAY,WAAW,UAAU,CAAA,GACnC,aACA,OAAA,CAAQ,MAAA,CAAO,MAAA,CAAO,IAAA,EAAM,UAAU,CAAA;AAC1C,MAAA,MAAA,CAAO,WAAA,CAAY,GAAA,CAAI,OAAO,GAAA,EAAK,KAAK,IAAA,KAAS;AAC/C,QAAA,IAAI,GAAA,CAAI,MAAA,KAAW,MAAA,IAAU,GAAA,CAAI,GAAA,EAAK,KAAA,CAAM,GAAG,CAAA,CAAE,CAAC,CAAA,KAAM,QAAA,EAAU,OAAO,IAAA,EAAK;AAC9E,QAAA,IAAI,IAAA,GAAO,EAAA;AACX,QAAA,WAAA,MAAiB,SAAS,GAAA,EAAK;AAC7B,UAAA,IAAA,IAAQ,KAAA;AACR,UAAA,IAAI,IAAA,CAAK,SAAS,QAAA,EAAU;AAC1B,YAAA,GAAA,CAAI,UAAA,GAAa,GAAA;AACjB,YAAA,GAAA,CAAI,GAAA,EAAI;AACR,YAAA;AAAA,UACF;AAAA,QACF;AAOA,QAAA,MAAA,CAAO,OAAA,CAAQ,QAAQ,SAAS,CAAA;AAChC,QAAA,IAAI;AACF,UAAA,MAAM,MAAA,GAAS,MAAM,eAAA,CAAgB,EAAE,UAAA,EAAY,MAAM,MAAA,CAAO,MAAA,CAAO,IAAA,EAAK,EAAG,IAAI,CAAA;AACnF,UAAA,GAAA,CAAI,UAAA,GAAa,MAAA,CAAO,EAAA,GAAK,GAAA,GAAM,GAAA;AACnC,UAAA,GAAA,CAAI,SAAA,CAAU,gBAAgB,kBAAkB,CAAA;AAChD,UAAA,GAAA,CAAI,GAAA,CAAI,IAAA,CAAK,SAAA,CAAU,MAAM,CAAC,CAAA;AAAA,QAChC,SAAS,KAAA,EAAO;AACd,UAAA,GAAA,CAAI,UAAA,GAAa,GAAA;AACjB,UAAA,GAAA,CAAI,SAAA,CAAU,gBAAgB,kBAAkB,CAAA;AAChD,UAAA,GAAA,CAAI,GAAA;AAAA,YACF,KAAK,SAAA,CAAU;AAAA,cACb,EAAA,EAAI,KAAA;AAAA,cACJ,KAAA,EAAO,KAAA,YAAiB,KAAA,GAAQ,KAAA,CAAM,OAAA,GAAU;AAAA,aACjD;AAAA,WACH;AAAA,QACF,CAAA,SAAE;AACA,UAAA,UAAA,CAAW,MAAM,MAAA,CAAO,OAAA,CAAQ,GAAA,CAAI,SAAS,GAAG,GAAG,CAAA;AAAA,QACrD;AAAA,MACF,CAAC,CAAA;AAAA,IACH;AAAA,GACF;AACF","file":"index.js","sourcesContent":["import fs from 'node:fs/promises'\nimport type { Plugin } from 'vite'\n\nconst RAW_MDX = /\\.mdx\\?raw$/\nconst VIRTUAL_PREFIX = '\\0mdx-raw:'\nconst VIRTUAL_SUFFIX = '.raw'\n\n/**\n * Serve raw `.mdx?raw` imports as plain strings.\n *\n * Vite's built-in `?raw` works for most files, but `@mdx-js/rollup` strips the\n * query and compiles every `.mdx` to a component — so `?raw` would otherwise\n * yield the compiled module, not the source. This `pre` plugin resolves\n * `*.mdx?raw` to a virtual id ending in `.raw` (not `.mdx`), which MDX's\n * extension check skips, then loads the file text for it.\n *\n * Standalone resume consumers need this so `import source from './resume.mdx?raw'`\n * returns the raw MDX text (e.g. to feed the in-app editor / source view).\n */\nexport function mdxRawPlugin(): Plugin {\n return {\n name: 'mdx-raw',\n enforce: 'pre',\n async resolveId(id, importer) {\n if (!RAW_MDX.test(id)) {\n return null\n }\n const clean = id.replace(/\\?raw$/, '')\n const resolved = await this.resolve(clean, importer, { skipSelf: true })\n const filePath = resolved?.id ?? clean\n return `${VIRTUAL_PREFIX}${filePath}${VIRTUAL_SUFFIX}`\n },\n async load(id) {\n if (!id.startsWith(VIRTUAL_PREFIX)) {\n return null\n }\n const filePath = id.slice(VIRTUAL_PREFIX.length, id.length - VIRTUAL_SUFFIX.length)\n const code = await fs.readFile(filePath, 'utf-8')\n return { code: `export default ${JSON.stringify(code)}`, map: null }\n },\n }\n}\n","import { writeFile } from 'node:fs/promises'\nimport { isAbsolute, relative, resolve } from 'node:path'\nimport type { Plugin } from 'vite'\n\nexport interface ResumeWriteBackOptions {\n /** Path (absolute or relative to Vite root) of the resume file to persist to. */\n resumePath?: string\n /** POST endpoint. Default '/__write-resume'. */\n endpoint?: string\n}\n\n/** @internal Not part of the public API — use `resumeWriteBackPlugin` instead. */\nexport async function handleWriteBack(\n cfg: { resumePath: string; root: string },\n body: string\n): Promise<{ ok: boolean; error?: string }> {\n const abs = isAbsolute(cfg.resumePath) ? cfg.resumePath : resolve(cfg.root, cfg.resumePath)\n const rel = relative(cfg.root, abs)\n if (rel.startsWith('..') || isAbsolute(rel)) {\n return { ok: false, error: 'resume path escapes project root' }\n }\n await writeFile(abs, body, 'utf8')\n return { ok: true }\n}\n\nconst MAX_BODY = 5_000_000\n\n/** DEV-ONLY plugin: POST <endpoint> writes the request body to the resume file. */\nexport function resumeWriteBackPlugin(options: ResumeWriteBackOptions = {}): Plugin {\n const endpoint = options.endpoint ?? '/__write-resume'\n const resumePath = options.resumePath ?? 'src/resume.mdx'\n\n return {\n name: '@atom63/resume:resume-write-back',\n apply: 'serve',\n configureServer(server) {\n const resumeAbs = isAbsolute(resumePath)\n ? resumePath\n : resolve(server.config.root, resumePath)\n server.middlewares.use(async (req, res, next) => {\n if (req.method !== 'POST' || req.url?.split('?')[0] !== endpoint) return next()\n let body = ''\n for await (const chunk of req) {\n body += chunk\n if (body.length > MAX_BODY) {\n res.statusCode = 413\n res.end()\n return\n }\n }\n // A GUI Save writes the resume file, which Vite would otherwise full-reload\n // — dropping you out of the editor (the editor already holds this content\n // in state, so the reload is pure disruption). Unwatch the resume file\n // around our own write so the change event never fires, then re-watch\n // shortly after so EXTERNAL edits (you / your agent editing resume.mdx)\n // still hot-reload normally.\n server.watcher.unwatch(resumeAbs)\n try {\n const result = await handleWriteBack({ resumePath, root: server.config.root }, body)\n res.statusCode = result.ok ? 200 : 400\n res.setHeader('content-type', 'application/json')\n res.end(JSON.stringify(result))\n } catch (error) {\n res.statusCode = 500\n res.setHeader('content-type', 'application/json')\n res.end(\n JSON.stringify({\n ok: false,\n error: error instanceof Error ? error.message : 'Failed to write resume',\n })\n )\n } finally {\n setTimeout(() => server.watcher.add(resumeAbs), 250)\n }\n })\n },\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atom63/resume",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "description": "Write resumes and CVs as MDX — a paginated, print-ready document engine + viewer for React, styled with a self-contained paper/document token system.",
@@ -62,8 +62,16 @@
62
62
  ],
63
63
  "files": [
64
64
  "dist",
65
- "src/styles"
65
+ "src/styles",
66
+ "CHANGELOG.md"
66
67
  ],
68
+ "scripts": {
69
+ "build": "tsup",
70
+ "dev": "tsup --watch",
71
+ "test": "vitest run",
72
+ "typecheck": "tsc --noEmit",
73
+ "lint": "biome check ."
74
+ },
67
75
  "dependencies": {
68
76
  "@mdx-js/mdx": "^3.1.1",
69
77
  "@mdx-js/react": "^3.1.1",
@@ -81,6 +89,8 @@
81
89
  }
82
90
  },
83
91
  "devDependencies": {
92
+ "@atom63/biome-config": "workspace:*",
93
+ "@atom63/tsconfig": "workspace:*",
84
94
  "@biomejs/biome": "2.4.6",
85
95
  "@testing-library/dom": "10.4.1",
86
96
  "@testing-library/jest-dom": "6.9.1",
@@ -94,15 +104,6 @@
94
104
  "react-dom": "^19.1.1",
95
105
  "tsup": "^8.3.5",
96
106
  "typescript": "~5.8.3",
97
- "vitest": "^4.1.8",
98
- "@atom63/biome-config": "0.1.0",
99
- "@atom63/tsconfig": "0.1.0"
100
- },
101
- "scripts": {
102
- "build": "tsup",
103
- "dev": "tsup --watch",
104
- "test": "vitest run",
105
- "typecheck": "tsc --noEmit",
106
- "lint": "biome check ."
107
+ "vitest": "^4.1.8"
107
108
  }
108
- }
109
+ }
@@ -6,6 +6,31 @@
6
6
  * or restyle without forking these classes.
7
7
  */
8
8
 
9
+ /*
10
+ * The page frames set width/height to the paper size and add the margins as
11
+ * padding, so they MUST be border-box. Scope a border-box reset to the document
12
+ * subtree — the package can't assume the host ships a global reset (without it a
13
+ * standalone consumer gets oversized, off-center pages and a wider content area).
14
+ */
15
+ [data-resume-document],
16
+ [data-resume-document] *,
17
+ [data-resume-document] *::before,
18
+ [data-resume-document] *::after {
19
+ box-sizing: border-box;
20
+ }
21
+
22
+ /*
23
+ * Zero the user-agent margins on flow elements. The layout spaces blocks with
24
+ * flex `gap`; default heading/paragraph/list margins ADD on top of it and blow
25
+ * out the vertical rhythm (the package can't assume the host ships a reset like
26
+ * Tailwind preflight). `:where()` keeps specificity 0 so the .doc-* classes
27
+ * below — e.g. .doc-section's intentional margin-top — still win.
28
+ */
29
+ [data-resume-document]
30
+ :where(h1, h2, h3, h4, h5, h6, p, ul, ol, li, figure, blockquote, dl, dd, pre) {
31
+ margin: 0;
32
+ }
33
+
9
34
  /* Layout primitives */
10
35
  .doc-meta-grid {
11
36
  display: grid;
@@ -164,7 +189,9 @@
164
189
  flex-direction: column;
165
190
  background: #fff;
166
191
  color: #000;
192
+ /* Paper on a desk: a tight contact shadow + a soft ambient lift. */
167
193
  box-shadow:
168
- 0 10px 15px -3px rgb(0 0 0 / 0.1),
169
- 0 4px 6px -4px rgb(0 0 0 / 0.1);
194
+ 0 0 0 1px rgb(24 24 27 / 0.04),
195
+ 0 1px 2px rgb(24 24 27 / 0.04),
196
+ 0 12px 32px -12px rgb(24 24 27 / 0.18);
170
197
  }
@@ -9,14 +9,19 @@
9
9
  * is selected with the ResumeViewer `pageSize` prop ('letter' | 'a4').
10
10
  *
11
11
  * Print-fixed by design: doc sizes are absolute px for PDF fidelity, independent
12
- * of any host typography-scale setting. The font *family* is inherited (so the
13
- * document follows the host font), which is why pagination re-measures on font
14
- * change.
12
+ * of any host typography-scale setting. The font family defaults to a clean sans
13
+ * stack via --doc-font; set --doc-font: inherit to follow the host font instead
14
+ * (pagination re-measures on font change).
15
15
  *
16
16
  * NOTE: --doc-block-gap MUST stay in sync with BLOCK_GAP_PX in geometry.ts (the
17
17
  * JS packer reads the same number to compute page breaks).
18
18
  */
19
19
  [data-resume-document] {
20
+ /* Font — follows the host app's font by default (so an embedded resume matches
21
+ its surroundings). Set --doc-font to a specific stack to override; the
22
+ create-resume scaffold sets a clean sans stack in its index.css. */
23
+ font-family: var(--doc-font, inherit);
24
+
20
25
  /* Document layout */
21
26
  --doc-meta-col: 11rem; /* left meta/label column (header name, section label, year|role) */
22
27
  --doc-resume-cols: 1fr 2fr; /* resume sidebar | main split */
@@ -26,7 +31,8 @@
26
31
  --doc-section-gap: 0.5rem; /* space above a section */
27
32
  --doc-group-gap: 0.125rem; /* within a tight group (title + meta line) */
28
33
 
29
- /* Type */
34
+ /* Type — package default matches OS63 (10px). The scaffold bumps --doc-text to
35
+ 11px in its index.css for a more readable starting point. */
30
36
  --doc-text: 10px;
31
37
  --doc-text-xs: 8px;
32
38
  --doc-leading: 1.4;
@@ -15,6 +15,19 @@
15
15
  the old muted canvas; the page frames come from the document and paper tokens
16
16
  defined in tokens.css and document.css. */
17
17
  .resume-viewer {
18
+ /* Chrome tokens — cool-tinted neutrals (never pure black/white). */
19
+ --rv-surface: oklch(0.995 0.001 264);
20
+ --rv-canvas: oklch(0.965 0.002 264);
21
+ --rv-border: oklch(0.28 0.01 264 / 0.1);
22
+ --rv-ink: oklch(0.32 0.008 264);
23
+ --rv-ink-muted: oklch(0.56 0.006 264);
24
+ --rv-hover: oklch(0.28 0.01 264 / 0.06);
25
+ --rv-active: oklch(0.28 0.01 264 / 0.1);
26
+ --rv-primary: oklch(0.34 0.012 264);
27
+ --rv-primary-hover: oklch(0.28 0.012 264);
28
+ --rv-primary-ink: oklch(0.98 0.001 264);
29
+ --rv-radius: 0.4375rem;
30
+
18
31
  display: flex;
19
32
  flex-direction: column;
20
33
  height: 100%;
@@ -26,78 +39,101 @@
26
39
  align-items: center;
27
40
  justify-content: space-between;
28
41
  gap: 0.75rem;
29
- padding: 0.375rem 0.75rem;
30
- border-bottom: 1px solid rgb(0 0 0 / 0.08);
31
- background: #fff;
42
+ padding: 0.4375rem 0.75rem;
43
+ border-bottom: 1px solid var(--rv-border);
44
+ background: var(--rv-surface);
32
45
  flex: 0 0 auto;
33
46
  }
34
47
 
35
48
  .resume-viewer-pagecount {
36
49
  font-size: 0.8125rem;
37
- color: rgb(0 0 0 / 0.55);
50
+ font-variant-numeric: tabular-nums;
51
+ color: var(--rv-ink-muted);
52
+ letter-spacing: 0.01em;
38
53
  }
39
54
 
40
55
  .resume-viewer-controls {
41
56
  display: flex;
42
57
  align-items: center;
43
- gap: 0.25rem;
58
+ gap: 0.375rem;
44
59
  }
45
60
 
61
+ /* Zoom out / % / in read as one control — group them in a subtle inset pill. */
46
62
  .resume-viewer-btn-group {
47
63
  display: flex;
48
64
  align-items: center;
65
+ gap: 0.125rem;
66
+ padding: 0.125rem;
67
+ border-radius: calc(var(--rv-radius) + 0.125rem);
68
+ background: var(--rv-hover);
49
69
  }
50
70
 
51
71
  .resume-viewer-btn {
52
72
  display: inline-flex;
53
73
  align-items: center;
54
74
  justify-content: center;
55
- height: 2rem;
56
- min-width: 2rem;
57
- padding: 0 0.375rem;
75
+ height: 1.875rem;
76
+ min-width: 1.875rem;
77
+ padding: 0 0.4375rem;
58
78
  border: 0;
59
- border-radius: 0.375rem;
79
+ border-radius: var(--rv-radius);
60
80
  background: transparent;
61
- color: rgb(0 0 0 / 0.7);
81
+ color: var(--rv-ink);
62
82
  font-size: 0.8125rem;
63
83
  cursor: pointer;
84
+ transition: background-color 120ms ease-out;
64
85
  }
65
86
 
66
87
  .resume-viewer-btn:hover:not(:disabled) {
67
- background: rgb(0 0 0 / 0.06);
88
+ background: var(--rv-active);
89
+ }
90
+
91
+ .resume-viewer-btn:active:not(:disabled) {
92
+ background: var(--rv-active);
93
+ transform: translateY(0.5px);
68
94
  }
69
95
 
70
96
  .resume-viewer-btn:disabled {
71
- opacity: 0.4;
97
+ opacity: 0.35;
72
98
  cursor: default;
73
99
  }
74
100
 
75
101
  .resume-viewer-btn-active {
76
- background: rgb(0 0 0 / 0.08);
77
- color: rgb(0 0 0 / 0.9);
102
+ background: var(--rv-active);
103
+ color: var(--rv-ink);
104
+ }
105
+
106
+ /* Save to PDF — the primary action. */
107
+ .resume-viewer-btn-primary {
108
+ padding: 0 0.875rem;
109
+ background: var(--rv-primary);
110
+ color: var(--rv-primary-ink);
111
+ font-weight: 500;
78
112
  }
79
113
 
80
- .resume-viewer-btn-text {
81
- padding: 0 0.625rem;
114
+ .resume-viewer-btn-primary:hover:not(:disabled) {
115
+ background: var(--rv-primary-hover);
82
116
  }
83
117
 
84
118
  .resume-viewer-zoom-label {
85
119
  display: inline-flex;
86
120
  align-items: center;
87
121
  justify-content: center;
88
- height: 2rem;
89
- width: 3rem;
122
+ height: 1.875rem;
123
+ width: 2.875rem;
90
124
  border: 0;
91
- border-radius: 0.375rem;
125
+ border-radius: var(--rv-radius);
92
126
  background: transparent;
93
- color: rgb(0 0 0 / 0.55);
127
+ color: var(--rv-ink-muted);
94
128
  font-size: 0.8125rem;
95
129
  font-variant-numeric: tabular-nums;
96
130
  cursor: pointer;
131
+ transition: background-color 120ms ease-out;
97
132
  }
98
133
 
99
134
  .resume-viewer-zoom-label:hover {
100
- background: rgb(0 0 0 / 0.06);
135
+ background: var(--rv-active);
136
+ color: var(--rv-ink);
101
137
  }
102
138
 
103
139
  .resume-viewer-icon {
@@ -109,7 +145,7 @@
109
145
  flex: 1 1 auto;
110
146
  min-height: 0;
111
147
  overflow: auto;
112
- background: rgb(244 244 245);
148
+ background: var(--rv-canvas);
113
149
  }
114
150
 
115
151
  .resume-viewer-scroll-grab {