@drawnagency/primitives 0.1.80 → 0.1.81

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.
Files changed (102) hide show
  1. package/dist/{chunk-KVQFMVXE.js → chunk-DKXISNJ2.js} +2 -2
  2. package/dist/{chunk-RSITF24B.js → chunk-EVNEMJZ6.js} +9 -3
  3. package/dist/{chunk-VI26O4E3.js → chunk-FDBZZ4AT.js} +1 -1
  4. package/dist/{chunk-QB5Q23G2.js → chunk-FN4NKUZU.js} +1 -1
  5. package/dist/{chunk-EMGLZQCM.js → chunk-MBJUWOM2.js} +1 -1
  6. package/dist/{chunk-4TVODHD2.js → chunk-P4ZNHZMP.js} +6 -6
  7. package/dist/{chunk-UNUJPLZV.js → chunk-PJOHAWNV.js} +1 -1
  8. package/dist/{chunk-2DDWWTRX.js → chunk-Q6LZOH5M.js} +1 -1
  9. package/dist/chunk-WP5UX4MI.js +63 -0
  10. package/dist/{chunk-3LNKOLZP.js → chunk-YKOLOZB2.js} +1 -1
  11. package/dist/{chunk-7XWER6YR.js → chunk-ZEXR7JYQ.js} +3 -3
  12. package/dist/components/editor/PagesModal.d.ts +2 -0
  13. package/dist/components/editor/PagesModal.d.ts.map +1 -1
  14. package/dist/components/editor/index.js +6 -5
  15. package/dist/components/primitives/index.js +7 -6
  16. package/dist/components/sections/brand-guide.js +7 -6
  17. package/dist/components/sections/register-schemas.js +10 -9
  18. package/dist/components/sections/register.js +10 -9
  19. package/dist/components/shell/DocInspector.d.ts +17 -0
  20. package/dist/components/shell/DocInspector.d.ts.map +1 -0
  21. package/dist/components/shell/DocumentEditorOverlay.d.ts +29 -0
  22. package/dist/components/shell/DocumentEditorOverlay.d.ts.map +1 -0
  23. package/dist/components/shell/DocumentPresentOverlay.d.ts +30 -0
  24. package/dist/components/shell/DocumentPresentOverlay.d.ts.map +1 -0
  25. package/dist/components/shell/EditorShell.d.ts +31 -0
  26. package/dist/components/shell/EditorShell.d.ts.map +1 -1
  27. package/dist/document/assets/fonts/albra-grotesk-bold.woff +0 -0
  28. package/dist/document/assets/fonts/albra-grotesk-regular.woff +0 -0
  29. package/dist/document/assets/fonts/kapra-neue-expanded-medium.woff +0 -0
  30. package/dist/document/assets/fonts/kapra-neue-expanded-regular.woff +0 -0
  31. package/dist/document/components/DocCoverPage.d.ts +11 -0
  32. package/dist/document/components/DocCoverPage.d.ts.map +1 -0
  33. package/dist/document/components/DocPage.d.ts +10 -0
  34. package/dist/document/components/DocPage.d.ts.map +1 -0
  35. package/dist/document/components/DocRail.d.ts +27 -0
  36. package/dist/document/components/DocRail.d.ts.map +1 -0
  37. package/dist/document/components/DocSectionPage.d.ts +12 -0
  38. package/dist/document/components/DocSectionPage.d.ts.map +1 -0
  39. package/dist/document/components/DocViewer.d.ts +18 -0
  40. package/dist/document/components/DocViewer.d.ts.map +1 -0
  41. package/dist/document/components/Wordmark.d.ts +4 -0
  42. package/dist/document/components/Wordmark.d.ts.map +1 -0
  43. package/dist/document/components/measure.d.ts +6 -0
  44. package/dist/document/components/measure.d.ts.map +1 -0
  45. package/dist/document/components/useDocPages.d.ts +12 -0
  46. package/dist/document/components/useDocPages.d.ts.map +1 -0
  47. package/dist/document/engine/blocks.d.ts +2 -0
  48. package/dist/document/engine/blocks.d.ts.map +1 -0
  49. package/dist/document/engine/harvest.d.ts +3 -0
  50. package/dist/document/engine/harvest.d.ts.map +1 -0
  51. package/dist/document/engine/masonry.d.ts +25 -0
  52. package/dist/document/engine/masonry.d.ts.map +1 -0
  53. package/dist/document/engine/paginate.d.ts +40 -0
  54. package/dist/document/engine/paginate.d.ts.map +1 -0
  55. package/dist/document/engine/settings.d.ts +70 -0
  56. package/dist/document/engine/settings.d.ts.map +1 -0
  57. package/dist/document/engine/types.d.ts +40 -0
  58. package/dist/document/engine/types.d.ts.map +1 -0
  59. package/dist/document/index.d.ts +13 -0
  60. package/dist/document/index.d.ts.map +1 -0
  61. package/dist/document/index.js +881 -0
  62. package/dist/document/styles/document.css +208 -0
  63. package/dist/index.js +29 -28
  64. package/dist/lib/document-input.d.ts +25 -0
  65. package/dist/lib/document-input.d.ts.map +1 -0
  66. package/dist/lib/index.js +10 -9
  67. package/dist/lib/pages.d.ts +2 -0
  68. package/dist/lib/pages.d.ts.map +1 -1
  69. package/dist/media/index.js +4 -3
  70. package/dist/schemas/index.js +3 -2
  71. package/dist/schemas/site-config.d.ts +223 -1
  72. package/dist/schemas/site-config.d.ts.map +1 -1
  73. package/package.json +5 -1
  74. package/src/components/editor/PagesModal.tsx +16 -2
  75. package/src/components/shell/DocInspector.tsx +278 -0
  76. package/src/components/shell/DocumentEditorOverlay.tsx +169 -0
  77. package/src/components/shell/DocumentPresentOverlay.tsx +87 -0
  78. package/src/components/shell/EditorShell.tsx +128 -5
  79. package/src/document/assets/fonts/albra-grotesk-bold.woff +0 -0
  80. package/src/document/assets/fonts/albra-grotesk-regular.woff +0 -0
  81. package/src/document/assets/fonts/kapra-neue-expanded-medium.woff +0 -0
  82. package/src/document/assets/fonts/kapra-neue-expanded-regular.woff +0 -0
  83. package/src/document/components/DocCoverPage.tsx +45 -0
  84. package/src/document/components/DocPage.tsx +40 -0
  85. package/src/document/components/DocRail.tsx +160 -0
  86. package/src/document/components/DocSectionPage.tsx +45 -0
  87. package/src/document/components/DocViewer.tsx +285 -0
  88. package/src/document/components/Wordmark.tsx +18 -0
  89. package/src/document/components/measure.tsx +105 -0
  90. package/src/document/components/useDocPages.ts +67 -0
  91. package/src/document/engine/blocks.ts +87 -0
  92. package/src/document/engine/harvest.ts +80 -0
  93. package/src/document/engine/masonry.ts +89 -0
  94. package/src/document/engine/paginate.ts +209 -0
  95. package/src/document/engine/settings.ts +77 -0
  96. package/src/document/engine/types.ts +54 -0
  97. package/src/document/index.ts +18 -0
  98. package/src/document/styles/document.css +208 -0
  99. package/src/lib/document-input.ts +57 -0
  100. package/src/lib/pages.ts +22 -0
  101. package/src/schemas/site-config.ts +22 -7
  102. /package/dist/{chunk-4YEOTFVU.js → chunk-YBVK254P.js} +0 -0
