dsh-image-gen 0.6.0 → 0.6.1

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/lib/client.js CHANGED
@@ -48,6 +48,30 @@ window.__ModuleLoader__.load({
48
48
  let react_jsx_runtime = require("react/jsx-runtime");
49
49
  let react = require("react");
50
50
  react = __toESM(react, 1);
51
+ //#region lib/types/client/tl/css-supports-shim.js
52
+ /**
53
+ * CSS.supports shim for tldraw's module-scope environment probe (#40).
54
+ *
55
+ * @tldraw/editor's tlenv initialization runs at import time and calls
56
+ * CSS.supports("color", "color(display-p3 1 1 1)") guarded only by
57
+ * `typeof CSS !== "undefined"`. Environments that expose a `CSS` global
58
+ * without the `supports` method (embedded webviews with partial globals,
59
+ * browsers predating the API) therefore throw "CSS.supports is not a
60
+ * function" while DSH imports the client entry, which aborts the whole
61
+ * plugin load - the image tools and gallery die together with the canvas.
62
+ *
63
+ * This module must be the first import of the client entry so it evaluates
64
+ * before any tldraw code. It installs a conservative `supports()` returning
65
+ * false only when the method is genuinely missing; modern browsers are
66
+ * untouched (the probe already treats false as "no P3 / no backdrop filter",
67
+ * which only downgrades the canvas color space, not the plugin).
68
+ */
69
+ /** Install a fallback `supports` on a CSS-like object when the method is missing. */
70
+ function ensureCssSupports(cssLike) {
71
+ if (typeof cssLike.supports !== "function") cssLike.supports = () => false;
72
+ }
73
+ if (typeof CSS !== "undefined") ensureCssSupports(CSS);
74
+ //#endregion
51
75
  //#region lib/types/shared.js
52
76
  /** Browser route used by the generated-image card. */
53
77
  const IMAGE_ROUTE = "/plugins/dsh-image-gen/image";
@@ -906,6 +930,28 @@ window.__ModuleLoader__.load({
906
930
  * This source code is licensed under the ISC license.
907
931
  * See the LICENSE file in the root directory of this source tree.
908
932
  */
933
+ const Info = createLucideIcon("info", [
934
+ ["circle", {
935
+ cx: "12",
936
+ cy: "12",
937
+ r: "10",
938
+ key: "1mglay"
939
+ }],
940
+ ["path", {
941
+ d: "M12 16v-4",
942
+ key: "1dtifu"
943
+ }],
944
+ ["path", {
945
+ d: "M12 8h.01",
946
+ key: "e9boi3"
947
+ }]
948
+ ]);
949
+ /**
950
+ * @license lucide-react v1.38.0 - ISC
951
+ *
952
+ * This source code is licensed under the ISC license.
953
+ * See the LICENSE file in the root directory of this source tree.
954
+ */
909
955
  const LoaderCircle = createLucideIcon("loader-circle", [["path", {
910
956
  d: "M21 12a9 9 0 1 1-6.219-8.56",
911
957
  key: "13zald"
@@ -115467,6 +115513,84 @@ img.ProseMirror-separator {
115467
115513
  //#region node_modules/.pnpm/tldraw@5.3.0_@floating-ui+d_6aac9d8fd21c97d7663c6dd43e72f388/node_modules/tldraw/dist-esm/index.mjs
115468
115514
  registerTldrawLibraryVersion("tldraw", "5.3.0", "esm");
115469
115515
  //#endregion
115516
+ //#region lib/types/client/tl/tl-brand-tokens.js
115517
+ /**
115518
+ * Single source of truth for the brand colors used across the tldraw surface.
115519
+ *
115520
+ * Consumed by tl-theme-css.ts (DOM chrome via CSS variables) and
115521
+ * tl-brand-theme.ts (canvas renderer via the runtime theme patch), so
115522
+ * re-tinting the surface is a one-file change instead of a hex hunt across
115523
+ * two files. Values mirror the workbench tokens in studio-style.ts
115524
+ * (`--ig-blue` and friends).
115525
+ */
115526
+ /** Brand blue, shared with studio-style.ts `--ig-blue`. */
115527
+ const BRAND_BLUE = "#2f64f5";
115528
+ /** Brand blue RGB components, for composing rgba() tints in one place. */
115529
+ const BRAND_BLUE_RGB = "47, 100, 245";
115530
+ /** Brand blue tuned for dark surfaces. */
115531
+ const BRAND_BLUE_DARK = "#5b83f8";
115532
+ /** Brand blue (dark) RGB components, for composing rgba() tints. */
115533
+ const BRAND_BLUE_DARK_RGB = "91, 131, 248";
115534
+ /** Cool ink from the workbench foreground family. */
115535
+ const BRAND_INK = "#182033";
115536
+ /** Flat canvas surface; also feeds export backgrounds, so it stays solid. */
115537
+ const CANVAS_BG = "#e6ebf5";
115538
+ /** Canvas gradient stops (top and bottom); the mid stop is CANVAS_BG. */
115539
+ const CANVAS_BG_TOP = "#eef2fa";
115540
+ const CANVAS_BG_BOTTOM = "#dfe6f4";
115541
+ //#endregion
115542
+ //#region lib/types/client/tl/tl-brand-theme.js
115543
+ /** Re-tint the palette's "blue" family to the brand blue. */
115544
+ function brandBluePalette(colors, dark) {
115545
+ const blue = colors.blue;
115546
+ if (typeof blue !== "object" || blue === null) return colors;
115547
+ const solid = dark ? BRAND_BLUE_DARK : BRAND_BLUE;
115548
+ return {
115549
+ ...colors,
115550
+ blue: {
115551
+ ...blue,
115552
+ solid,
115553
+ fill: solid,
115554
+ linedFill: dark ? blue.linedFill : "#5a82f7",
115555
+ frameHeadingStroke: dark ? blue.frameHeadingStroke : "#6f8ff5",
115556
+ frameHeadingFill: dark ? blue.frameHeadingFill : "#f7f9ff",
115557
+ frameStroke: dark ? blue.frameStroke : "#6f8ff5",
115558
+ frameFill: dark ? blue.frameFill : "#f7f9ff",
115559
+ noteFill: dark ? blue.noteFill : "#91a9ff",
115560
+ semi: dark ? blue.semi : "#e3e9fd",
115561
+ pattern: dark ? blue.pattern : "#5a82f7"
115562
+ }
115563
+ };
115564
+ }
115565
+ /** Re-tint the special (string) theme colors: selection, marquee, ink, surface. */
115566
+ function brandSpecials(colors, dark) {
115567
+ const lightOnly = dark ? {} : {
115568
+ text: BRAND_INK,
115569
+ background: CANVAS_BG,
115570
+ negativeSpace: CANVAS_BG
115571
+ };
115572
+ return {
115573
+ ...colors,
115574
+ ...lightOnly,
115575
+ selectionStroke: dark ? BRAND_BLUE_DARK : BRAND_BLUE,
115576
+ selectionFill: `rgba(${dark ? BRAND_BLUE_DARK_RGB : BRAND_BLUE_RGB}, ${dark ? "0.22" : "0.15"})`,
115577
+ brushFill: dark ? "rgba(148, 163, 184, 0.12)" : "rgba(100, 116, 139, 0.1)",
115578
+ brushStroke: dark ? "rgba(148, 163, 184, 0.3)" : "rgba(100, 116, 139, 0.28)"
115579
+ };
115580
+ }
115581
+ /** Apply the brand theme patch to a freshly mounted tldraw editor. */
115582
+ function applyBrandTheme(editor) {
115583
+ const base = editor.getTheme("default");
115584
+ if (base === void 0) return;
115585
+ editor.updateTheme({
115586
+ ...base,
115587
+ colors: {
115588
+ light: brandBluePalette(brandSpecials(base.colors.light, false), false),
115589
+ dark: brandBluePalette(brandSpecials(base.colors.dark, true), true)
115590
+ }
115591
+ });
115592
+ }
115593
+ //#endregion
115470
115594
  //#region lib/types/client/tl/tl-canvas-bridge.js
115471
115595
  const items = [];
115472
115596
  const listeners = /* @__PURE__ */ new Set();
@@ -115772,6 +115896,15 @@ img.ProseMirror-separator {
115772
115896
  const MAX_DISPLAY_SIDE = 380;
115773
115897
  const GRID_GAP = 40;
115774
115898
  const GRID_COLS = 3;
115899
+ /**
115900
+ * Dock the stock toolbar vertically along the left edge (Figma-style) instead
115901
+ * of tldraw's signature bottom-center pill. It is still the same
115902
+ * DefaultToolbar with the same tools and overflow handling — only the
115903
+ * orientation changes, which is what makes the whole composition read as our
115904
+ * own surface rather than stock tldraw. TL_THEME_CSS offsets it from the
115905
+ * screen edge like the other floating cards.
115906
+ */
115907
+ const StudioToolbar = () => (0, react_jsx_runtime.jsx)(DefaultToolbar, { orientation: "vertical" });
115775
115908
  function tlAssetIdFor(galleryId) {
115776
115909
  return `asset:ig-${galleryId.replace(/[^A-Za-z0-9_-]/g, "_")}`;
115777
115910
  }
@@ -115941,16 +116074,21 @@ img.ProseMirror-separator {
115941
116074
  (0, react.useEffect)(() => subscribeTlLandings(processQueue), [processQueue]);
115942
116075
  return (0, react_jsx_runtime.jsx)("div", {
115943
116076
  className: "dsh-ig-tl-canvas",
115944
- children: (0, react_jsx_runtime.jsx)(Tldraw, { onMount: (editor) => {
115945
- editorRef.current = editor;
115946
- console.info(`[dsh-image-gen] tldraw mounted (instance ${editor.id})`);
115947
- processQueue();
115948
- const stopSync = startCanvasSync(editor);
115949
- return () => {
115950
- stopSync();
115951
- editorRef.current = null;
115952
- };
115953
- } })
116077
+ children: (0, react_jsx_runtime.jsx)(Tldraw, {
116078
+ components: { Toolbar: StudioToolbar },
116079
+ onMount: (editor) => {
116080
+ editorRef.current = editor;
116081
+ applyBrandTheme(editor);
116082
+ editor.updateInstanceState({ isGridMode: true });
116083
+ console.info(`[dsh-image-gen] tldraw mounted (instance ${editor.id})`);
116084
+ processQueue();
116085
+ const stopSync = startCanvasSync(editor);
116086
+ return () => {
116087
+ stopSync();
116088
+ editorRef.current = null;
116089
+ };
116090
+ }
116091
+ })
115954
116092
  });
115955
116093
  });
115956
116094
  //#endregion
@@ -116057,7 +116195,8 @@ img.ProseMirror-separator {
116057
116195
  collapseGenerate: "折叠生成面板",
116058
116196
  expandGenerate: "展开生成面板",
116059
116197
  generatePanel: "生成面板",
116060
- findInspiration: "找灵感"
116198
+ findInspiration: "找灵感",
116199
+ infiniteCanvasHint: "无限画布与侧边栏会话联动,才是完整功能。打开 DSH 右侧边栏的「图像工作台」即可解锁。"
116061
116200
  },
116062
116201
  en: {
116063
116202
  title: "Cloud Image Studio",
@@ -116158,10 +116297,13 @@ img.ProseMirror-separator {
116158
116297
  collapseGenerate: "Collapse generate panel",
116159
116298
  expandGenerate: "Expand generate panel",
116160
116299
  generatePanel: "Generate",
116161
- findInspiration: "Find inspiration"
116300
+ findInspiration: "Find inspiration",
116301
+ infiniteCanvasHint: "The infinite canvas is at its best when linked with the sidebar conversation. Open Image Studio from the DSH right sidebar to unlock the full experience."
116162
116302
  }
116163
116303
  };
116164
- const StudioView = ({ locale, workspace, initialPrompt, initialCanvasSurface, onInitialPromptApplied, onOpenInspiration }) => {
116304
+ /** localStorage flag remembering that the one-time infinite-canvas hint was dismissed. */
116305
+ const INFINITE_CANVAS_HINT_KEY = "dsh-ig-infinite-canvas-hint-dismissed";
116306
+ const StudioView = ({ locale, workspace, initialPrompt, initialCanvasSurface, showInfiniteCanvasHint, onInitialPromptApplied, onOpenInspiration }) => {
116165
116307
  const [lang, setLang] = (0, react.useState)(() => locale?.getSnapshot?.().active?.startsWith("en") ? "en" : "zh");
116166
116308
  const [config, setConfig] = (0, react.useState)(null);
116167
116309
  const [configLoading, setConfigLoading] = (0, react.useState)(true);
@@ -116169,8 +116311,9 @@ img.ProseMirror-separator {
116169
116311
  const [items, setItems] = (0, react.useState)([]);
116170
116312
  const [sidebarCollapsed, setSidebarCollapsed] = (0, react.useState)(false);
116171
116313
  /** Manual-only collapse for the right generate form; the form stays put in
116172
- * narrow seats because it is the primary generation surface. */
116173
- const [generateCollapsed, setGenerateCollapsed] = (0, react.useState)(false);
116314
+ * narrow seats because it is the primary generation surface. Starts folded
116315
+ * when the studio opens straight into the infinite canvas. */
116316
+ const [generateCollapsed, setGenerateCollapsed] = (0, react.useState)(initialCanvasSurface === "infinite");
116174
116317
  /** Workbench root, observed so a narrow column (right sidebar) can fold the recent rail. */
116175
116318
  const workbenchRootRef = (0, react.useRef)(null);
116176
116319
  /** Set once the user toggles the rail by hand; auto-folding then stands down for this mount. */
@@ -116203,11 +116346,29 @@ img.ProseMirror-separator {
116203
116346
  const dragStartRef = (0, react.useRef)(null);
116204
116347
  const hasDraggedRef = (0, react.useRef)(false);
116205
116348
  const [canvasSurface, setCanvasSurface] = (0, react.useState)(initialCanvasSurface ?? "preview");
116349
+ /** Generate-panel state from before the infinite canvas was opened, so the
116350
+ * panel is restored (not just forced open) when returning to preview. */
116351
+ const generatePanelBeforeInfiniteRef = (0, react.useRef)(false);
116206
116352
  /** Mirrors `canvasSurface` for async reads: a generation started on the
116207
116353
  * infinite canvas must land there even if the user flips back to preview
116208
116354
  * while the request is in flight. */
116209
116355
  const canvasSurfaceRef = (0, react.useRef)(canvasSurface);
116210
116356
  canvasSurfaceRef.current = canvasSurface;
116357
+ /** One-time infinite-canvas entry hint, dismissed per-install (localStorage)
116358
+ * so it educates once instead of nagging on every visit. */
116359
+ const [canvasHintDismissed, setCanvasHintDismissed] = (0, react.useState)(() => {
116360
+ try {
116361
+ return localStorage.getItem(INFINITE_CANVAS_HINT_KEY) === "true";
116362
+ } catch {
116363
+ return false;
116364
+ }
116365
+ });
116366
+ const dismissCanvasHint = (0, react.useCallback)(() => {
116367
+ setCanvasHintDismissed(true);
116368
+ try {
116369
+ localStorage.setItem(INFINITE_CANVAS_HINT_KEY, "true");
116370
+ } catch {}
116371
+ }, []);
116211
116372
  const [dragging, setDragging] = (0, react.useState)(false);
116212
116373
  const [lightboxOpen, setLightboxOpen] = (0, react.useState)(false);
116213
116374
  const [showDeleteModal, setShowDeleteModal] = (0, react.useState)(false);
@@ -116905,7 +117066,6 @@ img.ProseMirror-separator {
116905
117066
  }) : displayItems.map((item) => (0, react_jsx_runtime.jsx)(RecentItem, {
116906
117067
  item,
116907
117068
  active: selected?.id === item.id,
116908
- lang,
116909
117069
  onClick: () => selectItem(item)
116910
117070
  }, item.id)), items.length > visibleLimit && (0, react_jsx_runtime.jsx)("button", {
116911
117071
  type: "button",
@@ -116920,68 +117080,72 @@ img.ProseMirror-separator {
116920
117080
  children: [
116921
117081
  (0, react_jsx_runtime.jsxs)("div", {
116922
117082
  className: "dsh-ig-canvas-toolbar",
116923
- children: [(0, react_jsx_runtime.jsxs)("div", {
116924
- className: "dsh-ig-canvas-toolbar-left",
116925
- children: [sidebarCollapsed && (0, react_jsx_runtime.jsxs)("button", {
116926
- type: "button",
116927
- onClick: () => {
116928
- railToggledByUserRef.current = true;
116929
- setSidebarCollapsed(false);
116930
- },
116931
- title: t("expandSidebar"),
116932
- children: [(0, react_jsx_runtime.jsx)(PanelLeft, { size: 14 }), (0, react_jsx_runtime.jsx)("span", { children: t("recent") })]
116933
- }), (0, react_jsx_runtime.jsxs)("div", {
116934
- className: "dsh-ig-status-badge-wrap",
116935
- children: [(0, react_jsx_runtime.jsxs)("div", {
116936
- className: "dsh-ig-status-badge",
116937
- children: [
116938
- (0, react_jsx_runtime.jsx)("span", { className: `dsh-ig-status-dot ${configuredCount > 0 ? "is-ready" : "is-muted"}` }),
116939
- (0, react_jsx_runtime.jsx)("span", {
116940
- className: "dsh-ig-status-text",
116941
- children: configuredCount > 0 ? t("configuredCount", { count: String(configuredCount) }) : t("unconfiguredStatus")
116942
- }),
116943
- (0, react_jsx_runtime.jsx)(ChevronDown, {
116944
- size: 11,
116945
- className: "dsh-ig-status-arrow"
116946
- })
116947
- ]
116948
- }), config !== null && (0, react_jsx_runtime.jsxs)("div", {
116949
- className: "dsh-ig-status-popover",
116950
- children: [(0, react_jsx_runtime.jsx)("div", {
116951
- className: "dsh-ig-status-popover-title",
116952
- children: t("providerStatus")
116953
- }), (0, react_jsx_runtime.jsx)("ul", {
116954
- className: "dsh-ig-status-list",
116955
- children: config.providers.map((p) => (0, react_jsx_runtime.jsxs)("li", {
116956
- className: p.configured ? "is-configured" : "is-missing",
116957
- children: [(0, react_jsx_runtime.jsxs)("div", {
116958
- className: "dsh-ig-status-item-name",
116959
- children: [(0, react_jsx_runtime.jsx)("strong", { children: p.label }), (0, react_jsx_runtime.jsx)("small", { children: p.model })]
116960
- }), (0, react_jsx_runtime.jsx)("span", {
116961
- className: "dsh-ig-status-item-tag",
116962
- children: p.configured ? t("configured") : t("unconfigured")
116963
- })]
116964
- }, p.provider))
117083
+ children: [
117084
+ (0, react_jsx_runtime.jsxs)("div", {
117085
+ className: "dsh-ig-canvas-toolbar-left",
117086
+ children: [sidebarCollapsed && (0, react_jsx_runtime.jsxs)("button", {
117087
+ type: "button",
117088
+ onClick: () => {
117089
+ railToggledByUserRef.current = true;
117090
+ setSidebarCollapsed(false);
117091
+ },
117092
+ title: t("expandSidebar"),
117093
+ children: [(0, react_jsx_runtime.jsx)(PanelLeft, { size: 14 }), (0, react_jsx_runtime.jsx)("span", { children: t("recent") })]
117094
+ }), (0, react_jsx_runtime.jsxs)("div", {
117095
+ className: "dsh-ig-status-badge-wrap",
117096
+ children: [(0, react_jsx_runtime.jsxs)("div", {
117097
+ className: "dsh-ig-status-badge",
117098
+ children: [
117099
+ (0, react_jsx_runtime.jsx)("span", { className: `dsh-ig-status-dot ${configuredCount > 0 ? "is-ready" : "is-muted"}` }),
117100
+ (0, react_jsx_runtime.jsx)("span", {
117101
+ className: "dsh-ig-status-text",
117102
+ children: configuredCount > 0 ? t("configuredCount", { count: String(configuredCount) }) : t("unconfiguredStatus")
117103
+ }),
117104
+ (0, react_jsx_runtime.jsx)(ChevronDown, {
117105
+ size: 11,
117106
+ className: "dsh-ig-status-arrow"
117107
+ })
117108
+ ]
117109
+ }), config !== null && (0, react_jsx_runtime.jsxs)("div", {
117110
+ className: "dsh-ig-status-popover",
117111
+ children: [(0, react_jsx_runtime.jsx)("div", {
117112
+ className: "dsh-ig-status-popover-title",
117113
+ children: t("providerStatus")
117114
+ }), (0, react_jsx_runtime.jsx)("ul", {
117115
+ className: "dsh-ig-status-list",
117116
+ children: config.providers.map((p) => (0, react_jsx_runtime.jsxs)("li", {
117117
+ className: p.configured ? "is-configured" : "is-missing",
117118
+ children: [(0, react_jsx_runtime.jsxs)("div", {
117119
+ className: "dsh-ig-status-item-name",
117120
+ children: [(0, react_jsx_runtime.jsx)("strong", { children: p.label }), (0, react_jsx_runtime.jsx)("small", { children: p.model })]
117121
+ }), (0, react_jsx_runtime.jsx)("span", {
117122
+ className: "dsh-ig-status-item-tag",
117123
+ children: p.configured ? t("configured") : t("unconfigured")
117124
+ })]
117125
+ }, p.provider))
117126
+ })]
116965
117127
  })]
116966
117128
  })]
116967
- })]
116968
- }), (0, react_jsx_runtime.jsxs)("div", {
116969
- className: "dsh-ig-canvas-toolbar-right",
116970
- children: [
116971
- selected !== null && (0, react_jsx_runtime.jsxs)("button", {
116972
- type: "button",
116973
- onClick: startNew,
116974
- title: t("newGeneration"),
116975
- children: [(0, react_jsx_runtime.jsx)(Plus, { size: 13 }), (0, react_jsx_runtime.jsx)("span", { children: t("newGeneration") })]
116976
- }),
116977
- (0, react_jsx_runtime.jsxs)("button", {
117129
+ }),
117130
+ (0, react_jsx_runtime.jsxs)("div", {
117131
+ className: "dsh-ig-canvas-toolbar-center",
117132
+ children: [(0, react_jsx_runtime.jsxs)("button", {
116978
117133
  type: "button",
116979
117134
  className: canvasSurface === "infinite" ? "is-active" : "",
116980
- onClick: () => setCanvasSurface((surface) => surface === "infinite" ? "preview" : "infinite"),
117135
+ onClick: () => {
117136
+ if (canvasSurface === "infinite") {
117137
+ setGenerateCollapsed(generatePanelBeforeInfiniteRef.current);
117138
+ setCanvasSurface("preview");
117139
+ return;
117140
+ }
117141
+ generatePanelBeforeInfiniteRef.current = generateCollapsed;
117142
+ setGenerateCollapsed(true);
117143
+ setCanvasSurface("infinite");
117144
+ },
116981
117145
  title: t("infiniteCanvas"),
116982
117146
  children: [(0, react_jsx_runtime.jsx)(Frame, { size: 14 }), (0, react_jsx_runtime.jsx)("span", { children: t("infiniteCanvas") })]
116983
- }),
116984
- canvasSurface === "preview" && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
117147
+ }), canvasSurface === "preview" && (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
117148
+ (0, react_jsx_runtime.jsx)("span", { className: "dsh-ig-toolbar-divider" }),
116985
117149
  (0, react_jsx_runtime.jsxs)("button", {
116986
117150
  type: "button",
116987
117151
  className: fit ? "is-active" : "",
@@ -117013,25 +117177,50 @@ img.ProseMirror-separator {
117013
117177
  },
117014
117178
  title: "Zoom in",
117015
117179
  children: (0, react_jsx_runtime.jsx)(ZoomIn, { size: 16 })
117180
+ })
117181
+ ] })]
117182
+ }),
117183
+ (0, react_jsx_runtime.jsxs)("div", {
117184
+ className: "dsh-ig-canvas-toolbar-right",
117185
+ children: [
117186
+ canvasSurface === "preview" && selected !== null && (0, react_jsx_runtime.jsxs)("button", {
117187
+ type: "button",
117188
+ onClick: startNew,
117189
+ title: t("newGeneration"),
117190
+ children: [(0, react_jsx_runtime.jsx)(Plus, { size: 13 }), (0, react_jsx_runtime.jsx)("span", { children: t("newGeneration") })]
117016
117191
  }),
117017
- (0, react_jsx_runtime.jsx)("button", {
117192
+ canvasSurface === "preview" && (0, react_jsx_runtime.jsx)("button", {
117018
117193
  type: "button",
117019
117194
  onClick: () => setLightboxOpen(true),
117020
117195
  title: t("fullscreen"),
117021
117196
  disabled: selected === null,
117022
117197
  children: (0, react_jsx_runtime.jsx)(Expand, { size: 16 })
117198
+ }),
117199
+ generateCollapsed && (0, react_jsx_runtime.jsxs)("button", {
117200
+ type: "button",
117201
+ onClick: () => {
117202
+ setGenerateCollapsed(false);
117203
+ },
117204
+ title: t("expandGenerate"),
117205
+ children: [(0, react_jsx_runtime.jsx)(PanelRight, { size: 14 }), (0, react_jsx_runtime.jsx)("span", { children: t("generatePanel") })]
117023
117206
  })
117024
- ] }),
117025
- generateCollapsed && (0, react_jsx_runtime.jsxs)("button", {
117026
- type: "button",
117027
- onClick: () => {
117028
- setGenerateCollapsed(false);
117029
- },
117030
- title: t("expandGenerate"),
117031
- children: [(0, react_jsx_runtime.jsx)(PanelRight, { size: 14 }), (0, react_jsx_runtime.jsx)("span", { children: t("generatePanel") })]
117032
- })
117033
- ]
117034
- })]
117207
+ ]
117208
+ })
117209
+ ]
117210
+ }),
117211
+ canvasSurface === "infinite" && showInfiniteCanvasHint && !canvasHintDismissed && (0, react_jsx_runtime.jsxs)("div", {
117212
+ className: "dsh-ig-canvas-hint",
117213
+ children: [
117214
+ (0, react_jsx_runtime.jsx)(Info, { size: 14 }),
117215
+ (0, react_jsx_runtime.jsx)("span", { children: t("infiniteCanvasHint") }),
117216
+ (0, react_jsx_runtime.jsx)("button", {
117217
+ type: "button",
117218
+ onClick: dismissCanvasHint,
117219
+ title: t("close"),
117220
+ "aria-label": t("close"),
117221
+ children: (0, react_jsx_runtime.jsx)(X, { size: 13 })
117222
+ })
117223
+ ]
117035
117224
  }),
117036
117225
  canvasSurface === "infinite" ? (0, react_jsx_runtime.jsx)(StudioTlCanvas, {}) : (0, react_jsx_runtime.jsx)("div", {
117037
117226
  className: `dsh-ig-canvas ${isDragging ? "is-dragging" : ""}`,
@@ -117782,31 +117971,23 @@ img.ProseMirror-separator {
117782
117971
  }
117783
117972
  ];
117784
117973
  }
117785
- const RecentItem = ({ item, active, lang, onClick }) => {
117974
+ const RecentItem = ({ item, active, onClick }) => {
117786
117975
  const [setRef, inView] = useInView();
117787
117976
  const image = useAttachmentImage(item.attachment, inView);
117788
- return (0, react_jsx_runtime.jsxs)("button", {
117977
+ return (0, react_jsx_runtime.jsx)("button", {
117789
117978
  ref: setRef,
117790
117979
  type: "button",
117791
117980
  className: `dsh-ig-recent-item ${active ? "is-active" : ""}`,
117792
117981
  onClick,
117793
- children: [(0, react_jsx_runtime.jsx)("div", {
117982
+ title: item.prompt,
117983
+ children: (0, react_jsx_runtime.jsx)("div", {
117794
117984
  className: "dsh-ig-recent-thumb",
117795
117985
  children: image.url !== null ? (0, react_jsx_runtime.jsx)("img", {
117796
117986
  src: image.url,
117797
117987
  alt: "",
117798
117988
  loading: "lazy"
117799
117989
  }) : (0, react_jsx_runtime.jsx)(ImagePlus, { size: 18 })
117800
- }), (0, react_jsx_runtime.jsxs)("div", { children: [
117801
- (0, react_jsx_runtime.jsx)("strong", { children: item.prompt }),
117802
- (0, react_jsx_runtime.jsx)("span", { children: formatRelativeTime$1(item.createdAt, lang) }),
117803
- (0, react_jsx_runtime.jsxs)("small", { children: [
117804
- item.provider,
117805
- " · ",
117806
- item.model
117807
- ] }),
117808
- (0, react_jsx_runtime.jsx)("small", { children: item.attachment.width && item.attachment.height ? `${item.attachment.width} × ${item.attachment.height}` : "—" })
117809
- ] })]
117990
+ })
117810
117991
  });
117811
117992
  };
117812
117993
  function useInView() {
@@ -119975,6 +120156,7 @@ img.ProseMirror-separator {
119975
120156
  workspace: activeWorkspace,
119976
120157
  initialPrompt: studioDraft,
119977
120158
  initialCanvasSurface,
120159
+ showInfiniteCanvasHint: !inSidebar,
119978
120160
  onInitialPromptApplied: clearStudioDraft,
119979
120161
  onOpenInspiration: () => setActiveTab("inspiration")
119980
120162
  })
@@ -120510,17 +120692,23 @@ img.ProseMirror-separator {
120510
120692
  .dsh-ig-count-badge{font-size:11px;min-width:22px;text-align:center;padding:2px 6px;border-radius:999px;background:var(--ig-soft);color:#7b8498;font-weight:500}
120511
120693
  .dsh-ig-collapse-btn{width:28px;height:28px;display:inline-flex;align-items:center;justify-content:center;border:1px solid transparent;border-radius:6px;background:transparent;color:#717b90;cursor:pointer;transition:all .15s}
120512
120694
  .dsh-ig-collapse-btn:hover{border-color:var(--ig-line);background:var(--ig-soft);color:var(--foreground,#182033)}
120513
- .dsh-ig-recent-scroll{flex:1 1 0;height:0;padding:8px;overflow-x:hidden;overflow-y:auto;min-height:0}.dsh-ig-recent-empty{min-height:180px;display:flex;flex-direction:column;justify-content:center;align-items:center;gap:10px;color:#929bad;text-align:center;font-size:12px}
120514
- .dsh-ig-recent-item{display:grid;grid-template-columns:70px minmax(0,1fr);gap:10px;width:100%;padding:8px;border:1px solid transparent;border-radius:9px;background:transparent;text-align:left;cursor:pointer;transition:background .16s,border-color .16s,transform .16s}.dsh-ig-recent-item:hover{background:var(--ig-soft);transform:translateY(-1px)}.dsh-ig-recent-item.is-active{border-color:#89a7ff;background:rgba(47,100,245,.075);box-shadow:inset 3px 0 0 var(--ig-blue)}.dsh-ig-recent-thumb{width:70px;height:70px;border-radius:7px;display:flex;align-items:center;justify-content:center;overflow:hidden;background:#eef1f7;color:#929bad}.dsh-ig-recent-thumb img{width:100%;height:100%;object-fit:cover}.dsh-ig-recent-item>div:last-child{min-width:0;padding:1px 0}.dsh-ig-recent-item strong{display:block;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;font-size:12.5px;line-height:19px;font-weight:630}.dsh-ig-recent-item span,.dsh-ig-recent-item small{display:block;white-space:nowrap;text-overflow:ellipsis;overflow:hidden;color:#828b9e;font-size:10.5px;line-height:16px}
120515
- .dsh-ig-load-more{width:100%;height:32px;margin:8px 0;display:flex;align-items:center;justify-content:center;border:1px dashed var(--ig-line);border-radius:7px;background:transparent;font-size:11px;color:#6b758b;cursor:pointer;transition:background .15s,color .15s}.dsh-ig-load-more:hover{background:var(--ig-soft);color:var(--ig-blue);border-color:#a2b9f6}
120695
+ .dsh-ig-recent-scroll{flex:1 1 0;height:0;padding:8px;overflow-x:hidden;overflow-y:auto;min-height:0;display:grid;grid-template-columns:repeat(auto-fill,minmax(78px,1fr));gap:6px;align-content:start}.dsh-ig-recent-empty{min-height:180px;display:flex;flex-direction:column;justify-content:center;align-items:center;gap:10px;color:#929bad;text-align:center;font-size:12px;grid-column:1/-1}
120696
+ .dsh-ig-recent-item{position:relative;width:100%;aspect-ratio:1;align-self:start;padding:0;border:1px solid transparent;border-radius:8px;background:transparent;cursor:pointer;transition:border-color .16s,box-shadow .16s}.dsh-ig-recent-item:hover{border-color:#b9ccfa;box-shadow:0 1px 4px rgba(30,42,76,.14)}.dsh-ig-recent-item.is-active{border-color:var(--ig-blue);box-shadow:0 0 0 2px rgba(47,100,245,.18)}.dsh-ig-recent-thumb{width:100%;height:100%;border-radius:7px;display:flex;align-items:center;justify-content:center;overflow:hidden;background:#eef1f7;color:#929bad}.dsh-ig-recent-thumb img{width:100%;height:100%;object-fit:cover}
120697
+ .dsh-ig-load-more{grid-column:1/-1;width:100%;height:32px;margin:4px 0 2px;display:flex;align-items:center;justify-content:center;border:1px dashed var(--ig-line);border-radius:7px;background:transparent;font-size:11px;color:#6b758b;cursor:pointer;transition:background .15s,color .15s}.dsh-ig-load-more:hover{background:var(--ig-soft);color:var(--ig-blue);border-color:#a2b9f6}
120516
120698
  .dsh-ig-mini-btn{height:22px;padding:0 7px;display:inline-flex;align-items:center;gap:3px;border:1px solid var(--ig-line);border-radius:5px;background:var(--background,#fff);font-size:10.5px;font-weight:500;color:var(--foreground,#242f44);cursor:pointer;transition:all .15s}.dsh-ig-mini-btn:hover{border-color:#9bb4f5;color:var(--ig-blue);background:rgba(47,100,245,.06)}
120517
120699
  .dsh-ig-canvas-column{min-width:0;min-height:0;height:100%;overflow:hidden;display:flex;flex-direction:column;background:color-mix(in srgb,var(--background,#fff) 97%,#e8edf7)}
120518
- .dsh-ig-canvas-toolbar{height:46px;flex:0 0 46px;display:flex;align-items:center;justify-content:space-between;gap:8px;padding:0 14px;border-bottom:1px solid var(--ig-line)}
120519
- .dsh-ig-canvas-toolbar-left,.dsh-ig-canvas-toolbar-right{display:flex;align-items:center;gap:6px}
120700
+ .dsh-ig-canvas-toolbar{height:46px;flex:0 0 46px;display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:8px;padding:0 14px;border-bottom:1px solid var(--ig-line)}
120701
+ .dsh-ig-canvas-toolbar-left,.dsh-ig-canvas-toolbar-right{display:flex;align-items:center;gap:6px;min-width:0}
120702
+ .dsh-ig-canvas-toolbar-left{justify-self:start}
120703
+ .dsh-ig-canvas-toolbar-right{justify-self:end}
120704
+ .dsh-ig-canvas-toolbar-center{display:flex;align-items:center;justify-self:center;gap:6px;min-width:0}
120705
+ .dsh-ig-toolbar-divider{width:1px;height:16px;background:var(--ig-line);flex-shrink:0;margin:0 2px}
120520
120706
  .dsh-ig-canvas-toolbar button{height:30px;min-width:30px;padding:0 9px;display:inline-flex;align-items:center;justify-content:center;gap:5px;border:1px solid var(--ig-line);border-radius:7px;background:var(--background,#fff);color:var(--foreground,#242f44);font-size:11.5px;font-weight:500;cursor:pointer;transition:border-color .15s,background .15s,color .15s}
120521
120707
  .dsh-ig-canvas-toolbar button:hover{border-color:#9bb4f5;background:rgba(47,100,245,.05);color:var(--ig-blue)}
120522
120708
  .dsh-ig-canvas-toolbar button.is-active{border-color:#8ba8f8;background:rgba(47,100,245,.08);color:var(--ig-blue);font-weight:550}
120709
+ .dsh-ig-canvas-toolbar-center button.is-active{border-color:var(--ig-blue);background:rgba(47,100,245,.09);color:var(--ig-blue);font-weight:600;box-shadow:inset 0 0 0 1px rgba(47,100,245,.28)}
120523
120710
  .dsh-ig-zoom-display{font-size:11px;min-width:38px;text-align:center;color:#64748b;user-select:none;font-variant-numeric:tabular-nums}
120711
+ .dsh-ig-canvas-hint{flex:0 0 auto;display:flex;align-items:flex-start;gap:8px;padding:7px 12px;border-bottom:1px solid rgba(47,100,245,.14);background:rgba(47,100,245,.055);color:#3d4a66;font-size:11.5px;line-height:17px}.dsh-ig-canvas-hint>svg:first-child{flex-shrink:0;color:var(--ig-blue);margin-top:1px}.dsh-ig-canvas-hint>span{flex:1 1 0;min-width:0}.dsh-ig-canvas-hint>button{flex-shrink:0;width:22px;height:22px;display:inline-flex;align-items:center;justify-content:center;border:0;border-radius:5px;background:transparent;color:#8a94a8;cursor:pointer;transition:background .15s,color .15s}.dsh-ig-canvas-hint>button:hover{background:rgba(47,100,245,.1);color:var(--ig-blue)}
120524
120712
  .dsh-ig-status-badge-wrap{position:relative;display:inline-flex;align-items:center}
120525
120713
  .dsh-ig-status-badge{height:30px;display:inline-flex;align-items:center;gap:7px;padding:0 10px;border:1px solid var(--ig-line);border-radius:7px;background:var(--background,#fff);cursor:pointer;user-select:none;transition:border-color .15s,background .15s,box-shadow .15s}
120526
120714
  .dsh-ig-status-badge:hover{border-color:#9bb4f5;background:rgba(47,100,245,.04)}
@@ -120639,6 +120827,166 @@ img.ProseMirror-separator {
120639
120827
  //#region lib/types/client/tl/tl-css.js
120640
120828
  /** Bundled tldraw styles (tldraw 5.3.0, tldraw SDK license; the "Made with tldraw" watermark is part of the licensed default UI). Generated from tldraw/tldraw.css - do not edit by hand. */
120641
120829
  const TL_CSS = "/* THIS CSS FILE IS GENERATED! DO NOT EDIT. OR EDIT. I'M A COMMENT NOT A COP */ \n/* This file is created by the copy-css-files.mjs script in packages/tldraw. */\n/* It combines @tldraw/editor's editor.css and tldraw's ui.css */\n\n/* @tldraw/editor */\n\n.tl-container {\n width: 100%;\n height: 100%;\n font-size: 12px;\n /* Spacing */\n --tl-space-1: 2px;\n --tl-space-2: 4px;\n --tl-space-3: 8px;\n --tl-space-4: 12px;\n --tl-space-5: 16px;\n --tl-space-6: 20px;\n --tl-space-7: 28px;\n --tl-space-8: 32px;\n --tl-space-9: 64px;\n --tl-space-10: 72px;\n /* Radius */\n --tl-radius-0: 2px;\n --tl-radius-1: 4px;\n --tl-radius-2: 6px;\n --tl-radius-3: 9px;\n --tl-radius-4: 11px;\n\n /*\n * Canvas z-index\n *\n * .tl-canvas has `contain: strict` which creates its own stacking context.\n * background, grid, shapes, overlays, and blocker are all children of .tl-canvas,\n * so their z-indices are relative to the canvas, not the container.\n *\n * collaborators, watermark, and canvas-in-front are siblings of .tl-canvas\n * (outside it), so they compete with the UI layer (.tlui-layout at z-index 300).\n * canvas-in-front must be below the UI layer (300) to sit between\n * the canvas and the UI; collaborator cursors sit above canvas content\n * (and layers that anchor to it, like comment pins) but below the\n * in-front content and the UI panels. The watermark (a licensing element)\n * sits above that content so it can't be occluded, but still below the\n * in-front content and the UI.\n */\n --tl-layer-canvas-hidden: -999999;\n --tl-layer-canvas-background: 100;\n --tl-layer-canvas-grid: 150;\n --tl-layer-collaborators: 245;\n --tl-layer-watermark: 248;\n --tl-layer-canvas-in-front: 250;\n --tl-layer-canvas-shapes: 300;\n --tl-layer-canvas-overlays: 500;\n --tl-layer-canvas-blocker: 10000;\n\n /* Canvas overlays z-index */\n --tl-layer-overlays-collaborator-scribble: 10;\n --tl-layer-overlays-collaborator-brush: 20;\n --tl-layer-overlays-collaborator-shape-indicator: 30;\n --tl-layer-overlays-user-scribble: 40;\n --tl-layer-overlays-user-brush: 50;\n --tl-layer-overlays-user-snapline: 90;\n --tl-layer-overlays-selection-fg: 100;\n /* User handles need to be above selection edges / corners, matters for sticky note clone handles */\n --tl-layer-overlays-user-handles: 105;\n --tl-layer-overlays-user-indicator-hint: 110;\n --tl-layer-overlays-collaborator-cursor-hint: 120;\n --tl-layer-overlays-collaborator-cursor: 130;\n\n /* Text editor z-index */\n --tl-layer-text-container: 1;\n --tl-layer-text-content: 3;\n --tl-layer-text-editor: 4;\n\n /* Error fallback z-index */\n --tl-layer-error-overlay: 1;\n --tl-layer-error-canvas: 2;\n --tl-layer-error-canvas-after: 3;\n --tl-layer-error-content: 4;\n\n /* Misc */\n --tl-zoom: 1;\n --tl-tab-size: 2;\n\n /* Cursor SVGs */\n --tl-cursor-none: none;\n --tl-cursor-default:\n url(\"data:image/svg+xml,<svg height='32' width='32' viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg' style='color: black;'><defs><filter id='shadow' y='-40%' x='-40%' width='180px' height='180%' color-interpolation-filters='sRGB'><feDropShadow dx='1' dy='1' stdDeviation='1.2' flood-opacity='.5'/></filter></defs><g fill='none' transform='rotate(0 16 16)' filter='url(%23shadow)'><path d='m12 24.4219v-16.015l11.591 11.619h-6.781l-.411.124z' fill='white'/><path d='m21.0845 25.0962-3.605 1.535-4.682-11.089 3.686-1.553z' fill='white'/><path d='m19.751 24.4155-1.844.774-3.1-7.374 1.841-.775z' fill='black'/><path d='m13 10.814v11.188l2.969-2.866.428-.139h4.768z' fill='black'/></g></svg>\")\n 12 8,\n default;\n --tl-cursor-pointer:\n url(\"data:image/svg+xml,<svg height='32' width='32' viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg' style='color: black;'><defs><filter id='shadow' y='-40%' x='-40%' width='180px' height='180%' color-interpolation-filters='sRGB'><feDropShadow dx='1' dy='1' stdDeviation='1.2' flood-opacity='.5'/></filter></defs><g fill='none' transform='rotate(0 16 16)' filter='url(%23shadow)'><path d='m13.3315 21.3799c-.284-.359-.629-1.093-1.243-1.984-.348-.504-1.211-1.453-1.468-1.935-.223-.426-.199-.617-.146-.97.094-.628.738-1.117 1.425-1.051.519.049.959.392 1.355.716.239.195.533.574.71.788.163.196.203.277.377.509.23.307.302.459.214.121-.071-.496-.187-1.343-.355-2.092-.128-.568-.159-.657-.281-1.093-.129-.464-.195-.789-.316-1.281-.084-.348-.235-1.059-.276-1.459-.057-.547-.087-1.439.264-1.849.275-.321.906-.418 1.297-.22.512.259.803 1.003.936 1.3.239.534.387 1.151.516 1.961.164 1.031.466 2.462.476 2.763.024-.369-.068-1.146-.004-1.5.058-.321.328-.694.666-.795.286-.085.621-.116.916-.055.313.064.643.288.766.499.362.624.369 1.899.384 1.831.086-.376.071-1.229.284-1.584.14-.234.497-.445.687-.479.294-.052.655-.068.964-.008.249.049.586.345.677.487.218.344.342 1.317.379 1.658.015.141.074-.392.293-.736.406-.639 1.843-.763 1.898.639.025.654.02.624.02 1.064 0 .517-.012.828-.04 1.202-.031.4-.117 1.304-.242 1.742-.086.301-.371.978-.652 1.384 0 0-1.074 1.25-1.191 1.813-.118.562-.079.566-.102.965-.023.398.121.922.121.922s-.802.104-1.234.035c-.391-.063-.875-.841-1-1.079-.172-.328-.539-.265-.682-.023-.225.383-.709 1.07-1.051 1.113-.668.084-2.054.031-3.139.02 0 0 .185-1.011-.227-1.358-.305-.259-.83-.784-1.144-1.06z' fill='white'/><g stroke='black' stroke-linecap='round' stroke-width='.75'><path d='m13.3315 21.3799c-.284-.359-.629-1.093-1.243-1.984-.348-.504-1.211-1.453-1.468-1.935-.223-.426-.199-.617-.146-.97.094-.628.738-1.117 1.425-1.051.519.049.959.392 1.355.716.239.195.533.574.71.788.163.196.203.277.377.509.23.307.302.459.214.121-.071-.496-.187-1.343-.355-2.092-.128-.568-.159-.657-.281-1.093-.129-.464-.195-.789-.316-1.281-.084-.348-.235-1.059-.276-1.459-.057-.547-.087-1.439.264-1.849.275-.321.906-.418 1.297-.22.512.259.803 1.003.936 1.3.239.534.387 1.151.516 1.961.164 1.031.466 2.462.476 2.763.024-.369-.068-1.146-.004-1.5.058-.321.328-.694.666-.795.286-.085.621-.116.916-.055.313.064.643.288.766.499.362.624.369 1.899.384 1.831.086-.376.071-1.229.284-1.584.14-.234.497-.445.687-.479.294-.052.655-.068.964-.008.249.049.586.345.677.487.218.344.342 1.317.379 1.658.015.141.074-.392.293-.736.406-.639 1.843-.763 1.898.639.025.654.02.624.02 1.064 0 .517-.012.828-.04 1.202-.031.4-.117 1.304-.242 1.742-.086.301-.371.978-.652 1.384 0 0-1.074 1.25-1.191 1.813-.118.562-.079.566-.102.965-.023.398.121.922.121.922s-.802.104-1.234.035c-.391-.063-.875-.841-1-1.079-.172-.328-.539-.265-.682-.023-.225.383-.709 1.07-1.051 1.113-.668.084-2.054.031-3.139.02 0 0 .185-1.011-.227-1.358-.305-.259-.83-.784-1.144-1.06z' stroke-linejoin='round'/><path d='m21.5664 21.7344v-3.459'/><path d='m19.5508 21.7461-.016-3.473'/><path d='m17.5547 18.3047.021 3.426'/></g></g></svg>\")\n 14 10,\n pointer;\n --tl-cursor-cross:\n url(\"data:image/svg+xml,<svg height='32' width='32' viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg' style='color: black;'><defs><filter id='shadow' y='-40%' x='-40%' width='180px' height='180%' color-interpolation-filters='sRGB'><feDropShadow dx='1' dy='1' stdDeviation='1.2' flood-opacity='.5'/></filter></defs><g fill='none' transform='rotate(0 16 16)' filter='url(%23shadow)'><path d='m25 16h-6.01v-6h-2.98v6h-6.01v3h6.01v6h2.98v-6h6.01z' fill='white'/><path d='m23.9902 17.0103h-6v-6.01h-.98v6.01h-6v.98h6v6.01h.98v-6.01h6z' fill='%23231f1f'/></g></svg>\")\n 16 16,\n crosshair;\n --tl-cursor-comment:\n url(\"data:image/svg+xml,<svg height='32' width='32' viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg'><defs><filter id='shadow' y='-40%' x='-40%' width='180px' height='180%' color-interpolation-filters='sRGB'><feDropShadow dx='1' dy='1' stdDeviation='1.2' flood-opacity='.5'/></filter></defs><g filter='url(%23shadow)'><path d='M5 19.5 L5 11 A8 8 0 0 1 13 3 A8 8 0 0 1 21 11 A8 8 0 0 1 13 19 Z' fill='white' stroke='black' stroke-width='1' stroke-linejoin='round'/></g></svg>\")\n 5 19,\n crosshair;\n --tl-cursor-move:\n url(\"data:image/svg+xml,<svg height='32' width='32' viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg' style='color: black;'><defs><filter id='shadow' y='-40%' x='-40%' width='180px' height='180%' color-interpolation-filters='sRGB'><feDropShadow dx='1' dy='1' stdDeviation='1.2' flood-opacity='.5'/></filter></defs><g fill='none' transform='rotate(0 16 16)' filter='url(%23shadow)'><path d='m19 14h1v1h-1zm1 6h-1v-1h1zm-5-5h-1v-1h1zm0 5h-1v-1h1zm2-10.987-7.985 7.988 5.222 5.221 2.763 2.763 7.984-7.985z' fill='white'/><g fill='black'><path d='m23.5664 16.9971-2.557-2.809v1.829h-4.009-4.001v-1.829l-2.571 2.809 2.572 2.808-.001-1.808h4.001 4.009l-.001 1.808z'/><path d='m17.9873 17h.013v-4.001l1.807.001-2.807-2.571-2.809 2.57h1.809v4.001h.008v4.002l-1.828-.001 2.807 2.577 2.805-2.576h-1.805z'/></g></g></svg>\")\n 16 16,\n move;\n --tl-cursor-grab:\n url(\"data:image/svg+xml,<svg height='32' width='32' viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg' style='color: black;'><defs><filter id='shadow' y='-40%' x='-40%' width='180px' height='180%' color-interpolation-filters='sRGB'><feDropShadow dx='1' dy='1' stdDeviation='1.2' flood-opacity='.5'/></filter></defs><g fill='none' transform='rotate(0 16 16)' filter='url(%23shadow)'><path d='m13.5557 17.5742c-.098-.375-.196-.847-.406-1.552-.167-.557-.342-.859-.47-1.233-.155-.455-.303-.721-.496-1.181-.139-.329-.364-1.048-.457-1.44-.119-.509.033-.924.244-1.206.253-.339.962-.49 1.357-.351.371.13.744.512.916.788.288.46.357.632.717 1.542.393.992.564 1.918.611 2.231l.085.452c-.001-.04-.043-1.122-.044-1.162-.035-1.029-.06-1.823-.038-2.939.002-.126.064-.587.084-.715.078-.5.305-.8.673-.979.412-.201.926-.215 1.401-.017.423.173.626.55.687 1.022.014.109.094.987.093 1.107-.013 1.025.006 1.641.015 2.174.004.231.003 1.625.017 1.469.061-.656.094-3.189.344-3.942.144-.433.405-.746.794-.929.431-.203 1.113-.07 1.404.243.285.305.446.692.482 1.153.032.405-.019.897-.02 1.245 0 .867-.021 1.324-.037 2.121-.001.038-.015.298.023.182.094-.28.188-.542.266-.745.049-.125.241-.614.359-.859.114-.234.211-.369.415-.688.2-.313.415-.448.668-.561.54-.235 1.109.112 1.301.591.086.215.009.713-.028 1.105-.061.647-.254 1.306-.352 1.648-.128.447-.274 1.235-.34 1.601-.072.394-.234 1.382-.359 1.82-.086.301-.371.978-.652 1.384 0 0-1.074 1.25-1.192 1.812-.117.563-.078.567-.101.965-.024.399.121.923.121.923s-.802.104-1.234.034c-.391-.062-.875-.841-1-1.078-.172-.328-.539-.265-.682-.023-.225.383-.709 1.07-1.051 1.113-.668.084-2.054.03-3.139.02 0 0 .185-1.011-.227-1.358-.305-.26-.83-.784-1.144-1.06l-.832-.921c-.284-.36-.629-1.093-1.243-1.985-.348-.504-1.027-1.085-1.284-1.579-.223-.425-.331-.954-.19-1.325.225-.594.675-.897 1.362-.832.519.05.848.206 1.238.537.225.19.573.534.75.748.163.195.203.276.377.509.23.307.302.459.214.121' fill='white'/><g stroke='black' stroke-linecap='round' stroke-width='.75'><path d='m13.5557 17.5742c-.098-.375-.196-.847-.406-1.552-.167-.557-.342-.859-.47-1.233-.155-.455-.303-.721-.496-1.181-.139-.329-.364-1.048-.457-1.44-.119-.509.033-.924.244-1.206.253-.339.962-.49 1.357-.351.371.13.744.512.916.788.288.46.357.632.717 1.542.393.992.564 1.918.611 2.231l.085.452c-.001-.04-.043-1.122-.044-1.162-.035-1.029-.06-1.823-.038-2.939.002-.126.064-.587.084-.715.078-.5.305-.8.673-.979.412-.201.926-.215 1.401-.017.423.173.626.55.687 1.022.014.109.094.987.093 1.107-.013 1.025.006 1.641.015 2.174.004.231.003 1.625.017 1.469.061-.656.094-3.189.344-3.942.144-.433.405-.746.794-.929.431-.203 1.113-.07 1.404.243.285.305.446.692.482 1.153.032.405-.019.897-.02 1.245 0 .867-.021 1.324-.037 2.121-.001.038-.015.298.023.182.094-.28.188-.542.266-.745.049-.125.241-.614.359-.859.114-.234.211-.369.415-.688.2-.313.415-.448.668-.561.54-.235 1.109.112 1.301.591.086.215.009.713-.028 1.105-.061.647-.254 1.306-.352 1.648-.128.447-.274 1.235-.34 1.601-.072.394-.234 1.382-.359 1.82-.086.301-.371.978-.652 1.384 0 0-1.074 1.25-1.192 1.812-.117.563-.078.567-.101.965-.024.399.121.923.121.923s-.802.104-1.234.034c-.391-.062-.875-.841-1-1.078-.172-.328-.539-.265-.682-.023-.225.383-.709 1.07-1.051 1.113-.668.084-2.054.03-3.139.02 0 0 .185-1.011-.227-1.358-.305-.26-.83-.784-1.144-1.06l-.832-.921c-.284-.36-.629-1.093-1.243-1.985-.348-.504-1.027-1.085-1.284-1.579-.223-.425-.331-.954-.19-1.325.225-.594.675-.897 1.362-.832.519.05.848.206 1.238.537.225.19.573.534.75.748.163.195.203.276.377.509.23.307.302.459.214.121' stroke-linejoin='round'/><path d='m20.5664 21.7344v-3.459'/><path d='m18.5508 21.7461-.016-3.473'/><path d='m16.5547 18.3047.021 3.426'/></g></g></svg>\")\n 16 16,\n grab;\n --tl-cursor-grabbing:\n url(\"data:image/svg+xml,<svg height='32' width='32' viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg' style='color: black;'><defs><filter id='shadow' y='-40%' x='-40%' width='180px' height='180%' color-interpolation-filters='sRGB'><feDropShadow dx='1' dy='1' stdDeviation='1.2' flood-opacity='.5'/></filter></defs><g fill='none' transform='rotate(0 16 16)' filter='url(%23shadow)'><path d='m13.5732 12.0361c.48-.178 1.427-.069 1.677.473.213.462.396 1.241.406 1.075.024-.369-.024-1.167.137-1.584.117-.304.347-.59.686-.691.285-.086.62-.116.916-.055.313.064.642.287.765.499.362.623.368 1.899.385 1.831.064-.272.07-1.229.283-1.584.141-.235.497-.445.687-.479.294-.052.656-.068.964-.008.249.049.586.344.677.487.219.344.342 1.316.379 1.658.016.141.074-.393.293-.736.406-.639 1.844-.763 1.898.639.026.654.02.624.02 1.064 0 .516-.012.828-.04 1.202-.03.399-.116 1.304-.241 1.742-.086.301-.371.978-.653 1.384 0 0-1.074 1.25-1.191 1.812-.117.563-.078.567-.102.965-.023.399.121.923.121.923s-.801.104-1.234.034c-.391-.062-.875-.84-1-1.078-.172-.328-.539-.265-.682-.023-.224.383-.709 1.07-1.05 1.113-.669.084-2.055.03-3.14.02 0 0 .185-1.011-.227-1.358-.305-.26-.83-.784-1.144-1.06l-.832-.921c-.283-.36-1.002-.929-1.243-1.985-.213-.936-.192-1.395.037-1.77.232-.381.67-.589.854-.625.208-.042.692-.039.875.062.223.123.313.159.488.391.23.307.312.456.213.121-.076-.262-.322-.595-.434-.97-.109-.361-.401-.943-.38-1.526.008-.221.103-.771.832-1.042' fill='white'/><g stroke='black' stroke-width='.75'><path d='m13.5732 12.0361c.48-.178 1.427-.069 1.677.473.213.462.396 1.241.406 1.075.024-.369-.024-1.167.137-1.584.117-.304.347-.59.686-.691.285-.086.62-.116.916-.055.313.064.642.287.765.499.362.623.368 1.899.385 1.831.064-.272.07-1.229.283-1.584.141-.235.497-.445.687-.479.294-.052.656-.068.964-.008.249.049.586.344.677.487.219.344.342 1.316.379 1.658.016.141.074-.393.293-.736.406-.639 1.844-.763 1.898.639.026.654.02.624.02 1.064 0 .516-.012.828-.04 1.202-.03.399-.116 1.304-.241 1.742-.086.301-.371.978-.653 1.384 0 0-1.074 1.25-1.191 1.812-.117.563-.078.567-.102.965-.023.399.121.923.121.923s-.801.104-1.234.034c-.391-.062-.875-.84-1-1.078-.172-.328-.539-.265-.682-.023-.224.383-.709 1.07-1.05 1.113-.669.084-2.055.03-3.14.02 0 0 .185-1.011-.227-1.358-.305-.26-.83-.784-1.144-1.06l-.832-.921c-.283-.36-1.002-.929-1.243-1.985-.213-.936-.192-1.395.037-1.77.232-.381.67-.589.854-.625.208-.042.692-.039.875.062.223.123.313.159.488.391.23.307.312.456.213.121-.076-.262-.322-.595-.434-.97-.109-.361-.401-.943-.38-1.526.008-.221.103-.771.832-1.042z' stroke-linejoin='round'/><path d='m20.5664 19.7344v-3.459' stroke-linecap='round'/><path d='m18.5508 19.7461-.016-3.473' stroke-linecap='round'/><path d='m16.5547 16.3047.021 3.426' stroke-linecap='round'/></g></g></svg>\")\n 16 16,\n grabbing;\n --tl-cursor-text:\n url(\"data:image/svg+xml,<svg height='32' width='32' viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg' style='color: black;'><defs><filter id='shadow' y='-40%' x='-40%' width='180px' height='180%' color-interpolation-filters='sRGB'><feDropShadow dx='1' dy='1' stdDeviation='1.2' flood-opacity='.5'/></filter></defs><g fill='none' transform='rotate(0 16 16)' filter='url(%23shadow)'><path fill='white' d='M7.94 0a5.25 5.25 0 0 0-3.47 1.17A5.27 5.27 0 0 0 1 0H0v3h1c1.41 0 1.85.7 2 1v3.94H2v3h1v3c-.13.3-.57 1-2 1H0v3h1a5.27 5.27 0 0 0 3.47-1.17c.98.8 2.21 1.21 3.47 1.17h1v-3h-1c-1.41 0-1.85-.7-2-1v-3H7v-3H6V4c.13-.3.57-1 2-1h1V0H7.94z'/><path fill='black' d='M7.94 2V1a4 4 0 0 0-3.47 1.64A4 4 0 0 0 1 1v1c1.3-.17 2.56.6 3 1.84v5.1H3v1h1v4.16c-.45 1.24-1.7 2-3 1.84v1a4.05 4.05 0 0 0 3.47-1.63 4.05 4.05 0 0 0 3.47 1.63v-1A2.82 2.82 0 0 1 5 14.1V9.93h1v-1H5V3.85A2.81 2.81 0 0 1 7.94 2z'/></g></svg>\")\n 4 10,\n text;\n --tl-cursor-zoom-in:\n url(\"data:image/svg+xml,<svg height='32' width='32' viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg' style='color: black;'><defs><filter id='shadow' y='-40%' x='-40%' width='180px' height='180%' color-interpolation-filters='sRGB'><feDropShadow dx='1' dy='1' stdDeviation='1.2' flood-opacity='.5'/></filter></defs><g fill='none' transform='rotate(0 16 16)' filter='url(%23shadow)'><path d='m20.5 15c0 3.038-2.462 5.5-5.5 5.5s-5.5-2.462-5.5-5.5 2.462-5.5 5.5-5.5 5.5 2.462 5.5 5.5' fill='white'/><path d='m20.5 15c0 3.038-2.462 5.5-5.5 5.5s-5.5-2.462-5.5-5.5 2.462-5.5 5.5-5.5 5.5 2.462 5.5 5.5z' stroke='black'/><g fill='black'><path d='m18 14h-2v-2h-2v2h-2v1.98h2v2.02h2v-2.02h2z'/><path d='m23.5859 25 1.414-1.414-5.449-5.449-1.414 1.414z'/></g></g></svg>\")\n 16 16,\n zoom-in;\n --tl-cursor-zoom-out:\n url(\"data:image/svg+xml,<svg height='32' width='32' viewBox='0 0 32 32' xmlns='http://www.w3.org/2000/svg' style='color: black;'><defs><filter id='shadow' y='-40%' x='-40%' width='180px' height='180%' color-interpolation-filters='sRGB'><feDropShadow dx='1' dy='1' stdDeviation='1.2' flood-opacity='.5'/></filter></defs><g fill='none' transform='rotate(0 16 16)' filter='url(%23shadow)'><path d='m20.5 15c0 3.038-2.462 5.5-5.5 5.5s-5.5-2.462-5.5-5.5 2.462-5.5 5.5-5.5 5.5 2.462 5.5 5.5' fill='white'/><path d='m20.5 15c0 3.038-2.462 5.5-5.5 5.5s-5.5-2.462-5.5-5.5 2.462-5.5 5.5-5.5 5.5 2.462 5.5 5.5z' stroke='black'/><g fill='black'><path d='m18 16h-5.98v-1.98h5.98z'/><path d='m23.5859 25 1.414-1.414-5.449-5.449-1.414 1.414z'/></g></g></svg>\")\n 16 16,\n zoom-out;\n\n /* These cursor values get programmatically overridden */\n /* They're just here to help your editor autocomplete */\n --tl-cursor: var(--tl-cursor-default);\n --tl-cursor-resize-edge: ew-resize;\n --tl-cursor-resize-corner: nesw-resize;\n --tl-cursor-ew-resize: ew-resize;\n --tl-cursor-ns-resize: ns-resize;\n --tl-cursor-nesw-resize: nesw-resize;\n --tl-cursor-nwse-resize: nwse-resize;\n --tl-cursor-rotate: pointer;\n --tl-cursor-nwse-rotate: pointer;\n --tl-cursor-nesw-rotate: pointer;\n --tl-cursor-senw-rotate: pointer;\n --tl-cursor-swne-rotate: pointer;\n --tl-scale: calc(1 / var(--tl-zoom));\n /* fonts */\n --tl-font-draw: 'tldraw_draw', sans-serif;\n --tl-font-sans: 'tldraw_sans', sans-serif;\n --tl-font-serif: 'tldraw_serif', serif;\n --tl-font-mono: 'tldraw_mono', monospace;\n /* text outline */\n --tl-text-outline-a: calc(min(0.5, 1 / var(--tl-zoom)) * 2px);\n --tl-text-outline-b: calc(min(0.5, 1 / var(--tl-zoom)) * -2px);\n --tl-text-outline-reference:\n 0 var(--tl-text-outline-b) 0 var(--tl-color-background),\n 0 var(--tl-text-outline-a) 0 var(--tl-color-background),\n var(--tl-text-outline-b) var(--tl-text-outline-b) 0 var(--tl-color-background),\n var(--tl-text-outline-a) var(--tl-text-outline-b) 0 var(--tl-color-background),\n var(--tl-text-outline-a) var(--tl-text-outline-a) 0 var(--tl-color-background),\n var(--tl-text-outline-b) var(--tl-text-outline-a) 0 var(--tl-color-background);\n --tl-text-outline: var(--tl-text-outline-reference);\n /* own properties */\n position: relative;\n inset: 0px;\n height: 100%;\n width: 100%;\n overflow: clip;\n color: var(--tl-color-text);\n}\n\n.tl-theme__light {\n /* Canvas */\n --tl-color-selection-fill: hsl(210, 100%, 56%, 24%);\n --tl-color-selection-stroke: hsl(214, 84%, 56%);\n --tl-color-background: hsl(210, 20%, 98%);\n /* if you ever update --tl-color-background, update the hardcoded values in theme-init.js and globals.css\n they're there to make sure the background color matches the user's preference before the actual app loads*/\n --tl-color-grid: hsl(0, 0%, 43%);\n /* UI */\n --tl-color-low: hsl(204, 16%, 94%);\n --tl-color-low-border: hsl(204, 16%, 92%);\n --tl-color-muted-none: hsl(0, 0%, 0%, 0%);\n --tl-color-muted-0: hsl(0, 0%, 0%, 2%);\n --tl-color-muted-1: hsl(0, 0%, 0%, 10%);\n --tl-color-muted-2: hsl(0, 0%, 0%, 4.3%);\n --tl-color-hint: hsl(0, 0%, 0%, 5.5%);\n --tl-color-overlay: hsl(0, 0%, 0%, 20%);\n --tl-color-divider: hsl(0, 0%, 91%);\n --tl-color-panel: hsl(0, 0%, 99%);\n --tl-color-panel-contrast: hsl(0, 0%, 100%);\n --tl-color-panel-overlay: hsl(0, 0%, 100%, 82%);\n --tl-color-panel-transparent: hsla(0, 0%, 99%, 0%);\n --tl-color-selected: hsl(214, 84%, 56%);\n --tl-color-selected-contrast: hsl(0, 0%, 100%);\n --tl-color-focus: hsl(219, 65%, 50%);\n --tl-color-tooltip: hsla(200, 14%, 4%, 1);\n /* Text */\n --tl-color-text: hsl(0, 0%, 0%);\n --tl-color-text-0: hsl(0, 0%, 11%);\n --tl-color-text-1: hsl(0, 0%, 18%);\n --tl-color-text-3: hsl(204, 4%, 45%);\n --tl-color-text-disabled: hsl(204, 4%, 55%);\n --tl-color-text-shadow: hsl(0, 0%, 100%);\n --tl-color-text-highlight: hsl(52, 100%, 50%);\n --tl-color-text-highlight-p3: color(display-p3 0.972 0.8205 0.05);\n /* Named */\n --tl-color-primary: hsl(214, 84%, 56%);\n --tl-color-success: hsl(123, 46%, 34%);\n --tl-color-info: hsl(201, 98%, 41%);\n --tl-color-warning: hsl(27, 98%, 47%);\n --tl-color-danger: hsl(0, 90%, 43%);\n\n /* Shadows */\n --tl-shadow-1: 0px 1px 2px hsl(0, 0%, 0%, 25%), 0px 1px 3px hsl(0, 0%, 0%, 9%);\n --tl-shadow-2:\n 0px 0px 2px hsl(0, 0%, 0%, 16%), 0px 2px 3px hsl(0, 0%, 0%, 24%),\n 0px 2px 6px hsl(0, 0%, 0%, 0.1), inset 0px 0px 0px 1px var(--tl-color-panel-contrast);\n --tl-shadow-3:\n 0px 1px 2px hsl(0, 0%, 0%, 28%), 0px 2px 6px hsl(0, 0%, 0%, 14%),\n inset 0px 0px 0px 1px var(--tl-color-panel-contrast);\n --tl-shadow-4:\n 0px 0px 3px hsl(0, 0%, 0%, 19%), 0px 5px 4px hsl(0, 0%, 0%, 16%),\n 0px 2px 16px hsl(0, 0%, 0%, 6%), inset 0px 0px 0px 1px var(--tl-color-panel-contrast);\n}\n\n.tl-theme__dark {\n /* Canvas */\n --tl-color-selection-fill: hsl(209, 100%, 57%, 20%);\n --tl-color-selection-stroke: hsl(214, 84%, 56%);\n --tl-color-background: hsl(240, 5%, 6.5%);\n /* if you ever update --tl-color-background, update the hardcoded values in theme-init.js and globals.css\n they're there to make sure the background color matches the user's preference before the actual app loads*/\n --tl-color-grid: hsl(0, 0%, 40%);\n /* UI */\n --tl-color-low: hsl(260, 4.5%, 10.5%);\n --tl-color-low-border: hsl(207, 10%, 10%);\n --tl-color-muted-none: hsl(0, 0%, 100%, 0%);\n --tl-color-muted-0: hsl(0, 0%, 100%, 2%);\n --tl-color-muted-1: hsl(0, 0%, 100%, 10%);\n --tl-color-muted-2: hsl(0, 0%, 100%, 5%);\n --tl-color-hint: hsl(0, 0%, 100%, 7%);\n --tl-color-overlay: hsl(0, 0%, 0%, 50%);\n --tl-color-divider: hsl(240, 9%, 22%);\n --tl-color-panel: hsl(235, 6.8%, 13.5%);\n --tl-color-panel-contrast: hsl(245, 12%, 23%);\n --tl-color-panel-overlay: hsl(210, 10%, 24%, 82%);\n --tl-color-panel-transparent: hsla(235, 6.8%, 13.5%, 0%);\n --tl-color-selected: hsl(217, 89%, 61%);\n --tl-color-selected-contrast: hsl(0, 0%, 100%);\n --tl-color-focus: hsl(217, 76%, 80%);\n --tl-color-tooltip: hsla(0, 0%, 100%, 1);\n /* Text */\n --tl-color-text: hsl(210, 17%, 98%);\n --tl-color-text-0: hsl(0, 9%, 94%);\n --tl-color-text-1: hsl(0, 0%, 85%);\n --tl-color-text-3: hsl(204, 4%, 75%);\n --tl-color-text-disabled: hsl(204, 4%, 50%);\n --tl-color-text-shadow: hsl(210, 13%, 18%);\n --tl-color-text-highlight: hsl(52, 100%, 41%);\n --tl-color-text-highlight-p3: color(display-p3 0.8078 0.6225 0.0312);\n /* Named */\n --tl-color-primary: hsl(214, 84%, 56%);\n --tl-color-success: hsl(123, 38%, 57%);\n --tl-color-info: hsl(199, 92%, 56%);\n --tl-color-warning: hsl(36, 100%, 57%);\n --tl-color-danger: hsl(0, 82%, 66%);\n\n /* Shadows */\n --tl-shadow-1:\n 0px 1px 2px hsl(0, 0%, 0%, 16.1%), 0px 1px 3px hsl(0, 0%, 0%, 22%),\n inset 0px 0px 0px 1px var(--tl-color-panel-contrast);\n --tl-shadow-2:\n 0px 1px 3px hsl(0, 0%, 0%, 66.6%), 0px 2px 6px hsl(0, 0%, 0%, 33%),\n inset 0px 0px 0px 1px var(--tl-color-panel-contrast);\n --tl-shadow-3:\n 0px 1px 3px hsl(0, 0%, 0%, 50%), 0px 2px 12px hsl(0, 0%, 0%, 50%),\n inset 0px 0px 0px 1px var(--tl-color-panel-contrast);\n}\n\n.tl-counter-scaled {\n transform: scale(var(--tl-scale));\n transform-origin: top left;\n width: calc(100% * var(--tl-zoom));\n height: calc(100% * var(--tl-zoom));\n}\n\n.tl-container,\n.tl-container * {\n -webkit-touch-callout: none;\n -webkit-tap-highlight-color: transparent;\n scrollbar-highlight-color: transparent;\n -webkit-user-select: none;\n user-select: none;\n box-sizing: border-box;\n outline: none;\n}\n\n.tl-container a {\n -webkit-touch-callout: initial;\n}\n\n.tl-container__focused {\n outline: 1px solid var(--tl-color-low);\n}\n\n/* The host for <EditorPortal>, last among the container's children. It's a position in the DOM\n rather than a box: `display: contents` keeps what it holds positioned against the container. */\n.tl-portal-host {\n display: contents;\n}\n\ninput,\n*[contenteditable],\n*[contenteditable] * {\n user-select: text;\n}\n\n/* --------------------- Canvas --------------------- */\n\n.tl-canvas {\n position: absolute;\n inset: 0px;\n height: 100%;\n width: 100%;\n color: var(--tl-color-text);\n cursor: var(--tl-cursor);\n overflow: clip;\n content-visibility: auto;\n touch-action: none;\n contain: strict;\n}\n\n.tl-canvas__in-front {\n position: absolute;\n inset: 0;\n pointer-events: none;\n z-index: var(--tl-layer-canvas-in-front);\n}\n\n.tl-shapes {\n position: relative;\n z-index: var(--tl-layer-canvas-shapes);\n}\n\n.tl-svg-context {\n position: absolute;\n top: 0px;\n left: 0px;\n width: 100%;\n height: 100%;\n pointer-events: none;\n}\n\n/* ------------------- Background ------------------- */\n\n.tl-background__wrapper {\n z-index: var(--tl-layer-canvas-background);\n position: absolute;\n inset: 0px;\n height: 100%;\n width: 100%;\n}\n\n.tl-background {\n background-color: var(--tl-color-background);\n width: 100%;\n height: 100%;\n}\n\n/* --------------------- Grid Layer --------------------- */\n\n.tl-grid {\n position: absolute;\n inset: 0px;\n width: 100%;\n height: 100%;\n touch-action: none;\n pointer-events: none;\n z-index: var(--tl-layer-canvas-grid);\n contain: strict;\n}\n\n.tl-grid-dot {\n fill: var(--tl-color-grid);\n}\n\n/* --------------------- Layers --------------------- */\n\n.tl-html-layer {\n position: absolute;\n top: 0px;\n left: 0px;\n width: 1px;\n height: 1px;\n contain: layout style size;\n}\n\n/* The collaborator cursor layer: a sibling of .tl-canvas, above all canvas content, below the\n in-front layer (250) and the UI panels (300). Display-only — pointer events pass through. */\n.tl-collaborators {\n position: absolute;\n inset: 0px;\n z-index: var(--tl-layer-collaborators);\n pointer-events: none;\n overflow: clip;\n contain: layout style size;\n}\n\n.tl-collaborators__item {\n position: absolute;\n top: 0px;\n left: 0px;\n overflow: visible;\n pointer-events: none;\n transform-origin: top left;\n}\n\n.tl-cursor {\n overflow: visible;\n}\n\n/* --------------- Overlay Stack --------------- */\n\n/* -------------- Selection foreground -------------- */\n\n.tl-selection__bg {\n position: absolute;\n top: 0px;\n left: 0px;\n transform-origin: top left;\n background-color: transparent;\n pointer-events: all;\n}\n\n.tl-transparent {\n fill: transparent;\n stroke: transparent;\n}\n\n.tl-hidden {\n opacity: 0;\n pointer-events: none;\n}\n\n/* -------------- Nametag / cursor chat ------------- */\n\n.tl-nametag {\n position: absolute;\n top: 16px;\n left: 13px;\n width: fit-content;\n height: fit-content;\n max-width: 120px;\n padding: 3px 6px;\n white-space: nowrap;\n position: absolute;\n overflow: hidden;\n text-overflow: ellipsis;\n font-size: 12px;\n border-radius: var(--tl-radius-2);\n color: var(--tl-color-selected-contrast);\n}\n\n.tl-nametag-title {\n position: absolute;\n top: -2px;\n left: 13px;\n width: fit-content;\n height: fit-content;\n padding: 0px 6px;\n max-width: 120px;\n white-space: nowrap;\n position: absolute;\n overflow: hidden;\n text-overflow: ellipsis;\n font-size: 12px;\n text-shadow: var(--tl-text-outline);\n color: var(--tl-color-selected-contrast);\n}\n\n.tl-nametag-chat {\n position: absolute;\n top: 16px;\n left: 13px;\n width: fit-content;\n height: fit-content;\n color: var(--tl-color-selected-contrast);\n white-space: nowrap;\n position: absolute;\n padding: 3px 6px;\n font-size: 12px;\n opacity: 1;\n border-radius: var(--tl-radius-2);\n}\n\n.tl-cursor-chat {\n position: absolute;\n color: var(--tl-color-selected-contrast);\n white-space: nowrap;\n padding: 3px 6px;\n font-size: 12px;\n pointer-events: none;\n z-index: var(--tl-layer-cursor);\n margin-top: 16px;\n margin-left: 13px;\n opacity: 1;\n border: none;\n user-select: text;\n border-radius: var(--tl-radius-2);\n}\n\n.tl-cursor-chat .tl-cursor-chat__bubble {\n padding-right: 12px;\n}\n\n.tl-cursor-chat::selection {\n background: var(--tl-color-selected);\n color: var(--tl-color-selected-contrast);\n text-shadow: none;\n}\n\n.tl-cursor-chat::placeholder {\n color: var(--tl-color-selected-contrast);\n opacity: 0.7;\n}\n\n/* ---------------------- Text ---------------------- */\n\n.tl-text-shape-label {\n position: relative;\n font-weight: normal;\n min-width: 1px;\n padding: 0px;\n margin: 0px;\n border: none;\n width: fit-content;\n height: fit-content;\n font-variant: normal;\n font-style: normal;\n pointer-events: all;\n white-space: pre-wrap;\n overflow-wrap: break-word;\n}\n\n.tl-plain-text-wrapper[data-isediting='true'] .tl-text-content {\n opacity: 0;\n}\n\n.tl-rich-text-wrapper[data-isediting='true'] .tl-text-content {\n display: none;\n}\n\n.tl-text {\n /* remove overflow from textarea on windows */\n margin: 0px;\n padding: 0px;\n\n appearance: auto;\n background: none;\n border-image: none;\n border: 0px;\n caret-color: var(--tl-color-text);\n color: inherit;\n column-count: initial !important;\n display: inline-block;\n font-family: inherit;\n font-feature-settings: normal;\n font-kerning: auto;\n font-optical-sizing: auto;\n font-size: inherit;\n font-stretch: 100%;\n font-style: inherit;\n font-variant: inherit;\n font-variation-settings: normal;\n font-weight: inherit;\n letter-spacing: inherit;\n line-height: inherit;\n outline: none;\n overflow-wrap: break-word;\n text-align: inherit;\n text-indent: 0px;\n text-rendering: auto;\n text-shadow: inherit;\n text-transform: none;\n white-space: pre-wrap;\n line-break: normal;\n word-spacing: 0px;\n word-wrap: break-word;\n writing-mode: horizontal-tb !important;\n}\n\n.tl-text-measure {\n z-index: var(--tl-layer-canvas-hidden);\n opacity: 0;\n visibility: hidden;\n\n /* pointer-events: all; */\n /* opacity: 1; */\n /* z-index: 99999; */\n\n position: absolute;\n top: 0px;\n left: 0px;\n width: max-content;\n box-sizing: border-box;\n pointer-events: none;\n white-space: pre-wrap;\n word-wrap: break-word;\n overflow-wrap: break-word;\n resize: none;\n border: none;\n user-select: none;\n contain: layout style paint;\n /* N.B. This property, while discouraged (\"intended for Document Type Definition (DTD) designers\") is necessary for ensuring correct mixed RTL/LTR behavior when exporting SVGs. */\n unicode-bidi: plaintext;\n -webkit-user-select: none;\n}\n\n.tl-text-input,\n.tl-text-content {\n position: absolute;\n inset: 0px;\n height: 100%;\n width: 100%;\n min-width: 1px;\n min-height: 1px;\n outline: none;\n}\n\n.tl-text-content__wrapper {\n position: relative;\n width: fit-content;\n height: fit-content;\n display: flex;\n align-items: center;\n justify-content: center;\n pointer-events: none;\n min-height: auto;\n}\n\n.tl-text-content {\n overflow: visible;\n pointer-events: none;\n}\n\n.tl-text-input {\n resize: none;\n user-select: all;\n -webkit-user-select: text;\n cursor: var(--tl-cursor-text);\n}\n\n.tl-text-input:not(.tl-rich-text) {\n /*\n * Note: this `overflow: hidden` is key for scrollbars to not show up\n * plaintext/<textarea> editors.\n */\n overflow: hidden;\n}\n\n.tl-text-input::selection {\n background: var(--tl-color-selected);\n color: var(--tl-color-selected-contrast);\n text-shadow: none;\n}\n\n/* Text label */\n\n.tl-text-label {\n display: flex;\n justify-content: center;\n align-items: center;\n color: var(--tl-color-text);\n line-height: inherit;\n position: absolute;\n inset: 0px;\n height: 100%;\n width: 100%;\n}\n\n.tl-text-label[data-hastext='false'][data-isediting='false'] > .tl-text-label__inner {\n width: 40px;\n height: 40px;\n}\n\n.tl-text-label[data-hastext='true'][data-isediting='false'] .tl-text-content {\n pointer-events: all;\n}\n\n.tl-text-label__inner > .tl-text-input.tl-rich-text {\n display: none;\n position: static;\n}\n\n.tl-text-wrapper[data-isediting='false'] .tl-text-input {\n opacity: 0;\n cursor: var(--tl-cursor-default);\n}\n\n.tl-rich-text[data-is-ready-for-editing='true'],\n.tl-text-wrapper[data-is-ready-for-editing='true'] .tl-text-input {\n cursor: var(--tl-cursor-text);\n}\n\n.tl-text-label[data-textwrap='true'] > .tl-text-label__inner {\n max-width: 100%;\n}\n\n.tl-text-label[data-isediting='true'] {\n background-color: transparent;\n min-height: auto;\n}\n\n.tl-text-wrapper .tl-text-content {\n pointer-events: all;\n z-index: var(--tl-layer-text-content);\n}\n\n.tl-text-label__inner > .tl-text-content {\n position: relative;\n top: 0px;\n left: 0px;\n padding: inherit;\n height: fit-content;\n width: fit-content;\n border-radius: var(--tl-radius-1);\n max-width: 100%;\n}\n\n.tl-text-label__inner > .tl-text-input {\n position: absolute;\n inset: 0px;\n height: 100%;\n width: 100%;\n padding: inherit;\n}\n\n.tl-text-wrapper[data-isselected='true'] .tl-text-input {\n z-index: var(--tl-layer-text-editor);\n pointer-events: all;\n}\n\n/* This part of the rule helps preserve the occlusion rules for the shapes so we\n * don't click on shapes that are behind other shapes.\n * One extra nuance is we don't use this behavior for:\n * - arrows which have weird geometry and just gets in the way.\n * - draw/line shapes, because it feels restrictive to have them be 'in the way' of clicking on a textfield\n * - shapes that are not filled\n */\n.tl-canvas:is([data-iseditinganything='true'], [data-isselectinganything='true'])\n .tl-shape:not(\n [data-shape-type='arrow'],\n [data-shape-type='draw'],\n [data-shape-type='line'],\n [data-shape-type='highlight'],\n [data-shape-is-filled='false']\n ) {\n pointer-events: all;\n}\n\n.tl-rich-text .ProseMirror {\n word-wrap: break-word;\n overflow-wrap: break-word;\n white-space: pre-wrap;\n\n /**\n * Note: ProseMirror disables this in https://github.com/ProseMirror/prosemirror-view/commit/6b3b2205e2f3029cb8e8e86c55a190a22491df31\n * However, that was from 8 years ago and the browser caret issue\n * it mentions seems to be fixed. So, we're re-enabling it.\n * We'll tell ProseMirror maybe to get rid of this on their end.\n *\n */\n -webkit-font-variant-ligatures: inherit;\n font-variant-ligatures: inherit;\n font-feature-settings: inherit;\n\n /**\n * N.B. This following CSS Rule comes standard with the tiptap editor.\n * Combined with the above rule that it supersedes, it allows for\n * the auto-linking to work in text. Say, when typing example.com\n * this helps it automatically turn that bit of text into a link.\n *\n * However, specifically, the break-spaces features seems to cause\n * rendering differences when going in-and-out of edit mode. For example,\n * the statically rendered text 'the rain in spain falls mainly on the plain'\n * in a note shape will render differently when going in-and-out of edit mode.\n *\n * So, this is commented out to help make both the autolinking work (which now\n * relies on the white-space: pre-wrap to work and to make the static/edit-mode\n * rendering consistent.\n * In the future, we might consider just making the static rendering just use\n * white-space: break-spaces to make it consistent with the edit mode if need be.\n *\n * Also, the amount of ink I've spilt in my career writing comments explaining\n * white-space in contenteditable is incredible.\n *\n /* white-space: break-spaces; */\n}\n\n.tl-rich-text {\n tab-size: var(--tl-tab-size, 2);\n}\n\n.tl-rich-text p {\n margin: 0;\n /* Depending on the extensions, <p> tags can be empty, without a <br />. */\n min-height: 1lh;\n}\n\n.tl-rich-text ul,\n.tl-rich-text ol {\n text-align: left;\n margin: 0;\n padding-left: 1.625ch;\n /* Some resets, like Tailwind, nix the list styling. */\n list-style: revert;\n}\n\n.tl-rich-text ol:has(> li:nth-child(10)) {\n padding-left: 2.625ch;\n}\n\n.tl-rich-text ol:has(> li:nth-child(100)) {\n padding-left: 3.625ch;\n}\n\n.tl-rich-text h1,\n.tl-rich-text h2,\n.tl-rich-text h3,\n.tl-rich-text h4,\n.tl-rich-text h5,\n.tl-rich-text h6 {\n margin-top: 5px;\n margin-bottom: 10px;\n /**\n * Headings render at a larger font-size than the body text (via the browser's\n * default h1–h6 sizing), so they can't inherit the container's resolved\n * whole-pixel line-height — that value is derived from the base font size and\n * would cram the taller lines together. Instead, scale line-height with each\n * heading's own font-size using tldraw's unitless line-height multiplier, which\n * the rich-text render/measure paths expose here. The 1.35 fallback mirrors the\n * default theme line-height (see defaultThemes.ts).\n */\n line-height: var(--tl-rich-text-heading-line-height, 1.35);\n}\n\n.tl-rich-text a {\n color: var(--tl-color-primary);\n text-decoration: underline;\n}\n\n.tl-rich-text[data-is-select-tool-active='false'] a {\n cursor: inherit;\n}\n\n.tl-rich-text code {\n font-family: var(--tl-font-mono);\n}\n\n.tl-rich-text mark {\n background-color: #fddd00;\n color: currentColor;\n border-radius: 2px;\n}\n\n.tl-theme__light .tl-rich-text mark {\n text-shadow: none;\n}\n\n.tl-theme__dark .tl-rich-text mark {\n background-color: var(--tl-color-text-highlight);\n color: currentColor;\n text-shadow: none;\n}\n\n@supports (color: color(display-p3 1 1 1)) {\n @media (color-gamut: p3) {\n .tl-container:not(.tl-theme__force-sRGB) .tl-rich-text mark {\n background-color: var(--tl-color-text-highlight-p3);\n }\n }\n}\n\n.tl-text-wrapper[data-isediting='true'] .tl-rich-text {\n display: block;\n}\n\n.tl-text__outline {\n text-shadow: var(--tl-text-outline);\n}\n\n.tl-text__no-outline {\n text-shadow: none;\n}\n\n/* --------------------- Loading -------------------- */\n\n.tl-loading {\n background-color: var(--tl-color-background);\n color: var(--tl-color-text-1);\n height: 100%;\n width: 100%;\n display: flex;\n flex-direction: column;\n justify-content: center;\n align-items: center;\n gap: var(--tl-space-2);\n font-size: 14px;\n font-weight: 500;\n opacity: 0;\n animation: tl-fade-in 0.2s ease-in-out forwards;\n animation-delay: 0.2s;\n position: absolute;\n inset: 0px;\n z-index: var(--tl-layer-canvas-blocker);\n}\n\n@keyframes tl-fade-in {\n 0% {\n opacity: 0;\n }\n 100% {\n opacity: 1;\n }\n}\n\n.tl-spinner {\n animation: tl-spin 1s linear infinite;\n}\n\n@keyframes tl-spin {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n}\n\n/* ---------------- Hyperlink Button ---------------- */\n\n.tl-hyperlink-button {\n background: none;\n margin: 0px;\n position: absolute;\n top: 0px;\n right: 0px;\n height: 44px;\n width: 44px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 12px;\n font-weight: 400;\n color: var(--tl-color-text-1);\n padding: 13px;\n cursor: var(--tl-cursor-pointer);\n border: none;\n outline: none;\n pointer-events: all;\n z-index: 1;\n}\n\n.tl-hyperlink-button::after {\n content: '';\n z-index: -1;\n position: absolute;\n right: 6px;\n bottom: 6px;\n display: block;\n width: calc(100% - 12px);\n height: calc(100% - 12px);\n border-radius: var(--tl-radius-1);\n background-color: var(--tl-color-background);\n pointer-events: none;\n}\n\n.tl-hyperlink-button:focus-visible {\n color: var(--tl-color-selected);\n}\n\n.tl-hyperlink__icon {\n width: 15px;\n height: 15px;\n background-color: currentColor;\n pointer-events: none;\n}\n\n.tl-hyperlink-button__hidden {\n display: none;\n}\n\n/* ----------------- Shape indicator ---------------- */\n\n.tl-shape-indicator {\n transform-origin: top left;\n fill: none;\n stroke-width: calc(1.5px * var(--tl-scale));\n contain: size layout;\n}\n\n.tl-canvas-overlays {\n position: absolute;\n inset: 0;\n pointer-events: none;\n z-index: var(--tl-layer-canvas-overlays);\n}\n\n/* ---------------------- Shape --------------------- */\n\n.tl-shape {\n position: absolute;\n pointer-events: none;\n overflow: visible;\n transform-origin: top left;\n contain: size layout;\n}\n\n/* ---------------- Shape Containers ---------------- */\n\n.tl-svg-container {\n position: absolute;\n inset: 0px;\n height: 100%;\n width: 100%;\n pointer-events: none;\n stroke-linecap: round;\n stroke-linejoin: round;\n transform-origin: top left;\n overflow: visible;\n}\n\n.tl-html-container {\n position: absolute;\n inset: 0px;\n height: 100%;\n width: 100%;\n pointer-events: none;\n stroke-linecap: round;\n stroke-linejoin: round;\n /* content-visibility: auto; */\n transform-origin: top left;\n color: var(--tl-color-text-1);\n}\n\n.tl-content-slot {\n position: absolute;\n inset: 0px;\n height: 100%;\n width: 100%;\n pointer-events: none;\n transform-origin: top left;\n}\n\n/* -------------------- Group shape ------------------ */\n\n.tl-group {\n stroke: var(--tl-color-text);\n stroke-width: calc(1px * var(--tl-scale));\n opacity: 0.5;\n}\n\n/* --------------------- Arrow shape -------------------- */\n\n.tl-shape[data-shape-type='arrow'] .tl-text-label {\n position: absolute;\n top: -1px;\n left: -1px;\n width: 2px;\n height: 2px;\n padding: 0px;\n display: flex;\n justify-content: center;\n align-items: center;\n text-align: center;\n color: var(--tl-color-text);\n}\n\n.tl-shape[data-shape-type='arrow'] .tl-text-label__inner {\n border-radius: var(--tl-radius-1);\n box-sizing: content-box;\n height: max-content;\n width: max-content;\n}\n\n.tl-shape[data-shape-type='arrow'] .tl-text {\n height: max-content;\n}\n\n/* ------------------- Bookmark shape ------------------- */\n\n.tl-bookmark__container {\n width: 100%;\n height: 100%;\n position: relative;\n border: 1px solid var(--tl-color-panel-contrast);\n background-color: var(--tl-color-panel);\n border-radius: var(--tl-radius-2);\n display: flex;\n flex-direction: column;\n overflow: hidden;\n}\n\n.tl-bookmark__container--safariExport {\n border: 1px solid var(--tl-color-divider);\n}\n\n.tl-bookmark__image_container {\n flex: 1 1 100%;\n overflow: hidden;\n border-top-left-radius: var(--tl-radius-1);\n border-top-right-radius: var(--tl-radius-1);\n width: 100%;\n height: 100%;\n display: flex;\n justify-content: flex-end;\n align-items: flex-start;\n box-shadow: inset 0px 0px 0px 1px var(--tl-color-divider);\n}\n\n.tl-bookmark__image_container > .tl-hyperlink-button::after {\n background-color: var(--tl-color-panel);\n}\n\n.tl-bookmark__placeholder {\n width: 100%;\n height: 100%;\n background-color: var(--tl-color-muted-2);\n border-bottom: 1px solid var(--tl-color-muted-2);\n}\n\n.tl-bookmark__image {\n width: 100%;\n height: 100%;\n object-fit: cover;\n object-position: center;\n border-bottom: 1px solid var(--tl-color-muted-2);\n}\n\n.tl-bookmark__copy_container {\n background-color: var(--tl-color-muted-0);\n padding: var(--tl-space-4);\n pointer-events: all;\n display: flex;\n flex-direction: column;\n justify-content: space-between;\n flex: 1;\n}\n\n.tl-bookmark__copy_container:has(.tl-bookmark__link:only-child) {\n justify-content: center;\n}\n\n.tl-bookmark__heading,\n.tl-bookmark__description,\n.tl-bookmark__link {\n margin: 0px;\n width: 100%;\n font-family: inherit;\n}\n\n.tl-bookmark__heading {\n font-size: 16px;\n line-height: 1.6;\n font-weight: bold;\n padding-bottom: var(--tl-space-2);\n overflow: hidden;\n max-height: calc((16px * 1.6) * 2);\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 2;\n line-clamp: 2;\n text-overflow: ellipsis;\n display: -webkit-box;\n}\n\n.tl-bookmark__description {\n font-size: 12px;\n line-height: 1.5;\n overflow: hidden;\n max-height: calc((12px * 1.5) * 3);\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 3;\n line-clamp: 3;\n text-overflow: ellipsis;\n display: -webkit-box;\n color: var(--tl-color-text-1);\n margin: var(--tl-space-2) 0px;\n}\n\n.tl-bookmark__heading + .tl-bookmark__link,\n.tl-bookmark__description + .tl-bookmark__link {\n margin-top: var(--tl-space-3);\n}\n.tl-bookmark__link {\n font-size: 12px;\n pointer-events: all;\n display: flex;\n color: var(--tl-color-text-1);\n align-items: center;\n cursor: var(--tl-cursor-pointer);\n width: fit-content;\n max-width: 100%;\n text-decoration: none;\n}\n\n.tl-bookmark__link > span {\n flex-shrink: 0px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.tl-bookmark__link > .tl-hyperlink__icon {\n margin-right: 8px;\n flex-shrink: 0;\n}\n\n.tl-bookmark__link > .tl-bookmark__favicon {\n margin-right: 8px;\n width: 16px;\n height: 16px;\n flex-shrink: 0;\n}\n\n/* -------------- Image and video shape ------------- */\n\n.tl-image,\n.tl-video {\n object-fit: cover;\n background-size: cover;\n width: 100%;\n height: 100%;\n}\n\n.tl-video-container,\n.tl-image-container,\n.tl-embed-container {\n width: 100%;\n height: 100%;\n pointer-events: all;\n /* background-color: var(--tl-color-background); */\n\n display: flex;\n justify-content: center;\n align-items: center;\n}\n\n.tl-image-container {\n position: relative;\n}\n\n.tl-image {\n position: absolute;\n inset: 0;\n}\n\n.tl-video.tl-video-is-fullscreen {\n object-fit: contain;\n background-size: contain;\n}\n\n/* -------------------- Note shape ------------------- */\n\n.tl-note__container {\n position: relative;\n width: 100%;\n height: 100%;\n pointer-events: all;\n opacity: 1;\n z-index: var(--tl-layer-text-container);\n border-radius: 1px;\n}\n\n.tl-note__container > .tl-text-label {\n color: currentColor;\n}\n\n.tl-note__attribution {\n position: absolute;\n bottom: calc(4px * var(--note-attribution-scale, 1));\n right: calc(8px * var(--note-attribution-scale, 1));\n font-family: var(--tl-font-sans);\n pointer-events: auto;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n max-width: 60%;\n z-index: 1;\n}\n\n/* ------------------- Frame shape ------------------- */\n\n.tl-frame__body {\n stroke-width: calc(1px * var(--tl-scale));\n}\n\n.tl-frame__creating {\n stroke: var(--tl-color-selected);\n fill: none;\n}\n\n.tl-frame-heading {\n --tl-frame-padding-x: 6px;\n --tl-frame-height: 24px;\n --tl-frame-minimum-width: 32px;\n --tl-frame-offset-width: 16px;\n display: flex;\n align-items: center;\n position: absolute;\n transform-origin: 0% 100%;\n overflow: hidden;\n max-width: 100%;\n min-width: var(--tl-frame-minimum-width);\n height: auto;\n font-size: 12px;\n padding-bottom: 4px;\n pointer-events: all;\n}\n\n.tl-frame-heading-hit-area {\n pointer-events: all;\n /* scale from bottom left corner so we can pin it to the top left corner of the frame */\n transform-origin: 0% 100%;\n display: flex;\n height: var(--tl-frame-height);\n width: 100%;\n align-items: center;\n border-radius: var(--tl-radius-1);\n}\n\n.tl-frame-label {\n pointer-events: all;\n overflow: hidden;\n text-overflow: ellipsis;\n padding: 0px var(--tl-frame-padding-x);\n border-radius: var(--tl-radius-1);\n position: relative;\n font-size: inherit;\n white-space: pre;\n}\n\n.tl-frame-label__editing {\n color: transparent;\n white-space: pre;\n width: auto;\n min-width: var(--tl-frame-minimum-width);\n height: 100%;\n overflow: visible;\n background-color: var(--tl-color-panel);\n border-color: var(--tl-color-selected);\n box-shadow: inset 0px 0px 0px 1.5px var(--tl-color-selected);\n}\n\n.tl-frame-name-input {\n position: absolute;\n border: none;\n background: none;\n outline: none;\n padding: 0px var(--tl-frame-padding-x);\n inset: 0px;\n height: 100%;\n width: 100%;\n font-size: inherit;\n font-family: inherit;\n font-weight: inherit;\n width: 100%;\n color: var(--tl-color-text-1);\n border-radius: var(--tl-radius-1);\n user-select: all;\n -webkit-user-select: text;\n white-space: pre;\n cursor: var(--tl-cursor-text);\n}\n\n/* ------------------- Embed Shape ------------------ */\n\n.tl-embed {\n border: none;\n border-radius: var(--tl-radius-2);\n}\n\n/* -------------- Shape error boundary -------------- */\n\n.tl-shape-error-boundary {\n width: 100%;\n height: 100%;\n background-color: var(--tl-color-muted-1);\n border-width: calc(1px * var(--tl-scale));\n border-color: var(--tl-color-muted-1);\n border-style: solid;\n border-radius: calc(var(--tl-radius-1) * var(--tl-scale));\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n text-align: left;\n position: relative;\n pointer-events: all;\n overflow: hidden;\n padding: var(--tl-space-2);\n}\n\n.tl-shape-error-boundary::before {\n transform: scale(var(--tl-scale));\n content: 'Error';\n font-size: 12px;\n font-family: inherit;\n color: var(--tl-color-text-0);\n}\n\n/* ----------------- Error boundary ----------------- */\n\n.tl-error-boundary {\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: var(--tl-space-4);\n background-color: var(--tl-color-background);\n color: var(--tl-color-text-1);\n position: absolute;\n}\n\n.tl-error-boundary__overlay {\n position: absolute;\n inset: 0px;\n height: 100%;\n width: 100%;\n z-index: var(--tl-layer-error-overlay);\n background-color: var(--tl-color-overlay);\n}\n\n.tl-error-boundary__content * {\n user-select: all;\n -webkit-user-select: text;\n pointer-events: all;\n}\n\n.tl-error-boundary__canvas {\n pointer-events: none;\n position: absolute;\n inset: 0px;\n height: 100%;\n width: 100%;\n z-index: var(--tl-layer-error-canvas);\n}\n\n/* some browsers seem to have some weird interactions between stacking contexts\nand pointer-events. this ::after pseudo element covers the canvas and prevents\nit from receiving any pointer events or affecting the cursor. */\n.tl-error-boundary__canvas::after {\n content: ' ';\n display: block;\n position: absolute;\n inset: 0px;\n height: 100%;\n width: 100%;\n z-index: var(--tl-layer-error-canvas-after);\n pointer-events: all;\n}\n\n.tl-error-boundary__content {\n width: fit-content;\n height: fit-content;\n max-width: 100%;\n width: 400px;\n max-height: 100%;\n background-color: var(--tl-color-panel);\n padding: 16px;\n border-radius: 16px;\n box-shadow: var(--tl-shadow-2);\n font-size: 14px;\n font-weight: 400;\n display: flex;\n flex-direction: column;\n overflow: auto;\n z-index: var(--tl-layer-error-content);\n gap: 12px;\n}\n\n.tl-error-boundary__content__expanded {\n width: 600px;\n}\n\n.tl-error-boundary__content h2 {\n font-size: 16px;\n margin: 0px;\n font-weight: 500;\n}\n\n.tl-error-boundary__content h4 {\n border: 1px solid var(--tl-color-low-border);\n margin: -6px 0 0 0;\n padding: var(--tl-space-5);\n border-radius: var(--tl-radius-2);\n font-weight: normal;\n}\n\n.tl-error-boundary__content p {\n line-height: 1.5;\n margin: 0px;\n}\n\n.tl-error-boundary__content pre {\n background-color: var(--tl-color-muted-2);\n margin-top: 0;\n padding: var(--tl-space-5);\n border-radius: var(--tl-radius-2);\n overflow: auto;\n font-size: 12px;\n max-height: 320px;\n}\n\n.tl-error-boundary__content button {\n background: none;\n border: none;\n font-family: inherit;\n font-size: 14px;\n font-weight: 500;\n padding: var(--tl-space-4);\n border-radius: var(--tl-radius-3);\n cursor: var(--tl-cursor-pointer);\n color: inherit;\n background-color: transparent;\n}\n\n.tl-error-boundary__content a {\n color: var(--tl-color-selected);\n font-weight: 500;\n text-decoration: none;\n}\n\n.tl-error-boundary__content__error {\n position: relative;\n margin: -6px 0 0 0;\n}\n\n.tl-error-boundary__content__error button {\n position: absolute;\n top: var(--tl-space-2);\n right: var(--tl-space-2);\n font-size: 12px;\n padding: var(--tl-space-2) var(--tl-space-3);\n background-color: var(--tl-color-panel);\n border-radius: var(--tl-radius-1);\n}\n\n.tl-error-boundary__content__actions {\n display: flex;\n justify-content: space-between;\n gap: var(--tl-space-4);\n margin: 0px;\n margin-left: -4px;\n}\n.tl-error-boundary__content__actions__group {\n display: flex;\n gap: var(--tl-space-4);\n}\n.tl-error-boundary__content .tl-error-boundary__reset {\n color: var(--tl-color-danger);\n}\n.tl-error-boundary__content .tl-error-boundary__refresh {\n background-color: var(--tl-color-primary);\n color: var(--tl-color-selected-contrast);\n}\n.tl-container__focused:not(.tl-container__no-focus-ring)\n .tlui-button.tl-error-boundary__refresh:focus-visible {\n border-radius: 8px;\n outline-offset: 0;\n}\n\n/* ---------------- Hit test blocker ---------------- */\n\n.tl-hit-test-blocker {\n position: absolute;\n z-index: var(--tl-layer-canvas-blocker);\n inset: 0px;\n width: 100%;\n height: 100%;\n pointer-events: all;\n}\n\n.tl-hit-test-blocker__hidden {\n display: none;\n}\n\n/* --------------------- Hovers --------------------- */\n\n@media (hover: hover) {\n .tl-handle__create:hover {\n opacity: 1;\n }\n\n .tl-handle__bg:hover {\n cursor: var(--tl-cursor-grab);\n fill: var(--tl-color-selection-fill);\n }\n\n .tl-bookmark__link:hover {\n color: var(--tl-color-selected);\n }\n\n .tl-hyperlink-button:hover {\n color: var(--tl-color-selected);\n }\n\n .tl-error-boundary__content button:hover {\n background-color: var(--tl-color-low);\n }\n .tl-error-boundary__content a:hover {\n color: var(--tl-color-text-1);\n }\n .tl-error-boundary__content .tl-error-boundary__refresh:hover {\n background-color: var(--tl-color-primary);\n opacity: 0.9;\n }\n\n /* These three rules help preserve clicking into specific points in text areas *while*\n * already in edit mode when jumping from shape to shape. */\n .tl-canvas[data-iseditinganything='true'] .tl-text-wrapper:hover .tl-text-input {\n z-index: var(--tl-layer-text-editor);\n pointer-events: all;\n }\n}\n\n/* @tldraw/ui */\n\n.tl-container {\n /*\n * UI z-index layers\n *\n * menu-click-capture and panels (.tlui-layout) participate in the\n * container-level stacking context alongside the canvas layers:\n * watermark (200) < canvas-in-front (250) < menu-click-capture (250) < panels (300)\n *\n * menus, toasts, header-footer, etc. are children of .tlui-layout\n * (which creates its own stacking context at z-index 300), so their\n * z-indices are relative to the layout, not the container.\n *\n * cursor is rendered inside canvas-in-front (stacking context at\n * z-index 250), so their z-indices are relative to that context.\n */\n --tl-layer-above: 1;\n --tl-layer-focused-input: 10;\n --tl-layer-menu-click-capture: 250;\n --tl-layer-panels: 300;\n --tl-layer-menus: 400;\n --tl-layer-toasts: 650;\n --tl-layer-cursor: 700;\n --tl-layer-header-footer: 999;\n}\n\n/* Button */\n\n.tlui-button {\n position: relative;\n height: 40px;\n min-width: 40px;\n padding: 0px 12px;\n display: flex;\n align-items: center;\n justify-content: center;\n background-color: transparent;\n border: transparent;\n color: currentColor;\n cursor: pointer;\n pointer-events: all;\n font-weight: inherit;\n font-family: inherit;\n line-height: inherit;\n text-rendering: optimizeLegibility;\n font-size: 12px;\n gap: 0px;\n color: var(--tl-color-text-1);\n z-index: 0;\n}\n\n.tlui-button:disabled,\n.tlui-button[data-disabled] {\n color: var(--tl-color-text-disabled);\n text-shadow: none;\n cursor: default;\n}\n\n.tlui-button:disabled .tlui-kbd,\n.tlui-button[data-disabled] .tlui-kbd {\n color: var(--tl-color-text-disabled);\n}\n\n.tlui-button > * {\n position: relative;\n z-index: var(--tl-layer-above);\n}\n\n.tlui-button__label {\n font-size: 12px;\n flex-grow: 2;\n text-align: start;\n min-width: 0;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/*\n * We only want to show the focus ring if:\n * - the container is focused\n * - we're not using the mouse to interact (which is the .tl-container__no-focus-ring)\n */\n.tl-container__focused:not(.tl-container__no-focus-ring) .tlui-button:focus-visible {\n border-radius: 10px;\n outline: 2px solid var(--tl-color-focus);\n outline-offset: -5px;\n}\n.tl-container__focused:not(.tl-container__no-focus-ring) .tlui-button__tool:focus-visible {\n border-radius: 12px;\n}\n.tlui-slider__container:has(.tlui-slider__thumb:focus-visible) {\n border-radius: 10px;\n outline: 2px solid var(--tl-color-focus);\n outline-offset: -5px;\n}\n\n.tlui-button::after {\n display: block;\n content: '';\n position: absolute;\n inset: 4px;\n border-radius: var(--tl-radius-2);\n background: var(--tl-color-muted-2);\n opacity: 0;\n}\n\n.tlui-button__menu[data-highlighted]::after {\n opacity: 1;\n}\n\n.tlui-button[data-isactive='true']::after,\n.tlui-button[data-isactive='true']:not(:disabled, [data-disabled], :focus-visible):active:after {\n background: var(--tl-color-hint);\n opacity: 1;\n}\n\n.tlui-button[aria-expanded='true'][data-direction='left'] {\n background: linear-gradient(270deg, rgba(144, 144, 144, 0) 0%, var(--tl-color-muted-2) 100%);\n opacity: 1;\n}\n\n@media (hover: hover) {\n .tlui-button[aria-expanded='true'][data-direction='left']:not(:hover)::after {\n background: linear-gradient(270deg, rgba(144, 144, 144, 0) 0%, var(--tl-color-muted-2) 100%);\n opacity: 1;\n }\n\n .tlui-button:not(:disabled, [data-disabled]):hover {\n z-index: 1;\n }\n\n .tlui-button:not(:disabled, [data-disabled]):hover::after {\n opacity: 1;\n }\n}\n\n.tlui-button__icon + .tlui-button__label {\n margin-inline-start: var(--tl-space-2);\n}\n\n/* Low button */\n\n.tlui-button__low {\n border-radius: var(--tl-radius-3);\n background-color: var(--tl-color-low);\n}\n\n.tlui-button__low::after {\n background-color: var(--tl-color-muted-2);\n opacity: 0;\n}\n\n@media (hover: hover) {\n .tlui-button__low:hover::after {\n opacity: 1;\n }\n}\n\n/* Primary / danger buttons */\n\n.tlui-button__primary {\n color: var(--tl-color-primary);\n}\n\n.tlui-button__danger {\n color: var(--tl-color-danger);\n text-shadow: none;\n}\n\n@media (hover: hover) {\n .tlui-button__primary:not(:disabled, [data-disabled], :focus-visible):hover {\n color: var(--tl-color-primary);\n }\n\n .tlui-button__danger:not(:disabled, [data-disabled], :focus-visible):hover {\n color: var(--tl-color-danger);\n text-shadow: none;\n }\n}\n\n/* Menu button */\n\n.tlui-button__menu {\n height: 40px;\n min-height: 40px;\n width: 100%;\n gap: 8px;\n margin-top: -4px;\n}\n\n.tlui-button__menu::after {\n inset: 4px;\n border-radius: var(--tl-radius-2);\n}\n\n.tlui-button__menu > .tlui-icon + .tlui-button__label {\n margin-inline-start: 0px;\n}\n\n.tlui-button__menu:nth-child(1) {\n margin-top: 0px;\n}\n\n.tlui-button__menu:nth-last-child(1) {\n margin-bottom: 0px;\n}\n\n/* Menu checkbox button */\n\n.tlui-button__checkbox {\n padding-left: 8px;\n}\n\n.tlui-button__checkbox__indicator {\n width: 15px;\n height: 15px;\n}\n\n/* Tool lock button */\n\n.tlui-main-toolbar__lock-button {\n position: absolute;\n pointer-events: all;\n height: 40px;\n width: 40px;\n border-radius: var(--tl-radius-2);\n}\n\n.tlui-main-toolbar--horizontal .tlui-main-toolbar__lock-button {\n top: 4px;\n right: 0px;\n min-width: 0px;\n}\n\n.tlui-main-toolbar--vertical .tlui-main-toolbar__lock-button {\n bottom: 0px;\n min-height: 0px;\n}\n\n.tlui-main-toolbar__lock-button::after {\n top: 4px;\n left: 8px;\n inset: 4px;\n}\n\n/* Tool button */\n\n.tlui-button__tool {\n position: relative;\n height: 48px;\n width: 48px;\n}\n\n.tlui-button__tool::after {\n inset: 4px;\n border-radius: 8px;\n}\n\n.tlui-button__tool[aria-pressed='true'] {\n color: var(--tl-color-selected-contrast);\n}\n\n.tlui-button__tool[aria-pressed='true']:not(:disabled, [data-disabled], :focus-visible):active {\n color: var(--tl-color-selected-contrast);\n}\n\n.tlui-button__tool[aria-pressed='true']:not(:disabled, [data-disabled])::after {\n background: var(--tl-color-selected);\n opacity: 1;\n}\n\n.tlui-layout__mobile .tlui-main-toolbar--horizontal .tlui-button__tool {\n height: 48px;\n width: 43px;\n}\n\n.tlui-layout__mobile .tlui-main-toolbar--horizontal .tlui-button__tool > .tlui-icon {\n height: 16px;\n width: 16px;\n}\n\n/* Row layout */\n\n.tlui-row {\n display: flex;\n flex-direction: row;\n padding: 0 2px;\n}\n.tlui-row > * {\n margin-left: -2px;\n margin-right: -2px;\n}\n\n/* Column layout */\n\n.tlui-column {\n display: flex;\n flex-direction: column;\n padding: 2px 0;\n}\n.tlui-column > * {\n margin-top: -2px;\n margin-bottom: -2px;\n}\n\n/* Grid layout */\n\n.tlui-grid {\n display: grid;\n grid-template-columns: repeat(4, 1fr);\n grid-auto-flow: row;\n overflow: hidden;\n padding: 2px;\n}\n.tlui-grid > * {\n margin: -2px;\n}\n\n/* Zoom button */\n\n.tlui-zoom-menu__button {\n width: 60px;\n min-width: 60px;\n text-align: center;\n}\n\n.tlui-zoom-menu__button[data-state='open']::after {\n background: linear-gradient(0deg, rgba(144, 144, 144, 0) 0%, var(--tl-color-muted-2) 100%);\n opacity: 1;\n}\n\n/* --------------------- Layout --------------------- */\n\n.tlui-layout {\n position: relative;\n display: grid;\n grid-template-columns: 1fr;\n grid-template-rows: minmax(0px, 1fr) auto;\n grid-auto-rows: auto;\n height: 100%;\n max-height: 100%;\n overflow: clip;\n pointer-events: none;\n user-select: none;\n contain: strict;\n z-index: var(--tl-layer-panels);\n transform: translate3d(0, 0, 0);\n --tl-sab: env(safe-area-inset-bottom);\n font-weight: 500;\n line-height: 1.6;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n font-smooth: antialiased;\n text-rendering: optimizeLegibility;\n}\n\n.tlui-layout__top {\n grid-column: 1;\n grid-row: 1;\n display: flex;\n min-width: 0px;\n justify-content: space-between;\n}\n\n.tlui-layout__top__left {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n justify-content: flex-start;\n width: 100%;\n height: 100%;\n flex: 0 1 0;\n}\n\n.tlui-layout__top__right {\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n justify-content: flex-start;\n height: 100%;\n flex: 0 0 auto;\n min-width: 0px;\n}\n\n.tlui-scrollable,\n.tlui-scrollable * {\n pointer-events: all;\n touch-action: auto;\n overscroll-behavior: none;\n}\n\n/* ----------------- Helper Buttons ---------------- */\n\n.tlui-helper-buttons {\n position: relative;\n display: flex;\n flex-direction: column;\n justify-content: flex-start;\n align-items: flex-start;\n width: min-content;\n gap: var(--tl-space-3);\n margin: var(--tl-space-2) var(--tl-space-3);\n white-space: nowrap;\n pointer-events: none;\n z-index: var(--tl-layer-panels);\n}\n\n/* ---------------------- Icon ---------------------- */\n\n.tlui-icon {\n flex-shrink: 0;\n width: 18px;\n height: 18px;\n background-color: currentColor;\n}\n\n.tlui-icon__placeholder {\n flex-shrink: 0;\n width: 18px;\n height: 18px;\n background-color: transparent;\n}\n\n.tlui-icon__small {\n width: 15px;\n height: 15px;\n}\n\n.tlui-icon__tiny {\n width: 12px;\n height: 12px;\n}\n\n/* --------------------- Slider --------------------- */\n\n.tlui-slider__container {\n width: 100%;\n padding: 0px var(--tl-space-4);\n}\n\n.tlui-slider {\n position: relative;\n display: flex;\n align-items: center;\n user-select: none;\n touch-action: none;\n width: 100%;\n}\n\n.tlui-slider__track {\n position: relative;\n flex-grow: 1;\n height: 44px;\n cursor: pointer;\n}\n\n.tlui-slider__track::after {\n display: block;\n position: absolute;\n top: calc(50% - 2px);\n content: '';\n height: 3px;\n width: 100%;\n background-color: var(--tl-color-muted-1);\n border-radius: 14px;\n}\n\n.tlui-slider__range {\n position: absolute;\n top: calc(50% - 2px);\n left: 0px;\n height: 3px;\n background-color: var(--tl-color-selected);\n border-radius: 14px;\n}\n\n.tlui-slider__thumb {\n all: unset;\n cursor: grab;\n display: block;\n width: 18px;\n height: 18px;\n position: relative;\n top: -1px;\n background-color: var(--tl-color-panel);\n border-radius: 999px;\n box-shadow: inset 0px 0px 0px 2px var(--tl-color-text-1);\n}\n\n.tlui-slider__thumb:active {\n cursor: grabbing;\n box-shadow:\n inset 0px 0px 0px 2px var(--tl-color-text-1),\n var(--tl-shadow-1);\n}\n\n/* ---------------------- Input --------------------- */\n\n.tlui-input {\n background: none;\n margin: 0px;\n position: relative;\n z-index: var(--tl-layer-above);\n height: 40px;\n max-height: 40px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-family: inherit;\n font-size: 12px;\n font-weight: inherit;\n color: var(--tl-color-text-1);\n padding: var(--tl-space-4);\n padding-left: 0px;\n border: none;\n outline: none;\n text-overflow: ellipsis;\n width: 100%;\n user-select: all;\n text-rendering: optimizeLegibility;\n -webkit-user-select: auto !important;\n}\n\n.tlui-input::placeholder {\n color: var(--tl-color-text-3);\n}\n\n.tlui-input__wrapper {\n width: 100%;\n height: 44px;\n display: flex;\n align-items: center;\n gap: var(--tl-space-4);\n color: var(--tl-color-text);\n}\n\n.tlui-input__wrapper > .tlui-icon {\n flex-shrink: 0;\n}\n\n/* The label is a fixed bit of chrome next to the input; it should never be squeezed. Without this,\n an inherited `overflow-wrap: anywhere` (e.g. from .tlui-dialog__body) drops its min-content width\n to a single character and short labels wrap mid-word, e.g. \"URL\" breaking after \"UR\". */\n.tlui-input__wrapper > label {\n flex-shrink: 0;\n overflow-wrap: normal;\n}\n\n/* If mobile use 16px as font size */\n/* On iOS, font size under 16px in an input will make the page zoom into the input 🤦‍♂️ */\n/* https://css-tricks.com/16px-or-larger-text-prevents-ios-form-zoom/ */\n@supports (-webkit-touch-callout: none) {\n /* CSS specific to iOS devices */\n .tlui-input {\n font-size: 16px;\n }\n}\n\n/* --------------------- Select --------------------- */\n\n/* Trigger extends .tlui-button with space-between layout */\n.tlui-select__trigger {\n justify-content: space-between;\n gap: var(--tl-space-3);\n}\n\n.tlui-select__trigger[data-state='open']::after {\n opacity: 1;\n}\n\n.tlui-select__value {\n display: flex;\n align-items: center;\n gap: var(--tl-space-2);\n}\n\n.tlui-select__chevron {\n flex-shrink: 0;\n color: var(--tl-color-text-2);\n}\n\n/* Content extends .tlui-menu with Radix-aware sizing */\n.tlui-select__content {\n min-width: var(--radix-select-trigger-width);\n max-height: var(--radix-select-content-available-height);\n}\n\n.tlui-select__viewport {\n padding: 4px 0;\n}\n\n/* Item extends .tlui-button__checkbox with left-aligned text */\n.tlui-select__item {\n justify-content: flex-start;\n}\n\n.tlui-select__item-indicator {\n visibility: hidden;\n}\n\n.tlui-select__item[data-state='checked'] .tlui-select__item-indicator {\n visibility: visible;\n}\n\n/* ----------------------- Kbd ---------------------- */\n\n.tlui-kbd {\n font-family: inherit;\n font-size: 11px;\n line-height: 11px;\n display: grid;\n justify-items: center;\n grid-auto-flow: column;\n grid-template-columns: auto;\n grid-auto-columns: minmax(1em, auto);\n align-self: bottom;\n color: currentColor;\n margin-inline-start: var(--tl-space-4);\n}\n\n.tlui-kbd > span {\n width: 100%;\n text-align: center;\n display: inline;\n margin: 0px;\n padding: 2px;\n border-radius: 2px;\n}\n\n.tlui-kbd > span:last-child {\n padding-inline-end: 0;\n}\n\n.tlui-kbd:not(:last-child) {\n margin-inline-end: var(--tl-space-2);\n}\n\n/* Focus Mode Button */\n\n.tlui-focus-button {\n z-index: var(--tl-layer-panels);\n pointer-events: all;\n}\n\n/* ---------------------- Menu ---------------------- */\n\n.tlui-menu:empty {\n display: none;\n}\n\n.tlui-menu {\n z-index: var(--tl-layer-menus);\n height: fit-content;\n width: fit-content;\n border-radius: var(--tl-radius-3);\n pointer-events: all;\n touch-action: auto;\n overflow-y: auto;\n overscroll-behavior: none;\n background-color: var(--tl-color-panel);\n box-shadow: var(--tl-shadow-3);\n}\n\n@media (max-height: 600px) {\n .tlui-menu {\n max-height: 70vh;\n }\n}\n\n.tlui-menu::-webkit-scrollbar {\n display: none;\n}\n\n/* Menu groups */\n\n.tlui-menu__group {\n width: 100%;\n}\n\n.tlui-menu__group:empty {\n display: none;\n}\n\n.tlui-menu__group {\n border-bottom: 1px solid var(--tl-color-divider);\n}\n.tlui-menu__group:nth-last-of-type(1) {\n border-bottom: none;\n}\n\n/* Submenu triggers */\n\n.tlui-menu__submenu__trigger {\n --gradient-angle: 90deg;\n}\n\n.tlui-menu__submenu__trigger[data-direction='left'] {\n --gradient-angle: 270deg;\n}\n\n.tlui-menu__submenu__trigger[data-state='open']::after {\n opacity: 1;\n background: linear-gradient(\n var(--gradient-angle),\n rgba(144, 144, 144, 0) 0%,\n var(--tl-color-muted-2) 100%\n );\n}\n\n@media (hover: hover) {\n .tlui-menu__submenu__trigger:is(:hover, [data-state='open'])::after {\n opacity: 1;\n background: linear-gradient(\n var(--gradient-angle),\n rgba(144, 144, 144, 0) 0%,\n var(--tl-color-muted-2) 100%\n );\n }\n}\n\n/* Menu Sizes */\n\n.tlui-menu[data-size='large'] > .tlui-menu__group {\n min-width: initial;\n}\n\n.tlui-menu[data-size='medium'] > .tlui-menu__group {\n min-width: 144px;\n}\n\n.tlui-menu[data-size='small'] > .tlui-menu__group {\n min-width: 96px;\n}\n\n.tlui-menu[data-size='tiny'] > .tlui-menu__group {\n min-width: 0px;\n}\n\n.tlui-menu-click-capture {\n position: fixed;\n inset: 0;\n z-index: var(--tl-layer-menu-click-capture);\n}\n\n/* --------------------- Popover -------------------- */\n\n.tlui-popover {\n position: relative;\n display: flex;\n align-content: stretch;\n}\n\n.tlui-popover__content {\n position: relative;\n max-height: calc(var(--radix-popover-content-available-height) - 8px);\n margin: 0px;\n border: none;\n border-radius: var(--tl-radius-3);\n background-color: var(--tl-color-panel);\n box-shadow: var(--tl-shadow-3);\n z-index: var(--tl-layer-menus);\n overflow: hidden;\n overflow-y: auto;\n touch-action: auto;\n overscroll-behavior: none;\n scrollbar-width: none;\n -ms-overflow-style: none;\n}\n\n.tlui-hover-card__content {\n position: relative;\n margin: 0px;\n border: none;\n border-radius: var(--tl-radius-3);\n background-color: var(--tl-color-panel);\n box-shadow: var(--tl-shadow-3);\n z-index: var(--tl-layer-menus);\n}\n\n/* -------------------- Menu Zone ------------------- */\n\n.tlui-menu-zone {\n position: relative;\n z-index: var(--tl-layer-panels);\n width: fit-content;\n border-right: 2px solid var(--tl-color-background);\n border-bottom: 2px solid var(--tl-color-background);\n border-bottom-right-radius: var(--tl-radius-4);\n background-color: var(--tl-color-low);\n}\n\n.tlui-menu-zone *[data-state='open']::after {\n background: linear-gradient(180deg, rgba(144, 144, 144, 0) 0%, var(--tl-color-muted-2) 100%);\n opacity: 1;\n}\n\n@media (hover: hover) {\n .tlui-menu-zone *[data-state='open']:not(:hover)::after {\n background: linear-gradient(180deg, rgba(144, 144, 144, 0) 0%, var(--tl-color-muted-2) 100%);\n opacity: 1;\n }\n}\n\n/* ------------------- Page Select ------------------ */\n\n.tlui-page-menu__wrapper {\n display: flex;\n flex-direction: column;\n width: 220px;\n}\n\n.tlui-page-menu__trigger {\n width: auto;\n}\n\n.tlui-page-menu__trigger[data-state='open']::after {\n background: linear-gradient(180deg, rgba(144, 144, 144, 0) 0%, var(--tl-color-muted-2) 100%);\n opacity: 1;\n}\n\n.tlui-page-menu__list {\n flex: 0 0 auto;\n overflow-x: hidden;\n overflow-y: auto;\n scrollbar-width: none;\n -ms-overflow-style: none;\n}\n\n.tlui-page-menu__list::-webkit-scrollbar {\n display: none;\n}\n\n.tlui-page-menu__list__content {\n position: relative;\n overflow: hidden;\n}\n\n.tlui-page-menu__resize-handle {\n flex: 0 0 auto;\n position: relative;\n height: 1px;\n cursor: ns-resize;\n touch-action: none;\n background: var(--tl-color-divider);\n transition: background-color ease-in-out 150ms 80ms;\n z-index: 999;\n}\n\n/* Hit test area */\n.tlui-page-menu__resize-handle::after {\n content: '';\n position: absolute;\n inset: -4px 0;\n background: none;\n z-index: 2000;\n pointer-events: all;\n}\n\n/* Thick blue indicator */\n.tlui-page-menu__resize-handle::before {\n content: '';\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n height: 1.5px;\n background: transparent;\n pointer-events: none;\n z-index: 2001;\n}\n\n.tlui-page-menu__resize-handle[data-resizing='true']::before {\n background: var(--tl-color-selected);\n transition: none;\n}\n\n.tlui-page-menu__create-button {\n flex: 0 0 auto;\n margin: 0;\n}\n\n.tlui-page-menu__item__button {\n flex: 1 1 auto;\n min-width: 0px;\n}\n\n.tlui-page-menu__item__button::after,\n.tlui-page-menu__item__drag-handle::after {\n content: none;\n}\n\n.tlui-page-menu__item__button > .tlui-button__label {\n padding-inline-end: calc(40px - 12px);\n}\n\n.tlui-page-menu__item {\n position: absolute;\n top: 0px;\n inset-inline-start: 0px;\n width: 100%;\n height: 40px;\n display: flex;\n flex-direction: row;\n align-items: center;\n overflow: hidden;\n transition: transform 0.08s ease-out;\n}\n\n.tlui-page-menu__item::before {\n content: '';\n position: absolute;\n inset: 4px;\n border-radius: var(--tl-radius-2);\n background: var(--tl-color-muted-2);\n opacity: 0;\n pointer-events: none;\n}\n\n.tlui-page-menu__item > * {\n position: relative;\n z-index: var(--tl-layer-above);\n}\n\n.tlui-page-menu__item[data-iscurrent='true']:not([data-editing='true'])::before {\n background: var(--tl-color-hint);\n opacity: 1;\n}\n\n.tlui-page-menu__item__title {\n flex: 1;\n height: 40px;\n position: relative;\n}\n\n.tlui-page-menu__item__title::before {\n content: '';\n position: absolute;\n inset: 4px;\n border-radius: var(--tl-radius-2);\n border: 1px solid var(--tl-color-primary);\n pointer-events: none;\n}\n\n.tlui-page-menu__item__title > .tlui-input__wrapper {\n height: 100%;\n}\n\n.tlui-page-menu__item[data-dragging='true'],\n.tlui-page-menu__item[data-dragging='true'] .tlui-page-menu__item__button,\n.tlui-page-menu__item[data-dragging='true'] .tlui-page-menu__item__drag-handle {\n cursor: grabbing;\n transition: none;\n}\n\n.tlui-page-menu__item[data-dragging='true'] {\n background: var(--tl-color-panel);\n opacity: 1;\n}\n\n.tlui-page-menu__item__drag-handle {\n flex: 0 0 32px;\n min-width: 32px;\n padding: 0px;\n color: var(--tl-color-text-3);\n cursor: grab;\n touch-action: none;\n}\n\n.tlui-page-menu__item__drag-handle > .tlui-icon {\n position: relative;\n inset-inline-start: 1px;\n}\n\n.tlui-page-menu__item__drag-handle + .tlui-page-menu__item__button {\n padding-inline-start: 0px;\n}\n\n.tlui-page-menu__item__input {\n margin-inline-start: 12px;\n}\n\n.tlui-page-menu__item__submenu {\n position: absolute;\n inset-inline-end: 0px;\n top: 0px;\n height: 100%;\n z-index: 10;\n}\n\n.tlui-page-menu__item__submenu > .tlui-button {\n opacity: 0;\n color: var(--tl-color-text-3);\n}\n\n.tlui-page-menu__item__submenu > .tlui-button::after {\n content: none;\n}\n\n.tlui-page-menu__item:has(.tlui-button:focus-visible)\n > .tlui-page-menu__item__submenu\n > .tlui-button,\n.tlui-page-menu__item__submenu > .tlui-button[data-state='open'],\n.tlui-page-menu__item__submenu > .tlui-button:focus-visible {\n opacity: 1;\n}\n\n/* Hide the kebab while any row is being dragged: :hover sticks during pointer */\n/* capture, which would otherwise leave the kebab visible mid-drag. */\n.tlui-page-menu__list__content[data-dragging='true']\n .tlui-page-menu__item\n > .tlui-page-menu__item__submenu\n > .tlui-button {\n opacity: 0;\n}\n\n.tl-container[data-coarse='true'] .tlui-page-menu__item__submenu > .tlui-button {\n opacity: 1;\n}\n\n.tl-container[data-coarse='true'] .tlui-page-menu__item__button > .tlui-button__label {\n padding-inline-end: calc(40px - 12px);\n}\n\n@media (hover: hover) {\n .tlui-page-menu__resize-handle:hover::before {\n background: var(--tl-color-selected);\n height: 2px;\n transition: background-color 150ms 80ms;\n }\n\n .tlui-page-menu__item:not([data-editing='true']):hover::before {\n opacity: 1;\n }\n\n .tlui-page-menu__item:hover > .tlui-page-menu__item__submenu > .tlui-button {\n opacity: 1;\n }\n\n .tlui-page-menu__item__button > .tlui-button__label {\n padding-inline-end: 0px;\n }\n\n .tlui-page-menu__item:hover > .tlui-page-menu__item__button > .tlui-button__label {\n padding-inline-end: calc(40px - 12px);\n }\n\n .tlui-page-menu__item__submenu > .tlui-button:hover {\n color: var(--tl-color-text);\n }\n}\n/* -------------- Skip to main content -------------- */\n\n.tl-skip-to-main-content {\n position: fixed;\n top: 48px;\n left: -9999px;\n padding: 8px 16px;\n z-index: var(--tl-layer-toasts);\n}\n\n.tl-skip-to-main-content:focus {\n left: 8px;\n}\n\n/*\n * focusing skip-to-main-content hide top ui buttons to make sure we avoid\n * z-index clashes on hover (see issue #8328)\n */\n.tl-skip-to-main-content:focus + .tlui-layout__top .tlui-helper-buttons {\n display: none;\n}\n\n/* ---------------- Offline indicator --------------- */\n\n.tlui-offline-indicator {\n display: flex;\n color: var(--tl-color-background);\n background-color: var(--tl-color-text);\n border: none;\n width: 32px;\n height: 32px;\n align-items: center;\n justify-content: center;\n border-radius: 99px;\n cursor: default;\n opacity: 0;\n animation: tl-fade-in;\n animation-duration: 0.12s;\n animation-delay: 2s;\n animation-fill-mode: forwards;\n}\n\n/* ------------------- Style panel ------------------ */\n\n.tlui-style-panel__wrapper {\n box-shadow: var(--tl-shadow-2);\n border-radius: var(--tl-radius-3);\n pointer-events: all;\n background-color: var(--tl-color-panel);\n height: fit-content;\n max-height: 100%;\n margin: 8px;\n margin-top: 4px;\n touch-action: auto;\n overscroll-behavior: none;\n overflow-y: auto;\n overflow-x: hidden;\n color: var(--tl-color-text);\n}\n/* if the style panel is the only child (ie no share menu), increase the margin */\n.tlui-style-panel__wrapper:only-child {\n margin-top: 8px;\n}\n\n.tlui-style-panel {\n position: relative;\n z-index: var(--tl-layer-panels);\n pointer-events: all;\n width: 148px;\n max-width: 148px;\n}\n\n.tlui-style-panel[data-enhanced-a11y-mode='true'] .tlui-button[data-isactive='true'] {\n border-radius: 10px;\n outline: 2px solid var(--tl-color-text);\n outline-offset: -5px;\n}\n\n.tlui-style-panel::-webkit-scrollbar {\n display: none;\n}\n\n.tlui-style-panel .tlui-button.select {\n width: 100%;\n}\n\n.tlui-style-panel__section {\n display: flex;\n position: relative;\n flex-direction: column;\n}\n\n/*\nadd a border to the bottom of all but the last section. we have to handle empty sections too, which\nare hidden and shouldn't be counted\n*/\n.tlui-style-panel__section:not(:nth-last-child(-n + 1 of .tlui-style-panel__section:not(:empty))) {\n border-bottom: 1px solid var(--tl-color-divider);\n}\n/*\nif a section ends with a slider and we're adding a border, we need some extra space for visual\nbalance. we need to handle empty sections as above. is this the most complex css selector in all of\ntldraw? probably.\n*/\n.tlui-style-panel__section:has(.tlui-slider__container:last-child):not(\n :nth-last-child(-n + 1 of .tlui-style-panel__section:not(:empty))\n ) {\n margin-bottom: 7px;\n}\n\n.tlui-style-panel__section:empty {\n display: none;\n}\n\n/*\n * This is used in a couple places, like Align and Vertical Align.\n * It's because we have a toolbar with a Toggle Group but then an adjacent button\n * next to it that opens a popup.\n */\n.tlui-style-panel__section .tlui-toolbar:has(.tlui-toolbar) {\n flex-wrap: wrap;\n}\n\n.tlui-style-panel__section .tlui-toolbar:has(.tlui-toolbar) .tlui-style-panel__subheading {\n margin-left: -2px;\n}\n\n.tlui-style-panel__section__common:not(:only-child) {\n margin-bottom: 7px;\n border-bottom: 1px solid var(--tl-color-divider);\n}\n\n.tlui-toolbar-toggle-group-item,\n.tlui-style-panel__dropdown-picker > .tlui-button__icon {\n width: 40px;\n padding: 0;\n}\n\n.tlui-style-panel__dropdown-picker:only-child {\n flex: 1;\n}\n\n.tlui-style-panel__double-select-picker {\n display: flex;\n grid-template-columns: 1fr auto;\n align-items: center;\n padding-left: var(--tl-space-4);\n color: var(--tl-color-text-1);\n font-size: 12px;\n}\n\n.tlui-style-panel__double-select-picker-label {\n text-overflow: ellipsis;\n overflow: hidden;\n white-space: nowrap;\n flex-grow: 2;\n max-width: 100%;\n}\n\n.tlui-style-panel .tlui-button[data-state='open']::after {\n opacity: 1;\n background: linear-gradient(270deg, rgba(144, 144, 144, 0) 0%, var(--tl-color-muted-2) 100%);\n}\n\n@media (hover: hover) {\n .tlui-style-panel .tlui-button[aria-expanded='true'] {\n background: none;\n }\n .tlui-style-panel .tlui-button[data-state='open']:not(:hover)::after {\n opacity: 1;\n background: linear-gradient(270deg, rgba(144, 144, 144, 0) 0%, var(--tl-color-muted-2) 100%);\n }\n}\n\n/* Accessibility subheadings */\n\n.tlui-style-panel__section .tlui-style-panel__subheading,\n.tlui-style-panel__section__common .tlui-style-panel__subheading,\n.tlui-style-panel__subheading + .tlui-slider__container {\n margin: 0;\n padding: var(--tl-space-2) var(--tl-space-3) 0px var(--tl-space-4);\n font-size: 12px;\n font-weight: inherit;\n line-height: inherit;\n}\n\n.tlui-style-panel .tlui-style-panel__subheading:nth-of-type(1) {\n padding-top: var(--tl-space-3);\n}\n\n.tlui-style-panel__subheading + .tlui-slider__container {\n padding-top: 0px;\n}\n\n/* --------------------- Bottom --------------------- */\n\n.tlui-layout__bottom {\n grid-row: 2;\n width: 100%;\n min-width: 0px;\n}\n\n.tlui-layout__bottom__main {\n width: 100%;\n display: flex;\n align-items: flex-end;\n justify-content: center;\n}\n\n/* ------------------- Navigation ------------------- */\n\n.tlui-navigation-panel {\n display: flex;\n width: min-content;\n flex-direction: column;\n z-index: var(--tl-layer-panels);\n pointer-events: all;\n position: absolute;\n left: 0px;\n}\n\n.tl-container[dir='rtl'] .tlui-navigation-panel {\n left: auto;\n right: 0px;\n}\n\n.tlui-navigation-panel::before {\n content: '';\n display: block;\n position: absolute;\n z-index: -1;\n inset: -2px -2px 0px 0px;\n border-radius: 0;\n border-top: 2px solid var(--tl-color-background);\n border-right: 2px solid var(--tl-color-background);\n border-top-right-radius: var(--tl-radius-4);\n background-color: var(--tl-color-low);\n}\n\n.tl-container[dir='rtl'] .tlui-navigation-panel::before {\n inset: -2px 0px 0px -2px;\n border-right: 0;\n border-left: 2px solid var(--tl-color-background);\n border-top-right-radius: 0;\n border-top-left-radius: var(--tl-radius-4);\n}\n\n.tlui-navigation-panel[data-a11y='true']::before {\n display: none;\n}\n\n/* Minimap */\n\n.tlui-minimap {\n width: 100%;\n height: 96px;\n min-height: 96px;\n overflow: hidden;\n padding: var(--tl-space-3);\n padding-top: 0px;\n}\n\n.tlui-minimap__canvas {\n position: relative;\n width: 100%;\n height: 100%;\n}\n\n/* --------------------- Toolbar -------------------- */\n\n/* Wide container */\n.tlui-main-toolbar {\n grid-column: 1 / span 3;\n grid-row: 1;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-grow: 2;\n}\n\n.tlui-main-toolbar--horizontal {\n padding-bottom: calc(var(--tl-space-3) + var(--tl-sab));\n max-width: 100%;\n}\n\n.tlui-main-toolbar--vertical {\n position: absolute;\n left: 0;\n top: 90px; /* height of page menu + 'back to content' button */\n bottom: 140px; /* height of expanded mini-map */\n padding-left: calc(var(--tl-space-3) + var(--tl-sab));\n}\n\n[data-breakpoints-below*='6'] .tlui-main-toolbar--vertical {\n bottom: 90px; /* collapsed mini-map, but same as top to keep things looking even */\n}\n\n/* Centered Content */\n.tlui-main-toolbar__inner {\n position: relative;\n width: fit-content;\n display: flex;\n gap: var(--tl-space-3);\n align-items: flex-start;\n}\n\n.tlui-main-toolbar--vertical .tlui-main-toolbar__inner {\n flex-direction: column;\n}\n\n.tlui-main-toolbar--horizontal .tlui-main-toolbar__left {\n width: fit-content;\n}\n.tlui-main-toolbar--vertical .tlui-main-toolbar__left {\n display: flex;\n height: fit-content;\n}\n\n/* Row of controls + lock button */\n.tlui-main-toolbar__extras {\n position: relative;\n z-index: var(--tl-layer-above);\n pointer-events: none;\n align-self: stretch;\n}\n\n.tlui-main-toolbar__extras:empty {\n display: none;\n}\n\n.tlui-main-toolbar--horizontal .tlui-main-toolbar__extras {\n height: 48px;\n top: 6px;\n}\n\n.tlui-main-toolbar--vertical .tlui-main-toolbar__extras {\n width: 48px;\n order: 1;\n}\n\n.tlui-main-toolbar__extras__controls {\n position: relative;\n z-index: var(--tl-layer-above);\n background-color: var(--tl-color-low);\n border: 2px solid var(--tl-color-background);\n pointer-events: all;\n}\n\n.tlui-main-toolbar--horizontal .tlui-main-toolbar__extras__controls {\n border-top-left-radius: var(--tl-radius-4);\n border-top-right-radius: var(--tl-radius-4);\n margin-left: 8px;\n margin-right: 0px;\n width: fit-content;\n}\n\n.tlui-main-toolbar--vertical .tlui-main-toolbar__extras__controls {\n border-top-right-radius: var(--tl-radius-4);\n border-bottom-right-radius: var(--tl-radius-4);\n margin-top: 8px;\n margin-left: -2px;\n margin-bottom: 0px;\n width: fit-content;\n}\n\n.tlui-main-toolbar__tools {\n border-radius: var(--tl-radius-4);\n z-index: var(--tl-layer-panels);\n pointer-events: all;\n position: relative;\n background: var(--tl-color-panel);\n box-shadow: var(--tl-shadow-2);\n contain: layout style paint;\n}\n\n.tlui-main-toolbar--horizontal .tlui-main-toolbar__mobile-style-panel {\n align-self: flex-end;\n}\n.tlui-main-toolbar--vertical .tlui-main-toolbar__mobile-style-panel {\n align-self: flex-start;\n}\n\n.tlui-main-toolbar--horizontal .tlui-main-toolbar__overflow {\n width: 40px;\n margin-left: 2px;\n}\n.tlui-main-toolbar--vertical .tlui-main-toolbar__overflow {\n height: 40px;\n margin-top: 2px;\n}\n\n.tlui-layout__mobile .tlui-main-toolbar--horizontal .tlui-main-toolbar__overflow {\n width: 32px;\n padding: 0px;\n}\n\n.tlui-main-toolbar--horizontal *[data-state='open']::after {\n background: linear-gradient(0deg, rgba(144, 144, 144, 0) 0%, var(--tl-color-muted-2) 100%);\n opacity: 1;\n}\n.tlui-main-toolbar--vertical *[data-state='open']::after {\n background: linear-gradient(90deg, rgba(144, 144, 144, 0) 0%, var(--tl-color-muted-2) 100%);\n opacity: 1;\n}\n\n.tlui-main-toolbar__overflow-content {\n touch-action: none;\n}\n\n.tlui-main-toolbar__tools [data-toolbar-visible='false'],\n.tlui-main-toolbar__overflow-content [data-toolbar-visible='false'] {\n display: none;\n}\n\n.tlui-main-toolbar__group:empty {\n display: none;\n}\n.tlui-row.tlui-main-toolbar__group:not(:nth-last-child(-n + 1 of [data-toolbar-visible='true'])) {\n border-right: 1px solid var(--tl-color-divider);\n margin-right: 2px;\n}\n.tlui-column.tlui-main-toolbar__group:not(\n :nth-last-child(-n + 1 of [data-toolbar-visible='true'])\n ) {\n border-bottom: 1px solid var(--tl-color-divider);\n margin-bottom: 2px;\n}\n.tlui-grid.tlui-main-toolbar__group {\n grid-column: 1 / span 4;\n}\n.tlui-grid.tlui-main-toolbar__group:not(:nth-last-child(-n + 1 of [data-toolbar-visible='true'])) {\n border-bottom: 1px solid var(--tl-color-divider);\n margin-bottom: 2px;\n}\n\n@media (hover: hover) {\n .tlui-main-toolbar--horizontal *[data-state='open']:not(:hover)::after {\n background: linear-gradient(0deg, rgba(144, 144, 144, 0) 0%, var(--tl-color-muted-2) 100%);\n opacity: 1;\n }\n .tlui-main-toolbar--vertical *[data-state='open']:not(:hover)::after {\n background: linear-gradient(90deg, rgba(144, 144, 144, 0) 0%, var(--tl-color-muted-2) 100%);\n opacity: 1;\n }\n}\n\n/* ------------------- Tooltip -------------------- */\n\n.tlui-tooltip {\n font-size: 12px;\n padding: 2px 8px;\n border-radius: 4px;\n background-color: var(--tl-color-tooltip);\n box-shadow: none;\n color: var(--tl-color-text-shadow);\n max-width: 400px;\n width: fit-content;\n text-align: center;\n will-change: transform, opacity;\n z-index: 2;\n}\n\n.tlui-tooltip__arrow {\n fill: var(--tl-color-tooltip);\n will-change: opacity;\n}\n\n[data-radix-popper-content-wrapper]:has(.tlui-tooltip) {\n z-index: var(--tl-layer-toasts) !important;\n pointer-events: none;\n}\n\n/* ------------------- Debug panel ------------------ */\n\n.tlui-debug-panel {\n background-color: var(--tl-color-low);\n width: 100%;\n display: grid;\n align-items: center;\n grid-template-columns: 1fr auto auto auto;\n justify-content: space-between;\n padding-inline-start: var(--tl-space-4);\n border-top: 1px solid var(--tl-color-background);\n font-size: 12px;\n color: var(--tl-color-text-1);\n z-index: var(--tl-layer-panels);\n pointer-events: all;\n}\n\n.tlui-debug-panel__current-state {\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n}\n\n.tlui-debug-panel__fps {\n margin-right: 8px;\n}\n\n.tlui-debug-panel__fps__slow {\n font-weight: bold;\n color: var(--tl-color-danger);\n}\n\n.tlui-a11y-audit {\n border-collapse: collapse;\n}\n\n.tlui-a11y-audit th,\n.tlui-a11y-audit td {\n padding: 8px;\n border: 1px solid var(--tl-color-low-border);\n}\n\n/* --------------------- Toasts --------------------- */\n\n.tlui-toast__viewport {\n position: absolute;\n inset: 0px;\n margin: 0px;\n display: flex;\n align-items: flex-end;\n justify-content: flex-end;\n flex-direction: column;\n gap: var(--tl-space-3);\n pointer-events: none;\n padding: 0px var(--tl-space-3) 64px 0px;\n z-index: var(--tl-layer-toasts);\n}\n\n.tl-container[dir='rtl'] .tlui-toast__viewport {\n padding: 0px 0px 64px var(--tl-space-3);\n}\n\n.tlui-toast__viewport > * {\n pointer-events: all;\n}\n\n.tlui-toast__icon {\n padding-top: 11px;\n padding-inline-start: var(--tl-space-4);\n color: var(--tl-color-text-1);\n}\n\n.tlui-toast__container {\n min-width: 200px;\n display: flex;\n flex-direction: row;\n background-color: var(--tl-color-panel);\n box-shadow: var(--tl-shadow-2);\n border-radius: var(--tl-radius-3);\n font-size: 12px;\n}\n\n.tlui-toast__container[data-severity='success'] .tlui-icon {\n color: var(--tl-color-success);\n}\n\n.tlui-toast__container[data-severity='info'] .tlui-icon {\n color: var(--tl-color-info);\n}\n\n.tlui-toast__container[data-severity='warning'] .tlui-icon {\n color: var(--tl-color-warning);\n}\n\n.tlui-toast__container[data-severity='error'] .tlui-icon {\n color: var(--tl-color-danger);\n}\n\n.tlui-toast__main {\n flex-grow: 2;\n max-width: 280px;\n min-width: 0;\n}\n\n.tlui-toast__content {\n padding: var(--tl-space-4);\n display: flex;\n line-height: 1.4;\n flex-direction: column;\n gap: var(--tl-space-3);\n min-width: 0;\n}\n\n.tlui-toast__main[data-actions='true'] .tlui-toast__content {\n padding-bottom: var(--tl-space-2);\n}\n\n.tlui-toast__title {\n font-weight: bold;\n color: var(--tl-color-text-1);\n /* this makes the default toast look better */\n line-height: 16px;\n overflow-wrap: anywhere;\n}\n\n.tlui-toast__description {\n color: var(--tl-color-text-1);\n padding: var(--tl-space-3);\n margin: 0px;\n padding: 0px;\n overflow-wrap: anywhere;\n}\n\n.tlui-toast__icon + .tlui-toast__main > .tlui-toast__actions {\n padding-left: 0px;\n}\n\n.tlui-toast__actions {\n display: flex;\n flex-direction: row;\n justify-content: flex-start;\n margin-left: 0;\n}\n\n.tlui-toast__close {\n align-self: flex-end;\n flex-shrink: 0;\n}\n\n@media (prefers-reduced-motion: no-preference) {\n .tlui-toast__container[data-state='open'] {\n animation: tlui-slide-in 200ms cubic-bezier(0.785, 0.135, 0.15, 0.86);\n }\n\n .tlui-toast__container[data-state='closed'] {\n animation: tlui-fade-out 100ms ease-in;\n }\n\n .tlui-toast__container[data-swipe='move'] {\n transform: translateX(var(--radix-toast-swipe-move-x));\n }\n\n .tlui-toast__container[data-swipe='cancel'] {\n transform: translateX(0);\n transition: transform 200ms ease-out;\n }\n\n .tlui-toast__container[data-swipe='end'] {\n animation: tlui-slide-out 100ms ease-out;\n }\n\n .tl-container[dir='rtl'] .tlui-toast__container[data-state='open'] {\n animation: tlui-slide-in-rtl 200ms cubic-bezier(0.785, 0.135, 0.15, 0.86);\n }\n\n .tl-container[dir='rtl'] .tlui-toast__container[data-state='closed'] {\n animation: tlui-fade-out 100ms ease-in;\n }\n\n .tl-container[dir='rtl'] .tlui-toast__container[data-swipe='end'] {\n animation: tlui-slide-out-rtl 100ms ease-out;\n }\n}\n\n/* ---------------- Dialog ---------------- */\n\n.tlui-dialog__overlay {\n position: absolute;\n top: 0px;\n left: 0px;\n width: 100%;\n height: 100%;\n z-index: var(--tl-layer-canvas-overlays);\n background-color: var(--tl-color-overlay);\n animation: tl-fade-in 0.12s ease-out;\n}\n\n.tlui-dialog__positioner {\n position: absolute;\n top: 0px;\n left: 0px;\n width: 100%;\n height: 100%;\n z-index: var(--tl-layer-canvas-overlays);\n pointer-events: all;\n display: grid;\n place-items: center;\n overflow-y: auto;\n padding: 0px var(--tl-space-3);\n}\n\n.tlui-dialog__content {\n display: flex;\n flex-direction: column;\n position: relative;\n cursor: default;\n background-color: var(--tl-color-panel);\n box-shadow: var(--tl-shadow-3);\n border-radius: var(--tl-radius-3);\n font-size: 12px;\n overflow: hidden;\n min-width: 300px;\n max-width: 100%;\n max-height: 80%;\n}\n\n.tlui-dialog__header {\n position: relative;\n display: flex;\n align-items: center;\n flex: 0;\n z-index: var(--tl-layer-header-footer);\n padding-inline-start: var(--tl-space-4);\n color: var(--tl-color-text);\n height: 40px;\n}\n\n.tlui-dialog__header__title {\n flex: 1;\n font-weight: inherit;\n font-size: 12px;\n margin: 0px;\n color: var(--tl-color-text-1);\n}\n\n.tlui-dialog__header__close {\n justify-self: flex-end;\n}\n\n.tlui-dialog__body {\n padding: var(--tl-space-4) var(--tl-space-4);\n flex: 0 1;\n overflow-y: auto;\n overflow-x: hidden;\n color: var(--tl-color-text-1);\n user-select: all;\n -webkit-user-select: text;\n overflow-wrap: anywhere;\n}\n.tlui-dialog__body a {\n color: var(--tl-color-selected);\n}\n\n.tlui-dialog__body ul,\n.tlui-dialog__body ol {\n padding-left: 16px;\n display: flex;\n flex-direction: column;\n gap: var(--tl-space-2);\n}\n\n.tlui-dialog__footer {\n position: relative;\n min-height: 12px;\n z-index: var(--tl-layer-header-footer);\n}\n\n.tlui-dialog__footer__actions {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n}\n\n.tlui-dialog__footer__actions > .tlui-button:nth-last-child(n + 2) {\n margin-right: -4px;\n}\n\n/* --------------------- Dialogs -------------------- */\n\n/* Edit Link Dialog */\n\n.tlui-edit-link-dialog {\n display: flex;\n flex-direction: column;\n gap: var(--tl-space-4);\n color: var(--tl-color-text);\n}\n\n.tlui-edit-link-dialog__input {\n background-color: var(--tl-color-muted-2);\n flex-grow: 2;\n border-radius: var(--tl-radius-2);\n padding: 0px var(--tl-space-4);\n}\n\n/* Embed Dialog */\n\n.tlui-embed__spacer {\n flex-grow: 2;\n min-height: 0px;\n margin-left: calc(-1 * var(--tl-space-4));\n margin-top: calc(-1 * var(--tl-space-4));\n pointer-events: none;\n}\n\n.tlui-embed-dialog__list {\n display: flex;\n flex-direction: column;\n padding: 0px var(--tl-space-3) var(--tl-space-4) var(--tl-space-3);\n}\n\n.tlui-embed-dialog__item__image {\n width: 24px;\n height: 24px;\n display: flex;\n align-items: center;\n justify-content: center;\n background-size: contain;\n background-repeat: no-repeat;\n background-position: center center;\n background-color: var(--tl-color-selected-contrast);\n border-radius: var(--tl-radius-1);\n}\n\n.tlui-embed-dialog__enter {\n display: flex;\n flex-direction: column;\n gap: var(--tl-space-4);\n color: var(--tl-color-text-1);\n}\n\n.tlui-embed-dialog__input {\n background-color: var(--tl-color-muted-2);\n flex-grow: 2;\n border-radius: var(--tl-radius-2);\n padding: 0px var(--tl-space-4);\n}\n\n.tlui-embed-dialog__warning {\n color: var(--tl-color-danger);\n text-shadow: none;\n}\n\n.tlui-embed-dialog__instruction__link {\n display: flex;\n gap: var(--tl-space-1);\n margin-top: var(--tl-space-4);\n}\n\n.tlui-embed-dialog__enter a {\n color: var(--tl-color-text-1);\n}\n\n/* --------------- Keyboard shortcuts --------------- */\n\n.tlui-shortcuts-dialog__header {\n border-bottom: 1px solid var(--tl-color-divider);\n}\n\n.tlui-shortcuts-dialog__body {\n position: relative;\n columns: 3;\n column-gap: var(--tl-space-9);\n pointer-events: all;\n touch-action: auto;\n\n /* Terrible fix to allow firefox users to scroll the dialog */\n overflow-x: auto;\n\n scrollbar-width: none;\n}\n\n.tlui-shortcuts-dialog__body::-webkit-scrollbar {\n display: none;\n}\n\n.tlui-shortcuts-dialog__body__tablet {\n columns: 2;\n}\n\n.tlui-shortcuts-dialog__body__mobile {\n columns: 1;\n}\n\n.tlui-shortcuts-dialog__group {\n break-inside: avoid-column;\n padding-bottom: var(--tl-space-6);\n}\n\n.tlui-shortcuts-dialog__group__title {\n font-size: inherit;\n font-weight: inherit;\n margin: 0px;\n color: var(--tl-color-text-3);\n height: 32px;\n display: flex;\n align-items: center;\n}\n\n.tlui-shortcuts-dialog__group__content {\n display: flex;\n flex-direction: column;\n color: var(--tl-color-text-1);\n}\n\n.tlui-shortcuts-dialog__key-pair {\n display: flex;\n gap: var(--tl-space-4);\n align-items: center;\n justify-content: space-between;\n height: 32px;\n}\n\n.tlui-shortcuts-dialog__key-pair__key {\n flex: 1;\n font-size: 12px;\n}\n\n/* ------------------ Language menu ----------------- */\n\n.tlui-language-menu {\n max-height: 500px;\n}\n\n.tlui-language-menu::after {\n content: '';\n display: block;\n position: absolute;\n bottom: 0px;\n left: 0px;\n right: 0px;\n height: 24px;\n background: linear-gradient(\n to bottom,\n var(--tl-color-panel-transparent) 0%,\n var(--tl-color-panel) 90%,\n var(--tl-color-panel) 100%\n );\n border-bottom-left-radius: var(--tl-radius-3);\n border-bottom-right-radius: var(--tl-radius-3);\n pointer-events: none;\n}\n\n/* ------------------ Actions menu ------------------ */\n\n.tlui-actions-menu {\n max-height: calc(100vh - 150px);\n}\n\n/* -------------------- Help menu ------------------- */\n\n.tlui-help-menu {\n pointer-events: all;\n position: absolute;\n bottom: var(--tl-space-2);\n right: var(--tl-space-2);\n z-index: var(--tl-layer-panels);\n border: 2px solid var(--tl-color-background);\n border-radius: 100%;\n}\n\n/* ------------------- Da share zone ------------------ */\n\n.tlui-share-zone {\n padding: 0px 0px 0px 0px;\n display: flex;\n flex-direction: row;\n justify-content: flex-end;\n z-index: var(--tl-layer-panels);\n align-items: center;\n padding-top: 2px;\n padding-right: 4px;\n}\n\n/* ------------------- People Menu ------------------- */\n\n.tlui-people-menu__avatars-button {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n background: none;\n border: none;\n cursor: pointer;\n pointer-events: all;\n border-radius: var(--tl-radius-1);\n padding-right: 1px;\n height: 100%;\n}\n\n.tlui-people-menu__avatars {\n display: flex;\n flex-direction: row;\n}\n\n.tlui-people-menu__avatar {\n height: 24px;\n width: 24px;\n border: 2px solid var(--tl-color-background);\n background-color: var(--tl-color-low);\n border-radius: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n position: relative;\n font-size: 10px;\n font-weight: bold;\n text-align: center;\n color: var(--tl-color-selected-contrast);\n z-index: 2;\n}\n\n.tlui-people-menu__avatar:nth-of-type(n + 2) {\n margin-inline-start: -12px;\n}\n\n.tlui-people-menu__avatars-button[data-state='open'] {\n opacity: 1;\n}\n\n@media (hover: hover) {\n .tlui-people-menu__avatars-button:hover .tlui-people-menu__avatar {\n border-color: var(--tl-color-low);\n }\n}\n\n.tlui-people-menu__more {\n min-width: 0px;\n font-size: 11px;\n font-weight: 600;\n color: var(--tl-color-text-1);\n font-family: inherit;\n padding: 0px 4px;\n}\n.tlui-people-menu__more::after {\n border-radius: var(--tl-radius-2);\n inset: 0px;\n}\n\n.tlui-people-menu__wrapper {\n position: relative;\n display: flex;\n flex-direction: column;\n width: 220px;\n height: fit-content;\n max-height: 50vh;\n}\n\n.tlui-people-menu__section {\n position: relative;\n touch-action: auto;\n flex-direction: column;\n max-height: 100%;\n overflow-x: hidden;\n overflow-y: auto;\n touch-action: auto;\n}\n\n.tlui-people-menu__section:first-child,\n.tlui-people-menu__section:last-child {\n flex-shrink: 0;\n}\n\n.tlui-people-menu__section:not(:last-child) {\n border-bottom: 1px solid var(--tl-color-divider);\n}\n\n.tlui-people-menu__user {\n display: flex;\n justify-content: flex-start;\n align-items: center;\n}\n\n.tlui-people-menu__user__color {\n flex-shrink: 0;\n}\n\n.tlui-people-menu__user__name {\n text-align: start;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 12px;\n color: var(--tl-color-text-1);\n max-width: 100%;\n flex-grow: 1;\n flex-shrink: 100;\n}\n\n.tlui-people-menu__user__label {\n text-align: start;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 12px;\n color: var(--tl-color-text-3);\n flex-grow: 100;\n flex-shrink: 0;\n margin-inline-start: 4px;\n}\n\n.tlui-people-menu__user__input {\n flex-grow: 2;\n height: 100%;\n padding: 0px;\n margin: 0px;\n}\n\n.tlui-people-menu__user > .tlui-input__wrapper {\n width: auto;\n display: flex;\n align-items: auto;\n flex-grow: 2;\n gap: 8px;\n height: 100%;\n padding: 0px;\n}\n\n.tlui-people-menu__item {\n position: relative;\n}\n\n.tlui-people-menu__item:last-of-type .tlui-button__menu {\n margin-bottom: 0;\n}\n\n.tlui-people-menu__item__button {\n padding: 0 11px;\n overflow: hidden;\n}\n\n.tlui-people-menu__item > .tlui-button__menu {\n width: auto;\n display: flex;\n align-items: auto;\n justify-content: flex-start;\n flex-grow: 2;\n gap: 11px;\n}\n\n.tlui-people-menu__name {\n text-align: start;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.tlui-people-menu__item__follow {\n position: absolute;\n top: 0px;\n inset-inline-end: 0px;\n max-width: 40px;\n flex-shrink: 0;\n}\n\n.tlui-people-menu__item[data-follow='true'],\n.tlui-people-menu__item:has(.tlui-button:focus-visible) {\n padding-inline-end: 36px;\n}\n\n.tlui-people-menu__item[data-follow='true'] .tlui-people-menu__item__follow,\n.tlui-people-menu__item:has(.tlui-button:focus-visible) .tlui-people-menu__item__follow {\n opacity: 1;\n}\n\n@media (hover: hover) {\n .tlui-people-menu__item__follow {\n opacity: 0;\n }\n\n .tlui-people-menu__item:hover {\n padding-right: 36px;\n }\n .tlui-people-menu__item:hover .tlui-people-menu__item__follow {\n opacity: 1;\n }\n}\n\n/* --------------- Following indicator -------------- */\n\n.tlui-following-indicator {\n display: block;\n position: absolute;\n inset: 0px;\n border-width: 2px;\n border-style: solid;\n pointer-events: none;\n}\n\n/* --------------- Contextual toolbar --------------- */\n\n.tlui-contextual-toolbar {\n position: absolute;\n}\n\n/**\n * N.B. We hide the toolbar while mousing down, so no need to turn these off.\n */\n.tlui-contextual-toolbar,\n.tlui-contextual-toolbar * {\n pointer-events: all;\n}\n\n.tlui-contextual-toolbar [data-isactive='true']::after {\n background-color: var(--tl-color-muted-2);\n opacity: 1;\n}\n\n.tlui-contextual-toolbar {\n opacity: 0;\n transition: opacity 0.08s ease-in-out;\n}\n\n.tlui-contextual-toolbar,\n.tlui-contextual-toolbar * {\n pointer-events: none;\n}\n\n.tlui-contextual-toolbar[data-visible='true'] {\n opacity: 1;\n z-index: var(--tl-layer-menus);\n}\n\n.tlui-contextual-toolbar[data-interactive='true'],\n.tlui-contextual-toolbar[data-interactive='true'] * {\n pointer-events: all;\n}\n\n.tlui-rich-text__toolbar-link-input {\n margin-left: 12px;\n /*\n * Nice touch tweak: keep the link editor toolbar the same as the default toolbar.\n * This is so the toolbar size stays stable going in and out of the link editor.\n */\n width: 148px;\n}\n\n.tlui-media__toolbar-alt-text-input {\n margin-left: 12px;\n /*\n * Nice touch tweak: keep the link editor toolbar the same as the default toolbar.\n * This is so the toolbar size stays stable going in and out of the alt text editor.\n */\n min-width: 200px;\n}\n\n.tlui-contextual-toolbar .tlui-input__wrapper {\n height: 40px;\n}\n\n.tlui-image__toolbar .tlui-slider__container {\n width: 125px;\n}\n\n.tlui-image__toolbar .tlui-slider {\n height: 100%;\n}\n\n.tlui-image__toolbar .tlui-slider__track {\n height: 32px;\n}\n\n.tlui-image__toolbar .tlui-slider__thumb {\n width: 14px;\n height: 14px;\n}\n\n/* ------------------- Animations ------------------- */\n@keyframes tlui-fade-out {\n 0% {\n opacity: 1;\n }\n 100% {\n opacity: 0;\n }\n}\n\n@keyframes tlui-slide-in {\n from {\n transform: translateX(calc(100% + var(--tl-space-3)));\n }\n to {\n transform: translateX(0px);\n }\n}\n\n@keyframes tlui-slide-out {\n from {\n transform: translateX(var(--radix-toast-swipe-end-x));\n }\n to {\n transform: translateX(calc(100% + var(--tl-space-3)));\n }\n}\n\n@keyframes tlui-slide-in-rtl {\n from {\n transform: translateX(calc(-100% - var(--tl-space-3)));\n }\n to {\n transform: translateX(0px);\n }\n}\n\n@keyframes tlui-slide-out-rtl {\n from {\n transform: translateX(var(--radix-toast-swipe-end-x));\n }\n to {\n transform: translateX(calc(-100% - var(--tl-space-3)));\n }\n}\n\n";
120830
+ //#endregion
120831
+ //#region lib/types/client/tl/tl-theme-css.js
120832
+ /**
120833
+ * DSH image-gen brand skin for the tldraw surface.
120834
+ *
120835
+ * Injected AFTER the bundled tldraw styles (tl-css.ts): it only re-declares
120836
+ * tldraw's own CSS variables and restyles tldraw's own chrome classes, scoped
120837
+ * to our canvas container, so upgrading tldraw never requires edits here. This
120838
+ * layer owns the UI chrome (panels, toolbars, hover/active feedback, grid
120839
+ * dots); colors painted onto the canvas itself (shape palette, selection
120840
+ * handles, marquee) come from the runtime theme patch in tl-brand-theme.ts.
120841
+ * Dark chrome keeps stock tldraw colors: the plugin lives in DSH's light shell.
120842
+ *
120843
+ * Beyond re-tinting, this pass reshapes tldraw's signature chrome so the
120844
+ * surface no longer reads as "stock tldraw": the grey corner strips (top-left
120845
+ * menu zone, bottom-left navigation) become floating white cards with hairline
120846
+ * borders, matching the workbench card language.
120847
+ */
120848
+ /** Hairline border used on every floating chrome card. */
120849
+ const CARD_BORDER = "#d9e2f2";
120850
+ /** Shadow used on every floating chrome card (workbench blue-grey family). */
120851
+ const CARD_SHADOW = "0px 1px 2px rgba(30, 42, 76, 0.06), 0px 4px 12px rgba(30, 42, 76, 0.08)";
120852
+ const TL_THEME_CSS = `
120853
+ /* DSH image-gen skin for the tldraw surface (light chrome only). */
120854
+ .dsh-ig-tl-canvas .tl-container.tl-theme__light {
120855
+ /* Canvas surface: a clearly cool blue-grey board, distinguishable from the
120856
+ pure-white floating cards at a glance. Same token feeds the runtime
120857
+ theme patch (CANVAS_BG in tl-brand-theme.ts). */
120858
+ --tl-color-background: ${CANVAS_BG};
120859
+ --tl-color-grid: rgba(88, 110, 158, 0.5);
120860
+
120861
+ /* Brand accents: selection outlines, active tool, chips, focus rings. */
120862
+ --tl-color-selected: ${BRAND_BLUE};
120863
+ --tl-color-primary: ${BRAND_BLUE};
120864
+ --tl-color-focus: ${BRAND_BLUE};
120865
+ --tl-color-selection-stroke: ${BRAND_BLUE};
120866
+ --tl-color-selection-fill: rgba(${BRAND_BLUE_RGB}, 0.14);
120867
+
120868
+ /* Interaction feedback: hover chips and active-tool highlight are tinted
120869
+ brand blue instead of tldraw's neutral grey-black. */
120870
+ --tl-color-hint: rgba(${BRAND_BLUE_RGB}, 0.14);
120871
+ --tl-color-muted-1: rgba(${BRAND_BLUE_RGB}, 0.14);
120872
+ --tl-color-muted-2: rgba(${BRAND_BLUE_RGB}, 0.07);
120873
+
120874
+ /* Chrome: panels, borders, and text aligned with the workbench tokens. */
120875
+ --tl-color-panel: #ffffff;
120876
+ --tl-color-panel-contrast: #ffffff;
120877
+ --tl-color-low: #ffffff;
120878
+ --tl-color-low-border: ${CARD_BORDER};
120879
+ --tl-color-divider: #dfe6f3;
120880
+ --tl-color-text: ${BRAND_INK};
120881
+ --tl-color-text-0: ${BRAND_INK};
120882
+ --tl-color-text-1: #242f44;
120883
+ --tl-color-text-3: #6b7690;
120884
+
120885
+ /* Shadows: the workbench's cold blue-grey instead of tldraw's black. */
120886
+ --tl-shadow-1: 0px 1px 2px rgba(30, 42, 76, 0.24), 0px 1px 3px rgba(30, 42, 76, 0.08);
120887
+ --tl-shadow-2:
120888
+ 0px 0px 2px rgba(30, 42, 76, 0.14), 0px 2px 3px rgba(30, 42, 76, 0.2),
120889
+ 0px 2px 6px rgba(30, 42, 76, 0.08), inset 0px 0px 0px 1px var(--tl-color-panel-contrast);
120890
+ --tl-shadow-3:
120891
+ 0px 1px 2px rgba(30, 42, 76, 0.24), 0px 2px 6px rgba(30, 42, 76, 0.12),
120892
+ inset 0px 0px 0px 1px var(--tl-color-panel-contrast);
120893
+ --tl-shadow-4:
120894
+ 0px 0px 3px rgba(30, 42, 76, 0.16), 0px 5px 4px rgba(30, 42, 76, 0.13),
120895
+ 0px 2px 16px rgba(30, 42, 76, 0.05), inset 0px 0px 0px 1px var(--tl-color-panel-contrast);
120896
+
120897
+ /* Noticeably rounder chrome than tldraw's stock 4/6/9/11px. */
120898
+ --tl-radius-1: 6px;
120899
+ --tl-radius-2: 10px;
120900
+ --tl-radius-3: 13px;
120901
+ --tl-radius-4: 16px;
120902
+ }
120903
+
120904
+ /* ---------------------------------------------------------------
120905
+ * De-tldraw pass: reshape tldraw's signature corner strips and
120906
+ * floating chrome into the workbench's card language.
120907
+ * ------------------------------------------------------------- */
120908
+
120909
+ /* Canvas backdrop: subtle vertical gradient (light at top, a touch deeper at
120910
+ the bottom) for an airy, lit-from-above feel. Painted on the UI background
120911
+ layer ONLY — --tl-color-background stays a flat color because tldraw reuses
120912
+ it for export backgrounds and edge highlight lines, which must stay clean. */
120913
+ .dsh-ig-tl-canvas .tl-container.tl-theme__light .tl-background {
120914
+ background-image: linear-gradient(180deg, ${CANVAS_BG_TOP} 0%, ${CANVAS_BG} 60%, ${CANVAS_BG_BOTTOM} 100%);
120915
+ }
120916
+
120917
+ /* Top-left menu strip -> floating white card (Page menu + quick actions). */
120918
+ .dsh-ig-tl-canvas .tl-container.tl-theme__light .tlui-menu-zone {
120919
+ margin: 10px;
120920
+ background-color: #ffffff;
120921
+ border: 1px solid ${CARD_BORDER};
120922
+ border-radius: 12px;
120923
+ box-shadow: ${CARD_SHADOW};
120924
+ }
120925
+
120926
+ /* Bottom-left navigation strip -> floating white card (zoom, pages, minimap).
120927
+ Offsets detach it from the screen corner; the ::before paints the card. */
120928
+ .dsh-ig-tl-canvas .tl-container.tl-theme__light .tlui-navigation-panel {
120929
+ left: 12px;
120930
+ bottom: 12px;
120931
+ }
120932
+ .dsh-ig-tl-canvas .tl-container.tl-theme__light[dir='rtl'] .tlui-navigation-panel {
120933
+ left: auto;
120934
+ right: 12px;
120935
+ }
120936
+ .dsh-ig-tl-canvas .tl-container.tl-theme__light .tlui-navigation-panel::before {
120937
+ inset: -6px -6px -6px -6px;
120938
+ background-color: #ffffff;
120939
+ border: 1px solid ${CARD_BORDER};
120940
+ border-radius: 12px;
120941
+ box-shadow: ${CARD_SHADOW};
120942
+ }
120943
+
120944
+ /* Bottom toolbar pill -> bordered card so it matches the other cards. */
120945
+ .dsh-ig-tl-canvas .tl-container.tl-theme__light .tlui-main-toolbar__tools {
120946
+ border: 1px solid ${CARD_BORDER};
120947
+ }
120948
+
120949
+ /* Vertical left rail (Figma-style, set via components.Toolbar): float it off
120950
+ the screen edge like the other cards. Stock offsets (top 90px / bottom
120951
+ 140px) already clear the top-left menu card and bottom-left minimap card. */
120952
+ .dsh-ig-tl-canvas .tl-container.tl-theme__light .tlui-main-toolbar--vertical {
120953
+ left: 12px;
120954
+ padding-left: 0;
120955
+ }
120956
+
120957
+ /* Compact rail buttons (40px instead of stock 48px) for an app-like density
120958
+ that differs from tldraw's chunky mobile-sized chrome. */
120959
+ .dsh-ig-tl-canvas .tl-container.tl-theme__light .tlui-main-toolbar--vertical .tlui-button__tool {
120960
+ width: 40px;
120961
+ height: 40px;
120962
+ }
120963
+ .dsh-ig-tl-canvas .tl-container.tl-theme__light .tlui-main-toolbar--vertical .tlui-button__tool::after {
120964
+ inset: 3px;
120965
+ border-radius: 9px;
120966
+ }
120967
+ .dsh-ig-tl-canvas .tl-container.tl-theme__light .tlui-main-toolbar--vertical .tlui-main-toolbar__extras {
120968
+ width: 40px;
120969
+ }
120970
+
120971
+ /* Right style panel -> bordered card. */
120972
+ .dsh-ig-tl-canvas .tl-container.tl-theme__light .tlui-style-panel__wrapper {
120973
+ border: 1px solid ${CARD_BORDER};
120974
+ }
120975
+
120976
+ /* Dropdown menus and popovers -> hairline border for definition. */
120977
+ .dsh-ig-tl-canvas .tl-container.tl-theme__light .tlui-menu,
120978
+ .dsh-ig-tl-canvas .tl-container.tl-theme__light .tlui-popover__content {
120979
+ border: 1px solid ${CARD_BORDER};
120980
+ }
120981
+
120982
+ /* Active menu chips (e.g. style panel selections): brand tint instead of grey. */
120983
+ .dsh-ig-tl-canvas .tl-container.tl-theme__light .tlui-button[data-isactive='true'] {
120984
+ color: ${BRAND_BLUE};
120985
+ }
120986
+ .dsh-ig-tl-canvas .tl-container.tl-theme__light .tlui-button[data-isactive='true']::after {
120987
+ background: rgba(${BRAND_BLUE_RGB}, 0.14);
120988
+ }
120989
+ `;
120642
120990
  //#endregion
120643
120991
  //#region lib/types/client/inspiration-style.js
120644
120992
  const INSPIRATION_STYLE = `
@@ -120684,6 +121032,14 @@ img.ProseMirror-separator {
120684
121032
  kind: IMAGE_RESULT_NODE_KIND,
120685
121033
  target: "chat",
120686
121034
  match: (event) => {
121035
+ if (event.type === "tool/ptc-dispatch") {
121036
+ const subCallId = event.data.subCallId;
121037
+ if (typeof subCallId !== "string" || subCallId === "") return null;
121038
+ return imageResultFromPtcDispatch(event) === void 0 ? null : {
121039
+ id: `ptc:${subCallId}`,
121040
+ role: "start"
121041
+ };
121042
+ }
120687
121043
  const turn = eventTurn(event);
120688
121044
  if (turn === void 0) return null;
120689
121045
  if (event.type === "turn/start") return {
@@ -120705,6 +121061,14 @@ img.ProseMirror-separator {
120705
121061
  return null;
120706
121062
  },
120707
121063
  start: (_context, match) => {
121064
+ if (match.event.type === "tool/ptc-dispatch") {
121065
+ const result = imageResultFromPtcDispatch(match.event);
121066
+ if (result === void 0) throw new Error("dsh-image-result start requires a plugin image dispatch");
121067
+ return { results: [{
121068
+ ...result,
121069
+ seq: match.event.seq
121070
+ }] };
121071
+ }
120708
121072
  const turn = eventTurn(match.event);
120709
121073
  if (match.event.type !== "turn/start" || turn === void 0) throw new Error("dsh-image-result start requires turn/start");
120710
121074
  return {
@@ -120721,17 +121085,14 @@ img.ProseMirror-separator {
120721
121085
  ...context.state,
120722
121086
  answerSeq: match.event.seq
120723
121087
  };
121088
+ if (match.event.type === "tool/ptc-dispatch") {
121089
+ const result = imageResultFromPtcDispatch(match.event);
121090
+ return result === void 0 ? context.state : appendResult(context.state, result, match.event.seq);
121091
+ }
120724
121092
  if (match.event.type !== "tool/result") return context.state;
120725
121093
  const result = imageResultFromMeta(match.event.data.meta);
120726
121094
  if (result === void 0) return context.state;
120727
- if (context.state.results.some((candidate) => candidate.attachment.attachmentId === result.attachment.attachmentId)) return context.state;
120728
- return {
120729
- ...context.state,
120730
- results: [...context.state.results, {
120731
- ...result,
120732
- seq: match.event.seq
120733
- }]
120734
- };
121095
+ return appendResult(context.state, result, match.event.seq);
120735
121096
  },
120736
121097
  buildViewNode: (context) => {
120737
121098
  const state = context.state;
@@ -120747,13 +121108,24 @@ img.ProseMirror-separator {
120747
121108
  location,
120748
121109
  visibility: "visible",
120749
121110
  data: {
120750
- turn: state.turn,
121111
+ ...state.turn !== void 0 ? { turn: state.turn } : {},
120751
121112
  results: state.results.map(({ seq: _seq, ...result }) => result)
120752
121113
  }
120753
121114
  };
120754
121115
  }
120755
121116
  };
120756
121117
  }
121118
+ /** Append a parsed result unless its attachment is already collected. */
121119
+ function appendResult(state, result, seq) {
121120
+ if (state.results.some((candidate) => candidate.attachment.attachmentId === result.attachment.attachmentId)) return state;
121121
+ return {
121122
+ ...state,
121123
+ results: [...state.results, {
121124
+ ...result,
121125
+ seq
121126
+ }]
121127
+ };
121128
+ }
120757
121129
  function compactTranscript(options) {
120758
121130
  try {
120759
121131
  return options.isCompactTranscript?.() ?? true;
@@ -120781,6 +121153,60 @@ img.ProseMirror-separator {
120781
121153
  const turn = event.data.turn;
120782
121154
  return typeof turn === "number" && Number.isSafeInteger(turn) && turn >= 0 ? turn : void 0;
120783
121155
  }
121156
+ /** Plugin tools whose PTC (run_code) dispatch results are image results. */
121157
+ const PTC_IMAGE_TOOL_NAMES = /* @__PURE__ */ new Set(["generate_image", "edit_image"]);
121158
+ /**
121159
+ * Fixed summary text rendered by the tools' imageOutput(). PTC dispatch events
121160
+ * carry no presentationMeta, so provider/model/output are recovered from this
121161
+ * template rather than parsed out of natural language.
121162
+ */
121163
+ const PTC_SUMMARY_PATTERN = /^(?:Generated|Edited) one image with ([^/\s]+)\/(\S+) \((.+)\)\. Attachment ID: /;
121164
+ const PTC_SAVED_TO_PATTERN = / It was also saved to the workspace as (.+)\. (?:(?:Saving it to the workspace failed)|(?:It has no local file path)|(?:Respond to the user))/;
121165
+ /**
121166
+ * Parse a plugin image result from a tool/ptc-dispatch event (#38). PTC
121167
+ * run_code sub-calls carry neither a turn nor presentationMeta, so the result
121168
+ * is rebuilt from the dispatch payload itself: the image content block for the
121169
+ * attachment, arguments for the prompt (and edit source ids), and the tool's
121170
+ * fixed-format summary text for provider/model/output. Returns undefined for
121171
+ * anything that is not a successful image result from our own image tools.
121172
+ */
121173
+ function imageResultFromPtcDispatch(event) {
121174
+ if (event.type !== "tool/ptc-dispatch") return void 0;
121175
+ const data = event.data;
121176
+ if (data.isError === true) return void 0;
121177
+ if (typeof data.name !== "string" || !PTC_IMAGE_TOOL_NAMES.has(data.name)) return void 0;
121178
+ const content = Array.isArray(data.content) ? data.content : [];
121179
+ let attachment;
121180
+ let summaryText = "";
121181
+ for (const block of content) {
121182
+ const candidate = record(block);
121183
+ if (candidate === void 0) continue;
121184
+ if (candidate.type === "image" && attachment === void 0) attachment = imageAttachment(candidate.attachment);
121185
+ else if (candidate.type === "text" && summaryText === "" && typeof candidate.text === "string") summaryText = candidate.text;
121186
+ }
121187
+ if (attachment === void 0) return void 0;
121188
+ const summary = PTC_SUMMARY_PATTERN.exec(summaryText);
121189
+ const savedTo = PTC_SAVED_TO_PATTERN.exec(summaryText)?.[1];
121190
+ const args = record(data.arguments);
121191
+ const sourceIds = sourceAttachmentIds(args);
121192
+ return {
121193
+ attachment,
121194
+ prompt: stringValue(args?.prompt, "Generated Image"),
121195
+ provider: summary?.[1] ?? "",
121196
+ model: summary?.[2] ?? "",
121197
+ output: summary?.[3] ?? "",
121198
+ ...savedTo !== void 0 ? { savedTo } : {},
121199
+ ...sourceIds !== void 0 ? { sourceAttachmentIds: sourceIds } : {}
121200
+ };
121201
+ }
121202
+ /** Collect edit_image source attachment ids (single or list form) from dispatch arguments. */
121203
+ function sourceAttachmentIds(args) {
121204
+ if (args === void 0) return void 0;
121205
+ const single = typeof args.source_attachment_id === "string" ? [args.source_attachment_id] : void 0;
121206
+ const list = Array.isArray(args.source_attachment_ids) ? args.source_attachment_ids.filter((id) => typeof id === "string") : void 0;
121207
+ const ids = single ?? list;
121208
+ return ids !== void 0 && ids.length > 0 ? ids : void 0;
121209
+ }
120784
121210
  function imageAttachment(value) {
120785
121211
  const candidate = record(value);
120786
121212
  if (candidate === void 0 || typeof candidate.attachmentId !== "string" || !isImageMediaType(candidate.mediaType) || !positiveInteger(candidate.bytes) || !positiveInteger(candidate.width) || !positiveInteger(candidate.height)) return void 0;
@@ -121219,7 +121645,7 @@ img.ProseMirror-separator {
121219
121645
  `;
121220
121646
  //#endregion
121221
121647
  //#region lib/types/client/index.js
121222
- console.info(`[dsh-image-gen] canvas bundle 2026-09-12T10:11:10.729Z`);
121648
+ console.info(`[dsh-image-gen] canvas bundle 2026-09-12T16:41:18.261Z`);
121223
121649
  /** Right-sidebar tab-type registration id (DSH 0.1.5 `sidebarRightTabs`). */
121224
121650
  const SIDEBAR_STUDIO_TAB_ID = "dsh-image-gen/studio";
121225
121651
  /** The tab kind: a page type opened by kind, recognizing no resource address. */
@@ -121730,7 +122156,7 @@ img.ProseMirror-separator {
121730
122156
  ctx.effect(() => {
121731
122157
  const style = document.createElement("style");
121732
122158
  style.dataset.plugin = "dsh-image-gen";
121733
- style.textContent = `${STYLE}\n${STUDIO_STYLE}\n${INSPIRATION_STYLE}\n${PROVIDER_PILL_STYLE}\n${TL_CSS}`;
122159
+ style.textContent = `${STYLE}\n${STUDIO_STYLE}\n${INSPIRATION_STYLE}\n${PROVIDER_PILL_STYLE}\n${TL_CSS}\n${TL_THEME_CSS}`;
121734
122160
  document.head.appendChild(style);
121735
122161
  return () => {
121736
122162
  style.remove();