snapshot of https://github.com/excalidraw/excalidraw.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Tests / test (push) Failing after 15s

This commit is contained in:
upstream-mirror
2026-09-16 05:31:04 +05:00
commit 1a8d07ca6c
1286 changed files with 360144 additions and 0 deletions

View File

@@ -0,0 +1,37 @@
.visually-hidden {
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect(1px, 1px, 1px, 1px);
white-space: nowrap; /* added line */
user-select: none;
}
.LoadingMessage {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 999;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
pointer-events: none;
.Spinner {
font-size: 2.8em;
}
.LoadingMessage-text {
margin-top: 1em;
font-size: 0.8em;
}
}
.LoadingMessage--dark {
background-color: #121212;
color: #ced4da;
}

View File

@@ -0,0 +1,996 @@
@use "./variables.module" as *;
@use "./theme";
:root {
--zIndex-canvas: 1;
--zIndex-interactiveCanvas: 2;
--zIndex-svgLayer: 3;
--zIndex-wysiwyg: 3;
--zIndex-canvasButtons: 3;
--zIndex-layerUI: 4;
--zIndex-viewportStatusFrame: 5;
--zIndex-eyeDropperBackdrop: 5;
--zIndex-eyeDropperPreview: 6;
--zIndex-hyperlinkContainer: 7;
--zIndex-cursorHint: 8;
--zIndex-ui-bottom: 60;
--zIndex-ui-context-menu: 90;
--zIndex-ui-styles-popup: 100;
--zIndex-ui-top: 100;
--zIndex-ui-main-menu: 110;
--zIndex-ui-library: 120;
--zIndex-modal: 1000;
--zIndex-popup: 1001;
--zIndex-toast: 999999;
--sab: env(safe-area-inset-bottom);
--sal: env(safe-area-inset-left);
--sar: env(safe-area-inset-right);
--sat: env(safe-area-inset-top);
}
body.excalidraw-cursor-resize,
body.excalidraw-cursor-resize a:hover,
body.excalidraw-cursor-resize * {
cursor: ew-resize;
}
.excalidraw {
--ui-font: Assistant, system-ui, BlinkMacSystemFont, -apple-system, Segoe UI,
Roboto, Helvetica, Arial, sans-serif;
--viewport-status-frame-border-width: 0px;
&.excalidraw--viewport-status-border {
--viewport-status-frame-border-width: 4px;
}
font-family: var(--ui-font);
position: relative;
overflow: hidden;
color: var(--text-primary-color);
display: flex;
top: 0;
bottom: 0;
left: 0;
right: 0;
height: 100%;
width: 100%;
button {
// browser default. Looks kinda good on low-dpi.
font-size: 0.8333rem;
}
button,
label {
@include buttonNoHighlight;
}
button {
cursor: pointer;
user-select: none;
}
&:focus {
outline: none;
}
// serves 2 purposes:
// 1. prevent selecting text outside the component when double-clicking or
// dragging inside it (e.g. on canvas)
// 2. prevent selecting UI, both from the inside, and from outside the
// component (e.g. if you select text in a sidebar)
user-select: none;
.excalidraw-link,
a {
font-weight: 500;
text-decoration: none;
color: var(--link-color);
user-select: none;
cursor: pointer;
&:hover {
text-decoration: underline;
}
&:active {
text-decoration: none;
}
}
canvas {
touch-action: none;
// following props improve blurriness at certain devicePixelRatios.
// AFAIK it doesn't affect export (in fact, export seems sharp either way).
image-rendering: pixelated; // chromium
// NOTE: must be declared *after* the above
image-rendering: -moz-crisp-edges; // FF
z-index: var(--zIndex-canvas);
&.interactive {
z-index: var(--zIndex-interactiveCanvas);
}
// Remove the main canvas from document flow to avoid resizeObserver
// feedback loop (see https://github.com/excalidraw/excalidraw/pull/3379)
}
&__canvas-wrapper,
&__canvas.static {
pointer-events: none;
}
&__canvas {
position: absolute;
}
&__embeddable {
width: 100%;
height: 100%;
border: 0;
}
&__embeddable-container {
position: absolute;
z-index: 2;
transform-origin: top left;
pointer-events: none;
}
.FixedSideContainer {
padding-top: var(--sat, 0);
padding-right: var(--sar, 0);
padding-bottom: var(--sab, 0);
padding-left: var(--sal, 0);
}
.panelRow {
display: flex;
justify-content: space-between;
}
.selected-shape-actions {
display: flex;
flex-direction: column;
row-gap: 0.75rem;
h3,
legend,
.control-label {
margin: 0;
margin-bottom: 0.25rem;
font-size: 0.75rem;
color: var(--text-primary-color);
font-weight: 400;
display: block;
}
.control-label input {
display: block;
width: 100%;
}
legend {
padding: 0;
}
.iconSelectList {
flex-wrap: wrap;
position: relative;
}
.buttonList {
flex-wrap: wrap;
display: flex;
column-gap: 0.5rem;
row-gap: 0.5rem;
label {
font-size: 0.75rem;
}
input[type="radio"],
input[type="button"] {
opacity: 0;
position: absolute;
pointer-events: none;
}
.iconRow {
margin-top: 8px;
}
}
fieldset {
margin: 0;
padding: 0;
border: none;
}
}
.divider {
width: 1px;
background-color: $color-gray-2;
margin: 1px;
}
.welcome-screen-menu-item:focus-visible,
.dropdown-menu-item:focus-visible,
button:focus-visible,
.buttonList label:focus-within,
input:focus-visible {
outline: transparent;
box-shadow: 0 0 0 1px var(--color-brand-hover);
}
// radix doesn't allow differntiating between hover and keyboard active
// states (it's forcing :focus on both).
//
// proper handling would be to disable :focus-visible by default, and enable
// on keyboard arrows (it'd then have to be disabled again, e.g. on keydown
// or container focus)
//
// alas, that is left for another day
[data-radix-collection-item]:focus-visible {
box-shadow: none !important;
}
.buttonList {
.ToolIcon__icon {
all: unset !important;
display: flex !important;
}
button {
background-color: transparent;
}
label,
button,
.zIndexButton {
@include outlineButtonIconStyles;
}
}
// an icon button rendered outside a .buttonList context (e.g. the font
// picker trigger)
button.standalone {
@include outlineButtonIconStyles;
.ToolIcon__icon {
all: unset !important;
display: flex !important;
}
& > * {
// disallow pointer events on children, so we always have event.target
// on the button itself
pointer-events: none;
}
}
.App-top-bar {
z-index: var(--zIndex-ui-top);
display: flex;
flex-direction: column;
}
// mobile-only: pinned near the very top of the viewport (just below the
// viewport-status-frame border), independent of the top icon row's
// height, rather than competing for space with the bottom toolbar.
.viewport-status-frame__badge-row {
position: absolute;
top: calc(var(--viewport-status-frame-border-width) + 0.5rem);
left: 50%;
transform: translateX(-50%);
// explicit (not auto) so it participates correctly against the
// interactive canvas's own explicit z-index, regardless of value.
z-index: var(--zIndex-viewportStatusFrame);
pointer-events: none;
}
.App-welcome-screen {
z-index: var(--zIndex-layerUI);
}
.App-bottom-bar {
position: absolute;
// account for margins
width: calc(100% - 28px);
max-width: 450px;
bottom: 0;
left: 50%;
transform: translateX(-50%);
--bar-padding: calc(4 * var(--space-factor));
z-index: var(--zIndex-ui-bottom);
display: flex;
flex-direction: column;
pointer-events: none;
justify-content: center;
> .Island {
box-sizing: border-box;
max-height: 100%;
padding: 4px;
display: flex;
flex-direction: column;
pointer-events: var(--ui-pointerEvents);
}
}
.App-toolbar {
display: flex;
justify-content: center;
.eraser {
&.ToolIcon:hover {
--icon-fill-color: #fff;
--keybinding-color: #fff;
}
&.active {
background-color: var(--color-primary);
}
}
}
.excalidraw-ui-top-left,
.excalidraw-ui-top-right {
display: flex;
align-items: center;
gap: 0.5rem;
}
.App-toolbar-content {
display: flex;
flex-direction: column;
pointer-events: none;
& > * {
pointer-events: var(--ui-pointerEvents);
}
}
.App-mobile-menu {
width: 100%;
overflow-x: visible;
overflow-y: auto;
box-sizing: border-box;
margin-bottom: var(--bar-padding);
.selected-shape-actions {
padding: 8px 8px 0 8px;
}
}
.App-menu {
display: grid;
color: var(--icon-fill-color);
}
.shapes-section {
display: flex;
justify-content: center;
pointer-events: none !important;
& > * {
pointer-events: var(--ui-pointerEvents);
}
}
.App-menu_top {
grid-template-columns: 1fr 2fr 1fr;
grid-gap: 1rem;
align-items: flex-start;
cursor: default;
pointer-events: none !important;
& > * {
pointer-events: var(--ui-pointerEvents);
}
@media (min-width: 1536px) {
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 3rem;
}
}
.App-menu_top > .App-menu_top__left {
grid-column: 1;
justify-self: start;
// App-menu_top__left is a single-column grid, so its track width is
// driven by the widest row — including the host-provided
// renderTopLeftUI content, which can be arbitrarily wide. Its
// (stretched) grid-item wrappers must stay pointer-events:none so that
// slack isn't a dead zone over the canvas; only the actual
// (content-sized, non-stretched) interactive pieces opt back in.
// Specificity (not source order) is why this beats `.App-menu_top > *`.
pointer-events: none;
.selected-shape-actions-container,
.ToolIcon__penMode {
pointer-events: var(--ui-pointerEvents);
}
// .excalidraw-ui-top-left is itself `display: flex` (block-level), so
// it *also* stretches to fill this track's width — its own box needs
// the same none-then-opt-in treatment, one level deeper, so its real
// (content-sized, unstretched) children stay clickable without
// resizing/repositioning the row itself.
.excalidraw-ui-top-left {
pointer-events: none;
> * {
pointer-events: var(--ui-pointerEvents);
}
}
}
.App-menu_top > .shapes-section {
grid-column: 2;
}
.selected-shape-actions-container {
width: fit-content;
&--compact {
min-width: 48px;
}
}
.App-menu_top > .layer-ui__wrapper__top-right {
grid-column: 3;
justify-self: end;
}
.App-menu_bottom {
position: absolute;
bottom: 1rem;
display: flex;
justify-content: space-between;
align-items: flex-start;
cursor: default;
pointer-events: none !important;
box-sizing: border-box;
padding: 0 1rem;
&--transition-left {
section {
width: 185px;
}
}
section {
display: flex;
}
}
.App-menu_bottom > *:first-child {
justify-self: flex-start;
}
.App-menu_bottom > *:last-child {
justify-self: flex-end;
}
.App-menu_left {
grid-template-rows: 1fr auto 1fr;
height: 100%;
}
.App-menu_right {
grid-template-rows: 1fr;
height: 100%;
}
.App-menu__left {
overflow-y: auto;
padding: 0.75rem;
width: 12.5rem;
box-sizing: border-box;
position: absolute;
}
.dropdown-select {
height: 1.5rem;
padding: 0;
padding-inline-start: 0.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: 0.8rem;
font-family: inherit;
outline: none;
appearance: none;
background-image: var(--dropdown-icon);
background-repeat: no-repeat;
background-position: right 0.7rem top 50%, 0 0;
:root[dir="rtl"] & {
background-position: left 0.7rem top 50%, 0 0;
}
background-size: 0.65em auto, 100%;
&:focus {
box-shadow: 0 0 0 2px var(--focus-highlight-color);
}
&:hover {
background-color: var(--button-gray-2);
}
&:active {
background-color: var(--button-gray-2);
}
&__language {
height: 2rem;
background-color: var(--island-bg-color);
border-color: var(--default-border-color) !important;
cursor: pointer;
&:hover {
background-color: var(--island-bg-color);
}
}
}
.scroll-back-to-content {
border-radius: var(--border-radius-lg);
background-color: var(--island-bg-color);
color: var(--icon-fill-color);
border: 1px solid var(--default-border-color);
padding: 10px 20px;
position: absolute;
left: 50%;
bottom: 30px;
transform: translateX(-50%);
box-sizing: border-box;
max-width: calc(
100% - var(--viewport-status-frame-border-width) -
var(--viewport-status-frame-border-width)
);
pointer-events: var(--ui-pointerEvents);
font-family: inherit;
&:hover {
background-color: var(--button-hover-bg);
}
&:active {
border: 1px solid var(--button-active-border);
}
}
// always rendered as a flex column of [scroll-back-to-content, badge] —
// this positions the stack itself; children are centered/spaced via flex.
.floating-status-stack {
position: absolute;
left: var(--viewport-status-frame-border-width);
right: var(--viewport-status-frame-border-width);
bottom: calc(0.5rem + var(--viewport-status-frame-border-width));
display: flex;
flex-direction: column;
align-items: center;
gap: 0.5rem;
pointer-events: none;
.scroll-back-to-content {
position: static;
left: auto;
bottom: auto;
transform: none;
max-width: 100%;
pointer-events: var(--ui-pointerEvents);
}
}
// on mobile, when the default bottom toolbar renders, the stack is a
// normal (in-flow) row above MobileShapeActions/the toolbar Island inside
// .App-bottom-bar instead of being independently viewport-positioned —
// self-sizing regardless of whether MobileShapeActions is showing, with
// no magic-number height math needed to clear the toolbar.
.App-bottom-bar > .floating-status-stack {
position: static;
left: auto;
right: auto;
bottom: auto;
margin-bottom: 0.5rem;
}
.help-icon {
@include outlineButtonStyles;
@include filledButtonOnCanvas;
width: var(--lg-button-size);
height: var(--lg-button-size);
svg {
width: var(--lg-icon-size);
height: var(--lg-icon-size);
}
}
.reset-zoom-button {
font-family: var(--ui-font);
}
.finalize-button {
display: grid;
grid-auto-flow: column;
gap: 0.4em;
margin-top: auto;
margin-bottom: auto;
margin-inline-start: 0.6em;
}
.undo-redo-buttons,
.eraser-buttons {
display: grid;
grid-auto-flow: column;
margin-top: auto;
margin-bottom: auto;
margin-inline-start: 0.6em;
}
@include isMobile {
aside {
display: none;
}
.floating-status-stack {
bottom: calc(30px + var(--sab, 0));
z-index: var(--zIndex-ui-bottom);
}
}
.rtl-mirror {
:root[dir="rtl"] & {
transform: scaleX(-1);
}
}
.zen-mode-visibility {
visibility: visible;
opacity: 1;
height: auto;
width: auto;
transition: opacity 0.5s;
&.zen-mode-visibility--hidden {
visibility: hidden;
opacity: 0;
height: 0;
width: 0;
transition: opacity 0.5s;
}
}
.disable-pointerEvents {
pointer-events: none !important;
}
&.excalidraw--view-mode {
.App-menu {
display: flex;
justify-content: space-between;
}
}
&.excalidraw--non-interactive {
user-select: none;
canvas {
// let the page handle scrolling over the editor, but not the browser
// zoom gestures (pinch, double-tap)
touch-action: pan-x pan-y;
// no !important — inline cursor styles must win so that
// `interaction: { enabled: { links: true } }` can show a pointer over
// links
cursor: default;
}
&.excalidraw--allow-browser-zoom canvas {
touch-action: auto;
}
// with navigation allowed, touch input drives the canvas pan/zoom again
&.excalidraw--navigation canvas {
touch-action: none;
}
// with the active tool allowed (`interaction.enabled.tools`), touch
// input drives the tool again — e.g. laser strokes must not scroll the
// page
&.excalidraw--tools canvas {
touch-action: none;
}
// override inline pointer-events set when embeddable is active
// (also covers custom `renderEmbeddable` content) — unless embeds are
// an allowed interaction, in which case the inline (activation-driven)
// pointer-events apply, same as in view mode
&:not(.excalidraw--embeds) .excalidraw__embeddable-container__inner {
pointer-events: none !important;
}
}
input.is-redacted {
// we don't use type=password because browsers (chrome?) prompt
// you to save it which is annoying
-webkit-text-security: disc;
}
input[type="text"],
textarea:not(.excalidraw-wysiwyg) {
color: var(--text-primary-color);
border: 1.5px solid var(--input-border-color);
padding: 0.75rem;
white-space: nowrap;
border-radius: var(--space-factor);
background-color: var(--input-bg-color);
&:not(:focus) {
&:hover {
border-color: var(--color-brand-hover);
}
}
&:focus {
outline: none;
border-color: var(--color-brand-hover);
}
}
@media print {
.App-bottom-bar,
.FixedSideContainer,
.layer-ui__wrapper {
display: none;
}
}
// use custom, minimalistic scrollbar
// (doesn't work in Firefox)
::-webkit-scrollbar {
width: 4px;
height: 3px;
}
select::-webkit-scrollbar {
width: 10px;
}
::-webkit-scrollbar-thumb {
background: var(--scrollbar-thumb);
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--scrollbar-thumb-hover);
}
::-webkit-scrollbar-thumb:active {
background: var(--scrollbar-thumb);
}
.mobile-misc-tools-container {
position: absolute;
top: calc(5rem - var(--editor-container-padding));
right: calc(var(--editor-container-padding) * -1);
display: flex;
flex-direction: column;
border: 1px solid var(--sidebar-border-color);
border-top-left-radius: var(--border-radius-lg);
border-bottom-left-radius: var(--border-radius-lg);
border-right: 0;
overflow: hidden;
background-color: var(--island-bg-color);
.ToolIcon__icon {
width: 2rem;
height: 2rem;
border-radius: 0;
}
.default-sidebar-trigger {
border: 0;
}
}
.App-toolbar--mobile {
overflow: visible;
max-width: 98vw;
.ToolIcon__keybinding {
display: none;
}
}
.UserList-Wrapper {
margin: 0;
padding: 0;
border: none;
text-align: left;
legend {
display: block;
font-size: 0.75rem;
font-weight: 400;
margin: 0 0 0.25rem;
padding: 0;
}
}
.main-menu {
z-index: var(--zIndex-ui-main-menu);
}
.main-menu-trigger {
@include filledButtonOnCanvas;
}
.App-mobile-menu,
.App-menu__left {
--button-border: transparent;
--button-bg: var(--color-surface-mid);
}
@at-root .excalidraw.theme--dark#{&} {
.App-mobile-menu,
.App-menu__left {
--button-hover-bg: #363541;
--button-bg: var(--color-surface-high);
}
}
.App-menu__left {
.buttonList {
padding: 0.25rem 0;
}
}
.excalidraw__paragraph {
margin: 1rem 0;
}
.Modal__content {
.excalidraw__paragraph:first-child {
margin-top: 0;
}
.excalidraw__paragraph + .excalidraw__paragraph {
margin-top: 0rem;
}
}
.link-button {
display: flex;
text-decoration: none !important;
}
}
.ErrorSplash.excalidraw {
min-height: 100vh;
padding: 20px 0;
overflow: auto;
display: flex;
align-items: center;
justify-content: center;
user-select: text;
.ErrorSplash-messageContainer {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px;
background-color: $color-red-1;
border: 3px solid $color-red-9;
}
.ErrorSplash-paragraph {
margin: 15px 0;
max-width: 600px;
&.align-center {
text-align: center;
}
}
.bigger,
.bigger button {
font-size: 1.1em;
}
.smaller,
.smaller button {
font-size: 0.9em;
}
.ErrorSplash-details {
display: flex;
flex-direction: column;
align-items: flex-start;
textarea {
width: 100%;
margin: 10px 0;
font-family: "Cascadia";
font-size: 0.8em;
}
}
}
.excalidraw__embeddable-container {
.excalidraw__embeddable-container__inner {
position: relative;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
border-radius: var(--embeddable-radius);
}
.excalidraw__embeddable__generating {
position: absolute;
z-index: 1;
bottom: 1rem;
right: 1rem;
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.375rem 0.75rem;
border-radius: 12px;
background: rgba(0, 0, 0, 0.5);
color: #fff;
font-size: 0.875rem;
font-family: "Assistant";
pointer-events: none;
.Spinner {
--spinner-color: #fff;
flex: 0 0 auto;
height: auto;
margin: 0;
}
}
.excalidraw__embeddable__outer {
width: 100%;
height: 100%;
}
.excalidraw__embeddable__content {
width: 100%;
height: 100%;
transform-origin: top left;
&,
& > * {
border-radius: var(--embeddable-radius);
}
}
.excalidraw__embeddable-hint {
position: absolute;
z-index: 1;
background: rgba(0, 0, 0, 0.5);
padding: 1rem 1.6rem;
border-radius: 12px;
color: #fff;
font-weight: 700;
letter-spacing: 0.6px;
font-family: "Assistant";
}
}