@@ -0,0 +1,208 @@
1
+ /* Document artifact rendering — physical units only. This file styles the
2
+ artifact itself (editor canvas AND print route), so no Tailwind utilities
3
+ and no viewport-relative units. 11×17 tabloid landscape = 17in × 11in. */
4
+
5
+ /* ---- Drawn brand fonts ---------------------------------------------- */
6
+ /* Albra Grotesk (sans body) · Kapra Neue Expanded (uppercase labels).
7
+ Self-hosted, resolved relative to this file so bundlers can locate them
8
+ without a public root. */
9
+ @font-face {
10
+ font-family: "Albra Grotesk";
11
+ src: url("../assets/fonts/albra-grotesk-regular.woff") format("woff");
12
+ font-weight: 400;
13
+ font-style: normal;
14
+ font-display: swap;
15
+ }
16
+ @font-face {
17
+ font-family: "Albra Grotesk";
18
+ src: url("../assets/fonts/albra-grotesk-bold.woff") format("woff");
19
+ font-weight: 600;
20
+ font-style: normal;
21
+ font-display: swap;
22
+ }
23
+ @font-face {
24
+ font-family: "Kapra Neue Expanded";
25
+ src: url("../assets/fonts/kapra-neue-expanded-regular.woff") format("woff");
26
+ font-weight: 400;
27
+ font-style: normal;
28
+ font-display: swap;
29
+ }
30
+ @font-face {
31
+ font-family: "Kapra Neue Expanded";
32
+ src: url("../assets/fonts/kapra-neue-expanded-medium.woff") format("woff");
33
+ font-weight: 700;
34
+ font-style: normal;
35
+ font-display: swap;
36
+ }
37
+
38
+ /* ---- page chrome ------------------------------------------------------ */
39
+
40
+ .deck-page {
41
+ position: relative;
42
+ width: 17in;
43
+ height: 11in;
44
+ background: #fff;
45
+ color: #1a1a1a;
46
+ overflow: hidden;
47
+ }
48
+
49
+ .deck-chrome {
50
+ position: absolute;
51
+ top: 0.5in;
52
+ left: 0.6in;
53
+ right: 0.6in;
54
+ display: flex;
55
+ align-items: flex-end;
56
+ justify-content: space-between;
57
+ border-bottom: 1px solid #1a1a1a;
58
+ padding-bottom: 0.1in;
59
+ z-index: 10;
60
+ }
61
+
62
+ .deck-crumb {
63
+ font-family: "Kapra Neue Expanded", Helvetica, Arial, sans-serif;
64
+ font-size: 9pt;
65
+ letter-spacing: 0.12em;
66
+ text-transform: uppercase;
67
+ }
68
+
69
+ /* The wordmark SVG's viewBox includes an under-swash flourish below the
70
+ letters' baseline. The clip wrapper truncates the glyph so the hairline
71
+ rule slices INTO the letter bottoms (per the reference deck): the cut
72
+ falls at ~viewBox y=100.5 of 154.474 (0.407 / 0.625), removing the
73
+ under-swash, the D's descender loop, and the rounded letter bottoms.
74
+ The negative margin cancels .deck-chrome's padding-bottom so the cut
75
+ edge lands exactly on the rule, while the breadcrumb keeps its normal
76
+ gap above the rule. */
77
+ .deck-wordmark-clip {
78
+ height: 0.407in;
79
+ overflow: hidden;
80
+ margin-bottom: -0.1in;
81
+ }
82
+
83
+ .deck-wordmark {
84
+ height: 0.625in;
85
+ width: auto;
86
+ display: block;
87
+ }
88
+
89
+ .deck-body {
90
+ position: absolute;
91
+ top: 1.35in;
92
+ left: 0.6in;
93
+ right: 0.6in;
94
+ bottom: 0.6in;
95
+ }
96
+
97
+ /* Bleed layouts (cover) paint the whole page; chrome layers above via
98
+ .deck-chrome's z-index — layering only, chrome treatment is untouched. */
99
+ .deck-bleed {
100
+ position: absolute;
101
+ inset: 0;
102
+ }
103
+
104
+ /* ---- title card (document cover) --------------------------------------- */
105
+
106
+ .layout-titlecard {
107
+ position: absolute;
108
+ inset: 0;
109
+ color: #fff;
110
+ }
111
+ .layout-titlecard__masthead {
112
+ position: absolute;
113
+ top: 0.5in;
114
+ left: 0.6in;
115
+ right: 0.6in;
116
+ display: flex;
117
+ align-items: center;
118
+ gap: 0.3in;
119
+ }
120
+ .layout-titlecard__wordmark { height: 0.4in; width: auto; color: #fff; flex: none; }
121
+ .layout-titlecard__rule { flex: 1; height: 1px; background: #fff; }
122
+ /* Optional cover image: the band between the masthead rule (~1.1in) and the
123
+ title lockup (~3.2in from the page bottom). Image is 50% of the band's
124
+ height, natural aspect ratio, left-aligned to the content margin, its
125
+ center anchored at 45% of the band (top 20% + half of 50%). */
126
+ .layout-titlecard__image {
127
+ position: absolute;
128
+ top: 1.1in;
129
+ bottom: 3.2in;
130
+ left: 0.6in;
131
+ right: 0.6in;
132
+ }
133
+ .layout-titlecard__image img {
134
+ position: absolute;
135
+ top: 20%;
136
+ left: 0;
137
+ height: 50%;
138
+ width: auto;
139
+ max-width: 100%;
140
+ object-fit: contain;
141
+ }
142
+ .layout-titlecard__lockup {
143
+ position: absolute;
144
+ left: 0.6in;
145
+ bottom: 1.1in;
146
+ right: 0.6in;
147
+ }
148
+ .layout-titlecard__title {
149
+ font-family: "Kapra Neue Expanded", Helvetica, Arial, sans-serif;
150
+ font-size: 40pt;
151
+ letter-spacing: 0.14em;
152
+ text-transform: uppercase;
153
+ margin: 0 0 0.25in;
154
+ }
155
+ .layout-titlecard__titlerule { display: block; width: 100%; height: 1px; margin-bottom: 0.18in; }
156
+ .layout-titlecard__label {
157
+ font-family: "Kapra Neue Expanded", Helvetica, Arial, sans-serif;
158
+ font-size: 10pt;
159
+ letter-spacing: 0.12em;
160
+ text-transform: uppercase;
161
+ margin: 0;
162
+ }
163
+ .layout-titlecard__date {
164
+ font-family: "Kapra Neue Expanded", Helvetica, Arial, sans-serif;
165
+ font-size: 10pt;
166
+ letter-spacing: 0.12em;
167
+ text-transform: uppercase;
168
+ margin: 0.04in 0 0;
169
+ }
170
+
171
+ /* ---- document pages ----------------------------------------------------- */
172
+
173
+ .pdoc-title {
174
+ font-family: "Kapra Neue Expanded", Helvetica, Arial, sans-serif;
175
+ font-size: 22pt;
176
+ letter-spacing: 0.04em;
177
+ text-transform: uppercase;
178
+ margin: 0;
179
+ height: 0.9in; /* = DOC_GEOM.titleBandIn; keep in sync */
180
+ }
181
+ .pdoc-text { position: absolute; }
182
+ .pdoc-h2, .pdoc-h3 {
183
+ font-family: "Kapra Neue Expanded", Helvetica, Arial, sans-serif;
184
+ text-transform: uppercase;
185
+ letter-spacing: 0.04em;
186
+ line-height: 1.3;
187
+ margin: 0;
188
+ }
189
+ .pdoc-h2 { font-size: 14pt; }
190
+ .pdoc-h3 { font-size: 11pt; letter-spacing: 0.08em; }
191
+ .pdoc-prose {
192
+ font-family: "Albra Grotesk", Helvetica, Arial, sans-serif;
193
+ font-size: 12.5pt;
194
+ line-height: 1.55;
195
+ margin: 0;
196
+ }
197
+ /* Self-contained prose: renders identically with or without Tailwind
198
+ preflight. The editor loads preflight but the /print route and the
199
+ hidden measurer do not, so pin every element the upstream sanitizer
200
+ allows to zero margins and give lists explicit markers — otherwise
201
+ measured heights (and page counts) diverge across surfaces. */
202
+ .pdoc-prose :where(p, ul, ol, li, blockquote, h1, h2, h3, h4, h5, h6, pre, hr) { margin: 0; }
203
+ .pdoc-prose ul { list-style: disc; }
204
+ .pdoc-prose ol { list-style: decimal; }
205
+ .pdoc-prose ul, .pdoc-prose ol { padding-left: 0.25in; }
206
+ .pdoc-img { position: absolute; overflow: hidden; }
207
+ .pdoc-img img { width: 100%; height: 100%; display: block; }
208
+ .pdoc-measure { position: fixed; left: -10000px; top: 0; visibility: hidden; pointer-events: none; }
@@ -0,0 +1,57 @@
1
+ // Content adapters that turn portal content into the document engine's input
2
+ // shape. These live in the primitives lib (outside the sealed document/ subtree)
3
+ // so the editor (EditorShell) can reach them; primitives cannot import core, so
4
+ // the audience-filtered viewer/presentation adapter lives in core's
5
+ // document-route.ts and calls into this module for image mapping.
6
+ import type { MediaManifest } from "../schemas/media";
7
+ import type { Page } from "../schemas/site-config";
8
+ import type { LoadedSection } from "./loader";
9
+ import type { DocImage, DocSectionInput, DocSourceContent } from "../document";
10
+
11
+ /**
12
+ * Map a media manifest to the engine's pre-resolved image dictionary. Port of
13
+ * the harness's `portalImagesFrom` against the real MediaManifest type:
14
+ * - only `kind === "image"` entries (videos/animated skipped);
15
+ * - SVGs → `original.svg` for both preview and print;
16
+ * - other images: previewFile = largest variant ≤ 1080 (else the smallest),
17
+ * printFile = largest; images with zero variants are skipped.
18
+ * The manifest is already schema-parsed upstream, so no tolerant re-parse here.
19
+ */
20
+ export function docImagesFrom(manifest: MediaManifest): Record<string, DocImage> {
21
+ const out: Record<string, DocImage> = {};
22
+ for (const [imageId, item] of Object.entries(manifest.images)) {
23
+ if (item.kind !== "image") continue;
24
+ if (item.mimeType === "image/svg+xml") {
25
+ out[imageId] = { imageId, width: item.width, height: item.height, alt: item.alt, previewFile: "original.svg", printFile: "original.svg" };
26
+ continue;
27
+ }
28
+ const widths = item.variants.map((v) => v.width).sort((a, b) => a - b);
29
+ if (widths.length === 0) continue;
30
+ const preview = [...widths].reverse().find((w) => w <= 1080) ?? widths[0];
31
+ const print = widths[widths.length - 1];
32
+ out[imageId] = { imageId, width: item.width, height: item.height, alt: item.alt, previewFile: `${preview}.webp`, printFile: `${print}.webp` };
33
+ }
34
+ return out;
35
+ }
36
+
37
+ function toSectionInput(s: LoadedSection): DocSectionInput {
38
+ return { id: s.section.id, type: s.section.type, content: s.section.content, options: s.section.options };
39
+ }
40
+
41
+ /**
42
+ * Editor path: build the engine's source content from the caller's already-
43
+ * selected page + its draft sections, verbatim. No status/audience filtering —
44
+ * editors see everything (the shell passes in-memory draft content).
45
+ */
46
+ export function buildDraftDocumentInput(opts: {
47
+ page: Page;
48
+ sections: LoadedSection[];
49
+ manifest: MediaManifest;
50
+ }): DocSourceContent {
51
+ const { page, sections, manifest } = opts;
52
+ return {
53
+ pageTitle: page.title,
54
+ sections: sections.map(toSectionInput),
55
+ images: docImagesFrom(manifest),
56
+ };
57
+ }
package/src/lib/pages.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  import type { Page, SiteIndex, SectionMeta } from "../schemas/site-config";
2
2
  import { RESERVED_SLUGS } from "../schemas/site-config";
3
+ // Sanctioned outside->inside import: document/ is a sealed subtree, but pages.ts
4
+ // is allowed to reach into its engine to write the page-level document options
5
+ // block (mirrors the same sanctioned import in schemas/site-config.ts).
6
+ import { documentOptionsSchema, type DocumentOptions } from "../document/engine/settings";
3
7
 
4
8
  /**
5
9
  * Display fallback for blank page titles. addPage creates pages with an empty
@@ -96,6 +100,24 @@ export function setPageFields(
96
100
  return mapPages(index, (p) => (p.id === pageId ? { ...p, ...patch } : p));
97
101
  }
98
102
 
103
+ export function setPageDocument(index: SiteIndex, pageId: string, patch: Partial<DocumentOptions>): SiteIndex {
104
+ return {
105
+ ...index,
106
+ pages: index.pages.map((p) => {
107
+ if (p.id !== pageId) return p;
108
+ const current = p.document ?? documentOptionsSchema.parse({});
109
+ return {
110
+ ...p,
111
+ document: documentOptionsSchema.parse({
112
+ ...current,
113
+ ...patch,
114
+ sections: { ...current.sections, ...(patch.sections ?? {}) },
115
+ }),
116
+ };
117
+ }),
118
+ };
119
+ }
120
+
99
121
  export function setPageAudience(
100
122
  index: SiteIndex,
101
123
  pageId: string,
@@ -2,6 +2,10 @@ import { z } from "zod";
2
2
  import { MediaConfigSchema } from "./media";
3
3
  import { HexColorSchema, FontNameSchema } from "./shared";
4
4
  import { CORNER_RADIUS_OPTIONS } from "../lib/corner-radius";
5
+ // Sanctioned outside->inside import: document/ is a sealed subtree, but
6
+ // site-config.ts is allowed to reach into its engine to wire the page-level
7
+ // document options block onto PageSchema.
8
+ import { documentOptionsSchema } from "../document/engine/settings";
5
9
 
6
10
  const StatusSchema = z.enum(["draft", "live", "archived", "published"]).transform(
7
11
  (val) => val === "published" ? "live" as const : val,
@@ -15,12 +19,22 @@ export const SectionMetaSchema = z.object({
15
19
 
16
20
  export type SectionMeta = z.infer<typeof SectionMetaSchema>;
17
21
 
18
- // Page slugs that would shadow a platform route. KEEP IN SYNC with the root and
19
- // /edit/* route patterns injected in packages/core/src/integration.ts — if a new
20
- // root-level route is added there, add its first segment here (the test in Step 1
21
- // guards the known set). This is the single source of truth for reserved slugs;
22
- // the schema, uniquePageSlug, and /api/save all import it.
23
- export const RESERVED_SLUGS = ["edit", "api", "login", "set-password", "404"] as const;
22
+ // Page slugs that would shadow a platform route. KEEP IN SYNC with the root,
23
+ // /edit/*, and /present/* route patterns injected in
24
+ // packages/core/src/integration.ts — if a new root-level route is added there,
25
+ // add its first segment here (the test in Step 1 guards the known set). This is
26
+ // the single source of truth for reserved slugs; the schema, uniquePageSlug, and
27
+ // /api/save all import it.
28
+ export const RESERVED_SLUGS = ["edit", "api", "login", "set-password", "404", "present"] as const;
29
+
30
+ // Subset of RESERVED_SLUGS the index schema hard-rejects at parse time. Only
31
+ // slugs reserved since before client sites existed may hard-fail here: parse-
32
+ // rejecting a newly-reserved slug would brick an already-deployed index that
33
+ // uses it (viewer AND editor refuse to load, leaving no in-product rename
34
+ // path). Newly reserved slugs ("present") are enforced at authoring time only —
35
+ // PagesModal and uniquePageSlug block new uses via RESERVED_SLUGS, and the
36
+ // injected platform route simply shadows a grandfathered page until renamed.
37
+ const PARSE_REJECTED_SLUGS = ["edit", "api", "login", "set-password", "404"] as const;
24
38
 
25
39
  export const PageStatusSchema = z.enum(["live", "archived"]);
26
40
 
@@ -33,6 +47,7 @@ export const PageSchema = z.object({
33
47
  status: PageStatusSchema,
34
48
  access: z.array(z.string()),
35
49
  order: z.array(z.string()),
50
+ document: documentOptionsSchema.optional(),
36
51
  });
37
52
 
38
53
  export type Page = z.infer<typeof PageSchema>;
@@ -102,7 +117,7 @@ const CanonicalIndexSchema = z
102
117
  if (!SLUG_RE.test(p.slug)) {
103
118
  ctx.addIssue({ code: z.ZodIssueCode.custom, message: `Invalid slug "${p.slug}"` });
104
119
  }
105
- if ((RESERVED_SLUGS as readonly string[]).includes(p.slug)) {
120
+ if ((PARSE_REJECTED_SLUGS as readonly string[]).includes(p.slug)) {
106
121
  ctx.addIssue({ code: z.ZodIssueCode.custom, message: `Slug "${p.slug}" is reserved` });
107
122
  }
108
123
  }
File without changes