dsh-image-gen 0.6.1 → 0.6.7
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/README.en.md +381 -345
- package/README.md +381 -345
- package/README.zh-CN.md +3 -3
- package/docs/assets/readme/canvas-edit.webp +0 -0
- package/docs/assets/readme/canvas-generate.webp +0 -0
- package/docs/assets/readme/hero-en.webp +0 -0
- package/docs/assets/readme/hero-zh.webp +0 -0
- package/docs/assets/readme/workflow-overview-en.webp +0 -0
- package/docs/assets/readme/workflow-overview.webp +0 -0
- package/lib/client.js +1046 -374
- package/lib/client.js.map +1 -1
- package/lib/index.js +1421 -103
- package/lib/types/client/index.d.ts +4 -0
- package/lib/types/client/inspiration-api.d.ts +10 -0
- package/lib/types/client/inspiration-detail.d.ts +14 -0
- package/lib/types/client/provider-pill.d.ts +1 -1
- package/lib/types/client/tl/canvas-sync.d.ts +3 -3
- package/lib/types/client/tl/conversation-landings.d.ts +33 -0
- package/lib/types/client/tl/studio-tl-canvas.d.ts +2 -0
- package/lib/types/client/tl/tl-canvas-bridge.d.ts +12 -32
- package/lib/types/client/tldraw/generated/tldraw-assets.d.ts +12 -0
- package/lib/types/client/tldraw/generated/tldraw-css.d.ts +1 -0
- package/lib/types/client/tldraw/tldraw-style.d.ts +1 -0
- package/lib/types/client/tldraw/tldraw-workspace.d.ts +1 -0
- package/lib/types/config.d.ts +24 -0
- package/lib/types/inspiration/cache.d.ts +20 -0
- package/lib/types/inspiration/downloader.d.ts +27 -0
- package/lib/types/inspiration/routes.d.ts +13 -0
- package/lib/types/inspiration/sources.d.ts +33 -0
- package/lib/types/inspiration/types.d.ts +65 -0
- package/lib/types/inspiration/updater.d.ts +7 -0
- package/lib/types/openai-compatible.d.ts +15 -0
- package/lib/types/shared.d.ts +16 -1
- package/lib/types/subscription/blob.d.ts +23 -0
- package/lib/types/subscription/loopback.d.ts +11 -0
- package/lib/types/subscription/manager.d.ts +94 -0
- package/lib/types/subscription/oauth.d.ts +25 -0
- package/lib/types/subscription/subscription-route.d.ts +5 -0
- package/lib/types/subscription/vendors/antigravity.d.ts +66 -0
- package/lib/types/subscription/vendors/codex.d.ts +30 -0
- package/lib/types/subscription/vendors/grok.d.ts +32 -0
- package/lib/types/subscription.d.ts +42 -0
- package/lib/types/test-route.d.ts +9 -0
- package/package.json +8 -10
- package/skills/install-dsh-image-gen/SKILL.md +22 -22
- package/lib/types/client/studio-chat-panel.d.ts +0 -23
- package/lib/types/studio-chat.d.ts +0 -48
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const TLDRAW_STYLE = "/* 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\twidth: 100%;\n\theight: 100%;\n\tfont-size: 12px;\n\t/* Spacing */\n\t--tl-space-1: 2px;\n\t--tl-space-2: 4px;\n\t--tl-space-3: 8px;\n\t--tl-space-4: 12px;\n\t--tl-space-5: 16px;\n\t--tl-space-6: 20px;\n\t--tl-space-7: 28px;\n\t--tl-space-8: 32px;\n\t--tl-space-9: 64px;\n\t--tl-space-10: 72px;\n\t/* Radius */\n\t--tl-radius-0: 2px;\n\t--tl-radius-1: 4px;\n\t--tl-radius-2: 6px;\n\t--tl-radius-3: 9px;\n\t--tl-radius-4: 11px;\n\n\t/*\n\t * Canvas z-index\n\t *\n\t * .tl-canvas has `contain: strict` which creates its own stacking context.\n\t * background, grid, shapes, overlays, and blocker are all children of .tl-canvas,\n\t * so their z-indices are relative to the canvas, not the container.\n\t *\n\t * collaborators, watermark, and canvas-in-front are siblings of .tl-canvas\n\t * (outside it), so they compete with the UI layer (.tlui-layout at z-index 300).\n\t * canvas-in-front must be below the UI layer (300) to sit between\n\t * the canvas and the UI; collaborator cursors sit above canvas content\n\t * (and layers that anchor to it, like comment pins) but below the\n\t * in-front content and the UI panels. The watermark (a licensing element)\n\t * sits above that content so it can't be occluded, but still below the\n\t * in-front content and the UI.\n\t */\n\t--tl-layer-canvas-hidden: -999999;\n\t--tl-layer-canvas-background: 100;\n\t--tl-layer-canvas-grid: 150;\n\t--tl-layer-collaborators: 245;\n\t--tl-layer-watermark: 248;\n\t--tl-layer-canvas-in-front: 250;\n\t--tl-layer-canvas-shapes: 300;\n\t--tl-layer-canvas-overlays: 500;\n\t--tl-layer-canvas-blocker: 10000;\n\n\t/* Canvas overlays z-index */\n\t--tl-layer-overlays-collaborator-scribble: 10;\n\t--tl-layer-overlays-collaborator-brush: 20;\n\t--tl-layer-overlays-collaborator-shape-indicator: 30;\n\t--tl-layer-overlays-user-scribble: 40;\n\t--tl-layer-overlays-user-brush: 50;\n\t--tl-layer-overlays-user-snapline: 90;\n\t--tl-layer-overlays-selection-fg: 100;\n\t/* User handles need to be above selection edges / corners, matters for sticky note clone handles */\n\t--tl-layer-overlays-user-handles: 105;\n\t--tl-layer-overlays-user-indicator-hint: 110;\n\t--tl-layer-overlays-collaborator-cursor-hint: 120;\n\t--tl-layer-overlays-collaborator-cursor: 130;\n\n\t/* Text editor z-index */\n\t--tl-layer-text-container: 1;\n\t--tl-layer-text-content: 3;\n\t--tl-layer-text-editor: 4;\n\n\t/* Error fallback z-index */\n\t--tl-layer-error-overlay: 1;\n\t--tl-layer-error-canvas: 2;\n\t--tl-layer-error-canvas-after: 3;\n\t--tl-layer-error-content: 4;\n\n\t/* Misc */\n\t--tl-zoom: 1;\n\t--tl-tab-size: 2;\n\n\t/* Cursor SVGs */\n\t--tl-cursor-none: none;\n\t--tl-cursor-default:\n\t\turl(\"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\t\t\t12 8,\n\t\tdefault;\n\t--tl-cursor-pointer:\n\t\turl(\"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\t\t\t14 10,\n\t\tpointer;\n\t--tl-cursor-cross:\n\t\turl(\"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\t\t\t16 16,\n\t\tcrosshair;\n\t--tl-cursor-comment:\n\t\turl(\"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\t\t\t5 19,\n\t\tcrosshair;\n\t--tl-cursor-move:\n\t\turl(\"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\t\t\t16 16,\n\t\tmove;\n\t--tl-cursor-grab:\n\t\turl(\"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\t\t\t16 16,\n\t\tgrab;\n\t--tl-cursor-grabbing:\n\t\turl(\"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\t\t\t16 16,\n\t\tgrabbing;\n\t--tl-cursor-text:\n\t\turl(\"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\t\t\t4 10,\n\t\ttext;\n\t--tl-cursor-zoom-in:\n\t\turl(\"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\t\t\t16 16,\n\t\tzoom-in;\n\t--tl-cursor-zoom-out:\n\t\turl(\"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\t\t\t16 16,\n\t\tzoom-out;\n\n\t/* These cursor values get programmatically overridden */\n\t/* They're just here to help your editor autocomplete */\n\t--tl-cursor: var(--tl-cursor-default);\n\t--tl-cursor-resize-edge: ew-resize;\n\t--tl-cursor-resize-corner: nesw-resize;\n\t--tl-cursor-ew-resize: ew-resize;\n\t--tl-cursor-ns-resize: ns-resize;\n\t--tl-cursor-nesw-resize: nesw-resize;\n\t--tl-cursor-nwse-resize: nwse-resize;\n\t--tl-cursor-rotate: pointer;\n\t--tl-cursor-nwse-rotate: pointer;\n\t--tl-cursor-nesw-rotate: pointer;\n\t--tl-cursor-senw-rotate: pointer;\n\t--tl-cursor-swne-rotate: pointer;\n\t--tl-scale: calc(1 / var(--tl-zoom));\n\t/* fonts */\n\t--tl-font-draw: 'tldraw_draw', sans-serif;\n\t--tl-font-sans: 'tldraw_sans', sans-serif;\n\t--tl-font-serif: 'tldraw_serif', serif;\n\t--tl-font-mono: 'tldraw_mono', monospace;\n\t/* text outline */\n\t--tl-text-outline-a: calc(min(0.5, 1 / var(--tl-zoom)) * 2px);\n\t--tl-text-outline-b: calc(min(0.5, 1 / var(--tl-zoom)) * -2px);\n\t--tl-text-outline-reference:\n\t\t0 var(--tl-text-outline-b) 0 var(--tl-color-background),\n\t\t0 var(--tl-text-outline-a) 0 var(--tl-color-background),\n\t\tvar(--tl-text-outline-b) var(--tl-text-outline-b) 0 var(--tl-color-background),\n\t\tvar(--tl-text-outline-a) var(--tl-text-outline-b) 0 var(--tl-color-background),\n\t\tvar(--tl-text-outline-a) var(--tl-text-outline-a) 0 var(--tl-color-background),\n\t\tvar(--tl-text-outline-b) var(--tl-text-outline-a) 0 var(--tl-color-background);\n\t--tl-text-outline: var(--tl-text-outline-reference);\n\t/* own properties */\n\tposition: relative;\n\tinset: 0px;\n\theight: 100%;\n\twidth: 100%;\n\toverflow: clip;\n\tcolor: var(--tl-color-text);\n}\n\n.tl-theme__light {\n\t/* Canvas */\n\t--tl-color-selection-fill: hsl(210, 100%, 56%, 24%);\n\t--tl-color-selection-stroke: hsl(214, 84%, 56%);\n\t--tl-color-background: hsl(210, 20%, 98%);\n\t/* if you ever update --tl-color-background, update the hardcoded values in theme-init.js and globals.css\n\tthey're there to make sure the background color matches the user's preference before the actual app loads*/\n\t--tl-color-grid: hsl(0, 0%, 43%);\n\t/* UI */\n\t--tl-color-low: hsl(204, 16%, 94%);\n\t--tl-color-low-border: hsl(204, 16%, 92%);\n\t--tl-color-muted-none: hsl(0, 0%, 0%, 0%);\n\t--tl-color-muted-0: hsl(0, 0%, 0%, 2%);\n\t--tl-color-muted-1: hsl(0, 0%, 0%, 10%);\n\t--tl-color-muted-2: hsl(0, 0%, 0%, 4.3%);\n\t--tl-color-hint: hsl(0, 0%, 0%, 5.5%);\n\t--tl-color-overlay: hsl(0, 0%, 0%, 20%);\n\t--tl-color-divider: hsl(0, 0%, 91%);\n\t--tl-color-panel: hsl(0, 0%, 99%);\n\t--tl-color-panel-contrast: hsl(0, 0%, 100%);\n\t--tl-color-panel-overlay: hsl(0, 0%, 100%, 82%);\n\t--tl-color-panel-transparent: hsla(0, 0%, 99%, 0%);\n\t--tl-color-selected: hsl(214, 84%, 56%);\n\t--tl-color-selected-contrast: hsl(0, 0%, 100%);\n\t--tl-color-focus: hsl(219, 65%, 50%);\n\t--tl-color-tooltip: hsla(200, 14%, 4%, 1);\n\t/* Text */\n\t--tl-color-text: hsl(0, 0%, 0%);\n\t--tl-color-text-0: hsl(0, 0%, 11%);\n\t--tl-color-text-1: hsl(0, 0%, 18%);\n\t--tl-color-text-3: hsl(204, 4%, 45%);\n\t--tl-color-text-disabled: hsl(204, 4%, 55%);\n\t--tl-color-text-shadow: hsl(0, 0%, 100%);\n\t--tl-color-text-highlight: hsl(52, 100%, 50%);\n\t--tl-color-text-highlight-p3: color(display-p3 0.972 0.8205 0.05);\n\t/* Named */\n\t--tl-color-primary: hsl(214, 84%, 56%);\n\t--tl-color-success: hsl(123, 46%, 34%);\n\t--tl-color-info: hsl(201, 98%, 41%);\n\t--tl-color-warning: hsl(27, 98%, 47%);\n\t--tl-color-danger: hsl(0, 90%, 43%);\n\n\t/* Shadows */\n\t--tl-shadow-1: 0px 1px 2px hsl(0, 0%, 0%, 25%), 0px 1px 3px hsl(0, 0%, 0%, 9%);\n\t--tl-shadow-2:\n\t\t0px 0px 2px hsl(0, 0%, 0%, 16%), 0px 2px 3px hsl(0, 0%, 0%, 24%),\n\t\t0px 2px 6px hsl(0, 0%, 0%, 0.1), inset 0px 0px 0px 1px var(--tl-color-panel-contrast);\n\t--tl-shadow-3:\n\t\t0px 1px 2px hsl(0, 0%, 0%, 28%), 0px 2px 6px hsl(0, 0%, 0%, 14%),\n\t\tinset 0px 0px 0px 1px var(--tl-color-panel-contrast);\n\t--tl-shadow-4:\n\t\t0px 0px 3px hsl(0, 0%, 0%, 19%), 0px 5px 4px hsl(0, 0%, 0%, 16%),\n\t\t0px 2px 16px hsl(0, 0%, 0%, 6%), inset 0px 0px 0px 1px var(--tl-color-panel-contrast);\n}\n\n.tl-theme__dark {\n\t/* Canvas */\n\t--tl-color-selection-fill: hsl(209, 100%, 57%, 20%);\n\t--tl-color-selection-stroke: hsl(214, 84%, 56%);\n\t--tl-color-background: hsl(240, 5%, 6.5%);\n\t/* if you ever update --tl-color-background, update the hardcoded values in theme-init.js and globals.css\n\tthey're there to make sure the background color matches the user's preference before the actual app loads*/\n\t--tl-color-grid: hsl(0, 0%, 40%);\n\t/* UI */\n\t--tl-color-low: hsl(260, 4.5%, 10.5%);\n\t--tl-color-low-border: hsl(207, 10%, 10%);\n\t--tl-color-muted-none: hsl(0, 0%, 100%, 0%);\n\t--tl-color-muted-0: hsl(0, 0%, 100%, 2%);\n\t--tl-color-muted-1: hsl(0, 0%, 100%, 10%);\n\t--tl-color-muted-2: hsl(0, 0%, 100%, 5%);\n\t--tl-color-hint: hsl(0, 0%, 100%, 7%);\n\t--tl-color-overlay: hsl(0, 0%, 0%, 50%);\n\t--tl-color-divider: hsl(240, 9%, 22%);\n\t--tl-color-panel: hsl(235, 6.8%, 13.5%);\n\t--tl-color-panel-contrast: hsl(245, 12%, 23%);\n\t--tl-color-panel-overlay: hsl(210, 10%, 24%, 82%);\n\t--tl-color-panel-transparent: hsla(235, 6.8%, 13.5%, 0%);\n\t--tl-color-selected: hsl(217, 89%, 61%);\n\t--tl-color-selected-contrast: hsl(0, 0%, 100%);\n\t--tl-color-focus: hsl(217, 76%, 80%);\n\t--tl-color-tooltip: hsla(0, 0%, 100%, 1);\n\t/* Text */\n\t--tl-color-text: hsl(210, 17%, 98%);\n\t--tl-color-text-0: hsl(0, 9%, 94%);\n\t--tl-color-text-1: hsl(0, 0%, 85%);\n\t--tl-color-text-3: hsl(204, 4%, 75%);\n\t--tl-color-text-disabled: hsl(204, 4%, 50%);\n\t--tl-color-text-shadow: hsl(210, 13%, 18%);\n\t--tl-color-text-highlight: hsl(52, 100%, 41%);\n\t--tl-color-text-highlight-p3: color(display-p3 0.8078 0.6225 0.0312);\n\t/* Named */\n\t--tl-color-primary: hsl(214, 84%, 56%);\n\t--tl-color-success: hsl(123, 38%, 57%);\n\t--tl-color-info: hsl(199, 92%, 56%);\n\t--tl-color-warning: hsl(36, 100%, 57%);\n\t--tl-color-danger: hsl(0, 82%, 66%);\n\n\t/* Shadows */\n\t--tl-shadow-1:\n\t\t0px 1px 2px hsl(0, 0%, 0%, 16.1%), 0px 1px 3px hsl(0, 0%, 0%, 22%),\n\t\tinset 0px 0px 0px 1px var(--tl-color-panel-contrast);\n\t--tl-shadow-2:\n\t\t0px 1px 3px hsl(0, 0%, 0%, 66.6%), 0px 2px 6px hsl(0, 0%, 0%, 33%),\n\t\tinset 0px 0px 0px 1px var(--tl-color-panel-contrast);\n\t--tl-shadow-3:\n\t\t0px 1px 3px hsl(0, 0%, 0%, 50%), 0px 2px 12px hsl(0, 0%, 0%, 50%),\n\t\tinset 0px 0px 0px 1px var(--tl-color-panel-contrast);\n}\n\n.tl-counter-scaled {\n\ttransform: scale(var(--tl-scale));\n\ttransform-origin: top left;\n\twidth: calc(100% * var(--tl-zoom));\n\theight: calc(100% * var(--tl-zoom));\n}\n\n.tl-container,\n.tl-container * {\n\t-webkit-touch-callout: none;\n\t-webkit-tap-highlight-color: transparent;\n\tscrollbar-highlight-color: transparent;\n\t-webkit-user-select: none;\n\tuser-select: none;\n\tbox-sizing: border-box;\n\toutline: none;\n}\n\n.tl-container a {\n\t-webkit-touch-callout: initial;\n}\n\n.tl-container__focused {\n\toutline: 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\tdisplay: contents;\n}\n\ninput,\n*[contenteditable],\n*[contenteditable] * {\n\tuser-select: text;\n}\n\n/* --------------------- Canvas --------------------- */\n\n.tl-canvas {\n\tposition: absolute;\n\tinset: 0px;\n\theight: 100%;\n\twidth: 100%;\n\tcolor: var(--tl-color-text);\n\tcursor: var(--tl-cursor);\n\toverflow: clip;\n\tcontent-visibility: auto;\n\ttouch-action: none;\n\tcontain: strict;\n}\n\n.tl-canvas__in-front {\n\tposition: absolute;\n\tinset: 0;\n\tpointer-events: none;\n\tz-index: var(--tl-layer-canvas-in-front);\n}\n\n.tl-shapes {\n\tposition: relative;\n\tz-index: var(--tl-layer-canvas-shapes);\n}\n\n.tl-svg-context {\n\tposition: absolute;\n\ttop: 0px;\n\tleft: 0px;\n\twidth: 100%;\n\theight: 100%;\n\tpointer-events: none;\n}\n\n/* ------------------- Background ------------------- */\n\n.tl-background__wrapper {\n\tz-index: var(--tl-layer-canvas-background);\n\tposition: absolute;\n\tinset: 0px;\n\theight: 100%;\n\twidth: 100%;\n}\n\n.tl-background {\n\tbackground-color: var(--tl-color-background);\n\twidth: 100%;\n\theight: 100%;\n}\n\n/* --------------------- Grid Layer --------------------- */\n\n.tl-grid {\n\tposition: absolute;\n\tinset: 0px;\n\twidth: 100%;\n\theight: 100%;\n\ttouch-action: none;\n\tpointer-events: none;\n\tz-index: var(--tl-layer-canvas-grid);\n\tcontain: strict;\n}\n\n.tl-grid-dot {\n\tfill: var(--tl-color-grid);\n}\n\n/* --------------------- Layers --------------------- */\n\n.tl-html-layer {\n\tposition: absolute;\n\ttop: 0px;\n\tleft: 0px;\n\twidth: 1px;\n\theight: 1px;\n\tcontain: 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 \u2014 pointer events pass through. */\n.tl-collaborators {\n\tposition: absolute;\n\tinset: 0px;\n\tz-index: var(--tl-layer-collaborators);\n\tpointer-events: none;\n\toverflow: clip;\n\tcontain: layout style size;\n}\n\n.tl-collaborators__item {\n\tposition: absolute;\n\ttop: 0px;\n\tleft: 0px;\n\toverflow: visible;\n\tpointer-events: none;\n\ttransform-origin: top left;\n}\n\n.tl-cursor {\n\toverflow: visible;\n}\n\n/* --------------- Overlay Stack --------------- */\n\n/* -------------- Selection foreground -------------- */\n\n.tl-selection__bg {\n\tposition: absolute;\n\ttop: 0px;\n\tleft: 0px;\n\ttransform-origin: top left;\n\tbackground-color: transparent;\n\tpointer-events: all;\n}\n\n.tl-transparent {\n\tfill: transparent;\n\tstroke: transparent;\n}\n\n.tl-hidden {\n\topacity: 0;\n\tpointer-events: none;\n}\n\n/* -------------- Nametag / cursor chat ------------- */\n\n.tl-nametag {\n\tposition: absolute;\n\ttop: 16px;\n\tleft: 13px;\n\twidth: fit-content;\n\theight: fit-content;\n\tmax-width: 120px;\n\tpadding: 3px 6px;\n\twhite-space: nowrap;\n\tposition: absolute;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\tfont-size: 12px;\n\tborder-radius: var(--tl-radius-2);\n\tcolor: var(--tl-color-selected-contrast);\n}\n\n.tl-nametag-title {\n\tposition: absolute;\n\ttop: -2px;\n\tleft: 13px;\n\twidth: fit-content;\n\theight: fit-content;\n\tpadding: 0px 6px;\n\tmax-width: 120px;\n\twhite-space: nowrap;\n\tposition: absolute;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\tfont-size: 12px;\n\ttext-shadow: var(--tl-text-outline);\n\tcolor: var(--tl-color-selected-contrast);\n}\n\n.tl-nametag-chat {\n\tposition: absolute;\n\ttop: 16px;\n\tleft: 13px;\n\twidth: fit-content;\n\theight: fit-content;\n\tcolor: var(--tl-color-selected-contrast);\n\twhite-space: nowrap;\n\tposition: absolute;\n\tpadding: 3px 6px;\n\tfont-size: 12px;\n\topacity: 1;\n\tborder-radius: var(--tl-radius-2);\n}\n\n.tl-cursor-chat {\n\tposition: absolute;\n\tcolor: var(--tl-color-selected-contrast);\n\twhite-space: nowrap;\n\tpadding: 3px 6px;\n\tfont-size: 12px;\n\tpointer-events: none;\n\tz-index: var(--tl-layer-cursor);\n\tmargin-top: 16px;\n\tmargin-left: 13px;\n\topacity: 1;\n\tborder: none;\n\tuser-select: text;\n\tborder-radius: var(--tl-radius-2);\n}\n\n.tl-cursor-chat .tl-cursor-chat__bubble {\n\tpadding-right: 12px;\n}\n\n.tl-cursor-chat::selection {\n\tbackground: var(--tl-color-selected);\n\tcolor: var(--tl-color-selected-contrast);\n\ttext-shadow: none;\n}\n\n.tl-cursor-chat::placeholder {\n\tcolor: var(--tl-color-selected-contrast);\n\topacity: 0.7;\n}\n\n/* ---------------------- Text ---------------------- */\n\n.tl-text-shape-label {\n\tposition: relative;\n\tfont-weight: normal;\n\tmin-width: 1px;\n\tpadding: 0px;\n\tmargin: 0px;\n\tborder: none;\n\twidth: fit-content;\n\theight: fit-content;\n\tfont-variant: normal;\n\tfont-style: normal;\n\tpointer-events: all;\n\twhite-space: pre-wrap;\n\toverflow-wrap: break-word;\n}\n\n.tl-plain-text-wrapper[data-isediting='true'] .tl-text-content {\n\topacity: 0;\n}\n\n.tl-rich-text-wrapper[data-isediting='true'] .tl-text-content {\n\tdisplay: none;\n}\n\n.tl-text {\n\t/* remove overflow from textarea on windows */\n\tmargin: 0px;\n\tpadding: 0px;\n\n\tappearance: auto;\n\tbackground: none;\n\tborder-image: none;\n\tborder: 0px;\n\tcaret-color: var(--tl-color-text);\n\tcolor: inherit;\n\tcolumn-count: initial !important;\n\tdisplay: inline-block;\n\tfont-family: inherit;\n\tfont-feature-settings: normal;\n\tfont-kerning: auto;\n\tfont-optical-sizing: auto;\n\tfont-size: inherit;\n\tfont-stretch: 100%;\n\tfont-style: inherit;\n\tfont-variant: inherit;\n\tfont-variation-settings: normal;\n\tfont-weight: inherit;\n\tletter-spacing: inherit;\n\tline-height: inherit;\n\toutline: none;\n\toverflow-wrap: break-word;\n\ttext-align: inherit;\n\ttext-indent: 0px;\n\ttext-rendering: auto;\n\ttext-shadow: inherit;\n\ttext-transform: none;\n\twhite-space: pre-wrap;\n\tline-break: normal;\n\tword-spacing: 0px;\n\tword-wrap: break-word;\n\twriting-mode: horizontal-tb !important;\n}\n\n.tl-text-measure {\n\tz-index: var(--tl-layer-canvas-hidden);\n\topacity: 0;\n\tvisibility: hidden;\n\n\t/* pointer-events: all; */\n\t/* opacity: 1; */\n\t/* z-index: 99999; */\n\n\tposition: absolute;\n\ttop: 0px;\n\tleft: 0px;\n\twidth: max-content;\n\tbox-sizing: border-box;\n\tpointer-events: none;\n\twhite-space: pre-wrap;\n\tword-wrap: break-word;\n\toverflow-wrap: break-word;\n\tresize: none;\n\tborder: none;\n\tuser-select: none;\n\tcontain: layout style paint;\n\t/* 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\tunicode-bidi: plaintext;\n\t-webkit-user-select: none;\n}\n\n.tl-text-input,\n.tl-text-content {\n\tposition: absolute;\n\tinset: 0px;\n\theight: 100%;\n\twidth: 100%;\n\tmin-width: 1px;\n\tmin-height: 1px;\n\toutline: none;\n}\n\n.tl-text-content__wrapper {\n\tposition: relative;\n\twidth: fit-content;\n\theight: fit-content;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tpointer-events: none;\n\tmin-height: auto;\n}\n\n.tl-text-content {\n\toverflow: visible;\n\tpointer-events: none;\n}\n\n.tl-text-input {\n\tresize: none;\n\tuser-select: all;\n\t-webkit-user-select: text;\n\tcursor: var(--tl-cursor-text);\n}\n\n.tl-text-input:not(.tl-rich-text) {\n\t/*\n\t * Note: this `overflow: hidden` is key for scrollbars to not show up\n\t * plaintext/<textarea> editors.\n\t */\n\toverflow: hidden;\n}\n\n.tl-text-input::selection {\n\tbackground: var(--tl-color-selected);\n\tcolor: var(--tl-color-selected-contrast);\n\ttext-shadow: none;\n}\n\n/* Text label */\n\n.tl-text-label {\n\tdisplay: flex;\n\tjustify-content: center;\n\talign-items: center;\n\tcolor: var(--tl-color-text);\n\tline-height: inherit;\n\tposition: absolute;\n\tinset: 0px;\n\theight: 100%;\n\twidth: 100%;\n}\n\n.tl-text-label[data-hastext='false'][data-isediting='false'] > .tl-text-label__inner {\n\twidth: 40px;\n\theight: 40px;\n}\n\n.tl-text-label[data-hastext='true'][data-isediting='false'] .tl-text-content {\n\tpointer-events: all;\n}\n\n.tl-text-label__inner > .tl-text-input.tl-rich-text {\n\tdisplay: none;\n\tposition: static;\n}\n\n.tl-text-wrapper[data-isediting='false'] .tl-text-input {\n\topacity: 0;\n\tcursor: 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\tcursor: var(--tl-cursor-text);\n}\n\n.tl-text-label[data-textwrap='true'] > .tl-text-label__inner {\n\tmax-width: 100%;\n}\n\n.tl-text-label[data-isediting='true'] {\n\tbackground-color: transparent;\n\tmin-height: auto;\n}\n\n.tl-text-wrapper .tl-text-content {\n\tpointer-events: all;\n\tz-index: var(--tl-layer-text-content);\n}\n\n.tl-text-label__inner > .tl-text-content {\n\tposition: relative;\n\ttop: 0px;\n\tleft: 0px;\n\tpadding: inherit;\n\theight: fit-content;\n\twidth: fit-content;\n\tborder-radius: var(--tl-radius-1);\n\tmax-width: 100%;\n}\n\n.tl-text-label__inner > .tl-text-input {\n\tposition: absolute;\n\tinset: 0px;\n\theight: 100%;\n\twidth: 100%;\n\tpadding: inherit;\n}\n\n.tl-text-wrapper[data-isselected='true'] .tl-text-input {\n\tz-index: var(--tl-layer-text-editor);\n\tpointer-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\t.tl-shape:not(\n\t\t[data-shape-type='arrow'],\n\t\t[data-shape-type='draw'],\n\t\t[data-shape-type='line'],\n\t\t[data-shape-type='highlight'],\n\t\t[data-shape-is-filled='false']\n\t) {\n\tpointer-events: all;\n}\n\n.tl-rich-text .ProseMirror {\n\tword-wrap: break-word;\n\toverflow-wrap: break-word;\n\twhite-space: pre-wrap;\n\n\t/**\n\t * Note: ProseMirror disables this in https://github.com/ProseMirror/prosemirror-view/commit/6b3b2205e2f3029cb8e8e86c55a190a22491df31\n\t * However, that was from 8 years ago and the browser caret issue\n\t * it mentions seems to be fixed. So, we're re-enabling it.\n\t * We'll tell ProseMirror maybe to get rid of this on their end.\n\t *\n\t */\n\t-webkit-font-variant-ligatures: inherit;\n\tfont-variant-ligatures: inherit;\n\tfont-feature-settings: inherit;\n\n\t/**\n\t * N.B. This following CSS Rule comes standard with the tiptap editor.\n\t * Combined with the above rule that it supersedes, it allows for\n\t * the auto-linking to work in text. Say, when typing example.com\n\t * this helps it automatically turn that bit of text into a link.\n\t *\n\t * However, specifically, the break-spaces features seems to cause\n\t * rendering differences when going in-and-out of edit mode. For example,\n\t * the statically rendered text 'the rain in spain falls mainly on the plain'\n\t * in a note shape will render differently when going in-and-out of edit mode.\n\t *\n\t * So, this is commented out to help make both the autolinking work (which now\n\t * relies on the white-space: pre-wrap to work and to make the static/edit-mode\n\t * rendering consistent.\n\t * In the future, we might consider just making the static rendering just use\n\t * white-space: break-spaces to make it consistent with the edit mode if need be.\n\t *\n\t * Also, the amount of ink I've spilt in my career writing comments explaining\n\t * white-space in contenteditable is incredible.\n\t *\n\t/* white-space: break-spaces; */\n}\n\n.tl-rich-text {\n\ttab-size: var(--tl-tab-size, 2);\n}\n\n.tl-rich-text p {\n\tmargin: 0;\n\t/* Depending on the extensions, <p> tags can be empty, without a <br />. */\n\tmin-height: 1lh;\n}\n\n.tl-rich-text ul,\n.tl-rich-text ol {\n\ttext-align: left;\n\tmargin: 0;\n\tpadding-left: 1.625ch;\n\t/* Some resets, like Tailwind, nix the list styling. */\n\tlist-style: revert;\n}\n\n.tl-rich-text ol:has(> li:nth-child(10)) {\n\tpadding-left: 2.625ch;\n}\n\n.tl-rich-text ol:has(> li:nth-child(100)) {\n\tpadding-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\tmargin-top: 5px;\n\tmargin-bottom: 10px;\n\t/**\n\t * Headings render at a larger font-size than the body text (via the browser's\n\t * default h1\u2013h6 sizing), so they can't inherit the container's resolved\n\t * whole-pixel line-height \u2014 that value is derived from the base font size and\n\t * would cram the taller lines together. Instead, scale line-height with each\n\t * heading's own font-size using tldraw's unitless line-height multiplier, which\n\t * the rich-text render/measure paths expose here. The 1.35 fallback mirrors the\n\t * default theme line-height (see defaultThemes.ts).\n\t */\n\tline-height: var(--tl-rich-text-heading-line-height, 1.35);\n}\n\n.tl-rich-text a {\n\tcolor: var(--tl-color-primary);\n\ttext-decoration: underline;\n}\n\n.tl-rich-text[data-is-select-tool-active='false'] a {\n\tcursor: inherit;\n}\n\n.tl-rich-text code {\n\tfont-family: var(--tl-font-mono);\n}\n\n.tl-rich-text mark {\n\tbackground-color: #fddd00;\n\tcolor: currentColor;\n\tborder-radius: 2px;\n}\n\n.tl-theme__light .tl-rich-text mark {\n\ttext-shadow: none;\n}\n\n.tl-theme__dark .tl-rich-text mark {\n\tbackground-color: var(--tl-color-text-highlight);\n\tcolor: currentColor;\n\ttext-shadow: none;\n}\n\n@supports (color: color(display-p3 1 1 1)) {\n\t@media (color-gamut: p3) {\n\t\t.tl-container:not(.tl-theme__force-sRGB) .tl-rich-text mark {\n\t\t\tbackground-color: var(--tl-color-text-highlight-p3);\n\t\t}\n\t}\n}\n\n.tl-text-wrapper[data-isediting='true'] .tl-rich-text {\n\tdisplay: block;\n}\n\n.tl-text__outline {\n\ttext-shadow: var(--tl-text-outline);\n}\n\n.tl-text__no-outline {\n\ttext-shadow: none;\n}\n\n/* --------------------- Loading -------------------- */\n\n.tl-loading {\n\tbackground-color: var(--tl-color-background);\n\tcolor: var(--tl-color-text-1);\n\theight: 100%;\n\twidth: 100%;\n\tdisplay: flex;\n\tflex-direction: column;\n\tjustify-content: center;\n\talign-items: center;\n\tgap: var(--tl-space-2);\n\tfont-size: 14px;\n\tfont-weight: 500;\n\topacity: 0;\n\tanimation: tl-fade-in 0.2s ease-in-out forwards;\n\tanimation-delay: 0.2s;\n\tposition: absolute;\n\tinset: 0px;\n\tz-index: var(--tl-layer-canvas-blocker);\n}\n\n@keyframes tl-fade-in {\n\t0% {\n\t\topacity: 0;\n\t}\n\t100% {\n\t\topacity: 1;\n\t}\n}\n\n.tl-spinner {\n\tanimation: tl-spin 1s linear infinite;\n}\n\n@keyframes tl-spin {\n\t0% {\n\t\ttransform: rotate(0deg);\n\t}\n\t100% {\n\t\ttransform: rotate(360deg);\n\t}\n}\n\n/* ---------------- Hyperlink Button ---------------- */\n\n.tl-hyperlink-button {\n\tbackground: none;\n\tmargin: 0px;\n\tposition: absolute;\n\ttop: 0px;\n\tright: 0px;\n\theight: 44px;\n\twidth: 44px;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tfont-size: 12px;\n\tfont-weight: 400;\n\tcolor: var(--tl-color-text-1);\n\tpadding: 13px;\n\tcursor: var(--tl-cursor-pointer);\n\tborder: none;\n\toutline: none;\n\tpointer-events: all;\n\tz-index: 1;\n}\n\n.tl-hyperlink-button::after {\n\tcontent: '';\n\tz-index: -1;\n\tposition: absolute;\n\tright: 6px;\n\tbottom: 6px;\n\tdisplay: block;\n\twidth: calc(100% - 12px);\n\theight: calc(100% - 12px);\n\tborder-radius: var(--tl-radius-1);\n\tbackground-color: var(--tl-color-background);\n\tpointer-events: none;\n}\n\n.tl-hyperlink-button:focus-visible {\n\tcolor: var(--tl-color-selected);\n}\n\n.tl-hyperlink__icon {\n\twidth: 15px;\n\theight: 15px;\n\tbackground-color: currentColor;\n\tpointer-events: none;\n}\n\n.tl-hyperlink-button__hidden {\n\tdisplay: none;\n}\n\n/* ----------------- Shape indicator ---------------- */\n\n.tl-shape-indicator {\n\ttransform-origin: top left;\n\tfill: none;\n\tstroke-width: calc(1.5px * var(--tl-scale));\n\tcontain: size layout;\n}\n\n.tl-canvas-overlays {\n\tposition: absolute;\n\tinset: 0;\n\tpointer-events: none;\n\tz-index: var(--tl-layer-canvas-overlays);\n}\n\n/* ---------------------- Shape --------------------- */\n\n.tl-shape {\n\tposition: absolute;\n\tpointer-events: none;\n\toverflow: visible;\n\ttransform-origin: top left;\n\tcontain: size layout;\n}\n\n/* ---------------- Shape Containers ---------------- */\n\n.tl-svg-container {\n\tposition: absolute;\n\tinset: 0px;\n\theight: 100%;\n\twidth: 100%;\n\tpointer-events: none;\n\tstroke-linecap: round;\n\tstroke-linejoin: round;\n\ttransform-origin: top left;\n\toverflow: visible;\n}\n\n.tl-html-container {\n\tposition: absolute;\n\tinset: 0px;\n\theight: 100%;\n\twidth: 100%;\n\tpointer-events: none;\n\tstroke-linecap: round;\n\tstroke-linejoin: round;\n\t/* content-visibility: auto; */\n\ttransform-origin: top left;\n\tcolor: var(--tl-color-text-1);\n}\n\n.tl-content-slot {\n\tposition: absolute;\n\tinset: 0px;\n\theight: 100%;\n\twidth: 100%;\n\tpointer-events: none;\n\ttransform-origin: top left;\n}\n\n/* -------------------- Group shape ------------------ */\n\n.tl-group {\n\tstroke: var(--tl-color-text);\n\tstroke-width: calc(1px * var(--tl-scale));\n\topacity: 0.5;\n}\n\n/* --------------------- Arrow shape -------------------- */\n\n.tl-shape[data-shape-type='arrow'] .tl-text-label {\n\tposition: absolute;\n\ttop: -1px;\n\tleft: -1px;\n\twidth: 2px;\n\theight: 2px;\n\tpadding: 0px;\n\tdisplay: flex;\n\tjustify-content: center;\n\talign-items: center;\n\ttext-align: center;\n\tcolor: var(--tl-color-text);\n}\n\n.tl-shape[data-shape-type='arrow'] .tl-text-label__inner {\n\tborder-radius: var(--tl-radius-1);\n\tbox-sizing: content-box;\n\theight: max-content;\n\twidth: max-content;\n}\n\n.tl-shape[data-shape-type='arrow'] .tl-text {\n\theight: max-content;\n}\n\n/* ------------------- Bookmark shape ------------------- */\n\n.tl-bookmark__container {\n\twidth: 100%;\n\theight: 100%;\n\tposition: relative;\n\tborder: 1px solid var(--tl-color-panel-contrast);\n\tbackground-color: var(--tl-color-panel);\n\tborder-radius: var(--tl-radius-2);\n\tdisplay: flex;\n\tflex-direction: column;\n\toverflow: hidden;\n}\n\n.tl-bookmark__container--safariExport {\n\tborder: 1px solid var(--tl-color-divider);\n}\n\n.tl-bookmark__image_container {\n\tflex: 1 1 100%;\n\toverflow: hidden;\n\tborder-top-left-radius: var(--tl-radius-1);\n\tborder-top-right-radius: var(--tl-radius-1);\n\twidth: 100%;\n\theight: 100%;\n\tdisplay: flex;\n\tjustify-content: flex-end;\n\talign-items: flex-start;\n\tbox-shadow: inset 0px 0px 0px 1px var(--tl-color-divider);\n}\n\n.tl-bookmark__image_container > .tl-hyperlink-button::after {\n\tbackground-color: var(--tl-color-panel);\n}\n\n.tl-bookmark__placeholder {\n\twidth: 100%;\n\theight: 100%;\n\tbackground-color: var(--tl-color-muted-2);\n\tborder-bottom: 1px solid var(--tl-color-muted-2);\n}\n\n.tl-bookmark__image {\n\twidth: 100%;\n\theight: 100%;\n\tobject-fit: cover;\n\tobject-position: center;\n\tborder-bottom: 1px solid var(--tl-color-muted-2);\n}\n\n.tl-bookmark__copy_container {\n\tbackground-color: var(--tl-color-muted-0);\n\tpadding: var(--tl-space-4);\n\tpointer-events: all;\n\tdisplay: flex;\n\tflex-direction: column;\n\tjustify-content: space-between;\n\tflex: 1;\n}\n\n.tl-bookmark__copy_container:has(.tl-bookmark__link:only-child) {\n\tjustify-content: center;\n}\n\n.tl-bookmark__heading,\n.tl-bookmark__description,\n.tl-bookmark__link {\n\tmargin: 0px;\n\twidth: 100%;\n\tfont-family: inherit;\n}\n\n.tl-bookmark__heading {\n\tfont-size: 16px;\n\tline-height: 1.6;\n\tfont-weight: bold;\n\tpadding-bottom: var(--tl-space-2);\n\toverflow: hidden;\n\tmax-height: calc((16px * 1.6) * 2);\n\t-webkit-box-orient: vertical;\n\t-webkit-line-clamp: 2;\n\tline-clamp: 2;\n\ttext-overflow: ellipsis;\n\tdisplay: -webkit-box;\n}\n\n.tl-bookmark__description {\n\tfont-size: 12px;\n\tline-height: 1.5;\n\toverflow: hidden;\n\tmax-height: calc((12px * 1.5) * 3);\n\t-webkit-box-orient: vertical;\n\t-webkit-line-clamp: 3;\n\tline-clamp: 3;\n\ttext-overflow: ellipsis;\n\tdisplay: -webkit-box;\n\tcolor: var(--tl-color-text-1);\n\tmargin: var(--tl-space-2) 0px;\n}\n\n.tl-bookmark__heading + .tl-bookmark__link,\n.tl-bookmark__description + .tl-bookmark__link {\n\tmargin-top: var(--tl-space-3);\n}\n.tl-bookmark__link {\n\tfont-size: 12px;\n\tpointer-events: all;\n\tdisplay: flex;\n\tcolor: var(--tl-color-text-1);\n\talign-items: center;\n\tcursor: var(--tl-cursor-pointer);\n\twidth: fit-content;\n\tmax-width: 100%;\n\ttext-decoration: none;\n}\n\n.tl-bookmark__link > span {\n\tflex-shrink: 0px;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.tl-bookmark__link > .tl-hyperlink__icon {\n\tmargin-right: 8px;\n\tflex-shrink: 0;\n}\n\n.tl-bookmark__link > .tl-bookmark__favicon {\n\tmargin-right: 8px;\n\twidth: 16px;\n\theight: 16px;\n\tflex-shrink: 0;\n}\n\n/* -------------- Image and video shape ------------- */\n\n.tl-image,\n.tl-video {\n\tobject-fit: cover;\n\tbackground-size: cover;\n\twidth: 100%;\n\theight: 100%;\n}\n\n.tl-video-container,\n.tl-image-container,\n.tl-embed-container {\n\twidth: 100%;\n\theight: 100%;\n\tpointer-events: all;\n\t/* background-color: var(--tl-color-background); */\n\n\tdisplay: flex;\n\tjustify-content: center;\n\talign-items: center;\n}\n\n.tl-image-container {\n\tposition: relative;\n}\n\n.tl-image {\n\tposition: absolute;\n\tinset: 0;\n}\n\n.tl-video.tl-video-is-fullscreen {\n\tobject-fit: contain;\n\tbackground-size: contain;\n}\n\n/* -------------------- Note shape ------------------- */\n\n.tl-note__container {\n\tposition: relative;\n\twidth: 100%;\n\theight: 100%;\n\tpointer-events: all;\n\topacity: 1;\n\tz-index: var(--tl-layer-text-container);\n\tborder-radius: 1px;\n}\n\n.tl-note__container > .tl-text-label {\n\tcolor: currentColor;\n}\n\n.tl-note__attribution {\n\tposition: absolute;\n\tbottom: calc(4px * var(--note-attribution-scale, 1));\n\tright: calc(8px * var(--note-attribution-scale, 1));\n\tfont-family: var(--tl-font-sans);\n\tpointer-events: auto;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\tmax-width: 60%;\n\tz-index: 1;\n}\n\n/* ------------------- Frame shape ------------------- */\n\n.tl-frame__body {\n\tstroke-width: calc(1px * var(--tl-scale));\n}\n\n.tl-frame__creating {\n\tstroke: var(--tl-color-selected);\n\tfill: none;\n}\n\n.tl-frame-heading {\n\t--tl-frame-padding-x: 6px;\n\t--tl-frame-height: 24px;\n\t--tl-frame-minimum-width: 32px;\n\t--tl-frame-offset-width: 16px;\n\tdisplay: flex;\n\talign-items: center;\n\tposition: absolute;\n\ttransform-origin: 0% 100%;\n\toverflow: hidden;\n\tmax-width: 100%;\n\tmin-width: var(--tl-frame-minimum-width);\n\theight: auto;\n\tfont-size: 12px;\n\tpadding-bottom: 4px;\n\tpointer-events: all;\n}\n\n.tl-frame-heading-hit-area {\n\tpointer-events: all;\n\t/* scale from bottom left corner so we can pin it to the top left corner of the frame */\n\ttransform-origin: 0% 100%;\n\tdisplay: flex;\n\theight: var(--tl-frame-height);\n\twidth: 100%;\n\talign-items: center;\n\tborder-radius: var(--tl-radius-1);\n}\n\n.tl-frame-label {\n\tpointer-events: all;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\tpadding: 0px var(--tl-frame-padding-x);\n\tborder-radius: var(--tl-radius-1);\n\tposition: relative;\n\tfont-size: inherit;\n\twhite-space: pre;\n}\n\n.tl-frame-label__editing {\n\tcolor: transparent;\n\twhite-space: pre;\n\twidth: auto;\n\tmin-width: var(--tl-frame-minimum-width);\n\theight: 100%;\n\toverflow: visible;\n\tbackground-color: var(--tl-color-panel);\n\tborder-color: var(--tl-color-selected);\n\tbox-shadow: inset 0px 0px 0px 1.5px var(--tl-color-selected);\n}\n\n.tl-frame-name-input {\n\tposition: absolute;\n\tborder: none;\n\tbackground: none;\n\toutline: none;\n\tpadding: 0px var(--tl-frame-padding-x);\n\tinset: 0px;\n\theight: 100%;\n\twidth: 100%;\n\tfont-size: inherit;\n\tfont-family: inherit;\n\tfont-weight: inherit;\n\twidth: 100%;\n\tcolor: var(--tl-color-text-1);\n\tborder-radius: var(--tl-radius-1);\n\tuser-select: all;\n\t-webkit-user-select: text;\n\twhite-space: pre;\n\tcursor: var(--tl-cursor-text);\n}\n\n/* ------------------- Embed Shape ------------------ */\n\n.tl-embed {\n\tborder: none;\n\tborder-radius: var(--tl-radius-2);\n}\n\n/* -------------- Shape error boundary -------------- */\n\n.tl-shape-error-boundary {\n\twidth: 100%;\n\theight: 100%;\n\tbackground-color: var(--tl-color-muted-1);\n\tborder-width: calc(1px * var(--tl-scale));\n\tborder-color: var(--tl-color-muted-1);\n\tborder-style: solid;\n\tborder-radius: calc(var(--tl-radius-1) * var(--tl-scale));\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n\ttext-align: left;\n\tposition: relative;\n\tpointer-events: all;\n\toverflow: hidden;\n\tpadding: var(--tl-space-2);\n}\n\n.tl-shape-error-boundary::before {\n\ttransform: scale(var(--tl-scale));\n\tcontent: 'Error';\n\tfont-size: 12px;\n\tfont-family: inherit;\n\tcolor: var(--tl-color-text-0);\n}\n\n/* ----------------- Error boundary ----------------- */\n\n.tl-error-boundary {\n\twidth: 100%;\n\theight: 100%;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tpadding: var(--tl-space-4);\n\tbackground-color: var(--tl-color-background);\n\tcolor: var(--tl-color-text-1);\n\tposition: absolute;\n}\n\n.tl-error-boundary__overlay {\n\tposition: absolute;\n\tinset: 0px;\n\theight: 100%;\n\twidth: 100%;\n\tz-index: var(--tl-layer-error-overlay);\n\tbackground-color: var(--tl-color-overlay);\n}\n\n.tl-error-boundary__content * {\n\tuser-select: all;\n\t-webkit-user-select: text;\n\tpointer-events: all;\n}\n\n.tl-error-boundary__canvas {\n\tpointer-events: none;\n\tposition: absolute;\n\tinset: 0px;\n\theight: 100%;\n\twidth: 100%;\n\tz-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\tcontent: ' ';\n\tdisplay: block;\n\tposition: absolute;\n\tinset: 0px;\n\theight: 100%;\n\twidth: 100%;\n\tz-index: var(--tl-layer-error-canvas-after);\n\tpointer-events: all;\n}\n\n.tl-error-boundary__content {\n\twidth: fit-content;\n\theight: fit-content;\n\tmax-width: 100%;\n\twidth: 400px;\n\tmax-height: 100%;\n\tbackground-color: var(--tl-color-panel);\n\tpadding: 16px;\n\tborder-radius: 16px;\n\tbox-shadow: var(--tl-shadow-2);\n\tfont-size: 14px;\n\tfont-weight: 400;\n\tdisplay: flex;\n\tflex-direction: column;\n\toverflow: auto;\n\tz-index: var(--tl-layer-error-content);\n\tgap: 12px;\n}\n\n.tl-error-boundary__content__expanded {\n\twidth: 600px;\n}\n\n.tl-error-boundary__content h2 {\n\tfont-size: 16px;\n\tmargin: 0px;\n\tfont-weight: 500;\n}\n\n.tl-error-boundary__content h4 {\n\tborder: 1px solid var(--tl-color-low-border);\n\tmargin: -6px 0 0 0;\n\tpadding: var(--tl-space-5);\n\tborder-radius: var(--tl-radius-2);\n\tfont-weight: normal;\n}\n\n.tl-error-boundary__content p {\n\tline-height: 1.5;\n\tmargin: 0px;\n}\n\n.tl-error-boundary__content pre {\n\tbackground-color: var(--tl-color-muted-2);\n\tmargin-top: 0;\n\tpadding: var(--tl-space-5);\n\tborder-radius: var(--tl-radius-2);\n\toverflow: auto;\n\tfont-size: 12px;\n\tmax-height: 320px;\n}\n\n.tl-error-boundary__content button {\n\tbackground: none;\n\tborder: none;\n\tfont-family: inherit;\n\tfont-size: 14px;\n\tfont-weight: 500;\n\tpadding: var(--tl-space-4);\n\tborder-radius: var(--tl-radius-3);\n\tcursor: var(--tl-cursor-pointer);\n\tcolor: inherit;\n\tbackground-color: transparent;\n}\n\n.tl-error-boundary__content a {\n\tcolor: var(--tl-color-selected);\n\tfont-weight: 500;\n\ttext-decoration: none;\n}\n\n.tl-error-boundary__content__error {\n\tposition: relative;\n\tmargin: -6px 0 0 0;\n}\n\n.tl-error-boundary__content__error button {\n\tposition: absolute;\n\ttop: var(--tl-space-2);\n\tright: var(--tl-space-2);\n\tfont-size: 12px;\n\tpadding: var(--tl-space-2) var(--tl-space-3);\n\tbackground-color: var(--tl-color-panel);\n\tborder-radius: var(--tl-radius-1);\n}\n\n.tl-error-boundary__content__actions {\n\tdisplay: flex;\n\tjustify-content: space-between;\n\tgap: var(--tl-space-4);\n\tmargin: 0px;\n\tmargin-left: -4px;\n}\n.tl-error-boundary__content__actions__group {\n\tdisplay: flex;\n\tgap: var(--tl-space-4);\n}\n.tl-error-boundary__content .tl-error-boundary__reset {\n\tcolor: var(--tl-color-danger);\n}\n.tl-error-boundary__content .tl-error-boundary__refresh {\n\tbackground-color: var(--tl-color-primary);\n\tcolor: var(--tl-color-selected-contrast);\n}\n.tl-container__focused:not(.tl-container__no-focus-ring)\n\t.tlui-button.tl-error-boundary__refresh:focus-visible {\n\tborder-radius: 8px;\n\toutline-offset: 0;\n}\n\n/* ---------------- Hit test blocker ---------------- */\n\n.tl-hit-test-blocker {\n\tposition: absolute;\n\tz-index: var(--tl-layer-canvas-blocker);\n\tinset: 0px;\n\twidth: 100%;\n\theight: 100%;\n\tpointer-events: all;\n}\n\n.tl-hit-test-blocker__hidden {\n\tdisplay: none;\n}\n\n/* --------------------- Hovers --------------------- */\n\n@media (hover: hover) {\n\t.tl-handle__create:hover {\n\t\topacity: 1;\n\t}\n\n\t.tl-handle__bg:hover {\n\t\tcursor: var(--tl-cursor-grab);\n\t\tfill: var(--tl-color-selection-fill);\n\t}\n\n\t.tl-bookmark__link:hover {\n\t\tcolor: var(--tl-color-selected);\n\t}\n\n\t.tl-hyperlink-button:hover {\n\t\tcolor: var(--tl-color-selected);\n\t}\n\n\t.tl-error-boundary__content button:hover {\n\t\tbackground-color: var(--tl-color-low);\n\t}\n\t.tl-error-boundary__content a:hover {\n\t\tcolor: var(--tl-color-text-1);\n\t}\n\t.tl-error-boundary__content .tl-error-boundary__refresh:hover {\n\t\tbackground-color: var(--tl-color-primary);\n\t\topacity: 0.9;\n\t}\n\n\t/* 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\t.tl-canvas[data-iseditinganything='true'] .tl-text-wrapper:hover .tl-text-input {\n\t\tz-index: var(--tl-layer-text-editor);\n\t\tpointer-events: all;\n\t}\n}\n\n/* @tldraw/ui */\n\n.tl-container {\n\t/*\n\t * UI z-index layers\n\t *\n\t * menu-click-capture and panels (.tlui-layout) participate in the\n\t * container-level stacking context alongside the canvas layers:\n\t * watermark (200) < canvas-in-front (250) < menu-click-capture (250) < panels (300)\n\t *\n\t * menus, toasts, header-footer, etc. are children of .tlui-layout\n\t * (which creates its own stacking context at z-index 300), so their\n\t * z-indices are relative to the layout, not the container.\n\t *\n\t * cursor is rendered inside canvas-in-front (stacking context at\n\t * z-index 250), so their z-indices are relative to that context.\n\t */\n\t--tl-layer-above: 1;\n\t--tl-layer-focused-input: 10;\n\t--tl-layer-menu-click-capture: 250;\n\t--tl-layer-panels: 300;\n\t--tl-layer-menus: 400;\n\t--tl-layer-toasts: 650;\n\t--tl-layer-cursor: 700;\n\t--tl-layer-header-footer: 999;\n}\n\n/* Button */\n\n.tlui-button {\n\tposition: relative;\n\theight: 40px;\n\tmin-width: 40px;\n\tpadding: 0px 12px;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tbackground-color: transparent;\n\tborder: transparent;\n\tcolor: currentColor;\n\tcursor: pointer;\n\tpointer-events: all;\n\tfont-weight: inherit;\n\tfont-family: inherit;\n\tline-height: inherit;\n\ttext-rendering: optimizeLegibility;\n\tfont-size: 12px;\n\tgap: 0px;\n\tcolor: var(--tl-color-text-1);\n\tz-index: 0;\n}\n\n.tlui-button:disabled,\n.tlui-button[data-disabled] {\n\tcolor: var(--tl-color-text-disabled);\n\ttext-shadow: none;\n\tcursor: default;\n}\n\n.tlui-button:disabled .tlui-kbd,\n.tlui-button[data-disabled] .tlui-kbd {\n\tcolor: var(--tl-color-text-disabled);\n}\n\n.tlui-button > * {\n\tposition: relative;\n\tz-index: var(--tl-layer-above);\n}\n\n.tlui-button__label {\n\tfont-size: 12px;\n\tflex-grow: 2;\n\ttext-align: start;\n\tmin-width: 0;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-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\tborder-radius: 10px;\n\toutline: 2px solid var(--tl-color-focus);\n\toutline-offset: -5px;\n}\n.tl-container__focused:not(.tl-container__no-focus-ring) .tlui-button__tool:focus-visible {\n\tborder-radius: 12px;\n}\n.tlui-slider__container:has(.tlui-slider__thumb:focus-visible) {\n\tborder-radius: 10px;\n\toutline: 2px solid var(--tl-color-focus);\n\toutline-offset: -5px;\n}\n\n.tlui-button::after {\n\tdisplay: block;\n\tcontent: '';\n\tposition: absolute;\n\tinset: 4px;\n\tborder-radius: var(--tl-radius-2);\n\tbackground: var(--tl-color-muted-2);\n\topacity: 0;\n}\n\n.tlui-button__menu[data-highlighted]::after {\n\topacity: 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\tbackground: var(--tl-color-hint);\n\topacity: 1;\n}\n\n.tlui-button[aria-expanded='true'][data-direction='left'] {\n\tbackground: linear-gradient(270deg, rgba(144, 144, 144, 0) 0%, var(--tl-color-muted-2) 100%);\n\topacity: 1;\n}\n\n@media (hover: hover) {\n\t.tlui-button[aria-expanded='true'][data-direction='left']:not(:hover)::after {\n\t\tbackground: linear-gradient(270deg, rgba(144, 144, 144, 0) 0%, var(--tl-color-muted-2) 100%);\n\t\topacity: 1;\n\t}\n\n\t.tlui-button:not(:disabled, [data-disabled]):hover {\n\t\tz-index: 1;\n\t}\n\n\t.tlui-button:not(:disabled, [data-disabled]):hover::after {\n\t\topacity: 1;\n\t}\n}\n\n.tlui-button__icon + .tlui-button__label {\n\tmargin-inline-start: var(--tl-space-2);\n}\n\n/* Low button */\n\n.tlui-button__low {\n\tborder-radius: var(--tl-radius-3);\n\tbackground-color: var(--tl-color-low);\n}\n\n.tlui-button__low::after {\n\tbackground-color: var(--tl-color-muted-2);\n\topacity: 0;\n}\n\n@media (hover: hover) {\n\t.tlui-button__low:hover::after {\n\t\topacity: 1;\n\t}\n}\n\n/* Primary / danger buttons */\n\n.tlui-button__primary {\n\tcolor: var(--tl-color-primary);\n}\n\n.tlui-button__danger {\n\tcolor: var(--tl-color-danger);\n\ttext-shadow: none;\n}\n\n@media (hover: hover) {\n\t.tlui-button__primary:not(:disabled, [data-disabled], :focus-visible):hover {\n\t\tcolor: var(--tl-color-primary);\n\t}\n\n\t.tlui-button__danger:not(:disabled, [data-disabled], :focus-visible):hover {\n\t\tcolor: var(--tl-color-danger);\n\t\ttext-shadow: none;\n\t}\n}\n\n/* Menu button */\n\n.tlui-button__menu {\n\theight: 40px;\n\tmin-height: 40px;\n\twidth: 100%;\n\tgap: 8px;\n\tmargin-top: -4px;\n}\n\n.tlui-button__menu::after {\n\tinset: 4px;\n\tborder-radius: var(--tl-radius-2);\n}\n\n.tlui-button__menu > .tlui-icon + .tlui-button__label {\n\tmargin-inline-start: 0px;\n}\n\n.tlui-button__menu:nth-child(1) {\n\tmargin-top: 0px;\n}\n\n.tlui-button__menu:nth-last-child(1) {\n\tmargin-bottom: 0px;\n}\n\n/* Menu checkbox button */\n\n.tlui-button__checkbox {\n\tpadding-left: 8px;\n}\n\n.tlui-button__checkbox__indicator {\n\twidth: 15px;\n\theight: 15px;\n}\n\n/* Tool lock button */\n\n.tlui-main-toolbar__lock-button {\n\tposition: absolute;\n\tpointer-events: all;\n\theight: 40px;\n\twidth: 40px;\n\tborder-radius: var(--tl-radius-2);\n}\n\n.tlui-main-toolbar--horizontal .tlui-main-toolbar__lock-button {\n\ttop: 4px;\n\tright: 0px;\n\tmin-width: 0px;\n}\n\n.tlui-main-toolbar--vertical .tlui-main-toolbar__lock-button {\n\tbottom: 0px;\n\tmin-height: 0px;\n}\n\n.tlui-main-toolbar__lock-button::after {\n\ttop: 4px;\n\tleft: 8px;\n\tinset: 4px;\n}\n\n/* Tool button */\n\n.tlui-button__tool {\n\tposition: relative;\n\theight: 48px;\n\twidth: 48px;\n}\n\n.tlui-button__tool::after {\n\tinset: 4px;\n\tborder-radius: 8px;\n}\n\n.tlui-button__tool[aria-pressed='true'] {\n\tcolor: var(--tl-color-selected-contrast);\n}\n\n.tlui-button__tool[aria-pressed='true']:not(:disabled, [data-disabled], :focus-visible):active {\n\tcolor: var(--tl-color-selected-contrast);\n}\n\n.tlui-button__tool[aria-pressed='true']:not(:disabled, [data-disabled])::after {\n\tbackground: var(--tl-color-selected);\n\topacity: 1;\n}\n\n.tlui-layout__mobile .tlui-main-toolbar--horizontal .tlui-button__tool {\n\theight: 48px;\n\twidth: 43px;\n}\n\n.tlui-layout__mobile .tlui-main-toolbar--horizontal .tlui-button__tool > .tlui-icon {\n\theight: 16px;\n\twidth: 16px;\n}\n\n/* Row layout */\n\n.tlui-row {\n\tdisplay: flex;\n\tflex-direction: row;\n\tpadding: 0 2px;\n}\n.tlui-row > * {\n\tmargin-left: -2px;\n\tmargin-right: -2px;\n}\n\n/* Column layout */\n\n.tlui-column {\n\tdisplay: flex;\n\tflex-direction: column;\n\tpadding: 2px 0;\n}\n.tlui-column > * {\n\tmargin-top: -2px;\n\tmargin-bottom: -2px;\n}\n\n/* Grid layout */\n\n.tlui-grid {\n\tdisplay: grid;\n\tgrid-template-columns: repeat(4, 1fr);\n\tgrid-auto-flow: row;\n\toverflow: hidden;\n\tpadding: 2px;\n}\n.tlui-grid > * {\n\tmargin: -2px;\n}\n\n/* Zoom button */\n\n.tlui-zoom-menu__button {\n\twidth: 60px;\n\tmin-width: 60px;\n\ttext-align: center;\n}\n\n.tlui-zoom-menu__button[data-state='open']::after {\n\tbackground: linear-gradient(0deg, rgba(144, 144, 144, 0) 0%, var(--tl-color-muted-2) 100%);\n\topacity: 1;\n}\n\n/* --------------------- Layout --------------------- */\n\n.tlui-layout {\n\tposition: relative;\n\tdisplay: grid;\n\tgrid-template-columns: 1fr;\n\tgrid-template-rows: minmax(0px, 1fr) auto;\n\tgrid-auto-rows: auto;\n\theight: 100%;\n\tmax-height: 100%;\n\toverflow: clip;\n\tpointer-events: none;\n\tuser-select: none;\n\tcontain: strict;\n\tz-index: var(--tl-layer-panels);\n\ttransform: translate3d(0, 0, 0);\n\t--tl-sab: env(safe-area-inset-bottom);\n\tfont-weight: 500;\n\tline-height: 1.6;\n\t-webkit-font-smoothing: antialiased;\n\t-moz-osx-font-smoothing: grayscale;\n\tfont-smooth: antialiased;\n\ttext-rendering: optimizeLegibility;\n}\n\n.tlui-layout__top {\n\tgrid-column: 1;\n\tgrid-row: 1;\n\tdisplay: flex;\n\tmin-width: 0px;\n\tjustify-content: space-between;\n}\n\n.tlui-layout__top__left {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: flex-start;\n\tjustify-content: flex-start;\n\twidth: 100%;\n\theight: 100%;\n\tflex: 0 1 0;\n}\n\n.tlui-layout__top__right {\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: flex-end;\n\tjustify-content: flex-start;\n\theight: 100%;\n\tflex: 0 0 auto;\n\tmin-width: 0px;\n}\n\n.tlui-scrollable,\n.tlui-scrollable * {\n\tpointer-events: all;\n\ttouch-action: auto;\n\toverscroll-behavior: none;\n}\n\n/* ----------------- Helper Buttons ---------------- */\n\n.tlui-helper-buttons {\n\tposition: relative;\n\tdisplay: flex;\n\tflex-direction: column;\n\tjustify-content: flex-start;\n\talign-items: flex-start;\n\twidth: min-content;\n\tgap: var(--tl-space-3);\n\tmargin: var(--tl-space-2) var(--tl-space-3);\n\twhite-space: nowrap;\n\tpointer-events: none;\n\tz-index: var(--tl-layer-panels);\n}\n\n/* ---------------------- Icon ---------------------- */\n\n.tlui-icon {\n\tflex-shrink: 0;\n\twidth: 18px;\n\theight: 18px;\n\tbackground-color: currentColor;\n}\n\n.tlui-icon__placeholder {\n\tflex-shrink: 0;\n\twidth: 18px;\n\theight: 18px;\n\tbackground-color: transparent;\n}\n\n.tlui-icon__small {\n\twidth: 15px;\n\theight: 15px;\n}\n\n.tlui-icon__tiny {\n\twidth: 12px;\n\theight: 12px;\n}\n\n/* --------------------- Slider --------------------- */\n\n.tlui-slider__container {\n\twidth: 100%;\n\tpadding: 0px var(--tl-space-4);\n}\n\n.tlui-slider {\n\tposition: relative;\n\tdisplay: flex;\n\talign-items: center;\n\tuser-select: none;\n\ttouch-action: none;\n\twidth: 100%;\n}\n\n.tlui-slider__track {\n\tposition: relative;\n\tflex-grow: 1;\n\theight: 44px;\n\tcursor: pointer;\n}\n\n.tlui-slider__track::after {\n\tdisplay: block;\n\tposition: absolute;\n\ttop: calc(50% - 2px);\n\tcontent: '';\n\theight: 3px;\n\twidth: 100%;\n\tbackground-color: var(--tl-color-muted-1);\n\tborder-radius: 14px;\n}\n\n.tlui-slider__range {\n\tposition: absolute;\n\ttop: calc(50% - 2px);\n\tleft: 0px;\n\theight: 3px;\n\tbackground-color: var(--tl-color-selected);\n\tborder-radius: 14px;\n}\n\n.tlui-slider__thumb {\n\tall: unset;\n\tcursor: grab;\n\tdisplay: block;\n\twidth: 18px;\n\theight: 18px;\n\tposition: relative;\n\ttop: -1px;\n\tbackground-color: var(--tl-color-panel);\n\tborder-radius: 999px;\n\tbox-shadow: inset 0px 0px 0px 2px var(--tl-color-text-1);\n}\n\n.tlui-slider__thumb:active {\n\tcursor: grabbing;\n\tbox-shadow:\n\t\tinset 0px 0px 0px 2px var(--tl-color-text-1),\n\t\tvar(--tl-shadow-1);\n}\n\n/* ---------------------- Input --------------------- */\n\n.tlui-input {\n\tbackground: none;\n\tmargin: 0px;\n\tposition: relative;\n\tz-index: var(--tl-layer-above);\n\theight: 40px;\n\tmax-height: 40px;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tfont-family: inherit;\n\tfont-size: 12px;\n\tfont-weight: inherit;\n\tcolor: var(--tl-color-text-1);\n\tpadding: var(--tl-space-4);\n\tpadding-left: 0px;\n\tborder: none;\n\toutline: none;\n\ttext-overflow: ellipsis;\n\twidth: 100%;\n\tuser-select: all;\n\ttext-rendering: optimizeLegibility;\n\t-webkit-user-select: auto !important;\n}\n\n.tlui-input::placeholder {\n\tcolor: var(--tl-color-text-3);\n}\n\n.tlui-input__wrapper {\n\twidth: 100%;\n\theight: 44px;\n\tdisplay: flex;\n\talign-items: center;\n\tgap: var(--tl-space-4);\n\tcolor: var(--tl-color-text);\n}\n\n.tlui-input__wrapper > .tlui-icon {\n\tflex-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\tflex-shrink: 0;\n\toverflow-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 \uD83E\uDD26\u200D\u2642\uFE0F */\n/* https://css-tricks.com/16px-or-larger-text-prevents-ios-form-zoom/ */\n@supports (-webkit-touch-callout: none) {\n\t/* CSS specific to iOS devices */\n\t.tlui-input {\n\t\tfont-size: 16px;\n\t}\n}\n\n/* --------------------- Select --------------------- */\n\n/* Trigger extends .tlui-button with space-between layout */\n.tlui-select__trigger {\n\tjustify-content: space-between;\n\tgap: var(--tl-space-3);\n}\n\n.tlui-select__trigger[data-state='open']::after {\n\topacity: 1;\n}\n\n.tlui-select__value {\n\tdisplay: flex;\n\talign-items: center;\n\tgap: var(--tl-space-2);\n}\n\n.tlui-select__chevron {\n\tflex-shrink: 0;\n\tcolor: var(--tl-color-text-2);\n}\n\n/* Content extends .tlui-menu with Radix-aware sizing */\n.tlui-select__content {\n\tmin-width: var(--radix-select-trigger-width);\n\tmax-height: var(--radix-select-content-available-height);\n}\n\n.tlui-select__viewport {\n\tpadding: 4px 0;\n}\n\n/* Item extends .tlui-button__checkbox with left-aligned text */\n.tlui-select__item {\n\tjustify-content: flex-start;\n}\n\n.tlui-select__item-indicator {\n\tvisibility: hidden;\n}\n\n.tlui-select__item[data-state='checked'] .tlui-select__item-indicator {\n\tvisibility: visible;\n}\n\n/* ----------------------- Kbd ---------------------- */\n\n.tlui-kbd {\n\tfont-family: inherit;\n\tfont-size: 11px;\n\tline-height: 11px;\n\tdisplay: grid;\n\tjustify-items: center;\n\tgrid-auto-flow: column;\n\tgrid-template-columns: auto;\n\tgrid-auto-columns: minmax(1em, auto);\n\talign-self: bottom;\n\tcolor: currentColor;\n\tmargin-inline-start: var(--tl-space-4);\n}\n\n.tlui-kbd > span {\n\twidth: 100%;\n\ttext-align: center;\n\tdisplay: inline;\n\tmargin: 0px;\n\tpadding: 2px;\n\tborder-radius: 2px;\n}\n\n.tlui-kbd > span:last-child {\n\tpadding-inline-end: 0;\n}\n\n.tlui-kbd:not(:last-child) {\n\tmargin-inline-end: var(--tl-space-2);\n}\n\n/* Focus Mode Button */\n\n.tlui-focus-button {\n\tz-index: var(--tl-layer-panels);\n\tpointer-events: all;\n}\n\n/* ---------------------- Menu ---------------------- */\n\n.tlui-menu:empty {\n\tdisplay: none;\n}\n\n.tlui-menu {\n\tz-index: var(--tl-layer-menus);\n\theight: fit-content;\n\twidth: fit-content;\n\tborder-radius: var(--tl-radius-3);\n\tpointer-events: all;\n\ttouch-action: auto;\n\toverflow-y: auto;\n\toverscroll-behavior: none;\n\tbackground-color: var(--tl-color-panel);\n\tbox-shadow: var(--tl-shadow-3);\n}\n\n@media (max-height: 600px) {\n\t.tlui-menu {\n\t\tmax-height: 70vh;\n\t}\n}\n\n.tlui-menu::-webkit-scrollbar {\n\tdisplay: none;\n}\n\n/* Menu groups */\n\n.tlui-menu__group {\n\twidth: 100%;\n}\n\n.tlui-menu__group:empty {\n\tdisplay: none;\n}\n\n.tlui-menu__group {\n\tborder-bottom: 1px solid var(--tl-color-divider);\n}\n.tlui-menu__group:nth-last-of-type(1) {\n\tborder-bottom: none;\n}\n\n/* Submenu triggers */\n\n.tlui-menu__submenu__trigger {\n\t--gradient-angle: 90deg;\n}\n\n.tlui-menu__submenu__trigger[data-direction='left'] {\n\t--gradient-angle: 270deg;\n}\n\n.tlui-menu__submenu__trigger[data-state='open']::after {\n\topacity: 1;\n\tbackground: linear-gradient(\n\t\tvar(--gradient-angle),\n\t\trgba(144, 144, 144, 0) 0%,\n\t\tvar(--tl-color-muted-2) 100%\n\t);\n}\n\n@media (hover: hover) {\n\t.tlui-menu__submenu__trigger:is(:hover, [data-state='open'])::after {\n\t\topacity: 1;\n\t\tbackground: linear-gradient(\n\t\t\tvar(--gradient-angle),\n\t\t\trgba(144, 144, 144, 0) 0%,\n\t\t\tvar(--tl-color-muted-2) 100%\n\t\t);\n\t}\n}\n\n/* Menu Sizes */\n\n.tlui-menu[data-size='large'] > .tlui-menu__group {\n\tmin-width: initial;\n}\n\n.tlui-menu[data-size='medium'] > .tlui-menu__group {\n\tmin-width: 144px;\n}\n\n.tlui-menu[data-size='small'] > .tlui-menu__group {\n\tmin-width: 96px;\n}\n\n.tlui-menu[data-size='tiny'] > .tlui-menu__group {\n\tmin-width: 0px;\n}\n\n.tlui-menu-click-capture {\n\tposition: fixed;\n\tinset: 0;\n\tz-index: var(--tl-layer-menu-click-capture);\n}\n\n/* --------------------- Popover -------------------- */\n\n.tlui-popover {\n\tposition: relative;\n\tdisplay: flex;\n\talign-content: stretch;\n}\n\n.tlui-popover__content {\n\tposition: relative;\n\tmax-height: calc(var(--radix-popover-content-available-height) - 8px);\n\tmargin: 0px;\n\tborder: none;\n\tborder-radius: var(--tl-radius-3);\n\tbackground-color: var(--tl-color-panel);\n\tbox-shadow: var(--tl-shadow-3);\n\tz-index: var(--tl-layer-menus);\n\toverflow: hidden;\n\toverflow-y: auto;\n\ttouch-action: auto;\n\toverscroll-behavior: none;\n\tscrollbar-width: none;\n\t-ms-overflow-style: none;\n}\n\n.tlui-hover-card__content {\n\tposition: relative;\n\tmargin: 0px;\n\tborder: none;\n\tborder-radius: var(--tl-radius-3);\n\tbackground-color: var(--tl-color-panel);\n\tbox-shadow: var(--tl-shadow-3);\n\tz-index: var(--tl-layer-menus);\n}\n\n/* -------------------- Menu Zone ------------------- */\n\n.tlui-menu-zone {\n\tposition: relative;\n\tz-index: var(--tl-layer-panels);\n\twidth: fit-content;\n\tborder-right: 2px solid var(--tl-color-background);\n\tborder-bottom: 2px solid var(--tl-color-background);\n\tborder-bottom-right-radius: var(--tl-radius-4);\n\tbackground-color: var(--tl-color-low);\n}\n\n.tlui-menu-zone *[data-state='open']::after {\n\tbackground: linear-gradient(180deg, rgba(144, 144, 144, 0) 0%, var(--tl-color-muted-2) 100%);\n\topacity: 1;\n}\n\n@media (hover: hover) {\n\t.tlui-menu-zone *[data-state='open']:not(:hover)::after {\n\t\tbackground: linear-gradient(180deg, rgba(144, 144, 144, 0) 0%, var(--tl-color-muted-2) 100%);\n\t\topacity: 1;\n\t}\n}\n\n/* ------------------- Page Select ------------------ */\n\n.tlui-page-menu__wrapper {\n\tdisplay: flex;\n\tflex-direction: column;\n\twidth: 220px;\n}\n\n.tlui-page-menu__trigger {\n\twidth: auto;\n}\n\n.tlui-page-menu__trigger[data-state='open']::after {\n\tbackground: linear-gradient(180deg, rgba(144, 144, 144, 0) 0%, var(--tl-color-muted-2) 100%);\n\topacity: 1;\n}\n\n.tlui-page-menu__list {\n\tflex: 0 0 auto;\n\toverflow-x: hidden;\n\toverflow-y: auto;\n\tscrollbar-width: none;\n\t-ms-overflow-style: none;\n}\n\n.tlui-page-menu__list::-webkit-scrollbar {\n\tdisplay: none;\n}\n\n.tlui-page-menu__list__content {\n\tposition: relative;\n\toverflow: hidden;\n}\n\n.tlui-page-menu__resize-handle {\n\tflex: 0 0 auto;\n\tposition: relative;\n\theight: 1px;\n\tcursor: ns-resize;\n\ttouch-action: none;\n\tbackground: var(--tl-color-divider);\n\ttransition: background-color ease-in-out 150ms 80ms;\n\tz-index: 999;\n}\n\n/* Hit test area */\n.tlui-page-menu__resize-handle::after {\n\tcontent: '';\n\tposition: absolute;\n\tinset: -4px 0;\n\tbackground: none;\n\tz-index: 2000;\n\tpointer-events: all;\n}\n\n/* Thick blue indicator */\n.tlui-page-menu__resize-handle::before {\n\tcontent: '';\n\tposition: absolute;\n\ttop: 0;\n\tleft: 0;\n\tright: 0;\n\theight: 1.5px;\n\tbackground: transparent;\n\tpointer-events: none;\n\tz-index: 2001;\n}\n\n.tlui-page-menu__resize-handle[data-resizing='true']::before {\n\tbackground: var(--tl-color-selected);\n\ttransition: none;\n}\n\n.tlui-page-menu__create-button {\n\tflex: 0 0 auto;\n\tmargin: 0;\n}\n\n.tlui-page-menu__item__button {\n\tflex: 1 1 auto;\n\tmin-width: 0px;\n}\n\n.tlui-page-menu__item__button::after,\n.tlui-page-menu__item__drag-handle::after {\n\tcontent: none;\n}\n\n.tlui-page-menu__item__button > .tlui-button__label {\n\tpadding-inline-end: calc(40px - 12px);\n}\n\n.tlui-page-menu__item {\n\tposition: absolute;\n\ttop: 0px;\n\tinset-inline-start: 0px;\n\twidth: 100%;\n\theight: 40px;\n\tdisplay: flex;\n\tflex-direction: row;\n\talign-items: center;\n\toverflow: hidden;\n\ttransition: transform 0.08s ease-out;\n}\n\n.tlui-page-menu__item::before {\n\tcontent: '';\n\tposition: absolute;\n\tinset: 4px;\n\tborder-radius: var(--tl-radius-2);\n\tbackground: var(--tl-color-muted-2);\n\topacity: 0;\n\tpointer-events: none;\n}\n\n.tlui-page-menu__item > * {\n\tposition: relative;\n\tz-index: var(--tl-layer-above);\n}\n\n.tlui-page-menu__item[data-iscurrent='true']:not([data-editing='true'])::before {\n\tbackground: var(--tl-color-hint);\n\topacity: 1;\n}\n\n.tlui-page-menu__item__title {\n\tflex: 1;\n\theight: 40px;\n\tposition: relative;\n}\n\n.tlui-page-menu__item__title::before {\n\tcontent: '';\n\tposition: absolute;\n\tinset: 4px;\n\tborder-radius: var(--tl-radius-2);\n\tborder: 1px solid var(--tl-color-primary);\n\tpointer-events: none;\n}\n\n.tlui-page-menu__item__title > .tlui-input__wrapper {\n\theight: 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\tcursor: grabbing;\n\ttransition: none;\n}\n\n.tlui-page-menu__item[data-dragging='true'] {\n\tbackground: var(--tl-color-panel);\n\topacity: 1;\n}\n\n.tlui-page-menu__item__drag-handle {\n\tflex: 0 0 32px;\n\tmin-width: 32px;\n\tpadding: 0px;\n\tcolor: var(--tl-color-text-3);\n\tcursor: grab;\n\ttouch-action: none;\n}\n\n.tlui-page-menu__item__drag-handle > .tlui-icon {\n\tposition: relative;\n\tinset-inline-start: 1px;\n}\n\n.tlui-page-menu__item__drag-handle + .tlui-page-menu__item__button {\n\tpadding-inline-start: 0px;\n}\n\n.tlui-page-menu__item__input {\n\tmargin-inline-start: 12px;\n}\n\n.tlui-page-menu__item__submenu {\n\tposition: absolute;\n\tinset-inline-end: 0px;\n\ttop: 0px;\n\theight: 100%;\n\tz-index: 10;\n}\n\n.tlui-page-menu__item__submenu > .tlui-button {\n\topacity: 0;\n\tcolor: var(--tl-color-text-3);\n}\n\n.tlui-page-menu__item__submenu > .tlui-button::after {\n\tcontent: none;\n}\n\n.tlui-page-menu__item:has(.tlui-button:focus-visible)\n\t> .tlui-page-menu__item__submenu\n\t> .tlui-button,\n.tlui-page-menu__item__submenu > .tlui-button[data-state='open'],\n.tlui-page-menu__item__submenu > .tlui-button:focus-visible {\n\topacity: 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\t.tlui-page-menu__item\n\t> .tlui-page-menu__item__submenu\n\t> .tlui-button {\n\topacity: 0;\n}\n\n.tl-container[data-coarse='true'] .tlui-page-menu__item__submenu > .tlui-button {\n\topacity: 1;\n}\n\n.tl-container[data-coarse='true'] .tlui-page-menu__item__button > .tlui-button__label {\n\tpadding-inline-end: calc(40px - 12px);\n}\n\n@media (hover: hover) {\n\t.tlui-page-menu__resize-handle:hover::before {\n\t\tbackground: var(--tl-color-selected);\n\t\theight: 2px;\n\t\ttransition: background-color 150ms 80ms;\n\t}\n\n\t.tlui-page-menu__item:not([data-editing='true']):hover::before {\n\t\topacity: 1;\n\t}\n\n\t.tlui-page-menu__item:hover > .tlui-page-menu__item__submenu > .tlui-button {\n\t\topacity: 1;\n\t}\n\n\t.tlui-page-menu__item__button > .tlui-button__label {\n\t\tpadding-inline-end: 0px;\n\t}\n\n\t.tlui-page-menu__item:hover > .tlui-page-menu__item__button > .tlui-button__label {\n\t\tpadding-inline-end: calc(40px - 12px);\n\t}\n\n\t.tlui-page-menu__item__submenu > .tlui-button:hover {\n\t\tcolor: var(--tl-color-text);\n\t}\n}\n/* -------------- Skip to main content -------------- */\n\n.tl-skip-to-main-content {\n\tposition: fixed;\n\ttop: 48px;\n\tleft: -9999px;\n\tpadding: 8px 16px;\n\tz-index: var(--tl-layer-toasts);\n}\n\n.tl-skip-to-main-content:focus {\n\tleft: 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\tdisplay: none;\n}\n\n/* ---------------- Offline indicator --------------- */\n\n.tlui-offline-indicator {\n\tdisplay: flex;\n\tcolor: var(--tl-color-background);\n\tbackground-color: var(--tl-color-text);\n\tborder: none;\n\twidth: 32px;\n\theight: 32px;\n\talign-items: center;\n\tjustify-content: center;\n\tborder-radius: 99px;\n\tcursor: default;\n\topacity: 0;\n\tanimation: tl-fade-in;\n\tanimation-duration: 0.12s;\n\tanimation-delay: 2s;\n\tanimation-fill-mode: forwards;\n}\n\n/* ------------------- Style panel ------------------ */\n\n.tlui-style-panel__wrapper {\n\tbox-shadow: var(--tl-shadow-2);\n\tborder-radius: var(--tl-radius-3);\n\tpointer-events: all;\n\tbackground-color: var(--tl-color-panel);\n\theight: fit-content;\n\tmax-height: 100%;\n\tmargin: 8px;\n\tmargin-top: 4px;\n\ttouch-action: auto;\n\toverscroll-behavior: none;\n\toverflow-y: auto;\n\toverflow-x: hidden;\n\tcolor: 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\tmargin-top: 8px;\n}\n\n.tlui-style-panel {\n\tposition: relative;\n\tz-index: var(--tl-layer-panels);\n\tpointer-events: all;\n\twidth: 148px;\n\tmax-width: 148px;\n}\n\n.tlui-style-panel[data-enhanced-a11y-mode='true'] .tlui-button[data-isactive='true'] {\n\tborder-radius: 10px;\n\toutline: 2px solid var(--tl-color-text);\n\toutline-offset: -5px;\n}\n\n.tlui-style-panel::-webkit-scrollbar {\n\tdisplay: none;\n}\n\n.tlui-style-panel .tlui-button.select {\n\twidth: 100%;\n}\n\n.tlui-style-panel__section {\n\tdisplay: flex;\n\tposition: relative;\n\tflex-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\tborder-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\t\t:nth-last-child(-n + 1 of .tlui-style-panel__section:not(:empty))\n\t) {\n\tmargin-bottom: 7px;\n}\n\n.tlui-style-panel__section:empty {\n\tdisplay: 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\tflex-wrap: wrap;\n}\n\n.tlui-style-panel__section .tlui-toolbar:has(.tlui-toolbar) .tlui-style-panel__subheading {\n\tmargin-left: -2px;\n}\n\n.tlui-style-panel__section__common:not(:only-child) {\n\tmargin-bottom: 7px;\n\tborder-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\twidth: 40px;\n\tpadding: 0;\n}\n\n.tlui-style-panel__dropdown-picker:only-child {\n\tflex: 1;\n}\n\n.tlui-style-panel__double-select-picker {\n\tdisplay: flex;\n\tgrid-template-columns: 1fr auto;\n\talign-items: center;\n\tpadding-left: var(--tl-space-4);\n\tcolor: var(--tl-color-text-1);\n\tfont-size: 12px;\n}\n\n.tlui-style-panel__double-select-picker-label {\n\ttext-overflow: ellipsis;\n\toverflow: hidden;\n\twhite-space: nowrap;\n\tflex-grow: 2;\n\tmax-width: 100%;\n}\n\n.tlui-style-panel .tlui-button[data-state='open']::after {\n\topacity: 1;\n\tbackground: linear-gradient(270deg, rgba(144, 144, 144, 0) 0%, var(--tl-color-muted-2) 100%);\n}\n\n@media (hover: hover) {\n\t.tlui-style-panel .tlui-button[aria-expanded='true'] {\n\t\tbackground: none;\n\t}\n\t.tlui-style-panel .tlui-button[data-state='open']:not(:hover)::after {\n\t\topacity: 1;\n\t\tbackground: linear-gradient(270deg, rgba(144, 144, 144, 0) 0%, var(--tl-color-muted-2) 100%);\n\t}\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\tmargin: 0;\n\tpadding: var(--tl-space-2) var(--tl-space-3) 0px var(--tl-space-4);\n\tfont-size: 12px;\n\tfont-weight: inherit;\n\tline-height: inherit;\n}\n\n.tlui-style-panel .tlui-style-panel__subheading:nth-of-type(1) {\n\tpadding-top: var(--tl-space-3);\n}\n\n.tlui-style-panel__subheading + .tlui-slider__container {\n\tpadding-top: 0px;\n}\n\n/* --------------------- Bottom --------------------- */\n\n.tlui-layout__bottom {\n\tgrid-row: 2;\n\twidth: 100%;\n\tmin-width: 0px;\n}\n\n.tlui-layout__bottom__main {\n\twidth: 100%;\n\tdisplay: flex;\n\talign-items: flex-end;\n\tjustify-content: center;\n}\n\n/* ------------------- Navigation ------------------- */\n\n.tlui-navigation-panel {\n\tdisplay: flex;\n\twidth: min-content;\n\tflex-direction: column;\n\tz-index: var(--tl-layer-panels);\n\tpointer-events: all;\n\tposition: absolute;\n\tleft: 0px;\n}\n\n.tl-container[dir='rtl'] .tlui-navigation-panel {\n\tleft: auto;\n\tright: 0px;\n}\n\n.tlui-navigation-panel::before {\n\tcontent: '';\n\tdisplay: block;\n\tposition: absolute;\n\tz-index: -1;\n\tinset: -2px -2px 0px 0px;\n\tborder-radius: 0;\n\tborder-top: 2px solid var(--tl-color-background);\n\tborder-right: 2px solid var(--tl-color-background);\n\tborder-top-right-radius: var(--tl-radius-4);\n\tbackground-color: var(--tl-color-low);\n}\n\n.tl-container[dir='rtl'] .tlui-navigation-panel::before {\n\tinset: -2px 0px 0px -2px;\n\tborder-right: 0;\n\tborder-left: 2px solid var(--tl-color-background);\n\tborder-top-right-radius: 0;\n\tborder-top-left-radius: var(--tl-radius-4);\n}\n\n.tlui-navigation-panel[data-a11y='true']::before {\n\tdisplay: none;\n}\n\n/* Minimap */\n\n.tlui-minimap {\n\twidth: 100%;\n\theight: 96px;\n\tmin-height: 96px;\n\toverflow: hidden;\n\tpadding: var(--tl-space-3);\n\tpadding-top: 0px;\n}\n\n.tlui-minimap__canvas {\n\tposition: relative;\n\twidth: 100%;\n\theight: 100%;\n}\n\n/* --------------------- Toolbar -------------------- */\n\n/* Wide container */\n.tlui-main-toolbar {\n\tgrid-column: 1 / span 3;\n\tgrid-row: 1;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tflex-grow: 2;\n}\n\n.tlui-main-toolbar--horizontal {\n\tpadding-bottom: calc(var(--tl-space-3) + var(--tl-sab));\n\tmax-width: 100%;\n}\n\n.tlui-main-toolbar--vertical {\n\tposition: absolute;\n\tleft: 0;\n\ttop: 90px; /* height of page menu + 'back to content' button */\n\tbottom: 140px; /* height of expanded mini-map */\n\tpadding-left: calc(var(--tl-space-3) + var(--tl-sab));\n}\n\n[data-breakpoints-below*='6'] .tlui-main-toolbar--vertical {\n\tbottom: 90px; /* collapsed mini-map, but same as top to keep things looking even */\n}\n\n/* Centered Content */\n.tlui-main-toolbar__inner {\n\tposition: relative;\n\twidth: fit-content;\n\tdisplay: flex;\n\tgap: var(--tl-space-3);\n\talign-items: flex-start;\n}\n\n.tlui-main-toolbar--vertical .tlui-main-toolbar__inner {\n\tflex-direction: column;\n}\n\n.tlui-main-toolbar--horizontal .tlui-main-toolbar__left {\n\twidth: fit-content;\n}\n.tlui-main-toolbar--vertical .tlui-main-toolbar__left {\n\tdisplay: flex;\n\theight: fit-content;\n}\n\n/* Row of controls + lock button */\n.tlui-main-toolbar__extras {\n\tposition: relative;\n\tz-index: var(--tl-layer-above);\n\tpointer-events: none;\n\talign-self: stretch;\n}\n\n.tlui-main-toolbar__extras:empty {\n\tdisplay: none;\n}\n\n.tlui-main-toolbar--horizontal .tlui-main-toolbar__extras {\n\theight: 48px;\n\ttop: 6px;\n}\n\n.tlui-main-toolbar--vertical .tlui-main-toolbar__extras {\n\twidth: 48px;\n\torder: 1;\n}\n\n.tlui-main-toolbar__extras__controls {\n\tposition: relative;\n\tz-index: var(--tl-layer-above);\n\tbackground-color: var(--tl-color-low);\n\tborder: 2px solid var(--tl-color-background);\n\tpointer-events: all;\n}\n\n.tlui-main-toolbar--horizontal .tlui-main-toolbar__extras__controls {\n\tborder-top-left-radius: var(--tl-radius-4);\n\tborder-top-right-radius: var(--tl-radius-4);\n\tmargin-left: 8px;\n\tmargin-right: 0px;\n\twidth: fit-content;\n}\n\n.tlui-main-toolbar--vertical .tlui-main-toolbar__extras__controls {\n\tborder-top-right-radius: var(--tl-radius-4);\n\tborder-bottom-right-radius: var(--tl-radius-4);\n\tmargin-top: 8px;\n\tmargin-left: -2px;\n\tmargin-bottom: 0px;\n\twidth: fit-content;\n}\n\n.tlui-main-toolbar__tools {\n\tborder-radius: var(--tl-radius-4);\n\tz-index: var(--tl-layer-panels);\n\tpointer-events: all;\n\tposition: relative;\n\tbackground: var(--tl-color-panel);\n\tbox-shadow: var(--tl-shadow-2);\n\tcontain: layout style paint;\n}\n\n.tlui-main-toolbar--horizontal .tlui-main-toolbar__mobile-style-panel {\n\talign-self: flex-end;\n}\n.tlui-main-toolbar--vertical .tlui-main-toolbar__mobile-style-panel {\n\talign-self: flex-start;\n}\n\n.tlui-main-toolbar--horizontal .tlui-main-toolbar__overflow {\n\twidth: 40px;\n\tmargin-left: 2px;\n}\n.tlui-main-toolbar--vertical .tlui-main-toolbar__overflow {\n\theight: 40px;\n\tmargin-top: 2px;\n}\n\n.tlui-layout__mobile .tlui-main-toolbar--horizontal .tlui-main-toolbar__overflow {\n\twidth: 32px;\n\tpadding: 0px;\n}\n\n.tlui-main-toolbar--horizontal *[data-state='open']::after {\n\tbackground: linear-gradient(0deg, rgba(144, 144, 144, 0) 0%, var(--tl-color-muted-2) 100%);\n\topacity: 1;\n}\n.tlui-main-toolbar--vertical *[data-state='open']::after {\n\tbackground: linear-gradient(90deg, rgba(144, 144, 144, 0) 0%, var(--tl-color-muted-2) 100%);\n\topacity: 1;\n}\n\n.tlui-main-toolbar__overflow-content {\n\ttouch-action: none;\n}\n\n.tlui-main-toolbar__tools [data-toolbar-visible='false'],\n.tlui-main-toolbar__overflow-content [data-toolbar-visible='false'] {\n\tdisplay: none;\n}\n\n.tlui-main-toolbar__group:empty {\n\tdisplay: none;\n}\n.tlui-row.tlui-main-toolbar__group:not(:nth-last-child(-n + 1 of [data-toolbar-visible='true'])) {\n\tborder-right: 1px solid var(--tl-color-divider);\n\tmargin-right: 2px;\n}\n.tlui-column.tlui-main-toolbar__group:not(\n\t\t:nth-last-child(-n + 1 of [data-toolbar-visible='true'])\n\t) {\n\tborder-bottom: 1px solid var(--tl-color-divider);\n\tmargin-bottom: 2px;\n}\n.tlui-grid.tlui-main-toolbar__group {\n\tgrid-column: 1 / span 4;\n}\n.tlui-grid.tlui-main-toolbar__group:not(:nth-last-child(-n + 1 of [data-toolbar-visible='true'])) {\n\tborder-bottom: 1px solid var(--tl-color-divider);\n\tmargin-bottom: 2px;\n}\n\n@media (hover: hover) {\n\t.tlui-main-toolbar--horizontal *[data-state='open']:not(:hover)::after {\n\t\tbackground: linear-gradient(0deg, rgba(144, 144, 144, 0) 0%, var(--tl-color-muted-2) 100%);\n\t\topacity: 1;\n\t}\n\t.tlui-main-toolbar--vertical *[data-state='open']:not(:hover)::after {\n\t\tbackground: linear-gradient(90deg, rgba(144, 144, 144, 0) 0%, var(--tl-color-muted-2) 100%);\n\t\topacity: 1;\n\t}\n}\n\n/* ------------------- Tooltip -------------------- */\n\n.tlui-tooltip {\n\tfont-size: 12px;\n\tpadding: 2px 8px;\n\tborder-radius: 4px;\n\tbackground-color: var(--tl-color-tooltip);\n\tbox-shadow: none;\n\tcolor: var(--tl-color-text-shadow);\n\tmax-width: 400px;\n\twidth: fit-content;\n\ttext-align: center;\n\twill-change: transform, opacity;\n\tz-index: 2;\n}\n\n.tlui-tooltip__arrow {\n\tfill: var(--tl-color-tooltip);\n\twill-change: opacity;\n}\n\n[data-radix-popper-content-wrapper]:has(.tlui-tooltip) {\n\tz-index: var(--tl-layer-toasts) !important;\n\tpointer-events: none;\n}\n\n/* ------------------- Debug panel ------------------ */\n\n.tlui-debug-panel {\n\tbackground-color: var(--tl-color-low);\n\twidth: 100%;\n\tdisplay: grid;\n\talign-items: center;\n\tgrid-template-columns: 1fr auto auto auto;\n\tjustify-content: space-between;\n\tpadding-inline-start: var(--tl-space-4);\n\tborder-top: 1px solid var(--tl-color-background);\n\tfont-size: 12px;\n\tcolor: var(--tl-color-text-1);\n\tz-index: var(--tl-layer-panels);\n\tpointer-events: all;\n}\n\n.tlui-debug-panel__current-state {\n\toverflow: hidden;\n\twhite-space: nowrap;\n\ttext-overflow: ellipsis;\n}\n\n.tlui-debug-panel__fps {\n\tmargin-right: 8px;\n}\n\n.tlui-debug-panel__fps__slow {\n\tfont-weight: bold;\n\tcolor: var(--tl-color-danger);\n}\n\n.tlui-a11y-audit {\n\tborder-collapse: collapse;\n}\n\n.tlui-a11y-audit th,\n.tlui-a11y-audit td {\n\tpadding: 8px;\n\tborder: 1px solid var(--tl-color-low-border);\n}\n\n/* --------------------- Toasts --------------------- */\n\n.tlui-toast__viewport {\n\tposition: absolute;\n\tinset: 0px;\n\tmargin: 0px;\n\tdisplay: flex;\n\talign-items: flex-end;\n\tjustify-content: flex-end;\n\tflex-direction: column;\n\tgap: var(--tl-space-3);\n\tpointer-events: none;\n\tpadding: 0px var(--tl-space-3) 64px 0px;\n\tz-index: var(--tl-layer-toasts);\n}\n\n.tl-container[dir='rtl'] .tlui-toast__viewport {\n\tpadding: 0px 0px 64px var(--tl-space-3);\n}\n\n.tlui-toast__viewport > * {\n\tpointer-events: all;\n}\n\n.tlui-toast__icon {\n\tpadding-top: 11px;\n\tpadding-inline-start: var(--tl-space-4);\n\tcolor: var(--tl-color-text-1);\n}\n\n.tlui-toast__container {\n\tmin-width: 200px;\n\tdisplay: flex;\n\tflex-direction: row;\n\tbackground-color: var(--tl-color-panel);\n\tbox-shadow: var(--tl-shadow-2);\n\tborder-radius: var(--tl-radius-3);\n\tfont-size: 12px;\n}\n\n.tlui-toast__container[data-severity='success'] .tlui-icon {\n\tcolor: var(--tl-color-success);\n}\n\n.tlui-toast__container[data-severity='info'] .tlui-icon {\n\tcolor: var(--tl-color-info);\n}\n\n.tlui-toast__container[data-severity='warning'] .tlui-icon {\n\tcolor: var(--tl-color-warning);\n}\n\n.tlui-toast__container[data-severity='error'] .tlui-icon {\n\tcolor: var(--tl-color-danger);\n}\n\n.tlui-toast__main {\n\tflex-grow: 2;\n\tmax-width: 280px;\n\tmin-width: 0;\n}\n\n.tlui-toast__content {\n\tpadding: var(--tl-space-4);\n\tdisplay: flex;\n\tline-height: 1.4;\n\tflex-direction: column;\n\tgap: var(--tl-space-3);\n\tmin-width: 0;\n}\n\n.tlui-toast__main[data-actions='true'] .tlui-toast__content {\n\tpadding-bottom: var(--tl-space-2);\n}\n\n.tlui-toast__title {\n\tfont-weight: bold;\n\tcolor: var(--tl-color-text-1);\n\t/* this makes the default toast look better */\n\tline-height: 16px;\n\toverflow-wrap: anywhere;\n}\n\n.tlui-toast__description {\n\tcolor: var(--tl-color-text-1);\n\tpadding: var(--tl-space-3);\n\tmargin: 0px;\n\tpadding: 0px;\n\toverflow-wrap: anywhere;\n}\n\n.tlui-toast__icon + .tlui-toast__main > .tlui-toast__actions {\n\tpadding-left: 0px;\n}\n\n.tlui-toast__actions {\n\tdisplay: flex;\n\tflex-direction: row;\n\tjustify-content: flex-start;\n\tmargin-left: 0;\n}\n\n.tlui-toast__close {\n\talign-self: flex-end;\n\tflex-shrink: 0;\n}\n\n@media (prefers-reduced-motion: no-preference) {\n\t.tlui-toast__container[data-state='open'] {\n\t\tanimation: tlui-slide-in 200ms cubic-bezier(0.785, 0.135, 0.15, 0.86);\n\t}\n\n\t.tlui-toast__container[data-state='closed'] {\n\t\tanimation: tlui-fade-out 100ms ease-in;\n\t}\n\n\t.tlui-toast__container[data-swipe='move'] {\n\t\ttransform: translateX(var(--radix-toast-swipe-move-x));\n\t}\n\n\t.tlui-toast__container[data-swipe='cancel'] {\n\t\ttransform: translateX(0);\n\t\ttransition: transform 200ms ease-out;\n\t}\n\n\t.tlui-toast__container[data-swipe='end'] {\n\t\tanimation: tlui-slide-out 100ms ease-out;\n\t}\n\n\t.tl-container[dir='rtl'] .tlui-toast__container[data-state='open'] {\n\t\tanimation: tlui-slide-in-rtl 200ms cubic-bezier(0.785, 0.135, 0.15, 0.86);\n\t}\n\n\t.tl-container[dir='rtl'] .tlui-toast__container[data-state='closed'] {\n\t\tanimation: tlui-fade-out 100ms ease-in;\n\t}\n\n\t.tl-container[dir='rtl'] .tlui-toast__container[data-swipe='end'] {\n\t\tanimation: tlui-slide-out-rtl 100ms ease-out;\n\t}\n}\n\n/* ---------------- Dialog ---------------- */\n\n.tlui-dialog__overlay {\n\tposition: absolute;\n\ttop: 0px;\n\tleft: 0px;\n\twidth: 100%;\n\theight: 100%;\n\tz-index: var(--tl-layer-canvas-overlays);\n\tbackground-color: var(--tl-color-overlay);\n\tanimation: tl-fade-in 0.12s ease-out;\n}\n\n.tlui-dialog__positioner {\n\tposition: absolute;\n\ttop: 0px;\n\tleft: 0px;\n\twidth: 100%;\n\theight: 100%;\n\tz-index: var(--tl-layer-canvas-overlays);\n\tpointer-events: all;\n\tdisplay: grid;\n\tplace-items: center;\n\toverflow-y: auto;\n\tpadding: 0px var(--tl-space-3);\n}\n\n.tlui-dialog__content {\n\tdisplay: flex;\n\tflex-direction: column;\n\tposition: relative;\n\tcursor: default;\n\tbackground-color: var(--tl-color-panel);\n\tbox-shadow: var(--tl-shadow-3);\n\tborder-radius: var(--tl-radius-3);\n\tfont-size: 12px;\n\toverflow: hidden;\n\tmin-width: 300px;\n\tmax-width: 100%;\n\tmax-height: 80%;\n}\n\n.tlui-dialog__header {\n\tposition: relative;\n\tdisplay: flex;\n\talign-items: center;\n\tflex: 0;\n\tz-index: var(--tl-layer-header-footer);\n\tpadding-inline-start: var(--tl-space-4);\n\tcolor: var(--tl-color-text);\n\theight: 40px;\n}\n\n.tlui-dialog__header__title {\n\tflex: 1;\n\tfont-weight: inherit;\n\tfont-size: 12px;\n\tmargin: 0px;\n\tcolor: var(--tl-color-text-1);\n}\n\n.tlui-dialog__header__close {\n\tjustify-self: flex-end;\n}\n\n.tlui-dialog__body {\n\tpadding: var(--tl-space-4) var(--tl-space-4);\n\tflex: 0 1;\n\toverflow-y: auto;\n\toverflow-x: hidden;\n\tcolor: var(--tl-color-text-1);\n\tuser-select: all;\n\t-webkit-user-select: text;\n\toverflow-wrap: anywhere;\n}\n.tlui-dialog__body a {\n\tcolor: var(--tl-color-selected);\n}\n\n.tlui-dialog__body ul,\n.tlui-dialog__body ol {\n\tpadding-left: 16px;\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: var(--tl-space-2);\n}\n\n.tlui-dialog__footer {\n\tposition: relative;\n\tmin-height: 12px;\n\tz-index: var(--tl-layer-header-footer);\n}\n\n.tlui-dialog__footer__actions {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: flex-end;\n}\n\n.tlui-dialog__footer__actions > .tlui-button:nth-last-child(n + 2) {\n\tmargin-right: -4px;\n}\n\n/* --------------------- Dialogs -------------------- */\n\n/* Edit Link Dialog */\n\n.tlui-edit-link-dialog {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: var(--tl-space-4);\n\tcolor: var(--tl-color-text);\n}\n\n.tlui-edit-link-dialog__input {\n\tbackground-color: var(--tl-color-muted-2);\n\tflex-grow: 2;\n\tborder-radius: var(--tl-radius-2);\n\tpadding: 0px var(--tl-space-4);\n}\n\n/* Embed Dialog */\n\n.tlui-embed__spacer {\n\tflex-grow: 2;\n\tmin-height: 0px;\n\tmargin-left: calc(-1 * var(--tl-space-4));\n\tmargin-top: calc(-1 * var(--tl-space-4));\n\tpointer-events: none;\n}\n\n.tlui-embed-dialog__list {\n\tdisplay: flex;\n\tflex-direction: column;\n\tpadding: 0px var(--tl-space-3) var(--tl-space-4) var(--tl-space-3);\n}\n\n.tlui-embed-dialog__item__image {\n\twidth: 24px;\n\theight: 24px;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tbackground-size: contain;\n\tbackground-repeat: no-repeat;\n\tbackground-position: center center;\n\tbackground-color: var(--tl-color-selected-contrast);\n\tborder-radius: var(--tl-radius-1);\n}\n\n.tlui-embed-dialog__enter {\n\tdisplay: flex;\n\tflex-direction: column;\n\tgap: var(--tl-space-4);\n\tcolor: var(--tl-color-text-1);\n}\n\n.tlui-embed-dialog__input {\n\tbackground-color: var(--tl-color-muted-2);\n\tflex-grow: 2;\n\tborder-radius: var(--tl-radius-2);\n\tpadding: 0px var(--tl-space-4);\n}\n\n.tlui-embed-dialog__warning {\n\tcolor: var(--tl-color-danger);\n\ttext-shadow: none;\n}\n\n.tlui-embed-dialog__instruction__link {\n\tdisplay: flex;\n\tgap: var(--tl-space-1);\n\tmargin-top: var(--tl-space-4);\n}\n\n.tlui-embed-dialog__enter a {\n\tcolor: var(--tl-color-text-1);\n}\n\n/* --------------- Keyboard shortcuts --------------- */\n\n.tlui-shortcuts-dialog__header {\n\tborder-bottom: 1px solid var(--tl-color-divider);\n}\n\n.tlui-shortcuts-dialog__body {\n\tposition: relative;\n\tcolumns: 3;\n\tcolumn-gap: var(--tl-space-9);\n\tpointer-events: all;\n\ttouch-action: auto;\n\n\t/* Terrible fix to allow firefox users to scroll the dialog */\n\toverflow-x: auto;\n\n\tscrollbar-width: none;\n}\n\n.tlui-shortcuts-dialog__body::-webkit-scrollbar {\n\tdisplay: none;\n}\n\n.tlui-shortcuts-dialog__body__tablet {\n\tcolumns: 2;\n}\n\n.tlui-shortcuts-dialog__body__mobile {\n\tcolumns: 1;\n}\n\n.tlui-shortcuts-dialog__group {\n\tbreak-inside: avoid-column;\n\tpadding-bottom: var(--tl-space-6);\n}\n\n.tlui-shortcuts-dialog__group__title {\n\tfont-size: inherit;\n\tfont-weight: inherit;\n\tmargin: 0px;\n\tcolor: var(--tl-color-text-3);\n\theight: 32px;\n\tdisplay: flex;\n\talign-items: center;\n}\n\n.tlui-shortcuts-dialog__group__content {\n\tdisplay: flex;\n\tflex-direction: column;\n\tcolor: var(--tl-color-text-1);\n}\n\n.tlui-shortcuts-dialog__key-pair {\n\tdisplay: flex;\n\tgap: var(--tl-space-4);\n\talign-items: center;\n\tjustify-content: space-between;\n\theight: 32px;\n}\n\n.tlui-shortcuts-dialog__key-pair__key {\n\tflex: 1;\n\tfont-size: 12px;\n}\n\n/* ------------------ Language menu ----------------- */\n\n.tlui-language-menu {\n\tmax-height: 500px;\n}\n\n.tlui-language-menu::after {\n\tcontent: '';\n\tdisplay: block;\n\tposition: absolute;\n\tbottom: 0px;\n\tleft: 0px;\n\tright: 0px;\n\theight: 24px;\n\tbackground: linear-gradient(\n\t\tto bottom,\n\t\tvar(--tl-color-panel-transparent) 0%,\n\t\tvar(--tl-color-panel) 90%,\n\t\tvar(--tl-color-panel) 100%\n\t);\n\tborder-bottom-left-radius: var(--tl-radius-3);\n\tborder-bottom-right-radius: var(--tl-radius-3);\n\tpointer-events: none;\n}\n\n/* ------------------ Actions menu ------------------ */\n\n.tlui-actions-menu {\n\tmax-height: calc(100vh - 150px);\n}\n\n/* -------------------- Help menu ------------------- */\n\n.tlui-help-menu {\n\tpointer-events: all;\n\tposition: absolute;\n\tbottom: var(--tl-space-2);\n\tright: var(--tl-space-2);\n\tz-index: var(--tl-layer-panels);\n\tborder: 2px solid var(--tl-color-background);\n\tborder-radius: 100%;\n}\n\n/* ------------------- Da share zone ------------------ */\n\n.tlui-share-zone {\n\tpadding: 0px 0px 0px 0px;\n\tdisplay: flex;\n\tflex-direction: row;\n\tjustify-content: flex-end;\n\tz-index: var(--tl-layer-panels);\n\talign-items: center;\n\tpadding-top: 2px;\n\tpadding-right: 4px;\n}\n\n/* ------------------- People Menu ------------------- */\n\n.tlui-people-menu__avatars-button {\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: flex-end;\n\tbackground: none;\n\tborder: none;\n\tcursor: pointer;\n\tpointer-events: all;\n\tborder-radius: var(--tl-radius-1);\n\tpadding-right: 1px;\n\theight: 100%;\n}\n\n.tlui-people-menu__avatars {\n\tdisplay: flex;\n\tflex-direction: row;\n}\n\n.tlui-people-menu__avatar {\n\theight: 24px;\n\twidth: 24px;\n\tborder: 2px solid var(--tl-color-background);\n\tbackground-color: var(--tl-color-low);\n\tborder-radius: 100%;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tposition: relative;\n\tfont-size: 10px;\n\tfont-weight: bold;\n\ttext-align: center;\n\tcolor: var(--tl-color-selected-contrast);\n\tz-index: 2;\n}\n\n.tlui-people-menu__avatar:nth-of-type(n + 2) {\n\tmargin-inline-start: -12px;\n}\n\n.tlui-people-menu__avatars-button[data-state='open'] {\n\topacity: 1;\n}\n\n@media (hover: hover) {\n\t.tlui-people-menu__avatars-button:hover .tlui-people-menu__avatar {\n\t\tborder-color: var(--tl-color-low);\n\t}\n}\n\n.tlui-people-menu__more {\n\tmin-width: 0px;\n\tfont-size: 11px;\n\tfont-weight: 600;\n\tcolor: var(--tl-color-text-1);\n\tfont-family: inherit;\n\tpadding: 0px 4px;\n}\n.tlui-people-menu__more::after {\n\tborder-radius: var(--tl-radius-2);\n\tinset: 0px;\n}\n\n.tlui-people-menu__wrapper {\n\tposition: relative;\n\tdisplay: flex;\n\tflex-direction: column;\n\twidth: 220px;\n\theight: fit-content;\n\tmax-height: 50vh;\n}\n\n.tlui-people-menu__section {\n\tposition: relative;\n\ttouch-action: auto;\n\tflex-direction: column;\n\tmax-height: 100%;\n\toverflow-x: hidden;\n\toverflow-y: auto;\n\ttouch-action: auto;\n}\n\n.tlui-people-menu__section:first-child,\n.tlui-people-menu__section:last-child {\n\tflex-shrink: 0;\n}\n\n.tlui-people-menu__section:not(:last-child) {\n\tborder-bottom: 1px solid var(--tl-color-divider);\n}\n\n.tlui-people-menu__user {\n\tdisplay: flex;\n\tjustify-content: flex-start;\n\talign-items: center;\n}\n\n.tlui-people-menu__user__color,\n.tlui-people-menu__user__edit {\n\tflex-shrink: 0;\n}\n\n.tlui-people-menu__user__name {\n\ttext-align: start;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tfont-size: 12px;\n\tcolor: var(--tl-color-text-1);\n\tmax-width: 100%;\n\tflex-grow: 1;\n\tflex-shrink: 100;\n}\n\n.tlui-people-menu__user__label {\n\ttext-align: start;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n\tfont-size: 12px;\n\tcolor: var(--tl-color-text-3);\n\tflex-grow: 100;\n\tflex-shrink: 0;\n\tmargin-inline-start: 4px;\n}\n\n.tlui-people-menu__user__input {\n\tflex-grow: 2;\n\theight: 100%;\n\tpadding: 0px;\n\tmargin: 0px;\n}\n\n.tlui-people-menu__user > .tlui-input__wrapper {\n\twidth: auto;\n\tdisplay: flex;\n\talign-items: auto;\n\tflex-grow: 2;\n\tgap: 8px;\n\theight: 100%;\n\tpadding: 0px;\n}\n\n.tlui-people-menu__item {\n\tdisplay: flex;\n\talign-items: center;\n}\n\n.tlui-people-menu__item:last-of-type .tlui-button__menu {\n\tmargin-bottom: 0;\n}\n\n.tlui-people-menu__item__button {\n\tpadding-inline-start: 12px;\n\tpadding-inline-end: 0px;\n\toverflow: hidden;\n}\n\n.tlui-people-menu__item > .tlui-button__menu {\n\twidth: auto;\n\tdisplay: flex;\n\talign-items: auto;\n\tjustify-content: flex-start;\n\tflex-grow: 2;\n\tgap: 12px;\n}\n\n.tlui-people-menu__name {\n\ttext-align: start;\n\twhite-space: nowrap;\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n}\n\n.tlui-people-menu__item__follow {\n\tflex-shrink: 0;\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\topacity: 1;\n}\n\n@media (hover: hover) {\n\t.tlui-people-menu__item__follow {\n\t\topacity: 0;\n\t}\n\n\t.tlui-people-menu__item:hover .tlui-people-menu__item__follow {\n\t\topacity: 1;\n\t}\n}\n\n/* --------------- Following indicator -------------- */\n\n.tlui-following-indicator {\n\tdisplay: block;\n\tposition: absolute;\n\tinset: 0px;\n\tborder-width: 2px;\n\tborder-style: solid;\n\tpointer-events: none;\n}\n\n/* --------------- Contextual toolbar --------------- */\n\n.tlui-contextual-toolbar {\n\tposition: 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\tpointer-events: all;\n}\n\n.tlui-contextual-toolbar [data-isactive='true']::after {\n\tbackground-color: var(--tl-color-muted-2);\n\topacity: 1;\n}\n\n.tlui-contextual-toolbar {\n\topacity: 0;\n\ttransition: opacity 0.08s ease-in-out;\n}\n\n.tlui-contextual-toolbar,\n.tlui-contextual-toolbar * {\n\tpointer-events: none;\n}\n\n.tlui-contextual-toolbar[data-visible='true'] {\n\topacity: 1;\n\tz-index: var(--tl-layer-menus);\n}\n\n.tlui-contextual-toolbar[data-interactive='true'],\n.tlui-contextual-toolbar[data-interactive='true'] * {\n\tpointer-events: all;\n}\n\n.tlui-rich-text__toolbar-link-input {\n\tmargin-left: 12px;\n\t/*\n\t * Nice touch tweak: keep the link editor toolbar the same as the default toolbar.\n\t * This is so the toolbar size stays stable going in and out of the link editor.\n\t */\n\twidth: 148px;\n}\n\n.tlui-media__toolbar-alt-text-input {\n\tmargin-left: 12px;\n\t/*\n\t * Nice touch tweak: keep the link editor toolbar the same as the default toolbar.\n\t * This is so the toolbar size stays stable going in and out of the alt text editor.\n\t */\n\tmin-width: 200px;\n}\n\n.tlui-contextual-toolbar .tlui-input__wrapper {\n\theight: 40px;\n}\n\n.tlui-image__toolbar .tlui-slider__container {\n\twidth: 125px;\n}\n\n.tlui-image__toolbar .tlui-slider {\n\theight: 100%;\n}\n\n.tlui-image__toolbar .tlui-slider__track {\n\theight: 32px;\n}\n\n.tlui-image__toolbar .tlui-slider__thumb {\n\twidth: 14px;\n\theight: 14px;\n}\n\n/* ------------------- Animations ------------------- */\n@keyframes tlui-fade-out {\n\t0% {\n\t\topacity: 1;\n\t}\n\t100% {\n\t\topacity: 0;\n\t}\n}\n\n@keyframes tlui-slide-in {\n\tfrom {\n\t\ttransform: translateX(calc(100% + var(--tl-space-3)));\n\t}\n\tto {\n\t\ttransform: translateX(0px);\n\t}\n}\n\n@keyframes tlui-slide-out {\n\tfrom {\n\t\ttransform: translateX(var(--radix-toast-swipe-end-x));\n\t}\n\tto {\n\t\ttransform: translateX(calc(100% + var(--tl-space-3)));\n\t}\n}\n\n@keyframes tlui-slide-in-rtl {\n\tfrom {\n\t\ttransform: translateX(calc(-100% - var(--tl-space-3)));\n\t}\n\tto {\n\t\ttransform: translateX(0px);\n\t}\n}\n\n@keyframes tlui-slide-out-rtl {\n\tfrom {\n\t\ttransform: translateX(var(--radix-toast-swipe-end-x));\n\t}\n\tto {\n\t\ttransform: translateX(calc(-100% - var(--tl-space-3)));\n\t}\n}\n\n\n\n/* Workspace wrapper: fills the tab body like .dcv-root does for React Flow. */\n.dtv-root{position:relative;width:100%;height:100%;min-height:420px;overflow:hidden}\n.dtv-root .tl-container{--tl-font-sans:inherit}\n";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function TldrawWorkspace(): JSX.Element;
|
package/lib/types/config.d.ts
CHANGED
|
@@ -19,6 +19,19 @@ export interface Config {
|
|
|
19
19
|
/** OpenAI-compatible relay settings; independent from the official OpenAI row. */
|
|
20
20
|
openaiCompatBaseURL?: string;
|
|
21
21
|
openaiCompatModel?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Request shape the relay's images/edits endpoint expects (#41). Most
|
|
24
|
+
* relays take OpenAI's multipart form; some (e.g. SenseNova) accept the
|
|
25
|
+
* generations endpoint but run edits on their own JSON contract with
|
|
26
|
+
* `images: [{ image_url }]` objects. Defaults to `multipart`.
|
|
27
|
+
*/
|
|
28
|
+
openaiCompatEditFormat?: 'multipart' | 'jsonImageUrlArray';
|
|
29
|
+
/**
|
|
30
|
+
* Extra JSON fields merged into the JSON edit body last (can override the
|
|
31
|
+
* built-in defaults), e.g. SenseNova's `watermark`/`prompt_extend`.
|
|
32
|
+
* Ignored unless `openaiCompatEditFormat` is `jsonImageUrlArray`.
|
|
33
|
+
*/
|
|
34
|
+
openaiCompatEditExtra?: Record<string, unknown>;
|
|
22
35
|
seedreamBaseURL?: string;
|
|
23
36
|
seedreamModel?: string;
|
|
24
37
|
dashscopeEndpoint?: string;
|
|
@@ -64,6 +77,8 @@ export declare function resolveProvider(config: Config): {
|
|
|
64
77
|
model: string;
|
|
65
78
|
baseURL: string;
|
|
66
79
|
imageSize: string;
|
|
80
|
+
editFormat: 'multipart' | 'jsonImageUrlArray';
|
|
81
|
+
editExtra: Record<string, unknown>;
|
|
67
82
|
} | {
|
|
68
83
|
provider: 'seedream';
|
|
69
84
|
apiKeyEnv: string;
|
|
@@ -94,6 +109,15 @@ export declare function resolveProvider(config: Config): {
|
|
|
94
109
|
workflows: ComfyUIWorkflowEntry[];
|
|
95
110
|
workflow?: ComfyUIWorkflowEntry;
|
|
96
111
|
timeoutMs: number;
|
|
112
|
+
} | {
|
|
113
|
+
provider: 'chatgpt-sub';
|
|
114
|
+
model: string;
|
|
115
|
+
} | {
|
|
116
|
+
provider: 'grok-sub';
|
|
117
|
+
model: string;
|
|
118
|
+
} | {
|
|
119
|
+
provider: 'google-sub';
|
|
120
|
+
model: string;
|
|
97
121
|
};
|
|
98
122
|
/**
|
|
99
123
|
* Apply a per-call provider and/or model override on top of the saved config.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare const DEFAULT_MAX_CACHE_BYTES: number;
|
|
2
|
+
export declare function setInspirationCacheDir(dir: string): void;
|
|
3
|
+
export declare function getInspirationCacheDir(): string;
|
|
4
|
+
export declare function cacheKey(sourceId: string, caseId: string): string;
|
|
5
|
+
export interface CachedImageMetadata {
|
|
6
|
+
contentType: string;
|
|
7
|
+
sourceId: string;
|
|
8
|
+
caseId: string;
|
|
9
|
+
savedAt: number;
|
|
10
|
+
}
|
|
11
|
+
export declare function getCachedImagePath(key: string): string | null;
|
|
12
|
+
export declare function readCachedImage(key: string): {
|
|
13
|
+
data: Buffer;
|
|
14
|
+
contentType: string;
|
|
15
|
+
} | null;
|
|
16
|
+
export declare function writeCachedImage(key: string, data: Buffer, contentType: string, sourceId: string, caseId: string, maxBytes?: number): void;
|
|
17
|
+
/**
|
|
18
|
+
* Enforce max cache size constraint by evicting oldest accessed files.
|
|
19
|
+
*/
|
|
20
|
+
export declare function evictCacheIfNeeded(maxBytes?: number): void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { PromptCase, PromptSource } from './types.js';
|
|
2
|
+
export declare const MAX_IMAGE_BYTES: number;
|
|
3
|
+
export declare const FETCH_TIMEOUT_MS = 6000;
|
|
4
|
+
/**
|
|
5
|
+
* Validate that an IP or hostname is not private/loopback (SSRF protection).
|
|
6
|
+
*/
|
|
7
|
+
export declare function isPrivateOrLocalAddress(hostname: string): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Check binary magic bytes to verify genuine image type.
|
|
10
|
+
* Rejects SVGs, HTML, scripts, executables.
|
|
11
|
+
*/
|
|
12
|
+
export declare function sniffImageMimeType(buf: Buffer): 'image/jpeg' | 'image/png' | 'image/webp' | null;
|
|
13
|
+
/**
|
|
14
|
+
* Safely fetch a single URL with strict checks.
|
|
15
|
+
*/
|
|
16
|
+
export declare function fetchSingleImageUrl(urlStr: string, allowedDomains: string[]): Promise<{
|
|
17
|
+
data: Buffer;
|
|
18
|
+
contentType: string;
|
|
19
|
+
} | null>;
|
|
20
|
+
/**
|
|
21
|
+
* Multi-tier racing / fallback fetcher:
|
|
22
|
+
* Tries candidate URLs in order until a valid image is retrieved.
|
|
23
|
+
*/
|
|
24
|
+
export declare function fetchInspirationImage(source: PromptSource, promptCase: PromptCase): Promise<{
|
|
25
|
+
data: Buffer;
|
|
26
|
+
contentType: string;
|
|
27
|
+
} | null>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side route handlers for the Inspiration Library.
|
|
3
|
+
*/
|
|
4
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
5
|
+
export declare function serveInspirationCases(_req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
6
|
+
/**
|
|
7
|
+
* Handle GET /plugins/dsh-image-gen/inspiration/image/:sourceId/:caseId
|
|
8
|
+
*/
|
|
9
|
+
export declare function serveInspirationImage(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Handle POST /plugins/dsh-image-gen/inspiration/update
|
|
12
|
+
*/
|
|
13
|
+
export declare function serveInspirationUpdate(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { PromptCase, PromptSource } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Find the path to the inspiration snapshots folder.
|
|
4
|
+
* Supports both development (src/) and built distribution (root / lib).
|
|
5
|
+
*/
|
|
6
|
+
export declare function getSnapshotsDir(): string;
|
|
7
|
+
/**
|
|
8
|
+
* Load built-in prompt sources from packaged JSON snapshots.
|
|
9
|
+
*/
|
|
10
|
+
export declare function loadBuiltinSources(): PromptSource[];
|
|
11
|
+
/**
|
|
12
|
+
* Clear cached sources to allow reloading upon update.
|
|
13
|
+
*/
|
|
14
|
+
export declare function invalidateSourcesCache(): void;
|
|
15
|
+
/**
|
|
16
|
+
* Get a specific prompt source by ID.
|
|
17
|
+
*/
|
|
18
|
+
export declare function getPromptSource(sourceId: string): PromptSource | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Find a specific case within a source.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getPromptCase(sourceId: string, caseId: string): {
|
|
23
|
+
source: PromptSource;
|
|
24
|
+
promptCase: PromptCase;
|
|
25
|
+
} | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Resolve candidate URLs for downloading an inspiration case's image.
|
|
28
|
+
* Uses a prioritized multi-tier fallback:
|
|
29
|
+
* 1. Fast, reliable global CDN (fastly.jsdelivr.net) - free, doesn't load author's server
|
|
30
|
+
* 2. Canghe.ai official website static origin (fast in mainland China)
|
|
31
|
+
* 3. GitHub raw official fallback
|
|
32
|
+
*/
|
|
33
|
+
export declare function resolveCaseImageCandidateUrls(source: PromptSource, promptCase: PromptCase): string[];
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions and contracts for the Prompt Library / Inspiration system.
|
|
3
|
+
*/
|
|
4
|
+
export interface PromptCase {
|
|
5
|
+
/** Unique case ID within the source (e.g. "544" or "case-544") */
|
|
6
|
+
id: string;
|
|
7
|
+
/** Human-readable title */
|
|
8
|
+
title: string;
|
|
9
|
+
/** The full prompt text to be reused */
|
|
10
|
+
prompt: string;
|
|
11
|
+
/** Image identifier or path (e.g. "/images/case544.jpg" or "case544.jpg") */
|
|
12
|
+
image?: string | undefined;
|
|
13
|
+
/** Primary category (e.g. "UI & Interfaces", "Products & E-commerce") */
|
|
14
|
+
category?: string | undefined;
|
|
15
|
+
/** Visual styles (e.g. ["UI", "Poster", "Realistic"]) */
|
|
16
|
+
styles?: string[] | undefined;
|
|
17
|
+
/** Application scenarios (e.g. ["Tech", "Commerce"]) */
|
|
18
|
+
scenes?: string[] | undefined;
|
|
19
|
+
/** Original creator or source platform display name */
|
|
20
|
+
sourceLabel?: string | undefined;
|
|
21
|
+
/** Link to original social post or source article */
|
|
22
|
+
sourceUrl?: string | undefined;
|
|
23
|
+
/** Original author name if known */
|
|
24
|
+
author?: string | undefined;
|
|
25
|
+
/** Author homepage or profile */
|
|
26
|
+
authorUrl?: string | undefined;
|
|
27
|
+
/** License name if explicitly declared */
|
|
28
|
+
license?: string | undefined;
|
|
29
|
+
/** License URL if explicitly declared */
|
|
30
|
+
licenseUrl?: string | undefined;
|
|
31
|
+
/** Recommended model hint if available */
|
|
32
|
+
modelHint?: string | undefined;
|
|
33
|
+
/** Whether the case is marked as featured / staff pick */
|
|
34
|
+
featured?: boolean | undefined;
|
|
35
|
+
/** Direct link back to upstream repository documentation */
|
|
36
|
+
githubUrl?: string | undefined;
|
|
37
|
+
}
|
|
38
|
+
export interface PromptSource {
|
|
39
|
+
id: string;
|
|
40
|
+
name: string;
|
|
41
|
+
homepage?: string | undefined;
|
|
42
|
+
version: string;
|
|
43
|
+
schemaVersion: number;
|
|
44
|
+
/** Domain whitelist allowed for fetching images for this source */
|
|
45
|
+
allowedDomains: string[];
|
|
46
|
+
/** Array of prompt cases */
|
|
47
|
+
cases: PromptCase[];
|
|
48
|
+
/** Available categories extracted from cases */
|
|
49
|
+
categories?: string[] | undefined;
|
|
50
|
+
/** Available styles extracted from cases */
|
|
51
|
+
styles?: string[] | undefined;
|
|
52
|
+
/** Available scenes extracted from cases */
|
|
53
|
+
scenes?: string[] | undefined;
|
|
54
|
+
}
|
|
55
|
+
export interface InspirationManifest {
|
|
56
|
+
schemaVersion: number;
|
|
57
|
+
sourceVersion: string;
|
|
58
|
+
updatedAt: string;
|
|
59
|
+
sha256: string;
|
|
60
|
+
totalCases: number;
|
|
61
|
+
}
|
|
62
|
+
export interface InspirationCasesResponse {
|
|
63
|
+
sources: PromptSource[];
|
|
64
|
+
activeSourceId: string;
|
|
65
|
+
}
|
|
@@ -18,6 +18,8 @@ export declare function generateOpenAICompatibleImage(input: {
|
|
|
18
18
|
maxBytes: number;
|
|
19
19
|
signal: AbortSignal;
|
|
20
20
|
}): Promise<GeneratedCompatibleImage>;
|
|
21
|
+
/** How the edits endpoint expects its request body (#41). */
|
|
22
|
+
export type CompatEditFormat = 'multipart' | 'jsonImageUrlArray';
|
|
21
23
|
export declare function editOpenAICompatibleImage(input: {
|
|
22
24
|
apiKey: string;
|
|
23
25
|
baseURL: string;
|
|
@@ -27,4 +29,17 @@ export declare function editOpenAICompatibleImage(input: {
|
|
|
27
29
|
size?: string;
|
|
28
30
|
maxBytes: number;
|
|
29
31
|
signal: AbortSignal;
|
|
32
|
+
/**
|
|
33
|
+
* Request shape for the edits call. Most OpenAI-compatible channels take
|
|
34
|
+
* the standard multipart form; some (e.g. SenseNova) accept OpenAI's
|
|
35
|
+
* generations endpoint but run edits on their own JSON contract with
|
|
36
|
+
* `images: [{ image_url }]` objects. Defaults to the standard multipart.
|
|
37
|
+
*/
|
|
38
|
+
editFormat?: CompatEditFormat;
|
|
39
|
+
/**
|
|
40
|
+
* Channel-specific extra fields merged into the JSON edit body last (so
|
|
41
|
+
* they can override the defaults above), e.g. SenseNova's
|
|
42
|
+
* `watermark`/`prompt_extend`. Ignored in multipart mode.
|
|
43
|
+
*/
|
|
44
|
+
editExtra?: Readonly<Record<string, unknown>>;
|
|
30
45
|
}): Promise<GeneratedCompatibleImage>;
|
package/lib/types/shared.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export declare const SAVE_WORKSPACE_ROUTE = "/plugins/dsh-image-gen/save-workspa
|
|
|
14
14
|
export declare const TEST_CONNECTION_ROUTE = "/plugins/dsh-image-gen/test";
|
|
15
15
|
/** Browser route the workbench infinite canvas pushes live state through. */
|
|
16
16
|
export declare const CANVAS_STATE_ROUTE = "/plugins/dsh-image-gen/canvas-state";
|
|
17
|
+
export declare const SUBSCRIPTION_LOGIN_ROUTE = "/plugins/dsh-image-gen/subscription-login";
|
|
18
|
+
export declare const SUBSCRIPTION_STATUS_ROUTE = "/plugins/dsh-image-gen/subscription-status";
|
|
17
19
|
/** Coarse model-facing kind of one shape on the workbench infinite canvas. */
|
|
18
20
|
export type CanvasNodeKind = 'image' | 'draw' | 'text' | 'note' | 'geo' | 'arrow' | 'frame' | 'other';
|
|
19
21
|
/** Every accepted canvas node kind, for validating untrusted pushes. */
|
|
@@ -101,8 +103,17 @@ export interface CanvasStatePush {
|
|
|
101
103
|
/** Namespace persisted through DSH Settings. */
|
|
102
104
|
export declare const IMAGE_GENERATION_NAMESPACE = "image-generation";
|
|
103
105
|
/** Supported providers. */
|
|
104
|
-
export declare const IMAGE_PROVIDERS: readonly ["google", "openai", "openai-compat", "seedream", "dashscope", "xai", "zhipu", "comfyui"];
|
|
106
|
+
export declare const IMAGE_PROVIDERS: readonly ["google", "openai", "openai-compat", "seedream", "dashscope", "xai", "zhipu", "comfyui", "chatgpt-sub", "grok-sub", "google-sub"];
|
|
105
107
|
export type ImageProvider = typeof IMAGE_PROVIDERS[number];
|
|
108
|
+
/**
|
|
109
|
+
* Providers that generate through a logged-in subscription account instead
|
|
110
|
+
* of an API key. They use no credential reference and never join the
|
|
111
|
+
* cloud/BYOK sets.
|
|
112
|
+
*/
|
|
113
|
+
export declare const SUBSCRIPTION_PROVIDERS: readonly ["chatgpt-sub", "grok-sub", "google-sub"];
|
|
114
|
+
export type SubscriptionProvider = typeof SUBSCRIPTION_PROVIDERS[number];
|
|
115
|
+
/** True when the provider generates through a logged-in subscription account. */
|
|
116
|
+
export declare function isSubscriptionProvider(provider: ImageProvider): provider is SubscriptionProvider;
|
|
106
117
|
/** Providers supported by the first browser workbench release. */
|
|
107
118
|
export declare const CLOUD_IMAGE_PROVIDERS: readonly ["google", "openai", "openai-compat", "seedream", "dashscope", "xai", "zhipu"];
|
|
108
119
|
export type CloudImageProvider = typeof CLOUD_IMAGE_PROVIDERS[number];
|
|
@@ -135,6 +146,8 @@ export declare const CLOUD_CREDENTIAL_REFS: Record<CloudImageProvider, string>;
|
|
|
135
146
|
export declare function cloudCredentialRef(provider: ImageProvider): string | undefined;
|
|
136
147
|
/** Locale-neutral provider names for user-facing errors and status lines. */
|
|
137
148
|
export declare const PROVIDER_DISPLAY_NAMES: Record<ImageProvider, string>;
|
|
149
|
+
/** Display names for the subscription providers, separate from the BYOK table. */
|
|
150
|
+
export declare const SUBSCRIPTION_PROVIDER_DISPLAY_NAMES: Record<SubscriptionProvider, string>;
|
|
138
151
|
/** One selectable output option exposed by a provider profile. */
|
|
139
152
|
export interface StudioOption {
|
|
140
153
|
value: string;
|
|
@@ -260,5 +273,7 @@ export declare function uniqueComfyUIWorkflowName(name: string, existing: readon
|
|
|
260
273
|
* separator, and reduced to the non-empty side when the other is blank.
|
|
261
274
|
*/
|
|
262
275
|
export declare function mergeComfyUIPrompt(preset: string | undefined, user: string): string;
|
|
276
|
+
/** Default models for the subscription channels; fixed by the bridge protocol. */
|
|
277
|
+
export declare const DEFAULT_SUBSCRIPTION_MODELS: Record<SubscriptionProvider, string>;
|
|
263
278
|
export declare const DEFAULT_MODELS: Record<ImageProvider, string>;
|
|
264
279
|
export declare const DEFAULT_BASE_URLS: Record<ImageProvider, string>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth token blob persistence for subscription accounts.
|
|
3
|
+
*
|
|
4
|
+
* Adapted from @goodandready/dsh-subscriptions (MIT, (c) 2026 GooDAnDReaDY)
|
|
5
|
+
* lib/blob.js — trimmed to the two image vendors this plugin ships. The blob
|
|
6
|
+
* is stored through the DSH Credentials service under a provider-scoped ref
|
|
7
|
+
* (see refs.ts); it never reaches the browser side.
|
|
8
|
+
*/
|
|
9
|
+
/** Everything a vendor needs to call its API plus display identity. */
|
|
10
|
+
export interface SubscriptionBlob {
|
|
11
|
+
accessToken: string;
|
|
12
|
+
refreshToken: string;
|
|
13
|
+
/** Epoch ms when the access token stops working; 0 when unknown. */
|
|
14
|
+
expiresAt: number;
|
|
15
|
+
label: string;
|
|
16
|
+
email: string;
|
|
17
|
+
/** ChatGPT-only: the chatgpt-account-id request header value. */
|
|
18
|
+
accountId: string;
|
|
19
|
+
}
|
|
20
|
+
/** Serialize a blob for storage; at least one token must be present. */
|
|
21
|
+
export declare function serializeBlob(obj: Partial<SubscriptionBlob>): string;
|
|
22
|
+
/** Parse a stored blob back; invalid input throws. */
|
|
23
|
+
export declare function parseBlob(text: string): SubscriptionBlob;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Listen on the redirect_uri's port and resolve when the provider calls back.
|
|
3
|
+
* Rejects on port conflicts, network errors, or when nothing arrives in time.
|
|
4
|
+
*/
|
|
5
|
+
export declare function startLoopback(options: {
|
|
6
|
+
redirectUri: string;
|
|
7
|
+
timeoutMs?: number;
|
|
8
|
+
onCode: (params: URLSearchParams) => Promise<string>;
|
|
9
|
+
}): Promise<{
|
|
10
|
+
ok: true;
|
|
11
|
+
}>;
|