View File

@@ -0,0 +1,281 @@
@use "sass:color";
@use "./variables.module" as *;
@forward "./variables.module";
.excalidraw {
--theme-filter: none;
--button-destructive-bg-color: #{$color-red-1};
--button-destructive-color: #{$color-red-9};
--button-gray-1: #{$color-gray-2};
--button-gray-2: #{$color-gray-4};
--button-gray-3: #{$color-gray-5};
--mobile-action-button-bg: rgba(255, 255, 255, 0.35);
--mobile-color-border: var(--default-border-color);
--button-special-active-bg-color: #{$color-green-0};
--dialog-border-color: var(--color-gray-20);
--dropdown-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="292.4" height="292.4" viewBox="0 0 292 292"><path 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"/></svg>');
--focus-highlight-color: #{$color-blue-2};
--icon-fill-color: var(--color-on-surface);
--icon-green-fill-color: #{$color-green-9};
--default-bg-color: #fff;
--input-bg-color: #fff;
--input-border-color: #{$color-gray-4};
--input-hover-bg-color: #{$color-gray-1};
--input-label-color: #{$color-gray-7};
--island-bg-color: #ffffff;
--island-bg-color-alt: #fff;
--keybinding-color: var(--color-gray-40);
--link-color: #{$color-blue-7};
--link-color-hover: #{$color-blue-8};
--link-color-active: #{$color-blue-2};
--overlay-bg-color: #{color.adjust(#fff, $alpha: -0.12)};
--popup-bg-color: var(--island-bg-color);
--popup-secondary-bg-color: #{$color-gray-1};
--popup-text-color: #000;
--popup-text-inverted-color: #fff;
--select-highlight-color: #{$color-blue-5};
--shadow-island: 0px 0px 1px 0px rgba(0, 0, 0, 0.17),
0px 0px 3px 0px rgba(0, 0, 0, 0.08), 0px 7px 14px 0px rgba(0, 0, 0, 0.05);
--shadow-island-stronger: 0px 0px 1px 0px rgba(0, 0, 0, 0.17),
0px 0px 3px 0px rgba(0, 0, 0, 0.08), 0px 7px 14px 0px rgb(0 0 0 / 18%);
--button-hover-bg: var(--color-surface-high);
--button-active-bg: var(--color-surface-high);
--button-active-border: var(--color-brand-active);
--default-border-color: var(--color-surface-high);
--default-button-size: 2rem;
--default-icon-size: 1rem;
--lg-button-size: 2.25rem;
--lg-icon-size: 1rem;
--editor-container-padding: 1rem;
--mobile-action-button-size: 2rem;
--scrollbar-thumb: var(--button-gray-2);
--scrollbar-thumb-hover: var(--button-gray-3);
--color-slider-track: hsl(240, 100%, 90%);
--color-slider-thumb: var(--color-gray-80);
--modal-shadow: 0px 100px 80px rgba(0, 0, 0, 0.07),
0px 41.7776px 33.4221px rgba(0, 0, 0, 0.0503198),
0px 22.3363px 17.869px rgba(0, 0, 0, 0.0417275),
0px 12.5216px 10.0172px rgba(0, 0, 0, 0.035),
0px 6.6501px 5.32008px rgba(0, 0, 0, 0.0282725),
0px 2.76726px 2.21381px rgba(0, 0, 0, 0.0196802);
--avatar-border-color: var(--color-gray-20);
--sidebar-shadow: 0px 100px 80px rgba(0, 0, 0, 0.07),
0px 41.7776px 33.4221px rgba(0, 0, 0, 0.0503198),
0px 22.3363px 17.869px rgba(0, 0, 0, 0.0417275),
0px 12.5216px 10.0172px rgba(0, 0, 0, 0.035),
0px 6.6501px 5.32008px rgba(0, 0, 0, 0.0282725),
0px 2.76726px 2.21381px rgba(0, 0, 0, 0.0196802);
--sidebar-border-color: var(--color-surface-high);
--sidebar-bg-color: var(--island-bg-color);
--library-dropdown-shadow: 0px 15px 6px rgba(0, 0, 0, 0.01),
0px 8px 5px rgba(0, 0, 0, 0.05), 0px 4px 4px rgba(0, 0, 0, 0.09),
0px 1px 2px rgba(0, 0, 0, 0.1), 0px 0px 0px rgba(0, 0, 0, 0.1);
--chat-msg-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
--space-factor: 0.25rem;
--text-primary-color: var(--color-on-surface);
--color-selection: #6965db;
--color-icon-white: #fff;
--color-primary: #6965db;
--color-primary-darker: #5b57d1;
--color-primary-darkest: #4a47b1;
--color-primary-light: #e3e2fe;
--color-primary-light-darker: #d7d5ff;
--color-primary-hover: #5753d0;
--color-gray-10: #f5f5f5;
--color-gray-20: #ebebeb;
--color-gray-30: #d6d6d6;
--color-gray-40: #b8b8b8;
--color-gray-50: #999999;
--color-gray-60: #7a7a7a;
--color-gray-70: #5c5c5c;
--color-gray-80: #3d3d3d;
--color-gray-85: #242424;
--color-gray-90: #1e1e1e;
--color-gray-100: #121212;
--color-disabled: var(--color-gray-40);
--color-warning: #fceeca;
--color-warning-dark: #f5c354;
--color-warning-darker: #f3ab2c;
--color-warning-darkest: #ec8b14;
--color-text-warning: var(--text-primary-color);
--color-danger: #db6965;
--color-danger-dark: #db6965;
--color-danger-darker: #d65550;
--color-danger-darkest: #d1413c;
--color-danger-text: black;
--color-danger-background: #fff0f0;
--color-danger-icon-background: #ffdad6;
--color-danger-color: #700000;
--color-danger-icon-color: #700000;
--color-warning-background: var(--color-warning);
--color-warning-icon-background: var(--color-warning-dark);
--color-warning-color: var(--text-primary-color);
--color-warning-icon-color: var(--text-primary-color);
--color-muted: var(--color-gray-30);
--color-muted-darker: var(--color-gray-60);
--color-muted-darkest: var(--color-gray-100);
--color-muted-background: var(--color-gray-80);
--color-muted-background-darker: var(--color-gray-100);
--color-promo: var(--color-primary);
--color-success: #cafccc;
--color-success-darker: #bafabc;
--color-success-darkest: #a5eba8;
--color-success-text: #268029;
--color-success-contrast: #65bb6a;
--color-success-contrast-hover: #6bcf70;
--color-success-contrast-active: #6edf74;
--color-logo-icon: var(--color-primary);
--color-logo-text: #190064;
--border-radius-md: 0.375rem;
--border-radius-lg: 0.5rem;
--color-surface-high: #f1f0ff;
--color-surface-mid: #f6f6f9;
--color-surface-low: #ececf4;
--color-surface-lowest: #ffffff;
--color-on-surface: #1b1b1f;
--color-brand-hover: #5753d0;
--color-on-primary-container: #030064;
--color-surface-primary-container: #e0dfff;
--color-brand-active: #4440bf;
--color-border-outline: #767680;
--color-border-outline-variant: #c5c5d0;
--color-surface-primary-container: #e0dfff;
--color-badge: #0b6513;
--background-color-badge: #d3ffd2;
@include isMobile {
--editor-container-padding: 0.75rem;
}
@media screen and (min-device-width: 1921px) {
--lg-button-size: 2.5rem;
--lg-icon-size: 1.25rem;
--default-button-size: 2.25rem;
--default-icon-size: 1.25rem;
}
&.theme--dark {
&.theme--dark-background-none {
background: none;
}
}
&.theme--dark {
--theme-filter: invert(93%) hue-rotate(180deg);
--button-destructive-bg-color: #5a0000;
--button-destructive-color: #{$color-red-3};
--button-gray-1: #363636;
--button-gray-2: #272727;
--button-gray-3: #222;
--mobile-action-button-bg: var(--island-bg-color);
--mobile-color-border: rgba(255, 255, 255, 0.85);
--button-special-active-bg-color: #204624;
--dialog-border-color: var(--color-gray-80);
--dropdown-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="292.4" height="292.4" viewBox="0 0 292 292"><path 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"/></svg>');
--focus-highlight-color: #{$color-blue-6};
--icon-green-fill-color: #{$color-green-4};
--default-bg-color: #121212;
--input-bg-color: #121212;
--input-border-color: #2e2e2e;
--input-hover-bg-color: #181818;
--input-label-color: #{$color-gray-2};
--island-bg-color: #232329;
--island-bg-color-alt: hsl(240, 12%, 12%);
--keybinding-color: var(--color-gray-60);
--link-color: #{$color-blue-4};
--overlay-bg-color: #{color.adjust($color-gray-8, $alpha: -0.88)};
--popup-secondary-bg-color: #222;
--popup-text-color: #{$color-gray-4};
--popup-text-inverted-color: #2c2c2c;
--select-highlight-color: #{$color-blue-4};
--modal-shadow: 0px 100px 80px rgba(0, 0, 0, 0.07),
0px 41.7776px 33.4221px rgba(0, 0, 0, 0.0503198),
0px 22.3363px 17.869px rgba(0, 0, 0, 0.0417275),
0px 12.5216px 10.0172px rgba(0, 0, 0, 0.035),
0px 6.6501px 5.32008px rgba(0, 0, 0, 0.0282725),
0px 2.76726px 2.21381px rgba(0, 0, 0, 0.0196802);
--avatar-border-color: var(--color-gray-85);
--scrollbar-thumb: #{$color-gray-8};
--scrollbar-thumb-hover: #{$color-gray-7};
--color-slider-track: hsl(244, 23%, 39%);
// will be inverted to a lighter color.
--color-selection: #b4b0ff;
--color-icon-white: var(--color-gray-90);
--color-primary: #a8a5ff;
--color-primary-darker: #b2aeff;
--color-primary-darkest: #beb9ff;
--color-primary-light: #4f4d6f;
--color-primary-light-darker: #43415e;
--color-primary-hover: #bbb8ff;
--color-disabled: var(--color-gray-70);
--color-text-warning: var(--color-gray-80);
--color-danger: #ffa8a5;
--color-danger-dark: #672120;
--color-danger-darker: #8f2625;
--color-danger-darkest: #ac2b29;
--color-danger-text: #fbcbcc;
--color-danger-background: #fbcbcc;
--color-danger-icon-background: #672120;
--color-danger-color: #261919;
--color-danger-icon-color: #fbcbcc;
--color-warning-background: var(--color-warning);
--color-warning-icon-background: var(--color-warning-dark);
--color-warning-color: var(--color-gray-80);
--color-warning-icon-color: var(--color-gray-80);
--color-muted: var(--color-gray-80);
--color-muted-darker: var(--color-gray-60);
--color-muted-darkest: var(--color-gray-20);
--color-muted-background: var(--color-gray-40);
--color-muted-background-darker: var(--color-gray-20);
--color-logo-text: #e2dfff;
--color-surface-high: #2e2d39;
--color-surface-low: hsl(240, 8%, 15%);
--color-surface-mid: hsl(240 6% 10%);
--color-surface-lowest: hsl(0, 0%, 7%);
--color-on-surface: #e3e3e8;
--color-brand-hover: #bbb8ff;
--color-on-primary-container: #e0dfff;
--color-surface-primary-container: #403e6a;
--color-brand-active: #d0ccff;
--color-border-outline: #8e8d9c;
--color-border-outline-variant: #46464f;
--color-surface-primary-container: #403e6a;
}
}

