@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/react.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createContext, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
2
2
  import { createPortal } from "react-dom";
3
3
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
- //#region src/core-internal/assets.ts
4
+ //#region src/core/shapes/assets.ts
5
5
  var ASSET_REF_PATTERN = /^asset:[a-z0-9](?:[a-z0-9.-]{0,61}[a-z0-9])?:[A-Za-z0-9._~-]+$/;
6
6
  function isAssetRef(value) {
7
7
  return typeof value === "string" && ASSET_REF_PATTERN.test(value) && new TextEncoder().encode(value).length <= 512;
@@ -33,7 +33,7 @@ function clampAssetCacheBytes(value) {
33
33
  return Math.min(ASSET_CACHE_BYTES_MAX, Math.max(ASSET_CACHE_BYTES_MIN, Math.floor(value)));
34
34
  }
35
35
  //#endregion
36
- //#region src/core-internal/identifiers.ts
36
+ //#region src/core/document/identifiers.ts
37
37
  function documentId(value) {
38
38
  if (!value) throw new TypeError("DocumentId cannot be empty");
39
39
  return value;
@@ -234,7 +234,7 @@ function validation(message, path) {
234
234
  return new DocumentRecoveryError("DOCUMENT_VALIDATION_FAILED", message, void 0, path);
235
235
  }
236
236
  //#endregion
237
- //#region src/core-internal/affine2d.ts
237
+ //#region src/core/shapes/affine2d.ts
238
238
  var IDENTITY = [
239
239
  1,
240
240
  0,
@@ -318,7 +318,7 @@ function avgScale(m) {
318
318
  return (Math.hypot(m[0], m[1]) + Math.hypot(m[2], m[3])) / 2;
319
319
  }
320
320
  //#endregion
321
- //#region src/core-internal/boardSearch.ts
321
+ //#region src/core/document/boardSearch.ts
322
322
  function hay(s) {
323
323
  return s.toLowerCase();
324
324
  }
@@ -383,7 +383,7 @@ function searchBoard(query, board) {
383
383
  return hits;
384
384
  }
385
385
  //#endregion
386
- //#region src/core-internal/types.ts
386
+ //#region src/core/shapes/types.ts
387
387
  /** Erasure level at which a point counts as fully erased. */
388
388
  var ERASE_THRESHOLD = .95;
389
389
  function cloneStroke(stroke) {
@@ -455,7 +455,7 @@ function cloneImage(img) {
455
455
  return { ...img };
456
456
  }
457
457
  //#endregion
458
- //#region src/core-internal/clusterStore.ts
458
+ //#region src/core/document/clusterStore.ts
459
459
  /** A stroke joins a cluster last written to within this window at full reach. */
460
460
  var RECENT_MS = 8e3;
461
461
  /** Older clusters are still joinable, at half the gap threshold. */
@@ -626,7 +626,7 @@ function clamp$3(v, lo, hi) {
626
626
  return Math.min(hi, Math.max(lo, v));
627
627
  }
628
628
  //#endregion
629
- //#region src/core-internal/extensionTypes.ts
629
+ //#region src/core/shapes/extensionTypes.ts
630
630
  function cloneCustomObject(object) {
631
631
  return {
632
632
  ...object,
@@ -636,7 +636,7 @@ function cloneCustomObject(object) {
636
636
  };
637
637
  }
638
638
  //#endregion
639
- //#region src/core-internal/kitchenTimer.ts
639
+ //#region src/core/shapes/kitchenTimer.ts
640
640
  var TIMER_DEFAULT_DURATION_MS = 300 * 1e3;
641
641
  var TIMER_PRESETS_MS = [
642
642
  6e4,
@@ -692,7 +692,7 @@ function formatTimer(ms) {
692
692
  return `${Math.floor(total / 60)}:${(total % 60).toString().padStart(2, "0")}`;
693
693
  }
694
694
  //#endregion
695
- //#region src/core-internal/itemLock.ts
695
+ //#region src/core/shapes/itemLock.ts
696
696
  /**
697
697
  * Apply or clear a lock in place. Unlock always drops the holder so a stale
698
698
  * name cannot linger on an unlocked item.
@@ -729,7 +729,7 @@ function serializeLock(item) {
729
729
  };
730
730
  }
731
731
  //#endregion
732
- //#region src/core-internal/document.ts
732
+ //#region src/core/document/document.ts
733
733
  /**
734
734
  * The one place a live Stroke becomes its wire representation — in
735
735
  * particular, points collapse from {x,y,pressure,erase?} objects into
@@ -1288,7 +1288,7 @@ function computeBBox(stroke) {
1288
1288
  return world;
1289
1289
  }
1290
1290
  //#endregion
1291
- //#region src/core-internal/commands.ts
1291
+ //#region src/core/commands/commands.ts
1292
1292
  /** One drawing action — a marker stroke, or a shape's strokes as one unit. */
1293
1293
  var AddStrokesCommand = class {
1294
1294
  constructor(strokes) {
@@ -1630,7 +1630,7 @@ var LockItemsCommand = class {
1630
1630
  }
1631
1631
  };
1632
1632
  //#endregion
1633
- //#region src/core-internal/history.ts
1633
+ //#region src/core/history/history.ts
1634
1634
  var LIMIT = 200;
1635
1635
  var History = class {
1636
1636
  constructor(doc) {
@@ -1691,7 +1691,7 @@ var History = class {
1691
1691
  }
1692
1692
  };
1693
1693
  //#endregion
1694
- //#region src/core-internal/ops.ts
1694
+ //#region src/core/events/ops.ts
1695
1695
  var ADDED = [
1696
1696
  ["added", "strokes"],
1697
1697
  ["notesAdded", "notes"],
@@ -1745,7 +1745,7 @@ function changeToOps(change, restoring = false) {
1745
1745
  return ops;
1746
1746
  }
1747
1747
  //#endregion
1748
- //#region src/core-internal/ribbonEdges.ts
1748
+ //#region src/core/shapes/ribbonEdges.ts
1749
1749
  var MIN_WIDTH_FACTOR = .35;
1750
1750
  var END_TAPER = .55;
1751
1751
  function ribbonEdges(points, baseWidth) {
@@ -1777,7 +1777,7 @@ function ribbonEdges(points, baseWidth) {
1777
1777
  return out;
1778
1778
  }
1779
1779
  //#endregion
1780
- //#region src/core-internal/spatialIndex.ts
1780
+ //#region src/core/document/spatialIndex.ts
1781
1781
  var CELL = 8;
1782
1782
  var SpatialIndex = class {
1783
1783
  constructor(doc) {
@@ -1850,7 +1850,7 @@ function cellsOf(box) {
1850
1850
  return keys;
1851
1851
  }
1852
1852
  //#endregion
1853
- //#region src/core-internal/stamps.ts
1853
+ //#region src/core/shapes/stamps.ts
1854
1854
  var STAMPS = [
1855
1855
  {
1856
1856
  kind: "star",
@@ -1895,7 +1895,7 @@ function stampDataUrl(kind) {
1895
1895
  return `data:image/svg+xml;utf8,${encodeURIComponent(SVG[kind])}`;
1896
1896
  }
1897
1897
  //#endregion
1898
- //#region src/core-internal/presence.ts
1898
+ //#region src/collaboration/presence/presence.ts
1899
1899
  /** Keep beacons clear of typical chrome at the top and bottom of the Board. */
1900
1900
  var BEACON_INSET = {
1901
1901
  top: 72,
@@ -1931,7 +1931,7 @@ function clamp$2(value, min, max) {
1931
1931
  return Math.min(max, Math.max(min, value));
1932
1932
  }
1933
1933
  //#endregion
1934
- //#region src/core-internal/svg.ts
1934
+ //#region src/persistence/serialization/svg.ts
1935
1935
  /** Matches the key light used by the board shader and note shadows. */
1936
1936
  var LIGHT = {
1937
1937
  x: -.25,
@@ -2298,7 +2298,7 @@ function fmt(n) {
2298
2298
  return String(Math.round(n * 100) / 100);
2299
2299
  }
2300
2300
  //#endregion
2301
- //#region src/core-internal/sceneGeometry.ts
2301
+ //#region src/core/shapes/sceneGeometry.ts
2302
2302
  var EMPTY_BOUNDS = {
2303
2303
  x: 0,
2304
2304
  y: 0,
@@ -2362,7 +2362,7 @@ function textCorners(x, y, text, fontSize, align, m) {
2362
2362
  return rectCorners(x + (align === "center" ? -width / 2 : align === "end" ? -width : 0), y, width, height, m);
2363
2363
  }
2364
2364
  //#endregion
2365
- //#region src/core-internal/customObjects.ts
2365
+ //#region src/core/shapes/customObjects.ts
2366
2366
  var FALLBACK_LABEL_MAX_LENGTH = 80;
2367
2367
  function toReadonly(object) {
2368
2368
  return object;
@@ -2491,7 +2491,7 @@ function extensionIdFromType(type) {
2491
2491
  return slash === -1 ? type : type.slice(0, slash);
2492
2492
  }
2493
2493
  //#endregion
2494
- //#region src/core-internal/extensionRegistry.ts
2494
+ //#region src/core/shapes/extensionRegistry.ts
2495
2495
  function buildExtensionRegistry(extensions) {
2496
2496
  const list = extensions ?? [];
2497
2497
  const diagnostics = [];
@@ -2588,7 +2588,7 @@ function isWellFormedNamespacedId(id) {
2588
2588
  return isWellFormedId(id) && id.includes("/") && !id.startsWith("/") && !id.endsWith("/");
2589
2589
  }
2590
2590
  //#endregion
2591
- //#region src/core-internal/assetValidation.ts
2591
+ //#region src/persistence/adapters/assetValidation.ts
2592
2592
  function sniffImageMediaType(bytes) {
2593
2593
  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";
2594
2594
  if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) return "image/jpeg";
@@ -2675,7 +2675,7 @@ function validateDecodedDimensions(width, height) {
2675
2675
  };
2676
2676
  }
2677
2677
  //#endregion
2678
- //#region src/assetController.ts
2678
+ //#region src/persistence/adapters/assetController.ts
2679
2679
  var FAILURE_COALESCE_MS = 4e3;
2680
2680
  var AssetController = class {
2681
2681
  constructor(resolver, options) {
@@ -2854,7 +2854,7 @@ function toAssetResolutionError(cause, ref) {
2854
2854
  return new AssetResolutionError("unknown", true, cause instanceof Error ? cause.message : String(cause), ref);
2855
2855
  }
2856
2856
  //#endregion
2857
- //#region src/core-internal/assetExport.ts
2857
+ //#region src/persistence/serialization/assetRefCollector.ts
2858
2858
  /** Every unique Asset reference in the document, and which object(s) use it. */
2859
2859
  function collectDocumentAssetRefs(doc, registry) {
2860
2860
  const refs = /* @__PURE__ */ new Map();
@@ -2881,7 +2881,7 @@ function collectSceneAssetRefs(node, objectId, add) {
2881
2881
  if (node.kind === "group") for (const child of node.children) collectSceneAssetRefs(child, objectId, add);
2882
2882
  }
2883
2883
  //#endregion
2884
- //#region src/assetExport.ts
2884
+ //#region src/persistence/serialization/assetExport.ts
2885
2885
  /** Strict mode's rejection — never contains credentials, a fetchable location, or a raw resolver message. */
2886
2886
  var SvgAssetExportError = class extends Error {
2887
2887
  constructor(missingAssets) {
@@ -2937,7 +2937,7 @@ function toDataUri(bytes, mediaType) {
2937
2937
  return `data:${mediaType};base64,${btoa(binary)}`;
2938
2938
  }
2939
2939
  //#endregion
2940
- //#region src/assetIngestion.ts
2940
+ //#region src/persistence/adapters/assetIngestion.ts
2941
2941
  async function decodeDimensionsWithImageBitmap(bytes, mediaType) {
2942
2942
  const blob = new Blob([bytes], { type: mediaType });
2943
2943
  let bitmap;
@@ -35583,7 +35583,7 @@ var WebGLRenderer = class {
35583
35583
  }
35584
35584
  };
35585
35585
  //#endregion
35586
- //#region ../../src/scrawl/data/auditEvents.ts
35586
+ //#region src/core/events/auditEvents.ts
35587
35587
  var BY_LABEL = {
35588
35588
  draw: {
35589
35589
  action: "create",
@@ -35703,7 +35703,7 @@ function lowerFirst(s) {
35703
35703
  return s.charAt(0).toLowerCase() + s.slice(1);
35704
35704
  }
35705
35705
  //#endregion
35706
- //#region ../../src/scrawl/assets/AssetTextureCache.ts
35706
+ //#region src/renderer/shapes/AssetTextureCache.ts
35707
35707
  var AssetTextureCache = class {
35708
35708
  constructor(assetController) {
35709
35709
  this.assetController = assetController;
@@ -35794,7 +35794,7 @@ function toDecodeError(cause, ref) {
35794
35794
  return new AssetResolutionError("decode-failed", false, cause instanceof Error ? cause.message : String(cause), ref);
35795
35795
  }
35796
35796
  //#endregion
35797
- //#region ../../src/scrawl/engine/raycast.ts
35797
+ //#region src/renderer/canvas/raycast.ts
35798
35798
  var origin$1 = new Vector3();
35799
35799
  var target = new Vector3();
35800
35800
  function pointerToBoard(clientX, clientY, canvas, camera) {
@@ -35825,7 +35825,7 @@ function pointerToPlane(clientX, clientY, canvas, camera, z) {
35825
35825
  };
35826
35826
  }
35827
35827
  //#endregion
35828
- //#region ../../src/scrawl/engine/fog.ts
35828
+ //#region src/renderer/canvas/fog.ts
35829
35829
  function createFogUniforms(color) {
35830
35830
  return {
35831
35831
  uFogColor: { value: new Color(color) },
@@ -35846,18 +35846,24 @@ var FOG_GLSL = `
35846
35846
  }
35847
35847
  `;
35848
35848
  //#endregion
35849
- //#region ../../src/scrawl/engine/boardSurface.ts
35849
+ //#region src/renderer/canvas/boardSurface.ts
35850
35850
  var GRID_MODE_CODE = {
35851
35851
  none: 0,
35852
35852
  line: 1,
35853
35853
  dot: 2
35854
35854
  };
35855
+ var TEXTURE_CODE = {
35856
+ flat: 0,
35857
+ textured: 1
35858
+ };
35855
35859
  /** Used when a caller doesn't supply one (e.g. ScrawlEngine constructed without a Host theme). */
35856
35860
  var DEFAULT_BOARD_SURFACE_THEME = {
35857
35861
  color: "#f5f5f3",
35858
35862
  gridMode: "none",
35859
35863
  gridColor: "#deded8",
35860
- gridSpacing: 5
35864
+ gridSpacing: 5,
35865
+ gridLineWidth: 1,
35866
+ surfaceTexture: "flat"
35861
35867
  };
35862
35868
  /** Grid alpha fades out below this on-screen spacing (px) — otherwise it aliases into a solid wash when zoomed out. */
35863
35869
  var GRID_FADE_MIN_PX = 3;
@@ -35878,8 +35884,10 @@ var FRAGMENT = `
35878
35884
  uniform float uGridMode;
35879
35885
  uniform vec3 uGridColor;
35880
35886
  uniform float uGridSpacing;
35881
- uniform float uGridLineWorld;
35887
+ uniform float uWorldPerPixel;
35888
+ uniform float uGridLineWidthPx;
35882
35889
  uniform float uGridFade;
35890
+ uniform float uSurfaceTexture;
35883
35891
  varying vec3 vWorldPos;
35884
35892
 
35885
35893
  float hash(vec2 p) {
@@ -35899,37 +35907,45 @@ var FRAGMENT = `
35899
35907
  ${FOG_GLSL}
35900
35908
 
35901
35909
  // Distance from p's nearest axis-aligned grid line, in world units.
35910
+ // A crisp, ~uGridLineWidthPx-wide line: full alpha to its half-width, a
35911
+ // ~1px anti-aliased falloff beyond that — not a wide, soft-edged band.
35902
35912
  float gridLineAlpha(vec2 p) {
35903
35913
  vec2 cell = mod(p, uGridSpacing);
35904
35914
  vec2 distToLine = min(cell, uGridSpacing - cell);
35905
- vec2 lineAlpha2 = 1.0 - smoothstep(uGridLineWorld, uGridLineWorld * 2.0, distToLine);
35915
+ float halfWidth = 0.5 * uGridLineWidthPx * uWorldPerPixel;
35916
+ float aa = uWorldPerPixel;
35917
+ vec2 lineAlpha2 = 1.0 - smoothstep(halfWidth - aa * 0.5, halfWidth + aa * 0.5, distToLine);
35906
35918
  return max(lineAlpha2.x, lineAlpha2.y);
35907
35919
  }
35908
35920
 
35909
- // Distance from p's nearest grid intersection, in world units.
35921
+ // Distance from p's nearest grid intersection, in world units. Dot radius
35922
+ // matches the configured line width in pixels, same anti-aliasing.
35910
35923
  float gridDotAlpha(vec2 p) {
35911
35924
  vec2 cell = mod(p, uGridSpacing) - uGridSpacing * 0.5;
35912
35925
  float dist = length(cell);
35913
- float radius = uGridLineWorld * 1.5;
35914
- return 1.0 - smoothstep(radius, radius * 2.0, dist);
35926
+ float radius = uGridLineWidthPx * uWorldPerPixel;
35927
+ float aa = uWorldPerPixel;
35928
+ return 1.0 - smoothstep(radius - aa * 0.5, radius + aa * 0.5, dist);
35915
35929
  }
35916
35930
 
35917
35931
  void main() {
35918
35932
  vec3 col = uColor;
35919
35933
 
35920
- // Melamine micro-texture: two octaves of value noise, +-1.75% brightness.
35921
- float n = vnoise(vWorldPos.xy * 2.3) * 0.6 + vnoise(vWorldPos.xy * 9.7) * 0.4;
35922
- col *= 1.0 + (n - 0.5) * 0.035;
35934
+ if (uSurfaceTexture > 0.5) {
35935
+ // Melamine micro-texture: two octaves of value noise, +-1.75% brightness.
35936
+ float n = vnoise(vWorldPos.xy * 2.3) * 0.6 + vnoise(vWorldPos.xy * 9.7) * 0.4;
35937
+ col *= 1.0 + (n - 0.5) * 0.035;
35923
35938
 
35924
- // Satin sheen: Fresnel rim + a specular band from the key light. Both are
35925
- // ~zero in the straight-on working view and bloom as the camera tilts.
35926
- vec3 V = normalize(cameraPosition - vWorldPos);
35927
- vec3 N = vec3(0.0, 0.0, 1.0);
35928
- float fres = pow(1.0 - max(dot(N, V), 0.0), 3.0);
35929
- vec3 L = normalize(vec3(-0.25, 0.4, 0.88));
35930
- vec3 H = normalize(L + V);
35931
- float spec = pow(max(dot(N, H), 0.0), 90.0);
35932
- col += (fres * 0.18 + spec * 0.06) * vec3(1.0);
35939
+ // Satin sheen: Fresnel rim + a specular band from the key light. Both
35940
+ // are ~zero in the straight-on working view and bloom as the camera tilts.
35941
+ vec3 V = normalize(cameraPosition - vWorldPos);
35942
+ vec3 N = vec3(0.0, 0.0, 1.0);
35943
+ float fres = pow(1.0 - max(dot(N, V), 0.0), 3.0);
35944
+ vec3 L = normalize(vec3(-0.25, 0.4, 0.88));
35945
+ vec3 H = normalize(L + V);
35946
+ float spec = pow(max(dot(N, H), 0.0), 90.0);
35947
+ col += (fres * 0.18 + spec * 0.06) * vec3(1.0);
35948
+ }
35933
35949
 
35934
35950
  if (uGridMode > 0.5 && uGridFade > 0.001) {
35935
35951
  float gridAlpha = (uGridMode < 1.5 ? gridLineAlpha(vWorldPos.xy) : gridDotAlpha(vWorldPos.xy)) * uGridFade;
@@ -35949,8 +35965,10 @@ function createBoardSurface(fog, theme) {
35949
35965
  uGridMode: { value: GRID_MODE_CODE[theme.gridMode] },
35950
35966
  uGridColor: { value: new Color(theme.gridColor) },
35951
35967
  uGridSpacing: { value: Math.max(.001, theme.gridSpacing) },
35952
- uGridLineWorld: { value: .05 },
35968
+ uGridLineWidthPx: { value: Math.max(.1, theme.gridLineWidth) },
35969
+ uWorldPerPixel: { value: .05 },
35953
35970
  uGridFade: { value: 0 },
35971
+ uSurfaceTexture: { value: TEXTURE_CODE[theme.surfaceTexture] },
35954
35972
  ...fog
35955
35973
  }
35956
35974
  });
@@ -35958,13 +35976,15 @@ function createBoardSurface(fog, theme) {
35958
35976
  mesh.name = "board-surface";
35959
35977
  return mesh;
35960
35978
  }
35961
- /** Applies a theme change (initial or live) — everything except the per-frame fade/line-width. */
35979
+ /** Applies a theme change (initial or live) — everything except the per-frame fade/pixel-ratio. */
35962
35980
  function setBoardSurfaceTheme(mesh, theme) {
35963
35981
  const uniforms = mesh.material.uniforms;
35964
35982
  uniforms.uColor.value.set(theme.color);
35965
35983
  uniforms.uGridMode.value = GRID_MODE_CODE[theme.gridMode];
35966
35984
  uniforms.uGridColor.value.set(theme.gridColor);
35967
35985
  uniforms.uGridSpacing.value = Math.max(.001, theme.gridSpacing);
35986
+ uniforms.uGridLineWidthPx.value = Math.max(.1, theme.gridLineWidth);
35987
+ uniforms.uSurfaceTexture.value = TEXTURE_CODE[theme.surfaceTexture];
35968
35988
  }
35969
35989
  /** Keep the plane centered under the view, larger than the fog horizon, and the grid's on-screen weight/fade current. */
35970
35990
  function updateBoardSurface(mesh, centerX, centerY, fogFar, worldPerPixel) {
@@ -35972,7 +35992,7 @@ function updateBoardSurface(mesh, centerX, centerY, fogFar, worldPerPixel) {
35972
35992
  const size = fogFar * 2.5;
35973
35993
  mesh.scale.set(size, size, 1);
35974
35994
  const uniforms = mesh.material.uniforms;
35975
- uniforms.uGridLineWorld.value = worldPerPixel;
35995
+ uniforms.uWorldPerPixel.value = worldPerPixel;
35976
35996
  const t = (uniforms.uGridSpacing.value / Math.max(1e-6, worldPerPixel) - GRID_FADE_MIN_PX) / (GRID_FADE_MAX_PX - GRID_FADE_MIN_PX);
35977
35997
  uniforms.uGridFade.value = MathUtils.clamp(t, 0, 1);
35978
35998
  }
@@ -36145,7 +36165,7 @@ var CameraRig = class {
36145
36165
  }
36146
36166
  };
36147
36167
  //#endregion
36148
- //#region ../../src/scrawl/engine/scene.ts
36168
+ //#region src/renderer/canvas/scene.ts
36149
36169
  var SceneRig = class {
36150
36170
  constructor(canvas, boardTheme) {
36151
36171
  this.renderer = new WebGLRenderer({
@@ -36192,7 +36212,7 @@ var SceneRig = class {
36192
36212
  }
36193
36213
  };
36194
36214
  //#endregion
36195
- //#region ../../src/scrawl/ink/inkMaterial.ts
36215
+ //#region src/renderer/shapes/inkMaterial.ts
36196
36216
  var VERTEX = `
36197
36217
  attribute float aAlpha;
36198
36218
  varying vec2 vUv;
@@ -36323,7 +36343,7 @@ var InkMaterials = class {
36323
36343
  }
36324
36344
  };
36325
36345
  //#endregion
36326
- //#region ../../src/scrawl/ink/ribbonGeometry.ts
36346
+ //#region src/renderer/shapes/ribbonGeometry.ts
36327
36347
  /** Ink floats a hair above the board to avoid z-fighting with the surface. */
36328
36348
  var INK_Z = .02;
36329
36349
  /** Highlighter always renders under opaque ink (build prompt §4.1). */
@@ -36374,7 +36394,7 @@ function buildRibbonGeometry(points, baseWidth, z = INK_Z) {
36374
36394
  return geometry;
36375
36395
  }
36376
36396
  //#endregion
36377
- //#region ../../src/scrawl/ink/strokeRenderer.ts
36397
+ //#region src/renderer/shapes/strokeRenderer.ts
36378
36398
  var strokeZ = (stroke) => stroke.tool === "highlighter" ? HIGHLIGHT_Z : INK_Z;
36379
36399
  /**
36380
36400
  * Derives ink meshes from the document by subscription — the document never
@@ -36507,7 +36527,7 @@ function syncMatrix(mesh, stroke) {
36507
36527
  mesh.matrixWorldNeedsUpdate = true;
36508
36528
  }
36509
36529
  //#endregion
36510
- //#region ../../src/scrawl/scene-objects/markerProp.ts
36530
+ //#region src/renderer/shapes/markerProp.ts
36511
36531
  var BODY_LENGTH = 11;
36512
36532
  var BODY_RADIUS = .85;
36513
36533
  var TIP_LENGTH = 1.6;
@@ -36603,7 +36623,7 @@ var MarkerProp = class {
36603
36623
  }
36604
36624
  };
36605
36625
  //#endregion
36606
- //#region ../../src/scrawl/scene-objects/noteGeometry.ts
36626
+ //#region src/renderer/shapes/noteGeometry.ts
36607
36627
  /**
36608
36628
  * Height of the sheet above the board at (u, v) ∈ [0,1]².
36609
36629
  * v = 1 is the adhesive edge (always 0); u = 1 is the peel corner side.
@@ -36647,7 +36667,7 @@ function applyCurl(geometry, curl, peel) {
36647
36667
  color.needsUpdate = true;
36648
36668
  }
36649
36669
  //#endregion
36650
- //#region ../../src/scrawl/scene-objects/stickyNotes.ts
36670
+ //#region src/renderer/shapes/stickyNotes.ts
36651
36671
  var TEXTURE_SIZE = 512;
36652
36672
  var FOCUS_COLOR$4 = 2450411;
36653
36673
  var CURL = .3;
@@ -43223,7 +43243,7 @@ SYNCABLE_PROPS.forEach((prop) => {
43223
43243
  new Box3();
43224
43244
  new Color();
43225
43245
  //#endregion
43226
- //#region ../../src/scrawl/scene-objects/tables.ts
43246
+ //#region src/renderer/shapes/tables.ts
43227
43247
  var FONT_URL$2 = "/fonts/inter-medium.woff";
43228
43248
  var TABLE_Z = .012;
43229
43249
  var HEADER_Z = .014;
@@ -43572,7 +43592,7 @@ var TablesRenderer = class {
43572
43592
  }
43573
43593
  };
43574
43594
  //#endregion
43575
- //#region ../../src/scrawl/scene-objects/textBlocks.ts
43595
+ //#region src/renderer/text/textBlocks.ts
43576
43596
  var FONT_URL$1 = "/fonts/inter-medium.woff";
43577
43597
  var TEXT_Z = .02;
43578
43598
  var FOCUS_COLOR$2 = 2450411;
@@ -43676,7 +43696,7 @@ var TextBlocksRenderer = class {
43676
43696
  }
43677
43697
  };
43678
43698
  //#endregion
43679
- //#region ../../src/scrawl/scene-objects/comments.ts
43699
+ //#region src/renderer/shapes/comments.ts
43680
43700
  var PIN_Z = .04;
43681
43701
  var PIN_WIDTH = 3.6;
43682
43702
  var PIN_HEIGHT = 3.6;
@@ -43844,7 +43864,7 @@ function drawBubblePath(ctx, x, y, w, h, r, tailX, tailY, tailW) {
43844
43864
  ctx.closePath();
43845
43865
  }
43846
43866
  //#endregion
43847
- //#region ../../src/scrawl/scene-objects/images.ts
43867
+ //#region src/renderer/shapes/images.ts
43848
43868
  var IMAGE_Z = .015;
43849
43869
  var FRAME_BORDER = .4;
43850
43870
  var FOCUS_COLOR$1 = 2450411;
@@ -44054,7 +44074,7 @@ function applyPlaceholderTexture(visual, label) {
44054
44074
  visual.imageMesh.material.needsUpdate = true;
44055
44075
  }
44056
44076
  //#endregion
44057
- //#region ../../src/scrawl/scene-objects/kitchenTimers.ts
44077
+ //#region src/renderer/shapes/kitchenTimers.ts
44058
44078
  var FACE_SIZE = 256;
44059
44079
  var BODY_COLOR = 14753096;
44060
44080
  var KNOB_COLOR = 16448250;
@@ -44252,7 +44272,7 @@ function renderTimerFace(timer, canvas, now = Date.now()) {
44252
44272
  return c;
44253
44273
  }
44254
44274
  //#endregion
44255
- //#region ../../src/scrawl/scene-objects/customObjects.ts
44275
+ //#region src/renderer/shapes/customObjects.ts
44256
44276
  var FONT_URL = "/fonts/inter-medium.woff";
44257
44277
  var CUSTOM_Z = .02;
44258
44278
  var FOCUS_COLOR = 2450411;
@@ -44614,7 +44634,7 @@ function disposeObject3D(obj) {
44614
44634
  });
44615
44635
  }
44616
44636
  //#endregion
44617
- //#region ../../src/scrawl/tools/hitTest.ts
44637
+ //#region src/interaction/pointer/hitTest.ts
44618
44638
  /** Topmost (most recently added) stroke under the point, or null. */
44619
44639
  function hitStroke(doc, index, p, slop) {
44620
44640
  const candidates = index.query(p.x - slop, p.y - slop, p.x + slop, p.y + slop);
@@ -44655,7 +44675,7 @@ function distToSegmentSq(p, a, b) {
44655
44675
  return dx * dx + dy * dy;
44656
44676
  }
44657
44677
  //#endregion
44658
- //#region ../../src/scrawl/tools/eraserTool.ts
44678
+ //#region src/interaction/tools/eraserTool.ts
44659
44679
  var DECAY_PER_PASS = .4;
44660
44680
  /** A point can take another decay pass after this long — rubbing works. */
44661
44681
  var REARM_MS = 250;
@@ -44783,7 +44803,7 @@ function partitionByErasure(points) {
44783
44803
  return runs;
44784
44804
  }
44785
44805
  //#endregion
44786
- //#region ../../src/scrawl/tools/noteDrag.ts
44806
+ //#region src/interaction/tools/noteDrag.ts
44787
44807
  /**
44788
44808
  * Drag the focused note in X/Y. The grab is resolved on the note's own
44789
44809
  * z-plane, not the board, so the note doesn't jump under a tilted ray.
@@ -44850,7 +44870,7 @@ var NoteDragState = class {
44850
44870
  }
44851
44871
  };
44852
44872
  //#endregion
44853
- //#region ../../src/scrawl/tools/imageDrag.ts
44873
+ //#region src/interaction/tools/imageDrag.ts
44854
44874
  /** Drag the focused image in X/Y; one command per drag. */
44855
44875
  var ImageDragState = class {
44856
44876
  constructor(ctx) {
@@ -44902,7 +44922,7 @@ var ImageDragState = class {
44902
44922
  }
44903
44923
  };
44904
44924
  //#endregion
44905
- //#region ../../src/scrawl/tools/timerDrag.ts
44925
+ //#region src/interaction/tools/timerDrag.ts
44906
44926
  /** Drag the focused timer in X/Y; one command per drag. */
44907
44927
  var TimerDragState = class {
44908
44928
  constructor(ctx) {
@@ -44954,7 +44974,7 @@ var TimerDragState = class {
44954
44974
  }
44955
44975
  };
44956
44976
  //#endregion
44957
- //#region ../../src/scrawl/tools/shapes.ts
44977
+ //#region src/interaction/tools/shapeGeometry.ts
44958
44978
  /** Uniform synthetic pressure; the ink shader supplies the hand-drawn feel. */
44959
44979
  var PRESSURE = .7;
44960
44980
  var MIN_ELLIPSE_SEGMENTS = 24;
@@ -45143,7 +45163,7 @@ function snappedEnd(a, b) {
45143
45163
  };
45144
45164
  }
45145
45165
  //#endregion
45146
- //#region ../../src/scrawl/tools/shapeTool.ts
45166
+ //#region src/interaction/tools/shapeTool.ts
45147
45167
  var MIN_DRAG_PX$1 = 4;
45148
45168
  /** Drag out a shape; live preview through the real ink material. */
45149
45169
  var ShapeTool = class {
@@ -45174,7 +45194,7 @@ var ShapeTool = class {
45174
45194
  const strokes = this.polylines.map((points) => ({
45175
45195
  id: crypto.randomUUID(),
45176
45196
  color: this.ctx.inkColor(),
45177
- baseWidth: this.ctx.baseWidth(),
45197
+ baseWidth: this.ctx.shapeStrokeWidth(),
45178
45198
  points
45179
45199
  }));
45180
45200
  this.ctx.clusters.assign(strokes[0]);
@@ -45208,13 +45228,13 @@ var ShapeTool = class {
45208
45228
  this.previews[i].visible = polyline !== void 0;
45209
45229
  if (polyline) {
45210
45230
  this.previews[i].geometry.dispose();
45211
- this.previews[i].geometry = buildRibbonGeometry(polyline, this.ctx.baseWidth());
45231
+ this.previews[i].geometry = buildRibbonGeometry(polyline, this.ctx.shapeStrokeWidth());
45212
45232
  }
45213
45233
  }
45214
45234
  }
45215
45235
  };
45216
45236
  //#endregion
45217
- //#region ../../src/scrawl/tools/tableDrag.ts
45237
+ //#region src/interaction/tools/tableDrag.ts
45218
45238
  function getHandleCursor(handle) {
45219
45239
  switch (handle) {
45220
45240
  case "nw":
@@ -45334,7 +45354,7 @@ var TableDragState = class {
45334
45354
  }
45335
45355
  };
45336
45356
  //#endregion
45337
- //#region ../../src/scrawl/tools/tableTool.ts
45357
+ //#region src/interaction/tools/tableTool.ts
45338
45358
  var MIN_DRAG_PX = 6;
45339
45359
  var TableTool = class {
45340
45360
  constructor(ctx) {
@@ -45465,7 +45485,7 @@ var TableTool = class {
45465
45485
  }
45466
45486
  };
45467
45487
  //#endregion
45468
- //#region ../../src/scrawl/tools/commentTool.ts
45488
+ //#region src/interaction/tools/commentTool.ts
45469
45489
  /** Click on the board to drop a new comment pin or open an existing one. */
45470
45490
  var CommentTool = class {
45471
45491
  constructor(ctx) {
@@ -45500,7 +45520,7 @@ var CommentTool = class {
45500
45520
  }
45501
45521
  };
45502
45522
  //#endregion
45503
- //#region ../../src/scrawl/tools/textDrag.ts
45523
+ //#region src/interaction/tools/textDrag.ts
45504
45524
  /** Drag the focused text block in X/Y; one command per drag. */
45505
45525
  var TextDragState = class {
45506
45526
  constructor(ctx) {
@@ -45552,7 +45572,7 @@ var TextDragState = class {
45552
45572
  }
45553
45573
  };
45554
45574
  //#endregion
45555
- //#region ../../src/scrawl/tools/fsm.ts
45575
+ //#region src/interaction/tools/fsm.ts
45556
45576
  var ToolStateMachine = class {
45557
45577
  constructor() {
45558
45578
  this.states = /* @__PURE__ */ new Map();
@@ -45754,7 +45774,7 @@ var IdleState = class {
45754
45774
  }
45755
45775
  };
45756
45776
  //#endregion
45757
- //#region ../../src/scrawl/tools/gizmo.ts
45777
+ //#region src/renderer/selection/gizmo.ts
45758
45778
  var GIZMO_Z = .05;
45759
45779
  var HANDLE_PX = 9;
45760
45780
  var HANDLE_HIT_PX = 12;
@@ -45985,7 +46005,7 @@ function boxesEqual(a, b) {
45985
46005
  return a.minX === b.minX && a.minY === b.minY && a.maxX === b.maxX && a.maxY === b.maxY;
45986
46006
  }
45987
46007
  //#endregion
45988
- //#region ../../src/scrawl/ink/pressure.ts
46008
+ //#region src/interaction/tools/pressure.ts
45989
46009
  var SIM_MAX = 1;
45990
46010
  var SIM_MIN = .3;
45991
46011
  var SMOOTHING = .25;
@@ -46007,7 +46027,7 @@ function clamp(v, lo, hi) {
46007
46027
  return Math.min(hi, Math.max(lo, v));
46008
46028
  }
46009
46029
  //#endregion
46010
- //#region ../../src/scrawl/ink/strokeBuilder.ts
46030
+ //#region src/interaction/tools/strokeBuilder.ts
46011
46031
  var StrokeBuilder = class {
46012
46032
  constructor() {
46013
46033
  this.points = [];
@@ -46057,7 +46077,7 @@ function catmullRom(v0, v1, v2, v3, t) {
46057
46077
  return .5 * (2 * v1 + (-v0 + v2) * t + (2 * v0 - 5 * v1 + 4 * v2 - v3) * t2 + (-v0 + 3 * v1 - 3 * v2 + v3) * t3);
46058
46078
  }
46059
46079
  //#endregion
46060
- //#region ../../src/scrawl/tools/markerTool.ts
46080
+ //#region src/interaction/tools/markerTool.ts
46061
46081
  /** Sample spacing while drawing, in screen pixels. */
46062
46082
  var MIN_SAMPLE_PX = 1.5;
46063
46083
  /** A highlighter is a chisel tip: near-constant width, little pressure play. */
@@ -46160,7 +46180,7 @@ var MarkerTool = class {
46160
46180
  }
46161
46181
  };
46162
46182
  //#endregion
46163
- //#region ../../src/scrawl/tools/orbitTool.ts
46183
+ //#region src/interaction/tools/orbitTool.ts
46164
46184
  /**
46165
46185
  * Orbit-preview (build prompt §4.2): alt-drag tilts the camera off-axis to
46166
46186
  * read the board's material and depth; releasing eases back to the working
@@ -46195,7 +46215,7 @@ var OrbitTool = class {
46195
46215
  }
46196
46216
  };
46197
46217
  //#endregion
46198
- //#region ../../src/scrawl/tools/panTool.ts
46218
+ //#region src/interaction/tools/panTool.ts
46199
46219
  /** Grab-pan: content follows the pointer. */
46200
46220
  var PanTool = class {
46201
46221
  constructor(ctx) {
@@ -46225,7 +46245,7 @@ var PanTool = class {
46225
46245
  }
46226
46246
  };
46227
46247
  //#endregion
46228
- //#region ../../src/scrawl/tools/selection.ts
46248
+ //#region src/renderer/selection/selection.ts
46229
46249
  /** The set of selected stroke ids. Drops ids the document no longer has. */
46230
46250
  var SelectionManager = class {
46231
46251
  constructor(doc) {
@@ -46276,7 +46296,7 @@ var SelectionManager = class {
46276
46296
  }
46277
46297
  };
46278
46298
  //#endregion
46279
- //#region ../../src/scrawl/tools/selectTool.ts
46299
+ //#region src/interaction/tools/selectTool.ts
46280
46300
  var HIT_SLOP_PX = 4;
46281
46301
  var CLICK_TOLERANCE_PX = 3;
46282
46302
  var MIN_SCALE = .05;
@@ -46492,7 +46512,7 @@ function safeRatio(numerator, denominator) {
46492
46512
  return raw;
46493
46513
  }
46494
46514
  //#endregion
46495
- //#region ../../src/scrawl/tools/customTool.ts
46515
+ //#region src/interaction/tools/customTool.ts
46496
46516
  function extensionIdFromToolId(toolId) {
46497
46517
  const slash = toolId.indexOf("/");
46498
46518
  return slash === -1 ? toolId : toolId.slice(0, slash);
@@ -46607,12 +46627,19 @@ var ExtensionCommandBatch = class {
46607
46627
  }
46608
46628
  };
46609
46629
  //#endregion
46610
- //#region ../../src/scrawl/ScrawlEngine.ts
46630
+ //#region src/composition/ScrawlEngine.ts
46611
46631
  /** Marker stroke width in board units (~1% of the default view height). */
46612
46632
  var BASE_WIDTH = .5;
46613
46633
  /** Highlighter chisel width — wide enough to cover a line of handwriting. */
46614
46634
  var HIGHLIGHT_WIDTH = 2.2;
46615
46635
  /**
46636
+ * Shape (rectangle, ellipse, arrow, ...) border width in board units.
46637
+ * Deliberately thinner than BASE_WIDTH: a shape border reads as a crisp,
46638
+ * structural line, not an expressive marker stroke — this is also the
46639
+ * fallback when no Host theme configures ScrawlTheme.shapeStrokeWidth.
46640
+ */
46641
+ var DEFAULT_SHAPE_STROKE_WIDTH = .15;
46642
+ /**
46616
46643
  * Identity of a rendered selection badge: which item, its lock state, and
46617
46644
  * where it sits on screen (rounded to the pixel, so sub-pixel camera drift
46618
46645
  * does not churn React).
@@ -46633,10 +46660,11 @@ function emptyExtensionRegistry() {
46633
46660
  * React never reaches past this class.
46634
46661
  */
46635
46662
  var ScrawlEngine = class {
46636
- constructor(canvas, documentId$1, extensionRegistry = emptyExtensionRegistry(), assetController = new AssetController(void 0), boardTheme = DEFAULT_BOARD_SURFACE_THEME) {
46663
+ constructor(canvas, documentId$1, extensionRegistry = emptyExtensionRegistry(), assetController = new AssetController(void 0), boardTheme = DEFAULT_BOARD_SURFACE_THEME, shapeStrokeWidth = DEFAULT_SHAPE_STROKE_WIDTH) {
46637
46664
  this.canvas = canvas;
46638
46665
  this.extensionRegistry = extensionRegistry;
46639
46666
  this.boardTheme = boardTheme;
46667
+ this.shapeStrokeWidth = shapeStrokeWidth;
46640
46668
  this.onToolChange = null;
46641
46669
  this.onZoomChange = null;
46642
46670
  this.onAuditEvent = null;
@@ -46731,6 +46759,7 @@ var ScrawlEngine = class {
46731
46759
  setTool: (t) => this.setTool(t),
46732
46760
  inkColor: () => this.tool === "highlighter" ? this.highlightColor : this.color,
46733
46761
  baseWidth: () => this.tool === "highlighter" ? HIGHLIGHT_WIDTH : BASE_WIDTH,
46762
+ shapeStrokeWidth: () => this.shapeStrokeWidth,
46734
46763
  activeTool: () => this.tool,
46735
46764
  isSpaceHeld: () => this.spaceHeld,
46736
46765
  pointerToBoard: (e) => pointerToBoard(e.clientX, e.clientY, canvas, this.scene.cameraRig.camera),
@@ -46995,6 +47024,10 @@ var ScrawlEngine = class {
46995
47024
  this.boardTheme = theme;
46996
47025
  this.scene.applyBoardTheme(theme);
46997
47026
  }
47027
+ /** Live update of the shape tool's border width (board units). Already-drawn shapes keep the width they were drawn with. */
47028
+ setShapeStrokeWidth(width) {
47029
+ this.shapeStrokeWidth = width;
47030
+ }
46998
47031
  /**
46999
47032
  * Snap to a peer's camera. No-op while drawing so pointer-to-board
47000
47033
  * mapping does not warp the in-progress stroke.
@@ -48056,7 +48089,7 @@ var ScrawlEngine = class {
48056
48089
  }
48057
48090
  };
48058
48091
  //#endregion
48059
- //#region src/controller-internal.ts
48092
+ //#region src/composition/controller-internal.ts
48060
48093
  function createBoardController(options) {
48061
48094
  return createBoardControllerWithEngine(options);
48062
48095
  }
@@ -48074,9 +48107,12 @@ function createBoardControllerWithEngine(options, existingEngine) {
48074
48107
  color: options.boardTheme?.surface ?? DEFAULT_BOARD_SURFACE_THEME.color,
48075
48108
  gridMode: options.boardTheme?.gridMode ?? DEFAULT_BOARD_SURFACE_THEME.gridMode,
48076
48109
  gridColor: options.boardTheme?.gridColor ?? DEFAULT_BOARD_SURFACE_THEME.gridColor,
48077
- gridSpacing: options.boardTheme?.gridSpacing ?? DEFAULT_BOARD_SURFACE_THEME.gridSpacing
48110
+ gridSpacing: options.boardTheme?.gridSpacing ?? DEFAULT_BOARD_SURFACE_THEME.gridSpacing,
48111
+ gridLineWidth: options.boardTheme?.gridLineWidth ?? DEFAULT_BOARD_SURFACE_THEME.gridLineWidth,
48112
+ surfaceTexture: options.boardTheme?.surfaceTexture ?? DEFAULT_BOARD_SURFACE_THEME.surfaceTexture
48078
48113
  };
48079
- const engine = existingEngine ?? (options.canvas ? new ScrawlEngine(options.canvas, options.document.id, extensionRegistry, assetController, currentBoardTheme) : null);
48114
+ let currentShapeStrokeWidth = options.boardTheme?.shapeStrokeWidth ?? .15;
48115
+ const engine = existingEngine ?? (options.canvas ? new ScrawlEngine(options.canvas, options.document.id, extensionRegistry, assetController, currentBoardTheme, currentShapeStrokeWidth) : null);
48080
48116
  const document = engine?.document ?? new BoardDocument(documentId(options.document.id));
48081
48117
  const history = engine?.history ?? new History(document);
48082
48118
  const createId = options.createId ?? (() => crypto.randomUUID());
@@ -48565,9 +48601,15 @@ function createBoardControllerWithEngine(options, existingEngine) {
48565
48601
  color: theme.surface ?? currentBoardTheme.color,
48566
48602
  gridMode: theme.gridMode ?? currentBoardTheme.gridMode,
48567
48603
  gridColor: theme.gridColor ?? currentBoardTheme.gridColor,
48568
- gridSpacing: theme.gridSpacing ?? currentBoardTheme.gridSpacing
48604
+ gridSpacing: theme.gridSpacing ?? currentBoardTheme.gridSpacing,
48605
+ gridLineWidth: theme.gridLineWidth ?? currentBoardTheme.gridLineWidth,
48606
+ surfaceTexture: theme.surfaceTexture ?? currentBoardTheme.surfaceTexture
48569
48607
  };
48570
48608
  engine?.applyBoardTheme(currentBoardTheme);
48609
+ if (theme.shapeStrokeWidth !== void 0) {
48610
+ currentShapeStrokeWidth = theme.shapeStrokeWidth;
48611
+ engine?.setShapeStrokeWidth(currentShapeStrokeWidth);
48612
+ }
48571
48613
  } },
48572
48614
  view: {
48573
48615
  fit() {
@@ -49338,7 +49380,7 @@ function createLocalBoard(options) {
49338
49380
  };
49339
49381
  }
49340
49382
  //#endregion
49341
- //#region src/theme.ts
49383
+ //#region src/ui/theme.ts
49342
49384
  var scrawlThemePresets = Object.freeze({
49343
49385
  light: Object.freeze({
49344
49386
  surface: "#ffffff",
@@ -49365,9 +49407,12 @@ var scrawlThemePresets = Object.freeze({
49365
49407
  motionEasing: "cubic-bezier(0.2, 0, 0, 1)",
49366
49408
  density: "comfortable",
49367
49409
  boardSurface: "#f5f5f3",
49410
+ boardSurfaceTexture: "flat",
49368
49411
  gridMode: "none",
49369
49412
  gridColor: "#deded8",
49370
- gridSpacing: 5
49413
+ gridSpacing: 5,
49414
+ gridLineWidth: 1,
49415
+ shapeStrokeWidth: .15
49371
49416
  }),
49372
49417
  dark: Object.freeze({
49373
49418
  surface: "#171816",
@@ -49394,9 +49439,12 @@ var scrawlThemePresets = Object.freeze({
49394
49439
  motionEasing: "cubic-bezier(0.2, 0, 0, 1)",
49395
49440
  density: "comfortable",
49396
49441
  boardSurface: "#232420",
49442
+ boardSurfaceTexture: "flat",
49397
49443
  gridMode: "none",
49398
49444
  gridColor: "#33352f",
49399
- gridSpacing: 5
49445
+ gridSpacing: 5,
49446
+ gridLineWidth: 1,
49447
+ shapeStrokeWidth: .15
49400
49448
  })
49401
49449
  });
49402
49450
  var variableNames = {
@@ -49424,9 +49472,12 @@ var variableNames = {
49424
49472
  motionEasing: "--scrawl-motion-easing",
49425
49473
  density: "--scrawl-density",
49426
49474
  boardSurface: "--scrawl-board-surface",
49475
+ boardSurfaceTexture: "--scrawl-board-surface-texture",
49427
49476
  gridMode: "--scrawl-grid-mode",
49428
49477
  gridColor: "--scrawl-grid-color",
49429
- gridSpacing: "--scrawl-grid-spacing"
49478
+ gridSpacing: "--scrawl-grid-spacing",
49479
+ gridLineWidth: "--scrawl-grid-line-width",
49480
+ shapeStrokeWidth: "--scrawl-shape-stroke-width"
49430
49481
  };
49431
49482
  var colorTokens = /* @__PURE__ */ new Set([
49432
49483
  "surface",
@@ -49482,10 +49533,22 @@ function validateScrawlTheme(theme) {
49482
49533
  token,
49483
49534
  message: "Expected none, line, or dot"
49484
49535
  });
49536
+ else if (token === "boardSurfaceTexture" && value !== "flat" && value !== "textured") diagnostics.push({
49537
+ token,
49538
+ message: "Expected flat or textured"
49539
+ });
49485
49540
  else if (token === "gridSpacing" && (!finiteNumber(value) || value < .5 || value > 50)) diagnostics.push({
49486
49541
  token,
49487
49542
  message: "Expected spacing 0.5-50 board units"
49488
49543
  });
49544
+ else if (token === "gridLineWidth" && (!finiteNumber(value) || value < .5 || value > 8)) diagnostics.push({
49545
+ token,
49546
+ message: "Expected line width 0.5-8px"
49547
+ });
49548
+ else if (token === "shapeStrokeWidth" && (!finiteNumber(value) || value < .01 || value > 5)) diagnostics.push({
49549
+ token,
49550
+ message: "Expected stroke width 0.01-5 board units"
49551
+ });
49489
49552
  else if ((token.endsWith("Family") || token.startsWith("elevation") || token === "motionEasing") && (typeof value !== "string" || !value.trim() || /[;{}]/.test(value))) diagnostics.push({
49490
49553
  token,
49491
49554
  message: "Expected a safe CSS value"
@@ -49556,12 +49619,12 @@ function luminance(color) {
49556
49619
  return channels[0] * .2126 + channels[1] * .7152 + channels[2] * .0722;
49557
49620
  }
49558
49621
  function formatValue(token, value) {
49559
- if (token === "baseFontSize" || token === "controlRadius" || token === "panelRadius") return `${value}px`;
49622
+ if (token === "baseFontSize" || token === "controlRadius" || token === "panelRadius" || token === "gridLineWidth") return `${value}px`;
49560
49623
  if (token === "motionDuration") return `${value}ms`;
49561
49624
  return String(value);
49562
49625
  }
49563
49626
  //#endregion
49564
- //#region src/inline-editing.tsx
49627
+ //#region src/ui/dialogs/inline-editing.tsx
49565
49628
  /** Inline note, text, and table-cell editors — driven entirely by public controller events and commands. */
49566
49629
  function InlineEditors({ controller, renderPortal }) {
49567
49630
  const [editor, setEditor] = useState(null);
@@ -49792,7 +49855,7 @@ function InlineEditors({ controller, renderPortal }) {
49792
49855
  }))] });
49793
49856
  }
49794
49857
  //#endregion
49795
- //#region src/style-shelf.tsx
49858
+ //#region src/ui/toolbar/style-shelf.tsx
49796
49859
  var ERASER_RADII = [
49797
49860
  ["Fine", 1],
49798
49861
  ["Medium", 2],
@@ -49955,7 +50018,7 @@ function StyleShelf({ controller, snapshot }) {
49955
50018
  });
49956
50019
  }
49957
50020
  //#endregion
49958
- //#region src/default-ui.tsx
50021
+ //#region src/ui/default-ui.tsx
49959
50022
  var PRIMARY_TOOLS = [
49960
50023
  {
49961
50024
  tool: "select",
@@ -50484,7 +50547,7 @@ function download(blob, name) {
50484
50547
  URL.revokeObjectURL(url);
50485
50548
  }
50486
50549
  //#endregion
50487
- //#region src/presence.tsx
50550
+ //#region src/ui/panels/presence.tsx
50488
50551
  var labelStyle = {
50489
50552
  borderRadius: 999,
50490
50553
  color: "#fff",
@@ -50644,9 +50707,12 @@ function ScrawlProvider({ controller, children, preset = "light", theme, portalC
50644
50707
  useEffect(() => {
50645
50708
  controller.boardTheme.set({
50646
50709
  surface: resolved.values.boardSurface,
50710
+ surfaceTexture: resolved.values.boardSurfaceTexture,
50647
50711
  gridMode: resolved.values.gridMode,
50648
50712
  gridColor: resolved.values.gridColor,
50649
- gridSpacing: resolved.values.gridSpacing
50713
+ gridSpacing: resolved.values.gridSpacing,
50714
+ gridLineWidth: resolved.values.gridLineWidth,
50715
+ shapeStrokeWidth: resolved.values.shapeStrokeWidth
50650
50716
  });
50651
50717
  }, [controller, resolved]);
50652
50718
  useDeferredDisposal(controller, disposeOnUnmount);
@@ -50699,9 +50765,12 @@ function Scrawl({ children, preset, theme, portalContainer, className, style, on
50699
50765
  const resolved = resolveScrawlTheme(preset, theme);
50700
50766
  const boardTheme = {
50701
50767
  surface: resolved.values.boardSurface,
50768
+ surfaceTexture: resolved.values.boardSurfaceTexture,
50702
50769
  gridMode: resolved.values.gridMode,
50703
50770
  gridColor: resolved.values.gridColor,
50704
- gridSpacing: resolved.values.gridSpacing
50771
+ gridSpacing: resolved.values.gridSpacing,
50772
+ gridLineWidth: resolved.values.gridLineWidth,
50773
+ shapeStrokeWidth: resolved.values.shapeStrokeWidth
50705
50774
  };
50706
50775
  controller = createBoardController({
50707
50776
  ...options,