@scrawl-board/board 0.1.0-beta.1 → 0.1.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/browser.js CHANGED
@@ -1,4 +1,4 @@
1
- //#region src/core-internal/assets.ts
1
+ //#region src/core/shapes/assets.ts
2
2
  var ASSET_REF_PATTERN = /^asset:[a-z0-9](?:[a-z0-9.-]{0,61}[a-z0-9])?:[A-Za-z0-9._~-]+$/;
3
3
  function isAssetRef(value) {
4
4
  return typeof value === "string" && ASSET_REF_PATTERN.test(value) && new TextEncoder().encode(value).length <= 512;
@@ -30,7 +30,7 @@ function clampAssetCacheBytes(value) {
30
30
  return Math.min(ASSET_CACHE_BYTES_MAX, Math.max(ASSET_CACHE_BYTES_MIN, Math.floor(value)));
31
31
  }
32
32
  //#endregion
33
- //#region src/core-internal/identifiers.ts
33
+ //#region src/core/document/identifiers.ts
34
34
  function documentId(value) {
35
35
  if (!value) throw new TypeError("DocumentId cannot be empty");
36
36
  return value;
@@ -231,7 +231,7 @@ function validation(message, path) {
231
231
  return new DocumentRecoveryError("DOCUMENT_VALIDATION_FAILED", message, void 0, path);
232
232
  }
233
233
  //#endregion
234
- //#region src/core-internal/affine2d.ts
234
+ //#region src/core/shapes/affine2d.ts
235
235
  var IDENTITY = [
236
236
  1,
237
237
  0,
@@ -315,7 +315,7 @@ function avgScale(m) {
315
315
  return (Math.hypot(m[0], m[1]) + Math.hypot(m[2], m[3])) / 2;
316
316
  }
317
317
  //#endregion
318
- //#region src/core-internal/boardSearch.ts
318
+ //#region src/core/document/boardSearch.ts
319
319
  function hay(s) {
320
320
  return s.toLowerCase();
321
321
  }
@@ -380,7 +380,7 @@ function searchBoard(query, board) {
380
380
  return hits;
381
381
  }
382
382
  //#endregion
383
- //#region src/core-internal/types.ts
383
+ //#region src/core/shapes/types.ts
384
384
  /** Erasure level at which a point counts as fully erased. */
385
385
  var ERASE_THRESHOLD = .95;
386
386
  function cloneStroke(stroke) {
@@ -452,7 +452,7 @@ function cloneImage(img) {
452
452
  return { ...img };
453
453
  }
454
454
  //#endregion
455
- //#region src/core-internal/clusterStore.ts
455
+ //#region src/core/document/clusterStore.ts
456
456
  /** A stroke joins a cluster last written to within this window at full reach. */
457
457
  var RECENT_MS = 8e3;
458
458
  /** Older clusters are still joinable, at half the gap threshold. */
@@ -623,7 +623,7 @@ function clamp$2(v, lo, hi) {
623
623
  return Math.min(hi, Math.max(lo, v));
624
624
  }
625
625
  //#endregion
626
- //#region src/core-internal/extensionTypes.ts
626
+ //#region src/core/shapes/extensionTypes.ts
627
627
  function cloneCustomObject(object) {
628
628
  return {
629
629
  ...object,
@@ -633,7 +633,7 @@ function cloneCustomObject(object) {
633
633
  };
634
634
  }
635
635
  //#endregion
636
- //#region src/core-internal/kitchenTimer.ts
636
+ //#region src/core/shapes/kitchenTimer.ts
637
637
  var TIMER_DEFAULT_DURATION_MS = 300 * 1e3;
638
638
  function cloneTimer(timer) {
639
639
  return { ...timer };
@@ -683,7 +683,7 @@ function formatTimer(ms) {
683
683
  return `${Math.floor(total / 60)}:${(total % 60).toString().padStart(2, "0")}`;
684
684
  }
685
685
  //#endregion
686
- //#region src/core-internal/itemLock.ts
686
+ //#region src/core/shapes/itemLock.ts
687
687
  /**
688
688
  * Apply or clear a lock in place. Unlock always drops the holder so a stale
689
689
  * name cannot linger on an unlocked item.
@@ -720,7 +720,7 @@ function serializeLock(item) {
720
720
  };
721
721
  }
722
722
  //#endregion
723
- //#region src/core-internal/document.ts
723
+ //#region src/core/document/document.ts
724
724
  /**
725
725
  * The one place a live Stroke becomes its wire representation — in
726
726
  * particular, points collapse from {x,y,pressure,erase?} objects into
@@ -1279,7 +1279,7 @@ function computeBBox(stroke) {
1279
1279
  return world;
1280
1280
  }
1281
1281
  //#endregion
1282
- //#region src/core-internal/commands.ts
1282
+ //#region src/core/commands/commands.ts
1283
1283
  /** One drawing action — a marker stroke, or a shape's strokes as one unit. */
1284
1284
  var AddStrokesCommand = class {
1285
1285
  constructor(strokes) {
@@ -1621,7 +1621,7 @@ var LockItemsCommand = class {
1621
1621
  }
1622
1622
  };
1623
1623
  //#endregion
1624
- //#region src/core-internal/history.ts
1624
+ //#region src/core/history/history.ts
1625
1625
  var LIMIT = 200;
1626
1626
  var History = class {
1627
1627
  constructor(doc) {
@@ -1682,7 +1682,7 @@ var History = class {
1682
1682
  }
1683
1683
  };
1684
1684
  //#endregion
1685
- //#region src/core-internal/ops.ts
1685
+ //#region src/core/events/ops.ts
1686
1686
  var ADDED = [
1687
1687
  ["added", "strokes"],
1688
1688
  ["notesAdded", "notes"],
@@ -1736,7 +1736,7 @@ function changeToOps(change, restoring = false) {
1736
1736
  return ops;
1737
1737
  }
1738
1738
  //#endregion
1739
- //#region src/core-internal/ribbonEdges.ts
1739
+ //#region src/core/shapes/ribbonEdges.ts
1740
1740
  var MIN_WIDTH_FACTOR = .35;
1741
1741
  var END_TAPER = .55;
1742
1742
  function ribbonEdges(points, baseWidth) {
@@ -1768,7 +1768,7 @@ function ribbonEdges(points, baseWidth) {
1768
1768
  return out;
1769
1769
  }
1770
1770
  //#endregion
1771
- //#region src/core-internal/spatialIndex.ts
1771
+ //#region src/core/document/spatialIndex.ts
1772
1772
  var CELL = 8;
1773
1773
  var SpatialIndex = class {
1774
1774
  constructor(doc) {
@@ -1841,7 +1841,7 @@ function cellsOf(box) {
1841
1841
  return keys;
1842
1842
  }
1843
1843
  //#endregion
1844
- //#region src/core-internal/stamps.ts
1844
+ //#region src/core/shapes/stamps.ts
1845
1845
  var STAMPS = [
1846
1846
  {
1847
1847
  kind: "star",
@@ -1889,7 +1889,7 @@ function isStampKind(value) {
1889
1889
  return typeof value === "string" && STAMPS.some((s) => s.kind === value);
1890
1890
  }
1891
1891
  //#endregion
1892
- //#region src/core-internal/svg.ts
1892
+ //#region src/persistence/serialization/svg.ts
1893
1893
  /** Matches the key light used by the board shader and note shadows. */
1894
1894
  var LIGHT = {
1895
1895
  x: -.25,
@@ -2256,7 +2256,7 @@ function fmt(n) {
2256
2256
  return String(Math.round(n * 100) / 100);
2257
2257
  }
2258
2258
  //#endregion
2259
- //#region src/core-internal/sceneGeometry.ts
2259
+ //#region src/core/shapes/sceneGeometry.ts
2260
2260
  var EMPTY_BOUNDS = {
2261
2261
  x: 0,
2262
2262
  y: 0,
@@ -2320,7 +2320,7 @@ function textCorners(x, y, text, fontSize, align, m) {
2320
2320
  return rectCorners(x + (align === "center" ? -width / 2 : align === "end" ? -width : 0), y, width, height, m);
2321
2321
  }
2322
2322
  //#endregion
2323
- //#region src/core-internal/customObjects.ts
2323
+ //#region src/core/shapes/customObjects.ts
2324
2324
  var FALLBACK_LABEL_MAX_LENGTH = 80;
2325
2325
  function toReadonly(object) {
2326
2326
  return object;
@@ -2449,7 +2449,7 @@ function extensionIdFromType(type) {
2449
2449
  return slash === -1 ? type : type.slice(0, slash);
2450
2450
  }
2451
2451
  //#endregion
2452
- //#region src/core-internal/extensionRegistry.ts
2452
+ //#region src/core/shapes/extensionRegistry.ts
2453
2453
  function buildExtensionRegistry(extensions) {
2454
2454
  const list = extensions ?? [];
2455
2455
  const diagnostics = [];
@@ -2546,7 +2546,7 @@ function isWellFormedNamespacedId(id) {
2546
2546
  return isWellFormedId(id) && id.includes("/") && !id.startsWith("/") && !id.endsWith("/");
2547
2547
  }
2548
2548
  //#endregion
2549
- //#region src/core-internal/assetValidation.ts
2549
+ //#region src/persistence/adapters/assetValidation.ts
2550
2550
  function sniffImageMediaType(bytes) {
2551
2551
  if (bytes.length >= 8 && bytes[0] === 137 && bytes[1] === 80 && bytes[2] === 78 && bytes[3] === 71 && bytes[4] === 13 && bytes[5] === 10 && bytes[6] === 26 && bytes[7] === 10) return "image/png";
2552
2552
  if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) return "image/jpeg";
@@ -2633,7 +2633,7 @@ function validateDecodedDimensions(width, height) {
2633
2633
  };
2634
2634
  }
2635
2635
  //#endregion
2636
- //#region src/assetController.ts
2636
+ //#region src/persistence/adapters/assetController.ts
2637
2637
  var FAILURE_COALESCE_MS = 4e3;
2638
2638
  var AssetController = class {
2639
2639
  constructor(resolver, options) {
@@ -2812,7 +2812,7 @@ function toAssetResolutionError(cause, ref) {
2812
2812
  return new AssetResolutionError("unknown", true, cause instanceof Error ? cause.message : String(cause), ref);
2813
2813
  }
2814
2814
  //#endregion
2815
- //#region src/core-internal/assetExport.ts
2815
+ //#region src/persistence/serialization/assetRefCollector.ts
2816
2816
  /** Every unique Asset reference in the document, and which object(s) use it. */
2817
2817
  function collectDocumentAssetRefs(doc, registry) {
2818
2818
  const refs = /* @__PURE__ */ new Map();
@@ -2839,7 +2839,7 @@ function collectSceneAssetRefs(node, objectId, add) {
2839
2839
  if (node.kind === "group") for (const child of node.children) collectSceneAssetRefs(child, objectId, add);
2840
2840
  }
2841
2841
  //#endregion
2842
- //#region src/assetExport.ts
2842
+ //#region src/persistence/serialization/assetExport.ts
2843
2843
  /** Strict mode's rejection — never contains credentials, a fetchable location, or a raw resolver message. */
2844
2844
  var SvgAssetExportError = class extends Error {
2845
2845
  constructor(missingAssets) {
@@ -2895,7 +2895,7 @@ function toDataUri(bytes, mediaType) {
2895
2895
  return `data:${mediaType};base64,${btoa(binary)}`;
2896
2896
  }
2897
2897
  //#endregion
2898
- //#region src/assetIngestion.ts
2898
+ //#region src/persistence/adapters/assetIngestion.ts
2899
2899
  async function decodeDimensionsWithImageBitmap(bytes, mediaType) {
2900
2900
  const blob = new Blob([bytes], { type: mediaType });
2901
2901
  let bitmap;
@@ -35541,7 +35541,7 @@ var WebGLRenderer = class {
35541
35541
  }
35542
35542
  };
35543
35543
  //#endregion
35544
- //#region ../../src/scrawl/data/auditEvents.ts
35544
+ //#region src/core/events/auditEvents.ts
35545
35545
  var BY_LABEL = {
35546
35546
  draw: {
35547
35547
  action: "create",
@@ -35661,7 +35661,7 @@ function lowerFirst(s) {
35661
35661
  return s.charAt(0).toLowerCase() + s.slice(1);
35662
35662
  }
35663
35663
  //#endregion
35664
- //#region ../../src/scrawl/assets/AssetTextureCache.ts
35664
+ //#region src/renderer/shapes/AssetTextureCache.ts
35665
35665
  var AssetTextureCache = class {
35666
35666
  constructor(assetController) {
35667
35667
  this.assetController = assetController;
@@ -35752,7 +35752,7 @@ function toDecodeError(cause, ref) {
35752
35752
  return new AssetResolutionError("decode-failed", false, cause instanceof Error ? cause.message : String(cause), ref);
35753
35753
  }
35754
35754
  //#endregion
35755
- //#region ../../src/scrawl/engine/raycast.ts
35755
+ //#region src/renderer/canvas/raycast.ts
35756
35756
  var origin$1 = new Vector3();
35757
35757
  var target = new Vector3();
35758
35758
  function pointerToBoard(clientX, clientY, canvas, camera) {
@@ -35783,7 +35783,7 @@ function pointerToPlane(clientX, clientY, canvas, camera, z) {
35783
35783
  };
35784
35784
  }
35785
35785
  //#endregion
35786
- //#region ../../src/scrawl/engine/fog.ts
35786
+ //#region src/renderer/canvas/fog.ts
35787
35787
  function createFogUniforms(color) {
35788
35788
  return {
35789
35789
  uFogColor: { value: new Color(color) },
@@ -35804,18 +35804,24 @@ var FOG_GLSL = `
35804
35804
  }
35805
35805
  `;
35806
35806
  //#endregion
35807
- //#region ../../src/scrawl/engine/boardSurface.ts
35807
+ //#region src/renderer/canvas/boardSurface.ts
35808
35808
  var GRID_MODE_CODE = {
35809
35809
  none: 0,
35810
35810
  line: 1,
35811
35811
  dot: 2
35812
35812
  };
35813
+ var TEXTURE_CODE = {
35814
+ flat: 0,
35815
+ textured: 1
35816
+ };
35813
35817
  /** Used when a caller doesn't supply one (e.g. ScrawlEngine constructed without a Host theme). */
35814
35818
  var DEFAULT_BOARD_SURFACE_THEME = {
35815
35819
  color: "#f5f5f3",
35816
35820
  gridMode: "none",
35817
35821
  gridColor: "#deded8",
35818
- gridSpacing: 5
35822
+ gridSpacing: 5,
35823
+ gridLineWidth: 1,
35824
+ surfaceTexture: "flat"
35819
35825
  };
35820
35826
  /** Grid alpha fades out below this on-screen spacing (px) — otherwise it aliases into a solid wash when zoomed out. */
35821
35827
  var GRID_FADE_MIN_PX = 3;
@@ -35836,8 +35842,10 @@ var FRAGMENT = `
35836
35842
  uniform float uGridMode;
35837
35843
  uniform vec3 uGridColor;
35838
35844
  uniform float uGridSpacing;
35839
- uniform float uGridLineWorld;
35845
+ uniform float uWorldPerPixel;
35846
+ uniform float uGridLineWidthPx;
35840
35847
  uniform float uGridFade;
35848
+ uniform float uSurfaceTexture;
35841
35849
  varying vec3 vWorldPos;
35842
35850
 
35843
35851
  float hash(vec2 p) {
@@ -35857,37 +35865,45 @@ var FRAGMENT = `
35857
35865
  ${FOG_GLSL}
35858
35866
 
35859
35867
  // Distance from p's nearest axis-aligned grid line, in world units.
35868
+ // A crisp, ~uGridLineWidthPx-wide line: full alpha to its half-width, a
35869
+ // ~1px anti-aliased falloff beyond that — not a wide, soft-edged band.
35860
35870
  float gridLineAlpha(vec2 p) {
35861
35871
  vec2 cell = mod(p, uGridSpacing);
35862
35872
  vec2 distToLine = min(cell, uGridSpacing - cell);
35863
- vec2 lineAlpha2 = 1.0 - smoothstep(uGridLineWorld, uGridLineWorld * 2.0, distToLine);
35873
+ float halfWidth = 0.5 * uGridLineWidthPx * uWorldPerPixel;
35874
+ float aa = uWorldPerPixel;
35875
+ vec2 lineAlpha2 = 1.0 - smoothstep(halfWidth - aa * 0.5, halfWidth + aa * 0.5, distToLine);
35864
35876
  return max(lineAlpha2.x, lineAlpha2.y);
35865
35877
  }
35866
35878
 
35867
- // Distance from p's nearest grid intersection, in world units.
35879
+ // Distance from p's nearest grid intersection, in world units. Dot radius
35880
+ // matches the configured line width in pixels, same anti-aliasing.
35868
35881
  float gridDotAlpha(vec2 p) {
35869
35882
  vec2 cell = mod(p, uGridSpacing) - uGridSpacing * 0.5;
35870
35883
  float dist = length(cell);
35871
- float radius = uGridLineWorld * 1.5;
35872
- return 1.0 - smoothstep(radius, radius * 2.0, dist);
35884
+ float radius = uGridLineWidthPx * uWorldPerPixel;
35885
+ float aa = uWorldPerPixel;
35886
+ return 1.0 - smoothstep(radius - aa * 0.5, radius + aa * 0.5, dist);
35873
35887
  }
35874
35888
 
35875
35889
  void main() {
35876
35890
  vec3 col = uColor;
35877
35891
 
35878
- // Melamine micro-texture: two octaves of value noise, +-1.75% brightness.
35879
- float n = vnoise(vWorldPos.xy * 2.3) * 0.6 + vnoise(vWorldPos.xy * 9.7) * 0.4;
35880
- col *= 1.0 + (n - 0.5) * 0.035;
35892
+ if (uSurfaceTexture > 0.5) {
35893
+ // Melamine micro-texture: two octaves of value noise, +-1.75% brightness.
35894
+ float n = vnoise(vWorldPos.xy * 2.3) * 0.6 + vnoise(vWorldPos.xy * 9.7) * 0.4;
35895
+ col *= 1.0 + (n - 0.5) * 0.035;
35881
35896
 
35882
- // Satin sheen: Fresnel rim + a specular band from the key light. Both are
35883
- // ~zero in the straight-on working view and bloom as the camera tilts.
35884
- vec3 V = normalize(cameraPosition - vWorldPos);
35885
- vec3 N = vec3(0.0, 0.0, 1.0);
35886
- float fres = pow(1.0 - max(dot(N, V), 0.0), 3.0);
35887
- vec3 L = normalize(vec3(-0.25, 0.4, 0.88));
35888
- vec3 H = normalize(L + V);
35889
- float spec = pow(max(dot(N, H), 0.0), 90.0);
35890
- col += (fres * 0.18 + spec * 0.06) * vec3(1.0);
35897
+ // Satin sheen: Fresnel rim + a specular band from the key light. Both
35898
+ // are ~zero in the straight-on working view and bloom as the camera tilts.
35899
+ vec3 V = normalize(cameraPosition - vWorldPos);
35900
+ vec3 N = vec3(0.0, 0.0, 1.0);
35901
+ float fres = pow(1.0 - max(dot(N, V), 0.0), 3.0);
35902
+ vec3 L = normalize(vec3(-0.25, 0.4, 0.88));
35903
+ vec3 H = normalize(L + V);
35904
+ float spec = pow(max(dot(N, H), 0.0), 90.0);
35905
+ col += (fres * 0.18 + spec * 0.06) * vec3(1.0);
35906
+ }
35891
35907
 
35892
35908
  if (uGridMode > 0.5 && uGridFade > 0.001) {
35893
35909
  float gridAlpha = (uGridMode < 1.5 ? gridLineAlpha(vWorldPos.xy) : gridDotAlpha(vWorldPos.xy)) * uGridFade;
@@ -35907,8 +35923,10 @@ function createBoardSurface(fog, theme) {
35907
35923
  uGridMode: { value: GRID_MODE_CODE[theme.gridMode] },
35908
35924
  uGridColor: { value: new Color(theme.gridColor) },
35909
35925
  uGridSpacing: { value: Math.max(.001, theme.gridSpacing) },
35910
- uGridLineWorld: { value: .05 },
35926
+ uGridLineWidthPx: { value: Math.max(.1, theme.gridLineWidth) },
35927
+ uWorldPerPixel: { value: .05 },
35911
35928
  uGridFade: { value: 0 },
35929
+ uSurfaceTexture: { value: TEXTURE_CODE[theme.surfaceTexture] },
35912
35930
  ...fog
35913
35931
  }
35914
35932
  });
@@ -35916,13 +35934,15 @@ function createBoardSurface(fog, theme) {
35916
35934
  mesh.name = "board-surface";
35917
35935
  return mesh;
35918
35936
  }
35919
- /** Applies a theme change (initial or live) — everything except the per-frame fade/line-width. */
35937
+ /** Applies a theme change (initial or live) — everything except the per-frame fade/pixel-ratio. */
35920
35938
  function setBoardSurfaceTheme(mesh, theme) {
35921
35939
  const uniforms = mesh.material.uniforms;
35922
35940
  uniforms.uColor.value.set(theme.color);
35923
35941
  uniforms.uGridMode.value = GRID_MODE_CODE[theme.gridMode];
35924
35942
  uniforms.uGridColor.value.set(theme.gridColor);
35925
35943
  uniforms.uGridSpacing.value = Math.max(.001, theme.gridSpacing);
35944
+ uniforms.uGridLineWidthPx.value = Math.max(.1, theme.gridLineWidth);
35945
+ uniforms.uSurfaceTexture.value = TEXTURE_CODE[theme.surfaceTexture];
35926
35946
  }
35927
35947
  /** Keep the plane centered under the view, larger than the fog horizon, and the grid's on-screen weight/fade current. */
35928
35948
  function updateBoardSurface(mesh, centerX, centerY, fogFar, worldPerPixel) {
@@ -35930,7 +35950,7 @@ function updateBoardSurface(mesh, centerX, centerY, fogFar, worldPerPixel) {
35930
35950
  const size = fogFar * 2.5;
35931
35951
  mesh.scale.set(size, size, 1);
35932
35952
  const uniforms = mesh.material.uniforms;
35933
- uniforms.uGridLineWorld.value = worldPerPixel;
35953
+ uniforms.uWorldPerPixel.value = worldPerPixel;
35934
35954
  const t = (uniforms.uGridSpacing.value / Math.max(1e-6, worldPerPixel) - GRID_FADE_MIN_PX) / (GRID_FADE_MAX_PX - GRID_FADE_MIN_PX);
35935
35955
  uniforms.uGridFade.value = MathUtils.clamp(t, 0, 1);
35936
35956
  }
@@ -36103,7 +36123,7 @@ var CameraRig = class {
36103
36123
  }
36104
36124
  };
36105
36125
  //#endregion
36106
- //#region ../../src/scrawl/engine/scene.ts
36126
+ //#region src/renderer/canvas/scene.ts
36107
36127
  var SceneRig = class {
36108
36128
  constructor(canvas, boardTheme) {
36109
36129
  this.renderer = new WebGLRenderer({
@@ -36150,7 +36170,7 @@ var SceneRig = class {
36150
36170
  }
36151
36171
  };
36152
36172
  //#endregion
36153
- //#region ../../src/scrawl/ink/inkMaterial.ts
36173
+ //#region src/renderer/shapes/inkMaterial.ts
36154
36174
  var VERTEX = `
36155
36175
  attribute float aAlpha;
36156
36176
  varying vec2 vUv;
@@ -36281,7 +36301,7 @@ var InkMaterials = class {
36281
36301
  }
36282
36302
  };
36283
36303
  //#endregion
36284
- //#region ../../src/scrawl/ink/ribbonGeometry.ts
36304
+ //#region src/renderer/shapes/ribbonGeometry.ts
36285
36305
  /** Ink floats a hair above the board to avoid z-fighting with the surface. */
36286
36306
  var INK_Z = .02;
36287
36307
  /** Highlighter always renders under opaque ink (build prompt §4.1). */
@@ -36332,7 +36352,7 @@ function buildRibbonGeometry(points, baseWidth, z = INK_Z) {
36332
36352
  return geometry;
36333
36353
  }
36334
36354
  //#endregion
36335
- //#region ../../src/scrawl/ink/strokeRenderer.ts
36355
+ //#region src/renderer/shapes/strokeRenderer.ts
36336
36356
  var strokeZ = (stroke) => stroke.tool === "highlighter" ? HIGHLIGHT_Z : INK_Z;
36337
36357
  /**
36338
36358
  * Derives ink meshes from the document by subscription — the document never
@@ -36465,7 +36485,7 @@ function syncMatrix(mesh, stroke) {
36465
36485
  mesh.matrixWorldNeedsUpdate = true;
36466
36486
  }
36467
36487
  //#endregion
36468
- //#region ../../src/scrawl/scene-objects/markerProp.ts
36488
+ //#region src/renderer/shapes/markerProp.ts
36469
36489
  var BODY_LENGTH = 11;
36470
36490
  var BODY_RADIUS = .85;
36471
36491
  var TIP_LENGTH = 1.6;
@@ -36561,7 +36581,7 @@ var MarkerProp = class {
36561
36581
  }
36562
36582
  };
36563
36583
  //#endregion
36564
- //#region ../../src/scrawl/scene-objects/noteGeometry.ts
36584
+ //#region src/renderer/shapes/noteGeometry.ts
36565
36585
  /**
36566
36586
  * Height of the sheet above the board at (u, v) ∈ [0,1]².
36567
36587
  * v = 1 is the adhesive edge (always 0); u = 1 is the peel corner side.
@@ -36605,7 +36625,7 @@ function applyCurl(geometry, curl, peel) {
36605
36625
  color.needsUpdate = true;
36606
36626
  }
36607
36627
  //#endregion
36608
- //#region ../../src/scrawl/scene-objects/stickyNotes.ts
36628
+ //#region src/renderer/shapes/stickyNotes.ts
36609
36629
  var TEXTURE_SIZE = 512;
36610
36630
  var FOCUS_COLOR$4 = 2450411;
36611
36631
  var CURL = .3;
@@ -43181,7 +43201,7 @@ SYNCABLE_PROPS.forEach((prop) => {
43181
43201
  new Box3();
43182
43202
  new Color();
43183
43203
  //#endregion
43184
- //#region ../../src/scrawl/scene-objects/tables.ts
43204
+ //#region src/renderer/shapes/tables.ts
43185
43205
  var FONT_URL$2 = "/fonts/inter-medium.woff";
43186
43206
  var TABLE_Z = .012;
43187
43207
  var HEADER_Z = .014;
@@ -43530,7 +43550,7 @@ var TablesRenderer = class {
43530
43550
  }
43531
43551
  };
43532
43552
  //#endregion
43533
- //#region ../../src/scrawl/scene-objects/textBlocks.ts
43553
+ //#region src/renderer/text/textBlocks.ts
43534
43554
  var FONT_URL$1 = "/fonts/inter-medium.woff";
43535
43555
  var TEXT_Z = .02;
43536
43556
  var FOCUS_COLOR$2 = 2450411;
@@ -43634,7 +43654,7 @@ var TextBlocksRenderer = class {
43634
43654
  }
43635
43655
  };
43636
43656
  //#endregion
43637
- //#region ../../src/scrawl/scene-objects/comments.ts
43657
+ //#region src/renderer/shapes/comments.ts
43638
43658
  var PIN_Z = .04;
43639
43659
  var PIN_WIDTH = 3.6;
43640
43660
  var PIN_HEIGHT = 3.6;
@@ -43802,7 +43822,7 @@ function drawBubblePath(ctx, x, y, w, h, r, tailX, tailY, tailW) {
43802
43822
  ctx.closePath();
43803
43823
  }
43804
43824
  //#endregion
43805
- //#region ../../src/scrawl/scene-objects/images.ts
43825
+ //#region src/renderer/shapes/images.ts
43806
43826
  var IMAGE_Z = .015;
43807
43827
  var FRAME_BORDER = .4;
43808
43828
  var FOCUS_COLOR$1 = 2450411;
@@ -44012,7 +44032,7 @@ function applyPlaceholderTexture(visual, label) {
44012
44032
  visual.imageMesh.material.needsUpdate = true;
44013
44033
  }
44014
44034
  //#endregion
44015
- //#region ../../src/scrawl/scene-objects/kitchenTimers.ts
44035
+ //#region src/renderer/shapes/kitchenTimers.ts
44016
44036
  var FACE_SIZE = 256;
44017
44037
  var BODY_COLOR = 14753096;
44018
44038
  var KNOB_COLOR = 16448250;
@@ -44210,7 +44230,7 @@ function renderTimerFace(timer, canvas, now = Date.now()) {
44210
44230
  return c;
44211
44231
  }
44212
44232
  //#endregion
44213
- //#region ../../src/scrawl/scene-objects/customObjects.ts
44233
+ //#region src/renderer/shapes/customObjects.ts
44214
44234
  var FONT_URL = "/fonts/inter-medium.woff";
44215
44235
  var CUSTOM_Z = .02;
44216
44236
  var FOCUS_COLOR = 2450411;
@@ -44572,7 +44592,7 @@ function disposeObject3D(obj) {
44572
44592
  });
44573
44593
  }
44574
44594
  //#endregion
44575
- //#region ../../src/scrawl/tools/hitTest.ts
44595
+ //#region src/interaction/pointer/hitTest.ts
44576
44596
  /** Topmost (most recently added) stroke under the point, or null. */
44577
44597
  function hitStroke(doc, index, p, slop) {
44578
44598
  const candidates = index.query(p.x - slop, p.y - slop, p.x + slop, p.y + slop);
@@ -44613,7 +44633,7 @@ function distToSegmentSq(p, a, b) {
44613
44633
  return dx * dx + dy * dy;
44614
44634
  }
44615
44635
  //#endregion
44616
- //#region ../../src/scrawl/tools/eraserTool.ts
44636
+ //#region src/interaction/tools/eraserTool.ts
44617
44637
  var DECAY_PER_PASS = .4;
44618
44638
  /** A point can take another decay pass after this long — rubbing works. */
44619
44639
  var REARM_MS = 250;
@@ -44741,7 +44761,7 @@ function partitionByErasure(points) {
44741
44761
  return runs;
44742
44762
  }
44743
44763
  //#endregion
44744
- //#region ../../src/scrawl/tools/noteDrag.ts
44764
+ //#region src/interaction/tools/noteDrag.ts
44745
44765
  /**
44746
44766
  * Drag the focused note in X/Y. The grab is resolved on the note's own
44747
44767
  * z-plane, not the board, so the note doesn't jump under a tilted ray.
@@ -44808,7 +44828,7 @@ var NoteDragState = class {
44808
44828
  }
44809
44829
  };
44810
44830
  //#endregion
44811
- //#region ../../src/scrawl/tools/imageDrag.ts
44831
+ //#region src/interaction/tools/imageDrag.ts
44812
44832
  /** Drag the focused image in X/Y; one command per drag. */
44813
44833
  var ImageDragState = class {
44814
44834
  constructor(ctx) {
@@ -44860,7 +44880,7 @@ var ImageDragState = class {
44860
44880
  }
44861
44881
  };
44862
44882
  //#endregion
44863
- //#region ../../src/scrawl/tools/timerDrag.ts
44883
+ //#region src/interaction/tools/timerDrag.ts
44864
44884
  /** Drag the focused timer in X/Y; one command per drag. */
44865
44885
  var TimerDragState = class {
44866
44886
  constructor(ctx) {
@@ -44912,7 +44932,7 @@ var TimerDragState = class {
44912
44932
  }
44913
44933
  };
44914
44934
  //#endregion
44915
- //#region ../../src/scrawl/tools/shapes.ts
44935
+ //#region src/interaction/tools/shapeGeometry.ts
44916
44936
  /** Uniform synthetic pressure; the ink shader supplies the hand-drawn feel. */
44917
44937
  var PRESSURE = .7;
44918
44938
  var MIN_ELLIPSE_SEGMENTS = 24;
@@ -45101,7 +45121,7 @@ function snappedEnd(a, b) {
45101
45121
  };
45102
45122
  }
45103
45123
  //#endregion
45104
- //#region ../../src/scrawl/tools/shapeTool.ts
45124
+ //#region src/interaction/tools/shapeTool.ts
45105
45125
  var MIN_DRAG_PX$1 = 4;
45106
45126
  /** Drag out a shape; live preview through the real ink material. */
45107
45127
  var ShapeTool = class {
@@ -45132,7 +45152,7 @@ var ShapeTool = class {
45132
45152
  const strokes = this.polylines.map((points) => ({
45133
45153
  id: crypto.randomUUID(),
45134
45154
  color: this.ctx.inkColor(),
45135
- baseWidth: this.ctx.baseWidth(),
45155
+ baseWidth: this.ctx.shapeStrokeWidth(),
45136
45156
  points
45137
45157
  }));
45138
45158
  this.ctx.clusters.assign(strokes[0]);
@@ -45166,13 +45186,13 @@ var ShapeTool = class {
45166
45186
  this.previews[i].visible = polyline !== void 0;
45167
45187
  if (polyline) {
45168
45188
  this.previews[i].geometry.dispose();
45169
- this.previews[i].geometry = buildRibbonGeometry(polyline, this.ctx.baseWidth());
45189
+ this.previews[i].geometry = buildRibbonGeometry(polyline, this.ctx.shapeStrokeWidth());
45170
45190
  }
45171
45191
  }
45172
45192
  }
45173
45193
  };
45174
45194
  //#endregion
45175
- //#region ../../src/scrawl/tools/tableDrag.ts
45195
+ //#region src/interaction/tools/tableDrag.ts
45176
45196
  function getHandleCursor(handle) {
45177
45197
  switch (handle) {
45178
45198
  case "nw":
@@ -45292,7 +45312,7 @@ var TableDragState = class {
45292
45312
  }
45293
45313
  };
45294
45314
  //#endregion
45295
- //#region ../../src/scrawl/tools/tableTool.ts
45315
+ //#region src/interaction/tools/tableTool.ts
45296
45316
  var MIN_DRAG_PX = 6;
45297
45317
  var TableTool = class {
45298
45318
  constructor(ctx) {
@@ -45423,7 +45443,7 @@ var TableTool = class {
45423
45443
  }
45424
45444
  };
45425
45445
  //#endregion
45426
- //#region ../../src/scrawl/tools/commentTool.ts
45446
+ //#region src/interaction/tools/commentTool.ts
45427
45447
  /** Click on the board to drop a new comment pin or open an existing one. */
45428
45448
  var CommentTool = class {
45429
45449
  constructor(ctx) {
@@ -45458,7 +45478,7 @@ var CommentTool = class {
45458
45478
  }
45459
45479
  };
45460
45480
  //#endregion
45461
- //#region ../../src/scrawl/tools/textDrag.ts
45481
+ //#region src/interaction/tools/textDrag.ts
45462
45482
  /** Drag the focused text block in X/Y; one command per drag. */
45463
45483
  var TextDragState = class {
45464
45484
  constructor(ctx) {
@@ -45510,7 +45530,7 @@ var TextDragState = class {
45510
45530
  }
45511
45531
  };
45512
45532
  //#endregion
45513
- //#region ../../src/scrawl/tools/fsm.ts
45533
+ //#region src/interaction/tools/fsm.ts
45514
45534
  var ToolStateMachine = class {
45515
45535
  constructor() {
45516
45536
  this.states = /* @__PURE__ */ new Map();
@@ -45712,7 +45732,7 @@ var IdleState = class {
45712
45732
  }
45713
45733
  };
45714
45734
  //#endregion
45715
- //#region ../../src/scrawl/tools/gizmo.ts
45735
+ //#region src/renderer/selection/gizmo.ts
45716
45736
  var GIZMO_Z = .05;
45717
45737
  var HANDLE_PX = 9;
45718
45738
  var HANDLE_HIT_PX = 12;
@@ -45943,7 +45963,7 @@ function boxesEqual(a, b) {
45943
45963
  return a.minX === b.minX && a.minY === b.minY && a.maxX === b.maxX && a.maxY === b.maxY;
45944
45964
  }
45945
45965
  //#endregion
45946
- //#region ../../src/scrawl/ink/pressure.ts
45966
+ //#region src/interaction/tools/pressure.ts
45947
45967
  var SIM_MAX = 1;
45948
45968
  var SIM_MIN = .3;
45949
45969
  var SMOOTHING = .25;
@@ -45965,7 +45985,7 @@ function clamp(v, lo, hi) {
45965
45985
  return Math.min(hi, Math.max(lo, v));
45966
45986
  }
45967
45987
  //#endregion
45968
- //#region ../../src/scrawl/ink/strokeBuilder.ts
45988
+ //#region src/interaction/tools/strokeBuilder.ts
45969
45989
  var StrokeBuilder = class {
45970
45990
  constructor() {
45971
45991
  this.points = [];
@@ -46015,7 +46035,7 @@ function catmullRom(v0, v1, v2, v3, t) {
46015
46035
  return .5 * (2 * v1 + (-v0 + v2) * t + (2 * v0 - 5 * v1 + 4 * v2 - v3) * t2 + (-v0 + 3 * v1 - 3 * v2 + v3) * t3);
46016
46036
  }
46017
46037
  //#endregion
46018
- //#region ../../src/scrawl/tools/markerTool.ts
46038
+ //#region src/interaction/tools/markerTool.ts
46019
46039
  /** Sample spacing while drawing, in screen pixels. */
46020
46040
  var MIN_SAMPLE_PX = 1.5;
46021
46041
  /** A highlighter is a chisel tip: near-constant width, little pressure play. */
@@ -46118,7 +46138,7 @@ var MarkerTool = class {
46118
46138
  }
46119
46139
  };
46120
46140
  //#endregion
46121
- //#region ../../src/scrawl/tools/orbitTool.ts
46141
+ //#region src/interaction/tools/orbitTool.ts
46122
46142
  /**
46123
46143
  * Orbit-preview (build prompt §4.2): alt-drag tilts the camera off-axis to
46124
46144
  * read the board's material and depth; releasing eases back to the working
@@ -46153,7 +46173,7 @@ var OrbitTool = class {
46153
46173
  }
46154
46174
  };
46155
46175
  //#endregion
46156
- //#region ../../src/scrawl/tools/panTool.ts
46176
+ //#region src/interaction/tools/panTool.ts
46157
46177
  /** Grab-pan: content follows the pointer. */
46158
46178
  var PanTool = class {
46159
46179
  constructor(ctx) {
@@ -46183,7 +46203,7 @@ var PanTool = class {
46183
46203
  }
46184
46204
  };
46185
46205
  //#endregion
46186
- //#region ../../src/scrawl/tools/selection.ts
46206
+ //#region src/renderer/selection/selection.ts
46187
46207
  /** The set of selected stroke ids. Drops ids the document no longer has. */
46188
46208
  var SelectionManager = class {
46189
46209
  constructor(doc) {
@@ -46234,7 +46254,7 @@ var SelectionManager = class {
46234
46254
  }
46235
46255
  };
46236
46256
  //#endregion
46237
- //#region ../../src/scrawl/tools/selectTool.ts
46257
+ //#region src/interaction/tools/selectTool.ts
46238
46258
  var HIT_SLOP_PX = 4;
46239
46259
  var CLICK_TOLERANCE_PX = 3;
46240
46260
  var MIN_SCALE = .05;
@@ -46450,7 +46470,7 @@ function safeRatio(numerator, denominator) {
46450
46470
  return raw;
46451
46471
  }
46452
46472
  //#endregion
46453
- //#region ../../src/scrawl/tools/customTool.ts
46473
+ //#region src/interaction/tools/customTool.ts
46454
46474
  function extensionIdFromToolId(toolId) {
46455
46475
  const slash = toolId.indexOf("/");
46456
46476
  return slash === -1 ? toolId : toolId.slice(0, slash);
@@ -46565,12 +46585,19 @@ var ExtensionCommandBatch = class {
46565
46585
  }
46566
46586
  };
46567
46587
  //#endregion
46568
- //#region ../../src/scrawl/ScrawlEngine.ts
46588
+ //#region src/composition/ScrawlEngine.ts
46569
46589
  /** Marker stroke width in board units (~1% of the default view height). */
46570
46590
  var BASE_WIDTH = .5;
46571
46591
  /** Highlighter chisel width — wide enough to cover a line of handwriting. */
46572
46592
  var HIGHLIGHT_WIDTH = 2.2;
46573
46593
  /**
46594
+ * Shape (rectangle, ellipse, arrow, ...) border width in board units.
46595
+ * Deliberately thinner than BASE_WIDTH: a shape border reads as a crisp,
46596
+ * structural line, not an expressive marker stroke — this is also the
46597
+ * fallback when no Host theme configures ScrawlTheme.shapeStrokeWidth.
46598
+ */
46599
+ var DEFAULT_SHAPE_STROKE_WIDTH = .15;
46600
+ /**
46574
46601
  * Identity of a rendered selection badge: which item, its lock state, and
46575
46602
  * where it sits on screen (rounded to the pixel, so sub-pixel camera drift
46576
46603
  * does not churn React).
@@ -46591,10 +46618,11 @@ function emptyExtensionRegistry() {
46591
46618
  * React never reaches past this class.
46592
46619
  */
46593
46620
  var ScrawlEngine = class {
46594
- constructor(canvas, documentId$1, extensionRegistry = emptyExtensionRegistry(), assetController = new AssetController(void 0), boardTheme = DEFAULT_BOARD_SURFACE_THEME) {
46621
+ constructor(canvas, documentId$1, extensionRegistry = emptyExtensionRegistry(), assetController = new AssetController(void 0), boardTheme = DEFAULT_BOARD_SURFACE_THEME, shapeStrokeWidth = DEFAULT_SHAPE_STROKE_WIDTH) {
46595
46622
  this.canvas = canvas;
46596
46623
  this.extensionRegistry = extensionRegistry;
46597
46624
  this.boardTheme = boardTheme;
46625
+ this.shapeStrokeWidth = shapeStrokeWidth;
46598
46626
  this.onToolChange = null;
46599
46627
  this.onZoomChange = null;
46600
46628
  this.onAuditEvent = null;
@@ -46689,6 +46717,7 @@ var ScrawlEngine = class {
46689
46717
  setTool: (t) => this.setTool(t),
46690
46718
  inkColor: () => this.tool === "highlighter" ? this.highlightColor : this.color,
46691
46719
  baseWidth: () => this.tool === "highlighter" ? HIGHLIGHT_WIDTH : BASE_WIDTH,
46720
+ shapeStrokeWidth: () => this.shapeStrokeWidth,
46692
46721
  activeTool: () => this.tool,
46693
46722
  isSpaceHeld: () => this.spaceHeld,
46694
46723
  pointerToBoard: (e) => pointerToBoard(e.clientX, e.clientY, canvas, this.scene.cameraRig.camera),
@@ -46953,6 +46982,10 @@ var ScrawlEngine = class {
46953
46982
  this.boardTheme = theme;
46954
46983
  this.scene.applyBoardTheme(theme);
46955
46984
  }
46985
+ /** Live update of the shape tool's border width (board units). Already-drawn shapes keep the width they were drawn with. */
46986
+ setShapeStrokeWidth(width) {
46987
+ this.shapeStrokeWidth = width;
46988
+ }
46956
46989
  /**
46957
46990
  * Snap to a peer's camera. No-op while drawing so pointer-to-board
46958
46991
  * mapping does not warp the in-progress stroke.
@@ -48014,7 +48047,7 @@ var ScrawlEngine = class {
48014
48047
  }
48015
48048
  };
48016
48049
  //#endregion
48017
- //#region src/controller-internal.ts
48050
+ //#region src/composition/controller-internal.ts
48018
48051
  function createBoardController(options) {
48019
48052
  return createBoardControllerWithEngine(options);
48020
48053
  }
@@ -48032,9 +48065,12 @@ function createBoardControllerWithEngine(options, existingEngine) {
48032
48065
  color: options.boardTheme?.surface ?? DEFAULT_BOARD_SURFACE_THEME.color,
48033
48066
  gridMode: options.boardTheme?.gridMode ?? DEFAULT_BOARD_SURFACE_THEME.gridMode,
48034
48067
  gridColor: options.boardTheme?.gridColor ?? DEFAULT_BOARD_SURFACE_THEME.gridColor,
48035
- gridSpacing: options.boardTheme?.gridSpacing ?? DEFAULT_BOARD_SURFACE_THEME.gridSpacing
48068
+ gridSpacing: options.boardTheme?.gridSpacing ?? DEFAULT_BOARD_SURFACE_THEME.gridSpacing,
48069
+ gridLineWidth: options.boardTheme?.gridLineWidth ?? DEFAULT_BOARD_SURFACE_THEME.gridLineWidth,
48070
+ surfaceTexture: options.boardTheme?.surfaceTexture ?? DEFAULT_BOARD_SURFACE_THEME.surfaceTexture
48036
48071
  };
48037
- const engine = existingEngine ?? (options.canvas ? new ScrawlEngine(options.canvas, options.document.id, extensionRegistry, assetController, currentBoardTheme) : null);
48072
+ let currentShapeStrokeWidth = options.boardTheme?.shapeStrokeWidth ?? .15;
48073
+ const engine = existingEngine ?? (options.canvas ? new ScrawlEngine(options.canvas, options.document.id, extensionRegistry, assetController, currentBoardTheme, currentShapeStrokeWidth) : null);
48038
48074
  const document = engine?.document ?? new BoardDocument(documentId(options.document.id));
48039
48075
  const history = engine?.history ?? new History(document);
48040
48076
  const createId = options.createId ?? (() => crypto.randomUUID());
@@ -48523,9 +48559,15 @@ function createBoardControllerWithEngine(options, existingEngine) {
48523
48559
  color: theme.surface ?? currentBoardTheme.color,
48524
48560
  gridMode: theme.gridMode ?? currentBoardTheme.gridMode,
48525
48561
  gridColor: theme.gridColor ?? currentBoardTheme.gridColor,
48526
- gridSpacing: theme.gridSpacing ?? currentBoardTheme.gridSpacing
48562
+ gridSpacing: theme.gridSpacing ?? currentBoardTheme.gridSpacing,
48563
+ gridLineWidth: theme.gridLineWidth ?? currentBoardTheme.gridLineWidth,
48564
+ surfaceTexture: theme.surfaceTexture ?? currentBoardTheme.surfaceTexture
48527
48565
  };
48528
48566
  engine?.applyBoardTheme(currentBoardTheme);
48567
+ if (theme.shapeStrokeWidth !== void 0) {
48568
+ currentShapeStrokeWidth = theme.shapeStrokeWidth;
48569
+ engine?.setShapeStrokeWidth(currentShapeStrokeWidth);
48570
+ }
48529
48571
  } },
48530
48572
  view: {
48531
48573
  fit() {