gutterpress 0.11.19 → 0.11.20-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/api/index.js +2 -2
  2. package/dist/{build-czbaj4qf.js → build-qmjg9r2d.js} +4 -4
  3. package/dist/checks/pdf/index.d.ts +1 -0
  4. package/dist/checks/pdf/safe-margins.d.ts +3 -0
  5. package/dist/{cli-cgbzdrwz.js → cli-7z29z4wz.js} +1 -1
  6. package/dist/{cli-dzw28t3s.js → cli-gh0ym80r.js} +2 -1
  7. package/dist/{cli-xx94a1k6.js → cli-j802s31j.js} +5 -5
  8. package/dist/{cli-aw614ztv.js → cli-zacevwcy.js} +299 -184
  9. package/dist/cli.js +12 -12
  10. package/dist/{doctor-6e9perj7.js → doctor-fe7tcs4v.js} +2 -2
  11. package/dist/{engine-fe0n7v9p.js → engine-9xb1ky8d.js} +1 -1
  12. package/dist/{engine-zehw6tkv.js → engine-yax79k9m.js} +2 -2
  13. package/dist/{ext-kavfk1zn.js → ext-ahwxt6gv.js} +4 -4
  14. package/dist/{gutterpress-viewer-k4mpqjh0.js → gutterpress-viewer-retj6ym2.js} +515 -281
  15. package/dist/{index-jt552wtf.js → index-3nr5rzzp.js} +5 -5
  16. package/dist/{index-b866g2ph.js → index-6xwtb7cw.js} +1 -1
  17. package/dist/{index-g7vszjqf.js → index-razpty47.js} +298 -182
  18. package/dist/index.js +3 -3
  19. package/dist/lib/ghostscript.d.ts +7 -0
  20. package/dist/lib/presets.d.ts +3 -1
  21. package/dist/{manifest.schema-5pzhscqh.json → manifest.schema-74s1ykyv.json} +20 -1
  22. package/dist/{new-ckpas1ya.js → new-ftd4fec4.js} +4 -4
  23. package/dist/{preflight-yh57q9nn.js → preflight-8sqarcsr.js} +4 -4
  24. package/dist/preview/file-watcher.d.ts +16 -2
  25. package/dist/{preview-bridge-evecsh4w.js → preview-bridge-8wjcjec3.js} +3 -2
  26. package/dist/{preview-interface-2n1d9pbf.js → preview-interface-nnmrxyqm.js} +37 -41
  27. package/dist/{preview-a4x2ezjp.js → preview-q76xn95h.js} +4 -4
  28. package/dist/{preview-shell-hyp7g158.js → preview-shell-wk9k3gcd.js} +18 -6
  29. package/dist/{publish-sfn5hmnx.js → publish-7gdy6gbf.js} +4 -4
  30. package/dist/schema/manifest.types.d.ts +38 -3
  31. package/dist/{validate-281rsdsv.js → validate-e3ryegce.js} +4 -4
  32. package/package.json +1 -1
@@ -62,7 +62,6 @@
62
62
  --gp-sheet-bg: #fff;
63
63
  --gp-stage-bg: #4a4a52;
64
64
  --gp-guide: #e5484d;
65
- --gp-safe: #30a46c;
66
65
  background: var(--gp-stage-bg);
67
66
  margin: 0;
68
67
  padding: 32px;
@@ -275,110 +274,8 @@
275
274
  .gp-marginbox[data-align="center"] > .gp-marginbox-content { justify-content: center; }
276
275
  .gp-marginbox[data-align="end"] > .gp-marginbox-content { justify-content: flex-end; }
277
276
 
278
- /* margin & bleed overlay (overlay.ts) -------------------------------- */
279
- /* THE LOOK LIVES HERE, in these tokens, and nowhere else: colours, line
280
- width, line style, tint strength. The desktop app's settings change the
281
- colour (an inline \`--gp-ov-color\` on <body>) and pick a strength preset
282
- (\`data-overlay-strength\`); nothing in a book's CSS is involved or needed. */
283
- .gp-stage {
284
- --gp-ov-color: #2f80ed; /* margin box line + the tint over the margins */
285
- --gp-ov-width: 2px; /* line width as seen on screen, at any zoom */
286
- --gp-ov-style: dashed; /* margin box line style */
287
- --gp-ov-tint: 0.1; /* opacity of the tint over the margin area */
288
- --gp-ov-bleed-color: #e5484d; /* bleed band + trim line */
289
- --gp-ov-bleed-alpha: 0.45; /* opacity of the bleed band */
290
- --gp-ov-trim-color: #8a0b18; /* the trim line: darker than the band so it reads as an edge */
291
- --gp-ov-flag-width: 2px; /* overflow flag outline */
292
- --gp-ov-warn: #f59e0b; /* reaches into the margin */
293
- --gp-ov-error: #e5484d; /* crosses the trim / leaves the page */
294
- --gp-ov-intent: #8b5cf6; /* placed on purpose */
295
- }
296
- .gp-stage[data-overlay-strength="subtle"] {
297
- --gp-ov-width: 1px;
298
- --gp-ov-tint: 0.05;
299
- --gp-ov-bleed-alpha: 0.3;
300
- }
301
- .gp-stage[data-overlay-strength="strong"] {
302
- --gp-ov-width: 3px;
303
- --gp-ov-tint: 0.2;
304
- --gp-ov-bleed-alpha: 0.65;
305
- --gp-ov-flag-width: 3px;
306
- }
307
-
308
- /* One sibling layer per run, ABOVE the strip so the lines stay visible over
309
- author backgrounds and art. Absolutely positioned and pointer-transparent:
310
- it takes no flow space and no hits, so it cannot move a page break. Exists
311
- only while the overlay is on. */
312
- .gp-ov {
313
- position: absolute;
314
- inset: 0;
315
- z-index: 5;
316
- pointer-events: none;
317
- /* The stage is a paint transform, so a 2px line would shrink with the zoom
318
- and vanish at fit-to-width. Lines are sized in layout px divided by the
319
- zoom, which makes them the same width on screen at every zoom. */
320
- --gp-ov-w: calc(var(--gp-ov-width) / var(--gp-zoom, 1));
321
- --gp-ov-flag-w: calc(var(--gp-ov-flag-width) / var(--gp-zoom, 1));
322
- }
323
-
324
- .gp-ov-page {
325
- position: absolute;
326
- }
327
-
328
- /* the margins as a region: the trim minus the content box (clip-path, set in
329
- overlay.ts), so a margin reads as an area, not a hairline */
330
- .gp-ov-tint {
331
- position: absolute;
332
- inset: 0;
333
- background: color-mix(in srgb, var(--gp-ov-color) calc(var(--gp-ov-tint) * 100%), transparent);
334
- }
335
-
336
- /* margin box: the content area's outline */
337
- .gp-ov-margin {
338
- position: absolute;
339
- box-sizing: border-box;
340
- border: var(--gp-ov-w) var(--gp-ov-style) var(--gp-ov-color);
341
- }
342
-
343
- /* bleed: a translucent band OUTSIDE the trim (its width is the border) */
344
- .gp-ov-bleed {
345
- position: absolute;
346
- box-sizing: border-box;
347
- border-style: solid;
348
- border-color: color-mix(in srgb, var(--gp-ov-bleed-color) calc(var(--gp-ov-bleed-alpha) * 100%), transparent);
349
- }
350
-
351
- /* trim: where the sheet will be cut, drawn only when there is bleed — solid,
352
- just inside the edge so it never paints over the band (overlay.ts sizes and
353
- places it: the whole sheet, or the sheet less an in-sheet bleed) */
354
- .gp-ov-trim {
355
- position: absolute;
356
- outline: var(--gp-ov-w) solid var(--gp-ov-trim-color);
357
- outline-offset: calc(-1 * var(--gp-ov-w));
358
- }
359
-
360
- /* overflow flags — outline + faint tint, never a box change */
361
- .gp-ov-flag {
362
- position: absolute;
363
- box-sizing: border-box;
364
- }
365
- .gp-ov-flag[data-kind="margin"] {
366
- outline: var(--gp-ov-flag-w) solid var(--gp-ov-warn);
367
- background: color-mix(in srgb, var(--gp-ov-warn) 16%, transparent);
368
- }
369
- .gp-ov-flag[data-kind="bleed"],
370
- .gp-ov-flag[data-kind="off-page"] {
371
- outline: var(--gp-ov-flag-w) solid var(--gp-ov-error);
372
- background: color-mix(in srgb, var(--gp-ov-error) 18%, transparent);
373
- }
374
- /* placed on purpose (.gp-bleed, .gp-pin, …): present, not a problem */
375
- .gp-ov-flag[data-kind="intended"] {
376
- outline: var(--gp-ov-flag-w) dotted var(--gp-ov-intent);
377
- }
378
-
379
277
  /* designer mode ------------------------------------------------------- */
