diff --git a/resources/css/common.css b/resources/css/common.css
index 02375ad95..9193de449 100644
--- a/resources/css/common.css
+++ b/resources/css/common.css
@@ -789,3 +789,12 @@ a {
font-weight: 600;
font-size: 13px;
}
+
+/* excalidraw */
+.Island > div > div > div {
+ width: 44px;
+}
+
+.excalidraw hr {
+ margin: 0;
+}
diff --git a/resources/css/excalidraw.min.css b/resources/css/excalidraw.min.css
index 190340b0d..b957d0fc3 100644
--- a/resources/css/excalidraw.min.css
+++ b/resources/css/excalidraw.min.css
@@ -1,40 +1,2 @@
-.popover{position:absolute;z-index:10}.popover .cover{position:fixed;top:0;left:0;right:0;bottom:0}
-
-.context-menu{position:relative;border-radius:4px;box-shadow:0px 3px 10px rgba(0,0,0,0.2);padding:0;list-style:none;user-select:none;margin:-0.25rem 0 0 0.125rem;padding:0.25rem 0;background-color:#f1f3f5;border:1px solid #adb5bd}.context-menu-option{position:relative;width:100%;min-width:9.5rem;margin:0;padding:0.25rem 1rem 0.25rem 1.25rem;text-align:start;border-radius:0;background-color:transparent;border:none;white-space:nowrap}.context-menu-option:hover{color:#fff;background-color:#339af0}.context-menu-option:focus{z-index:1}
-
-:root{--button-gray-1: #e9ecef;--button-gray-2: #ced4da;--button-gray-3: #adb5bd;--button-blue: #a5d8ff}.ToolIcon{display:inline-flex;align-items:center;position:relative;font-family:Cascadia;cursor:pointer;background-color:var(--button-gray-1);-webkit-tap-highlight-color:transparent;border-radius:var(--space-factor)}.ToolIcon__icon{width:2.5rem;height:2.5rem;display:flex;justify-content:center;align-items:center;border-radius:var(--space-factor)}.ToolIcon__icon svg{position:relative;height:1em}.ToolIcon__icon+.ToolIcon__label{margin-inline-start:0}.ToolIcon__label{font-family:var(--ui-font);margin:0 0.8em;text-overflow:ellipsis}.ToolIcon_size_s .ToolIcon__icon{width:1.4rem;height:1.4rem;font-size:0.8em}.excalidraw .ToolIcon_type_button,.Modal .ToolIcon_type_button,.ToolIcon_type_button{padding:0;border:none;margin:0;font-size:inherit}.excalidraw .ToolIcon_type_button:hover,.Modal .ToolIcon_type_button:hover,.ToolIcon_type_button:hover{background-color:var(--button-gray-1)}.excalidraw .ToolIcon_type_button:active,.Modal .ToolIcon_type_button:active,.ToolIcon_type_button:active{background-color:var(--button-gray-2)}.excalidraw .ToolIcon_type_button:focus,.Modal .ToolIcon_type_button:focus,.ToolIcon_type_button:focus{box-shadow:0 0 0 2px var(--button-blue)}.excalidraw .ToolIcon_type_button.ToolIcon--selected,.Modal .ToolIcon_type_button.ToolIcon--selected,.ToolIcon_type_button.ToolIcon--selected{background-color:var(--button-gray-2)}.excalidraw .ToolIcon_type_button.ToolIcon--selected:active,.Modal .ToolIcon_type_button.ToolIcon--selected:active,.ToolIcon_type_button.ToolIcon--selected:active{background-color:var(--button-gray-3)}.excalidraw .ToolIcon_type_button--show,.Modal .ToolIcon_type_button--show,.ToolIcon_type_button--show{visibility:visible}.excalidraw .ToolIcon_type_button--hide,.Modal .ToolIcon_type_button--hide,.ToolIcon_type_button--hide{visibility:hidden}.ToolIcon_type_radio,.ToolIcon_type_checkbox{position:absolute;opacity:0;pointer-events:none}.ToolIcon_type_radio:checked+.ToolIcon__icon,.ToolIcon_type_checkbox:checked+.ToolIcon__icon{background-color:var(--button-gray-2)}.ToolIcon_type_radio:focus+.ToolIcon__icon,.ToolIcon_type_checkbox:focus+.ToolIcon__icon{box-shadow:0 0 0 2px var(--button-blue)}.ToolIcon_type_radio:active+.ToolIcon__icon,.ToolIcon_type_checkbox:active+.ToolIcon__icon{background-color:var(--button-gray-3)}.ToolIcon_type_floating{background-color:transparent}.ToolIcon_type_floating:hover{background-color:transparent}.ToolIcon_type_floating:active{background-color:transparent}.ToolIcon_type_floating:focus{box-shadow:none}.ToolIcon_type_floating .ToolIcon__icon{width:2rem;height:2em}.ToolIcon.ToolIcon__lock.ToolIcon_type_floating{margin-left:0.1rem}.ToolIcon__keybinding{position:absolute;bottom:2px;right:3px;font-size:0.5em;color:var(--button-gray-3);font-family:var(--ui-font);user-select:none}@media (max-width: 360px){.ToolIcon.ToolIcon__lock{display:inline-block;position:absolute;top:60px;right:-8px;margin-left:0;border-radius:20px 0 0 20px;background-color:var(--button-gray-1)}.ToolIcon.ToolIcon__lock:hover{background-color:var(--button-gray-1)}.ToolIcon.ToolIcon__lock:active{background-color:var(--button-gray-2)}.ToolIcon.ToolIcon__lock .ToolIcon__icon{width:2.5rem;height:2.5rem;border-radius:inherit}.ToolIcon.ToolIcon__lock svg{position:static}}:root[dir="ltr"] .unlocked-icon{left:2px}:root[dir="rtl"] .unlocked-icon{right:2px}
-
-.color-picker{background:#fff;border:0px solid rgba(255,255,255,0.25);box-shadow:rgba(0,0,0,0.25) 0px 1px 4px;border-radius:4px;position:absolute}:root[dir="ltr"] .color-picker{left:-5.5px}:root[dir="rtl"] .color-picker{right:-5.5px}.color-picker-control-container{display:grid;grid-template-columns:auto 1fr;align-items:center}.color-picker-triangle{width:0px;height:0px;border-style:solid;border-width:0px 9px 10px;border-color:transparent transparent #fff;position:absolute;top:-10px}:root[dir="ltr"] .color-picker-triangle{left:12px}:root[dir="rtl"] .color-picker-triangle{right:12px}.color-picker-triangle-shadow{border-color:transparent transparent rgba(0,0,0,0.1);top:-11px}.color-picker-content{padding:0.5rem;display:grid;grid-template-columns:repeat(5, auto);grid-gap:0.5rem}.color-picker-content .color-input-container{grid-column:1 / span 5}.excalidraw .color-picker-swatch{position:relative;height:1.875rem;width:1.875rem;cursor:pointer;border-radius:4px;margin:0;box-sizing:border-box;border:1px solid #ddd;background-color:currentColor !important}.excalidraw .color-picker-swatch:focus{box-shadow:0 0 4px 1px currentColor;border-color:#339af0}.color-picker-transparent{border-radius:4px;box-shadow:rgba(0,0,0,0.1) 0px 0px 0px 1px inset;position:absolute;top:0px;right:0px;bottom:0px;left:0px}.color-picker-transparent,.color-picker-label-swatch{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMUlEQVQ4T2NkYGAQYcAP3uCTZhw1gGGYhAGBZIA/nYDCgBDAm9BGDWAAJyRCgLaBCAAgXwixzAS0pgAAAABJRU5ErkJggg==") left center}.color-picker-hash{background:#dee2e6;height:1.875rem;width:1.875rem;color:#495057;display:flex;align-items:center;justify-content:center;position:relative}:root[dir="ltr"] .color-picker-hash{border-radius:4px 0px 0px 4px}:root[dir="rtl"] .color-picker-hash{border-radius:0px 4px 4px 0px}.color-input-container:focus-within .color-picker-hash{box-shadow:0 0 0 2px #a5d8ff}.color-input-container:focus-within .color-picker-hash::before,.color-input-container:focus-within .color-picker-hash::after{content:"";width:1px;height:100%;position:absolute;top:0}.color-input-container:focus-within .color-picker-hash::before{background:#dee2e6}:root[dir="ltr"] .color-input-container:focus-within .color-picker-hash::before{right:-1px}:root[dir="rtl"] .color-input-container:focus-within .color-picker-hash::before{left:-1px}.color-input-container:focus-within .color-picker-hash::after{background:#fff}:root[dir="ltr"] .color-input-container:focus-within .color-picker-hash::after{right:-2px}:root[dir="rtl"] .color-input-container:focus-within .color-picker-hash::after{left:-2px}.color-input-container{display:flex}.color-picker-input{width:12ch;margin:0;font-size:1rem;color:#343a40;border:0px;outline:none;height:1.75em;box-shadow:#dee2e6 0px 0px 0px 1px inset;float:left;padding:1px;padding-inline-start:0.5em;appearance:none}:root[dir="ltr"] .color-picker-input{border-radius:0px 4px 4px 0px}:root[dir="rtl"] .color-picker-input{border-radius:4px 0px 0px 4px}.excalidraw .color-picker-label-swatch{height:1.875rem;width:1.875rem;margin-inline-end:0.25rem;border:1px solid #dee2e6;position:relative;overflow:hidden;background-color:transparent !important}.excalidraw .color-picker-label-swatch:after{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background:var(--swatch-color)}.color-picker-keybinding{position:absolute;bottom:2px;font-size:0.7em;color:#ccc}:root[dir="ltr"] .color-picker-keybinding{right:2px}:root[dir="rtl"] .color-picker-keybinding{left:2px}
-
-.TextInput{display:inline-block;border:1.5px solid #e9ecef;line-height:1;padding:0.75rem;white-space:nowrap;border-radius:var(--space-factor);background-color:#fff}.TextInput:not(:focus):hover{background-color:#f1f3f5}.TextInput:focus{outline:none;box-shadow:0 0 0 2px #339af0}
-
-.Avatar{width:2.5rem;height:2.5rem;border-radius:1.25rem;display:flex;justify-content:center;align-items:center;color:#fff;cursor:pointer;font-size:0.8rem;font-weight:500}
-
-.Island{--padding: 0;background-color:var(--bg-color-island);backdrop-filter:saturate(100%) blur(10px);box-shadow:var(--shadow-island);border-radius:var(--border-radius-m);padding:calc(var(--padding) * var(--space-factor));position:relative;transition:box-shadow 0.5s ease-in-out}.Island.zen-mode{box-shadow:none}
-
-.Stack{--gap: 0;display:grid;gap:calc(var(--space-factor) * var(--gap))}.Stack_vertical{grid-template-columns:auto;grid-auto-flow:row;grid-auto-rows:min-content}.Stack_horizontal{grid-template-rows:auto;grid-auto-flow:column;grid-auto-columns:min-content}
-
-.FixedSideContainer{--margin: 0.25rem;position:absolute;pointer-events:none}.FixedSideContainer>*{pointer-events:all}.FixedSideContainer_side_top{left:var(--margin);top:var(--margin);right:var(--margin);z-index:2}.FixedSideContainer_side_top.zen-mode{right:42px}
-
-.UserList{pointer-events:none;padding:var(--space-factor) 40px var(--space-factor) var(--space-factor);display:flex;flex-wrap:wrap;justify-content:flex-end}.UserList>*{pointer-events:all;margin:0 0 var(--space-factor) var(--space-factor)}.UserList_mobile{padding:0;justify-content:normal}.UserList_mobile>*{margin:0 var(--space-factor) var(--space-factor) 0}
-
-.ExportDialog__preview{--preview-padding: calc(var(--space-factor) * 4);background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMUlEQVQ4T2NkYGAQYcAP3uCTZhw1gGGYhAGBZIA/nYDCgBDAm9BGDWAAJyRCgLaBCAAgXwixzAS0pgAAAABJRU5ErkJggg==") left center;text-align:center;padding:var(--preview-padding);margin-bottom:calc(var(--space-factor) * 3)}.ExportDialog__preview canvas{max-width:calc(100% - var(--preview-padding) * 2);max-height:25rem}.ExportDialog__actions{width:100%;display:flex;grid-gap:calc(var(--space-factor) * 2);align-items:top;justify-content:space-between}.ExportDialog__name{grid-column:project-name;margin:auto}@media (max-width: 550px){.ExportDialog{display:flex;flex-direction:column}.ExportDialog__actions{flex-direction:column;align-items:center}.ExportDialog__actions>*{margin-bottom:calc(var(--space-factor) * 3)}}@media (max-width: 600px), (max-height: 500px) and (max-width: 1000px){.ExportDialog__preview canvas{max-height:30vh}.ExportDialog__dialog,.ExportDialog__dialog .Island{height:100%;box-sizing:border-box}.ExportDialog__dialog .Island{overflow-y:auto}}
-
-.Modal{position:fixed;top:0;left:0;right:0;bottom:0;display:flex;align-items:center;justify-content:center;overflow:auto;padding:calc(var(--space-factor) * 10)}.Modal__background{position:fixed;top:0;left:0;right:0;bottom:0;z-index:1;background-color:rgba(0,0,0,0.3);backdrop-filter:blur(2px)}.Modal__content{position:relative;z-index:2;width:100%;max-width:var(--max-width);opacity:0;transform:translateY(10px);animation:Modal__content_fade-in 0.1s ease-out 0.05s forwards;position:relative;background:#fff;backdrop-filter:none}@media (max-width: 600px), (max-height: 500px) and (max-width: 1000px){.Modal__content{max-width:100%}}@keyframes Modal__content_fade-in{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.Modal__close{width:calc(var(--space-factor) * 7);height:calc(var(--space-factor) * 7);display:flex;align-items:center;justify-content:center}.Modal__close svg{height:calc(var(--space-factor) * 5)}.Modal__close--floating{position:absolute;right:calc(var(--space-factor) * 5);top:calc(var(--space-factor) * 5)}@media (max-width: 600px), (max-height: 500px) and (max-width: 1000px){.Modal{padding:0}.Modal__content{position:fixed;top:0;left:0;right:0;bottom:0}}
-
-.Dialog__title{display:grid;align-items:center;margin-top:0;grid-template-columns:1fr calc(var(--space-factor) * 7);grid-gap:var(--metric)}.Dialog__titleContent{flex:1}.Dialog .Modal__close{margin:0}@media (max-width: 600px), (max-height: 500px) and (max-width: 1000px){.Dialog{--metric: calc(var(--space-factor) * 4);--inset-left: max(var(--metric), var(--sal));--inset-right: max(var(--metric), var(--sar))}.Dialog__title{grid-template-columns:calc(var(--space-factor) * 7) 1fr calc( var(--space-factor) * 7);position:sticky;top:calc(-1 * var(--metric));margin:calc(-1 * var(--inset-right));margin-top:calc(-1 * var(--metric));margin-bottom:var(--metric);padding:calc(var(--space-factor) * 2);padding-left:var(--inset-left);padding-right:var(--inset-right);background:white;font-size:1.25em;box-sizing:border-box;border-bottom:1px solid #ced4da;z-index:1}.Dialog__titleContent{text-align:center}.Dialog .Island{width:100vw;height:100%;box-sizing:border-box;overflow-y:auto;padding-left:max(calc(var(--padding) * var(--space-factor)), var(--sal));padding-right:max(calc(var(--padding) * var(--space-factor)), var(--sar));padding-bottom:max(calc(var(--padding) * var(--space-factor)), var(--sab))}.Dialog .Modal__close{order:-1}}
-
-.HintViewer{color:#868e96;font-size:0.8rem;left:50%;pointer-events:none;position:absolute;top:54px;transform:translateX(calc(-50% - 16px));white-space:pre;text-align:center}@media (max-width: 600px), (max-height: 500px) and (max-width: 1000px){.HintViewer{position:static;transform:none;margin-top:0.5rem}}.HintViewer>span{background-color:rgba(255,255,255,0.88);padding:0.2rem 0.4rem;border-radius:3px}
-
-.RoomDialog-modalButton.is-collaborating{background-color:#ebfbee;color:#2b8a3e}.RoomDialog-modalButton-collaborators{min-width:1em;position:absolute;bottom:-5px;right:-5px;padding:3px;border-radius:50%;background-color:#40c057;color:#fff;font-size:0.7em;font-family:var(--ui-font)}.RoomDialog-linkContainer{display:flex;margin:1.5em 0}.RoomDialog-link{min-width:0;flex:1 1 auto;margin-left:1em;display:inline-block;cursor:pointer;border:none;height:2.5rem;line-height:2.5rem;padding:0 0.5rem;white-space:nowrap;border-radius:var(--space-factor);background-color:var(--button-gray-1)}.RoomDialog-usernameContainer{display:flex;margin:1.5em 0;display:flex;align-items:center;justify-content:center}.RoomDialog-username{appearance:none;min-width:0;flex:1 1 auto;margin-left:1em;height:2.5rem;font-size:1em;line-height:1.5;padding:0 0.5rem}.RoomDialog-sessionStartButtonContainer{display:flex;justify-content:center}.Modal .RoomDialog-stopSession{background-color:#ffe3e3;color:#c92a2a}
-
-.Tooltip{position:relative}.Tooltip__label{--arrow-size: 4px;visibility:hidden;width:10ch;background:#000;color:#fff;text-align:center;border-radius:4px;padding:4px;position:absolute;z-index:10;font-size:0.7rem;line-height:1.5;top:calc(100% + var(--arrow-size) + 3px);left:calc(-50% + var(--arrow-size) / 2 - 1px);word-wrap:break-word}.Tooltip__label::after{content:"";border:var(--arrow-size) solid transparent;border-bottom-color:#000;position:absolute;bottom:100%;left:calc(50% - var(--arrow-size))}body:active .Tooltip:not(:hover){pointer-events:none}body:not(:active) .Tooltip:hover .Tooltip__label{visibility:visible}.Tooltip__label:hover{visibility:visible}
-
-.layer-ui__wrapper .encrypted-icon{position:relative;margin-inline-start:15px;display:flex;justify-content:center;align-items:center;border-radius:var(--space-factor);color:#2b8a3e}.layer-ui__wrapper .encrypted-icon svg{width:1.2rem;height:1.2rem}.layer-ui__wrapper .encrypted-icon.tooltip .tooltip-text{visibility:hidden;width:20rem;bottom:calc(50% + 0.8rem + 6px);background-color:#000;color:#fff;text-align:center;border-radius:6px;padding:5px;position:absolute;z-index:10;font-size:13px;line-height:1.5;white-space:pre-wrap}:root[dir="ltr"] .layer-ui__wrapper .encrypted-icon.tooltip .tooltip-text{left:-5px}:root[dir="rtl"] .layer-ui__wrapper .encrypted-icon.tooltip .tooltip-text{right:-5px}.layer-ui__wrapper .encrypted-icon.tooltip .tooltip-text::after{--size: 6px;content:"";border:var(--size) solid transparent;border-top-color:#000;position:absolute;bottom:calc(-2 * var(--size))}:root[dir="ltr"] .layer-ui__wrapper .encrypted-icon.tooltip .tooltip-text::after{left:calc(5px + var(--size) / 2)}:root[dir="rtl"] .layer-ui__wrapper .encrypted-icon.tooltip .tooltip-text::after{right:calc(5px + var(--size) / 2)}body:active .layer-ui__wrapper .encrypted-icon.tooltip:not(:hover){pointer-events:none}body:not(:active) .layer-ui__wrapper .encrypted-icon.tooltip:hover .tooltip-text{visibility:visible}.layer-ui__wrapper .encrypted-icon .tooltip-text:hover{visibility:visible}.layer-ui__wrapper__github-corner{top:0;position:absolute;width:40px}:root[dir="ltr"] .layer-ui__wrapper__github-corner{right:0}:root[dir="rtl"] .layer-ui__wrapper__github-corner{left:0}.layer-ui__wrapper__footer{position:absolute;bottom:0px;width:190px}:root[dir="ltr"] .layer-ui__wrapper__footer{right:0}:root[dir="rtl"] .layer-ui__wrapper__footer{left:0}.layer-ui__wrapper .zen-mode-transition{transition:transform 0.5s ease-in-out}:root[dir="ltr"] .layer-ui__wrapper .zen-mode-transition.transition-left{transform:translate(-999px, 0)}:root[dir="ltr"] .layer-ui__wrapper .zen-mode-transition.transition-right{transform:translate(999px, 0px)}:root[dir="rtl"] .layer-ui__wrapper .zen-mode-transition.transition-left{transform:translate(999px, 0)}:root[dir="rtl"] .layer-ui__wrapper .zen-mode-transition.transition-right{transform:translate(-999px, 0)}.layer-ui__wrapper .zen-mode-transition.App-menu_bottom--transition-left{transform:translate(-92px, 0)}.layer-ui__wrapper .disable-zen-mode{height:30px;position:absolute;bottom:10px;right:15px;font-size:10px;padding:10px;font-weight:500;opacity:0;visibility:hidden;transition:visibility 0s linear 0s, opacity 0.5s}.layer-ui__wrapper .disable-zen-mode--visible{opacity:1;visibility:visible;transition:visibility 0s linear 300ms, opacity 0.5s;transition-delay:0.8s}
-
-@font-face{font-family:"Virgil";src:url(FG_Virgil.woff2);font-display:swap}@font-face{font-family:"Cascadia";src:url(Cascadia.woff2);font-display:swap}
-
-.visually-hidden{position:absolute !important;height:1px;width:1px;overflow:hidden;clip:rect(1px, 1px, 1px, 1px);white-space:nowrap}.LoadingMessage{position:absolute;top:0;right:0;bottom:0;left:0;z-index:999;display:flex;align-items:center;justify-content:center;pointer-events:none}.LoadingMessage span{background-color:rgba(255,255,255,0.8);border-radius:5px;padding:0.8em 1.2em;font-size:1.3em}
-
-:root{--text-color-primary: #343a40;--bg-color-main: #fff;--shadow-island: 0 1px 5px rgba(0, 0, 0, 0.15);--bg-color-island: rgba(255, 255, 255, 0.9);--border-radius-m: 4px;--space-factor: 0.25rem}:root{--sat: env(safe-area-inset-top);--sab: env(safe-area-inset-bottom);--sal: env(safe-area-inset-left);--sar: env(safe-area-inset-right);--text-color-primary: #343a40;--bg-color-main: #fff;--shadow-island: 0 1px 5px rgba(0,0,0,0.15);--border-radius-m: 4px;--space-factor: 0.25rem}.excalidraw{display:flex;position:fixed;top:0;bottom:0;left:0;right:0}.excalidraw [contenteditable]{user-select:auto;cursor:text}.excalidraw canvas{touch-action:none;user-select:none;image-rendering:pixelated;image-rendering:-moz-crisp-edges}.excalidraw .FixedSideContainer{padding-top:var(--sat, 0px);padding-right:var(--sar, 0px);padding-bottom:var(--sab, 0px);padding-left:var(--sal, 0px)}.excalidraw .panelRow{display:flex;justify-content:space-between}.excalidraw .panelColumn{display:flex;flex-direction:column}.excalidraw .panelColumn h3,.excalidraw .panelColumn legend,.excalidraw .panelColumn .control-label{margin-top:0.333rem;margin-bottom:0.333rem;font-size:0.75rem;color:var(--text-color-primary);font-weight:bold;display:block}.excalidraw .panelColumn .control-label input{display:block;width:100%}.excalidraw .panelColumn h3:first-child,.excalidraw .panelColumn legend:first-child,.excalidraw .panelColumn .control-label:first-child{margin-top:0}.excalidraw .panelColumn legend{padding:0}.excalidraw .panelColumn .buttonList{flex-wrap:wrap}.excalidraw .panelColumn .buttonList label{margin-right:0.25rem;font-size:0.75rem;display:inline-block}.excalidraw .panelColumn .buttonList input[type="radio"]{opacity:0;position:absolute;pointer-events:none}.excalidraw .panelColumn .buttonList .ToolIcon{margin:0 5px}.excalidraw .panelColumn .buttonList .ToolIcon__icon{width:28px;height:28px}.excalidraw .panelColumn fieldset{margin:0;margin-top:0.333rem;padding:0;border:none}.excalidraw .divider{width:1px;background-color:#e9ecef;margin:1px}.excalidraw .buttonList label:focus-within,.excalidraw input:focus{outline:transparent;box-shadow:0 0 0 2px #a5d8ff}.excalidraw .active,.excalidraw .buttonList label.active{background-color:#ced4da}.excalidraw .active:hover,.excalidraw .buttonList label.active:hover{background-color:#ced4da}.excalidraw .active:active,.excalidraw .buttonList label.active:active{background-color:#adb5bd}.excalidraw .App-bottom-bar{position:absolute;top:0;bottom:0;left:0;right:0;--bar-padding: calc(4 * var(--space-factor));padding-top:max(var(--bar-padding), var(--sat, 0px));padding-right:var(--sar, 0px);padding-bottom:var(--sab, 0px);padding-left:var(--sal, 0px);z-index:4;display:flex;align-items:flex-end;pointer-events:none}.excalidraw .App-bottom-bar>.Island{width:100%;max-width:100%;min-width:100%;box-sizing:border-box;max-height:100%;display:flex;flex-direction:column;pointer-events:initial}.excalidraw .App-toolbar{width:100%;box-sizing:border-box}.excalidraw .App-toolbar-content{display:flex;align-items:center;justify-content:space-between}.excalidraw .App-mobile-menu{width:100%;overflow-x:visible;overflow-y:auto;box-sizing:border-box;margin-bottom:var(--bar-padding)}.excalidraw .App-menu{display:grid}.excalidraw .App-menu_top{grid-template-columns:1fr auto 1fr;grid-gap:4px;align-items:flex-start;cursor:default;pointer-events:none !important}.excalidraw .App-menu_top>*{pointer-events:all}.excalidraw .App-menu_top>*:first-child{justify-self:flex-start}.excalidraw .App-menu_top>*:last-child{justify-self:flex-end}.excalidraw .App-menu_bottom{position:absolute;bottom:0;grid-template-columns:1fr auto 1fr;grid-gap:4px;align-items:flex-start;cursor:default;pointer-events:none !important}.excalidraw .App-menu_bottom--transition-left section{width:185px}.excalidraw .App-menu_bottom section{display:flex}.excalidraw .App-menu_bottom>*{pointer-events:all}.excalidraw .App-menu_bottom>*:first-child{justify-self:flex-start}.excalidraw .App-menu_bottom>*:last-child{justify-self:flex-end}.excalidraw .App-menu_left{grid-template-rows:1fr auto 1fr;height:100%}.excalidraw .App-menu_right{grid-template-rows:1fr;height:100%}.excalidraw .App-menu__left{overflow-y:auto;max-height:calc(100vh - 236px)}.excalidraw .ErrorSplash{min-height:100vh;padding:20px 0;overflow:auto;display:flex;align-items:center;justify-content:center;user-select:text}.excalidraw .ErrorSplash .ErrorSplash-messageContainer{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px;background-color:#ffe3e3;border:3px solid #c92a2a}.excalidraw .ErrorSplash .ErrorSplash-paragraph{margin:15px 0;max-width:600px}.excalidraw .ErrorSplash .ErrorSplash-paragraph.align-center{text-align:center}.excalidraw .ErrorSplash .bigger,.excalidraw .ErrorSplash .bigger button{font-size:1.1em}.excalidraw .ErrorSplash .smaller,.excalidraw .ErrorSplash .smaller button{font-size:0.9em}.excalidraw .ErrorSplash .ErrorSplash-details{display:flex;flex-direction:column;align-items:flex-start}.excalidraw .ErrorSplash .ErrorSplash-details textarea{width:100%;margin:10px 0;font-family:"Cascadia";font-size:0.8em}.excalidraw .dropdown-select{height:1.5rem;padding:0;padding-inline-start:0.5rem;padding-inline-end:1.5rem;background-color:#e9ecef;border-radius:var(--space-factor);border:1px solid #ced4da;font-size:0.8rem;outline:none;appearance:none;background-image:url('data:image/svg+xml,');background-repeat:no-repeat;background-position:right 0.7rem top 50%, 0 0;background-size:0.65em auto, 100%}:root[dir="rtl"] .excalidraw .dropdown-select{background-position:left 0.7rem top 50%, 0 0}.excalidraw .dropdown-select:focus{box-shadow:0 0 0 2px #a5d8ff}.excalidraw .dropdown-select:hover{background-color:#ced4da}.excalidraw .dropdown-select:active{background-color:#ced4da}.excalidraw .dropdown-select.dropdown-select--floating{position:absolute;margin-right:0.5em}.excalidraw .dropdown-select__language.dropdown-select--floating{position:fixed;bottom:10px}:root[dir="ltr"] .excalidraw .dropdown-select__language.dropdown-select--floating{right:44px}:root[dir="rtl"] .excalidraw .dropdown-select__language.dropdown-select--floating{left:44px}.excalidraw .zIndexButton{margin:0 5px;padding:5px;display:inline-flex;align-items:center;justify-content:center}.excalidraw .zIndexButton svg{width:18px;height:18px}.excalidraw .scroll-back-to-content{position:fixed;left:50%;bottom:30px;transform:translateX(-50%);padding:10px 20px}.excalidraw .help-icon{position:absolute;cursor:pointer;fill:#868e96;bottom:14px}:root[dir="ltr"] .excalidraw .help-icon{right:14px}:root[dir="rtl"] .excalidraw .help-icon{left:14px}@media (max-width: 600px), (max-height: 500px) and (max-width: 1000px){.excalidraw aside{display:none}.excalidraw .scroll-back-to-content{bottom:calc(80px + var(--sab, 0px));z-index:-1}}:root[dir="rtl"] .excalidraw .rtl-mirror{transform:scaleX(-1)}.excalidraw .github-corner{position:absolute;top:0;z-index:2}:root[dir="ltr"] .excalidraw .github-corner{right:0}:root[dir="rtl"] .excalidraw .github-corner{left:0}.excalidraw .zen-mode-visibility{visibility:visible;opacity:1;height:auto;width:auto;transition:opacity 0.5s}.excalidraw .zen-mode-visibility.zen-mode-visibility--hidden{visibility:hidden;opacity:0;height:0;width:0;transition:opacity 0.5s}.excalidraw .disable-pointerEvents{pointer-events:none !important}.excalidraw a,.Modal a{font-weight:500;text-decoration:none;color:#1c7ed6}.excalidraw a:hover,.Modal a:hover{text-decoration:underline}.excalidraw button,.excalidraw .buttonList label,.Modal button,.Modal .buttonList label{user-select:none;background-color:#e9ecef;border:0;border-radius:4px;margin:0.125rem 0;padding:0.25rem;white-space:nowrap;cursor:pointer}.excalidraw button:focus,.excalidraw .buttonList label:focus,.Modal button:focus,.Modal .buttonList label:focus{outline:transparent;box-shadow:0 0 0 2px #a5d8ff}.excalidraw button:hover,.excalidraw .buttonList label:hover,.Modal button:hover,.Modal .buttonList label:hover{background-color:#ced4da}.excalidraw button:active,.excalidraw .buttonList label:active,.Modal button:active,.Modal .buttonList label:active{background-color:#adb5bd}.excalidraw button:disabled,.excalidraw .buttonList label:disabled,.Modal button:disabled,.Modal .buttonList label:disabled{cursor:not-allowed}@media print{.App-bottom-bar,.FixedSideContainer,.layer-ui__wrapper{display:none}}
-
+.excalidraw .Dialog{-webkit-user-select:text;user-select:text;cursor:auto}.excalidraw .Dialog__title{display:grid;align-items:center;margin-top:0;grid-template-columns:1fr calc(var(--space-factor)*7);grid-gap:var(--metric);padding:calc(var(--space-factor)*2);text-align:center;font-feature-settings:"smcp";font-variant:small-caps;font-size:1.2em}.excalidraw .Dialog__titleContent{flex:1 1}.excalidraw .Dialog .Modal__close{color:var(--icon-fill-color);margin:0}.excalidraw .Dialog__content{padding:0 16px 16px}@media(max-height:500px)and (max-width:1000px),(max-width:600px){.excalidraw .Dialog{--metric:calc(var(--space-factor)*4);--inset-left:max(var(--metric),var(--sal));--inset-right:max(var(--metric),var(--sar))}.excalidraw .Dialog__title{grid-template-columns:calc(var(--space-factor)*7) 1fr calc(var(--space-factor)*7);position:-webkit-sticky;position:sticky;top:0;padding:calc(var(--space-factor)*2);background:var(--island-bg-color);font-size:1.25em;box-sizing:border-box;border-bottom:1px solid var(--button-gray-2);z-index:1}.excalidraw .Dialog__titleContent{text-align:center}.excalidraw .Dialog .Island{width:100vw;height:100%;box-sizing:border-box;overflow-y:auto;padding-left:max(calc(var(--padding)*var(--space-factor)),var(--sal));padding-right:max(calc(var(--padding)*var(--space-factor)),var(--sar));padding-bottom:max(calc(var(--padding)*var(--space-factor)),var(--sab))}.excalidraw .Dialog .Modal__close{order:-1}}.excalidraw .Island{--padding:0;background-color:var(--island-bg-color);-webkit-backdrop-filter:saturate(100%) blur(10px);backdrop-filter:saturate(100%) blur(10px);box-shadow:var(--shadow-island);border-radius:4px;padding:calc(var(--padding)*var(--space-factor));position:relative;transition:box-shadow .5s ease-in-out}.excalidraw .Island.zen-mode{box-shadow:none}.excalidraw.excalidraw-modal-container{position:absolute;z-index:10}.excalidraw .Modal{position:absolute;top:0;left:0;right:0;bottom:0;display:flex;align-items:center;justify-content:center;overflow:auto;padding:calc(var(--space-factor)*10)}.excalidraw .Modal__background{position:absolute;top:0;left:0;right:0;bottom:0;z-index:1;background-color:rgba(0,0,0,.3);-webkit-backdrop-filter:blur(2px);backdrop-filter:blur(2px)}.excalidraw .Modal__content{z-index:2;width:100%;max-width:var(--max-width);max-height:100%;opacity:0;transform:translateY(10px);animation:Modal__content_fade-in .1s ease-out .05s forwards;position:relative;overflow-y:auto;background:var(--island-bg-color);-webkit-backdrop-filter:none;backdrop-filter:none;border:1px solid var(--dialog-border-color);box-shadow:0 2px 10px rgba(0,0,0,.25);border-radius:6px}@media(max-height:500px)and (max-width:1000px),(max-width:600px){.excalidraw .Modal__content{max-width:100%;border:0;border-radius:0}}@keyframes Modal__content_fade-in{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.excalidraw .Modal__close{width:calc(var(--space-factor)*7);height:calc(var(--space-factor)*7);display:flex;align-items:center;justify-content:center}.excalidraw .Modal__close svg{height:calc(var(--space-factor)*5)}@media(max-height:500px)and (max-width:1000px),(max-width:600px){.excalidraw .Modal{padding:0}.excalidraw .Modal__content{position:absolute;top:0;left:0;right:0;bottom:0}}.excalidraw .ToolIcon{display:inline-flex;position:relative;font-family:Cascadia;cursor:pointer;background-color:var(--button-gray-1);-webkit-tap-highlight-color:transparent}.excalidraw .ToolIcon,.excalidraw .ToolIcon__icon{align-items:center;border-radius:var(--space-factor)}.excalidraw .ToolIcon__icon{width:2.5rem;height:2.5rem;color:var(--icon-fill-color);display:flex;justify-content:center}.excalidraw .ToolIcon__icon svg{position:relative;height:1em;fill:var(--icon-fill-color);color:var(--icon-fill-color)}.excalidraw .ToolIcon__icon+.ToolIcon__label{margin-inline-start:0}.excalidraw .ToolIcon__label{color:var(--icon-fill-color);font-family:var(--ui-font);margin:0 .8em;text-overflow:ellipsis}.excalidraw .ToolIcon_size_s .ToolIcon__icon{width:1.4rem;height:1.4rem;font-size:.8em}.excalidraw .excalidraw .ToolIcon_type_button,.excalidraw .Modal .ToolIcon_type_button,.excalidraw .ToolIcon_type_button{padding:0;border:none;margin:0;font-size:inherit}.excalidraw .excalidraw .ToolIcon_type_button:hover,.excalidraw .Modal .ToolIcon_type_button:hover,.excalidraw .ToolIcon_type_button:hover{background-color:var(--button-gray-1)}.excalidraw .excalidraw .ToolIcon_type_button:active,.excalidraw .Modal .ToolIcon_type_button:active,.excalidraw .ToolIcon_type_button:active{background-color:var(--button-gray-2)}.excalidraw .excalidraw .ToolIcon_type_button:focus,.excalidraw .Modal .ToolIcon_type_button:focus,.excalidraw .ToolIcon_type_button:focus{box-shadow:0 0 0 2px var(--focus-highlight-color)}.excalidraw .excalidraw .ToolIcon_type_button.ToolIcon--selected,.excalidraw .Modal .ToolIcon_type_button.ToolIcon--selected,.excalidraw .ToolIcon_type_button.ToolIcon--selected{background-color:var(--button-gray-2)}.excalidraw .excalidraw .ToolIcon_type_button.ToolIcon--selected:active,.excalidraw .Modal .ToolIcon_type_button.ToolIcon--selected:active,.excalidraw .ToolIcon_type_button.ToolIcon--selected:active{background-color:var(--button-gray-3)}.excalidraw .excalidraw .ToolIcon_type_button--show,.excalidraw .Modal .ToolIcon_type_button--show,.excalidraw .ToolIcon_type_button--show{visibility:visible}.excalidraw .excalidraw .ToolIcon_type_button--hide,.excalidraw .Modal .ToolIcon_type_button--hide,.excalidraw .ToolIcon_type_button--hide{visibility:hidden}.excalidraw .ToolIcon_type_checkbox,.excalidraw .ToolIcon_type_radio{position:absolute;opacity:0;pointer-events:none}.excalidraw .ToolIcon_type_checkbox:not(.ToolIcon_toggle_opaque):checked+.ToolIcon__icon,.excalidraw .ToolIcon_type_radio:not(.ToolIcon_toggle_opaque):checked+.ToolIcon__icon{background-color:var(--button-gray-2)}.excalidraw .ToolIcon_type_checkbox:focus+.ToolIcon__icon,.excalidraw .ToolIcon_type_radio:focus+.ToolIcon__icon{box-shadow:0 0 0 2px var(--focus-highlight-color)}.excalidraw .ToolIcon_type_checkbox:active+.ToolIcon__icon,.excalidraw .ToolIcon_type_radio:active+.ToolIcon__icon{background-color:var(--button-gray-3)}.excalidraw .ToolIcon_type_floating,.excalidraw .ToolIcon_type_floating:active,.excalidraw .ToolIcon_type_floating:hover{background-color:initial}.excalidraw .ToolIcon_type_floating:focus{box-shadow:none}.excalidraw .ToolIcon_type_floating .ToolIcon__icon{width:2rem;height:2em}.excalidraw .ToolIcon.ToolIcon__lock.ToolIcon_type_floating{margin-left:.1rem}.excalidraw .ToolIcon__keybinding{position:absolute;bottom:2px;right:3px;font-size:.5em;color:var(--keybinding-color);font-family:var(--ui-font);-webkit-user-select:none;user-select:none}@media(max-width:425px){.excalidraw .Shape .ToolIcon__icon{width:2rem;height:2rem}.excalidraw .Shape .ToolIcon__icon svg{height:.8em}}@media(max-width:760px){.excalidraw .ToolIcon.ToolIcon__lock{display:inline-block;position:absolute;top:60px;right:-8px;margin-left:0;border-radius:20px 0 0 20px;z-index:1}.excalidraw .ToolIcon.ToolIcon__lock,.excalidraw .ToolIcon.ToolIcon__lock:hover{background-color:var(--button-gray-1)}.excalidraw .ToolIcon.ToolIcon__lock:active{background-color:var(--button-gray-2)}.excalidraw .ToolIcon.ToolIcon__lock .ToolIcon__icon{border-radius:inherit}.excalidraw .ToolIcon.ToolIcon__lock svg{position:static}}.excalidraw .TooltipIcon{width:.9em;height:.9em;margin-left:5px;margin-top:1px}@media(max-height:500px)and (max-width:1000px),(max-width:600px){.excalidraw .TooltipIcon{display:none}}:root[dir=ltr] .excalidraw .unlocked-icon{left:2px}:root[dir=rtl] .excalidraw .unlocked-icon{right:2px}.excalidraw .popover{position:fixed;z-index:10}.excalidraw .color-picker{background:var(--popup-bg-color);border:0 solid hsla(0,0%,100%,.25);box-shadow:0 1px 4px rgba(0,0,0,.25);border-radius:4px;position:absolute}:root[dir=ltr] .excalidraw .color-picker{left:-5.5px}:root[dir=rtl] .excalidraw .color-picker{right:-5.5px}.excalidraw .color-picker-control-container{display:grid;grid-template-columns:auto 1fr;align-items:center}.excalidraw .color-picker-triangle{width:0;height:0;border-left:9px solid transparent;border-bottom:10px solid var(--popup-bg-color);border-right:9px solid transparent;border-top:0 solid transparent;position:absolute;top:-10px}:root[dir=ltr] .excalidraw .color-picker-triangle{left:12px}:root[dir=rtl] .excalidraw .color-picker-triangle{right:12px}.excalidraw .color-picker-triangle-shadow{border-color:transparent transparent rgba(0,0,0,.1);top:-11px}.excalidraw .color-picker-content{padding:.5rem;display:grid;grid-template-columns:repeat(5,auto);grid-gap:.5rem;border-radius:4px}.excalidraw .color-picker-content:focus{outline:none;box-shadow:0 0 0 2px var(--focus-highlight-color)}.excalidraw .color-picker-content .color-input-container{grid-column:1/span 5}.excalidraw .color-picker-swatch{position:relative;height:1.875rem;width:1.875rem;cursor:pointer;border-radius:4px;margin:0;box-sizing:border-box;border:1px solid #ddd;background-color:currentColor!important;filter:var(--appearance-filter)}.excalidraw .color-picker-swatch:focus{box-shadow:0 0 4px 1px currentColor;border-color:var(--select-highlight-color)}.excalidraw .color-picker-transparent{border-radius:4px;box-shadow:inset 0 0 0 1px rgba(0,0,0,.1);position:absolute;top:0;right:0;bottom:0;left:0}.excalidraw .color-picker-label-swatch,.excalidraw .color-picker-transparent{background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMUlEQVQ4T2NkYGAQYcAP3uCTZhw1gGGYhAGBZIA/nYDCgBDAm9BGDWAAJyRCgLaBCAAgXwixzAS0pgAAAABJRU5ErkJggg==") 0}.excalidraw .color-picker-hash{background:var(--input-border-color);height:1.875rem;width:1.875rem;color:var(--input-label-color);display:flex;align-items:center;justify-content:center;position:relative}:root[dir=ltr] .excalidraw .color-picker-hash{border-radius:4px 0 0 4px}:root[dir=rtl] .excalidraw .color-picker-hash{border-radius:0 4px 4px 0}.excalidraw .color-input-container:focus-within .color-picker-hash{box-shadow:0 0 0 2px var(--focus-highlight-color)}.excalidraw .color-input-container:focus-within .color-picker-hash:after,.excalidraw .color-input-container:focus-within .color-picker-hash:before{content:"";width:1px;height:100%;position:absolute;top:0}.excalidraw .color-input-container:focus-within .color-picker-hash:before{background:var(--input-border-color)}:root[dir=ltr] .excalidraw .color-input-container:focus-within .color-picker-hash:before{right:-1px}:root[dir=rtl] .excalidraw .color-input-container:focus-within .color-picker-hash:before{left:-1px}.excalidraw .color-input-container:focus-within .color-picker-hash:after{background:var(--input-bg-color)}:root[dir=ltr] .excalidraw .color-input-container:focus-within .color-picker-hash:after{right:-2px}:root[dir=rtl] .excalidraw .color-input-container:focus-within .color-picker-hash:after{left:-2px}.excalidraw .color-input-container{display:flex}.excalidraw .color-picker-input{width:12ch;margin:0;font-size:1rem;background-color:var(--input-bg-color);color:var(--text-primary-color);border:0;outline:none;height:1.75em;box-shadow:var(--input-border-color) 0 0 0 1px inset;float:left;padding:1px;padding-inline-start:.5em;-webkit-appearance:none;-moz-appearance:none;appearance:none}:root[dir=ltr] .excalidraw .color-picker-input{border-radius:0 4px 4px 0}:root[dir=rtl] .excalidraw .color-picker-input{border-radius:4px 0 0 4px}.excalidraw .color-picker-label-swatch{height:1.875rem;width:1.875rem;margin-inline-end:.25rem;border:1px solid #dee2e6;position:relative;overflow:hidden;background-color:initial!important;filter:var(--appearance-filter)}.excalidraw .color-picker-label-swatch:after{content:"";position:absolute;top:0;left:0;width:100%;height:100%;background:var(--swatch-color)}.excalidraw .color-picker-keybinding{position:absolute;bottom:2px;font-size:.7em}:root[dir=ltr] .excalidraw .color-picker-keybinding{right:2px}:root[dir=rtl] .excalidraw .color-picker-keybinding{left:2px}@media(max-height:500px)and (max-width:1000px),(max-width:600px){.excalidraw .color-picker-keybinding{display:none}}.excalidraw .color-picker-type-canvasBackground .color-picker-keybinding{color:#aaa}.excalidraw .color-picker-type-elementBackground .color-picker-keybinding{color:#fff}.excalidraw .color-picker-swatch[aria-label=transparent] .color-picker-keybinding{color:#aaa}.excalidraw .color-picker-type-elementStroke .color-picker-keybinding{color:#d4d4d4}.excalidraw.Appearance_dark .color-picker-swatch[aria-label=transparent] .color-picker-keybinding,.excalidraw.Appearance_dark .color-picker-type-elementBackground .color-picker-keybinding{color:#000}.excalidraw .picker-container{display:inline-block;box-sizing:border-box;margin-right:.25rem}.excalidraw .picker{background:var(--popup-bg-color);border:0 solid hsla(0,0%,100%,.25);box-shadow:0 1px 4px rgba(0,0,0,.25);border-radius:4px;position:absolute}.excalidraw .picker-container button,.excalidraw .picker button{position:relative;display:flex;align-items:center;justify-content:center}.excalidraw .picker-container button:focus,.excalidraw .picker button:focus{outline:transparent;background-color:var(--button-gray-2)}.excalidraw .picker-container button:focus svg,.excalidraw .picker button:focus svg{opacity:1}.excalidraw .picker-container button:hover,.excalidraw .picker button:hover{background-color:var(--button-gray-2)}.excalidraw .picker-container button:active,.excalidraw .picker button:active{background-color:var(--button-gray-3)}.excalidraw .picker-container button:disabled,.excalidraw .picker button:disabled{cursor:not-allowed}.excalidraw .picker-container button svg,.excalidraw .picker button svg{margin:0;width:36px;height:18px;opacity:.6;pointer-events:none}.excalidraw .picker button{padding:.25rem .28rem .35rem .25rem}.excalidraw .picker-triangle{width:0;height:0;position:relative;top:-10px;z-index:10}:root[dir=ltr] .excalidraw .picker-triangle{left:12px}:root[dir=rtl] .excalidraw .picker-triangle{right:12px}.excalidraw .picker-triangle:before{content:"";position:absolute;border-color:transparent transparent rgba(0,0,0,.1);border-style:solid;border-width:0 9px 10px;top:-1px}.excalidraw .picker-triangle:after{content:"";position:absolute;border-left:9px solid transparent;border-bottom:10px solid var(--popup-bg-color);border-right:9px solid transparent;border-top:0 solid transparent}.excalidraw .picker-content{padding:.5rem;display:grid;grid-auto-flow:column;grid-gap:.5rem;border-radius:4px}:root[dir=rtl] .excalidraw .picker-content{padding:.4rem}.excalidraw .picker-keybinding{position:absolute;bottom:2px;font-size:.7em;color:var(--keybinding-color)}:root[dir=ltr] .excalidraw .picker-keybinding{right:2px}:root[dir=rtl] .excalidraw .picker-keybinding{left:2px}@media(max-height:500px)and (max-width:1000px),(max-width:600px){.excalidraw .picker-keybinding{display:none}}.excalidraw .picker-type-canvasBackground .picker-keybinding{color:#aaa}.excalidraw .picker-type-elementBackground .picker-keybinding{color:#fff}.excalidraw .picker-swatch[aria-label=transparent] .picker-keybinding{color:#aaa}.excalidraw .picker-type-elementStroke .picker-keybinding{color:#d4d4d4}.excalidraw.Appearance_dark .picker-swatch[aria-label=transparent] .picker-keybinding,.excalidraw.Appearance_dark .picker-type-elementBackground .picker-keybinding{color:#000}.excalidraw .TextInput{color:var(--text-primary-color);display:inline-block;border:1.5px solid var(--button-gray-1);line-height:1;padding:.75rem;white-space:nowrap;border-radius:var(--space-factor);background-color:var(--input-bg-color)}.excalidraw .TextInput:not(:focus):hover{background-color:var(--input-hover-bg-color)}.excalidraw .TextInput:focus{outline:none;box-shadow:0 0 0 2px var(--focus-highlight-color)}.excalidraw .Tooltip{position:relative}.excalidraw .Tooltip__label{--arrow-size:4px;visibility:hidden;background:#000;color:#fff;text-align:center;border-radius:6px;padding:8px;position:absolute;z-index:10;font-size:13px;line-height:1.5;font-weight:500;left:calc(50% + var(--arrow-size)/2 - 1px);transform:translateX(-50%);word-wrap:break-word}.excalidraw .Tooltip__label:after{content:"";border:var(--arrow-size) solid transparent;position:absolute;left:calc(50% - var(--arrow-size))}.excalidraw .Tooltip__label--above{bottom:calc(100% + var(--arrow-size) + 3px)}.excalidraw .Tooltip__label--above:after{border-top-color:#000;top:100%}.excalidraw .Tooltip__label--below{top:calc(100% + var(--arrow-size) + 3px)}.excalidraw .Tooltip__label--below:after{border-bottom-color:#000;bottom:100%}.excalidraw .Tooltip:hover .Tooltip__label,.excalidraw .Tooltip__label:hover{visibility:visible}.excalidraw .Avatar{width:2.5rem;height:2.5rem;border-radius:1.25rem;display:flex;justify-content:center;align-items:center;color:#fff;cursor:pointer;font-size:.8rem;font-weight:500}.excalidraw .context-menu{position:relative;border-radius:4px;box-shadow:0 3px 10px rgba(0,0,0,.2);list-style:none;-webkit-user-select:none;user-select:none;margin:-.25rem 0 0 .125rem;padding:.5rem 0;background-color:var(--popup-secondary-bg-color);border:1px solid var(--button-gray-3);cursor:default}.excalidraw .context-menu button{color:var(--popup-text-color)}.excalidraw .context-menu-option{position:relative;width:100%;min-width:9.5rem;margin:0;padding:.25rem 1rem .25rem 1.25rem;text-align:start;border-radius:0;background-color:initial;border:none;white-space:nowrap;display:grid;grid-template-columns:1fr .2fr;align-items:center}.excalidraw .context-menu-option.checkmark:before{position:absolute;left:6px;margin-bottom:1px;content:"✓"}.excalidraw .context-menu-option.dangerous .context-menu-option__label{color:#f03e3e}.excalidraw .context-menu-option .context-menu-option__label{justify-self:start;margin-inline-end:20px}.excalidraw .context-menu-option .context-menu-option__shortcut{justify-self:end;opacity:.6;font-family:inherit;font-size:.7rem}.excalidraw .context-menu-option:hover{color:var(--popup-bg-color);background-color:var(--select-highlight-color)}.excalidraw .context-menu-option:hover.dangerous{background-color:#fa5252}.excalidraw .context-menu-option:hover.dangerous .context-menu-option__label{color:var(--popup-bg-color)}.excalidraw .context-menu-option:focus{z-index:1}@media(max-height:500px)and (max-width:1000px),(max-width:600px){.excalidraw .context-menu-option{display:block}.excalidraw .context-menu-option .context-menu-option__label{margin-inline-end:0}.excalidraw .context-menu-option .context-menu-option__shortcut{display:none}}.excalidraw .context-menu-option-separator{border:none;border-top:1px solid #adb5bd}.excalidraw .Stack{--gap:0;display:grid;grid-gap:calc(var(--space-factor)*var(--gap));gap:calc(var(--space-factor)*var(--gap))}.excalidraw .Stack_vertical{grid-template-columns:auto;grid-auto-flow:row;grid-auto-rows:-webkit-min-content;grid-auto-rows:min-content}.excalidraw .Stack_horizontal{grid-template-rows:auto;grid-auto-flow:column;grid-auto-columns:-webkit-min-content;grid-auto-columns:min-content}.excalidraw .CollabButton.is-collaborating{background-color:var(--button-special-active-bg-color)}.excalidraw .CollabButton.is-collaborating .ToolIcon__icon svg,.excalidraw .CollabButton.is-collaborating .ToolIcon__label{color:var(--icon-green-fill-color)}.excalidraw .CollabButton-collaborators{min-width:1em;position:absolute;bottom:-5px;padding:3px;border-radius:50%;background-color:#40c057;color:#fff;font-size:.7em;font-family:var(--ui-font)}:root[dir=ltr] .excalidraw .CollabButton-collaborators{right:-5px}:root[dir=rtl] .excalidraw .CollabButton-collaborators{left:-5px}.excalidraw .ExportDialog__preview{--preview-padding:calc(var(--space-factor)*4);background:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAMUlEQVQ4T2NkYGAQYcAP3uCTZhw1gGGYhAGBZIA/nYDCgBDAm9BGDWAAJyRCgLaBCAAgXwixzAS0pgAAAABJRU5ErkJggg==") 0;text-align:center;padding:var(--preview-padding);margin-bottom:calc(var(--space-factor)*3)}.excalidraw .ExportDialog__preview canvas{max-width:calc(100% - var(--preview-padding)*2);max-height:25rem}.excalidraw.Appearance_dark .ExportDialog__preview canvas{filter:none}.excalidraw .ExportDialog__actions{width:100%;display:flex;grid-gap:calc(var(--space-factor)*2);align-items:top;justify-content:space-between}.excalidraw .ExportDialog__name{grid-column:project-name;margin:auto}.excalidraw .ExportDialog__name .TextInput{height:calc(1rem - 3px)}@media(max-height:500px)and (max-width:1000px),(max-width:600px){.excalidraw .ExportDialog{display:flex;flex-direction:column}.excalidraw .ExportDialog__actions{flex-direction:column;align-items:center}.excalidraw .ExportDialog__actions>*{margin-bottom:calc(var(--space-factor)*3)}.excalidraw .ExportDialog__preview canvas{max-height:30vh}.excalidraw .ExportDialog__dialog,.excalidraw .ExportDialog__dialog .Island{height:100%;box-sizing:border-box}.excalidraw .ExportDialog__dialog .Island{overflow-y:auto}}.excalidraw .FixedSideContainer{--margin:0.25rem;position:absolute;pointer-events:none}.excalidraw .FixedSideContainer>*{pointer-events:all}.excalidraw .FixedSideContainer_side_top{left:var(--margin);top:var(--margin);right:var(--margin);z-index:2}.excalidraw .FixedSideContainer_side_top.zen-mode{right:42px}.excalidraw .HintViewer{pointer-events:none;box-sizing:border-box;position:absolute;display:flex;justify-content:center;left:0;top:100%;max-width:100%;width:100%;margin-top:6px;text-align:center;color:#868e96;font-size:.8rem}@media(max-height:500px)and (max-width:1000px),(max-width:600px){.excalidraw .HintViewer{position:static;padding-right:2em}}.excalidraw .HintViewer>span{padding:.2rem .4rem;background-color:var(--overlay-bg-color);border-radius:4px}.excalidraw .layer-ui__library{margin:auto;display:flex;align-items:center;justify-content:center}.excalidraw .layer-ui__library .layer-ui__library-header{display:flex;align-items:center;width:100%;margin:2px 0}.excalidraw .layer-ui__library .layer-ui__library-header button{margin:0 2px}.excalidraw .layer-ui__library .layer-ui__library-header a{margin-inline-start:auto;padding-inline-end:18px;white-space:nowrap}.excalidraw .layer-ui__library-message{padding:10px 20px;max-width:200px}.excalidraw .layer-ui__library-items{max-height:50vh;overflow:auto}.excalidraw .layer-ui__wrapper{z-index:var(--zIndex-layerUI)}.excalidraw .layer-ui__wrapper .encrypted-icon{position:relative;margin-inline-start:15px;display:flex;justify-content:center;align-items:center;border-radius:var(--space-factor);color:#2b8a3e}.excalidraw .layer-ui__wrapper .encrypted-icon svg{width:1.2rem;height:1.2rem}.excalidraw .layer-ui__wrapper__github-corner{top:0;position:absolute;width:40px}:root[dir=ltr] .excalidraw .layer-ui__wrapper__github-corner{right:0}:root[dir=rtl] .excalidraw .layer-ui__wrapper__github-corner{left:0}.excalidraw .layer-ui__wrapper__footer{position:absolute;z-index:100;bottom:0;width:190px}:root[dir=ltr] .excalidraw .layer-ui__wrapper__footer{right:0}:root[dir=rtl] .excalidraw .layer-ui__wrapper__footer{left:0}.excalidraw .layer-ui__wrapper .zen-mode-transition{transition:transform .5s ease-in-out}:root[dir=ltr] .excalidraw .layer-ui__wrapper .zen-mode-transition.transition-left{transform:translate(-999px)}:root[dir=ltr] .excalidraw .layer-ui__wrapper .zen-mode-transition.transition-right,:root[dir=rtl] .excalidraw .layer-ui__wrapper .zen-mode-transition.transition-left{transform:translate(999px)}:root[dir=rtl] .excalidraw .layer-ui__wrapper .zen-mode-transition.transition-right{transform:translate(-999px)}:root[dir=ltr] .excalidraw .layer-ui__wrapper .zen-mode-transition.App-menu_bottom--transition-left{transform:translate(-92px)}:root[dir=rtl] .excalidraw .layer-ui__wrapper .zen-mode-transition.App-menu_bottom--transition-left{transform:translate(92px)}.excalidraw .layer-ui__wrapper .disable-zen-mode{height:30px;position:absolute;bottom:10px;font-size:10px;padding:10px;font-weight:500;opacity:0;visibility:hidden;transition:visibility 0s linear 0s,opacity .5s}[dir=ltr] .excalidraw .layer-ui__wrapper .disable-zen-mode{right:15px}[dir=rtl] .excalidraw .layer-ui__wrapper .disable-zen-mode{left:15px}.excalidraw .layer-ui__wrapper .disable-zen-mode--visible{opacity:1;visibility:visible;transition:visibility 0s linear .3s,opacity .5s;transition-delay:.8s}.excalidraw .library-unit{align-items:center;border:1px solid var(--button-gray-2);display:flex;justify-content:center;position:relative;width:63px;height:63px}.excalidraw .library-unit__dragger{display:flex;height:100%;width:100%}.excalidraw .library-unit__dragger>svg{filter:var(--appearance-filter);flex-grow:1;max-height:100%;max-width:100%}.excalidraw .library-unit__removeFromLibrary,.excalidraw .library-unit__removeFromLibrary:active,.excalidraw .library-unit__removeFromLibrary:hover{align-items:center;background:none;border:none;color:var(--icon-fill-color);display:flex;justify-content:center;margin:0;padding:0;position:absolute;right:5px;top:5px}.excalidraw .library-unit__removeFromLibrary>svg{height:16px;width:16px}.excalidraw .library-unit__pulse{transform:scale(1);animation:library-unit__pulse-animation 1s ease-in infinite}.excalidraw .library-unit__adder{position:absolute;left:50%;top:50%;width:20px;height:20px;margin-left:-10px;margin-top:-10px;pointer-events:none}.excalidraw .library-unit__active{cursor:pointer}@keyframes library-unit__pulse-animation{0%{transform:scale(.95)}50%{transform:scale(1)}to{transform:scale(.95)}}.excalidraw .UserList{pointer-events:none;padding:var(--space-factor) 40px var(--space-factor) var(--space-factor);display:flex;flex-wrap:wrap;justify-content:flex-end}.excalidraw .UserList>*{pointer-events:all;margin:0 0 var(--space-factor) var(--space-factor)}.excalidraw .UserList_mobile{padding:0;justify-content:normal}.excalidraw .UserList_mobile>*{margin:0 var(--space-factor) var(--space-factor) 0}@media(max-height:500px)and (max-width:1000px),(max-width:600px){.excalidraw .PasteChartDialog .Island{display:flex;flex-direction:column}}.excalidraw .PasteChartDialog .container{display:flex;align-items:center;justify-content:space-around;flex-wrap:wrap}@media(max-height:500px)and (max-width:1000px),(max-width:600px){.excalidraw .PasteChartDialog .container{flex-direction:column;justify-content:center}}.excalidraw .PasteChartDialog .ChartPreview{margin:8px;text-align:center;width:192px;height:128px;border-radius:2px;padding:1px;border:1px solid #ced4da;display:flex;align-items:center;justify-content:center;background:transparent}.excalidraw .PasteChartDialog .ChartPreview div{display:inline-block}.excalidraw .PasteChartDialog .ChartPreview svg{max-height:120px;max-width:186px}.excalidraw .PasteChartDialog .ChartPreview:hover{padding:0;border:2px solid #339af0}.excalidraw .HelpDialog h3{border-bottom:1px solid var(--button-gray-2);padding-bottom:4px}.excalidraw .HelpDialog--island{border:1px solid var(--button-gray-2);margin-bottom:16px}.excalidraw .HelpDialog--island-title{margin:0;padding:4px;background-color:var(--button-gray-1);text-align:center}.excalidraw .HelpDialog--shortcut{border-top:1px solid var(--button-gray-2)}.excalidraw .HelpDialog--key{word-break:keep-all;border:1px solid var(--button-gray-2);padding:2px 8px;margin:auto 4px;background-color:var(--button-gray-1);border-radius:2px;font-size:.8em;min-height:26px;box-sizing:border-box;display:flex;align-items:center;font-family:inherit}.excalidraw .HelpDialog--header{display:flex;flex-direction:row;justify-content:space-evenly;margin-bottom:32px;padding-bottom:16px}.excalidraw .HelpDialog--btn{border:1px solid var(--link-color);padding:8px 32px;border-radius:4px}.excalidraw .HelpDialog--btn:hover{text-decoration:none}.excalidraw .Stats{position:absolute;top:64px;right:12px;font-size:12px;z-index:999}.excalidraw .Stats h3{margin:0 24px 8px 0;white-space:nowrap}.excalidraw .Stats .close{float:right;height:16px;width:16px;cursor:pointer}.excalidraw .Stats .close svg{width:100%;height:100%}.excalidraw .Stats table{width:100%}.excalidraw .Stats table th{border-bottom:1px solid var(--input-border-color);padding:4px}.excalidraw .Stats table tr td:nth-child(2){min-width:24px;text-align:right}:root[dir=rtl] .excalidraw .Stats{left:12px;right:auto}:root[dir=rtl] .excalidraw .Stats h3{margin:0 0 8px 24px}:root[dir=rtl] .excalidraw .Stats .close{float:left}.excalidraw .Toast{animation:fade-in .5s;background-color:var(--button-gray-1);border-radius:4px;bottom:10px;box-sizing:border-box;cursor:default;left:50%;margin-left:-150px;padding:4px 0;position:absolute;text-align:center;width:300px;z-index:999999}.excalidraw .Toast__message{color:var(--popup-text-color);white-space:pre-wrap}@keyframes fade-in{0%{opacity:0}to{opacity:1}}.visually-hidden{position:absolute!important;height:1px;width:1px;overflow:hidden;clip:rect(1px,1px,1px,1px);white-space:nowrap}.LoadingMessage{position:absolute;top:0;right:0;bottom:0;left:0;z-index:999;display:flex;align-items:center;justify-content:center;pointer-events:none}.LoadingMessage span{background-color:var(--button-gray-1);border-radius:5px;padding:.8em 1.2em;color:var(--popup-text-color);font-size:1.3em}.excalidraw{--appearance-filter:none;--button-destructive-bg-color:#ffe3e3;--button-destructive-color:#c92a2a;--button-gray-1:#e9ecef;--button-gray-2:#ced4da;--button-gray-3:#adb5bd;--button-special-active-bg-color:#ebfbee;--dialog-border-color:#868e96;--dropdown-icon:url('data:image/svg+xml;charset=utf-8,');--focus-highlight-color:#a5d8ff;--icon-fill-color:#000;--icon-green-fill-color:#2b8a3e;--input-bg-color:#fff;--input-border-color:#dee2e6;--input-hover-bg-color:#f1f3f5;--input-label-color:#495057;--island-bg-color:hsla(0,0%,100%,0.9);--keybinding-color:#adb5bd;--link-color:#1c7ed6;--overlay-bg-color:hsla(0,0%,100%,0.88);--popup-bg-color:#fff;--popup-secondary-bg-color:#f1f3f5;--popup-text-color:#000;--popup-text-inverted-color:#fff;--sab:env(safe-area-inset-bottom);--sal:env(safe-area-inset-left);--sar:env(safe-area-inset-right);--sat:env(safe-area-inset-top);--select-highlight-color:#339af0;--shadow-island:0 1px 5px rgba(0,0,0,0.15);--space-factor:0.25rem;--text-primary-color:#343a40}.excalidraw.Appearance_dark{background:#000}.excalidraw.Appearance_dark.Appearance_dark-background-none{background:none}.excalidraw.Appearance_dark{--appearance-filter:invert(93%) hue-rotate(180deg);--button-destructive-bg-color:#5a0000;--button-destructive-color:#ffa8a8;--button-gray-1:#363636;--button-gray-2:#272727;--button-gray-3:#222;--button-special-active-bg-color:#204624;--dialog-border-color:#212529;--dropdown-icon:url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='292.4' height='292.4' viewBox='0 0 292 292'%3E%3Cpath fill='%23ced4da' d='M287 197L159 69c-4-3-8-5-13-5s-9 2-13 5L5 197c-3 4-5 8-5 13s2 9 5 13c4 4 8 5 13 5h256c5 0 9-1 13-5s5-8 5-13-1-9-5-13z'/%3E%3C/svg%3E");--focus-highlight-color:#228be6;--icon-fill-color:#ced4da;--icon-green-fill-color:#69db7c;--input-bg-color:#121212;--input-border-color:#2e2e2e;--input-hover-bg-color:#181818;--input-label-color:#e9ecef;--island-bg-color:#1e1e1e;--keybinding-color:#868e96;--overlay-bg-color:rgba(52,58,64,0.12);--popup-bg-color:#2c2c2c;--popup-secondary-bg-color:#222;--popup-text-color:#ced4da;--popup-text-inverted-color:#2c2c2c;--select-highlight-color:#4dabf7;--shadow-island:0 1px 5px rgba(0,0,0,0.3);--text-primary-color:#ced4da}:root{--zIndex-canvas:1;--zIndex-wysiwyg:2;--zIndex-layerUI:3}.excalidraw{position:relative;overflow:hidden;color:var(--text-primary-color);display:flex;top:0;bottom:0;left:0;right:0}.excalidraw a{font-weight:500;text-decoration:none;color:var(--link-color)}.excalidraw a:hover{text-decoration:underline}.excalidraw canvas{touch-action:none;-webkit-user-select:none;user-select:none;image-rendering:pixelated;image-rendering:-moz-crisp-edges;z-index:var(--zIndex-canvas)}.excalidraw.Appearance_dark canvas{filter:var(--appearance-filter)}.excalidraw .FixedSideContainer{padding:var(--sat,0) var(--sar,0) var(--sab,0) var(--sal,0)}.excalidraw .panelRow{display:flex;justify-content:space-between}.excalidraw .panelColumn{display:flex;flex-direction:column}.excalidraw .panelColumn .control-label,.excalidraw .panelColumn h3,.excalidraw .panelColumn legend{margin-top:.333rem;margin-bottom:.333rem;font-size:.75rem;color:var(--text-primary-color);font-weight:700;display:block}.excalidraw .panelColumn .control-label input{display:block;width:100%}.excalidraw .panelColumn .control-label:first-child,.excalidraw .panelColumn h3:first-child,.excalidraw .panelColumn legend:first-child{margin-top:0}.excalidraw .panelColumn legend{padding:0}.excalidraw .panelColumn .iconSelectList{flex-wrap:wrap;position:relative}.excalidraw .panelColumn .buttonList{flex-wrap:wrap}.excalidraw .panelColumn .buttonList label{margin-right:.25rem;font-size:.75rem;display:inline-block}.excalidraw .panelColumn .buttonList input[type=button],.excalidraw .panelColumn .buttonList input[type=radio]{opacity:0;position:absolute;pointer-events:none}.excalidraw .panelColumn .buttonList .iconRow{margin-top:8px}.excalidraw .panelColumn .buttonList .ToolIcon{margin:0;margin-inline-end:8px}.excalidraw .panelColumn .buttonList .ToolIcon:focus{outline:transparent;box-shadow:0 0 0 2px var(--focus-highlight-color)}.excalidraw .panelColumn .buttonList .ToolIcon:hover{background-color:var(--button-gray-2)}.excalidraw .panelColumn .buttonList .ToolIcon:active{background-color:var(--button-gray-3)}.excalidraw .panelColumn .buttonList .ToolIcon:disabled{cursor:not-allowed}.excalidraw .panelColumn .buttonList .ToolIcon__icon{width:28px;height:28px}.excalidraw .panelColumn fieldset{margin:.333rem 0 0;padding:0;border:none}.excalidraw .divider{width:1px;background-color:#e9ecef;margin:1px}.excalidraw .buttonList label:focus-within,.excalidraw input:focus{outline:transparent;box-shadow:0 0 0 2px var(--focus-highlight-color)}.excalidraw .buttonList label,.excalidraw button{-webkit-user-select:none;user-select:none;background-color:var(--button-gray-1);border:0;border-radius:4px;margin:.125rem 0;padding:.25rem;white-space:nowrap;cursor:pointer}.excalidraw .buttonList label:focus,.excalidraw button:focus{outline:transparent;box-shadow:0 0 0 2px var(--focus-highlight-color)}.excalidraw .buttonList label:hover,.excalidraw button:hover{background-color:var(--button-gray-2)}.excalidraw .buttonList label:active,.excalidraw button:active{background-color:var(--button-gray-3)}.excalidraw .buttonList label:disabled,.excalidraw button:disabled{cursor:not-allowed}.excalidraw .active,.excalidraw .active:hover,.excalidraw .buttonList label.active,.excalidraw .buttonList label.active:hover{background-color:var(--button-gray-2)}.excalidraw .active:active,.excalidraw .buttonList label.active:active{background-color:var(--button-gray-3)}.excalidraw .buttonList.buttonListIcon label{display:inline-flex;justify-content:center;align-items:center}.excalidraw .buttonList.buttonListIcon label svg{width:36px;height:18px;opacity:.6}.excalidraw .buttonList.buttonListIcon label.active svg{opacity:1}.excalidraw .App-top-bar{z-index:var(--zIndex-layerUI);display:flex;flex-direction:column;align-items:center}.excalidraw .App-bottom-bar{position:absolute;top:0;bottom:0;left:0;right:0;--bar-padding:calc(var(--space-factor)*4);padding:max(var(--bar-padding),var(--sat,0)) var(--sar,0) var(--sab,0) var(--sal,0);z-index:4;display:flex;align-items:flex-end;pointer-events:none}.excalidraw .App-bottom-bar>.Island{width:100%;max-width:100%;min-width:100%;box-sizing:border-box;max-height:100%;display:flex;flex-direction:column;pointer-events:auto}.excalidraw .App-bottom-bar>.Island .panelColumn{padding:8px 8px 0}.excalidraw .App-toolbar{width:100%;box-sizing:border-box}.excalidraw .App-toolbar-content{display:flex;align-items:center;justify-content:space-between;padding:8px}.excalidraw .App-mobile-menu{width:100%;overflow-x:visible;overflow-y:auto;box-sizing:border-box;margin-bottom:var(--bar-padding)}.excalidraw .App-menu{display:grid;color:var(--icon-fill-color)}.excalidraw .App-menu_top{grid-template-columns:1fr auto 1fr;grid-gap:4px;align-items:flex-start;cursor:default;pointer-events:none!important}.excalidraw .layer-ui__wrapper:not(.disable-pointerEvents) .App-menu_top>*{pointer-events:all}.excalidraw .App-menu_top>:first-child{justify-self:flex-start}.excalidraw .App-menu_top>:last-child{justify-self:flex-end}.excalidraw .App-menu_bottom{position:absolute;bottom:0;grid-template-columns:1fr auto 1fr;grid-gap:4px;align-items:flex-start;cursor:default;pointer-events:none!important;z-index:100}:root[dir=ltr] .excalidraw .App-menu_bottom{left:.25rem}:root[dir=rtl] .excalidraw .App-menu_bottom{right:.25rem}.excalidraw .App-menu_bottom--transition-left section{width:185px}.excalidraw .App-menu_bottom section{display:flex}.excalidraw .layer-ui__wrapper:not(.disable-pointerEvents) .App-menu_bottom>*{pointer-events:all}.excalidraw .App-menu_bottom>:first-child{justify-self:flex-start}.excalidraw .App-menu_bottom>:last-child{justify-self:flex-end}.excalidraw .App-menu_left{grid-template-rows:1fr auto 1fr;height:100%}.excalidraw .App-menu_right{grid-template-rows:1fr;height:100%}.excalidraw .App-menu__left{overflow-y:auto}.excalidraw .dropdown-select{height:1.5rem;padding:0;padding-inline-start:.5rem;padding-inline-end:1.5rem;color:var(--icon-fill-color);background-color:var(--button-gray-1);border-radius:var(--space-factor);border:1px solid var(--button-gray-2);font-size:.8rem;outline:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-image:var(--dropdown-icon);background-repeat:no-repeat;background-position:right .7rem top 50%,0 0;background-size:.65em auto,100%}:root[dir=rtl] .excalidraw .dropdown-select{background-position:left .7rem top 50%,0 0}.excalidraw .dropdown-select:focus{box-shadow:0 0 0 2px var(--focus-highlight-color)}.excalidraw .dropdown-select:active,.excalidraw .dropdown-select:hover{background-color:var(--button-gray-2)}.excalidraw .dropdown-select.dropdown-select--floating{position:absolute;margin:.5em}.excalidraw .dropdown-select__language.dropdown-select--floating{position:absolute;bottom:10px}:root[dir=ltr] .excalidraw .dropdown-select__language.dropdown-select--floating{right:44px}:root[dir=rtl] .excalidraw .dropdown-select__language.dropdown-select--floating{left:44px}.excalidraw .zIndexButton{margin:0;margin-inline-end:8px;padding:5px;display:inline-flex;align-items:center;justify-content:center}.excalidraw .zIndexButton svg{width:18px;height:18px}.excalidraw .scroll-back-to-content{color:var(--popup-text-color);position:absolute;left:50%;bottom:30px;transform:translateX(-50%);padding:10px 20px}.excalidraw .help-icon{position:absolute;cursor:pointer;fill:#868e96;bottom:14px;width:1.5rem}:root[dir=ltr] .excalidraw .help-icon{right:14px}:root[dir=rtl] .excalidraw .help-icon{left:14px}@media(max-height:500px)and (max-width:1000px),(max-width:600px){.excalidraw aside{display:none}.excalidraw .scroll-back-to-content{bottom:calc(80px + var(--sab, 0));z-index:-1}}:root[dir=rtl] .excalidraw .rtl-mirror{transform:scaleX(-1)}.excalidraw .github-corner{position:absolute;top:0;z-index:2}:root[dir=ltr] .excalidraw .github-corner{right:0}:root[dir=rtl] .excalidraw .github-corner{left:0}.excalidraw .zen-mode-visibility{visibility:visible;opacity:1;height:auto;width:auto;transition:opacity .5s}.excalidraw .zen-mode-visibility.zen-mode-visibility--hidden{visibility:hidden;opacity:0;height:0;width:0;transition:opacity .5s}.excalidraw .disable-pointerEvents{pointer-events:none!important}.excalidraw.excalidraw--view-mode .App-menu{display:flex;justify-content:space-between}@media print{.excalidraw .App-bottom-bar,.excalidraw .FixedSideContainer,.excalidraw .layer-ui__wrapper{display:none}}.ErrorSplash.excalidraw{min-height:100vh;padding:20px 0;overflow:auto;display:flex;align-items:center;justify-content:center;-webkit-user-select:text;user-select:text}.ErrorSplash.excalidraw .ErrorSplash-messageContainer{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px;background-color:#ffe3e3;border:3px solid #c92a2a}.ErrorSplash.excalidraw .ErrorSplash-paragraph{margin:15px 0;max-width:600px}.ErrorSplash.excalidraw .ErrorSplash-paragraph.align-center{text-align:center}.ErrorSplash.excalidraw .bigger,.ErrorSplash.excalidraw .bigger button{font-size:1.1em}.ErrorSplash.excalidraw .smaller,.ErrorSplash.excalidraw .smaller button{font-size:.9em}.ErrorSplash.excalidraw .ErrorSplash-details{display:flex;flex-direction:column;align-items:flex-start}.ErrorSplash.excalidraw .ErrorSplash-details textarea{width:100%;margin:10px 0;font-family:"Cascadia";font-size:.8em}:export{isMobileQuery:(max-width:600px),(max-height:500px) and (max-width:1000px);appearanceFilter:invert(93%) hue-rotate(180deg)}.excalidraw .RoomDialog-linkContainer{display:flex;margin:1.5em 0}.excalidraw .RoomDialog-link{color:var(--text-primary-color);min-width:0;flex:1 1 auto;margin-inline-start:1em;display:inline-block;cursor:pointer;border:none;height:2.5rem;line-height:2.5rem;padding:0 .5rem;white-space:nowrap;border-radius:var(--space-factor);background-color:var(--button-gray-1)}.excalidraw .RoomDialog-emoji{font-family:sans-serif}.excalidraw .RoomDialog-usernameContainer{margin:1.5em 0;display:flex;align-items:center;justify-content:center}@media(max-height:500px)and (max-width:1000px),(max-width:600px){.excalidraw .RoomDialog-usernameContainer{flex-direction:column;align-items:stretch}}@media(max-height:500px)and (max-width:1000px),(max-width:600px){.excalidraw .RoomDialog-usernameLabel{font-weight:700}}.excalidraw .RoomDialog-username{background-color:var(--input-bg-color);border-color:var(--input-border-color);-webkit-appearance:none;-moz-appearance:none;appearance:none;min-width:0;flex:1 1 auto;margin-inline-start:1em;height:2.5rem;font-size:1em;line-height:1.5;padding:0 .5rem}@media(max-height:500px)and (max-width:1000px),(max-width:600px){.excalidraw .RoomDialog-username{margin-top:.5em;margin-inline-start:0}}.excalidraw .RoomDialog-sessionStartButtonContainer{display:flex;justify-content:center}.excalidraw .Modal .RoomDialog-stopSession{background-color:var(--button-destructive-bg-color)}.excalidraw .Modal .RoomDialog-stopSession .ToolIcon__icon svg,.excalidraw .Modal .RoomDialog-stopSession .ToolIcon__label{color:var(--button-destructive-color)}
+/*# sourceMappingURL=main.b0bde4ac.chunk.css.map */
\ No newline at end of file
diff --git a/resources/css/fonts.css b/resources/css/fonts.css
new file mode 100644
index 000000000..d78847d10
--- /dev/null
+++ b/resources/css/fonts.css
@@ -0,0 +1,13 @@
+/* http://www.eaglefonts.com/fg-virgil-ttf-131249.htm */
+@font-face {
+ font-family: "Virgil";
+ src: url("../fonts/Virgil.woff2");
+ font-display: swap;
+}
+
+/* https://github.com/microsoft/cascadia-code */
+@font-face {
+ font-family: "Cascadia";
+ src: url("../fonts/Cascadia.woff2");
+ font-display: swap;
+}
diff --git a/resources/css/style.css b/resources/css/style.css
index da0a45cfa..3a6e60b31 100644
--- a/resources/css/style.css
+++ b/resources/css/style.css
@@ -2,6 +2,7 @@
@import "./inter.css";
@import "./reveal.min.css";
@import "./reveal_black.min.css";
+@import "./fonts.css";
@import "./excalidraw.min.css";
@import "./katex.min.css";
@import "./codemirror.min.css";
diff --git a/resources/css/style.dev.css b/resources/css/style.dev.css
index 7d9a95075..39523d6d0 100644
--- a/resources/css/style.dev.css
+++ b/resources/css/style.dev.css
@@ -2,6 +2,7 @@
@import "./inter.css";
@import "./reveal.min.css";
@import "./reveal_black.min.css";
+@import "./fonts.css";
@import "./excalidraw.min.css";
@import "./katex.min.css";
@import "./codemirror.min.css";
@@ -10,4 +11,4 @@
@import "./datepicker.css";
@import "./highlight.css";
@import "./tailwind.core.css"; /* Build by gulp. Check `_buildTailwind` for more detail */
-@import "./common.css";
\ No newline at end of file
+@import "./common.css";
diff --git a/resources/css/Cascadia.woff2 b/resources/fonts/Cascadia.woff2
similarity index 100%
rename from resources/css/Cascadia.woff2
rename to resources/fonts/Cascadia.woff2
diff --git a/resources/css/FG_Virgil.woff2 b/resources/fonts/FG_Virgil.woff2
similarity index 100%
rename from resources/css/FG_Virgil.woff2
rename to resources/fonts/FG_Virgil.woff2
diff --git a/resources/fonts/Virgil.woff2 b/resources/fonts/Virgil.woff2
new file mode 100644
index 000000000..1d61d3fa2
Binary files /dev/null and b/resources/fonts/Virgil.woff2 differ
diff --git a/shadow-cljs.edn b/shadow-cljs.edn
index 32fea3cc0..006f6078a 100644
--- a/shadow-cljs.edn
+++ b/shadow-cljs.edn
@@ -12,6 +12,9 @@
:depends-on #{:main}}
:age-encryption
{:entries [frontend.extensions.age-encryption]
+ :depends-on #{:main}}
+ :excalidraw
+ {:entries [frontend.extensions.excalidraw]
:depends-on #{:main}}}
:output-dir "./static/js"
diff --git a/src/main/frontend/commands.cljs b/src/main/frontend/commands.cljs
index f4a659a77..5ab2989f6 100644
--- a/src/main/frontend/commands.cljs
+++ b/src/main/frontend/commands.cljs
@@ -8,7 +8,9 @@
[goog.dom :as gdom]
[goog.object :as gobj]
[frontend.format :as format]
- [frontend.handler.common :as common-handler]))
+ [frontend.handler.common :as common-handler]
+ [frontend.handler.draw :as draw]
+ [promesa.core :as p]))
;; TODO: move to frontend.handler.editor.commands
@@ -102,10 +104,13 @@
["Scheduled" [[:editor/clear-current-slash]
[:editor/show-date-picker]]]
["Query" [[:editor/input "{{query }}" {:backward-pos 2}]]]
- ["Draw" [[:editor/input "/draw "]
- [:editor/show-input [{:command :draw
- :id :title
- :placeholder "Draw title"}]]]]
+ ["Draw" (fn []
+ (let [file (draw/file-name)
+ path (str config/default-draw-directory "/" file)
+ text (util/format "[[%s]]" path)]
+ (p/let [_ (draw/create-draw-with-default-content path)]
+ (println "draw file created, " path))
+ text))]
["WAITING" (->marker "WAITING")]
["CANCELED" (->marker "CANCELED")]
["Tomorrow" #(get-page-ref-text (date/tomorrow))]
diff --git a/src/main/frontend/components/block.cljs b/src/main/frontend/components/block.cljs
index 30043e761..8eaeeb0e8 100644
--- a/src/main/frontend/components/block.cljs
+++ b/src/main/frontend/components/block.cljs
@@ -16,7 +16,6 @@
[goog.dom :as gdom]
[frontend.handler.expand :as expand]
[frontend.components.svg :as svg]
- [frontend.components.draw :as draw]
[frontend.components.datetime :as datetime-comp]
[frontend.ui :as ui]
[frontend.handler.editor :as editor-handler]
@@ -48,7 +47,8 @@
[frontend.commands :as commands]
[lambdaisland.glogi :as log]
[frontend.context.i18n :as i18n]
- [frontend.template :as template]))
+ [frontend.template :as template]
+ [shadow.loader :as loader]))
;; TODO: remove rum/with-context because it'll make reactive queries not working
@@ -385,32 +385,41 @@
full-path (.. util/node-path (join repo-path (config/get-local-asset-absolute-path path)))]
[:a.asset-ref {:target "_blank" :href full-path} (or title path)]))
+(defonce excalidraw-loaded? (atom false))
+(rum/defc excalidraw < rum/reactive
+ {:init (fn [state]
+ (p/let [_ (loader/load :excalidraw)]
+ (reset! excalidraw-loaded? true))
+ state)}
+ [file]
+ (let [loaded? (rum/react excalidraw-loaded?)
+ draw-component (if loaded?
+ (resolve 'frontend.extensions.excalidraw/draw))]
+ (when draw-component
+ (draw-component {:file file}))))
+
(rum/defc page-reference < rum/reactive
[html-export? s config label]
(let [show-brackets? (state/show-brackets?)
nested-link? (:nested-link? config)
- contents-page? (= "contents" (string/lower-case (str (:id config))))]
- [:span.page-reference
- (when (and (or show-brackets? nested-link?)
- (not html-export?)
- (not contents-page?))
- [:span.text-gray-500.bracket "[["])
- (if (string/ends-with? s ".excalidraw")
- [:a.page-ref
- {:on-click (fn [e]
- (util/stop e)
- (set! (.-href js/window.location)
- (rfe/href :draw nil {:file (string/replace s (str config/default-draw-directory "/") "")})))}
- [:span
- (svg/excalidraw-logo)
- (string/capitalize (draw/get-file-title s))]]
+ contents-page? (= "contents" (string/lower-case (str (:id config))))
+ draw? (string/ends-with? s ".excalidraw")]
+ (if (string/ends-with? s ".excalidraw")
+ [:div.draw {:on-click (fn [e]
+ (.stopPropagation e))}
+ (excalidraw s)]
+ [:span.page-reference
+ (when (and (or show-brackets? nested-link?)
+ (not html-export?)
+ (not contents-page?))
+ [:span.text-gray-500.bracket "[["])
(page-cp (assoc config
:label (mldoc/plain->text label)
- :contents-page? contents-page?) {:page/name s}))
- (when (and (or show-brackets? nested-link?)
- (not html-export?)
- (not contents-page?))
- [:span.text-gray-500.bracket "]]"])]))
+ :contents-page? contents-page?) {:page/name s})
+ (when (and (or show-brackets? nested-link?)
+ (not html-export?)
+ (not contents-page?))
+ [:span.text-gray-500.bracket "]]"])])))
(defn- latex-environment-content
[name option content]
diff --git a/src/main/frontend/components/draw.cljs b/src/main/frontend/components/draw.cljs
deleted file mode 100644
index 1ff7e2d4c..000000000
--- a/src/main/frontend/components/draw.cljs
+++ /dev/null
@@ -1,497 +0,0 @@
-(ns frontend.components.draw
- (:require [rum.core :as rum]
- [goog.object :as gobj]
- [frontend.rum :as r]
- [frontend.util :as util :refer-macros [profile]]
- [frontend.mixins :as mixins]
- [frontend.storage :as storage]
- [frontend.components.svg :as svg]
- [cljs-bean.core :as bean]
- [dommy.core :as d]
- [clojure.string :as string]
- [frontend.handler.notification :as notification]
- [frontend.handler.draw :as draw :refer
- [*files
- *current-file
- *current-title
- *file-loading?
- *elements
- *unsaved?
- *search-files
- *saving-title
- *excalidraw]]
- [frontend.handler.file :as file]
- [frontend.ui :as ui]
- [frontend.loader :as loader]
- [frontend.config :as config]
- [frontend.state :as state]
- [frontend.search :as search]
- [frontend.components.repo :as repo]
- [promesa.core :as p]
- [reitit.frontend.easy :as rfe]))
-
-(defn loaded? []
- js/window.Excalidraw)
-
-(defonce *loaded? (atom false))
-
-(defonce draw-state :draw-state)
-
-(defn get-draw-state []
- (storage/get draw-state))
-(defn set-draw-state! [value]
- (storage/set draw-state value))
-(defn get-k
- ([k]
- (get-k k (state/get-current-repo)))
- ([repo k]
- (when repo
- (get-in (get-draw-state) [repo k]))))
-
-(defn set-k
- [k v]
- (when-let [repo (state/get-current-repo)]
- (let [state (get-draw-state)]
- (let [new-state (assoc-in state [repo k] v)]
- (set-draw-state! new-state)))))
-
-(defn get-last-file
- ([]
- (get-k :last-file))
- ([repo]
- (get-k repo :last-file)))
-
-(defn get-last-title
- ([]
- (get-k :last-title))
- ([repo]
- (get-k repo :last-title)))
-
-(defn set-last-file!
- [value]
- (set-k :last-file value))
-(defn set-last-title!
- [value]
- (set-k :last-title value))
-
-(defn get-last-elements
- []
- (storage/get-json (str (state/get-current-repo) "-" "last-elements")))
-(defn get-last-app-state
- []
- (storage/get-json (str (state/get-current-repo) "-" "last-app-state")))
-
-(defn set-last-elements!
- [value]
- (storage/set-json (str (state/get-current-repo) "-" "last-elements") value))
-(defn set-last-app-state!
- [value]
- (storage/set-json (str (state/get-current-repo) "-" "last-app-state") value))
-
-(defn set-excalidraw-component!
- []
- (reset! *excalidraw (r/adapt-class
- (gobj/get js/window.Excalidraw "default"))))
-
-(defn serialize-as-json
- [elements app-state]
- (when (loaded?)
- (when-let [f (gobj/get js/window.Excalidraw "serializeAsJSON")]
- (f elements app-state))))
-
-;; api restore
-
-(defn from-json
- [text]
- (when-not (string/blank? text)
- (try
- (when-let [data (js/JSON.parse text)]
- (if (not= "excalidraw" (gobj/get data "type"))
- (notification/show!
- (util/format "Could not load this invalid excalidraw file")
- :error)
- {:elements (gobj/get data "elements")
- :app-state (gobj/get data "appState")}))
- (catch js/Error e
- (prn "from json error:")
- (js/console.dir e)
- (notification/show!
- (util/format "Could not load this invalid excalidraw file")
- :error)))))
-
-(defn get-file-title
- [file]
- (when file
- (let [s (subs file 20)
- title (string/replace s ".excalidraw" "")]
- (string/replace title "-" " "))))
-
-(defn save-excalidraw!
- [state _event file ok-handler]
- (let [title @*current-title]
- (cond
- (string/blank? title)
- (do
- (reset! *saving-title nil)
- (notification/show!
- "Please specify a title first!"
- :error)
- ;; TODO: focus the title input
-)
-
- (= title @*saving-title)
- nil
-
- :else
- (when-let [elements (get-last-elements)]
- (reset! *saving-title title)
- (let [app-state (get-last-app-state)
- [option] (:rum/args state)
- file (util/trim-safe
- (or
- file
- @*current-file
- (:file option)
- (draw/title->file-name title)))
- data (serialize-as-json elements app-state)]
- (when file
- (draw/save-excalidraw! file data
- (fn [file]
- (reset! *files
- (distinct (conj @*files file)))
- (reset! *current-file file)
- (reset! *unsaved? false)
- (set-last-file! file)
- (when ok-handler (ok-handler file))
- (reset! *saving-title nil)))))))))
-
-(defn- clear-canvas!
- []
- (when-let [canvas (d/by-id "canvas")]
- (let [context (.getContext canvas "2d")]
- (.clearRect context 0 0 (gobj/get canvas "width") (gobj/get canvas "height"))
- (set! (.-fillStyle context) "#FFF")
- (.fillRect context 0 0 (gobj/get canvas "width") (gobj/get canvas "height")))))
-
-(defn- new-file!
- []
- ;; TODO: save current firstly
- (clear-canvas!)
- (reset! *current-title "")
- (reset! *current-file nil)
- (reset! *elements nil)
- (set-last-elements! nil)
- (set-last-title! nil)
- (set-last-file! nil)
- (set-last-app-state! nil))
-
-(defn- rename-file!
- [file new-title]
- (when-not (string/blank? new-title)
- (let [new-file (draw/title->file-name new-title)]
- (when-not (= (string/trim file) (string/trim new-file))
- (save-excalidraw!
- {} {} new-file
- (fn []
- (set-last-file! new-file)
- (util/p-handle
- (file/remove-file!
- (state/get-current-repo)
- (str config/default-draw-directory "/" file))
- (fn [_]
- (reset! *files (->> (conj @*files new-file)
- (remove #(= file %))
- distinct
- (vec)))
- (reset! *current-file new-file)
- (notification/show!
- "File was renamed successfully!"
- :success))
- (fn [error]
- (println "Rename file failed, reason: ")
- (js/console.dir error)))))))))
-
-(rum/defc draw-title < rum/reactive
- (mixins/event-mixin
- (fn [state]
- (let [old-title @*current-title]
- (mixins/hide-when-esc-or-outside
- state
- :on-hide (fn [state e event]
- (let [title (and @*current-title (string/trim @*current-title))
- file @*current-file]
- (when (or
- (string/blank? old-title)
- (not= (string/trim old-title) title))
- (cond
- (and file (not (string/blank? title)))
- (rename-file! file title)
-
- (and (not file)
- (not (string/blank? title))
- (seq @*elements)) ; new file
- (save-excalidraw! {} {} nil nil)
-
- :else
- nil))))))
- state))
- []
- (let [current-title (rum/react *current-title)]
- [:input#draw-title.font-medium.w-48.px-2.py-1.ml-2
- {:on-click (fn [e]
- (util/stop e))
- :placeholder "Untitled"
- :auto-complete "off"
- :default-value (or (and current-title (string/capitalize current-title)) "")
- :on-change (fn [e]
- (when-let [value (util/evalue e)]
- (set-last-title! value)
- (reset! *current-title value)))}]))
-
-(rum/defc files-search < rum/reactive
- [state]
- [:div#search-wrapper.relative.w-full.text-gray-400.focus-within:text-gray-600
- [:div.absolute.inset-y-0.flex.items-center.pointer-events-none.left-3
- [:svg.h-4.w-4
- {:view-box "0 0 20 20", :fill "currentColor"}
- [:path
- {:d
- "M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z",
- :clip-rule "evenodd",
- :fill-rule "evenodd"}]]]
- [:input.block.w-full.pl-2.sm:text-sm.sm:leading-3.mb-2.mt-2.border-none.outline-none.focus:outline-none
- {:style {:padding-left "2rem"
- :border-radius 0}
- :placeholder "Search"
- :auto-complete "off"
- :on-change (fn [e]
- (let [value (util/evalue e)
- files @*files]
- (reset! *search-files
- (if (string/blank? value)
- files
- (search/fuzzy-search files value :limit 10)))))}]])
-
-(rum/defcs save-button < rum/reactive
- [state]
- (let [unsaved? (rum/react *unsaved?)]
- [:a.ml-2 {:title (if unsaved? "Save changes" "Save")
- :on-click (fn [e]
- (save-excalidraw! state e nil nil))}
- [:div.ToolIcon__icon {:class (if unsaved? "bg-orange-400" "bg-gray-200")
- :style {:width "2rem"
- :height "2rem"}}
- svg/save]]))
-
-(rum/defcs files < rum/reactive
- [state]
- (let [all-files (rum/react *files)
- search-files (rum/react *search-files)
- files (if (seq search-files) search-files all-files)
- current-file (rum/react *current-file)
- unsaved? (rum/react *unsaved?)]
- [:div.flex-row.flex.items-center
- [:a.ml-2 {:title "New file"
- :on-click new-file!}
- [:div.ToolIcon__icon.bg-gray-200 {:style {:width "2rem"
- :height "2rem"}}
- svg/plus]]
-
- (ui/dropdown-with-links
- (fn [{:keys [toggle-fn]}]
- [:div.ToolIcon__icon.ml-2.cursor.bg-gray-200 {:title "List files"
- :on-click toggle-fn
- :style {:width "2rem"
- :height "2rem"}}
- svg/folder])
- (mapv
- (fn [file]
- {:title (get-file-title file)
- :options {:title file
- :on-click
- (fn [e]
- (util/stop e)
- (set-last-file! file)
- (reset! *current-file file)
- (reset! *current-title (get-file-title file))
- (reset! *search-files []))}})
- files)
- {:modal-class (util/hiccup->class
- "origin-top-right.absolute.left-0.mt-2.rounded-md.shadow-lg.whitespace-no-wrap.bg-white.w-48.dropdown-overflow-auto")
- :links-header (when (>= (count all-files) 5)
- (files-search))})
-
- (save-button)
-
- (let [links (->> [(when @*current-file
- {:title "Delete"
- :options {:style {:color "#db1111"}
- :on-click (fn [e]
- (util/stop e)
- (when-let [current-file @*current-file]
- (p/let [_ (file/remove-file! (state/get-current-repo)
- (str config/default-draw-directory "/" current-file))]
- (reset! *files (remove #(= current-file %) @*files))
- (new-file!))))}})]
- (remove nil?))]
- (when (seq links)
- (ui/dropdown-with-links
- (fn [{:keys [toggle-fn]}]
- [:div.ToolIcon__icon.ml-2.cursor.bg-gray-200
- {:title "More options"
- :on-click toggle-fn
- :style {:width "2rem"
- :height "2rem"}}
- (svg/vertical-dots nil)])
- links
- {:modal-class (util/hiccup->class
- "origin-top-right.absolute.left-0.mt-2.rounded-md.shadow-lg.whitespace-no-wrap.bg-white.w-48.dropdown-overflow-auto")})))
-
- (draw-title)]))
-
-(defn- set-canvas-actions-style!
- [state]
- (when-let [section (first (d/by-tag "section"))]
- (when (= "canvasActions-title" (d/attr section "aria-labelledby"))
- (d/set-style! section "margin-top" "48px")))
- state)
-
-(rum/defcs draw-inner < rum/reactive
- (mixins/keyboard-mixin (util/->system-modifier "ctrl+s")
- (fn [state e]
- (save-excalidraw! state e nil nil)))
- (mixins/keyboard-mixin "alt+z" set-canvas-actions-style!)
- {:init (fn [state]
- (reset! *elements nil)
- (let [[option] (:rum/args state)
- file (or @*current-file
- (:file option))]
- (do
- (reset! *current-title (get-file-title file))
- (set-last-file! file))
- (cond
- file
- (do
- (reset! *file-loading? true)
- (draw/load-excalidraw-file
- file
- (fn [data]
- (let [{:keys [elements app-state]} (from-json data)]
- (reset! *elements elements)
- (reset! *file-loading? false)))))
-
- :else
- (when-let [elements (get-last-elements)]
- ;; TODO: keep this for history undo
- (reset! *elements (remove #(gobj/get % "isDeleted") elements))))
- (assoc state
- ::layout (atom [js/window.innerWidth js/window.innerHeight]))))
- :did-mount set-canvas-actions-style!
- :did-update set-canvas-actions-style!}
- [state option]
- (let [current-repo (state/sub :git/current-repo)
- elements (rum/react *elements)
- loading? (rum/react *file-loading?)
- file (rum/react *current-file)
- layout (get state ::layout)
- [width height] (rum/react layout)
- options (bean/->js {:zenModeEnabled true
- :viewBackgroundColor "#FFF"})
- excalidraw-component @*excalidraw]
- [:div.draw.white-theme {:style {:background "#FFF"}}
- (when (and (or (and file elements)
- (nil? file))
- excalidraw-component)
- (excalidraw-component
- {:width (get option :width width)
- :height (get option :height height)
- :on-resize (fn []
- (reset! layout [js/window.innerWidth js/window.innerHeight]))
-
- :on-change (or (:on-change option)
- (fn [elements state]
- (when (not= (bean/->clj elements)
- (bean/->clj @*elements))
- (reset! *unsaved? true))
- (set-last-elements! elements)
- (set-last-app-state! state)
- (reset! *elements elements)))
- :options options
- :user (bean/->js {:name (or (:user-name option)
- (:name (state/get-me))
- (util/unique-id))})
- :on-username-change (fn [])
- :initial-data (or elements #js [])}))
- [:div.absolute.top-4.left-4.hidden.md:block
- [:div.flex.flex-row.items-center
- [:a.mr-3.opacity-70.hover:opacity-100 {:href (rfe/href :home)
- :title "Back to logseq"}
- (svg/logo false)]
- (files)
- (when loading?
- svg/loading)]]
- (ui/notification)
-
- (when current-repo
- [:div.absolute.top-4.right-4.hidden.md:block
- [:div.flex.flex-row.items-center
- (repo/sync-status current-repo)
- (repo/repos-dropdown true
- (fn [repo]
- (reset! *current-file (get-last-file repo))))]])]))
-
-(rum/defcs draw-2 < rum/reactive
- {:init (fn [state]
- (let [repo (storage/get :git/current-repo)]
-
- (let [current-title (get-last-title repo)]
- (reset! *current-title current-title))
- (let [current-file (or
- (get-in (first (:rum/args state))
- [:query-params :file])
- (get-last-file repo))]
- (reset! *current-file current-file)
- (reset! *current-title (get-file-title current-file))))
-
- (if (loaded?)
- (set-excalidraw-component!)
- (loader/load
- (config/asset-uri "/static/js/excalidraw.min.js")
- (fn []
- (reset! *loaded? true)
- (set-excalidraw-component!))))
-
- (draw/get-all-excalidraw-files
- (fn [files]
- (reset! *files (distinct files))))
-
- (state/set-draw! true)
- state)
- :will-unmount (fn [state]
- (state/set-draw! false)
- state)}
- [state option]
- (let [loaded? (or (loaded?)
- (rum/react *loaded?))
- current-repo (state/sub :git/current-repo)
- component (rum/react *excalidraw)]
- (if component
- (let [current-file (rum/react *current-file)
- current-file (or current-file
- (and current-repo
- (get-last-file current-repo)))]
- (let [key (if current-repo
- (str current-repo "-"
- (or (and current-file (str "draw-" current-file))
- "draw-with-no-file"))
- "draw-with-no-file")]
- (rum/with-key (draw-inner option) key)))
- [:div.center svg/loading])))
-
-(rum/defc draw < rum/reactive
- [option]
- (let [db-restoring? (state/sub :db/restoring?)]
- (if db-restoring?
- [:div.ls-center
- (ui/loading "Loading")]
- (draw-2 option))))
diff --git a/src/main/frontend/components/draw.css b/src/main/frontend/components/draw.css
deleted file mode 100644
index bb62ea316..000000000
--- a/src/main/frontend/components/draw.css
+++ /dev/null
@@ -1,23 +0,0 @@
-#draw {
- -webkit-app-region: no-drag;
- overflow: hidden;
-}
-
-#draw iframe {
- width: 100%;
- height: 100%;
- border: none;
-}
-
-.draw {
- display: flex;
- position: fixed;
- top: 0;
- bottom: 0;
- left: 0;
- right: 0;
-}
-
-.excalidraw-embed .draw {
- position: relative;
-}
\ No newline at end of file
diff --git a/src/main/frontend/components/sidebar.cljs b/src/main/frontend/components/sidebar.cljs
index 3f09dd9c3..d81bade7d 100644
--- a/src/main/frontend/components/sidebar.cljs
+++ b/src/main/frontend/components/sidebar.cljs
@@ -131,7 +131,7 @@
(ui/loading (t :loading))]]
:else
- [:div {:style {:margin-bottom (if global-graph-pages? 0 120)}}
+ [:div.max-w-7xl.mx-auto {:style {:margin-bottom (if global-graph-pages? 0 120)}}
main-content])]]
(right-sidebar/sidebar)]))
@@ -176,7 +176,7 @@
preferred-format (state/sub [:me :preferred_format])
logged? (:name me)]
(rum/with-context [[t] i18n/*tongue-context*]
- [:div.max-w-7xl.mx-auto
+ [:div
(cond
(and default-home
(= :home (state/get-current-route))
diff --git a/src/main/frontend/encrypt.cljs b/src/main/frontend/encrypt.cljs
index 5fef0a5c7..b5be16315 100644
--- a/src/main/frontend/encrypt.cljs
+++ b/src/main/frontend/encrypt.cljs
@@ -14,8 +14,9 @@
(defn content-encrypted?
[content]
- (or (str/starts-with? content age-pem-header-line)
- (str/starts-with? content age-version-line)))
+ (when content
+ (or (str/starts-with? content age-pem-header-line)
+ (str/starts-with? content age-version-line))))
(defn encrypted-db?
[repo-url]
@@ -100,4 +101,4 @@
lazy-decrypt-with-user-passphrase (resolve 'frontend.extensions.age-encryption/decrypt-with-user-passphrase)
content (utf8/encode content)
decrypted (lazy-decrypt-with-user-passphrase passphrase content)]
- (utf8/decode decrypted)))
\ No newline at end of file
+ (utf8/decode decrypted)))
diff --git a/src/main/frontend/extensions/excalidraw.cljs b/src/main/frontend/extensions/excalidraw.cljs
new file mode 100644
index 000000000..8321b1021
--- /dev/null
+++ b/src/main/frontend/extensions/excalidraw.cljs
@@ -0,0 +1,136 @@
+(ns frontend.extensions.excalidraw
+ (:require [rum.core :as rum]
+ [goog.object :as gobj]
+ [frontend.rum :as r]
+ [frontend.util :as util :refer-macros [profile]]
+ [frontend.mixins :as mixins]
+ [frontend.storage :as storage]
+ [frontend.components.svg :as svg]
+ [cljs-bean.core :as bean]
+ [dommy.core :as d]
+ [clojure.string :as string]
+ [frontend.handler.notification :as notification]
+ [frontend.handler.draw :as draw]
+ [frontend.handler.file :as file]
+ [frontend.handler.ui :as ui-handler]
+ [frontend.ui :as ui]
+ [frontend.loader :as loader]
+ [frontend.config :as config]
+ [frontend.state :as state]
+ [frontend.search :as search]
+ [frontend.components.repo :as repo]
+ [promesa.core :as p]
+ [reitit.frontend.easy :as rfe]
+ ["@excalidraw/excalidraw" :as Excalidraw]))
+
+(def excalidraw (r/adapt-class (gobj/get Excalidraw "default")))
+
+(defn from-json
+ [text]
+ (when-not (string/blank? text)
+ (try
+ (js/JSON.parse text)
+ (catch js/Error e
+ (println "from json error:")
+ (js/console.dir e)
+ (notification/show!
+ (util/format "Could not load this invalid excalidraw file")
+ :error)))))
+
+(defonce *bounding-width (atom nil))
+(defn- get-bounding-width
+ [ref]
+ (when ref
+ (when-let [current (gobj/get ref "current")]
+ (-> current
+ (.getBoundingClientRect)
+ (gobj/get "width")))))
+
+(rum/defcs draw-inner < rum/reactive
+ (rum/local true ::zen-mode?)
+ (rum/local false ::view-mode?)
+ (rum/local nil ::elements)
+ [state data option]
+ (let [current-repo (state/sub :git/current-repo)
+ bounding-width (rum/react *bounding-width)
+ *zen-mode? (get state ::zen-mode?)
+ *view-mode? (get state ::view-mode?)
+ wide-mode? (state/sub :ui/wide-mode?)
+ *elements (get state ::elements)
+ file (:file option)]
+ (when data
+ [:div.overflow-hidden
+ [:div.my-1 {:style {:font-size 10}}
+ [:a.mr-2 {:on-click ui-handler/toggle-wide-mode!}
+ (util/format "Wide Mode (%s)" (if wide-mode? "ON" "OFF"))]
+ [:a.mr-2 {:on-click #(swap! *zen-mode? not)}
+ (util/format "Zen Mode (%s)" (if @*zen-mode? "ON" "OFF"))]
+ [:a.mr-2 {:on-click #(swap! *view-mode? not)}
+ (util/format "View Mode (%s)" (if @*view-mode? "ON" "OFF"))]]
+ [:div
+ (excalidraw
+ (merge
+ {:on-change (fn [elements state]
+ (let [elements->clj (bean/->clj elements)]
+ (when (and (seq elements->clj)
+ (not= elements @*elements))
+ (let [state (bean/->clj state)]
+ (draw/save-excalidraw!
+ file
+ (-> {:type "excalidraw"
+ :version 2
+ :source config/website
+ :elements elements
+ :appState (select-keys state [:gridSize :viewBackgroundColor])}
+ bean/->js
+ (js/JSON.stringify)))
+ (reset! *elements elements)))))
+ :zen-mode-enabled @*zen-mode?
+ :view-mode-enabled @*view-mode?
+ :grid-mode-enabled false
+ :initial-data data}
+ (if wide-mode?
+ {:height 650}
+ {:width 800
+ :height 500})))]])))
+
+(rum/defcs draw-container < rum/reactive
+ {:init (fn [state]
+ (let [[option] (:rum/args state)
+ file (:file option)
+ *data (atom nil)
+ *loading? (atom true)]
+ (when file
+ (draw/load-excalidraw-file
+ file
+ (fn [data]
+ (let [data (from-json data)]
+ (reset! *data data)
+ (reset! *loading? false)))))
+ (assoc state
+ ::data *data
+ ::loading? *loading?)))}
+ [state option]
+ (let [*data (get state ::data)
+ *loading? (get state ::loading?)
+ loading? (rum/react *loading?)
+ data (rum/react *data)
+ db-restoring? (state/sub :db/restoring?)]
+ (when (:file option)
+ (cond
+ db-restoring?
+ [:div.ls-center
+ (ui/loading "Loading")]
+
+ (false? loading?)
+ (draw-inner data option)
+
+ :else ; loading
+ nil))))
+
+(rum/defc draw < rum/reactive
+ [option]
+ (let [repo (state/get-current-repo)
+ granted? (state/sub [:nfs/user-granted? repo])]
+ (when-not (and (config/local-db? repo) (not granted?))
+ (draw-container option))))
diff --git a/src/main/frontend/fs/nfs.cljs b/src/main/frontend/fs/nfs.cljs
index d44b5ef40..8749751ff 100644
--- a/src/main/frontend/fs/nfs.cljs
+++ b/src/main/frontend/fs/nfs.cljs
@@ -133,11 +133,13 @@
not-changed? (= last-modified-at local-last-modified-at)
format (-> (util/get-file-ext path)
(config/get-file-format))
- pending-writes (state/get-write-chan-length)]
+ pending-writes (state/get-write-chan-length)
+ draw? (and path (string/ends-with? path ".excalidraw"))]
(if (and local-content (or old-content
;; temporally fix
- (and path (string/ends-with? path ".excalidraw"))) new?
+ draw?) new?
(or
+ draw?
;; Writing not finished
(> pending-writes 0)
;; not changed by other editors
diff --git a/src/main/frontend/handler/draw.cljs b/src/main/frontend/handler/draw.cljs
index 3e23d1d33..ba333b199 100644
--- a/src/main/frontend/handler/draw.cljs
+++ b/src/main/frontend/handler/draw.cljs
@@ -14,37 +14,6 @@
[cljs-time.core :as t]
[cljs-time.coerce :as tc]))
-;; state
-(defonce *files (atom nil))
-(defonce *current-file (atom nil))
-(defonce *current-title (atom ""))
-(defonce *file-loading? (atom nil))
-(defonce *elements (atom nil))
-(defonce *unsaved? (atom false))
-(defonce *search-files (atom []))
-(defonce *saving-title (atom nil))
-(defonce *excalidraw (atom nil))
-
-;; TODO: refactor
-(defonce draw-state :draw-state)
-
-(defn get-draw-state []
- (storage/get draw-state))
-(defn set-draw-state! [value]
- (storage/set draw-state value))
-
-(defn set-k
- [k v]
- (when-let [repo (state/get-current-repo)]
- (let [state (get-draw-state)]
- (let [new-state (assoc-in state [repo k] v)]
- (set-draw-state! new-state)))))
-
-(defn set-last-file!
- [value]
- (set-k :last-file value))
-
-;; excalidraw
(defn create-draws-directory!
[repo]
(when repo
@@ -55,8 +24,8 @@
(fn [_error] nil)))))
(defn save-excalidraw!
- [file data ok-handler]
- (let [path (str config/default-draw-directory "/" file)
+ [file data]
+ (let [path file
repo (state/get-current-repo)]
(when repo
(let [repo-dir (config/get-repo-dir repo)]
@@ -65,7 +34,6 @@
(create-draws-directory! repo)
(fs/write-file! repo repo-dir path data nil)
(git-handler/git-add repo path)
- (ok-handler file)
(db/transact! repo
[{:file/path path
:page/name file
@@ -75,58 +43,30 @@
(prn "Write file failed, path: " path ", data: " data)
(js/console.dir error))))))))
-(defn get-all-excalidraw-files
- [ok-handler]
- (when-let [repo (state/get-current-repo)]
- (p/let [_ (create-draws-directory! repo)]
- (let [dir (str (config/get-repo-dir repo)
- "/"
- config/default-draw-directory)]
- (util/p-handle
- (fs/readdir dir)
- (fn [files]
- (let [files (-> (filter #(string/ends-with? % ".excalidraw") files)
- (distinct)
- (sort)
- (reverse))]
- (ok-handler files)))
- (fn [error]
- (js/console.dir error)))))))
-
(defn load-excalidraw-file
[file ok-handler]
(when-let [repo (state/get-current-repo)]
(util/p-handle
- (file-handler/load-file repo (str config/default-draw-directory "/" file))
+ (file-handler/load-file repo file)
(fn [content]
(ok-handler content))
(fn [error]
- (prn "Error loading " file ": "
- error)))))
+ (println "Error loading " file ": "
+ error)))))
(defonce default-content
(util/format
"{\n \"type\": \"excalidraw\",\n \"version\": 2,\n \"source\": \"%s\",\n \"elements\": [],\n \"appState\": {\n \"viewBackgroundColor\": \"#FFF\",\n \"gridSize\": null\n }\n}"
config/website))
-(defn title->file-name
- [title]
- (when (not (string/blank? title))
- (let [title (string/lower-case (string/replace title " " "-"))]
- (str (date/get-date-time-string-2) "-" title ".excalidraw"))))
+(defn file-name
+ []
+ (str (date/get-date-time-string-2) ".excalidraw"))
(defn create-draw-with-default-content
- [current-file ok-handler]
+ [current-file]
(when-let [repo (state/get-current-repo)]
(p/let [exists? (fs/file-exists? (config/get-repo-dir repo)
(str config/default-draw-directory current-file))]
(when-not exists?
- (save-excalidraw! current-file default-content
- (fn [file]
- (reset! *files
- (distinct (conj @*files file)))
- (reset! *current-file file)
- (reset! *unsaved? false)
- (set-last-file! file)
- (reset! *saving-title nil)
- (ok-handler)))))))
+ (save-excalidraw! current-file default-content)))))
diff --git a/src/main/frontend/handler/editor.cljs b/src/main/frontend/handler/editor.cljs
index 222b33375..f9594b3b3 100644
--- a/src/main/frontend/handler/editor.cljs
+++ b/src/main/frontend/handler/editor.cljs
@@ -10,7 +10,6 @@
[frontend.handler.repo :as repo-handler]
[frontend.handler.file :as file-handler]
[frontend.handler.notification :as notification]
- [frontend.handler.draw :as draw]
[frontend.handler.expand :as expand]
[frontend.handler.block :as block-handler]
[frontend.format.mldoc :as mldoc]
@@ -2112,22 +2111,6 @@
(get-link format link label)
format
{:last-pattern (str commands/slash "link")})))
- :draw
- (when-not (string/blank? (:title m))
- (let [file (draw/title->file-name (:title m))
- value (util/format
- "[[%s]]\n"
- file
- file)]
- (insert-command! id
- value
- format
- {:last-pattern (str commands/slash "draw ")})
- (draw/create-draw-with-default-content
- file
- (fn []
- (let [input (gdom/getElement "download")]
- (.click input))))))
nil)
(state/set-editor-show-input! nil)
diff --git a/src/main/frontend/handler/ui.cljs b/src/main/frontend/handler/ui.cljs
index 161f6c2e0..5dd9a2db8 100644
--- a/src/main/frontend/handler/ui.cljs
+++ b/src/main/frontend/handler/ui.cljs
@@ -92,3 +92,12 @@
;; (state/get-custom-css-link)
)]
(util/add-style! style)))
+
+(defn toggle-wide-mode!
+ []
+ (let [wide? (state/get-wide-mode?)
+ elements (array-seq (js/document.getElementsByClassName "cp__sidebar-main-content"))
+ max-width (if wide? "var(--ls-main-content-max-width)" "100%")]
+ (when-let [element (first elements)]
+ (dom/set-style! element :max-width max-width))
+ (state/toggle-wide-mode!)))
diff --git a/src/main/frontend/keyboards.cljs b/src/main/frontend/keyboards.cljs
index 152f65ef2..793804811 100644
--- a/src/main/frontend/keyboards.cljs
+++ b/src/main/frontend/keyboards.cljs
@@ -51,6 +51,8 @@
(enable-when-not-editing-mode! ui-handler/toggle-contents!)
(or (shortcut :editor/toggle-settings) "t s")
(enable-when-not-editing-mode! ui-handler/toggle-settings-modal!)
+ (or (shortcut :ui/toggle-wide-mode) "t w")
+ (enable-when-not-editing-mode! ui-handler/toggle-wide-mode!)
(or (shortcut :ui/toggle-between-page-and-file) "s")
(enable-when-not-editing-mode! route-handler/toggle-between-page-and-file!)
(or (shortcut :git/commit) "c")
diff --git a/src/main/frontend/routes.cljs b/src/main/frontend/routes.cljs
index 9a598f043..640f0d288 100644
--- a/src/main/frontend/routes.cljs
+++ b/src/main/frontend/routes.cljs
@@ -4,7 +4,6 @@
[frontend.components.file :as file]
[frontend.components.page :as page]
[frontend.components.diff :as diff]
- [frontend.components.draw :as draw]
[frontend.components.journal :as journal]
[frontend.components.settings :as settings]
[frontend.components.external :as external]
@@ -52,10 +51,6 @@
{:name :diff
:view diff/diff}]
- ["/draw"
- {:name :draw
- :view draw/draw}]
-
["/settings"
{:name :settings
:view settings/settings}]
diff --git a/src/main/frontend/state.cljs b/src/main/frontend/state.cljs
index 7f18c7923..3dbece8d7 100644
--- a/src/main/frontend/state.cljs
+++ b/src/main/frontend/state.cljs
@@ -54,6 +54,7 @@
:ui/sidebar-open? false
:ui/left-sidebar-open? false
:ui/theme (or (storage/get :ui/theme) "dark")
+ :ui/wide-mode? false
;; :show-all, :hide-block-body, :hide-block-children
:ui/cycle-collapse :show-all
:ui/collapsed-blocks {}
@@ -899,10 +900,13 @@
(set-state! :indexeddb/support? value))
(defn set-modal!
- [modal-panel-content]
- (swap! state assoc
- :modal/show? (boolean modal-panel-content)
- :modal/panel-content modal-panel-content))
+ ([modal-panel-content]
+ (set-modal! modal-panel-content false))
+ ([modal-panel-content fullscreen?]
+ (swap! state assoc
+ :modal/show? (boolean modal-panel-content)
+ :modal/panel-content modal-panel-content
+ :modal/fullscreen? fullscreen?)))
(defn close-modal!
[]
@@ -992,6 +996,14 @@
[]
(get-in @state [:repo/changed-files (get-current-repo)]))
+(defn get-wide-mode?
+ []
+ (:ui/wide-mode? @state))
+
+(defn toggle-wide-mode!
+ []
+ (update-state! :ui/wide-mode? not))
+
(defn set-online!
[value]
(set-state! :network/online? value))
diff --git a/src/main/frontend/ui.cljs b/src/main/frontend/ui.cljs
index d218d1d5f..ff289b20b 100644
--- a/src/main/frontend/ui.cljs
+++ b/src/main/frontend/ui.cljs
@@ -424,7 +424,7 @@
[:div.absolute.inset-0.opacity-75]])
(rum/defc modal-panel
- [panel-content transition-state close-fn]
+ [panel-content transition-state close-fn fullscreen?]
[:div.ui__modal-panel.transform.transition-all.sm:min-w-lg.sm
{:class (case transition-state
"entering" "ease-out duration-300 opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
@@ -444,7 +444,7 @@
:stroke-linejoin "round"
:stroke-linecap "round"}]]]]
- [:div.panel-content
+ [:div {:class (if fullscreen? "" "panel-content")}
(panel-content close-fn)]])
(rum/defc modal < rum/reactive
@@ -458,6 +458,7 @@
:outside? false)))
[]
(let [modal-panel-content (state/sub :modal/panel-content)
+ fullscreen? (state/sub :modal/fullscreen?)
show? (boolean modal-panel-content)
close-fn #(state/close-modal!)
modal-panel-content (or modal-panel-content (fn [close] [:div]))]
@@ -470,7 +471,7 @@
(css-transition
{:in show? :timeout 0}
(fn [state]
- (modal-panel modal-panel-content state close-fn)))]))
+ (modal-panel modal-panel-content state close-fn fullscreen?)))]))
(defn make-confirm-modal
[{:keys [tag title sub-title sub-checkbox? on-cancel on-confirm] :as opts}]