@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/index.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
  /** Hard ceiling on a reference's own wire length — independent of any resource limit below. */
7
7
  var ASSET_REF_MAX_BYTES = 512;
@@ -41,7 +41,7 @@ function clampAssetCacheBytes(value) {
41
41
  return Math.min(ASSET_CACHE_BYTES_MAX, Math.max(ASSET_CACHE_BYTES_MIN, Math.floor(value)));
42
42
  }
43
43
  //#endregion
44
- //#region src/core-internal/identifiers.ts
44
+ //#region src/core/document/identifiers.ts
45
45
  function documentId(value) {
46
46
  if (!value) throw new TypeError("DocumentId cannot be empty");
47
47
  return value;
@@ -51,7 +51,7 @@ function strokeId(value) {
51
51
  return value;
52
52
  }
53
53
  //#endregion
54
- //#region src/document-schema.ts
54
+ //#region src/core/document/document-schema.ts
55
55
  var CURRENT_DOCUMENT_SCHEMA_VERSION = 1;
56
56
  var DocumentRecoveryError = class extends Error {
57
57
  constructor(code, message, originalBytes, path) {
@@ -259,7 +259,7 @@ function validation(message, path) {
259
259
  return new DocumentRecoveryError("DOCUMENT_VALIDATION_FAILED", message, void 0, path);
260
260
  }
261
261
  //#endregion
262
- //#region src/core-internal/affine2d.ts
262
+ //#region src/core/shapes/affine2d.ts
263
263
  var IDENTITY = [
264
264
  1,
265
265
  0,
@@ -343,7 +343,7 @@ function avgScale(m) {
343
343
  return (Math.hypot(m[0], m[1]) + Math.hypot(m[2], m[3])) / 2;
344
344
  }
345
345
  //#endregion
346
- //#region src/core-internal/boardSearch.ts
346
+ //#region src/core/document/boardSearch.ts
347
347
  function hay(s) {
348
348
  return s.toLowerCase();
349
349
  }
@@ -408,7 +408,7 @@ function searchBoard(query, board) {
408
408
  return hits;
409
409
  }
410
410
  //#endregion
411
- //#region src/core-internal/types.ts
411
+ //#region src/core/shapes/types.ts
412
412
  /** Erasure level at which a point counts as fully erased. */
413
413
  var ERASE_THRESHOLD = .95;
414
414
  function cloneStroke(stroke) {
@@ -484,7 +484,7 @@ function cloneImage(img) {
484
484
  return { ...img };
485
485
  }
486
486
  //#endregion
487
- //#region src/core-internal/clusterStore.ts
487
+ //#region src/core/document/clusterStore.ts
488
488
  /** A stroke joins a cluster last written to within this window at full reach. */
489
489
  var RECENT_MS = 8e3;
490
490
  /** Older clusters are still joinable, at half the gap threshold. */
@@ -655,7 +655,7 @@ function clamp$3(v, lo, hi) {
655
655
  return Math.min(hi, Math.max(lo, v));
656
656
  }
657
657
  //#endregion
658
- //#region src/core-internal/extensionTypes.ts
658
+ //#region src/core/shapes/extensionTypes.ts
659
659
  function cloneCustomObject(object) {
660
660
  return {
661
661
  ...object,
@@ -665,7 +665,7 @@ function cloneCustomObject(object) {
665
665
  };
666
666
  }
667
667
  //#endregion
668
- //#region src/core-internal/kitchenTimer.ts
668
+ //#region src/core/shapes/kitchenTimer.ts
669
669
  var TIMER_DEFAULT_SIZE = 12;
670
670
  var TIMER_DEFAULT_DURATION_MS = 300 * 1e3;
671
671
  var TIMER_PRESETS_MS = [
@@ -722,7 +722,7 @@ function formatTimer(ms) {
722
722
  return `${Math.floor(total / 60)}:${(total % 60).toString().padStart(2, "0")}`;
723
723
  }
724
724
  //#endregion
725
- //#region src/core-internal/itemLock.ts
725
+ //#region src/core/shapes/itemLock.ts
726
726
  /**
727
727
  * Apply or clear a lock in place. Unlock always drops the holder so a stale
728
728
  * name cannot linger on an unlocked item.
@@ -759,7 +759,7 @@ function serializeLock(item) {
759
759
  };
760
760
  }
761
761
  //#endregion
762
- //#region src/core-internal/document.ts
762
+ //#region src/core/document/document.ts
763
763
  /**
764
764
  * The one place a live Stroke becomes its wire representation — in
765
765
  * particular, points collapse from {x,y,pressure,erase?} objects into
@@ -1318,7 +1318,7 @@ function computeBBox(stroke) {
1318
1318
  return world;
1319
1319
  }
1320
1320
  //#endregion
1321
- //#region src/core-internal/commands.ts
1321
+ //#region src/core/commands/commands.ts
1322
1322
  /** One drawing action — a marker stroke, or a shape's strokes as one unit. */
1323
1323
  var AddStrokesCommand = class {
1324
1324
  constructor(strokes) {
@@ -1660,7 +1660,7 @@ var LockItemsCommand = class {
1660
1660
  }
1661
1661
  };
1662
1662
  //#endregion
1663
- //#region src/core-internal/history.ts
1663
+ //#region src/core/history/history.ts
1664
1664
  var LIMIT = 200;
1665
1665
  var History = class {
1666
1666
  constructor(doc) {
@@ -1721,7 +1721,7 @@ var History = class {
1721
1721
  }
1722
1722
  };
1723
1723
  //#endregion
1724
- //#region src/core-internal/ops.ts
1724
+ //#region src/core/events/ops.ts
1725
1725
  var ADDED = [
1726
1726
  ["added", "strokes"],
1727
1727
  ["notesAdded", "notes"],
@@ -1775,7 +1775,7 @@ function changeToOps(change, restoring = false) {
1775
1775
  return ops;
1776
1776
  }
1777
1777
  //#endregion
1778
- //#region src/core-internal/ribbonEdges.ts
1778
+ //#region src/core/shapes/ribbonEdges.ts
1779
1779
  var MIN_WIDTH_FACTOR = .35;
1780
1780
  var END_TAPER = .55;
1781
1781
  function ribbonEdges(points, baseWidth) {
@@ -1807,7 +1807,7 @@ function ribbonEdges(points, baseWidth) {
1807
1807
  return out;
1808
1808
  }
1809
1809
  //#endregion
1810
- //#region src/core-internal/spatialIndex.ts
1810
+ //#region src/core/document/spatialIndex.ts
1811
1811
  var CELL = 8;
1812
1812
  var SpatialIndex = class {
1813
1813
  constructor(doc) {
@@ -1880,7 +1880,7 @@ function cellsOf(box) {
1880
1880
  return keys;
1881
1881
  }
1882
1882
  //#endregion
1883
- //#region src/core-internal/stamps.ts
1883
+ //#region src/core/shapes/stamps.ts
1884
1884
  var STAMP_SIZE = 6;
1885
1885
  var STAMPS = [
1886
1886
  {
@@ -1929,7 +1929,7 @@ function isStampKind(value) {
1929
1929
  return typeof value === "string" && STAMPS.some((s) => s.kind === value);
1930
1930
  }
1931
1931
  //#endregion
1932
- //#region src/core-internal/presence.ts
1932
+ //#region src/collaboration/presence/presence.ts
1933
1933
  /** Keep beacons clear of typical chrome at the top and bottom of the Board. */
1934
1934
  var BEACON_INSET = {
1935
1935
  top: 72,
@@ -1965,7 +1965,7 @@ function clamp$2(value, min, max) {
1965
1965
  return Math.min(max, Math.max(min, value));
1966
1966
  }
1967
1967
  //#endregion
1968
- //#region src/core-internal/svg.ts
1968
+ //#region src/persistence/serialization/svg.ts
1969
1969
  /** Matches the key light used by the board shader and note shadows. */
1970
1970
  var LIGHT = {
1971
1971
  x: -.25,
@@ -2336,7 +2336,7 @@ function fmt(n) {
2336
2336
  var SDK_PACKAGE_NAME = "@scrawl-board/board";
2337
2337
  var SDK_DEVELOPMENT_VERSION = "0.0.0-development";
2338
2338
  //#endregion
2339
- //#region src/core-internal/sceneGeometry.ts
2339
+ //#region src/core/shapes/sceneGeometry.ts
2340
2340
  var EMPTY_BOUNDS = {
2341
2341
  x: 0,
2342
2342
  y: 0,
@@ -2400,7 +2400,7 @@ function textCorners(x, y, text, fontSize, align, m) {
2400
2400
  return rectCorners(x + (align === "center" ? -width / 2 : align === "end" ? -width : 0), y, width, height, m);
2401
2401
  }
2402
2402
  //#endregion
2403
- //#region src/core-internal/customObjects.ts
2403
+ //#region src/core/shapes/customObjects.ts
2404
2404
  var FALLBACK_LABEL_MAX_LENGTH = 80;
2405
2405
  function toReadonly(object) {
2406
2406
  return object;
@@ -2529,7 +2529,7 @@ function extensionIdFromType(type) {
2529
2529
  return slash === -1 ? type : type.slice(0, slash);
2530
2530
  }
2531
2531
  //#endregion
2532
- //#region src/core-internal/extensionRegistry.ts
2532
+ //#region src/core/shapes/extensionRegistry.ts
2533
2533
  function buildExtensionRegistry(extensions) {
2534
2534
  const list = extensions ?? [];
2535
2535
  const diagnostics = [];
@@ -2626,7 +2626,7 @@ function isWellFormedNamespacedId(id) {
2626
2626
  return isWellFormedId(id) && id.includes("/") && !id.startsWith("/") && !id.endsWith("/");
2627
2627
  }
2628
2628
  //#endregion
2629
- //#region src/core-internal/assetValidation.ts
2629
+ //#region src/persistence/adapters/assetValidation.ts
2630
2630
  function sniffImageMediaType(bytes) {
2631
2631
  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";
2632
2632
  if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) return "image/jpeg";
@@ -2713,7 +2713,7 @@ function validateDecodedDimensions(width, height) {
2713
2713
  };
2714
2714
  }
2715
2715
  //#endregion
2716
- //#region src/assetController.ts
2716
+ //#region src/persistence/adapters/assetController.ts
2717
2717
  var FAILURE_COALESCE_MS = 4e3;
2718
2718
  var AssetController = class {
2719
2719
  constructor(resolver, options) {
@@ -2892,7 +2892,7 @@ function toAssetResolutionError(cause, ref) {
2892
2892
  return new AssetResolutionError("unknown", true, cause instanceof Error ? cause.message : String(cause), ref);
2893
2893
  }
2894
2894
  //#endregion
2895
- //#region src/core-internal/assetExport.ts
2895
+ //#region src/persistence/serialization/assetRefCollector.ts
2896
2896
  /** Every unique Asset reference in the document, and which object(s) use it. */
2897
2897
  function collectDocumentAssetRefs(doc, registry) {
2898
2898
  const refs = /* @__PURE__ */ new Map();
@@ -2919,7 +2919,7 @@ function collectSceneAssetRefs(node, objectId, add) {
2919
2919
  if (node.kind === "group") for (const child of node.children) collectSceneAssetRefs(child, objectId, add);
2920
2920
  }
2921
2921
  //#endregion
2922
- //#region src/assetExport.ts
2922
+ //#region src/persistence/serialization/assetExport.ts
2923
2923
  /** Strict mode's rejection — never contains credentials, a fetchable location, or a raw resolver message. */
2924
2924
  var SvgAssetExportError = class extends Error {
2925
2925
  constructor(missingAssets) {
@@ -2975,7 +2975,7 @@ function toDataUri(bytes, mediaType) {
2975
2975
  return `data:${mediaType};base64,${btoa(binary)}`;
2976
2976
  }
2977
2977
  //#endregion
2978
- //#region src/assetIngestion.ts
2978
+ //#region src/persistence/adapters/assetIngestion.ts
2979
2979
  async function decodeDimensionsWithImageBitmap(bytes, mediaType) {
2980
2980
  const blob = new Blob([bytes], { type: mediaType });
2981
2981
  let bitmap;
@@ -35621,7 +35621,7 @@ var WebGLRenderer = class {
35621
35621
  }
35622
35622
  };
35623
35623
  //#endregion
35624
- //#region ../../src/scrawl/data/auditEvents.ts
35624
+ //#region src/core/events/auditEvents.ts
35625
35625
  var BY_LABEL = {
35626
35626
  draw: {
35627
35627
  action: "create",
@@ -35741,7 +35741,7 @@ function lowerFirst(s) {
35741
35741
  return s.charAt(0).toLowerCase() + s.slice(1);
35742
35742
  }
35743
35743
  //#endregion
35744
- //#region ../../src/scrawl/assets/AssetTextureCache.ts
35744
+ //#region src/renderer/shapes/AssetTextureCache.ts
35745
35745
  var AssetTextureCache = class {
35746
35746
  constructor(assetController) {
35747
35747
  this.assetController = assetController;
@@ -35832,7 +35832,7 @@ function toDecodeError(cause, ref) {
35832
35832
  return new AssetResolutionError("decode-failed", false, cause instanceof Error ? cause.message : String(cause), ref);
35833
35833
  }
35834
35834
  //#endregion
35835
- //#region ../../src/scrawl/engine/raycast.ts
35835
+ //#region src/renderer/canvas/raycast.ts
35836
35836
  var origin$1 = new Vector3();
35837
35837
  var target = new Vector3();
35838
35838
  function pointerToBoard(clientX, clientY, canvas, camera) {
@@ -35863,7 +35863,7 @@ function pointerToPlane(clientX, clientY, canvas, camera, z) {
35863
35863
  };
35864
35864
  }
35865
35865
  //#endregion
35866
- //#region ../../src/scrawl/engine/fog.ts
35866
+ //#region src/renderer/canvas/fog.ts
35867
35867
  function createFogUniforms(color) {
35868
35868
  return {
35869
35869
  uFogColor: { value: new Color(color) },
@@ -35884,18 +35884,24 @@ var FOG_GLSL = `
35884
35884
  }
35885
35885
  `;
35886
35886
  //#endregion
35887
- //#region ../../src/scrawl/engine/boardSurface.ts
35887
+ //#region src/renderer/canvas/boardSurface.ts
35888
35888
  var GRID_MODE_CODE = {
35889
35889
  none: 0,
35890
35890
  line: 1,
35891
35891
  dot: 2
35892
35892
  };
35893
+ var TEXTURE_CODE = {
35894
+ flat: 0,
35895
+ textured: 1
35896
+ };
35893
35897
  /** Used when a caller doesn't supply one (e.g. ScrawlEngine constructed without a Host theme). */
35894
35898
  var DEFAULT_BOARD_SURFACE_THEME = {
35895
35899
  color: "#f5f5f3",
35896
35900
  gridMode: "none",
35897
35901
  gridColor: "#deded8",
35898
- gridSpacing: 5
35902
+ gridSpacing: 5,
35903
+ gridLineWidth: 1,
35904
+ surfaceTexture: "flat"
35899
35905
  };
35900
35906
  /** Grid alpha fades out below this on-screen spacing (px) — otherwise it aliases into a solid wash when zoomed out. */
35901
35907
  var GRID_FADE_MIN_PX = 3;
@@ -35916,8 +35922,10 @@ var FRAGMENT = `
35916
35922
  uniform float uGridMode;
35917
35923
  uniform vec3 uGridColor;
35918
35924
  uniform float uGridSpacing;
35919
- uniform float uGridLineWorld;
35925
+ uniform float uWorldPerPixel;
35926
+ uniform float uGridLineWidthPx;
35920
35927
  uniform float uGridFade;
35928
+ uniform float uSurfaceTexture;
35921
35929
  varying vec3 vWorldPos;
35922
35930
 
35923
35931
  float hash(vec2 p) {
@@ -35937,37 +35945,45 @@ var FRAGMENT = `
35937
35945
  ${FOG_GLSL}
35938
35946
 
35939
35947
  // Distance from p's nearest axis-aligned grid line, in world units.
35948
+ // A crisp, ~uGridLineWidthPx-wide line: full alpha to its half-width, a
35949
+ // ~1px anti-aliased falloff beyond that — not a wide, soft-edged band.
35940
35950
  float gridLineAlpha(vec2 p) {
35941
35951
  vec2 cell = mod(p, uGridSpacing);
35942
35952
  vec2 distToLine = min(cell, uGridSpacing - cell);
35943
- vec2 lineAlpha2 = 1.0 - smoothstep(uGridLineWorld, uGridLineWorld * 2.0, distToLine);
35953
+ float halfWidth = 0.5 * uGridLineWidthPx * uWorldPerPixel;
35954
+ float aa = uWorldPerPixel;
35955
+ vec2 lineAlpha2 = 1.0 - smoothstep(halfWidth - aa * 0.5, halfWidth + aa * 0.5, distToLine);
35944
35956
  return max(lineAlpha2.x, lineAlpha2.y);
35945
35957
  }
35946
35958
 
35947
- // Distance from p's nearest grid intersection, in world units.
35959
+ // Distance from p's nearest grid intersection, in world units. Dot radius
35960
+ // matches the configured line width in pixels, same anti-aliasing.
35948
35961
  float gridDotAlpha(vec2 p) {
35949
35962
  vec2 cell = mod(p, uGridSpacing) - uGridSpacing * 0.5;
35950
35963
  float dist = length(cell);
35951
- float radius = uGridLineWorld * 1.5;
35952
- return 1.0 - smoothstep(radius, radius * 2.0, dist);
35964
+ float radius = uGridLineWidthPx * uWorldPerPixel;
35965
+ float aa = uWorldPerPixel;
35966
+ return 1.0 - smoothstep(radius - aa * 0.5, radius + aa * 0.5, dist);
35953
35967
  }
35954
35968
 
35955
35969
  void main() {
35956
35970
  vec3 col = uColor;
35957
35971
 
35958
- // Melamine micro-texture: two octaves of value noise, +-1.75% brightness.
35959
- float n = vnoise(vWorldPos.xy * 2.3) * 0.6 + vnoise(vWorldPos.xy * 9.7) * 0.4;
35960
- col *= 1.0 + (n - 0.5) * 0.035;
35972
+ if (uSurfaceTexture > 0.5) {
35973
+ // Melamine micro-texture: two octaves of value noise, +-1.75% brightness.
35974
+ float n = vnoise(vWorldPos.xy * 2.3) * 0.6 + vnoise(vWorldPos.xy * 9.7) * 0.4;
35975
+ col *= 1.0 + (n - 0.5) * 0.035;
35961
35976
 
35962
- // Satin sheen: Fresnel rim + a specular band from the key light. Both are
35963
- // ~zero in the straight-on working view and bloom as the camera tilts.
35964
- vec3 V = normalize(cameraPosition - vWorldPos);
35965
- vec3 N = vec3(0.0, 0.0, 1.0);
35966
- float fres = pow(1.0 - max(dot(N, V), 0.0), 3.0);
35967
- vec3 L = normalize(vec3(-0.25, 0.4, 0.88));
35968
- vec3 H = normalize(L + V);
35969
- float spec = pow(max(dot(N, H), 0.0), 90.0);
35970
- col += (fres * 0.18 + spec * 0.06) * vec3(1.0);
35977
+ // Satin sheen: Fresnel rim + a specular band from the key light. Both
35978
+ // are ~zero in the straight-on working view and bloom as the camera tilts.
35979
+ vec3 V = normalize(cameraPosition - vWorldPos);
35980
+ vec3 N = vec3(0.0, 0.0, 1.0);
35981
+ float fres = pow(1.0 - max(dot(N, V), 0.0), 3.0);
35982
+ vec3 L = normalize(vec3(-0.25, 0.4, 0.88));
35983
+ vec3 H = normalize(L + V);
35984
+ float spec = pow(max(dot(N, H), 0.0), 90.0);
35985
+ col += (fres * 0.18 + spec * 0.06) * vec3(1.0);
35986
+ }
35971
35987
 
35972
35988
  if (uGridMode > 0.5 && uGridFade > 0.001) {
35973
35989
  float gridAlpha = (uGridMode < 1.5 ? gridLineAlpha(vWorldPos.xy) : gridDotAlpha(vWorldPos.xy)) * uGridFade;
@@ -35987,8 +36003,10 @@ function createBoardSurface(fog, theme) {
35987
36003
  uGridMode: { value: GRID_MODE_CODE[theme.gridMode] },
35988
36004
  uGridColor: { value: new Color(theme.gridColor) },
35989
36005
  uGridSpacing: { value: Math.max(.001, theme.gridSpacing) },
35990
- uGridLineWorld: { value: .05 },
36006
+ uGridLineWidthPx: { value: Math.max(.1, theme.gridLineWidth) },
36007
+ uWorldPerPixel: { value: .05 },
35991
36008
  uGridFade: { value: 0 },
36009
+ uSurfaceTexture: { value: TEXTURE_CODE[theme.surfaceTexture] },
35992
36010
  ...fog
35993
36011
  }
35994
36012
  });
@@ -35996,13 +36014,15 @@ function createBoardSurface(fog, theme) {
35996
36014
  mesh.name = "board-surface";
35997
36015
  return mesh;
35998
36016
  }
35999
- /** Applies a theme change (initial or live) — everything except the per-frame fade/line-width. */
36017
+ /** Applies a theme change (initial or live) — everything except the per-frame fade/pixel-ratio. */
36000
36018
  function setBoardSurfaceTheme(mesh, theme) {
36001
36019
  const uniforms = mesh.material.uniforms;
36002
36020
  uniforms.uColor.value.set(theme.color);
36003
36021
  uniforms.uGridMode.value = GRID_MODE_CODE[theme.gridMode];
36004
36022
  uniforms.uGridColor.value.set(theme.gridColor);
36005
36023
  uniforms.uGridSpacing.value = Math.max(.001, theme.gridSpacing);
36024
+ uniforms.uGridLineWidthPx.value = Math.max(.1, theme.gridLineWidth);
36025
+ uniforms.uSurfaceTexture.value = TEXTURE_CODE[theme.surfaceTexture];
36006
36026
  }
36007
36027
  /** Keep the plane centered under the view, larger than the fog horizon, and the grid's on-screen weight/fade current. */
36008
36028
  function updateBoardSurface(mesh, centerX, centerY, fogFar, worldPerPixel) {
@@ -36010,7 +36030,7 @@ function updateBoardSurface(mesh, centerX, centerY, fogFar, worldPerPixel) {
36010
36030
  const size = fogFar * 2.5;
36011
36031
  mesh.scale.set(size, size, 1);
36012
36032
  const uniforms = mesh.material.uniforms;
36013
- uniforms.uGridLineWorld.value = worldPerPixel;
36033
+ uniforms.uWorldPerPixel.value = worldPerPixel;
36014
36034
  const t = (uniforms.uGridSpacing.value / Math.max(1e-6, worldPerPixel) - GRID_FADE_MIN_PX) / (GRID_FADE_MAX_PX - GRID_FADE_MIN_PX);
36015
36035
  uniforms.uGridFade.value = MathUtils.clamp(t, 0, 1);
36016
36036
  }
@@ -36183,7 +36203,7 @@ var CameraRig = class {
36183
36203
  }
36184
36204
  };
36185
36205
  //#endregion
36186
- //#region ../../src/scrawl/engine/scene.ts
36206
+ //#region src/renderer/canvas/scene.ts
36187
36207
  var SceneRig = class {
36188
36208
  constructor(canvas, boardTheme) {
36189
36209
  this.renderer = new WebGLRenderer({
@@ -36230,7 +36250,7 @@ var SceneRig = class {
36230
36250
  }
36231
36251
  };
36232
36252
  //#endregion
36233
- //#region ../../src/scrawl/ink/inkMaterial.ts
36253
+ //#region src/renderer/shapes/inkMaterial.ts
36234
36254
  var VERTEX = `
36235
36255
  attribute float aAlpha;
36236
36256
  varying vec2 vUv;
@@ -36361,7 +36381,7 @@ var InkMaterials = class {
36361
36381
  }
36362
36382
  };
36363
36383
  //#endregion
36364
- //#region ../../src/scrawl/ink/ribbonGeometry.ts
36384
+ //#region src/renderer/shapes/ribbonGeometry.ts
36365
36385
  /** Ink floats a hair above the board to avoid z-fighting with the surface. */
36366
36386
  var INK_Z = .02;
36367
36387
  /** Highlighter always renders under opaque ink (build prompt §4.1). */
@@ -36412,7 +36432,7 @@ function buildRibbonGeometry(points, baseWidth, z = INK_Z) {
36412
36432
  return geometry;
36413
36433
  }
36414
36434
  //#endregion
36415
- //#region ../../src/scrawl/ink/strokeRenderer.ts
36435
+ //#region src/renderer/shapes/strokeRenderer.ts
36416
36436
  var strokeZ = (stroke) => stroke.tool === "highlighter" ? HIGHLIGHT_Z : INK_Z;
36417
36437
  /**
36418
36438
  * Derives ink meshes from the document by subscription — the document never
@@ -36545,7 +36565,7 @@ function syncMatrix(mesh, stroke) {
36545
36565
  mesh.matrixWorldNeedsUpdate = true;
36546
36566
  }
36547
36567
  //#endregion
36548
- //#region ../../src/scrawl/scene-objects/markerProp.ts
36568
+ //#region src/renderer/shapes/markerProp.ts
36549
36569
  var BODY_LENGTH = 11;
36550
36570
  var BODY_RADIUS = .85;
36551
36571
  var TIP_LENGTH = 1.6;
@@ -36641,7 +36661,7 @@ var MarkerProp = class {
36641
36661
  }
36642
36662
  };
36643
36663
  //#endregion
36644
- //#region ../../src/scrawl/scene-objects/noteGeometry.ts
36664
+ //#region src/renderer/shapes/noteGeometry.ts
36645
36665
  /**
36646
36666
  * Height of the sheet above the board at (u, v) ∈ [0,1]².
36647
36667
  * v = 1 is the adhesive edge (always 0); u = 1 is the peel corner side.
@@ -36685,7 +36705,7 @@ function applyCurl(geometry, curl, peel) {
36685
36705
  color.needsUpdate = true;
36686
36706
  }
36687
36707
  //#endregion
36688
- //#region ../../src/scrawl/scene-objects/stickyNotes.ts
36708
+ //#region src/renderer/shapes/stickyNotes.ts
36689
36709
  var TEXTURE_SIZE = 512;
36690
36710
  var FOCUS_COLOR$4 = 2450411;
36691
36711
  var CURL = .3;
@@ -43261,7 +43281,7 @@ SYNCABLE_PROPS.forEach((prop) => {
43261
43281
  new Box3();
43262
43282
  new Color();
43263
43283
  //#endregion
43264
- //#region ../../src/scrawl/scene-objects/tables.ts
43284
+ //#region src/renderer/shapes/tables.ts
43265
43285
  var FONT_URL$2 = "/fonts/inter-medium.woff";
43266
43286
  var TABLE_Z = .012;
43267
43287
  var HEADER_Z = .014;
@@ -43610,7 +43630,7 @@ var TablesRenderer = class {
43610
43630
  }
43611
43631
  };
43612
43632
  //#endregion
43613
- //#region ../../src/scrawl/scene-objects/textBlocks.ts
43633
+ //#region src/renderer/text/textBlocks.ts
43614
43634
  var FONT_URL$1 = "/fonts/inter-medium.woff";
43615
43635
  var TEXT_Z = .02;
43616
43636
  var FOCUS_COLOR$2 = 2450411;
@@ -43714,7 +43734,7 @@ var TextBlocksRenderer = class {
43714
43734
  }
43715
43735
  };
43716
43736
  //#endregion
43717
- //#region ../../src/scrawl/scene-objects/comments.ts
43737
+ //#region src/renderer/shapes/comments.ts
43718
43738
  var PIN_Z = .04;
43719
43739
  var PIN_WIDTH = 3.6;
43720
43740
  var PIN_HEIGHT = 3.6;
@@ -43882,7 +43902,7 @@ function drawBubblePath(ctx, x, y, w, h, r, tailX, tailY, tailW) {
43882
43902
  ctx.closePath();
43883
43903
  }
43884
43904
  //#endregion
43885
- //#region ../../src/scrawl/scene-objects/images.ts
43905
+ //#region src/renderer/shapes/images.ts
43886
43906
  var IMAGE_Z = .015;
43887
43907
  var FRAME_BORDER = .4;
43888
43908
  var FOCUS_COLOR$1 = 2450411;
@@ -44092,7 +44112,7 @@ function applyPlaceholderTexture(visual, label) {
44092
44112
  visual.imageMesh.material.needsUpdate = true;
44093
44113
  }
44094
44114
  //#endregion
44095
- //#region ../../src/scrawl/scene-objects/kitchenTimers.ts
44115
+ //#region src/renderer/shapes/kitchenTimers.ts
44096
44116
  var FACE_SIZE = 256;
44097
44117
  var BODY_COLOR = 14753096;
44098
44118
  var KNOB_COLOR = 16448250;
@@ -44290,7 +44310,7 @@ function renderTimerFace(timer, canvas, now = Date.now()) {
44290
44310
  return c;
44291
44311
  }
44292
44312
  //#endregion
44293
- //#region ../../src/scrawl/scene-objects/customObjects.ts
44313
+ //#region src/renderer/shapes/customObjects.ts
44294
44314
  var FONT_URL = "/fonts/inter-medium.woff";
44295
44315
  var CUSTOM_Z = .02;
44296
44316
  var FOCUS_COLOR = 2450411;
@@ -44652,7 +44672,7 @@ function disposeObject3D(obj) {
44652
44672
  });
44653
44673
  }
44654
44674
  //#endregion
44655
- //#region ../../src/scrawl/tools/hitTest.ts
44675
+ //#region src/interaction/pointer/hitTest.ts
44656
44676
  /** Topmost (most recently added) stroke under the point, or null. */
44657
44677
  function hitStroke(doc, index, p, slop) {
44658
44678
  const candidates = index.query(p.x - slop, p.y - slop, p.x + slop, p.y + slop);
@@ -44693,7 +44713,7 @@ function distToSegmentSq(p, a, b) {
44693
44713
  return dx * dx + dy * dy;
44694
44714
  }
44695
44715
  //#endregion
44696
- //#region ../../src/scrawl/tools/eraserTool.ts
44716
+ //#region src/interaction/tools/eraserTool.ts
44697
44717
  var DECAY_PER_PASS = .4;
44698
44718
  /** A point can take another decay pass after this long — rubbing works. */
44699
44719
  var REARM_MS = 250;
@@ -44821,7 +44841,7 @@ function partitionByErasure(points) {
44821
44841
  return runs;
44822
44842
  }
44823
44843
  //#endregion
44824
- //#region ../../src/scrawl/tools/noteDrag.ts
44844
+ //#region src/interaction/tools/noteDrag.ts
44825
44845
  /**
44826
44846
  * Drag the focused note in X/Y. The grab is resolved on the note's own
44827
44847
  * z-plane, not the board, so the note doesn't jump under a tilted ray.
@@ -44888,7 +44908,7 @@ var NoteDragState = class {
44888
44908
  }
44889
44909
  };
44890
44910
  //#endregion
44891
- //#region ../../src/scrawl/tools/imageDrag.ts
44911
+ //#region src/interaction/tools/imageDrag.ts
44892
44912
  /** Drag the focused image in X/Y; one command per drag. */
44893
44913
  var ImageDragState = class {
44894
44914
  constructor(ctx) {
@@ -44940,7 +44960,7 @@ var ImageDragState = class {
44940
44960
  }
44941
44961
  };
44942
44962
  //#endregion
44943
- //#region ../../src/scrawl/tools/timerDrag.ts
44963
+ //#region src/interaction/tools/timerDrag.ts
44944
44964
  /** Drag the focused timer in X/Y; one command per drag. */
44945
44965
  var TimerDragState = class {
44946
44966
  constructor(ctx) {
@@ -44992,7 +45012,7 @@ var TimerDragState = class {
44992
45012
  }
44993
45013
  };
44994
45014
  //#endregion
44995
- //#region ../../src/scrawl/tools/shapes.ts
45015
+ //#region src/interaction/tools/shapeGeometry.ts
44996
45016
  /** Uniform synthetic pressure; the ink shader supplies the hand-drawn feel. */
44997
45017
  var PRESSURE = .7;
44998
45018
  var MIN_ELLIPSE_SEGMENTS = 24;
@@ -45181,7 +45201,7 @@ function snappedEnd(a, b) {
45181
45201
  };
45182
45202
  }
45183
45203
  //#endregion
45184
- //#region ../../src/scrawl/tools/shapeTool.ts
45204
+ //#region src/interaction/tools/shapeTool.ts
45185
45205
  var MIN_DRAG_PX$1 = 4;
45186
45206
  /** Drag out a shape; live preview through the real ink material. */
45187
45207
  var ShapeTool = class {
@@ -45212,7 +45232,7 @@ var ShapeTool = class {
45212
45232
  const strokes = this.polylines.map((points) => ({
45213
45233
  id: crypto.randomUUID(),
45214
45234
  color: this.ctx.inkColor(),
45215
- baseWidth: this.ctx.baseWidth(),
45235
+ baseWidth: this.ctx.shapeStrokeWidth(),
45216
45236
  points
45217
45237
  }));
45218
45238
  this.ctx.clusters.assign(strokes[0]);
@@ -45246,13 +45266,13 @@ var ShapeTool = class {
45246
45266
  this.previews[i].visible = polyline !== void 0;
45247
45267
  if (polyline) {
45248
45268
  this.previews[i].geometry.dispose();
45249
- this.previews[i].geometry = buildRibbonGeometry(polyline, this.ctx.baseWidth());
45269
+ this.previews[i].geometry = buildRibbonGeometry(polyline, this.ctx.shapeStrokeWidth());
45250
45270
  }
45251
45271
  }
45252
45272
  }
45253
45273
  };
45254
45274
  //#endregion
45255
- //#region ../../src/scrawl/tools/tableDrag.ts
45275
+ //#region src/interaction/tools/tableDrag.ts
45256
45276
  function getHandleCursor(handle) {
45257
45277
  switch (handle) {
45258
45278
  case "nw":
@@ -45372,7 +45392,7 @@ var TableDragState = class {
45372
45392
  }
45373
45393
  };
45374
45394
  //#endregion
45375
- //#region ../../src/scrawl/tools/tableTool.ts
45395
+ //#region src/interaction/tools/tableTool.ts
45376
45396
  var MIN_DRAG_PX = 6;
45377
45397
  var TableTool = class {
45378
45398
  constructor(ctx) {
@@ -45503,7 +45523,7 @@ var TableTool = class {
45503
45523
  }
45504
45524
  };
45505
45525
  //#endregion
45506
- //#region ../../src/scrawl/tools/commentTool.ts
45526
+ //#region src/interaction/tools/commentTool.ts
45507
45527
  /** Click on the board to drop a new comment pin or open an existing one. */
45508
45528
  var CommentTool = class {
45509
45529
  constructor(ctx) {
@@ -45538,7 +45558,7 @@ var CommentTool = class {
45538
45558
  }
45539
45559
  };
45540
45560
  //#endregion
45541
- //#region ../../src/scrawl/tools/textDrag.ts
45561
+ //#region src/interaction/tools/textDrag.ts
45542
45562
  /** Drag the focused text block in X/Y; one command per drag. */
45543
45563
  var TextDragState = class {
45544
45564
  constructor(ctx) {
@@ -45590,7 +45610,7 @@ var TextDragState = class {
45590
45610
  }
45591
45611
  };
45592
45612
  //#endregion
45593
- //#region ../../src/scrawl/tools/fsm.ts
45613
+ //#region src/interaction/tools/fsm.ts
45594
45614
  var ToolStateMachine = class {
45595
45615
  constructor() {
45596
45616
  this.states = /* @__PURE__ */ new Map();
@@ -45792,7 +45812,7 @@ var IdleState = class {
45792
45812
  }
45793
45813
  };
45794
45814
  //#endregion
45795
- //#region ../../src/scrawl/tools/gizmo.ts
45815
+ //#region src/renderer/selection/gizmo.ts
45796
45816
  var GIZMO_Z = .05;
45797
45817
  var HANDLE_PX = 9;
45798
45818
  var HANDLE_HIT_PX = 12;
@@ -46023,7 +46043,7 @@ function boxesEqual(a, b) {
46023
46043
  return a.minX === b.minX && a.minY === b.minY && a.maxX === b.maxX && a.maxY === b.maxY;
46024
46044
  }
46025
46045
  //#endregion
46026
- //#region ../../src/scrawl/ink/pressure.ts
46046
+ //#region src/interaction/tools/pressure.ts
46027
46047
  var SIM_MAX = 1;
46028
46048
  var SIM_MIN = .3;
46029
46049
  var SMOOTHING = .25;
@@ -46045,7 +46065,7 @@ function clamp(v, lo, hi) {
46045
46065
  return Math.min(hi, Math.max(lo, v));
46046
46066
  }
46047
46067
  //#endregion
46048
- //#region ../../src/scrawl/ink/strokeBuilder.ts
46068
+ //#region src/interaction/tools/strokeBuilder.ts
46049
46069
  var StrokeBuilder = class {
46050
46070
  constructor() {
46051
46071
  this.points = [];
@@ -46095,7 +46115,7 @@ function catmullRom(v0, v1, v2, v3, t) {
46095
46115
  return .5 * (2 * v1 + (-v0 + v2) * t + (2 * v0 - 5 * v1 + 4 * v2 - v3) * t2 + (-v0 + 3 * v1 - 3 * v2 + v3) * t3);
46096
46116
  }
46097
46117
  //#endregion
46098
- //#region ../../src/scrawl/tools/markerTool.ts
46118
+ //#region src/interaction/tools/markerTool.ts
46099
46119
  /** Sample spacing while drawing, in screen pixels. */
46100
46120
  var MIN_SAMPLE_PX = 1.5;
46101
46121
  /** A highlighter is a chisel tip: near-constant width, little pressure play. */
@@ -46198,7 +46218,7 @@ var MarkerTool = class {
46198
46218
  }
46199
46219
  };
46200
46220
  //#endregion
46201
- //#region ../../src/scrawl/tools/orbitTool.ts
46221
+ //#region src/interaction/tools/orbitTool.ts
46202
46222
  /**
46203
46223
  * Orbit-preview (build prompt §4.2): alt-drag tilts the camera off-axis to
46204
46224
  * read the board's material and depth; releasing eases back to the working
@@ -46233,7 +46253,7 @@ var OrbitTool = class {
46233
46253
  }
46234
46254
  };
46235
46255
  //#endregion
46236
- //#region ../../src/scrawl/tools/panTool.ts
46256
+ //#region src/interaction/tools/panTool.ts
46237
46257
  /** Grab-pan: content follows the pointer. */
46238
46258
  var PanTool = class {
46239
46259
  constructor(ctx) {
@@ -46263,7 +46283,7 @@ var PanTool = class {
46263
46283
  }
46264
46284
  };
46265
46285
  //#endregion
46266
- //#region ../../src/scrawl/tools/selection.ts
46286
+ //#region src/renderer/selection/selection.ts
46267
46287
  /** The set of selected stroke ids. Drops ids the document no longer has. */
46268
46288
  var SelectionManager = class {
46269
46289
  constructor(doc) {
@@ -46314,7 +46334,7 @@ var SelectionManager = class {
46314
46334
  }
46315
46335
  };
46316
46336
  //#endregion
46317
- //#region ../../src/scrawl/tools/selectTool.ts
46337
+ //#region src/interaction/tools/selectTool.ts
46318
46338
  var HIT_SLOP_PX = 4;
46319
46339
  var CLICK_TOLERANCE_PX = 3;
46320
46340
  var MIN_SCALE = .05;
@@ -46530,7 +46550,7 @@ function safeRatio(numerator, denominator) {
46530
46550
  return raw;
46531
46551
  }
46532
46552
  //#endregion
46533
- //#region ../../src/scrawl/tools/customTool.ts
46553
+ //#region src/interaction/tools/customTool.ts
46534
46554
  function extensionIdFromToolId(toolId) {
46535
46555
  const slash = toolId.indexOf("/");
46536
46556
  return slash === -1 ? toolId : toolId.slice(0, slash);
@@ -46645,12 +46665,19 @@ var ExtensionCommandBatch = class {
46645
46665
  }
46646
46666
  };
46647
46667
  //#endregion
46648
- //#region ../../src/scrawl/ScrawlEngine.ts
46668
+ //#region src/composition/ScrawlEngine.ts
46649
46669
  /** Marker stroke width in board units (~1% of the default view height). */
46650
46670
  var BASE_WIDTH = .5;
46651
46671
  /** Highlighter chisel width — wide enough to cover a line of handwriting. */
46652
46672
  var HIGHLIGHT_WIDTH = 2.2;
46653
46673
  /**
46674
+ * Shape (rectangle, ellipse, arrow, ...) border width in board units.
46675
+ * Deliberately thinner than BASE_WIDTH: a shape border reads as a crisp,
46676
+ * structural line, not an expressive marker stroke — this is also the
46677
+ * fallback when no Host theme configures ScrawlTheme.shapeStrokeWidth.
46678
+ */
46679
+ var DEFAULT_SHAPE_STROKE_WIDTH = .15;
46680
+ /**
46654
46681
  * Identity of a rendered selection badge: which item, its lock state, and
46655
46682
  * where it sits on screen (rounded to the pixel, so sub-pixel camera drift
46656
46683
  * does not churn React).
@@ -46671,10 +46698,11 @@ function emptyExtensionRegistry() {
46671
46698
  * React never reaches past this class.
46672
46699
  */
46673
46700
  var ScrawlEngine = class {
46674
- constructor(canvas, documentId$1, extensionRegistry = emptyExtensionRegistry(), assetController = new AssetController(void 0), boardTheme = DEFAULT_BOARD_SURFACE_THEME) {
46701
+ constructor(canvas, documentId$1, extensionRegistry = emptyExtensionRegistry(), assetController = new AssetController(void 0), boardTheme = DEFAULT_BOARD_SURFACE_THEME, shapeStrokeWidth = DEFAULT_SHAPE_STROKE_WIDTH) {
46675
46702
  this.canvas = canvas;
46676
46703
  this.extensionRegistry = extensionRegistry;
46677
46704
  this.boardTheme = boardTheme;
46705
+ this.shapeStrokeWidth = shapeStrokeWidth;
46678
46706
  this.onToolChange = null;
46679
46707
  this.onZoomChange = null;
46680
46708
  this.onAuditEvent = null;
@@ -46769,6 +46797,7 @@ var ScrawlEngine = class {
46769
46797
  setTool: (t) => this.setTool(t),
46770
46798
  inkColor: () => this.tool === "highlighter" ? this.highlightColor : this.color,
46771
46799
  baseWidth: () => this.tool === "highlighter" ? HIGHLIGHT_WIDTH : BASE_WIDTH,
46800
+ shapeStrokeWidth: () => this.shapeStrokeWidth,
46772
46801
  activeTool: () => this.tool,
46773
46802
  isSpaceHeld: () => this.spaceHeld,
46774
46803
  pointerToBoard: (e) => pointerToBoard(e.clientX, e.clientY, canvas, this.scene.cameraRig.camera),
@@ -47033,6 +47062,10 @@ var ScrawlEngine = class {
47033
47062
  this.boardTheme = theme;
47034
47063
  this.scene.applyBoardTheme(theme);
47035
47064
  }
47065
+ /** Live update of the shape tool's border width (board units). Already-drawn shapes keep the width they were drawn with. */
47066
+ setShapeStrokeWidth(width) {
47067
+ this.shapeStrokeWidth = width;
47068
+ }
47036
47069
  /**
47037
47070
  * Snap to a peer's camera. No-op while drawing so pointer-to-board
47038
47071
  * mapping does not warp the in-progress stroke.
@@ -48094,7 +48127,7 @@ var ScrawlEngine = class {
48094
48127
  }
48095
48128
  };
48096
48129
  //#endregion
48097
- //#region src/controller-internal.ts
48130
+ //#region src/composition/controller-internal.ts
48098
48131
  function createBoardController(options) {
48099
48132
  return createBoardControllerWithEngine(options);
48100
48133
  }
@@ -48112,9 +48145,12 @@ function createBoardControllerWithEngine(options, existingEngine) {
48112
48145
  color: options.boardTheme?.surface ?? DEFAULT_BOARD_SURFACE_THEME.color,
48113
48146
  gridMode: options.boardTheme?.gridMode ?? DEFAULT_BOARD_SURFACE_THEME.gridMode,
48114
48147
  gridColor: options.boardTheme?.gridColor ?? DEFAULT_BOARD_SURFACE_THEME.gridColor,
48115
- gridSpacing: options.boardTheme?.gridSpacing ?? DEFAULT_BOARD_SURFACE_THEME.gridSpacing
48148
+ gridSpacing: options.boardTheme?.gridSpacing ?? DEFAULT_BOARD_SURFACE_THEME.gridSpacing,
48149
+ gridLineWidth: options.boardTheme?.gridLineWidth ?? DEFAULT_BOARD_SURFACE_THEME.gridLineWidth,
48150
+ surfaceTexture: options.boardTheme?.surfaceTexture ?? DEFAULT_BOARD_SURFACE_THEME.surfaceTexture
48116
48151
  };
48117
- const engine = existingEngine ?? (options.canvas ? new ScrawlEngine(options.canvas, options.document.id, extensionRegistry, assetController, currentBoardTheme) : null);
48152
+ let currentShapeStrokeWidth = options.boardTheme?.shapeStrokeWidth ?? .15;
48153
+ const engine = existingEngine ?? (options.canvas ? new ScrawlEngine(options.canvas, options.document.id, extensionRegistry, assetController, currentBoardTheme, currentShapeStrokeWidth) : null);
48118
48154
  const document = engine?.document ?? new BoardDocument(documentId(options.document.id));
48119
48155
  const history = engine?.history ?? new History(document);
48120
48156
  const createId = options.createId ?? (() => crypto.randomUUID());
@@ -48603,9 +48639,15 @@ function createBoardControllerWithEngine(options, existingEngine) {
48603
48639
  color: theme.surface ?? currentBoardTheme.color,
48604
48640
  gridMode: theme.gridMode ?? currentBoardTheme.gridMode,
48605
48641
  gridColor: theme.gridColor ?? currentBoardTheme.gridColor,
48606
- gridSpacing: theme.gridSpacing ?? currentBoardTheme.gridSpacing
48642
+ gridSpacing: theme.gridSpacing ?? currentBoardTheme.gridSpacing,
48643
+ gridLineWidth: theme.gridLineWidth ?? currentBoardTheme.gridLineWidth,
48644
+ surfaceTexture: theme.surfaceTexture ?? currentBoardTheme.surfaceTexture
48607
48645
  };
48608
48646
  engine?.applyBoardTheme(currentBoardTheme);
48647
+ if (theme.shapeStrokeWidth !== void 0) {
48648
+ currentShapeStrokeWidth = theme.shapeStrokeWidth;
48649
+ engine?.setShapeStrokeWidth(currentShapeStrokeWidth);
48650
+ }
48609
48651
  } },
48610
48652
  view: {
48611
48653
  fit() {
@@ -49376,7 +49418,7 @@ function createLocalBoard(options) {
49376
49418
  };
49377
49419
  }
49378
49420
  //#endregion
49379
- //#region src/theme.ts
49421
+ //#region src/ui/theme.ts
49380
49422
  var scrawlThemePresets = Object.freeze({
49381
49423
  light: Object.freeze({
49382
49424
  surface: "#ffffff",
@@ -49403,9 +49445,12 @@ var scrawlThemePresets = Object.freeze({
49403
49445
  motionEasing: "cubic-bezier(0.2, 0, 0, 1)",
49404
49446
  density: "comfortable",
49405
49447
  boardSurface: "#f5f5f3",
49448
+ boardSurfaceTexture: "flat",
49406
49449
  gridMode: "none",
49407
49450
  gridColor: "#deded8",
49408
- gridSpacing: 5
49451
+ gridSpacing: 5,
49452
+ gridLineWidth: 1,
49453
+ shapeStrokeWidth: .15
49409
49454
  }),
49410
49455
  dark: Object.freeze({
49411
49456
  surface: "#171816",
@@ -49432,9 +49477,12 @@ var scrawlThemePresets = Object.freeze({
49432
49477
  motionEasing: "cubic-bezier(0.2, 0, 0, 1)",
49433
49478
  density: "comfortable",
49434
49479
  boardSurface: "#232420",
49480
+ boardSurfaceTexture: "flat",
49435
49481
  gridMode: "none",
49436
49482
  gridColor: "#33352f",
49437
- gridSpacing: 5
49483
+ gridSpacing: 5,
49484
+ gridLineWidth: 1,
49485
+ shapeStrokeWidth: .15
49438
49486
  })
49439
49487
  });
49440
49488
  var variableNames = {
@@ -49462,9 +49510,12 @@ var variableNames = {
49462
49510
  motionEasing: "--scrawl-motion-easing",
49463
49511
  density: "--scrawl-density",
49464
49512
  boardSurface: "--scrawl-board-surface",
49513
+ boardSurfaceTexture: "--scrawl-board-surface-texture",
49465
49514
  gridMode: "--scrawl-grid-mode",
49466
49515
  gridColor: "--scrawl-grid-color",
49467
- gridSpacing: "--scrawl-grid-spacing"
49516
+ gridSpacing: "--scrawl-grid-spacing",
49517
+ gridLineWidth: "--scrawl-grid-line-width",
49518
+ shapeStrokeWidth: "--scrawl-shape-stroke-width"
49468
49519
  };
49469
49520
  var colorTokens = /* @__PURE__ */ new Set([
49470
49521
  "surface",
@@ -49520,10 +49571,22 @@ function validateScrawlTheme(theme) {
49520
49571
  token,
49521
49572
  message: "Expected none, line, or dot"
49522
49573
  });
49574
+ else if (token === "boardSurfaceTexture" && value !== "flat" && value !== "textured") diagnostics.push({
49575
+ token,
49576
+ message: "Expected flat or textured"
49577
+ });
49523
49578
  else if (token === "gridSpacing" && (!finiteNumber(value) || value < .5 || value > 50)) diagnostics.push({
49524
49579
  token,
49525
49580
  message: "Expected spacing 0.5-50 board units"
49526
49581
  });
49582
+ else if (token === "gridLineWidth" && (!finiteNumber(value) || value < .5 || value > 8)) diagnostics.push({
49583
+ token,
49584
+ message: "Expected line width 0.5-8px"
49585
+ });
49586
+ else if (token === "shapeStrokeWidth" && (!finiteNumber(value) || value < .01 || value > 5)) diagnostics.push({
49587
+ token,
49588
+ message: "Expected stroke width 0.01-5 board units"
49589
+ });
49527
49590
  else if ((token.endsWith("Family") || token.startsWith("elevation") || token === "motionEasing") && (typeof value !== "string" || !value.trim() || /[;{}]/.test(value))) diagnostics.push({
49528
49591
  token,
49529
49592
  message: "Expected a safe CSS value"
@@ -49594,12 +49657,12 @@ function luminance(color) {
49594
49657
  return channels[0] * .2126 + channels[1] * .7152 + channels[2] * .0722;
49595
49658
  }
49596
49659
  function formatValue(token, value) {
49597
- if (token === "baseFontSize" || token === "controlRadius" || token === "panelRadius") return `${value}px`;
49660
+ if (token === "baseFontSize" || token === "controlRadius" || token === "panelRadius" || token === "gridLineWidth") return `${value}px`;
49598
49661
  if (token === "motionDuration") return `${value}ms`;
49599
49662
  return String(value);
49600
49663
  }
49601
49664
  //#endregion
49602
- //#region src/inline-editing.tsx
49665
+ //#region src/ui/dialogs/inline-editing.tsx
49603
49666
  /** Inline note, text, and table-cell editors — driven entirely by public controller events and commands. */
49604
49667
  function InlineEditors({ controller, renderPortal }) {
49605
49668
  const [editor, setEditor] = useState(null);
@@ -49830,7 +49893,7 @@ function InlineEditors({ controller, renderPortal }) {
49830
49893
  }))] });
49831
49894
  }
49832
49895
  //#endregion
49833
- //#region src/style-shelf.tsx
49896
+ //#region src/ui/toolbar/style-shelf.tsx
49834
49897
  var ERASER_RADII = [
49835
49898
  ["Fine", 1],
49836
49899
  ["Medium", 2],
@@ -49993,7 +50056,7 @@ function StyleShelf({ controller, snapshot }) {
49993
50056
  });
49994
50057
  }
49995
50058
  //#endregion
49996
- //#region src/default-ui.tsx
50059
+ //#region src/ui/default-ui.tsx
49997
50060
  var PRIMARY_TOOLS = [
49998
50061
  {
49999
50062
  tool: "select",
@@ -50522,7 +50585,7 @@ function download(blob, name) {
50522
50585
  URL.revokeObjectURL(url);
50523
50586
  }
50524
50587
  //#endregion
50525
- //#region src/presence.tsx
50588
+ //#region src/ui/panels/presence.tsx
50526
50589
  var labelStyle = {
50527
50590
  borderRadius: 999,
50528
50591
  color: "#fff",
@@ -50682,9 +50745,12 @@ function ScrawlProvider({ controller, children, preset = "light", theme, portalC
50682
50745
  useEffect(() => {
50683
50746
  controller.boardTheme.set({
50684
50747
  surface: resolved.values.boardSurface,
50748
+ surfaceTexture: resolved.values.boardSurfaceTexture,
50685
50749
  gridMode: resolved.values.gridMode,
50686
50750
  gridColor: resolved.values.gridColor,
50687
- gridSpacing: resolved.values.gridSpacing
50751
+ gridSpacing: resolved.values.gridSpacing,
50752
+ gridLineWidth: resolved.values.gridLineWidth,
50753
+ shapeStrokeWidth: resolved.values.shapeStrokeWidth
50688
50754
  });
50689
50755
  }, [controller, resolved]);
50690
50756
  useDeferredDisposal(controller, disposeOnUnmount);
@@ -50737,9 +50803,12 @@ function Scrawl({ children, preset, theme, portalContainer, className, style, on
50737
50803
  const resolved = resolveScrawlTheme(preset, theme);
50738
50804
  const boardTheme = {
50739
50805
  surface: resolved.values.boardSurface,
50806
+ surfaceTexture: resolved.values.boardSurfaceTexture,
50740
50807
  gridMode: resolved.values.gridMode,
50741
50808
  gridColor: resolved.values.gridColor,
50742
- gridSpacing: resolved.values.gridSpacing
50809
+ gridSpacing: resolved.values.gridSpacing,
50810
+ gridLineWidth: resolved.values.gridLineWidth,
50811
+ shapeStrokeWidth: resolved.values.shapeStrokeWidth
50743
50812
  };
50744
50813
  controller = createBoardController({
50745
50814
  ...options,