380
278
  .gp-guide-trim,
381
- .gp-guide-safe,
382
279
  .gp-crop-mark {
383
280
  position: absolute;
384
281
  pointer-events: none;
@@ -386,13 +283,11 @@
386
283
  }
387
284
 
388
285
  .gp-stage[data-designer="on"] .gp-guide-trim,
389
- .gp-stage[data-designer="on"] .gp-guide-safe,
390
286
  .gp-stage[data-designer="on"] .gp-crop-mark {
391
287
  display: block;
392
288
  }
393
289
 
394
290
  .gp-guide-trim { outline: 1px dashed var(--gp-guide); }
395
- .gp-guide-safe { outline: 1px dashed var(--gp-safe); }
396
291
  .gp-crop-mark { background: var(--gp-guide); }
397
292
 
398
293
  .gp-warning {
@@ -419,10 +314,8 @@
419
314
  transform: none;
420
315
  }
421
316
  .gp-layer,
422
- .gp-ov,
423
317
  .gp-warning,
424
318
  .gp-guide-trim,
425
- .gp-guide-safe,
426
319
  .gp-crop-mark {
427
320
  display: none !important;
428
321
  }
@@ -2202,96 +2095,325 @@
2202
2095
  return /right/.test(name) ? "end" : "start";
2203
2096
  }
2204
2097
 
2205
- // src/engine/compiler/tier2.ts
2206
- var DEFAULT_SLUG_PT = 18;
2207
- function pageGeometry(model, opts) {
2208
- const base = resolvePage(model);
2209
- const trim = { width: base.geometry.width, height: base.geometry.height };
2210
- const bleed = opts.bleedPt ?? base.geometry.bleed;
2211
- const wantsMarks = base.geometry.marks.length > 0 || opts.marks === true;
2212
- const slug = wantsMarks ? opts.slugPt ?? DEFAULT_SLUG_PT : 0;
2213
- return {
2214
- trim,
2215
- bleed,
2216
- slug,
2217
- marks: base.geometry.marks.length ? base.geometry.marks : wantsMarks ? ["crop"] : [],
2218
- media: {
2219
- width: trim.width + 2 * (bleed + slug),
2220
- height: trim.height + 2 * (bleed + slug)
2221
- }
2222
- };
2223
- }
2224
-
2225
2098
  // src/engine/viewer/overlay.ts