View File

@@ -0,0 +1,252 @@
$color-red-1: #ffe3e3;
$color-red-3: #ffa8a8;
$color-red-6: #fa5252;
$color-red-7: #f03e3e;
$color-red-8: #e03131;
$color-red-9: #c92a2a;
$color-gray-1: #f1f3f5;
$color-gray-2: #e9ecef;
$color-gray-4: #ced4da;
$color-gray-5: #adb5bd;
$color-gray-6: #868e96;
$color-gray-7: #495057;
$color-gray-8: #343a40;
$color-green-0: #ebfbee;
$color-green-2: #b2f2bb;
$color-green-4: #69db7c;
$color-green-9: #2b8a3e;
$color-blue-1: #d0ebff;
$color-blue-2: #a5d8ff;
$color-blue-4: #4dabf7;
$color-blue-5: #339af0;
$color-blue-6: #228be6;
$color-blue-7: #1c7ed6;
$color-blue-8: #1971c2;
@mixin isMobile() {
@at-root .excalidraw--mobile#{&} {
@content;
}
}
// the .ToolIcon_type_toggle.ToolIcon--checked selectors use both classes so
// they keep outranking the :hover/:active rules below
@mixin toolbarButtonColorStates {
&.fillable {
&.ToolIcon_type_toggle.ToolIcon--checked .ToolIcon__icon {
--icon-fill-color: var(--color-on-primary-container);
svg {
fill: var(--icon-fill-color);
}
}
}
&.ToolIcon_type_toggle.ToolIcon--checked .ToolIcon__icon {
background: var(--color-surface-primary-container);
--keybinding-color: var(--color-on-primary-container);
svg {
color: var(--color-on-primary-container);
}
}
.ToolIcon__keybinding {
bottom: 4px;
right: 4px;
}
.ToolIcon__icon {
&:hover {
background: var(--button-hover-bg);
}
&:active {
background: var(--button-hover-bg);
border: 1px solid var(--button-active-border);
svg {
color: var(--color-on-primary-container);
}
}
&[aria-disabled="true"] {
background: initial;
border: none;
svg {
color: var(--color-disabled);
}
}
}
// disabled toggle buttons (e.g. non-forced tools while `props.activeTool`
// is set) — greyed out, without hover/active affordances
&.ToolIcon_type_toggle:disabled {
cursor: default;
--keybinding-color: var(--color-disabled);
.ToolIcon__icon {
svg {
color: var(--color-disabled);
}
&:hover,
&:active {
background: initial;
border: none;
svg {
color: var(--color-disabled);
}
}
}
}
}
@mixin outlineButtonStyles {
display: flex;
justify-content: center;
align-items: center;
padding: 0.625rem;
width: var(--button-width, var(--default-button-size));
height: var(--button-height, var(--default-button-size));
box-sizing: border-box;
border-width: 1px;
border-style: solid;
border-color: var(--button-border, var(--default-border-color));
border-radius: var(--border-radius-lg);
cursor: pointer;
background-color: var(--button-bg, var(--island-bg-color));
color: var(--button-color, var(--color-on-surface));
font-family: var(--ui-font);
svg {
width: var(--button-width, var(--lg-icon-size));
height: var(--button-height, var(--lg-icon-size));
}
&:hover {
background-color: var(--button-hover-bg, var(--island-bg-color));
border-color: var(
--button-hover-border,
var(--button-border, var(--default-border-color))
);
color: var(
--button-hover-color,
var(--button-color, var(--text-primary-color, inherit))
);
}
&:active {
background-color: var(--button-active-bg, var(--island-bg-color));
border-color: var(--button-active-border, var(--color-primary-darkest));
}
&.active,
&.ToolIcon--checked {
background-color: var(
--button-selected-bg,
var(--color-surface-primary-container)
);
border-color: var(
--button-selected-border,
var(--color-surface-primary-container)
);
&:hover {
background-color: var(
--button-selected-hover-bg,
var(--color-surface-primary-container)
);
}
svg {
color: var(--button-color, var(--color-on-primary-container));
}
}
@include isMobile() {
width: var(--mobile-action-button-size, var(--default-button-size));
height: var(--mobile-action-button-size, var(--default-button-size));
}
}
@mixin buttonNoHighlight {
-webkit-tap-highlight-color: transparent;
-webkit-touch-callout: none;
user-select: none;
}
@mixin outlineButtonIconStyles {
@include outlineButtonStyles;
& {
padding: 0;
}
svg {
width: var(--default-icon-size);
height: var(--default-icon-size);
}
}
@mixin avatarStyles {
width: var(--avatar-size, 1.5rem);
height: var(--avatar-size, 1.5rem);
position: relative;
border-radius: 100%;
outline-offset: 2px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
font-size: 0.75rem;
font-weight: 700;
line-height: 1;
color: var(--color-gray-90);
flex: 0 0 auto;
&:active {
transform: scale(0.94);
}
&-img {
width: 100%;
height: 100%;
border-radius: 100%;
}
&::before {
content: "";
position: absolute;
top: -3px;
right: -3px;
bottom: -3px;
left: -3px;
border-radius: 100%;
}
&.is-followed::before {
border-color: var(--color-primary-hover);
box-shadow: 0 0 0 1px var(--color-primary-hover);
}
&.is-current-user {
cursor: auto;
}
}
@mixin filledButtonOnCanvas {
border: none;
box-shadow: 0 0 0 1px var(--color-surface-lowest);
background-color: var(--color-surface-low);
&:active {
box-shadow: 0 0 0 1px var(--color-brand-active);
}
@include isMobile() {
width: var(--mobile-action-button-size, 2rem);
height: var(--mobile-action-button-size, 2rem);
}
}