@atom63/resume 0.1.0 → 0.2.0

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.
@@ -4,16 +4,30 @@ import { jsx, jsxs } from 'react/jsx-runtime';
4
4
 
5
5
  // src/geometry.ts
6
6
  var DPI = 96;
7
- var PAGE = {
8
- widthPx: 8.5 * DPI,
9
- heightPx: 11 * DPI,
10
- padXPx: 0.7 * DPI,
11
- padYPx: 0.6 * DPI,
12
- gapPx: 32
13
- // screen-only gap between page frames
7
+ var MM = DPI / 25.4;
8
+ var PAD_X_PX = 0.7 * DPI;
9
+ var PAD_Y_PX = 0.6 * DPI;
10
+ var GAP_PX = 32;
11
+ var DIMENSIONS = {
12
+ letter: { widthPx: 8.5 * DPI, heightPx: 11 * DPI, cssPageSize: "letter" },
13
+ a4: { widthPx: 210 * MM, heightPx: 297 * MM, cssPageSize: "A4" }
14
14
  };
15
- var CONTENT_WIDTH_PX = PAGE.widthPx - 2 * PAGE.padXPx;
16
- var USABLE_HEIGHT_PX = PAGE.heightPx - 2 * PAGE.padYPx;
15
+ function getPageGeometry(size = "letter") {
16
+ const d = DIMENSIONS[size];
17
+ return {
18
+ widthPx: d.widthPx,
19
+ heightPx: d.heightPx,
20
+ padXPx: PAD_X_PX,
21
+ padYPx: PAD_Y_PX,
22
+ gapPx: GAP_PX,
23
+ contentWidthPx: d.widthPx - 2 * PAD_X_PX,
24
+ usableHeightPx: d.heightPx - 2 * PAD_Y_PX,
25
+ cssPageSize: d.cssPageSize
26
+ };
27
+ }
28
+ var PAGE = getPageGeometry("letter");
29
+ PAGE.contentWidthPx;
30
+ PAGE.usableHeightPx;
17
31
  var BLOCK_GAP_PX = 6;
18
32
  var WRAPPER_PADDING_X = 32;
19
33
 
@@ -70,6 +84,8 @@ function usePaginationReport() {
70
84
  }
71
85
  var ResumeFontFamilyContext = createContext(void 0);
72
86
  var useResumeFontFamily = () => useContext(ResumeFontFamilyContext);
87
+ var ResumePageSizeContext = createContext("letter");
88
+ var useResumePageSize = () => useContext(ResumePageSizeContext);
73
89
  var baseMdxComponents = {
74
90
  h2: (props) => /* @__PURE__ */ jsx("h2", { ...props }),
75
91
  h5: (props) => /* @__PURE__ */ jsx("h5", { ...props }),
@@ -253,11 +269,13 @@ function blockHeight(el) {
253
269
  function PaginatedResume({ children }) {
254
270
  const { header, footer, blocks } = extract(children);
255
271
  const fontFamily = useResumeFontFamily();
272
+ const pageSize = useResumePageSize();
273
+ const geo = getPageGeometry(pageSize);
256
274
  const report = usePaginationReport();
257
275
  const measureRef = useRef(null);
258
276
  const [heights, setHeights] = useState(null);
259
277
  const [headerH, setHeaderH] = useState(0);
260
- const contentKey = `${fontFamily}:${blocks.sidebar.length}:${blocks.main.length}`;
278
+ const contentKey = `${fontFamily}:${pageSize}:${blocks.sidebar.length}:${blocks.main.length}`;
261
279
  useLayoutEffect(() => {
262
280
  const root = measureRef.current;
263
281
  if (!root) return;
@@ -304,7 +322,7 @@ function PaginatedResume({ children }) {
304
322
  position: "absolute",
305
323
  top: 0,
306
324
  left: -99999,
307
- width: CONTENT_WIDTH_PX,
325
+ width: geo.contentWidthPx,
308
326
  visibility: "hidden",
309
327
  pointerEvents: "none"
310
328
  },
@@ -335,12 +353,12 @@ function PaginatedResume({ children }) {
335
353
  const mainBlocks = toBlocks("main");
336
354
  const pack = packIntoPages({
337
355
  columns: { sidebar: sidebarBlocks, main: mainBlocks },
338
- pageUsableHeight: (p) => p === 0 ? Math.max(0, USABLE_HEIGHT_PX - headerH) : USABLE_HEIGHT_PX,
356
+ pageUsableHeight: (p) => p === 0 ? Math.max(0, geo.usableHeightPx - headerH) : geo.usableHeightPx,
339
357
  blockGap: BLOCK_GAP_PX
340
358
  });
341
359
  return /* @__PURE__ */ jsxs("div", { "data-resume-document": true, children: [
342
360
  measureLayer,
343
- /* @__PURE__ */ jsx("div", { className: "doc-pages", style: { gap: PAGE.gapPx }, children: Array.from({ length: pack.pageCount }, (_, p) => {
361
+ /* @__PURE__ */ jsx("div", { className: "doc-pages", style: { gap: geo.gapPx }, children: Array.from({ length: pack.pageCount }, (_, p) => {
344
362
  const sideOnPage = blocks.sidebar.filter(
345
363
  (_2, i) => pack.pageOf.sidebar[`sidebar-${i}`] === p
346
364
  );
@@ -352,10 +370,10 @@ function PaginatedResume({ children }) {
352
370
  className: "doc-page",
353
371
  "data-resume-page": true,
354
372
  style: {
355
- width: PAGE.widthPx,
356
- height: PAGE.heightPx,
373
+ width: geo.widthPx,
374
+ height: geo.heightPx,
357
375
  overflow: "hidden",
358
- padding: `${PAGE.padYPx}px ${PAGE.padXPx}px`
376
+ padding: `${geo.padYPx}px ${geo.padXPx}px`
359
377
  },
360
378
  children: [
361
379
  p === 0 && header,
@@ -379,6 +397,6 @@ function PageCountReporter({ count, report }) {
379
397
  return null;
380
398
  }
381
399
 
382
- export { BLOCK_GAP_PX, Columns, Entry, Footer, Group, Header, Main, PAGE, PaginatedResume, PaginationReportContext, ResumeFontFamilyContext, Rule, Section, Sidebar, WRAPPER_PADDING_X, packIntoPages, resumeMdxComponents, usePaginationReport, useResumeFontFamily };
383
- //# sourceMappingURL=chunk-FL25EF7U.js.map
384
- //# sourceMappingURL=chunk-FL25EF7U.js.map
400
+ export { BLOCK_GAP_PX, Columns, Entry, Footer, Group, Header, Main, PAGE, PaginatedResume, PaginationReportContext, ResumeFontFamilyContext, ResumePageSizeContext, Rule, Section, Sidebar, WRAPPER_PADDING_X, getPageGeometry, packIntoPages, resumeMdxComponents, usePaginationReport, useResumeFontFamily, useResumePageSize };
401
+ //# sourceMappingURL=chunk-ARBKVUHN.js.map
402
+ //# sourceMappingURL=chunk-ARBKVUHN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/geometry.ts","../src/pagination/pagination.ts","../src/document/pagination-context.tsx","../src/mdx/base-components.tsx","../src/mdx/external-href.ts","../src/document/components.tsx","../src/document/paginated-resume.tsx"],"names":["jsx","jsxs","_"],"mappings":"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","file":"chunk-ARBKVUHN.js","sourcesContent":["// packages/resume/src/geometry.ts\n// Numeric page geometry — the single source of truth shared by the JS packer\n// and the rendered page frames. Print-fixed px at 96dpi for PDF fidelity.\n\nconst DPI = 96\nconst MM = DPI / 25.4\n\nexport type PageSize = 'letter' | 'a4'\n\nexport interface PageGeometry {\n widthPx: number\n heightPx: number\n padXPx: number\n padYPx: number\n /** Screen-only gap between page frames. */\n gapPx: number\n contentWidthPx: number\n usableHeightPx: number\n /** Value for the CSS `@page { size: … }` print rule. */\n cssPageSize: 'letter' | 'A4'\n}\n\n// Both sizes keep the same physical 0.7in x 0.6in margins.\nconst PAD_X_PX = 0.7 * DPI\nconst PAD_Y_PX = 0.6 * DPI\nconst GAP_PX = 32\n\nconst DIMENSIONS: Record<\n PageSize,\n { widthPx: number; heightPx: number; cssPageSize: 'letter' | 'A4' }\n> = {\n letter: { widthPx: 8.5 * DPI, heightPx: 11 * DPI, cssPageSize: 'letter' },\n a4: { widthPx: 210 * MM, heightPx: 297 * MM, cssPageSize: 'A4' },\n}\n\n/** Resolve the print-fixed geometry for a page size (defaults to US Letter). */\nexport function getPageGeometry(size: PageSize = 'letter'): PageGeometry {\n const d = DIMENSIONS[size]\n return {\n widthPx: d.widthPx,\n heightPx: d.heightPx,\n padXPx: PAD_X_PX,\n padYPx: PAD_Y_PX,\n gapPx: GAP_PX,\n contentWidthPx: d.widthPx - 2 * PAD_X_PX,\n usableHeightPx: d.heightPx - 2 * PAD_Y_PX,\n cssPageSize: d.cssPageSize,\n }\n}\n\n/** Back-compat default geometry (US Letter). */\nexport const PAGE = getPageGeometry('letter')\n\n// Kept for back-compat; equal to the Letter geometry's derived values.\nexport const CONTENT_WIDTH_PX = PAGE.contentWidthPx\nexport const USABLE_HEIGHT_PX = PAGE.usableHeightPx\n\n// Vertical gap between blocks within a column — must match --doc-block-gap\n// (0.375rem = 6px) in styles/tokens.css and the column gap in document.css.\nexport const BLOCK_GAP_PX = 6\n\n// Outer horizontal padding the viewer reserves around the page in its scroll area.\nexport const WRAPPER_PADDING_X = 32\n","// packages/resume/src/pagination/pagination.ts\n\nexport interface Block {\n id: string\n /** Measured px (offsetHeight + vertical margins). */\n height: number\n /** Section headings: don't leave them orphaned at a page bottom. */\n keepWithNext: boolean\n}\n\nexport type ColumnKey = 'sidebar' | 'main'\n\nexport interface PackInput {\n columns: Record<ColumnKey, Block[]>\n /** Usable content height for a given page index; index 0 may be shorter (header band). */\n pageUsableHeight: (pageIndex: number) => number\n /**\n * Vertical gap rendered between consecutive blocks in a column (the flex\n * `gap`). Block heights are measured individually, so the packer must add\n * this back or pages overflow once they hold many blocks. Defaults to 0.\n */\n blockGap?: number\n}\n\nexport interface PackResult {\n pageCount: number\n pageOf: Record<ColumnKey, Record<string, number>>\n /** Blocks taller than a full page — placed alone, allowed to overflow. */\n oversized: string[]\n}\n\nfunction packColumn(\n blocks: Block[],\n pageUsableHeight: (p: number) => number,\n gap: number\n): { pageOf: Record<string, number>; pages: number; oversized: string[] } {\n const pageOf: Record<string, number> = {}\n const oversized: string[] = []\n let page = 0\n let used = 0\n\n for (let i = 0; i < blocks.length; i++) {\n const b = blocks[i]\n let cap = pageUsableHeight(page)\n // A gap precedes every block except the first on its page.\n const gapBefore = used > 0 ? gap : 0\n\n // Move to a fresh page if this block (plus its leading gap) overflows.\n if (used > 0 && used + gapBefore + b.height > cap) {\n page++\n used = 0\n cap = pageUsableHeight(page)\n } else if (\n // Keep-with-next: heading fits alone but heading + gap + next block\n // doesn't — push the heading forward so it isn't orphaned.\n used > 0 &&\n b.keepWithNext &&\n used + gapBefore + b.height <= cap &&\n i + 1 < blocks.length &&\n used + gapBefore + b.height + gap + blocks[i + 1].height > cap\n ) {\n page++\n used = 0\n cap = pageUsableHeight(page)\n }\n\n // Oversized: taller than a whole (now-empty) page. Place alone, allow overflow.\n if (b.height > cap && used === 0) {\n oversized.push(b.id)\n pageOf[b.id] = page\n page++\n used = 0\n continue\n }\n\n pageOf[b.id] = page\n used += (used > 0 ? gap : 0) + b.height\n }\n\n return { pageOf, pages: page + 1, oversized }\n}\n\n/** Assign measured blocks to pages per column. Pure: no DOM. */\nexport function packIntoPages(input: PackInput): PackResult {\n const gap = input.blockGap ?? 0\n const sidebar = packColumn(input.columns.sidebar, input.pageUsableHeight, gap)\n const main = packColumn(input.columns.main, input.pageUsableHeight, gap)\n return {\n pageCount: Math.max(sidebar.pages, main.pages, 1),\n pageOf: { sidebar: sidebar.pageOf, main: main.pageOf },\n oversized: [...sidebar.oversized, ...main.oversized],\n }\n}\n","// packages/resume/src/document/pagination-context.tsx\nimport { createContext, useContext } from 'react'\nimport type { PageSize } from '../geometry'\n\n/** PaginatedResume reports its computed page count up to the host (ResumeViewer). */\nexport const PaginationReportContext = createContext<((pageCount: number) => void) | null>(null)\n\nexport function usePaginationReport(): (pageCount: number) => void {\n const report = useContext(PaginationReportContext)\n return report ?? (() => {})\n}\n\n/** Optional font-family hint — changes trigger re-measure/re-pagination. */\nexport const ResumeFontFamilyContext = createContext<string | undefined>(undefined)\nexport const useResumeFontFamily = () => useContext(ResumeFontFamilyContext)\n\n/** Page size for the document — a change re-measures/re-paginates. */\nexport const ResumePageSizeContext = createContext<PageSize>('letter')\nexport const useResumePageSize = () => useContext(ResumePageSizeContext)\n","import type React from 'react'\n\n/**\n * Vendored, dependency-free base prose component map.\n *\n * This is a minimal fallback map that the resume spreads over\n * (`...baseMdxComponents`) before defining its own `h1/h3/h4/p/ul/li/strong/em/a/hr`.\n * It deliberately has NO dependency on `@atom63/ui`, lightbox, article blocks,\n * example containers, or any other `@atom63/*` package — every entry is a plain\n * HTML-element component so the resume package stays self-contained.\n *\n * It only needs to provide sane defaults for elements the resume does NOT override.\n */\nexport const baseMdxComponents = {\n h2: (props: React.HTMLAttributes<HTMLHeadingElement>) => <h2 {...props} />,\n h5: (props: React.HTMLAttributes<HTMLHeadingElement>) => <h5 {...props} />,\n h6: (props: React.HTMLAttributes<HTMLHeadingElement>) => <h6 {...props} />,\n ol: (props: React.HTMLAttributes<HTMLOListElement>) => <ol {...props} />,\n blockquote: (props: React.HTMLAttributes<HTMLElement>) => <blockquote {...props} />,\n code: (props: React.HTMLAttributes<HTMLElement>) => <code {...props} />,\n pre: (props: React.HTMLAttributes<HTMLPreElement>) => <pre {...props} />,\n img: (props: React.ImgHTMLAttributes<HTMLImageElement>) => (\n // biome-ignore lint/a11y/useAltText: alt is forwarded via spread props\n <img {...props} />\n ),\n} as const\n","const MDX_SPECIAL_PROTOCOL = /^(mailto|tel|javascript):/i\n\n/**\n * True when `href` should open in a new tab: off-origin http(s), protocol-relative,\n * or absolute http(s) during SSR. Same-origin URLs, in-app paths (`/…`, `#…`),\n * relative paths, and `mailto:` / `tel:` / `javascript:` stay in-tab / default behavior.\n */\nexport function isExternalMdxHref(href: string | undefined): boolean {\n if (!href) {\n return false\n }\n const t = href.trim()\n if (t.startsWith('#') || t.startsWith('/')) {\n return false\n }\n if (MDX_SPECIAL_PROTOCOL.test(t)) {\n return false\n }\n if (t.startsWith('//')) {\n return true\n }\n if (typeof globalThis.window !== 'undefined') {\n try {\n const u = new URL(t, globalThis.window.location.href)\n if (u.protocol !== 'http:' && u.protocol !== 'https:') {\n return false\n }\n return u.origin !== globalThis.window.location.origin\n } catch {\n return false\n }\n }\n try {\n const u = new URL(t)\n return u.protocol === 'http:' || u.protocol === 'https:'\n } catch {\n return false\n }\n}\n","import clsx from 'clsx'\nimport { baseMdxComponents } from '../mdx/base-components'\nimport { isExternalMdxHref } from '../mdx/external-href'\nimport { PaginatedResume } from './paginated-resume'\n\n// Styling for these primitives lives in resume-document.css as a small token\n// system (--doc-*) + semantic classes (.doc-*). Components only compose those —\n// no hardcoded sizes/colors/grid widths — so the resume and CV share one source\n// of truth. See paginated-resume.tsx for where the .css is imported and where\n// the [data-resume-document] token scope is applied.\n\nexport function Header({\n children,\n meta = false,\n}: {\n children: React.ReactNode\n /**\n * Use the universal meta grid (left label column + content), the same track\n * widths as <Section> and <Entry>, so the header aligns with everything\n * below it. The default is the resume's sidebar|main split.\n */\n meta?: boolean\n}) {\n return (\n <div\n className={clsx('doc-header', meta ? 'doc-meta-grid' : 'doc-resume-grid')}\n data-resume-header\n >\n {children}\n </div>\n )\n}\n\nfunction HeaderLeft({ children }: { children: React.ReactNode }) {\n return <div className=\"doc-header-col doc-header-left\">{children}</div>\n}\n\nfunction HeaderRight({ children }: { children: React.ReactNode }) {\n return <div className=\"doc-header-col doc-header-right\">{children}</div>\n}\n\nfunction Links({ children }: { children: React.ReactNode }) {\n return <div className=\"doc-group doc-links\">{children}</div>\n}\n\nexport function Group({ children }: { children: React.ReactNode }) {\n return <div className=\"doc-group\">{children}</div>\n}\n\n/**\n * A record row (award, media mention, talk, …): a `year | role` meta column on\n * the left and the linked title on the right. Each Entry is a single pagination\n * block, so render long CV lists as a flat sequence of Entry elements (NOT one\n * <ul>) so pages can break between them.\n */\nexport function Entry({\n year,\n role,\n children,\n}: {\n year?: string\n role?: string\n children: React.ReactNode\n}) {\n return (\n <div className=\"doc-meta-grid\">\n <span className=\"doc-meta\">{role ? `${year} | ${role}` : year}</span>\n <div className=\"doc-body doc-entry-body\">{children}</div>\n </div>\n )\n}\n\n/** A thin full-width divider — the CV uses one under the header band. */\nexport function Rule() {\n return <div className=\"doc-rule\" />\n}\n\n/**\n * Editorial section: the label sits in the left meta column (aligned with the\n * `year | role` column of Entry rows) and the content fills the right column,\n * so prose sections share one left edge with the record rows below. The\n * .doc-section margin keeps every section on the same vertical rhythm as the\n * record-section headings (h3). One pagination block — keep a body under a page.\n */\nexport function Section({ label, children }: { label: string; children: React.ReactNode }) {\n return (\n <div className=\"doc-section doc-meta-grid\">\n <h3 className=\"doc-heading\">{label}</h3>\n <div className=\"doc-col\">{children}</div>\n </div>\n )\n}\n\nexport function Footer({ children }: { children: React.ReactNode }) {\n return (\n <div className=\"doc-footer\" data-footer>\n {children}\n </div>\n )\n}\n\nexport function Columns({ children }: { children: React.ReactNode }) {\n return <div className=\"doc-resume-grid\">{children}</div>\n}\n\nexport function Sidebar({ children }: { children: React.ReactNode }) {\n return (\n <div className=\"doc-col doc-col-sidebar\" data-resume-col=\"sidebar\">\n {children}\n </div>\n )\n}\n\nexport function Main({ children }: { children: React.ReactNode }) {\n return (\n <div className=\"doc-col doc-col-main\" data-resume-col=\"main\">\n {children}\n </div>\n )\n}\n\nexport const resumeMdxComponents = {\n ...baseMdxComponents,\n\n ResumeDocument: PaginatedResume,\n\n Header,\n HeaderLeft,\n HeaderRight,\n Links,\n Group,\n Entry,\n Rule,\n Section,\n Footer,\n Columns,\n Sidebar,\n Main,\n\n h1: ({ children, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => (\n <h1 className=\"doc-title\" {...props}>\n {children}\n </h1>\n ),\n\n h3: ({ children, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => (\n <h3 className=\"doc-section doc-heading\" data-resume-keep-with-next {...props}>\n {children}\n </h3>\n ),\n\n h4: ({ children, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => (\n <h4 className=\"doc-subheading\" {...props}>\n {children}\n </h4>\n ),\n\n p: ({ children, ...props }: React.HTMLAttributes<HTMLParagraphElement>) => (\n <p className=\"doc-body\" {...props}>\n {children}\n </p>\n ),\n\n ul: ({ children, ...props }: React.HTMLAttributes<HTMLUListElement>) => (\n <ul className=\"doc-list\" {...props}>\n {children}\n </ul>\n ),\n\n li: ({ children, ...props }: React.HTMLAttributes<HTMLLIElement>) => (\n <li className=\"doc-body\" {...props}>\n {children}\n </li>\n ),\n\n strong: ({ children, ...props }: React.HTMLAttributes<HTMLElement>) => (\n <strong className=\"doc-strong\" {...props}>\n {children}\n </strong>\n ),\n\n em: ({ children, ...props }: React.HTMLAttributes<HTMLElement>) => (\n <em className=\"doc-meta doc-em\" {...props}>\n {children}\n </em>\n ),\n\n a: ({ children, href, target, rel, ...props }: React.AnchorHTMLAttributes<HTMLAnchorElement>) => {\n const external = isExternalMdxHref(href)\n return (\n <a\n className=\"doc-link\"\n href={href}\n {...props}\n rel={external ? (rel ?? 'noopener noreferrer') : rel}\n target={external ? (target ?? '_blank') : target}\n >\n {children}\n </a>\n )\n },\n\n hr: (_props: React.HTMLAttributes<HTMLHRElement>) => <div className=\"doc-hr\" />,\n}\n","import {\n Children,\n Fragment,\n isValidElement,\n type ReactNode,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n} from 'react'\nimport { BLOCK_GAP_PX, getPageGeometry } from '../geometry'\nimport { type Block, type ColumnKey, packIntoPages } from '../pagination/pagination'\nimport { Columns, Footer, Header, Main, Sidebar } from './components'\nimport { usePaginationReport, useResumeFontFamily, useResumePageSize } from './pagination-context'\nimport '../styles/tokens.css'\nimport '../styles/document.css'\n\ntype Columned = Record<ColumnKey, ReactNode[]>\n\n/**\n * Flatten one level, descending through Fragments. MDX wraps top-level content\n * (and component children) in Fragments, so `Children.toArray` alone yields the\n * Fragment, not the Header/Columns/Sidebar elements inside it.\n */\nfunction flattenChildren(children: ReactNode): ReactNode[] {\n const out: ReactNode[] = []\n for (const child of Children.toArray(children)) {\n if (isValidElement(child) && child.type === Fragment) {\n out.push(...flattenChildren((child.props as { children: ReactNode }).children))\n } else {\n out.push(child)\n }\n }\n return out\n}\n\n/** Pull Header / Columns(Sidebar,Main) / Footer out of the MDX wrapper children. */\nfunction extract(children: ReactNode): {\n header: ReactNode | null\n footer: ReactNode | null\n blocks: Columned\n} {\n let header: ReactNode | null = null\n let footer: ReactNode | null = null\n const blocks: Columned = { sidebar: [], main: [] }\n\n for (const child of flattenChildren(children)) {\n if (!isValidElement(child)) continue\n if (child.type === Header) header = child\n else if (child.type === Footer) footer = child\n else if (child.type === Columns) {\n for (const col of flattenChildren((child.props as { children: ReactNode }).children)) {\n if (!isValidElement(col)) continue\n const key: ColumnKey | null =\n col.type === Sidebar ? 'sidebar' : col.type === Main ? 'main' : null\n if (key) {\n blocks[key] = flattenChildren((col.props as { children: ReactNode }).children)\n }\n }\n }\n }\n return { header, footer, blocks }\n}\n\n/** Read a block element's full vertical extent (border box + margins). */\nfunction blockHeight(el: HTMLElement): number {\n const cs = getComputedStyle(el)\n const mt = Number.parseFloat(cs.marginTop) || 0\n const mb = Number.parseFloat(cs.marginBottom) || 0\n return el.getBoundingClientRect().height + mt + mb\n}\n\n/**\n * Mounted as the MDX `wrapper`, so it receives [Header, Columns, Footer].\n * Measures each column's blocks in a hidden layer, packs them into pages, and\n * renders physical page frames. Reports pageCount up to ResumeApp.\n */\nexport function PaginatedResume({ children }: { children: ReactNode }) {\n const { header, footer, blocks } = extract(children)\n const fontFamily = useResumeFontFamily()\n const pageSize = useResumePageSize()\n const geo = getPageGeometry(pageSize)\n const report = usePaginationReport()\n\n const measureRef = useRef<HTMLDivElement>(null)\n const [heights, setHeights] = useState<Record<ColumnKey, number[]> | null>(null)\n const [headerH, setHeaderH] = useState(0)\n\n // contentKey changes whenever the rendered content could change height.\n // Print-fixed (px) sizes: only the inherited font *family* and the block\n // counts can change measured heights — the OS typography scale is ignored.\n const contentKey = `${fontFamily}:${pageSize}:${blocks.sidebar.length}:${blocks.main.length}`\n\n // contentKey is the intentional re-measure trigger: a change in font family /\n // block counts must force a fresh measure even though it isn't read in the body.\n // biome-ignore lint/correctness/useExhaustiveDependencies: deliberate re-run trigger\n useLayoutEffect(() => {\n const root = measureRef.current\n if (!root) return\n const measure = () => {\n const cols: Record<ColumnKey, number[]> = { sidebar: [], main: [] }\n for (const key of ['sidebar', 'main'] as ColumnKey[]) {\n const colEl = root.querySelector(`[data-resume-col=\"${key}\"]`)\n if (colEl) {\n cols[key] = Array.from(colEl.children).map(c => blockHeight(c as HTMLElement))\n }\n }\n const headerEl = root.querySelector('[data-resume-header]') as HTMLElement | null\n setHeaderH(headerEl ? blockHeight(headerEl) : 0)\n setHeights(cols)\n }\n measure()\n const ro = new ResizeObserver(measure)\n ro.observe(root)\n\n // Re-measure on window (os63 window) resize — e.g. maximize/restore. The\n // measure root is fixed-width, so its own observer never catches this;\n // watch the enclosing scroll viewport, which tracks the window size. A\n // double rAF lets layout settle so a mid-transition read doesn't stick\n // (which collapsed multi-page docs to a single clipped page).\n const viewport = root.closest('[data-slot=\"scroll-area-viewport\"]')\n // Debounce to a TRAILING measure: the os63 window's maximize/restore is a\n // spring animation, so reading mid-transition lands on a wrong height that\n // then sticks. Wait for the resize to stop, then measure the settled layout.\n let settleTimer: ReturnType<typeof setTimeout> | undefined\n const scheduleMeasure = () => {\n clearTimeout(settleTimer)\n settleTimer = setTimeout(measure, 250)\n }\n const vpObserver = viewport ? new ResizeObserver(scheduleMeasure) : null\n vpObserver?.observe(viewport as Element)\n\n if (typeof document !== 'undefined' && 'fonts' in document) {\n document.fonts.ready.then(measure).catch(() => {})\n }\n return () => {\n ro.disconnect()\n vpObserver?.disconnect()\n clearTimeout(settleTimer)\n }\n }, [contentKey])\n\n // The hidden measuring layer renders the real structure at content width.\n const measureLayer = (\n <div\n aria-hidden\n data-resume-measure\n ref={measureRef}\n style={{\n position: 'absolute',\n top: 0,\n left: -99999,\n width: geo.contentWidthPx,\n visibility: 'hidden',\n pointerEvents: 'none',\n }}\n >\n {header}\n {blocks.sidebar.length === 0 ? (\n // Single-column docs (e.g. the CV) put everything in Main and render\n // full-width. Measure at full width too so block heights match the\n // full-width page render below (otherwise they'd be measured at the\n // narrower 2/3 main-column width and mis-paginate).\n <div className=\"doc-col\" data-resume-col=\"main\">\n {blocks.main}\n </div>\n ) : (\n <Columns>\n <Sidebar>{blocks.sidebar}</Sidebar>\n <Main>{blocks.main}</Main>\n </Columns>\n )}\n </div>\n )\n\n if (!heights) {\n return <div data-resume-document>{measureLayer}</div>\n }\n\n const toBlocks = (key: ColumnKey): Block[] =>\n blocks[key].map((node, i) => ({\n id: `${key}-${i}`,\n height: heights[key][i] ?? 0,\n keepWithNext:\n isValidElement(node) &&\n Boolean((node.props as Record<string, unknown>)['data-resume-keep-with-next']),\n }))\n\n const sidebarBlocks = toBlocks('sidebar')\n const mainBlocks = toBlocks('main')\n\n const pack = packIntoPages({\n columns: { sidebar: sidebarBlocks, main: mainBlocks },\n pageUsableHeight: p =>\n p === 0 ? Math.max(0, geo.usableHeightPx - headerH) : geo.usableHeightPx,\n blockGap: BLOCK_GAP_PX,\n })\n\n return (\n <div data-resume-document>\n {measureLayer}\n <div className=\"doc-pages\" style={{ gap: geo.gapPx }}>\n {Array.from({ length: pack.pageCount }, (_, p) => {\n const sideOnPage = blocks.sidebar.filter(\n (_, i) => pack.pageOf.sidebar[`sidebar-${i}`] === p\n )\n const mainOnPage = blocks.main.filter((_, i) => pack.pageOf.main[`main-${i}`] === p)\n // Key the layout on the WHOLE document, not the current page: a\n // single-column doc (the CV — no sidebar at all) renders full\n // width, but a two-column doc keeps its grid on every page,\n // including continuation pages where the sidebar has run out, so\n // the main column stays in its right-hand track instead of\n // jumping to full width (which broke the 2-col layout on page 2+).\n const isTwoColumn = blocks.sidebar.length > 0\n return (\n <div\n className=\"doc-page\"\n data-resume-page\n key={`page-${String(p)}`}\n style={{\n width: geo.widthPx,\n height: geo.heightPx,\n overflow: 'hidden',\n padding: `${geo.padYPx}px ${geo.padXPx}px`,\n }}\n >\n {p === 0 && header}\n {isTwoColumn ? (\n <div className=\"doc-resume-grid\">\n <div className=\"doc-col doc-col-sidebar\">{sideOnPage}</div>\n <div className=\"doc-col doc-col-main\">{mainOnPage}</div>\n </div>\n ) : (\n <div className=\"doc-col\">{mainOnPage}</div>\n )}\n {p === pack.pageCount - 1 && footer}\n </div>\n )\n })}\n </div>\n <PageCountReporter count={pack.pageCount} report={report} />\n </div>\n )\n}\n\n/** Reports pageCount via an effect (no setState-during-render). */\nfunction PageCountReporter({ count, report }: { count: number; report: (n: number) => void }) {\n useEffect(() => {\n report(count)\n }, [count, report])\n return null\n}\n"]}
@@ -1,10 +1,5 @@
1
1
  /* src/styles/tokens.css */
2
2
  [data-resume-document] {
3
- --paper-width: 8.5in;
4
- --paper-height: 11in;
5
- --paper-pad-x: 0.7in;
6
- --paper-pad-y: 0.6in;
7
- --paper-gap: 32px;
8
3
  --doc-meta-col: 11rem;
9
4
  --doc-resume-cols: 1fr 2fr;
10
5
  --doc-col-gap: 0.75rem;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/styles/tokens.css","../../src/styles/document.css","../../src/editor/editor.css"],"sourcesContent":["/*\n * @atom63/resume — paper/document design tokens.\n *\n * The single source of truth for the resume/CV look. Override any token (e.g.\n * in your own stylesheet, scoped to [data-resume-document]) to restyle the\n * resume and CV together; the React primitives only compose these.\n *\n * --paper-* : page geometry (defaults: US Letter @ 96dpi; override for A4).\n * --doc-* : document language (layout, type, ink).\n *\n * Print-fixed by design: doc sizes are absolute px for PDF fidelity, independent\n * of any host typography-scale setting. The font *family* is inherited (so the\n * document follows the host font), which is why pagination re-measures on font\n * change.\n *\n * NOTE: the numeric --paper-* defaults and --doc-block-gap MUST stay in sync with\n * geometry.ts (the JS packer reads the same numbers to compute page breaks).\n */\n[data-resume-document] {\n /* Page geometry (US Letter @ 96dpi — see geometry.ts) */\n --paper-width: 8.5in;\n --paper-height: 11in;\n --paper-pad-x: 0.7in;\n --paper-pad-y: 0.6in;\n --paper-gap: 32px; /* screen-only gap between page frames */\n\n /* Document layout */\n --doc-meta-col: 11rem; /* left meta/label column (header name, section label, year|role) */\n --doc-resume-cols: 1fr 2fr; /* resume sidebar | main split */\n --doc-col-gap: 0.75rem; /* gap between the meta column and content */\n --doc-col-pad: 1.5rem; /* resume two-column inner gutter */\n --doc-block-gap: 0.375rem; /* between blocks within a column (== BLOCK_GAP_PX) */\n --doc-section-gap: 0.5rem; /* space above a section */\n --doc-group-gap: 0.125rem; /* within a tight group (title + meta line) */\n\n /* Type */\n --doc-text: 10px;\n --doc-text-xs: 8px;\n --doc-leading: 1.4;\n --doc-leading-heading: 1.5;\n\n /* Ink (on the white page) */\n --doc-ink: #000; /* headings, strong */\n --doc-ink-body: #404040; /* body copy (neutral-700) */\n --doc-ink-muted: #737373; /* meta / labels (neutral-500) */\n --doc-ink-faint: #a3a3a3; /* footer, faint detail (neutral-400) */\n --doc-rule: #d4d4d4; /* dividers (neutral-300) */\n}\n","/*\n * Resume & CV document layout + type primitives. These classes only COMPOSE the\n * design tokens — the token vocabulary itself (the paper and doc custom\n * properties) lives in tokens.css (imported separately, or together via\n * styles.css). Keep this file token-free so consumers can adopt just the tokens,\n * or restyle without forking these classes.\n */\n\n/* Layout primitives */\n.doc-meta-grid {\n display: grid;\n min-width: 0;\n grid-template-columns: var(--doc-meta-col) minmax(0, 1fr);\n gap: var(--doc-col-gap);\n}\n.doc-resume-grid {\n display: grid;\n min-width: 0;\n grid-template-columns: var(--doc-resume-cols);\n}\n.doc-col {\n display: flex;\n min-width: 0;\n flex-direction: column;\n gap: var(--doc-block-gap);\n}\n.doc-group {\n display: flex;\n flex-direction: column;\n gap: var(--doc-group-gap);\n}\n.doc-section {\n margin-top: var(--doc-section-gap);\n}\n\n/* Type primitives */\n.doc-body {\n font-size: var(--doc-text);\n line-height: var(--doc-leading);\n color: var(--doc-ink-body);\n overflow-wrap: break-word;\n}\n.doc-meta {\n font-size: var(--doc-text);\n line-height: var(--doc-leading);\n color: var(--doc-ink-muted);\n}\n.doc-heading {\n font-size: var(--doc-text);\n line-height: var(--doc-leading-heading);\n color: var(--doc-ink);\n font-weight: 700;\n text-transform: uppercase;\n}\n.doc-title {\n font-size: var(--doc-text);\n line-height: 1;\n color: var(--doc-ink);\n font-weight: 700;\n text-transform: uppercase;\n}\n.doc-subheading {\n font-size: var(--doc-text);\n line-height: 1;\n color: var(--doc-ink);\n font-weight: 600;\n}\n.doc-strong {\n color: var(--doc-ink);\n font-weight: 600;\n}\n.doc-link {\n color: var(--doc-ink-body);\n text-decoration: underline;\n /* touch-manipulation equivalent for links */\n touch-action: manipulation;\n}\n.doc-rule {\n margin: 0.25rem 0;\n border-bottom: 1px solid var(--doc-rule);\n}\n.doc-footer {\n font-size: var(--doc-text-xs);\n color: var(--doc-ink-faint);\n /* Layout (replacing Tailwind utilities) */\n margin-top: auto;\n display: flex;\n align-items: flex-end;\n justify-content: space-between;\n}\n.doc-footer a {\n color: inherit;\n}\n\n/* Header / layout helpers (replacing Tailwind utilities) */\n.doc-header {\n margin-bottom: 0.75rem;\n}\n.doc-header-col {\n display: flex;\n min-width: 0;\n flex-direction: column;\n justify-content: space-between;\n}\n.doc-header-left {\n padding-right: var(--doc-col-pad);\n}\n.doc-header-right {\n padding-left: var(--doc-col-pad);\n}\n.doc-header-right > p + p {\n margin-top: 0.125rem;\n}\n\n.doc-links {\n margin-top: 0.75rem;\n}\n.doc-links > p {\n margin-top: 0;\n color: var(--doc-ink-muted);\n}\n.doc-links a {\n color: inherit;\n}\n.doc-links em {\n color: var(--doc-ink-faint);\n}\n\n.doc-entry-body {\n min-width: 0;\n}\n\n/* Two-column grid placement (sidebar | main), used on every page incl. continuations */\n.doc-col-sidebar {\n grid-column-start: 1;\n grid-row-start: 1;\n padding-right: var(--doc-col-pad);\n}\n.doc-col-main {\n grid-column-start: 2;\n grid-row-start: 1;\n padding-left: var(--doc-col-pad);\n}\n\n.doc-list {\n list-style: disc;\n padding-left: 0.75rem;\n}\n.doc-em {\n font-style: normal;\n}\n.doc-hr {\n margin: 0.75rem 0;\n}\n\n/* Page frame (Tailwind shadow-lg + bg-white + text-black + flex column) */\n.doc-pages {\n display: flex;\n flex-direction: column;\n}\n.doc-page {\n position: relative;\n display: flex;\n flex-direction: column;\n background: #fff;\n color: #000;\n box-shadow:\n 0 10px 15px -3px rgb(0 0 0 / 0.1),\n 0 4px 6px -4px rgb(0 0 0 / 0.1);\n}\n","/* packages/resume/src/editor/editor.css\n Minimal, Tailwind-free layout for the split-pane resume editor. */\n\n.resume-editor {\n display: flex;\n flex-direction: row;\n width: 100%;\n height: 100%;\n min-height: 0;\n}\n\n.resume-editor-pane {\n flex: 1 1 50%;\n min-width: 0;\n min-height: 0;\n overflow: auto;\n}\n\n.resume-editor-source {\n display: block;\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n resize: none;\n border: none;\n outline: none;\n padding: 1rem;\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;\n font-size: 0.8125rem;\n line-height: 1.5;\n tab-size: 2;\n}\n\n.resume-editor-preview {\n border-left: 1px solid rgba(0, 0, 0, 0.1);\n}\n\n.resume-mdx-live-empty {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n padding: 2rem;\n font-size: 0.875rem;\n opacity: 0.6;\n}\n\n.resume-mdx-live-error {\n padding: 1rem;\n font-size: 0.875rem;\n color: #b00020;\n}\n"],"mappings":";AAkBA,CAAC;AAEC,iBAAe;AACf,kBAAgB;AAChB,iBAAe;AACf,iBAAe;AACf,eAAa;AAGb,kBAAgB;AAChB,qBAAmB,IAAI;AACvB,iBAAe;AACf,iBAAe;AACf,mBAAiB;AACjB,qBAAmB;AACnB,mBAAiB;AAGjB,cAAY;AACZ,iBAAe;AACf,iBAAe;AACf,yBAAuB;AAGvB,aAAW;AACX,kBAAgB;AAChB,mBAAiB;AACjB,mBAAiB;AACjB,cAAY;AACd;;;ACtCA,CAAC;AACC,WAAS;AACT,aAAW;AACX,yBAAuB,IAAI,gBAAgB,OAAO,CAAC,EAAE;AACrD,OAAK,IAAI;AACX;AACA,CAAC;AACC,WAAS;AACT,aAAW;AACX,yBAAuB,IAAI;AAC7B;AACA,CAAC;AACC,WAAS;AACT,aAAW;AACX,kBAAgB;AAChB,OAAK,IAAI;AACX;AACA,CAAC;AACC,WAAS;AACT,kBAAgB;AAChB,OAAK,IAAI;AACX;AACA,CAAC;AACC,cAAY,IAAI;AAClB;AAGA,CAAC;AACC,aAAW,IAAI;AACf,eAAa,IAAI;AACjB,SAAO,IAAI;AACX,iBAAe;AACjB;AACA,CAAC;AACC,aAAW,IAAI;AACf,eAAa,IAAI;AACjB,SAAO,IAAI;AACb;AACA,CAAC;AACC,aAAW,IAAI;AACf,eAAa,IAAI;AACjB,SAAO,IAAI;AACX,eAAa;AACb,kBAAgB;AAClB;AACA,CAAC;AACC,aAAW,IAAI;AACf,eAAa;AACb,SAAO,IAAI;AACX,eAAa;AACb,kBAAgB;AAClB;AACA,CAAC;AACC,aAAW,IAAI;AACf,eAAa;AACb,SAAO,IAAI;AACX,eAAa;AACf;AACA,CAAC;AACC,SAAO,IAAI;AACX,eAAa;AACf;AACA,CAAC;AACC,SAAO,IAAI;AACX,mBAAiB;AAEjB,gBAAc;AAChB;AACA,CAAC;AACC,UAAQ,QAAQ;AAChB,iBAAe,IAAI,MAAM,IAAI;AAC/B;AACA,CAAC;AACC,aAAW,IAAI;AACf,SAAO,IAAI;AAEX,cAAY;AACZ,WAAS;AACT,eAAa;AACb,mBAAiB;AACnB;AACA,CATC,WASW;AACV,SAAO;AACT;AAGA,CAAC;AACC,iBAAe;AACjB;AACA,CAAC;AACC,WAAS;AACT,aAAW;AACX,kBAAgB;AAChB,mBAAiB;AACnB;AACA,CAAC;AACC,iBAAe,IAAI;AACrB;AACA,CAAC;AACC,gBAAc,IAAI;AACpB;AACA,CAHC,iBAGiB,EAAE,EAAE,EAAE;AACtB,cAAY;AACd;AAEA,CAAC;AACC,cAAY;AACd;AACA,CAHC,UAGU,EAAE;AACX,cAAY;AACZ,SAAO,IAAI;AACb;AACA,CAPC,UAOU;AACT,SAAO;AACT;AACA,CAVC,UAUU;AACT,SAAO,IAAI;AACb;AAEA,CAAC;AACC,aAAW;AACb;AAGA,CAAC;AACC,qBAAmB;AACnB,kBAAgB;AAChB,iBAAe,IAAI;AACrB;AACA,CAAC;AACC,qBAAmB;AACnB,kBAAgB;AAChB,gBAAc,IAAI;AACpB;AAEA,CAAC;AACC,cAAY;AACZ,gBAAc;AAChB;AACA,CAAC;AACC,cAAY;AACd;AACA,CAAC;AACC,UAAQ,QAAQ;AAClB;AAGA,CAAC;AACC,WAAS;AACT,kBAAgB;AAClB;AACA,CAAC;AACC,YAAU;AACV,WAAS;AACT,kBAAgB;AAChB,cAAY;AACZ,SAAO;AACP,cACE,EAAE,KAAK,KAAK,KAAK,IAAI,EAAE,EAAE,EAAE,EAAE,IAAI,EACjC,EAAE,IAAI,IAAI,KAAK,IAAI,EAAE,EAAE,EAAE,EAAE;AAC/B;;;ACtKA,CAAC;AACC,WAAS;AACT,kBAAgB;AAChB,SAAO;AACP,UAAQ;AACR,cAAY;AACd;AAEA,CAAC;AACC,QAAM,EAAE,EAAE;AACV,aAAW;AACX,cAAY;AACZ,YAAU;AACZ;AAEA,CAAC;AACC,WAAS;AACT,SAAO;AACP,UAAQ;AACR,cAAY;AACZ,UAAQ;AACR,UAAQ;AACR,WAAS;AACT,WAAS;AACT;AAAA,IAAa,YAAY;AAAA,IAAE,cAAc;AAAA,IAAE,KAAK;AAAA,IAAE,MAAM;AAAA,IAAE,QAAQ;AAAA,IAAE;AACpE,aAAW;AACX,eAAa;AACb,YAAU;AACZ;AAEA,CAAC;AACC,eAAa,IAAI,MAAM,KAAK,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE;AACvC;AAEA,CAAC;AACC,WAAS;AACT,eAAa;AACb,mBAAiB;AACjB,UAAQ;AACR,WAAS;AACT,aAAW;AACX,WAAS;AACX;AAEA,CAAC;AACC,WAAS;AACT,aAAW;AACX,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../src/styles/tokens.css","../../src/styles/document.css","../../src/editor/editor.css"],"sourcesContent":["/*\n * @atom63/resume — document design tokens (the --doc-* vocabulary).\n *\n * The single source of truth for the resume/CV look. Override any token (e.g.\n * in your own stylesheet, scoped to [data-resume-document]) to restyle the\n * resume and CV together; the React primitives only compose these.\n *\n * Page SIZE is not a token it is fixed numeric geometry (see geometry.ts) and\n * is selected with the ResumeViewer `pageSize` prop ('letter' | 'a4').\n *\n * Print-fixed by design: doc sizes are absolute px for PDF fidelity, independent\n * of any host typography-scale setting. The font *family* is inherited (so the\n * document follows the host font), which is why pagination re-measures on font\n * change.\n *\n * NOTE: --doc-block-gap MUST stay in sync with BLOCK_GAP_PX in geometry.ts (the\n * JS packer reads the same number to compute page breaks).\n */\n[data-resume-document] {\n /* Document layout */\n --doc-meta-col: 11rem; /* left meta/label column (header name, section label, year|role) */\n --doc-resume-cols: 1fr 2fr; /* resume sidebar | main split */\n --doc-col-gap: 0.75rem; /* gap between the meta column and content */\n --doc-col-pad: 1.5rem; /* resume two-column inner gutter */\n --doc-block-gap: 0.375rem; /* between blocks within a column (== BLOCK_GAP_PX) */\n --doc-section-gap: 0.5rem; /* space above a section */\n --doc-group-gap: 0.125rem; /* within a tight group (title + meta line) */\n\n /* Type */\n --doc-text: 10px;\n --doc-text-xs: 8px;\n --doc-leading: 1.4;\n --doc-leading-heading: 1.5;\n\n /* Ink (on the white page) */\n --doc-ink: #000; /* headings, strong */\n --doc-ink-body: #404040; /* body copy (neutral-700) */\n --doc-ink-muted: #737373; /* meta / labels (neutral-500) */\n --doc-ink-faint: #a3a3a3; /* footer, faint detail (neutral-400) */\n --doc-rule: #d4d4d4; /* dividers (neutral-300) */\n}\n","/*\n * Resume & CV document layout + type primitives. These classes only COMPOSE the\n * design tokens — the token vocabulary itself (the paper and doc custom\n * properties) lives in tokens.css (imported separately, or together via\n * styles.css). Keep this file token-free so consumers can adopt just the tokens,\n * or restyle without forking these classes.\n */\n\n/* Layout primitives */\n.doc-meta-grid {\n display: grid;\n min-width: 0;\n grid-template-columns: var(--doc-meta-col) minmax(0, 1fr);\n gap: var(--doc-col-gap);\n}\n.doc-resume-grid {\n display: grid;\n min-width: 0;\n grid-template-columns: var(--doc-resume-cols);\n}\n.doc-col {\n display: flex;\n min-width: 0;\n flex-direction: column;\n gap: var(--doc-block-gap);\n}\n.doc-group {\n display: flex;\n flex-direction: column;\n gap: var(--doc-group-gap);\n}\n.doc-section {\n margin-top: var(--doc-section-gap);\n}\n\n/* Type primitives */\n.doc-body {\n font-size: var(--doc-text);\n line-height: var(--doc-leading);\n color: var(--doc-ink-body);\n overflow-wrap: break-word;\n}\n.doc-meta {\n font-size: var(--doc-text);\n line-height: var(--doc-leading);\n color: var(--doc-ink-muted);\n}\n.doc-heading {\n font-size: var(--doc-text);\n line-height: var(--doc-leading-heading);\n color: var(--doc-ink);\n font-weight: 700;\n text-transform: uppercase;\n}\n.doc-title {\n font-size: var(--doc-text);\n line-height: 1;\n color: var(--doc-ink);\n font-weight: 700;\n text-transform: uppercase;\n}\n.doc-subheading {\n font-size: var(--doc-text);\n line-height: 1;\n color: var(--doc-ink);\n font-weight: 600;\n}\n.doc-strong {\n color: var(--doc-ink);\n font-weight: 600;\n}\n.doc-link {\n color: var(--doc-ink-body);\n text-decoration: underline;\n /* touch-manipulation equivalent for links */\n touch-action: manipulation;\n}\n.doc-rule {\n margin: 0.25rem 0;\n border-bottom: 1px solid var(--doc-rule);\n}\n.doc-footer {\n font-size: var(--doc-text-xs);\n color: var(--doc-ink-faint);\n /* Layout (replacing Tailwind utilities) */\n margin-top: auto;\n display: flex;\n align-items: flex-end;\n justify-content: space-between;\n}\n.doc-footer a {\n color: inherit;\n}\n\n/* Header / layout helpers (replacing Tailwind utilities) */\n.doc-header {\n margin-bottom: 0.75rem;\n}\n.doc-header-col {\n display: flex;\n min-width: 0;\n flex-direction: column;\n justify-content: space-between;\n}\n.doc-header-left {\n padding-right: var(--doc-col-pad);\n}\n.doc-header-right {\n padding-left: var(--doc-col-pad);\n}\n.doc-header-right > p + p {\n margin-top: 0.125rem;\n}\n\n.doc-links {\n margin-top: 0.75rem;\n}\n.doc-links > p {\n margin-top: 0;\n color: var(--doc-ink-muted);\n}\n.doc-links a {\n color: inherit;\n}\n.doc-links em {\n color: var(--doc-ink-faint);\n}\n\n.doc-entry-body {\n min-width: 0;\n}\n\n/* Two-column grid placement (sidebar | main), used on every page incl. continuations */\n.doc-col-sidebar {\n grid-column-start: 1;\n grid-row-start: 1;\n padding-right: var(--doc-col-pad);\n}\n.doc-col-main {\n grid-column-start: 2;\n grid-row-start: 1;\n padding-left: var(--doc-col-pad);\n}\n\n.doc-list {\n list-style: disc;\n padding-left: 0.75rem;\n}\n.doc-em {\n font-style: normal;\n}\n.doc-hr {\n margin: 0.75rem 0;\n}\n\n/* Page frame (Tailwind shadow-lg + bg-white + text-black + flex column) */\n.doc-pages {\n display: flex;\n flex-direction: column;\n}\n.doc-page {\n position: relative;\n display: flex;\n flex-direction: column;\n background: #fff;\n color: #000;\n box-shadow:\n 0 10px 15px -3px rgb(0 0 0 / 0.1),\n 0 4px 6px -4px rgb(0 0 0 / 0.1);\n}\n","/* packages/resume/src/editor/editor.css\n Minimal, Tailwind-free layout for the split-pane resume editor. */\n\n.resume-editor {\n display: flex;\n flex-direction: row;\n width: 100%;\n height: 100%;\n min-height: 0;\n}\n\n.resume-editor-pane {\n flex: 1 1 50%;\n min-width: 0;\n min-height: 0;\n overflow: auto;\n}\n\n.resume-editor-source {\n display: block;\n width: 100%;\n height: 100%;\n box-sizing: border-box;\n resize: none;\n border: none;\n outline: none;\n padding: 1rem;\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;\n font-size: 0.8125rem;\n line-height: 1.5;\n tab-size: 2;\n}\n\n.resume-editor-preview {\n border-left: 1px solid rgba(0, 0, 0, 0.1);\n}\n\n.resume-mdx-live-empty {\n display: flex;\n align-items: center;\n justify-content: center;\n height: 100%;\n padding: 2rem;\n font-size: 0.875rem;\n opacity: 0.6;\n}\n\n.resume-mdx-live-error {\n padding: 1rem;\n font-size: 0.875rem;\n color: #b00020;\n}\n"],"mappings":"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","names":[]}
@@ -1,4 +1,4 @@
1
- import { resumeMdxComponents } from '../chunk-FL25EF7U.js';
1
+ import { resumeMdxComponents } from '../chunk-ARBKVUHN.js';
2
2
  import { MDXProvider } from '@mdx-js/react';
3
3
  import { useEffect, useState, useRef, Component } from 'react';
4
4
  import { jsx, jsxs } from 'react/jsx-runtime';
package/dist/index.css CHANGED
@@ -1,10 +1,5 @@
1
1
  /* src/styles/tokens.css */
2
2
  [data-resume-document] {
3
- --paper-width: 8.5in;
4
- --paper-height: 11in;
5
- --paper-pad-x: 0.7in;
6
- --paper-pad-y: 0.6in;
7
- --paper-gap: 32px;
8
3
  --doc-meta-col: 11rem;
9
4
  --doc-resume-cols: 1fr 2fr;
10
5
  --doc-col-gap: 0.75rem;
@@ -327,7 +322,6 @@
327
322
  break-after: auto;
328
323
  }
329
324
  @page {
330
- size: letter;
331
325
  margin: 0;
332
326
  }
333
327
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/styles/tokens.css","../src/styles/document.css","../src/styles/viewer.css"],"sourcesContent":["/*\n * @atom63/resume — paper/document design tokens.\n *\n * The single source of truth for the resume/CV look. Override any token (e.g.\n * in your own stylesheet, scoped to [data-resume-document]) to restyle the\n * resume and CV together; the React primitives only compose these.\n *\n * --paper-* : page geometry (defaults: US Letter @ 96dpi; override for A4).\n * --doc-* : document language (layout, type, ink).\n *\n * Print-fixed by design: doc sizes are absolute px for PDF fidelity, independent\n * of any host typography-scale setting. The font *family* is inherited (so the\n * document follows the host font), which is why pagination re-measures on font\n * change.\n *\n * NOTE: the numeric --paper-* defaults and --doc-block-gap MUST stay in sync with\n * geometry.ts (the JS packer reads the same numbers to compute page breaks).\n */\n[data-resume-document] {\n /* Page geometry (US Letter @ 96dpi — see geometry.ts) */\n --paper-width: 8.5in;\n --paper-height: 11in;\n --paper-pad-x: 0.7in;\n --paper-pad-y: 0.6in;\n --paper-gap: 32px; /* screen-only gap between page frames */\n\n /* Document layout */\n --doc-meta-col: 11rem; /* left meta/label column (header name, section label, year|role) */\n --doc-resume-cols: 1fr 2fr; /* resume sidebar | main split */\n --doc-col-gap: 0.75rem; /* gap between the meta column and content */\n --doc-col-pad: 1.5rem; /* resume two-column inner gutter */\n --doc-block-gap: 0.375rem; /* between blocks within a column (== BLOCK_GAP_PX) */\n --doc-section-gap: 0.5rem; /* space above a section */\n --doc-group-gap: 0.125rem; /* within a tight group (title + meta line) */\n\n /* Type */\n --doc-text: 10px;\n --doc-text-xs: 8px;\n --doc-leading: 1.4;\n --doc-leading-heading: 1.5;\n\n /* Ink (on the white page) */\n --doc-ink: #000; /* headings, strong */\n --doc-ink-body: #404040; /* body copy (neutral-700) */\n --doc-ink-muted: #737373; /* meta / labels (neutral-500) */\n --doc-ink-faint: #a3a3a3; /* footer, faint detail (neutral-400) */\n --doc-rule: #d4d4d4; /* dividers (neutral-300) */\n}\n","/*\n * Resume & CV document layout + type primitives. These classes only COMPOSE the\n * design tokens — the token vocabulary itself (the paper and doc custom\n * properties) lives in tokens.css (imported separately, or together via\n * styles.css). Keep this file token-free so consumers can adopt just the tokens,\n * or restyle without forking these classes.\n */\n\n/* Layout primitives */\n.doc-meta-grid {\n display: grid;\n min-width: 0;\n grid-template-columns: var(--doc-meta-col) minmax(0, 1fr);\n gap: var(--doc-col-gap);\n}\n.doc-resume-grid {\n display: grid;\n min-width: 0;\n grid-template-columns: var(--doc-resume-cols);\n}\n.doc-col {\n display: flex;\n min-width: 0;\n flex-direction: column;\n gap: var(--doc-block-gap);\n}\n.doc-group {\n display: flex;\n flex-direction: column;\n gap: var(--doc-group-gap);\n}\n.doc-section {\n margin-top: var(--doc-section-gap);\n}\n\n/* Type primitives */\n.doc-body {\n font-size: var(--doc-text);\n line-height: var(--doc-leading);\n color: var(--doc-ink-body);\n overflow-wrap: break-word;\n}\n.doc-meta {\n font-size: var(--doc-text);\n line-height: var(--doc-leading);\n color: var(--doc-ink-muted);\n}\n.doc-heading {\n font-size: var(--doc-text);\n line-height: var(--doc-leading-heading);\n color: var(--doc-ink);\n font-weight: 700;\n text-transform: uppercase;\n}\n.doc-title {\n font-size: var(--doc-text);\n line-height: 1;\n color: var(--doc-ink);\n font-weight: 700;\n text-transform: uppercase;\n}\n.doc-subheading {\n font-size: var(--doc-text);\n line-height: 1;\n color: var(--doc-ink);\n font-weight: 600;\n}\n.doc-strong {\n color: var(--doc-ink);\n font-weight: 600;\n}\n.doc-link {\n color: var(--doc-ink-body);\n text-decoration: underline;\n /* touch-manipulation equivalent for links */\n touch-action: manipulation;\n}\n.doc-rule {\n margin: 0.25rem 0;\n border-bottom: 1px solid var(--doc-rule);\n}\n.doc-footer {\n font-size: var(--doc-text-xs);\n color: var(--doc-ink-faint);\n /* Layout (replacing Tailwind utilities) */\n margin-top: auto;\n display: flex;\n align-items: flex-end;\n justify-content: space-between;\n}\n.doc-footer a {\n color: inherit;\n}\n\n/* Header / layout helpers (replacing Tailwind utilities) */\n.doc-header {\n margin-bottom: 0.75rem;\n}\n.doc-header-col {\n display: flex;\n min-width: 0;\n flex-direction: column;\n justify-content: space-between;\n}\n.doc-header-left {\n padding-right: var(--doc-col-pad);\n}\n.doc-header-right {\n padding-left: var(--doc-col-pad);\n}\n.doc-header-right > p + p {\n margin-top: 0.125rem;\n}\n\n.doc-links {\n margin-top: 0.75rem;\n}\n.doc-links > p {\n margin-top: 0;\n color: var(--doc-ink-muted);\n}\n.doc-links a {\n color: inherit;\n}\n.doc-links em {\n color: var(--doc-ink-faint);\n}\n\n.doc-entry-body {\n min-width: 0;\n}\n\n/* Two-column grid placement (sidebar | main), used on every page incl. continuations */\n.doc-col-sidebar {\n grid-column-start: 1;\n grid-row-start: 1;\n padding-right: var(--doc-col-pad);\n}\n.doc-col-main {\n grid-column-start: 2;\n grid-row-start: 1;\n padding-left: var(--doc-col-pad);\n}\n\n.doc-list {\n list-style: disc;\n padding-left: 0.75rem;\n}\n.doc-em {\n font-style: normal;\n}\n.doc-hr {\n margin: 0.75rem 0;\n}\n\n/* Page frame (Tailwind shadow-lg + bg-white + text-black + flex column) */\n.doc-pages {\n display: flex;\n flex-direction: column;\n}\n.doc-page {\n position: relative;\n display: flex;\n flex-direction: column;\n background: #fff;\n color: #000;\n box-shadow:\n 0 10px 15px -3px rgb(0 0 0 / 0.1),\n 0 4px 6px -4px rgb(0 0 0 / 0.1);\n}\n","/* Screen preview — keep letter layout stable on mobile browsers. */\n#resume-print-target,\n#resume-print-target * {\n -webkit-text-size-adjust: none;\n text-size-adjust: none;\n}\n\n[data-resume-scale-root] {\n transform-origin: top left;\n}\n\n/* ResumeViewer chrome — the standalone component's own layout (toolbar +\n scroll viewport + scaled page stage). Tailwind-free; the package ships these\n classes so the viewer is self-styled. The neutral viewport background mirrors\n the old muted canvas; the page frames come from the document and paper tokens\n defined in tokens.css and document.css. */\n.resume-viewer {\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n}\n\n.resume-viewer-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 0.75rem;\n padding: 0.375rem 0.75rem;\n border-bottom: 1px solid rgb(0 0 0 / 0.08);\n background: #fff;\n flex: 0 0 auto;\n}\n\n.resume-viewer-pagecount {\n font-size: 0.8125rem;\n color: rgb(0 0 0 / 0.55);\n}\n\n.resume-viewer-controls {\n display: flex;\n align-items: center;\n gap: 0.25rem;\n}\n\n.resume-viewer-btn-group {\n display: flex;\n align-items: center;\n}\n\n.resume-viewer-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: 2rem;\n min-width: 2rem;\n padding: 0 0.375rem;\n border: 0;\n border-radius: 0.375rem;\n background: transparent;\n color: rgb(0 0 0 / 0.7);\n font-size: 0.8125rem;\n cursor: pointer;\n}\n\n.resume-viewer-btn:hover:not(:disabled) {\n background: rgb(0 0 0 / 0.06);\n}\n\n.resume-viewer-btn:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n.resume-viewer-btn-active {\n background: rgb(0 0 0 / 0.08);\n color: rgb(0 0 0 / 0.9);\n}\n\n.resume-viewer-btn-text {\n padding: 0 0.625rem;\n}\n\n.resume-viewer-zoom-label {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: 2rem;\n width: 3rem;\n border: 0;\n border-radius: 0.375rem;\n background: transparent;\n color: rgb(0 0 0 / 0.55);\n font-size: 0.8125rem;\n font-variant-numeric: tabular-nums;\n cursor: pointer;\n}\n\n.resume-viewer-zoom-label:hover {\n background: rgb(0 0 0 / 0.06);\n}\n\n.resume-viewer-icon {\n width: 1rem;\n height: 1rem;\n}\n\n.resume-viewer-scroll {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n background: rgb(244 244 245);\n}\n\n.resume-viewer-scroll-grab {\n cursor: grab;\n}\n\n.resume-viewer-scroll-grabbing {\n cursor: grabbing;\n}\n\n.resume-viewer-scroll-mobile {\n touch-action: none;\n user-select: none;\n}\n\n.resume-viewer-stage {\n margin-top: 1.5rem;\n margin-bottom: 1.5rem;\n padding-left: 1rem;\n padding-right: 1rem;\n}\n\n.resume-viewer-page-box {\n margin-left: auto;\n margin-right: auto;\n}\n\n.resume-viewer-scale-root {\n position: relative;\n}\n\n.resume-viewer-content {\n position: relative;\n display: flex;\n flex-direction: column;\n color: #000;\n}\n\n[data-slot=\"scroll-area-viewport\"][data-pan-active],\n.resume-viewer-scroll[data-pan-active] {\n background-color: color-mix(in oklch, var(--muted) 42%, transparent);\n}\n\n@media (prefers-reduced-motion: reduce) {\n [data-slot=\"scroll-area-viewport\"][data-pan-active],\n .resume-viewer-scroll[data-pan-active] {\n transition: none;\n }\n}\n\n#resume-print-target a,\n#resume-print-target button {\n touch-action: manipulation;\n}\n\n@media print {\n /* Body children are hidden/restored via JS (beforeprint/afterprint)\n to preserve <style>/<link>/<script> tags. */\n\n /* Hard isolation: while the resume's print clone exists, hide every other\n direct body child (other app windows, stray clones) regardless of JS\n hide-ordering across apps. Scoped to the clone's presence so it never\n affects another app's print. */\n body:has(#resume-print-clone) > :not(#resume-print-clone) {\n display: none !important;\n }\n\n /* The clone is a plain wrapper around the engine's real 8.5x11 page frames\n (a direct child of body — no positioning needed). */\n #resume-print-clone {\n color: black !important;\n background: white !important;\n }\n\n /* Frames stack with a screen-only gap; remove it so each maps 1:1 to a page. */\n #resume-print-clone > div {\n gap: 0 !important;\n }\n\n [data-resume-page] {\n box-shadow: none !important;\n break-after: page;\n break-inside: avoid;\n }\n [data-resume-page]:last-child {\n break-after: auto;\n }\n\n @page {\n size: letter;\n margin: 0;\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/styles/tokens.css","../src/styles/document.css","../src/styles/viewer.css"],"sourcesContent":["/*\n * @atom63/resume — document design tokens (the --doc-* vocabulary).\n *\n * The single source of truth for the resume/CV look. Override any token (e.g.\n * in your own stylesheet, scoped to [data-resume-document]) to restyle the\n * resume and CV together; the React primitives only compose these.\n *\n * Page SIZE is not a token — it is fixed numeric geometry (see geometry.ts) and\n * is selected with the ResumeViewer `pageSize` prop ('letter' | 'a4').\n *\n * Print-fixed by design: doc sizes are absolute px for PDF fidelity, independent\n * of any host typography-scale setting. The font *family* is inherited (so the\n * document follows the host font), which is why pagination re-measures on font\n * change.\n *\n * NOTE: --doc-block-gap MUST stay in sync with BLOCK_GAP_PX in geometry.ts (the\n * JS packer reads the same number to compute page breaks).\n */\n[data-resume-document] {\n /* Document layout */\n --doc-meta-col: 11rem; /* left meta/label column (header name, section label, year|role) */\n --doc-resume-cols: 1fr 2fr; /* resume sidebar | main split */\n --doc-col-gap: 0.75rem; /* gap between the meta column and content */\n --doc-col-pad: 1.5rem; /* resume two-column inner gutter */\n --doc-block-gap: 0.375rem; /* between blocks within a column (== BLOCK_GAP_PX) */\n --doc-section-gap: 0.5rem; /* space above a section */\n --doc-group-gap: 0.125rem; /* within a tight group (title + meta line) */\n\n /* Type */\n --doc-text: 10px;\n --doc-text-xs: 8px;\n --doc-leading: 1.4;\n --doc-leading-heading: 1.5;\n\n /* Ink (on the white page) */\n --doc-ink: #000; /* headings, strong */\n --doc-ink-body: #404040; /* body copy (neutral-700) */\n --doc-ink-muted: #737373; /* meta / labels (neutral-500) */\n --doc-ink-faint: #a3a3a3; /* footer, faint detail (neutral-400) */\n --doc-rule: #d4d4d4; /* dividers (neutral-300) */\n}\n","/*\n * Resume & CV document layout + type primitives. These classes only COMPOSE the\n * design tokens — the token vocabulary itself (the paper and doc custom\n * properties) lives in tokens.css (imported separately, or together via\n * styles.css). Keep this file token-free so consumers can adopt just the tokens,\n * or restyle without forking these classes.\n */\n\n/* Layout primitives */\n.doc-meta-grid {\n display: grid;\n min-width: 0;\n grid-template-columns: var(--doc-meta-col) minmax(0, 1fr);\n gap: var(--doc-col-gap);\n}\n.doc-resume-grid {\n display: grid;\n min-width: 0;\n grid-template-columns: var(--doc-resume-cols);\n}\n.doc-col {\n display: flex;\n min-width: 0;\n flex-direction: column;\n gap: var(--doc-block-gap);\n}\n.doc-group {\n display: flex;\n flex-direction: column;\n gap: var(--doc-group-gap);\n}\n.doc-section {\n margin-top: var(--doc-section-gap);\n}\n\n/* Type primitives */\n.doc-body {\n font-size: var(--doc-text);\n line-height: var(--doc-leading);\n color: var(--doc-ink-body);\n overflow-wrap: break-word;\n}\n.doc-meta {\n font-size: var(--doc-text);\n line-height: var(--doc-leading);\n color: var(--doc-ink-muted);\n}\n.doc-heading {\n font-size: var(--doc-text);\n line-height: var(--doc-leading-heading);\n color: var(--doc-ink);\n font-weight: 700;\n text-transform: uppercase;\n}\n.doc-title {\n font-size: var(--doc-text);\n line-height: 1;\n color: var(--doc-ink);\n font-weight: 700;\n text-transform: uppercase;\n}\n.doc-subheading {\n font-size: var(--doc-text);\n line-height: 1;\n color: var(--doc-ink);\n font-weight: 600;\n}\n.doc-strong {\n color: var(--doc-ink);\n font-weight: 600;\n}\n.doc-link {\n color: var(--doc-ink-body);\n text-decoration: underline;\n /* touch-manipulation equivalent for links */\n touch-action: manipulation;\n}\n.doc-rule {\n margin: 0.25rem 0;\n border-bottom: 1px solid var(--doc-rule);\n}\n.doc-footer {\n font-size: var(--doc-text-xs);\n color: var(--doc-ink-faint);\n /* Layout (replacing Tailwind utilities) */\n margin-top: auto;\n display: flex;\n align-items: flex-end;\n justify-content: space-between;\n}\n.doc-footer a {\n color: inherit;\n}\n\n/* Header / layout helpers (replacing Tailwind utilities) */\n.doc-header {\n margin-bottom: 0.75rem;\n}\n.doc-header-col {\n display: flex;\n min-width: 0;\n flex-direction: column;\n justify-content: space-between;\n}\n.doc-header-left {\n padding-right: var(--doc-col-pad);\n}\n.doc-header-right {\n padding-left: var(--doc-col-pad);\n}\n.doc-header-right > p + p {\n margin-top: 0.125rem;\n}\n\n.doc-links {\n margin-top: 0.75rem;\n}\n.doc-links > p {\n margin-top: 0;\n color: var(--doc-ink-muted);\n}\n.doc-links a {\n color: inherit;\n}\n.doc-links em {\n color: var(--doc-ink-faint);\n}\n\n.doc-entry-body {\n min-width: 0;\n}\n\n/* Two-column grid placement (sidebar | main), used on every page incl. continuations */\n.doc-col-sidebar {\n grid-column-start: 1;\n grid-row-start: 1;\n padding-right: var(--doc-col-pad);\n}\n.doc-col-main {\n grid-column-start: 2;\n grid-row-start: 1;\n padding-left: var(--doc-col-pad);\n}\n\n.doc-list {\n list-style: disc;\n padding-left: 0.75rem;\n}\n.doc-em {\n font-style: normal;\n}\n.doc-hr {\n margin: 0.75rem 0;\n}\n\n/* Page frame (Tailwind shadow-lg + bg-white + text-black + flex column) */\n.doc-pages {\n display: flex;\n flex-direction: column;\n}\n.doc-page {\n position: relative;\n display: flex;\n flex-direction: column;\n background: #fff;\n color: #000;\n box-shadow:\n 0 10px 15px -3px rgb(0 0 0 / 0.1),\n 0 4px 6px -4px rgb(0 0 0 / 0.1);\n}\n","/* Screen preview — keep letter layout stable on mobile browsers. */\n#resume-print-target,\n#resume-print-target * {\n -webkit-text-size-adjust: none;\n text-size-adjust: none;\n}\n\n[data-resume-scale-root] {\n transform-origin: top left;\n}\n\n/* ResumeViewer chrome — the standalone component's own layout (toolbar +\n scroll viewport + scaled page stage). Tailwind-free; the package ships these\n classes so the viewer is self-styled. The neutral viewport background mirrors\n the old muted canvas; the page frames come from the document and paper tokens\n defined in tokens.css and document.css. */\n.resume-viewer {\n display: flex;\n flex-direction: column;\n height: 100%;\n overflow: hidden;\n}\n\n.resume-viewer-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 0.75rem;\n padding: 0.375rem 0.75rem;\n border-bottom: 1px solid rgb(0 0 0 / 0.08);\n background: #fff;\n flex: 0 0 auto;\n}\n\n.resume-viewer-pagecount {\n font-size: 0.8125rem;\n color: rgb(0 0 0 / 0.55);\n}\n\n.resume-viewer-controls {\n display: flex;\n align-items: center;\n gap: 0.25rem;\n}\n\n.resume-viewer-btn-group {\n display: flex;\n align-items: center;\n}\n\n.resume-viewer-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: 2rem;\n min-width: 2rem;\n padding: 0 0.375rem;\n border: 0;\n border-radius: 0.375rem;\n background: transparent;\n color: rgb(0 0 0 / 0.7);\n font-size: 0.8125rem;\n cursor: pointer;\n}\n\n.resume-viewer-btn:hover:not(:disabled) {\n background: rgb(0 0 0 / 0.06);\n}\n\n.resume-viewer-btn:disabled {\n opacity: 0.4;\n cursor: default;\n}\n\n.resume-viewer-btn-active {\n background: rgb(0 0 0 / 0.08);\n color: rgb(0 0 0 / 0.9);\n}\n\n.resume-viewer-btn-text {\n padding: 0 0.625rem;\n}\n\n.resume-viewer-zoom-label {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: 2rem;\n width: 3rem;\n border: 0;\n border-radius: 0.375rem;\n background: transparent;\n color: rgb(0 0 0 / 0.55);\n font-size: 0.8125rem;\n font-variant-numeric: tabular-nums;\n cursor: pointer;\n}\n\n.resume-viewer-zoom-label:hover {\n background: rgb(0 0 0 / 0.06);\n}\n\n.resume-viewer-icon {\n width: 1rem;\n height: 1rem;\n}\n\n.resume-viewer-scroll {\n flex: 1 1 auto;\n min-height: 0;\n overflow: auto;\n background: rgb(244 244 245);\n}\n\n.resume-viewer-scroll-grab {\n cursor: grab;\n}\n\n.resume-viewer-scroll-grabbing {\n cursor: grabbing;\n}\n\n.resume-viewer-scroll-mobile {\n touch-action: none;\n user-select: none;\n}\n\n.resume-viewer-stage {\n margin-top: 1.5rem;\n margin-bottom: 1.5rem;\n padding-left: 1rem;\n padding-right: 1rem;\n}\n\n.resume-viewer-page-box {\n margin-left: auto;\n margin-right: auto;\n}\n\n.resume-viewer-scale-root {\n position: relative;\n}\n\n.resume-viewer-content {\n position: relative;\n display: flex;\n flex-direction: column;\n color: #000;\n}\n\n[data-slot=\"scroll-area-viewport\"][data-pan-active],\n.resume-viewer-scroll[data-pan-active] {\n background-color: color-mix(in oklch, var(--muted) 42%, transparent);\n}\n\n@media (prefers-reduced-motion: reduce) {\n [data-slot=\"scroll-area-viewport\"][data-pan-active],\n .resume-viewer-scroll[data-pan-active] {\n transition: none;\n }\n}\n\n#resume-print-target a,\n#resume-print-target button {\n touch-action: manipulation;\n}\n\n@media print {\n /* Body children are hidden/restored via JS (beforeprint/afterprint)\n to preserve <style>/<link>/<script> tags. */\n\n /* Hard isolation: while the resume's print clone exists, hide every other\n direct body child (other app windows, stray clones) regardless of JS\n hide-ordering across apps. Scoped to the clone's presence so it never\n affects another app's print. */\n body:has(#resume-print-clone) > :not(#resume-print-clone) {\n display: none !important;\n }\n\n /* The clone is a plain wrapper around the engine's real 8.5x11 page frames\n (a direct child of body — no positioning needed). */\n #resume-print-clone {\n color: black !important;\n background: white !important;\n }\n\n /* Frames stack with a screen-only gap; remove it so each maps 1:1 to a page. */\n #resume-print-clone > div {\n gap: 0 !important;\n }\n\n [data-resume-page] {\n box-shadow: none !important;\n break-after: page;\n break-inside: avoid;\n }\n [data-resume-page]:last-child {\n break-after: auto;\n }\n\n /* `size` is set at runtime by the viewer (per the active pageSize); see\n ResumeViewer's injected <style>. Only the zero margin is static here. */\n @page {\n margin: 0;\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.ts CHANGED
@@ -102,21 +102,34 @@ declare const resumeMdxComponents: {
102
102
  img: (props: React.ImgHTMLAttributes<HTMLImageElement>) => react_jsx_runtime.JSX.Element;
103
103
  };
104
104
 
105
+ type PageSize = 'letter' | 'a4';
106
+ interface PageGeometry {
107
+ widthPx: number;
108
+ heightPx: number;
109
+ padXPx: number;
110
+ padYPx: number;
111
+ /** Screen-only gap between page frames. */
112
+ gapPx: number;
113
+ contentWidthPx: number;
114
+ usableHeightPx: number;
115
+ /** Value for the CSS `@page { size: … }` print rule. */
116
+ cssPageSize: 'letter' | 'A4';
117
+ }
118
+ /** Resolve the print-fixed geometry for a page size (defaults to US Letter). */
119
+ declare function getPageGeometry(size?: PageSize): PageGeometry;
120
+ /** Back-compat default geometry (US Letter). */
121
+ declare const PAGE: PageGeometry;
122
+ declare const BLOCK_GAP_PX = 6;
123
+
105
124
  /** PaginatedResume reports its computed page count up to the host (ResumeViewer). */
106
125
  declare const PaginationReportContext: react.Context<((pageCount: number) => void) | null>;
107
126
  declare function usePaginationReport(): (pageCount: number) => void;
108
127
  /** Optional font-family hint — changes trigger re-measure/re-pagination. */
109
128
  declare const ResumeFontFamilyContext: react.Context<string | undefined>;
110
129
  declare const useResumeFontFamily: () => string | undefined;
111
-
112
- declare const PAGE: {
113
- readonly widthPx: number;
114
- readonly heightPx: number;
115
- readonly padXPx: number;
116
- readonly padYPx: number;
117
- readonly gapPx: 32;
118
- };
119
- declare const BLOCK_GAP_PX = 6;
130
+ /** Page size for the document — a change re-measures/re-paginates. */
131
+ declare const ResumePageSizeContext: react.Context<PageSize>;
132
+ declare const useResumePageSize: () => PageSize;
120
133
 
121
134
  interface Block {
122
135
  id: string;
@@ -155,6 +168,8 @@ interface ResumeViewerProps {
155
168
  fontFamily?: string;
156
169
  /** Whether this viewer owns global print + keyboard shortcuts. Default true. */
157
170
  isActive?: boolean;
171
+ /** Paper size for the document + print output. Default 'letter'. */
172
+ pageSize?: PageSize;
158
173
  /** Suggested PDF filename (becomes document.title during print). */
159
174
  pdfFilename?: string;
160
175
  /** Called after a successful copy-to-clipboard (host shows its own toast). */
@@ -177,7 +192,7 @@ interface ResumeViewerProps {
177
192
  toolbarEnd?: ReactNode;
178
193
  }
179
194
 
180
- declare function ResumeViewer({ Content, components, fontFamily, isActive, pdfFilename, onCopy, onError, className, toolbarStart, toolbarEnd, }: ResumeViewerProps): react_jsx_runtime.JSX.Element;
195
+ declare function ResumeViewer({ Content, components, fontFamily, isActive, pageSize, pdfFilename, onCopy, onError, className, toolbarStart, toolbarEnd, }: ResumeViewerProps): react_jsx_runtime.JSX.Element;
181
196
 
182
197
  /** True when the viewport is narrow (<= 768px). SSR-safe (defaults false). */
183
198
  declare function useIsMobile(breakpointPx?: number): boolean;
@@ -194,6 +209,8 @@ interface UseResumeViewportOptions {
194
209
  isMobile: boolean;
195
210
  /** Whether this viewport owns global print + keyboard shortcuts. */
196
211
  isActive: boolean;
212
+ /** Paper size driving the page dimensions + fit-to-width. Default 'letter'. */
213
+ pageSize?: PageSize;
197
214
  /** Suggested PDF filename (becomes document.title during print). */
198
215
  pdfFilename?: string;
199
216
  /** Called after a successful copy-to-clipboard. */
@@ -212,6 +229,8 @@ interface UseResumeViewport {
212
229
  isFitToWidth: boolean;
213
230
  /** Total stacked page height (unscaled px) — for sizing the page box. */
214
231
  totalHeight: number;
232
+ /** Resolved geometry for the active page size — for sizing the scaled box. */
233
+ geo: PageGeometry;
215
234
  handleZoomIn: () => void;
216
235
  handleZoomOut: () => void;
217
236
  handleActualSize: () => void;
@@ -227,6 +246,6 @@ interface UseResumeViewport {
227
246
  * derived state + action handlers. The host renders the scaled page stage; the
228
247
  * pagination engine inside it reports its page count through `setPageCount`.
229
248
  */
230
- declare function useResumeViewport({ scrollRef, contentRef, isMobile, isActive, pdfFilename, onCopy, onError, }: UseResumeViewportOptions): UseResumeViewport;
249
+ declare function useResumeViewport({ scrollRef, contentRef, isMobile, isActive, pageSize, pdfFilename, onCopy, onError, }: UseResumeViewportOptions): UseResumeViewport;
231
250
 
232
- export { BLOCK_GAP_PX, type Block, type ColumnKey, Columns, Entry, Footer, Group, Header, MAX_SCALE, MIN_SCALE, Main, PAGE, type PackInput, type PackResult, PaginatedResume, PaginationReportContext, ResumeFontFamilyContext, ResumeViewer, type ResumeViewerProps, Rule, SCALE_STEP, Section, Sidebar, type UseResumeViewport, type UseResumeViewportOptions, packIntoPages, resumeMdxComponents, usePaginationReport, useResumeFontFamily, useIsMobile as useResumeIsMobile, useResumeViewport };
251
+ export { BLOCK_GAP_PX, type Block, type ColumnKey, Columns, Entry, Footer, Group, Header, MAX_SCALE, MIN_SCALE, Main, PAGE, type PackInput, type PackResult, type PageGeometry, type PageSize, PaginatedResume, PaginationReportContext, ResumeFontFamilyContext, ResumePageSizeContext, ResumeViewer, type ResumeViewerProps, Rule, SCALE_STEP, Section, Sidebar, type UseResumeViewport, type UseResumeViewportOptions, getPageGeometry, packIntoPages, resumeMdxComponents, usePaginationReport, useResumeFontFamily, useIsMobile as useResumeIsMobile, useResumePageSize, useResumeViewport };
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
- import { PAGE, ResumeFontFamilyContext, PaginationReportContext, resumeMdxComponents, WRAPPER_PADDING_X } from './chunk-FL25EF7U.js';
2
- export { BLOCK_GAP_PX, Columns, Entry, Footer, Group, Header, Main, PAGE, PaginatedResume, PaginationReportContext, ResumeFontFamilyContext, Rule, Section, Sidebar, packIntoPages, resumeMdxComponents, usePaginationReport, useResumeFontFamily } from './chunk-FL25EF7U.js';
1
+ import { getPageGeometry, ResumePageSizeContext, ResumeFontFamilyContext, PaginationReportContext, resumeMdxComponents, WRAPPER_PADDING_X } from './chunk-ARBKVUHN.js';
2
+ export { BLOCK_GAP_PX, Columns, Entry, Footer, Group, Header, Main, PAGE, PaginatedResume, PaginationReportContext, ResumeFontFamilyContext, ResumePageSizeContext, Rule, Section, Sidebar, getPageGeometry, packIntoPages, resumeMdxComponents, usePaginationReport, useResumeFontFamily, useResumePageSize } from './chunk-ARBKVUHN.js';
3
3
  import { MDXProvider } from '@mdx-js/react';
4
4
  import clsx from 'clsx';
5
5
  import { Maximize2, Minus, Plus, ClipboardCopy } from 'lucide-react';
@@ -116,10 +116,12 @@ function useResumeViewport({
116
116
  contentRef,
117
117
  isMobile,
118
118
  isActive,
119
+ pageSize = "letter",
119
120
  pdfFilename,
120
121
  onCopy,
121
122
  onError
122
123
  }) {
124
+ const geo = getPageGeometry(pageSize);
123
125
  const isActiveRef = useRef(isActive);
124
126
  isActiveRef.current = isActive;
125
127
  const printRequestedRef = useRef(false);
@@ -151,7 +153,7 @@ function useResumeViewport({
151
153
  if (!viewport) return;
152
154
  const updateFitScale = () => {
153
155
  const available = Math.max(0, viewport.clientWidth - WRAPPER_PADDING_X);
154
- setFitScale(clampResumeZoom(Math.min(1, available / PAGE.widthPx)));
156
+ setFitScale(clampResumeZoom(Math.min(1, available / geo.widthPx)));
155
157
  };
156
158
  updateFitScale();
157
159
  const observer = new ResizeObserver(updateFitScale);
@@ -161,7 +163,7 @@ function useResumeViewport({
161
163
  observer.disconnect();
162
164
  window.visualViewport?.removeEventListener("resize", updateFitScale);
163
165
  };
164
- }, [scrollRef]);
166
+ }, [scrollRef, geo.widthPx]);
165
167
  const captureViewportCenter = useCallback(() => {
166
168
  const el = scrollRef.current;
167
169
  if (!el) return;
@@ -442,13 +444,14 @@ function useResumeViewport({
442
444
  onError?.(error);
443
445
  }
444
446
  }, [contentRef, onCopy, onError]);
445
- const totalHeight = pageCount * PAGE.heightPx + Math.max(0, pageCount - 1) * PAGE.gapPx;
447
+ const totalHeight = pageCount * geo.heightPx + Math.max(0, pageCount - 1) * geo.gapPx;
446
448
  return {
447
449
  pageCount,
448
450
  setPageCount,
449
451
  effectiveScale,
450
452
  isFitToWidth,
451
453
  totalHeight,
454
+ geo,
452
455
  handleZoomIn,
453
456
  handleZoomOut,
454
457
  handleActualSize,
@@ -464,6 +467,7 @@ function ResumeViewer({
464
467
  components,
465
468
  fontFamily,
466
469
  isActive = true,
470
+ pageSize = "letter",
467
471
  pdfFilename,
468
472
  onCopy,
469
473
  onError,
@@ -480,6 +484,7 @@ function ResumeViewer({
480
484
  effectiveScale,
481
485
  isFitToWidth,
482
486
  totalHeight,
487
+ geo,
483
488
  handleZoomIn,
484
489
  handleZoomOut,
485
490
  handleActualSize,
@@ -493,11 +498,13 @@ function ResumeViewer({
493
498
  contentRef,
494
499
  isMobile,
495
500
  isActive,
501
+ pageSize,
496
502
  pdfFilename,
497
503
  onCopy,
498
504
  onError
499
505
  });
500
506
  return /* @__PURE__ */ jsxs("div", { className: "resume-viewer", children: [
507
+ /* @__PURE__ */ jsx("style", { children: `@page { size: ${geo.cssPageSize}; margin: 0; }` }),
501
508
  /* @__PURE__ */ jsxs("div", { className: "resume-viewer-toolbar", children: [
502
509
  toolbarStart,
503
510
  /* @__PURE__ */ jsxs("span", { className: "resume-viewer-pagecount", children: [
@@ -599,7 +606,7 @@ function ResumeViewer({
599
606
  {
600
607
  className: "resume-viewer-page-box",
601
608
  style: {
602
- width: PAGE.widthPx * effectiveScale,
609
+ width: geo.widthPx * effectiveScale,
603
610
  height: totalHeight * effectiveScale
604
611
  },
605
612
  children: /* @__PURE__ */ jsx(
@@ -608,11 +615,11 @@ function ResumeViewer({
608
615
  className: "resume-viewer-scale-root",
609
616
  "data-resume-scale-root": "",
610
617
  style: {
611
- width: PAGE.widthPx,
618
+ width: geo.widthPx,
612
619
  height: totalHeight,
613
620
  transform: effectiveScale === 1 ? void 0 : `scale(${effectiveScale})`
614
621
  },
615
- children: /* @__PURE__ */ jsx(ResumeFontFamilyContext.Provider, { value: fontFamily, children: /* @__PURE__ */ jsx(PaginationReportContext.Provider, { value: setPageCount, children: /* @__PURE__ */ jsx(MDXProvider, { components: components ?? resumeMdxComponents, children: /* @__PURE__ */ jsx(
622
+ children: /* @__PURE__ */ jsx(ResumePageSizeContext.Provider, { value: pageSize, children: /* @__PURE__ */ jsx(ResumeFontFamilyContext.Provider, { value: fontFamily, children: /* @__PURE__ */ jsx(PaginationReportContext.Provider, { value: setPageCount, children: /* @__PURE__ */ jsx(MDXProvider, { components: components ?? resumeMdxComponents, children: /* @__PURE__ */ jsx(
616
623
  "div",
617
624
  {
618
625
  className: "resume-viewer-content",
@@ -621,7 +628,7 @@ function ResumeViewer({
621
628
  style: { zIndex: 1 },
622
629
  children: /* @__PURE__ */ jsx(Content, {})
623
630
  }
624
- ) }) }) })
631
+ ) }) }) }) })
625
632
  }
626
633
  )
627
634
  }
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 { PAGE, 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 /** 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 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 pdfFilename,\n onCopy,\n onError,\n}: UseResumeViewportOptions): UseResumeViewport {\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 / PAGE.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])\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 * PAGE.heightPx + Math.max(0, pageCount - 1) * PAGE.gapPx\n\n return {\n pageCount,\n setPageCount,\n effectiveScale,\n isFitToWidth,\n totalHeight,\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 { PaginationReportContext, ResumeFontFamilyContext } from '../document/pagination-context'\nimport { PAGE } from '../geometry'\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 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 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 pdfFilename,\n onCopy,\n onError,\n })\n\n return (\n <div className=\"resume-viewer\">\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: PAGE.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: PAGE.widthPx,\n height: totalHeight,\n transform: effectiveScale === 1 ? undefined : `scale(${effectiveScale})`,\n }}\n >\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 </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":"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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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atom63/resume",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
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.",
@@ -95,8 +95,8 @@
95
95
  "tsup": "^8.3.5",
96
96
  "typescript": "~5.8.3",
97
97
  "vitest": "^4.1.8",
98
- "@atom63/tsconfig": "0.1.0",
99
- "@atom63/biome-config": "0.1.0"
98
+ "@atom63/biome-config": "0.1.0",
99
+ "@atom63/tsconfig": "0.1.0"
100
100
  },
101
101
  "scripts": {
102
102
  "build": "tsup",
@@ -1,29 +1,22 @@
1
1
  /*
2
- * @atom63/resume — paper/document design tokens.
2
+ * @atom63/resume — document design tokens (the --doc-* vocabulary).
3
3
  *
4
4
  * The single source of truth for the resume/CV look. Override any token (e.g.
5
5
  * in your own stylesheet, scoped to [data-resume-document]) to restyle the
6
6
  * resume and CV together; the React primitives only compose these.
7
7
  *
8
- * --paper-* : page geometry (defaults: US Letter @ 96dpi; override for A4).
9
- * --doc-* : document language (layout, type, ink).
8
+ * Page SIZE is not a token it is fixed numeric geometry (see geometry.ts) and
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
12
  * of any host typography-scale setting. The font *family* is inherited (so the
13
13
  * document follows the host font), which is why pagination re-measures on font
14
14
  * change.
15
15
  *
16
- * NOTE: the numeric --paper-* defaults and --doc-block-gap MUST stay in sync with
17
- * geometry.ts (the JS packer reads the same numbers to compute page breaks).
16
+ * NOTE: --doc-block-gap MUST stay in sync with BLOCK_GAP_PX in geometry.ts (the
17
+ * JS packer reads the same number to compute page breaks).
18
18
  */
19
19
  [data-resume-document] {
20
- /* Page geometry (US Letter @ 96dpi — see geometry.ts) */
21
- --paper-width: 8.5in;
22
- --paper-height: 11in;
23
- --paper-pad-x: 0.7in;
24
- --paper-pad-y: 0.6in;
25
- --paper-gap: 32px; /* screen-only gap between page frames */
26
-
27
20
  /* Document layout */
28
21
  --doc-meta-col: 11rem; /* left meta/label column (header name, section label, year|role) */
29
22
  --doc-resume-cols: 1fr 2fr; /* resume sidebar | main split */
@@ -198,8 +198,9 @@
198
198
  break-after: auto;
199
199
  }
200
200
 
201
+ /* `size` is set at runtime by the viewer (per the active pageSize); see
202
+ ResumeViewer's injected <style>. Only the zero margin is static here. */
201
203
  @page {
202
- size: letter;
203
204
  margin: 0;
204
205
  }
205
206
  }
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/geometry.ts","../src/pagination/pagination.ts","../src/document/pagination-context.tsx","../src/mdx/base-components.tsx","../src/mdx/external-href.ts","../src/document/components.tsx","../src/document/paginated-resume.tsx"],"names":["jsx","jsxs","_"],"mappings":"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","file":"chunk-FL25EF7U.js","sourcesContent":["// packages/resume/src/geometry.ts\n// Numeric page geometry — the single source of truth shared by the JS packer\n// and the default --paper-*/--doc-* token values in styles/tokens.css.\n// Print-fixed px at 96dpi for PDF fidelity. Keep tokens.css defaults in sync.\n\nconst DPI = 96\n\nexport const PAGE = {\n widthPx: 8.5 * DPI,\n heightPx: 11 * DPI,\n padXPx: 0.7 * DPI,\n padYPx: 0.6 * DPI,\n gapPx: 32, // screen-only gap between page frames\n} as const\n\nexport const CONTENT_WIDTH_PX = PAGE.widthPx - 2 * PAGE.padXPx\nexport const USABLE_HEIGHT_PX = PAGE.heightPx - 2 * PAGE.padYPx\n\n// Vertical gap between blocks within a column — must match --doc-block-gap\n// (0.375rem = 6px) in styles/tokens.css and the column gap in document.css.\nexport const BLOCK_GAP_PX = 6\n\n// Outer horizontal padding the viewer reserves around the page in its scroll area.\nexport const WRAPPER_PADDING_X = 32\n","// packages/resume/src/pagination/pagination.ts\n\nexport interface Block {\n id: string\n /** Measured px (offsetHeight + vertical margins). */\n height: number\n /** Section headings: don't leave them orphaned at a page bottom. */\n keepWithNext: boolean\n}\n\nexport type ColumnKey = 'sidebar' | 'main'\n\nexport interface PackInput {\n columns: Record<ColumnKey, Block[]>\n /** Usable content height for a given page index; index 0 may be shorter (header band). */\n pageUsableHeight: (pageIndex: number) => number\n /**\n * Vertical gap rendered between consecutive blocks in a column (the flex\n * `gap`). Block heights are measured individually, so the packer must add\n * this back or pages overflow once they hold many blocks. Defaults to 0.\n */\n blockGap?: number\n}\n\nexport interface PackResult {\n pageCount: number\n pageOf: Record<ColumnKey, Record<string, number>>\n /** Blocks taller than a full page — placed alone, allowed to overflow. */\n oversized: string[]\n}\n\nfunction packColumn(\n blocks: Block[],\n pageUsableHeight: (p: number) => number,\n gap: number\n): { pageOf: Record<string, number>; pages: number; oversized: string[] } {\n const pageOf: Record<string, number> = {}\n const oversized: string[] = []\n let page = 0\n let used = 0\n\n for (let i = 0; i < blocks.length; i++) {\n const b = blocks[i]\n let cap = pageUsableHeight(page)\n // A gap precedes every block except the first on its page.\n const gapBefore = used > 0 ? gap : 0\n\n // Move to a fresh page if this block (plus its leading gap) overflows.\n if (used > 0 && used + gapBefore + b.height > cap) {\n page++\n used = 0\n cap = pageUsableHeight(page)\n } else if (\n // Keep-with-next: heading fits alone but heading + gap + next block\n // doesn't — push the heading forward so it isn't orphaned.\n used > 0 &&\n b.keepWithNext &&\n used + gapBefore + b.height <= cap &&\n i + 1 < blocks.length &&\n used + gapBefore + b.height + gap + blocks[i + 1].height > cap\n ) {\n page++\n used = 0\n cap = pageUsableHeight(page)\n }\n\n // Oversized: taller than a whole (now-empty) page. Place alone, allow overflow.\n if (b.height > cap && used === 0) {\n oversized.push(b.id)\n pageOf[b.id] = page\n page++\n used = 0\n continue\n }\n\n pageOf[b.id] = page\n used += (used > 0 ? gap : 0) + b.height\n }\n\n return { pageOf, pages: page + 1, oversized }\n}\n\n/** Assign measured blocks to pages per column. Pure: no DOM. */\nexport function packIntoPages(input: PackInput): PackResult {\n const gap = input.blockGap ?? 0\n const sidebar = packColumn(input.columns.sidebar, input.pageUsableHeight, gap)\n const main = packColumn(input.columns.main, input.pageUsableHeight, gap)\n return {\n pageCount: Math.max(sidebar.pages, main.pages, 1),\n pageOf: { sidebar: sidebar.pageOf, main: main.pageOf },\n oversized: [...sidebar.oversized, ...main.oversized],\n }\n}\n","// packages/resume/src/document/pagination-context.tsx\nimport { createContext, useContext } from 'react'\n\n/** PaginatedResume reports its computed page count up to the host (ResumeViewer). */\nexport const PaginationReportContext = createContext<((pageCount: number) => void) | null>(null)\n\nexport function usePaginationReport(): (pageCount: number) => void {\n const report = useContext(PaginationReportContext)\n return report ?? (() => {})\n}\n\n/** Optional font-family hint — changes trigger re-measure/re-pagination. */\nexport const ResumeFontFamilyContext = createContext<string | undefined>(undefined)\nexport const useResumeFontFamily = () => useContext(ResumeFontFamilyContext)\n","import type React from 'react'\n\n/**\n * Vendored, dependency-free base prose component map.\n *\n * This is a minimal fallback map that the resume spreads over\n * (`...baseMdxComponents`) before defining its own `h1/h3/h4/p/ul/li/strong/em/a/hr`.\n * It deliberately has NO dependency on `@atom63/ui`, lightbox, article blocks,\n * example containers, or any other `@atom63/*` package — every entry is a plain\n * HTML-element component so the resume package stays self-contained.\n *\n * It only needs to provide sane defaults for elements the resume does NOT override.\n */\nexport const baseMdxComponents = {\n h2: (props: React.HTMLAttributes<HTMLHeadingElement>) => <h2 {...props} />,\n h5: (props: React.HTMLAttributes<HTMLHeadingElement>) => <h5 {...props} />,\n h6: (props: React.HTMLAttributes<HTMLHeadingElement>) => <h6 {...props} />,\n ol: (props: React.HTMLAttributes<HTMLOListElement>) => <ol {...props} />,\n blockquote: (props: React.HTMLAttributes<HTMLElement>) => <blockquote {...props} />,\n code: (props: React.HTMLAttributes<HTMLElement>) => <code {...props} />,\n pre: (props: React.HTMLAttributes<HTMLPreElement>) => <pre {...props} />,\n img: (props: React.ImgHTMLAttributes<HTMLImageElement>) => (\n // biome-ignore lint/a11y/useAltText: alt is forwarded via spread props\n <img {...props} />\n ),\n} as const\n","const MDX_SPECIAL_PROTOCOL = /^(mailto|tel|javascript):/i\n\n/**\n * True when `href` should open in a new tab: off-origin http(s), protocol-relative,\n * or absolute http(s) during SSR. Same-origin URLs, in-app paths (`/…`, `#…`),\n * relative paths, and `mailto:` / `tel:` / `javascript:` stay in-tab / default behavior.\n */\nexport function isExternalMdxHref(href: string | undefined): boolean {\n if (!href) {\n return false\n }\n const t = href.trim()\n if (t.startsWith('#') || t.startsWith('/')) {\n return false\n }\n if (MDX_SPECIAL_PROTOCOL.test(t)) {\n return false\n }\n if (t.startsWith('//')) {\n return true\n }\n if (typeof globalThis.window !== 'undefined') {\n try {\n const u = new URL(t, globalThis.window.location.href)\n if (u.protocol !== 'http:' && u.protocol !== 'https:') {\n return false\n }\n return u.origin !== globalThis.window.location.origin\n } catch {\n return false\n }\n }\n try {\n const u = new URL(t)\n return u.protocol === 'http:' || u.protocol === 'https:'\n } catch {\n return false\n }\n}\n","import clsx from 'clsx'\nimport { baseMdxComponents } from '../mdx/base-components'\nimport { isExternalMdxHref } from '../mdx/external-href'\nimport { PaginatedResume } from './paginated-resume'\n\n// Styling for these primitives lives in resume-document.css as a small token\n// system (--doc-*) + semantic classes (.doc-*). Components only compose those —\n// no hardcoded sizes/colors/grid widths — so the resume and CV share one source\n// of truth. See paginated-resume.tsx for where the .css is imported and where\n// the [data-resume-document] token scope is applied.\n\nexport function Header({\n children,\n meta = false,\n}: {\n children: React.ReactNode\n /**\n * Use the universal meta grid (left label column + content), the same track\n * widths as <Section> and <Entry>, so the header aligns with everything\n * below it. The default is the resume's sidebar|main split.\n */\n meta?: boolean\n}) {\n return (\n <div\n className={clsx('doc-header', meta ? 'doc-meta-grid' : 'doc-resume-grid')}\n data-resume-header\n >\n {children}\n </div>\n )\n}\n\nfunction HeaderLeft({ children }: { children: React.ReactNode }) {\n return <div className=\"doc-header-col doc-header-left\">{children}</div>\n}\n\nfunction HeaderRight({ children }: { children: React.ReactNode }) {\n return <div className=\"doc-header-col doc-header-right\">{children}</div>\n}\n\nfunction Links({ children }: { children: React.ReactNode }) {\n return <div className=\"doc-group doc-links\">{children}</div>\n}\n\nexport function Group({ children }: { children: React.ReactNode }) {\n return <div className=\"doc-group\">{children}</div>\n}\n\n/**\n * A record row (award, media mention, talk, …): a `year | role` meta column on\n * the left and the linked title on the right. Each Entry is a single pagination\n * block, so render long CV lists as a flat sequence of Entry elements (NOT one\n * <ul>) so pages can break between them.\n */\nexport function Entry({\n year,\n role,\n children,\n}: {\n year?: string\n role?: string\n children: React.ReactNode\n}) {\n return (\n <div className=\"doc-meta-grid\">\n <span className=\"doc-meta\">{role ? `${year} | ${role}` : year}</span>\n <div className=\"doc-body doc-entry-body\">{children}</div>\n </div>\n )\n}\n\n/** A thin full-width divider — the CV uses one under the header band. */\nexport function Rule() {\n return <div className=\"doc-rule\" />\n}\n\n/**\n * Editorial section: the label sits in the left meta column (aligned with the\n * `year | role` column of Entry rows) and the content fills the right column,\n * so prose sections share one left edge with the record rows below. The\n * .doc-section margin keeps every section on the same vertical rhythm as the\n * record-section headings (h3). One pagination block — keep a body under a page.\n */\nexport function Section({ label, children }: { label: string; children: React.ReactNode }) {\n return (\n <div className=\"doc-section doc-meta-grid\">\n <h3 className=\"doc-heading\">{label}</h3>\n <div className=\"doc-col\">{children}</div>\n </div>\n )\n}\n\nexport function Footer({ children }: { children: React.ReactNode }) {\n return (\n <div className=\"doc-footer\" data-footer>\n {children}\n </div>\n )\n}\n\nexport function Columns({ children }: { children: React.ReactNode }) {\n return <div className=\"doc-resume-grid\">{children}</div>\n}\n\nexport function Sidebar({ children }: { children: React.ReactNode }) {\n return (\n <div className=\"doc-col doc-col-sidebar\" data-resume-col=\"sidebar\">\n {children}\n </div>\n )\n}\n\nexport function Main({ children }: { children: React.ReactNode }) {\n return (\n <div className=\"doc-col doc-col-main\" data-resume-col=\"main\">\n {children}\n </div>\n )\n}\n\nexport const resumeMdxComponents = {\n ...baseMdxComponents,\n\n ResumeDocument: PaginatedResume,\n\n Header,\n HeaderLeft,\n HeaderRight,\n Links,\n Group,\n Entry,\n Rule,\n Section,\n Footer,\n Columns,\n Sidebar,\n Main,\n\n h1: ({ children, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => (\n <h1 className=\"doc-title\" {...props}>\n {children}\n </h1>\n ),\n\n h3: ({ children, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => (\n <h3 className=\"doc-section doc-heading\" data-resume-keep-with-next {...props}>\n {children}\n </h3>\n ),\n\n h4: ({ children, ...props }: React.HTMLAttributes<HTMLHeadingElement>) => (\n <h4 className=\"doc-subheading\" {...props}>\n {children}\n </h4>\n ),\n\n p: ({ children, ...props }: React.HTMLAttributes<HTMLParagraphElement>) => (\n <p className=\"doc-body\" {...props}>\n {children}\n </p>\n ),\n\n ul: ({ children, ...props }: React.HTMLAttributes<HTMLUListElement>) => (\n <ul className=\"doc-list\" {...props}>\n {children}\n </ul>\n ),\n\n li: ({ children, ...props }: React.HTMLAttributes<HTMLLIElement>) => (\n <li className=\"doc-body\" {...props}>\n {children}\n </li>\n ),\n\n strong: ({ children, ...props }: React.HTMLAttributes<HTMLElement>) => (\n <strong className=\"doc-strong\" {...props}>\n {children}\n </strong>\n ),\n\n em: ({ children, ...props }: React.HTMLAttributes<HTMLElement>) => (\n <em className=\"doc-meta doc-em\" {...props}>\n {children}\n </em>\n ),\n\n a: ({ children, href, target, rel, ...props }: React.AnchorHTMLAttributes<HTMLAnchorElement>) => {\n const external = isExternalMdxHref(href)\n return (\n <a\n className=\"doc-link\"\n href={href}\n {...props}\n rel={external ? (rel ?? 'noopener noreferrer') : rel}\n target={external ? (target ?? '_blank') : target}\n >\n {children}\n </a>\n )\n },\n\n hr: (_props: React.HTMLAttributes<HTMLHRElement>) => <div className=\"doc-hr\" />,\n}\n","import {\n Children,\n Fragment,\n isValidElement,\n type ReactNode,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n} from 'react'\nimport { BLOCK_GAP_PX, CONTENT_WIDTH_PX, PAGE, USABLE_HEIGHT_PX } from '../geometry'\nimport { type Block, type ColumnKey, packIntoPages } from '../pagination/pagination'\nimport { Columns, Footer, Header, Main, Sidebar } from './components'\nimport { usePaginationReport, useResumeFontFamily } from './pagination-context'\nimport '../styles/tokens.css'\nimport '../styles/document.css'\n\ntype Columned = Record<ColumnKey, ReactNode[]>\n\n/**\n * Flatten one level, descending through Fragments. MDX wraps top-level content\n * (and component children) in Fragments, so `Children.toArray` alone yields the\n * Fragment, not the Header/Columns/Sidebar elements inside it.\n */\nfunction flattenChildren(children: ReactNode): ReactNode[] {\n const out: ReactNode[] = []\n for (const child of Children.toArray(children)) {\n if (isValidElement(child) && child.type === Fragment) {\n out.push(...flattenChildren((child.props as { children: ReactNode }).children))\n } else {\n out.push(child)\n }\n }\n return out\n}\n\n/** Pull Header / Columns(Sidebar,Main) / Footer out of the MDX wrapper children. */\nfunction extract(children: ReactNode): {\n header: ReactNode | null\n footer: ReactNode | null\n blocks: Columned\n} {\n let header: ReactNode | null = null\n let footer: ReactNode | null = null\n const blocks: Columned = { sidebar: [], main: [] }\n\n for (const child of flattenChildren(children)) {\n if (!isValidElement(child)) continue\n if (child.type === Header) header = child\n else if (child.type === Footer) footer = child\n else if (child.type === Columns) {\n for (const col of flattenChildren((child.props as { children: ReactNode }).children)) {\n if (!isValidElement(col)) continue\n const key: ColumnKey | null =\n col.type === Sidebar ? 'sidebar' : col.type === Main ? 'main' : null\n if (key) {\n blocks[key] = flattenChildren((col.props as { children: ReactNode }).children)\n }\n }\n }\n }\n return { header, footer, blocks }\n}\n\n/** Read a block element's full vertical extent (border box + margins). */\nfunction blockHeight(el: HTMLElement): number {\n const cs = getComputedStyle(el)\n const mt = Number.parseFloat(cs.marginTop) || 0\n const mb = Number.parseFloat(cs.marginBottom) || 0\n return el.getBoundingClientRect().height + mt + mb\n}\n\n/**\n * Mounted as the MDX `wrapper`, so it receives [Header, Columns, Footer].\n * Measures each column's blocks in a hidden layer, packs them into pages, and\n * renders physical page frames. Reports pageCount up to ResumeApp.\n */\nexport function PaginatedResume({ children }: { children: ReactNode }) {\n const { header, footer, blocks } = extract(children)\n const fontFamily = useResumeFontFamily()\n const report = usePaginationReport()\n\n const measureRef = useRef<HTMLDivElement>(null)\n const [heights, setHeights] = useState<Record<ColumnKey, number[]> | null>(null)\n const [headerH, setHeaderH] = useState(0)\n\n // contentKey changes whenever the rendered content could change height.\n // Print-fixed (px) sizes: only the inherited font *family* and the block\n // counts can change measured heights — the OS typography scale is ignored.\n const contentKey = `${fontFamily}:${blocks.sidebar.length}:${blocks.main.length}`\n\n // contentKey is the intentional re-measure trigger: a change in font family /\n // block counts must force a fresh measure even though it isn't read in the body.\n // biome-ignore lint/correctness/useExhaustiveDependencies: deliberate re-run trigger\n useLayoutEffect(() => {\n const root = measureRef.current\n if (!root) return\n const measure = () => {\n const cols: Record<ColumnKey, number[]> = { sidebar: [], main: [] }\n for (const key of ['sidebar', 'main'] as ColumnKey[]) {\n const colEl = root.querySelector(`[data-resume-col=\"${key}\"]`)\n if (colEl) {\n cols[key] = Array.from(colEl.children).map(c => blockHeight(c as HTMLElement))\n }\n }\n const headerEl = root.querySelector('[data-resume-header]') as HTMLElement | null\n setHeaderH(headerEl ? blockHeight(headerEl) : 0)\n setHeights(cols)\n }\n measure()\n const ro = new ResizeObserver(measure)\n ro.observe(root)\n\n // Re-measure on window (os63 window) resize — e.g. maximize/restore. The\n // measure root is fixed-width, so its own observer never catches this;\n // watch the enclosing scroll viewport, which tracks the window size. A\n // double rAF lets layout settle so a mid-transition read doesn't stick\n // (which collapsed multi-page docs to a single clipped page).\n const viewport = root.closest('[data-slot=\"scroll-area-viewport\"]')\n // Debounce to a TRAILING measure: the os63 window's maximize/restore is a\n // spring animation, so reading mid-transition lands on a wrong height that\n // then sticks. Wait for the resize to stop, then measure the settled layout.\n let settleTimer: ReturnType<typeof setTimeout> | undefined\n const scheduleMeasure = () => {\n clearTimeout(settleTimer)\n settleTimer = setTimeout(measure, 250)\n }\n const vpObserver = viewport ? new ResizeObserver(scheduleMeasure) : null\n vpObserver?.observe(viewport as Element)\n\n if (typeof document !== 'undefined' && 'fonts' in document) {\n document.fonts.ready.then(measure).catch(() => {})\n }\n return () => {\n ro.disconnect()\n vpObserver?.disconnect()\n clearTimeout(settleTimer)\n }\n }, [contentKey])\n\n // The hidden measuring layer renders the real structure at content width.\n const measureLayer = (\n <div\n aria-hidden\n data-resume-measure\n ref={measureRef}\n style={{\n position: 'absolute',\n top: 0,\n left: -99999,\n width: CONTENT_WIDTH_PX,\n visibility: 'hidden',\n pointerEvents: 'none',\n }}\n >\n {header}\n {blocks.sidebar.length === 0 ? (\n // Single-column docs (e.g. the CV) put everything in Main and render\n // full-width. Measure at full width too so block heights match the\n // full-width page render below (otherwise they'd be measured at the\n // narrower 2/3 main-column width and mis-paginate).\n <div className=\"doc-col\" data-resume-col=\"main\">\n {blocks.main}\n </div>\n ) : (\n <Columns>\n <Sidebar>{blocks.sidebar}</Sidebar>\n <Main>{blocks.main}</Main>\n </Columns>\n )}\n </div>\n )\n\n if (!heights) {\n return <div data-resume-document>{measureLayer}</div>\n }\n\n const toBlocks = (key: ColumnKey): Block[] =>\n blocks[key].map((node, i) => ({\n id: `${key}-${i}`,\n height: heights[key][i] ?? 0,\n keepWithNext:\n isValidElement(node) &&\n Boolean((node.props as Record<string, unknown>)['data-resume-keep-with-next']),\n }))\n\n const sidebarBlocks = toBlocks('sidebar')\n const mainBlocks = toBlocks('main')\n\n const pack = packIntoPages({\n columns: { sidebar: sidebarBlocks, main: mainBlocks },\n pageUsableHeight: p => (p === 0 ? Math.max(0, USABLE_HEIGHT_PX - headerH) : USABLE_HEIGHT_PX),\n blockGap: BLOCK_GAP_PX,\n })\n\n return (\n <div data-resume-document>\n {measureLayer}\n <div className=\"doc-pages\" style={{ gap: PAGE.gapPx }}>\n {Array.from({ length: pack.pageCount }, (_, p) => {\n const sideOnPage = blocks.sidebar.filter(\n (_, i) => pack.pageOf.sidebar[`sidebar-${i}`] === p\n )\n const mainOnPage = blocks.main.filter((_, i) => pack.pageOf.main[`main-${i}`] === p)\n // Key the layout on the WHOLE document, not the current page: a\n // single-column doc (the CV — no sidebar at all) renders full\n // width, but a two-column doc keeps its grid on every page,\n // including continuation pages where the sidebar has run out, so\n // the main column stays in its right-hand track instead of\n // jumping to full width (which broke the 2-col layout on page 2+).\n const isTwoColumn = blocks.sidebar.length > 0\n return (\n <div\n className=\"doc-page\"\n data-resume-page\n key={`page-${String(p)}`}\n style={{\n width: PAGE.widthPx,\n height: PAGE.heightPx,\n overflow: 'hidden',\n padding: `${PAGE.padYPx}px ${PAGE.padXPx}px`,\n }}\n >\n {p === 0 && header}\n {isTwoColumn ? (\n <div className=\"doc-resume-grid\">\n <div className=\"doc-col doc-col-sidebar\">{sideOnPage}</div>\n <div className=\"doc-col doc-col-main\">{mainOnPage}</div>\n </div>\n ) : (\n <div className=\"doc-col\">{mainOnPage}</div>\n )}\n {p === pack.pageCount - 1 && footer}\n </div>\n )\n })}\n </div>\n <PageCountReporter count={pack.pageCount} report={report} />\n </div>\n )\n}\n\n/** Reports pageCount via an effect (no setState-during-render). */\nfunction PageCountReporter({ count, report }: { count: number; report: (n: number) => void }) {\n useEffect(() => {\n report(count)\n }, [count, report])\n return null\n}\n"]}