2226
- function documentBleedPt(model) {
2227
- return pageGeometry(model, { model }).bleed;
2228
- }
2229
- function pageBoxes(g, margin, bleed, scale = 1) {
2230
- const w = g.width * scale;
2231
- const h = g.height * scale;
2232
- const b = Math.max(0, bleed) * scale;
2099
+ function pageBoxes(area, side, scale = 1) {
2100
+ const w = area.width * scale;
2101
+ const h = area.height * scale;
2102
+ const b = Math.max(0, area.bleed) * scale;
2103
+ const s = Math.max(0, area.safe) * scale;
2104
+ const a = Math.max(0, area.artSafe) * scale;
2105
+ const [left, right] = side === "verso" ? [a, s] : [s, a];
2106
+ const trim = { x: side === "verso" ? b : 0, y: b, w: Math.max(0, w - b), h: Math.max(0, h - 2 * b) };
2233
2107
  return {
2234
- host: { x: 0, y: 0, w, h },
2235
- trim: { x: 0, y: 0, w, h },
2236
- content: {
2237
- x: margin.left * scale,
2238
- y: margin.top * scale,
2239
- w: w - (margin.left + margin.right) * scale,
2240
- h: h - (margin.top + margin.bottom) * scale
2108
+ sheet: { x: 0, y: 0, w, h },
2109
+ trim,
2110
+ safe: { x: trim.x + s, y: trim.y + s, w: Math.max(0, trim.w - 2 * s), h: Math.max(0, trim.h - 2 * s) },
2111
+ safeArt: {
2112
+ x: trim.x + left,
2113
+ y: trim.y + a,
2114
+ w: Math.max(0, trim.w - left - right),
2115
+ h: Math.max(0, trim.h - 2 * a)
2241
2116
  },
2242
- bleedBox: { x: -b, y: -b, w: w + 2 * b, h: h + 2 * b },
2243
- bleed: b
2244
- };
2245
- }
2246
- function insetPageBoxes(g, margin, bleed, side, scale = 1) {
2247
- const sheet = pageBoxes(g, margin, 0, scale);
2248
- const b = Math.max(0, bleed) * scale;
2249
- return {
2250
- host: sheet.host,
2251
- trim: { x: side === "verso" ? b : 0, y: b, w: sheet.host.w - b, h: sheet.host.h - 2 * b },
2252
- content: sheet.content,
2253
- bleedBox: sheet.host,
2254
2117
  bleed: b
2255
2118
  };
2256
2119
  }
2257
- var SEVERITY = { inside: 0, margin: 1, bleed: 2, "off-page": 3 };
2258
- var within = (r, outer, tol) => r.x >= outer.x - tol && r.y >= outer.y - tol && r.x + r.w <= outer.x + outer.w + tol && r.y + r.h <= outer.y + outer.h + tol;
2259
- function classifyRect(r, boxes, tol = 0) {
2260
- if (within(r, boxes.content, tol))
2261
- return "inside";
2262
- if (within(r, boxes.trim, tol))
2263
- return "margin";
2264
- if (boxes.bleed > 0 && within(r, boxes.bleedBox, tol))
2265
- return "bleed";
2266
- return "off-page";
2267
- }
2268
- function worstKind(kinds) {
2269
- return kinds.reduce((worst, k) => SEVERITY[k] > SEVERITY[worst] ? k : worst, "inside");
2120
+ var SEVERITY = { inside: 0, unsafe: 1, bleed: 2, "off-page": 3 };
2121
+ function classifyRect(r, boxes, rule, tol = 0) {
2122
+ const { sheet, trim } = boxes;
2123
+ const safe = rule === "art" ? boxes.safeArt : boxes.safe;
2124
+ const sides = [
2125
+ [-r.x, -safe.x, -trim.x, -sheet.x],
2126
+ [r.x + r.w, safe.x + safe.w, trim.x + trim.w, sheet.x + sheet.w],
2127
+ [-r.y, -safe.y, -trim.y, -sheet.y],
2128
+ [r.y + r.h, safe.y + safe.h, trim.y + trim.h, sheet.y + sheet.h]
2129
+ ];
2130
+ let worst = { kind: "inside", gap: 0, need: 0 };
2131
+ let beyond = 0;
2132
+ for (const [ink, limit, edge, sheetEdge] of sides) {
2133
+ const past = ink - limit;
2134
+ if (past <= tol || rule === "art" && ink >= sheetEdge - tol)
2135
+ continue;
2136
+ const kind = ink > sheetEdge + tol ? "off-page" : ink > edge + tol ? "bleed" : "unsafe";
2137
+ if (SEVERITY[kind] > SEVERITY[worst.kind] || kind === worst.kind && past > beyond) {
2138
+ worst = { kind, gap: edge - ink, need: edge - limit };
2139
+ beyond = past;
2140
+ }
2141
+ }
2142
+ return worst;
2270
2143
  }
2271
2144
  var INTENT_SELECTOR = ".gp-bleed, .gp-pin, .gp-flush, .gp-behind, .gp-page-full-bleed";
2145
+ var OVERLAY_LAYERS = ["bleed", "safe", "art", "margins"];
2146
+ function layersFrom(state) {
2147
+ const named = state && typeof state === "object" ? state : null;
2148
+ const on = (layer) => named ? named[layer] === true : !!state;
2149
+ return { bleed: on("bleed"), safe: on("safe"), art: on("art"), margins: on("margins") };
2150
+ }
2272
2151
  var OVERLAY_STRENGTHS = ["subtle", "normal", "strong"];
2273
2152
  var HEX_COLOR = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i;
2274
2153
  function sanitizeOverlayStyle(style) {
2275
2154
  const s = style && typeof style === "object" ? style : {};
2155
+ const given = s.colors && typeof s.colors === "object" ? s.colors : {};
2156
+ const hex = (v) => typeof v === "string" && HEX_COLOR.test(v.trim()) ? v.trim() : undefined;
2276
2157
  const out = {};
2277
- if (typeof s.color === "string" && HEX_COLOR.test(s.color.trim()))
2278
- out.color = s.color.trim();
2158
+ for (const layer of OVERLAY_LAYERS) {
2159
+ const color = hex(given[layer]) ?? (layer === "safe" ? hex(s.color) : undefined);
2160
+ if (color)
2161
+ (out.colors ??= {})[layer] = color;
2162
+ }
2279
2163
  if (OVERLAY_STRENGTHS.includes(s.strength))
2280
2164
  out.strength = s.strength;
2281
2165
  return out;
2282
2166
  }
2283
- var OVERLAY_OFF = { on: false, issues: 0, intended: 0, bleedPt: 0 };
2284
- function pageFrameMargin(margin, versoShift, side) {
2285
- if (side === "recto" || !versoShift)
2286
- return margin;
2287
- return { ...margin, left: margin.left - versoShift, right: margin.right + versoShift };
2167
+ var OVERLAY_OFF = {
2168
+ on: false,
2169
+ layers: { bleed: false, safe: false, art: false, margins: false },
2170
+ issues: 0,
2171
+ intended: 0,
2172
+ bleedPt: 0,
2173
+ safePt: 0,
2174
+ artSafePt: 0,
2175
+ counts: { text: 0, art: 0, bleed: 0, offPage: 0, intended: 0 }
2176
+ };
2177
+ function printAreaFromMeta() {
2178
+ const raw = document.querySelector('meta[name="gp-print-area"]')?.getAttribute("content");
2179
+ if (!raw)
2180
+ return null;
2181
+ try {
2182
+ const v = JSON.parse(raw);
2183
+ const n = (x) => typeof x === "number" && Number.isFinite(x) && x > 0 ? x : 0;
2184
+ if (!n(v.width) || !n(v.height))
2185
+ return null;
2186
+ const safe = n(v.safe);
2187
+ const spec = v.spec && typeof v.spec.label === "string" && typeof v.spec.url === "string" && /^https?:\/\//i.test(v.spec.url) ? v.spec : undefined;
2188
+ return {
2189
+ width: n(v.width),
2190
+ height: n(v.height),
2191
+ bleed: n(v.bleed),
2192
+ safe,
2193
+ artSafe: typeof v.artSafe === "number" ? n(v.artSafe) : safe,
2194
+ preset: typeof v.preset === "string" ? v.preset : undefined,
2195
+ spec
2196
+ };
2197
+ } catch {
2198
+ return null;
2199
+ }
2288
2200
  }
2289
- function configInsetBleedPt() {
2290
- const v = parseFloat(document.querySelector('meta[name="gp-inset-bleed"]')?.getAttribute("content") ?? "");
2291
- return Number.isFinite(v) && v > 0 ? v : 0;
2201
+ function sheetArea(sheet) {
2202
+ const px = (name) => parseFloat(sheet.style.getPropertyValue(name)) || 0;
2203
+ return { width: px("--gp-page-w") / PX_PER_PT, height: px("--gp-page-h") / PX_PER_PT, bleed: 0, safe: 0, artSafe: 0 };
2204
+ }
2205
+ var OVERLAY_CSS = `
2206
+ .root {
2207
+ --gp-ov-color: #2f80ed; /* safe layer: the text line + the tint outside it */
2208
+ --gp-ov-width: 2px; /* line width as seen on screen, at any zoom */
2209
+ --gp-ov-style: dashed; /* text safe line style */
2210
+ --gp-ov-art-style: dotted; /* art safe line style */
2211
+ --gp-ov-tint: 0.1; /* opacity of the tint between the text line and the trim */
2212
+ --gp-ov-bleed-color: #e5484d; /* bleed layer: the band, and (darker) the trim line */
2213
+ --gp-ov-bleed-alpha: 0.45; /* opacity of the bleed band */
2214
+ --gp-ov-trim-color: color-mix(in srgb, var(--gp-ov-bleed-color) 60%, #000); /* darker than the band so it reads as an edge */
2215
+ --gp-ov-art-color: #00b4d8; /* art layer: the art line (dotted, a second cue) */
2216
+ --gp-ov-margins-color: #1a9c6b; /* margins layer: the page's own content box */
2217
+ --gp-ov-flag-width: 2px; /* overflow flag outline */
2218
+ --gp-ov-warn: #f59e0b; /* leaves the safe area */
2219
+ --gp-ov-error: #e5484d; /* crosses the trim / leaves the sheet */
2220
+ --gp-ov-intent: #8b5cf6; /* placed on purpose */
2221
+ /* The overlay is scaled with the stage, so a 2px line would shrink with the
2222
+ zoom; dividing by it keeps lines the same width on screen at every zoom. */
2223
+ --gp-ov-w: calc(var(--gp-ov-width) / var(--gp-ov-z, 1));
2224
+ --gp-ov-flag-w: calc(var(--gp-ov-flag-width) / var(--gp-ov-z, 1));
2225
+ --gp-ov-margins-w: calc(1px / var(--gp-ov-z, 1)); /* thin, whatever the strength */
2226
+ position: absolute;
2227
+ inset: 0;
2228
+ pointer-events: none;
2229
+ }
2230
+ /* the stage's zoomed copy, holding the pages' boxes; the legend sits beside it, not in it */
2231
+ .gp-ov-canvas {
2232
+ position: absolute;
2233
+ left: 0;
2234
+ top: 0;
2235
+ transform-origin: 0 0;
2236
+ }
2237
+ .root[data-strength="subtle"] {
2238
+ --gp-ov-width: 1px;
2239
+ --gp-ov-tint: 0.05;
2240
+ --gp-ov-bleed-alpha: 0.3;
2241
+ }
2242
+ .root[data-strength="strong"] {
2243
+ --gp-ov-width: 3px;
2244
+ --gp-ov-tint: 0.2;
2245
+ --gp-ov-bleed-alpha: 0.65;
2246
+ --gp-ov-flag-width: 3px;
2247
+ }
2248
+ .gp-ov-page, .gp-ov-tint, .gp-ov-safe, .gp-ov-art, .gp-ov-bleed, .gp-ov-trim, .gp-ov-margins, .gp-ov-flag {
2249
+ position: absolute;
2250
+ box-sizing: border-box;
2251
+ }
2252
+ /* the band between the text line and the trim, as a REGION (clip-path, set in
2253
+ overlay.ts), so it reads as an area, not a hairline */
2254
+ .gp-ov-tint {
2255
+ background: color-mix(in srgb, var(--gp-ov-color) calc(var(--gp-ov-tint) * 100%), transparent);
2256
+ }
2257
+ .gp-ov-safe {
2258
+ border: var(--gp-ov-w) var(--gp-ov-style) var(--gp-ov-color);
2259
+ }
2260
+ .gp-ov-art {
2261
+ border: var(--gp-ov-w) var(--gp-ov-art-style) var(--gp-ov-art-color);
2262
+ }
2263
+ /* bleed: a translucent band between the trim and the sheet edge (its width is the border) */
2264
+ .gp-ov-bleed {
2265
+ border-style: solid;
2266
+ border-color: color-mix(in srgb, var(--gp-ov-bleed-color) calc(var(--gp-ov-bleed-alpha) * 100%), transparent);
2267
+ }
2268
+ /* trim: where the sheet will be cut, drawn only when there is bleed */
2269
+ .gp-ov-trim {
2270
+ outline: var(--gp-ov-w) solid var(--gp-ov-trim-color);
2271
+ outline-offset: calc(-1 * var(--gp-ov-w));
2272
+ }
2273
+ /* margins: the page's own content box, a thin solid line */
2274
+ .gp-ov-margins {
2275
+ border: var(--gp-ov-margins-w) solid var(--gp-ov-margins-color);
2276
+ }
2277
+ /* overflow flags: outline + faint tint, never a box change */
2278
+ .gp-ov-flag[data-kind="unsafe"] {
2279
+ outline: var(--gp-ov-flag-w) solid var(--gp-ov-warn);
2280
+ background: color-mix(in srgb, var(--gp-ov-warn) 16%, transparent);
2281
+ }
2282
+ .gp-ov-flag[data-kind="bleed"],
2283
+ .gp-ov-flag[data-kind="off-page"] {
2284
+ outline: var(--gp-ov-flag-w) solid var(--gp-ov-error);
2285
+ background: color-mix(in srgb, var(--gp-ov-error) 18%, transparent);
2286
+ }
2287
+ .gp-ov-flag[data-kind="intended"] {
2288
+ outline: var(--gp-ov-flag-w) dotted var(--gp-ov-intent);
2289
+ }
2290
+ /* the legend: small, fixed in a corner of the viewport, and the one part that takes the pointer (its link) */
2291
+ .gp-ov-legend {
2292
+ position: fixed;
2293
+ right: 12px;
2294
+ bottom: 12px;
2295
+ box-sizing: border-box;
2296
+ max-width: min(420px, calc(100vw - 24px));
2297
+ padding: 8px 10px;
2298
+ border-radius: 8px;
2299
+ background: rgba(20, 20, 24, 0.92);
2300
+ color: #fff;
2301
+ font: 11px/1.45 ui-sans-serif, system-ui, sans-serif;
2302
+ box-shadow: 0 1px 6px rgba(0, 0, 0, 0.4);
2303
+ pointer-events: auto;
2304
+ }
2305
+ .gp-ov-legend > div + div { margin-top: 3px; }
2306
+ .gp-ov-legend a { color: #9cc2ff; }
2307
+ .gp-ov-row { display: flex; align-items: flex-start; gap: 6px; }
2308
+ .gp-ov-swatch {
2309
+ flex: none;
2310
+ box-sizing: border-box;
2311
+ width: 10px;
2312
+ height: 10px;
2313
+ margin-top: 3px;
2314
+ border: 1px solid rgba(255, 255, 255, 0.7);
2315
+ border-radius: 2px;
2316
+ }
2317
+ .gp-ov-swatch[data-layer="bleed"] { background: var(--gp-ov-bleed-color); }
2318
+ .gp-ov-swatch[data-layer="safe"] { background: var(--gp-ov-color); }
2319
+ .gp-ov-swatch[data-layer="art"] { background: var(--gp-ov-art-color); }
2320
+ .gp-ov-swatch[data-layer="margins"] { background: var(--gp-ov-margins-color); }
2321
+ .gp-ov-note { opacity: 0.75; }
2322
+ @media print {
2323
+ .root { display: none; }
2324
+ }
2325
+ `;
2326
+ var HOST_STYLE = [
2327
+ "all: initial",
2328
+ "display: block",
2329
+ "position: absolute",
2330
+ "left: 0",
2331
+ "top: 0",
2332
+ "width: 0",
2333
+ "height: 0",
2334
+ "margin: 0",
2335
+ "padding: 0",
2336
+ "border: 0",
2337
+ "overflow: visible",
2338
+ "visibility: visible",
2339
+ "opacity: 1",
2340
+ "filter: none",
2341
+ "clip-path: none",
2342
+ "mask: none",
2343
+ "z-index: 2147483647",
2344
+ "pointer-events: none",
2345
+ "contain: style"
2346
+ ].map((d) => `${d} !important`).join("; ");
2347
+ var OVERLAY_TAG = "gp-overlay";
2348
+ var host = null;
2349
+ var root = null;
2350
+ var canvas = null;
2351
+ var observer = null;
2352
+ var currentStyle = {};
2353
+ var COLOR_TOKEN = {
2354
+ bleed: "--gp-ov-bleed-color",
2355
+ safe: "--gp-ov-color",
2356
+ art: "--gp-ov-art-color",
2357
+ margins: "--gp-ov-margins-color"
2358
+ };
2359
+ function applyStyle() {
2360
+ if (!root)
2361
+ return;
2362
+ if (currentStyle.strength)
2363
+ root.dataset.strength = currentStyle.strength;
2364
+ for (const layer of OVERLAY_LAYERS) {
2365
+ const color = currentStyle.colors?.[layer];
2366
+ if (color)
2367
+ root.style.setProperty(COLOR_TOKEN[layer], color);
2368
+ }
2369
+ }
2370
+ function setOverlayStyle(style) {
2371
+ currentStyle = { ...currentStyle, ...style, colors: { ...currentStyle.colors, ...style.colors } };
2372
+ applyStyle();
2373
+ }
2374
+ function stageFrame() {
2375
+ const stage = document.body;
2376
+ const r = stage.getBoundingClientRect();
2377
+ const zoom = stage.offsetWidth > 0 ? r.width / stage.offsetWidth : 1;
2378
+ return { left: r.left, top: r.top, zoom: zoom || 1 };
2379
+ }
2380
+ function syncToStage() {
2381
+ if (!root || !canvas)
2382
+ return;
2383
+ const { left, top, zoom } = stageFrame();
2384
+ canvas.style.transform = `translate(${left + window.scrollX}px, ${top + window.scrollY}px) scale(${zoom})`;
2385
+ root.style.setProperty("--gp-ov-z", String(zoom));
2386
+ }
2387
+ function ensureRoot() {
2388
+ if (!(host && root && host.isConnected)) {
2389
+ host = document.createElement(OVERLAY_TAG);
2390
+ host.setAttribute("style", HOST_STYLE);
2391
+ const shadow = host.attachShadow({ mode: "open" });
2392
+ const sheet = document.createElement("style");
2393
+ sheet.textContent = OVERLAY_CSS;
2394
+ root = document.createElement("div");
2395
+ root.className = "root";
2396
+ shadow.append(sheet, root);
2397
+ document.documentElement.appendChild(host);
2398
+ applyStyle();
2399
+ observer = new MutationObserver(syncToStage);
2400
+ observer.observe(document.documentElement, { attributes: true, attributeFilter: ["style"] });
2401
+ observer.observe(document.body, { attributes: true, attributeFilter: ["style"] });
2402
+ }
2403
+ root.textContent = "";
2404
+ canvas = div("gp-ov-canvas");
2405
+ canvas.setAttribute("aria-hidden", "true");
2406
+ root.appendChild(canvas);
2407
+ return canvas;
2408
+ }
2409
+ function clearOverlay() {
2410
+ observer?.disconnect();
2411
+ observer = null;
2412
+ host?.remove();
2413
+ host = null;
2414
+ root = null;
2415
+ canvas = null;
2292
2416
  }
2293
- var SKIP = new Set(["SCRIPT", "STYLE", "LINK", "TEMPLATE", "BR", "WBR", "SOURCE", "TRACK", "META"]);
2294
- var TOL_PX = 1;
2295
2417
  function div(cls, box) {
2296
2418
  const el = document.createElement("div");
2297
2419
  el.className = cls;
@@ -2305,111 +2427,225 @@
2305
2427
  el.style.width = `${r.w}px`;
2306
2428
  el.style.height = `${r.h}px`;
2307
2429
  }
2308
- function marginTint(b) {
2309
- const el = div("gp-ov-tint");
2430
+ function unsafeTint(b) {
2431
+ const el = div("gp-ov-tint", b.sheet);
2310
2432
  const t = b.trim;
2311
- const clamp = (v, lo, hi) => Math.min(hi, Math.max(lo, v));
2312
- const l = clamp(b.content.x, t.x, t.x + t.w);
2313
- const r = clamp(b.content.x + b.content.w, t.x, t.x + t.w);
2314
- const top = clamp(b.content.y, t.y, t.y + t.h);
2315
- const bot = clamp(b.content.y + b.content.h, t.y, t.y + t.h);
2316
- const x2 = t.x + t.w;
2317
- const y2 = t.y + t.h;
2318
- el.style.clipPath = `polygon(evenodd, ${t.x}px ${t.y}px, ${x2}px ${t.y}px, ${x2}px ${y2}px, ${t.x}px ${y2}px, ${t.x}px ${t.y}px, ` + `${l}px ${top}px, ${l}px ${bot}px, ${r}px ${bot}px, ${r}px ${top}px, ${l}px ${top}px)`;
2433
+ const s = b.safe;
2434
+ el.style.clipPath = `polygon(evenodd, ${t.x}px ${t.y}px, ${t.x + t.w}px ${t.y}px, ${t.x + t.w}px ${t.y + t.h}px, ${t.x}px ${t.y + t.h}px, ${t.x}px ${t.y}px, ` + `${s.x}px ${s.y}px, ${s.x}px ${s.y + s.h}px, ${s.x + s.w}px ${s.y + s.h}px, ${s.x + s.w}px ${s.y}px, ${s.x}px ${s.y}px)`;
2319
2435
  return el;
2320
2436
  }
2321
- function clearOverlay() {
2322
- for (const layer of Array.from(document.querySelectorAll(".gp-ov")))
2323
- layer.remove();
2437
+ function marginsBox(p, side) {
2438
+ const { width, height, margin } = p.strip.geometry;
2439
+ const shift = side === "verso" ? p.strip.versoShift : 0;
2440
+ const left = margin.left - shift;
2441
+ const right = margin.right + shift;
2442
+ return {
2443
+ x: left * PX_PER_PT,
2444
+ y: margin.top * PX_PER_PT,
2445
+ w: Math.max(0, width - left - right) * PX_PER_PT,
2446
+ h: Math.max(0, height - margin.top - margin.bottom) * PX_PER_PT
2447
+ };
2448
+ }
2449
+ var TOL_PX = 1;
2450
+ var SKIP = new Set(["SCRIPT", "STYLE", "LINK", "TEMPLATE", "BR", "WBR", "SOURCE", "TRACK", "META"]);
2451
+ var ART_TAGS = new Set(["img", "svg", "video", "canvas"]);
2452
+ var inches = (px) => +(Math.abs(px) / 96).toFixed(2);
2453
+ var ptIn = (pt2) => `${+(pt2 / 72).toFixed(3)}in`;
2454
+ function legend(layers, area, summary) {
2455
+ const el = div("gp-ov-legend");
2456
+ const add = (cls, ...content) => {
2457
+ const line = div(cls);
2458
+ line.append(...content);
2459
+ el.appendChild(line);
2460
+ };
2461
+ const row = (layer, name, text) => {
2462
+ const swatch = div("gp-ov-swatch");
2463
+ swatch.dataset.layer = layer;
2464
+ const bold = document.createElement("b");
2465
+ bold.textContent = name;
2466
+ const label = document.createElement("span");
2467
+ label.append(bold, text);
2468
+ add("gp-ov-row", swatch, label);
2469
+ };
2470
+ const { bleedPt, safePt, artSafePt, counts } = summary;
2471
+ if (layers.bleed) {
2472
+ row("bleed", "Bleed", bleedPt > 0 ? ` ${ptIn(bleedPt)} — top, bottom, outside; none at the binding` : " — none set (page.bleed in manifest.yaml)");
2473
+ }
2474
+ if (layers.safe) {
2475
+ row("safe", "Safe area: text", safePt > 0 ? ` — ${ptIn(safePt)}, every edge` : " — none set (page.safe in manifest.yaml)");
2476
+ }
2477
+ if (layers.art) {
2478
+ row("art", "Safe area: art", safePt <= 0 && artSafePt <= 0 ? " — none set (page.artSafe in manifest.yaml)" : artSafePt === safePt ? ` — ${ptIn(artSafePt)}, every edge` : ` — ${ptIn(artSafePt)} top, bottom, outside; ${ptIn(safePt)} at the binding`);
2479
+ }
2480
+ if (layers.margins)
2481
+ row("margins", "Page margins", " — your stylesheet's @page margins");
2482
+ if (area && (layers.bleed || layers.safe || layers.art)) {
2483
+ if (area.spec) {
2484
+ const link = document.createElement("a");
2485
+ link.href = area.spec.url;
2486
+ link.target = "_blank";
2487
+ link.rel = "noopener";
2488
+ link.textContent = area.spec.label;
2489
+ add("gp-ov-note", "from ", link);
2490
+ } else {
2491
+ add("gp-ov-note", `from manifest.yaml${area.preset ? ` (preset ${area.preset})` : ""}`);
2492
+ }
2493
+ }
2494
+ if (layers.safe || layers.art) {
2495
+ const found = [];
2496
+ if (counts.text)
2497
+ found.push(`${counts.text} text outside the safe area`);
2498
+ if (counts.art)
2499
+ found.push(`${counts.art} art outside${counts.text ? "" : " the safe area"}`);
2500
+ if (counts.bleed)
2501
+ found.push(`${counts.bleed} in the bleed band`);
2502
+ if (counts.offPage)
2503
+ found.push(`${counts.offPage} off the page`);
2504
+ if (!found.length) {
2505
+ found.push(`${layers.safe && layers.art ? "everything" : layers.safe ? "all text" : "all art"} inside the safe area`);
2506
+ }
2507
+ if (counts.intended)
2508
+ found.push(`${counts.intended} placed on purpose`);
2509
+ add("gp-ov-found", found.join(" · "));
2510
+ }
2511
+ const key = document.documentElement.dataset.overlayKey;
2512
+ if (key)
2513
+ add("gp-ov-note", `${key} hides`);
2514
+ return el;
2324
2515
  }
2325
- function paintOverlay(pages, strips, bleedPt, insetBleedPt = 0) {
2326
- const inSheet = bleedPt <= 0 && insetBleedPt > 0;
2327
- clearOverlay();
2328
- const layers = new Map;
2516
+ function paintOverlay(pages, strips, area, layers) {
2517
+ const layer = ensureRoot();
2518
+ syncToStage();
2519
+ const stage = stageFrame();
2329
2520
  const painted = new Map;
2330
2521
  for (const p of pages) {
2331
- const run = p.sheet.closest(".gp-run");
2332
- if (!run)
2333
- continue;
2334
- let layer = layers.get(run);
2335
- if (!layer) {
2336
- layer = div("gp-ov");
2337
- run.appendChild(layer);
2338
- layers.set(run, layer);
2339
- }
2340
- const boxes = inSheet ? insetPageBoxes(p.geometry, p.margin, insetBleedPt, p.sheet.dataset.side === "verso" ? "verso" : "recto", PX_PER_PT) : pageBoxes(p.geometry, p.margin, bleedPt, PX_PER_PT);
2341
- const host = div("gp-ov-page", boxes.host);
2342
- host.style.left = p.sheet.style.left;
2343
- host.style.top = p.sheet.style.top;
2344
- host.dataset.page = String(p.index + 1);
2345
- if (boxes.bleed > 0) {
2346
- const bb = boxes.bleedBox;
2522
+ const side = p.sheet.dataset.side === "verso" ? "verso" : "recto";
2523
+ const boxes = pageBoxes(area ?? sheetArea(p.sheet), side, PX_PER_PT);
2524
+ const rect = p.sheet.getBoundingClientRect();
2525
+ const host2 = div("gp-ov-page", {
2526
+ x: (rect.left - stage.left) / stage.zoom,
2527
+ y: (rect.top - stage.top) / stage.zoom,
2528
+ w: boxes.sheet.w,
2529
+ h: boxes.sheet.h
2530
+ });
2531
+ host2.dataset.page = String(p.index + 1);
2532
+ if (layers.bleed && boxes.bleed > 0) {
2533
+ const sh = boxes.sheet;
2347
2534
  const t = boxes.trim;
2348
- const band = div("gp-ov-bleed", bb);
2349
- band.style.borderWidth = `${t.y - bb.y}px ${bb.x + bb.w - (t.x + t.w)}px ${bb.y + bb.h - (t.y + t.h)}px ${t.x - bb.x}px`;
2350
- host.appendChild(band);
2351
- host.appendChild(div("gp-ov-trim", t));
2352
- }
2353
- host.appendChild(marginTint(boxes));
2354
- host.appendChild(div("gp-ov-margin", boxes.content));
2355
- layer.appendChild(host);
2356
- painted.set(p.index, { host, boxes, sheet: p.sheet });
2535
+ const band = div("gp-ov-bleed", sh);
2536
+ band.style.borderWidth = `${t.y - sh.y}px ${sh.x + sh.w - (t.x + t.w)}px ${sh.y + sh.h - (t.y + t.h)}px ${t.x - sh.x}px`;
2537
+ host2.appendChild(band);
2538
+ host2.appendChild(div("gp-ov-trim", t));
2539
+ }
2540
+ if (layers.safe && area && area.safe > 0) {
2541
+ host2.appendChild(unsafeTint(boxes));
2542
+ host2.appendChild(div("gp-ov-safe", boxes.safe));
2543
+ }
2544
+ if (layers.art && area && area.artSafe > 0)
2545
+ host2.appendChild(div("gp-ov-art", boxes.safeArt));
2546
+ if (layers.margins && !p.padded)
2547
+ host2.appendChild(div("gp-ov-margins", marginsBox(p, side)));
2548
+ layer.appendChild(host2);
2549
+ painted.set(p.index, { host: host2, boxes, sheet: p.sheet, rect });
2357
2550
  }
2358
2551
  const flags = [];
2359
- let issues = 0;
2360
- let intended = 0;
2361
- for (const strip of strips) {
2362
- const zoom = cssZoomOf(strip.el);
2363
- const colW = (parseFloat(strip.el.style.getPropertyValue("--gp-content-w")) || 0) * zoom;
2364
- const colH = (parseFloat(strip.el.style.getPropertyValue("--gp-content-h")) || 0) * zoom;
2365
- const visit = (el) => {
2366
- if (SKIP.has(el.tagName) || el.classList.contains("gp-wrap-spacer"))
2367
- return;
2368
- const kinds = [];
2369
- const found = [];
2370
- for (const r of Array.from(el.getClientRects())) {
2552
+ const counts = { text: 0, art: 0, bleed: 0, offPage: 0, intended: 0 };
2553
+ const seen = new Set;
2554
+ const range = document.createRange();
2555
+ const source = area?.spec?.label ?? "the safe area";
2556
+ const inspect = (root2, pageOf2, zoom) => {
2557
+ if (!layers.safe && !layers.art)
2558
+ return;
2559
+ const judge = (el, rule, rects) => {
2560
+ for (const r of rects) {
2371
2561
  if (r.width <= 0 && r.height <= 0)
2372
2562
  continue;
2373
- const ax = r.left + Math.min(r.width, colW || r.width) / 2;
2374
- const ay = r.top + Math.min(r.height, colH || r.height) / 2;
2375
- const target = painted.get(strip.offset + indexInStrip(ax, ay, strip));
2563
+ const target = pageOf2(r);
2376
2564
  if (!target)
2377
2565
  continue;
2378
2566
  target.rect ??= target.sheet.getBoundingClientRect();
2379
- const local = {
2567
+ const box = {
2380
2568
  x: (r.left - target.rect.left) / zoom,
2381
2569
  y: (r.top - target.rect.top) / zoom,
2382
2570
  w: r.width / zoom,
2383
2571
  h: r.height / zoom
2384
2572
  };
2385
- const kind = classifyRect(local, target.boxes, TOL_PX);
2386
- kinds.push(kind);
2387
- if (kind !== "inside")
2388
- found.push({ host: target.host, kind, box: local });
2573
+ const { kind, gap, need } = classifyRect(box, target.boxes, rule, TOL_PX);
2574
+ if (kind === "inside")
2575
+ continue;
2576
+ const key = `${target.host.dataset.page} ${box.x} ${box.y} ${box.w} ${box.h}`;
2577
+ if (seen.has(key))
2578
+ continue;
2579
+ seen.add(key);
2580
+ const placed = el.closest(INTENT_SELECTOR) !== null;
2581
+ const bucket = placed ? "intended" : kind === "unsafe" ? rule : kind === "bleed" ? "bleed" : "offPage";
2582
+ counts[bucket]++;
2583
+ const title = `${rule} ${inches(gap)}in ${gap < 0 ? "past" : "from"} the trim; ${source} needs ${inches(need)}in`;
2584
+ flags.push({
2585
+ host: target.host,
2586
+ kind: placed ? "intended" : kind,
2587
+ rule,
2588
+ box,
2589
+ title: placed ? `${title} (placed on purpose)` : title
2590
+ });
2591
+ }
2592
+ };
2593
+ const visit = (node) => {
2594
+ if (node.nodeType === Node.TEXT_NODE) {
2595
+ if (!layers.safe || !node.nodeValue?.trim())
2596
+ return;
2597
+ range.selectNodeContents(node);
2598
+ judge(node.parentElement, "text", range.getClientRects());
2599
+ return;
2389
2600
  }
2390
- const worst = worstKind(kinds);
2391
- if (worst !== "inside") {
2392
- const isIntended = el.closest(INTENT_SELECTOR) !== null;
2393
- if (isIntended)
2394
- intended++;
2395
- else
2396
- issues++;
2397
- for (const f of found)
2398
- flags.push(isIntended ? { ...f, kind: "intended" } : f);
2601
+ if (!(node instanceof Element) || SKIP.has(node.tagName) || node.classList.contains("gp-wrap-spacer"))
2399
2602
  return;
2603
+ if (layers.art) {
2604
+ const cs = getComputedStyle(node);
2605
+ if (ART_TAGS.has(node.localName) || cs.backgroundImage !== "none" || !/^rgba\(.*, 0\)$/.test(cs.backgroundColor) || parseFloat(cs.borderTopWidth) > 0 || parseFloat(cs.borderRightWidth) > 0 || parseFloat(cs.borderBottomWidth) > 0 || parseFloat(cs.borderLeftWidth) > 0) {
2606
+ judge(node, "art", node.getClientRects());
2607
+ }
2400
2608
  }
2401
- for (const child of Array.from(el.children))
2609
+ for (const child of node.childNodes)
2402
2610
  visit(child);
2403
2611
  };
2404
- for (const child of Array.from(strip.el.children))
2405
- visit(child);
2612
+ visit(root2);
2613
+ };
2614
+ for (const strip of strips) {
2615
+ const zoom = cssZoomOf(strip.el);
2616
+ const colW = (parseFloat(strip.el.style.getPropertyValue("--gp-content-w")) || 0) * zoom;
2617
+ const colH = (parseFloat(strip.el.style.getPropertyValue("--gp-content-h")) || 0) * zoom;
2618
+ const pageOf2 = (r) => {
2619
+ const ax = r.left + Math.min(r.width, colW || r.width) / 2;
2620
+ const ay = r.top + Math.min(r.height, colH || r.height) / 2;
2621
+ return painted.get(strip.offset + indexInStrip(ax, ay, strip));
2622
+ };
2623
+ for (const child of strip.el.childNodes)
2624
+ inspect(child, pageOf2, zoom);
2625
+ }
2626
+ for (const target of painted.values()) {
2627
+ for (const furniture of target.sheet.querySelectorAll(".gp-marginbox"))
2628
+ inspect(furniture, () => target, stage.zoom);
2406
2629
  }
2407
2630
  for (const f of flags) {
2408
- const el = div(`gp-ov-flag`, f.box);
2631
+ const el = div("gp-ov-flag", f.box);
2409
2632
  el.dataset.kind = f.kind;
2633
+ el.dataset.rule = f.rule;
2634
+ el.title = f.title;
2410
2635
  f.host.appendChild(el);
2411
2636
  }
2412
- return inSheet ? { on: true, issues, intended, bleedPt: insetBleedPt, bleedInSheet: true } : { on: true, issues, intended, bleedPt };
2637
+ const summary = {
2638
+ on: true,
2639
+ layers,
2640
+ issues: counts.text + counts.art + counts.bleed + counts.offPage,
2641
+ intended: counts.intended,
2642
+ bleedPt: area?.bleed ?? 0,
2643
+ safePt: area?.safe ?? 0,
2644
+ artSafePt: area?.artSafe ?? 0,
2645
+ counts
2646
+ };
2647
+ root?.appendChild(legend(layers, area, summary));
2648
+ return summary;
2413
2649
  }
2414
2650
 
2415
2651
  // src/engine/viewer/decorate.ts
@@ -2441,10 +2677,11 @@
2441
2677
  let paddedPages = new Set;
2442
2678
  const warnings = [];
2443
2679
  let overlayOn = false;
2680
+ let overlayLayers = OVERLAY_OFF.layers;
2444
2681
  let overlayPages = [];
2445
2682
  let overlaySummary = OVERLAY_OFF;
2446
2683
  const paintOverlayNow = () => {
2447
- overlaySummary = paintOverlay(overlayPages, layout.strips, documentBleedPt(model), configInsetBleedPt());
2684
+ overlaySummary = paintOverlay(overlayPages, layout.strips, printAreaFromMeta(), overlayLayers);
2448
2685
  window.dispatchEvent(new CustomEvent("gp:overlay", { detail: overlaySummary }));
2449
2686
  };
2450
2687
  const api = {
@@ -2457,19 +2694,31 @@
2457
2694
  setDesigner(on) {
2458
2695
  document.body.dataset.designer = on ? "on" : "off";
2459
2696
  },
2460
- setOverlay(on, style) {
2697
+ setOverlay(state, style) {
2461
2698
  if (style) {
2462
- const { color, strength } = sanitizeOverlayStyle(style);
2699
+ const clean = sanitizeOverlayStyle(style);
2700
+ setOverlayStyle(clean);
2701
+ const { strength, colors } = clean;
2463
2702
  if (strength)
2464
2703
  document.body.dataset.overlayStrength = strength;
2465
- if (color)
2466
- document.body.style.setProperty("--gp-ov-color", color);
2704
+ if (colors?.safe)
2705
+ document.body.dataset.overlayColor = colors.safe;
2706
+ if (colors?.art)
2707
+ document.body.dataset.overlayArtColor = colors.art;
2708
+ if (colors?.bleed)
2709
+ document.body.dataset.overlayBleedColor = colors.bleed;
2710
+ if (colors?.margins)
2711
+ document.body.dataset.overlayMarginsColor = colors.margins;
2467
2712
  }
2468
2713
  const wasOn = overlayOn;
2469
- overlayOn = on;
2470
- document.body.dataset.overlay = on ? "on" : "off";
2471
- if (on) {
2472
- if (!(wasOn && style))
2714
+ const next = layersFrom(state);
2715
+ const sameLayers = OVERLAY_LAYERS.every((layer) => next[layer] === overlayLayers[layer]);
2716
+ overlayLayers = next;
2717
+ overlayOn = OVERLAY_LAYERS.some((layer) => next[layer]);
2718
+ document.body.dataset.overlay = overlayOn ? "on" : "off";
2719
+ if (overlayOn) {
2720
+ document.body.dataset.overlayLayers = OVERLAY_LAYERS.filter((layer) => next[layer]).join(",");
2721
+ if (!(wasOn && sameLayers && style))
2473
2722
  paintOverlayNow();
2474
2723
  } else {
2475
2724
  clearOverlay();
@@ -2613,8 +2862,8 @@
2613
2862
  }
2614
2863
  if (!marked.length)
2615
2864
  return;
2616
- const canvas = document.createElement("canvas");
2617
- const cx = canvas.getContext("2d");
2865
+ const canvas2 = document.createElement("canvas");
2866
+ const cx = canvas2.getContext("2d");
2618
2867
  for (const m of marked)
2619
2868
  m.el.setAttribute(m.attr, m.raw.replace(LEADER_RE, ""));
2620
2869
  document.body.offsetHeight;
@@ -2678,13 +2927,7 @@
2678
2927
  drawMarginBoxes(sheet, ctx, layout.totalPages - paddedPages.size);
2679
2928
  drawGuides(sheet, ctx);
2680
2929
  drawCropMarks(sheet, ctx);
2681
- overlayPages.push({
2682
- index: bookIndex,
2683
- sheet,
2684
- strip,
2685
- geometry: ctx.geometry,
2686
- margin: ctx.padded || blankPages.has(bookIndex) ? ctx.geometry.margin : pageFrameMargin(flushMargins(g.margin, strip.flushEdges ?? []), strip.versoShift, side)
2687
- });
2930
+ overlayPages.push({ index: bookIndex, sheet, strip, padded: ctx.padded });
2688
2931
  }
2689
2932
  const rows = Math.max(1, Math.ceil((strip.pages + shift) / perRow));
2690
2933
  run.style.height = `${rowStride * (rows - 1) + PX_PER_PT * g.height}px`;
@@ -2773,15 +3016,6 @@
2773
3016
  });
2774
3017
  sheet.appendChild(trim);
2775
3018
  }
2776
- const safe = document.createElement("div");
2777
- safe.className = "gp-guide-safe";
2778
- Object.assign(safe.style, {
2779
- left: px(g.margin.left),
2780
- top: px(g.margin.top),
2781
- width: px(g.width - g.margin.left - g.margin.right),
2782
- height: px(g.height - g.margin.top - g.margin.bottom)
2783
- });
2784
- sheet.appendChild(safe);
2785
3019
  }
2786
3020
  function checkOverflow(strip, out) {
2787
3021
  const h = strip.el.clientHeight;