mirror of
https://github.com/ParkerTenBroeck/automata.git
synced 2026-06-06 21:24:06 -04:00
semi finalized theming
This commit is contained in:
parent
c12f7b325f
commit
620415c824
14 changed files with 728 additions and 224 deletions
|
|
@ -5,7 +5,7 @@
|
|||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>Automata</title>
|
||||
|
||||
|
||||
<link href="style.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
|
|
@ -28,20 +28,48 @@
|
|||
<div class="vSplit" style="--split-default: 20%" title="Drag to resize canvas width"></div>
|
||||
|
||||
<section>
|
||||
meow
|
||||
<button id="themeToggle" class="btn btn-grey" title="Toggle light/dark">
|
||||
🌙 Dark
|
||||
</button>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<div class="hSplit" style="--split-default: 50%" title="Drag to resize canvas height"></div>
|
||||
|
||||
<div style="padding-bottom: 10px;">
|
||||
<div>
|
||||
<button id="togglePhysics">Toggle Physics</button>
|
||||
<button id="resetLayout">Reset Layout</button>
|
||||
<div style="padding-bottom: 10px">
|
||||
<div class="controls" style="background: var(--bg-0)">
|
||||
<button id="togglePhysics" class="btn btn-toggle active" title="Toggle physics layout">
|
||||
Physics: ON
|
||||
</button>
|
||||
|
||||
<button id="resetLayout" class="btn btn-grey" title="Re-enable physics on nodes and reset layout">
|
||||
Reset Layout
|
||||
</button>
|
||||
|
||||
<span class="spacer"></span>
|
||||
|
||||
<button id="resetSim" class="btn btn-blue" title="Stop and reset simulation">
|
||||
⟲ Reset
|
||||
</button>
|
||||
<button id="playPause" class="btn btn-green" title="Run / pause simulation">
|
||||
▶ Play
|
||||
</button>
|
||||
|
||||
<button id="step" class="btn btn-grey" title="Advance one step">
|
||||
Step
|
||||
</button>
|
||||
|
||||
<label class="speed">
|
||||
Speed
|
||||
<input id="speed" type="range" min="1" max="60" value="10" />
|
||||
<span id="speedLabel">10x</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="hSplit styleOnly" title="Drag to resize canvas height"></div>
|
||||
|
||||
<div style="height:100%">
|
||||
<div class="vscroll">
|
||||
<div id="editor" class="editor"></div>
|
||||
<div id="editor" class="editor"></div>
|
||||
</div>
|
||||
|
||||
<div class="vSplit" style="--split-default: 40%" title="Drag to resize terminal/editor width"></div>
|
||||
|
|
|
|||
116
web/root/src/controls.ts
Normal file
116
web/root/src/controls.ts
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
import {nodes, edges, network} from "./visualizer.ts"
|
||||
|
||||
const togglePhysicsBtn = document.getElementById("togglePhysics") as HTMLButtonElement;
|
||||
const resetLayoutBtn = document.getElementById("resetLayout") as HTMLButtonElement;
|
||||
const playPauseBtn = document.getElementById("playPause") as HTMLButtonElement;
|
||||
const stepBtn = document.getElementById("step") as HTMLButtonElement;
|
||||
const speedSlider = document.getElementById("speed") as HTMLInputElement;
|
||||
const speedLabel = document.getElementById("speedLabel") as HTMLSpanElement;
|
||||
const resetSimBtn = document.getElementById("resetSim") as HTMLButtonElement;
|
||||
|
||||
|
||||
function stepSimulation(): void {
|
||||
console.log("step");
|
||||
}
|
||||
|
||||
function resetSimulation(): void {
|
||||
console.log("reset");
|
||||
}
|
||||
|
||||
// ---- Physics toggle (styled label) ----
|
||||
function setPhysicsButtonUI(enabled: boolean) {
|
||||
togglePhysicsBtn.classList.toggle("active", enabled);
|
||||
togglePhysicsBtn.textContent = enabled ? "Physics: ON" : "Physics: OFF";
|
||||
}
|
||||
|
||||
togglePhysicsBtn.onclick = () => {
|
||||
const enabled = !togglePhysicsBtn.classList.contains("active");
|
||||
setPhysicsButtonUI(enabled);
|
||||
network.setOptions({ physics: { enabled } });
|
||||
};
|
||||
|
||||
setPhysicsButtonUI(togglePhysicsBtn.classList.contains("active"));
|
||||
|
||||
resetLayoutBtn.onclick = () => {
|
||||
try {
|
||||
nodes.forEach((n) => {
|
||||
n.physics = true;
|
||||
n.x = undefined;
|
||||
n.y = undefined;
|
||||
});
|
||||
network.setData({ nodes, edges });
|
||||
} catch {
|
||||
// Last resort
|
||||
network.setData({ nodes, edges });
|
||||
}
|
||||
|
||||
// If physics button is OFF, keep it OFF (don’t surprise the user)
|
||||
const physicsEnabled = togglePhysicsBtn.classList.contains("active");
|
||||
network.setOptions({ physics: { enabled: physicsEnabled } });
|
||||
};
|
||||
|
||||
// ---- Play/Pause + Speed ----
|
||||
let running = false;
|
||||
let timer: number | null = null;
|
||||
|
||||
// speed slider is "steps per second"
|
||||
function getStepsPerSecond() {
|
||||
return Math.max(1, Math.min(60, Number(speedSlider.value) || 10));
|
||||
}
|
||||
function updateSpeedUI() {
|
||||
speedLabel.textContent = `${getStepsPerSecond()}×`;
|
||||
}
|
||||
updateSpeedUI();
|
||||
|
||||
speedSlider.addEventListener("input", () => {
|
||||
updateSpeedUI();
|
||||
if (running) restartTimer();
|
||||
});
|
||||
|
||||
function stopTimer() {
|
||||
if (timer !== null) {
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function restartTimer() {
|
||||
stopTimer();
|
||||
const sps = getStepsPerSecond();
|
||||
const intervalMs = Math.round(1000 / sps);
|
||||
|
||||
timer = globalThis.window.setInterval(() => {
|
||||
// If your step can throw, keep the interval alive:
|
||||
try { stepSimulation(); } catch (e) { console.error(e); }
|
||||
}, intervalMs);
|
||||
}
|
||||
|
||||
function setRunning(on: boolean) {
|
||||
running = on;
|
||||
playPauseBtn.textContent = running ? "⏸ Pause" : "▶ Play";
|
||||
playPauseBtn.classList.toggle("btn-primary", !running);
|
||||
playPauseBtn.classList.toggle("btn-secondary", running);
|
||||
|
||||
// Disable step while running (optional, but feels nice)
|
||||
stepBtn.disabled = running;
|
||||
|
||||
if (running) restartTimer();
|
||||
else stopTimer();
|
||||
}
|
||||
|
||||
playPauseBtn.onclick = () => setRunning(!running);
|
||||
|
||||
stepBtn.onclick = () => {
|
||||
stepSimulation();
|
||||
};
|
||||
|
||||
resetSimBtn.onclick = () => {
|
||||
// Stop if running
|
||||
if (running) setRunning(false);
|
||||
|
||||
// Reset
|
||||
resetSimulation();
|
||||
|
||||
// Optional: re-enable Step after reset
|
||||
stepBtn.disabled = false;
|
||||
};
|
||||
|
|
@ -8,13 +8,13 @@ import {
|
|||
ViewPlugin,
|
||||
lineNumbers,
|
||||
highlightActiveLineGutter,
|
||||
highlightActiveLine
|
||||
} from "npm:@codemirror/view";
|
||||
|
||||
import { EditorState, StateField, Text } from "npm:@codemirror/state";
|
||||
import { defaultKeymap, history, historyKeymap } from "npm:@codemirror/commands";
|
||||
import { bracketMatching, indentOnInput } from "npm:@codemirror/language";
|
||||
import { closeBrackets } from "npm:@codemirror/autocomplete";
|
||||
import { oneDark } from "npm:@codemirror/theme-one-dark";
|
||||
|
||||
|
||||
import wasm from "./wasm.ts"
|
||||
|
|
@ -292,9 +292,9 @@ const state = EditorState.create({
|
|||
history(),
|
||||
indentOnInput(),
|
||||
bracketMatching(),
|
||||
highlightActiveLine(),
|
||||
closeBrackets(),
|
||||
keymap.of([...defaultKeymap, ...historyKeymap]),
|
||||
oneDark,
|
||||
|
||||
analysisField,
|
||||
diagHover,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
|
||||
import "./editor.ts"
|
||||
import "./visualizer.ts"
|
||||
import "./splitters.ts"
|
||||
import "./splitters.ts"
|
||||
import "./controls.ts"
|
||||
import "./theme.ts"
|
||||
|
|
@ -65,7 +65,7 @@ function setFlexFill(pane: HTMLElement) {
|
|||
|
||||
export function enableFlexSplitters() {
|
||||
// Horizontal: A | hSplit | B (top/split/bottom)
|
||||
for (const splitter of document.querySelectorAll<HTMLElement>(".hSplit")) {
|
||||
for (const splitter of document.querySelectorAll<HTMLElement>(".hSplit:not(.styleOnly)")) {
|
||||
const parent = splitter.parentElement as HTMLElement | null;
|
||||
if (!parent) continue;
|
||||
|
||||
|
|
@ -133,7 +133,7 @@ export function enableFlexSplitters() {
|
|||
}
|
||||
|
||||
// Vertical: A | vSplit | B (left/split/right)
|
||||
for (const splitter of document.querySelectorAll<HTMLElement>(".vSplit")) {
|
||||
for (const splitter of document.querySelectorAll<HTMLElement>(".vSplit:not(.styleOnly)")) {
|
||||
const parent = splitter.parentElement as HTMLElement | null;
|
||||
if (!parent) continue;
|
||||
|
||||
|
|
@ -158,7 +158,7 @@ export function enableFlexSplitters() {
|
|||
// --split-default: 30% (right pane width)
|
||||
// --split-min-a: 220px (min left)
|
||||
// --split-min-b: 220px (min right)
|
||||
const defPct = getVarPct(splitter, "--split-default", 30);
|
||||
const defPct = getVarPct(splitter, "--split-default", 50);
|
||||
const minA = getVarPx(splitter, "--split-min-a", 220);
|
||||
const minB = getVarPx(splitter, "--split-min-b", 220);
|
||||
|
||||
|
|
|
|||
75
web/root/src/theme.ts
Normal file
75
web/root/src/theme.ts
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import { network } from "./visualizer.ts";
|
||||
|
||||
function cssVar(name: string, fallback = ""): string {
|
||||
return getComputedStyle(document.documentElement)
|
||||
.getPropertyValue(name)
|
||||
.trim() || fallback;
|
||||
}
|
||||
|
||||
const themeBtn = document.getElementById("themeToggle") as HTMLButtonElement;
|
||||
|
||||
type Theme = "dark" | "light";
|
||||
|
||||
function getPreferredTheme(): Theme {
|
||||
// 1) saved preference
|
||||
const saved = localStorage.getItem("theme");
|
||||
if (saved === "dark" || saved === "light") return saved;
|
||||
|
||||
// 2) OS preference
|
||||
const prefersLight = globalThis.window.matchMedia?.(
|
||||
"(prefers-color-scheme: light)",
|
||||
)?.matches;
|
||||
return prefersLight ? "light" : "dark";
|
||||
}
|
||||
|
||||
function setTheme(theme: Theme) {
|
||||
document.documentElement.dataset.theme = theme;
|
||||
localStorage.setItem("theme", theme);
|
||||
|
||||
// update button label
|
||||
themeBtn.textContent = theme === "dark" ? "🌙 Dark" : "☀️ Light";
|
||||
applyGraphTheme();
|
||||
}
|
||||
|
||||
function toggleTheme() {
|
||||
const current = (document.documentElement.dataset.theme as Theme) || "dark";
|
||||
setTheme(current === "dark" ? "light" : "dark");
|
||||
}
|
||||
|
||||
// init
|
||||
setTheme(getPreferredTheme());
|
||||
|
||||
// click handler
|
||||
themeBtn.addEventListener("click", toggleTheme);
|
||||
|
||||
// optional: respond to OS theme changes (only if user hasn't chosen a theme)
|
||||
globalThis.window.matchMedia?.("(prefers-color-scheme: light)")
|
||||
?.addEventListener("change", () => {
|
||||
if (localStorage.getItem("theme")) return; // user has chosen, don't override
|
||||
setTheme(getPreferredTheme());
|
||||
});
|
||||
|
||||
export function applyGraphTheme() {
|
||||
network.setOptions({
|
||||
nodes: {
|
||||
color: {
|
||||
background: cssVar("--graph-node-bg"),
|
||||
border: cssVar("--graph-node-border"),
|
||||
highlight: {
|
||||
background: cssVar("--graph-node-active-bg"),
|
||||
border: cssVar("--graph-node-active-border"),
|
||||
},
|
||||
},
|
||||
font: {
|
||||
color: cssVar("--graph-node-text"),
|
||||
},
|
||||
},
|
||||
edges: {
|
||||
color: {
|
||||
color: cssVar("--graph-edge"),
|
||||
highlight: cssVar("--graph-edge-active"),
|
||||
hover: cssVar("--graph-edge-hover"),
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -3,11 +3,8 @@
|
|||
// deno-lint-ignore no-import-prefix
|
||||
import * as vis from "npm:vis-network/standalone";
|
||||
|
||||
|
||||
|
||||
const nodes = new vis.DataSet<vis.Node>();
|
||||
const edges = new vis.DataSet<vis.Edge>();
|
||||
|
||||
export const nodes = new vis.DataSet<vis.Node>();
|
||||
export const edges = new vis.DataSet<vis.Edge>();
|
||||
|
||||
const automaton = {
|
||||
states: ["q0", "q1"],
|
||||
|
|
@ -18,29 +15,29 @@ const automaton = {
|
|||
{
|
||||
from: "q0",
|
||||
to: "q0",
|
||||
label: "ε, z0 → A z0\n"
|
||||
label: "ε, z0 → A z0\n",
|
||||
},
|
||||
{
|
||||
from: "q0",
|
||||
to: "q0",
|
||||
label: "ε, z0 → B z0"
|
||||
label: "ε, z0 → B z0",
|
||||
},
|
||||
{
|
||||
from: "q0",
|
||||
to: "q1",
|
||||
label: "ε, z0 → z0"
|
||||
label: "ε, z0 → z0",
|
||||
},
|
||||
{
|
||||
from: "q1",
|
||||
to: "q1",
|
||||
label: "a, A → ε"
|
||||
label: "a, A → ε",
|
||||
},
|
||||
{
|
||||
from: "q1",
|
||||
to: "q1",
|
||||
label: "b, B → ε"
|
||||
}
|
||||
]
|
||||
label: "b, B → ε",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
function renderNode({
|
||||
|
|
@ -57,7 +54,6 @@ function renderNode({
|
|||
ctx.save();
|
||||
const r = style.size;
|
||||
|
||||
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, r, 0, 2 * Math.PI);
|
||||
ctx.fillStyle = "red";
|
||||
|
|
@ -67,29 +63,27 @@ function renderNode({
|
|||
ctx.stroke();
|
||||
|
||||
ctx.fillStyle = "black";
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textAlign = "center";
|
||||
ctx.fillText(label, x, y, r);
|
||||
|
||||
|
||||
ctx.textAlign = 'center';
|
||||
ctx.strokeStyle = 'white';
|
||||
ctx.textAlign = "center";
|
||||
ctx.strokeStyle = "white";
|
||||
ctx.fillStyle = "black";
|
||||
let cy = y - (r + 10);
|
||||
for (const part of "meow[]\nbeeep".split("\n").reverse()) {
|
||||
const metrics = ctx.measureText(part);
|
||||
cy -= metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
|
||||
cy -= metrics.actualBoundingBoxAscent +
|
||||
metrics.actualBoundingBoxDescent;
|
||||
ctx.strokeText(part, x, cy);
|
||||
ctx.fillText(part, x, cy);
|
||||
}
|
||||
|
||||
|
||||
ctx.restore();
|
||||
},
|
||||
nodeDimensions: { width: 20, height: 20 },
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Populate nodes
|
||||
for (const state of automaton.states) {
|
||||
nodes.add({
|
||||
|
|
@ -104,24 +98,31 @@ automaton.transitions.forEach((t, i) => {
|
|||
id: `e${i}`,
|
||||
from: t.from,
|
||||
to: t.to,
|
||||
label: t.label
|
||||
label: t.label,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function chosen_edge(_: vis.ChosenNodeValues, id: vis.IdType,selected: boolean, hovered: boolean) {
|
||||
console.log("edge", id, selected, hovered)
|
||||
function chosen_edge(
|
||||
_: vis.ChosenNodeValues,
|
||||
id: vis.IdType,
|
||||
selected: boolean,
|
||||
hovered: boolean,
|
||||
) {
|
||||
console.log("edge", id, selected, hovered);
|
||||
}
|
||||
|
||||
function chosen_node(_: vis.ChosenNodeValues, id: vis.IdType,selected: boolean, hovered: boolean) {
|
||||
console.log("node", id, selected, hovered)
|
||||
function chosen_node(
|
||||
_: vis.ChosenNodeValues,
|
||||
id: vis.IdType,
|
||||
selected: boolean,
|
||||
hovered: boolean,
|
||||
) {
|
||||
console.log("node", id, selected, hovered);
|
||||
}
|
||||
|
||||
|
||||
const network: vis.Network = createGraph();
|
||||
export const network: vis.Network = createGraph();
|
||||
|
||||
function createGraph(): vis.Network {
|
||||
|
||||
const container = document.getElementById("graph")!;
|
||||
|
||||
const network = new vis.Network(
|
||||
|
|
@ -129,13 +130,15 @@ function createGraph(): vis.Network {
|
|||
{ nodes, edges },
|
||||
{
|
||||
layout: { improvedLayout: true },
|
||||
autoResize: true,
|
||||
width: "99%",
|
||||
physics: {
|
||||
enabled: true,
|
||||
solver: "barnesHut",
|
||||
barnesHut: { gravitationalConstant: -8000, springLength: 120, springConstant: 0.04 },
|
||||
stabilization: { iterations: 200 }
|
||||
barnesHut: {
|
||||
gravitationalConstant: -8000,
|
||||
springLength: 120,
|
||||
springConstant: 0.04,
|
||||
},
|
||||
stabilization: { iterations: 200 },
|
||||
},
|
||||
interaction: {
|
||||
dragNodes: true,
|
||||
|
|
@ -149,7 +152,7 @@ function createGraph(): vis.Network {
|
|||
color: {
|
||||
background: "#1f6feb",
|
||||
border: "#79c0ff",
|
||||
highlight: { background: "#388bfd", border: "#a5d6ff" }
|
||||
highlight: { background: "#388bfd", border: "#a5d6ff" },
|
||||
},
|
||||
// @ts-expect-error bad library
|
||||
chosen: {
|
||||
|
|
@ -162,7 +165,7 @@ function createGraph(): vis.Network {
|
|||
edges: {
|
||||
chosen: {
|
||||
// @ts-expect-error bad library
|
||||
edge: chosen_edge
|
||||
edge: chosen_edge,
|
||||
},
|
||||
arrowStrikethrough: false,
|
||||
font: { align: "middle", color: "#000000ff" },
|
||||
|
|
@ -170,20 +173,19 @@ function createGraph(): vis.Network {
|
|||
// @ts-expect-error bad library
|
||||
smooth: { type: "dynamic" },
|
||||
arrows: "to",
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
);
|
||||
vis.DataSet
|
||||
vis.DataSet;
|
||||
|
||||
network.on("doubleClick", (params: any) => {
|
||||
|
||||
for (const node_id of params.nodes){
|
||||
for (const node_id of params.nodes) {
|
||||
// @ts-expect-error bad library
|
||||
const node: vis.Node = nodes.get(node_id)!;
|
||||
node.physics = !node.physics;
|
||||
nodes.update(node)
|
||||
nodes.update(node);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return network;
|
||||
}
|
||||
}
|
||||
99
web/root/style/controls.scss
Normal file
99
web/root/style/controls.scss
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
.controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-2);
|
||||
padding: var(--space-2);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.controls .spacer {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.btn {
|
||||
appearance: none;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
color: var(--fg-0);
|
||||
padding: 8px 12px;
|
||||
border-radius: var(--radius-md);
|
||||
font:
|
||||
600 13px/1.1
|
||||
var(--font-ui);
|
||||
cursor: pointer;
|
||||
|
||||
transition:
|
||||
transform 0.04s ease,
|
||||
background var(--dur-med) var(--ease-standard),
|
||||
border-color var(--dur-med) var(--ease-standard),
|
||||
opacity var(--dur-med) var(--ease-standard);
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.10);
|
||||
border-color: rgba(255, 255, 255, 0.20);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.45;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-green {
|
||||
background: color-mix(in srgb, var(--success) 18%, transparent);
|
||||
border-color: color-mix(in srgb, var(--success) 35%, transparent);
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb, var(--success) 26%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-blue {
|
||||
background: color-mix(in srgb, var(--accent) 14%, transparent);
|
||||
border-color: color-mix(in srgb, var(--accent) 40%, transparent);
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb, var(--accent) 22%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-grey {
|
||||
background: color-mix(in srgb, var(--accent) 12%, transparent);
|
||||
border-color: color-mix(in srgb, var(--accent) 28%, transparent);
|
||||
|
||||
&:hover {
|
||||
background: color-mix(in srgb, var(--accent) 18%, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.btn-toggle.active {
|
||||
background: color-mix(in srgb, var(--warning) 14%, transparent);
|
||||
border-color: color-mix(in srgb, var(--warning) 30%, transparent);
|
||||
}
|
||||
|
||||
|
||||
.speed {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-1);
|
||||
padding: 6px 10px;
|
||||
border: 1px solid rgba(255, 255, 255, 0.12);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border-radius: var(--radius-md);
|
||||
font: 600 12.5px var(--font-ui);
|
||||
color: var(--fg-0);
|
||||
}
|
||||
|
||||
.speed input[type="range"] {
|
||||
width: 160px;
|
||||
}
|
||||
|
||||
.speed #speedLabel {
|
||||
min-width: 40px;
|
||||
text-align: right;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
|
@ -1,126 +1,131 @@
|
|||
@use "tooltip.scss";
|
||||
|
||||
/* Editor layout */
|
||||
|
||||
.editor {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
|
||||
.cm-editor {
|
||||
height: 100%;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.cm-scroller {
|
||||
overflow-y: auto !important;
|
||||
overflow-y: auto !important;
|
||||
background: var(--bg-0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
.diag {
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
.cm-editor {
|
||||
height: 100%;
|
||||
background: var(--bg-1);
|
||||
color: var(--fg-0);
|
||||
}
|
||||
|
||||
.diag li {
|
||||
margin: 6px 0;
|
||||
.cm-gutters {
|
||||
background: var(--bg-2) !important;
|
||||
color: var(--fg-muted);
|
||||
border-right: 1px solid color-mix(in srgb, var(--fg-muted) 20%, transparent)!important;
|
||||
}
|
||||
|
||||
/* --- Syntax colors via CSS classes applied by decorations --- */
|
||||
.cm-lineNumbers .cm-gutterElement {
|
||||
padding: 0 10px 0 6px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.cm-activeLine {
|
||||
background: color-mix(in srgb, var(--accent) 6%, transparent)!important;
|
||||
}
|
||||
|
||||
.cm-activeLineGutter {
|
||||
background: color-mix(in srgb, var(--accent) 8%, transparent)!important;
|
||||
color: var(--fg-0);
|
||||
}
|
||||
|
||||
.cm-cursor {
|
||||
border-left: 2px solid var(--accent)!important;
|
||||
}
|
||||
|
||||
.cm-focused .cm-cursor {
|
||||
border-left-color: var(--accent)!important;
|
||||
}
|
||||
|
||||
/* Syntax colors */
|
||||
|
||||
.tok-comment {
|
||||
color: #1a7b24;
|
||||
color: color-mix(in srgb, var(--success) 65%, var(--fg-muted));
|
||||
}
|
||||
|
||||
.tok-keyword {
|
||||
color: #b99400;
|
||||
font-weight: 600;
|
||||
color: var(--warning);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tok-error {
|
||||
color: #ff0505;
|
||||
font-weight: 1000;
|
||||
color: var(--error);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.tok-ident {
|
||||
color: #90d4e0;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.tok-brace {
|
||||
color: #d73a49;
|
||||
font-weight: 600;
|
||||
color: var(--error);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tok-punc {
|
||||
color: #ffffff;
|
||||
color: var(--fg-0);
|
||||
}
|
||||
|
||||
.tok-string {
|
||||
color: #03621e;
|
||||
color: color-mix(in srgb, var(--success) 75%, transparent);
|
||||
}
|
||||
|
||||
/* Rainbow bracket depth classes */
|
||||
/* Rainbow brackets */
|
||||
|
||||
.rb-0 {
|
||||
color: #a35;
|
||||
font-weight: 700;
|
||||
color: color-mix(in srgb, var(--error) 85%, transparent);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.rb-1 {
|
||||
color: #ed0;
|
||||
font-weight: 700;
|
||||
color: color-mix(in srgb, var(--warning) 85%, transparent);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.rb-2 {
|
||||
color: #9d5;
|
||||
font-weight: 700;
|
||||
color: color-mix(in srgb, var(--success) 85%, transparent);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.rb-3 {
|
||||
color: #2cb;
|
||||
font-weight: 700;
|
||||
color: color-mix(in srgb, var(--accent) 85%, transparent);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.rb-4 {
|
||||
color: #36b;
|
||||
font-weight: 700;
|
||||
color: color-mix(in srgb, var(--focus) 85%, transparent);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.rb-5 {
|
||||
color: #639;
|
||||
font-weight: 700;
|
||||
color: color-mix(in srgb, var(--accent) 60%, var(--fg-muted));
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
/* Severity underline styles*/
|
||||
|
||||
|
||||
/* Optional: diagnostics panel coloring */
|
||||
.diag li.error {
|
||||
color: #d73a49;
|
||||
}
|
||||
|
||||
.diag li.warning {
|
||||
color: #b08800;
|
||||
}
|
||||
|
||||
.diag li.info {
|
||||
color: #0366d6;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/* Severity underline styles */
|
||||
.cm-diag-error {
|
||||
text-decoration: underline wavy #d73a49;
|
||||
/* red */
|
||||
text-underline-offset: 2px;
|
||||
text-decoration: underline wavy var(--error);
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
.cm-diag-warning {
|
||||
text-decoration: underline wavy #ffd33d;
|
||||
/* yellow */
|
||||
text-underline-offset: 2px;
|
||||
text-decoration: underline wavy var(--warning);
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
.cm-diag-info {
|
||||
text-decoration: underline wavy #79c0ff;
|
||||
/* cyan-ish */
|
||||
text-underline-offset: 2px;
|
||||
text-decoration: underline wavy var(--accent);
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,41 +1,41 @@
|
|||
.centered {
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: #f0f0f0;
|
||||
font-size: 24px;
|
||||
font-family: Ubuntu-Light, Helvetica, sans-serif;
|
||||
text-align: center;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: #f0f0f0;
|
||||
font-size: 24px;
|
||||
font-family: Ubuntu-Light, Helvetica, sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.lds-dual-ring {
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.lds-dual-ring:after {
|
||||
content: " ";
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin: 0px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid #fff;
|
||||
border-color: #fff transparent #fff transparent;
|
||||
animation: lds-dual-ring 1.2s linear infinite;
|
||||
content: " ";
|
||||
display: block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin: 0px;
|
||||
border-radius: 50%;
|
||||
border: 3px solid #fff;
|
||||
border-color: #fff transparent #fff transparent;
|
||||
animation: lds-dual-ring 1.2s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes lds-dual-ring {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,46 +1,60 @@
|
|||
@use "editor.scss";
|
||||
@use "terminal.scss";
|
||||
@use "loading.scss";
|
||||
@use "controls.scss";
|
||||
@use "themes.scss";
|
||||
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
font-family: system-ui, sans-serif;
|
||||
background: #909090;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
color: var(--fg-0);
|
||||
font-family: var(--font-ui);
|
||||
background: #909090;
|
||||
}
|
||||
|
||||
.app {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
overflow: hidden;
|
||||
background-color: var(--bg-0);
|
||||
}
|
||||
|
||||
.graph {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #111;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--graph-bg);
|
||||
}
|
||||
|
||||
.vscroll {
|
||||
height: 100%;
|
||||
overflow-x: scroll;
|
||||
height: 100%;
|
||||
overflow-x: scroll;
|
||||
}
|
||||
|
||||
.hSplit {
|
||||
cursor: row-resize;
|
||||
:not( .styleOnly){
|
||||
cursor: col-resize;
|
||||
}
|
||||
height: 8px;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
background: var(--separator-bg);
|
||||
transition:
|
||||
background var(--dur-med) var(--ease-standard),
|
||||
box-shadow var(--dur-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
.vSplit {
|
||||
cursor: col-resize;
|
||||
:not( .styleOnly){
|
||||
cursor: col-resize;
|
||||
}
|
||||
width: 8px;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
background: var(--separator-bg);
|
||||
transition:
|
||||
background var(--dur-med) var(--ease-standard),
|
||||
box-shadow var(--dur-fast) var(--ease-standard);
|
||||
}
|
||||
|
||||
.hSplit:hover,
|
||||
.vSplit:hover {
|
||||
background: rgba(121, 192, 255, 0.25);
|
||||
}
|
||||
.hSplit:hover:not(.styleOnly),
|
||||
.vSplit:hover:not(.styleOnly) {
|
||||
background: var(--separator-hover);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
.terminal {
|
||||
background: #0b0f14;
|
||||
color: #c9d1d9;
|
||||
background: var(--bg-1);
|
||||
color: var(--fg-0);
|
||||
padding: 1em;
|
||||
margin: 0px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12.5px;
|
||||
line-height: 1.35;
|
||||
white-space: pre-wrap;
|
||||
|
|
@ -18,31 +18,31 @@
|
|||
.ansi-bold { font-weight: 700; }
|
||||
.ansi-dim { opacity: 0.7; }
|
||||
|
||||
/* Foreground colors (standard + bright) */
|
||||
.ansi-fg-30 { color: #0b0f14; } /* black */
|
||||
.ansi-fg-31 { color: #ff7b72; } /* red */
|
||||
.ansi-fg-32 { color: #7ee787; } /* green */
|
||||
.ansi-fg-33 { color: #f2cc60; } /* yellow */
|
||||
.ansi-fg-34 { color: #79c0ff; } /* blue */
|
||||
.ansi-fg-35 { color: #d2a8ff; } /* magenta */
|
||||
.ansi-fg-36 { color: #a5d6ff; } /* cyan */
|
||||
.ansi-fg-37 { color: #c9d1d9; } /* white */
|
||||
/* Foreground colors */
|
||||
.ansi-fg-30 { color: var(--ansi-fg-30); }
|
||||
.ansi-fg-31 { color: var(--ansi-fg-31); }
|
||||
.ansi-fg-32 { color: var(--ansi-fg-32); }
|
||||
.ansi-fg-33 { color: var(--ansi-fg-33); }
|
||||
.ansi-fg-34 { color: var(--ansi-fg-34); }
|
||||
.ansi-fg-35 { color: var(--ansi-fg-35); }
|
||||
.ansi-fg-36 { color: var(--ansi-fg-36); }
|
||||
.ansi-fg-37 { color: var(--ansi-fg-37); }
|
||||
|
||||
.ansi-fg-90 { color: #6e7681; } /* bright black / gray */
|
||||
.ansi-fg-91 { color: #ffa198; }
|
||||
.ansi-fg-92 { color: #a6f3a6; }
|
||||
.ansi-fg-93 { color: #ffe082; }
|
||||
.ansi-fg-94 { color: #a5d6ff; }
|
||||
.ansi-fg-95 { color: #e3b8ff; }
|
||||
.ansi-fg-96 { color: #c7f0ff; }
|
||||
.ansi-fg-97 { color: #ffffff; }
|
||||
.ansi-fg-90 { color: var(--ansi-fg-90); }
|
||||
.ansi-fg-91 { color: var(--ansi-fg-91); }
|
||||
.ansi-fg-92 { color: var(--ansi-fg-92); }
|
||||
.ansi-fg-93 { color: var(--ansi-fg-93); }
|
||||
.ansi-fg-94 { color: var(--ansi-fg-94); }
|
||||
.ansi-fg-95 { color: var(--ansi-fg-95); }
|
||||
.ansi-fg-96 { color: var(--ansi-fg-96); }
|
||||
.ansi-fg-97 { color: var(--ansi-fg-97); }
|
||||
|
||||
/* Background colors (optional) */
|
||||
.ansi-bg-40 { background: #0b0f14; }
|
||||
.ansi-bg-41 { background: rgba(255, 123, 114, 0.22); }
|
||||
.ansi-bg-42 { background: rgba(126, 231, 135, 0.18); }
|
||||
.ansi-bg-43 { background: rgba(242, 204, 96, 0.18); }
|
||||
.ansi-bg-44 { background: rgba(121, 192, 255, 0.18); }
|
||||
.ansi-bg-45 { background: rgba(210, 168, 255, 0.18); }
|
||||
.ansi-bg-46 { background: rgba(165, 214, 255, 0.18); }
|
||||
.ansi-bg-47 { background: rgba(201, 209, 217, 0.10); }
|
||||
/* Background colors */
|
||||
.ansi-bg-40 { background: var(--ansi-bg-40); }
|
||||
.ansi-bg-41 { background: var(--ansi-bg-41); }
|
||||
.ansi-bg-42 { background: var(--ansi-bg-42); }
|
||||
.ansi-bg-43 { background: var(--ansi-bg-43); }
|
||||
.ansi-bg-44 { background: var(--ansi-bg-44); }
|
||||
.ansi-bg-45 { background: var(--ansi-bg-45); }
|
||||
.ansi-bg-46 { background: var(--ansi-bg-46); }
|
||||
.ansi-bg-47 { background: var(--ansi-bg-47); }
|
||||
159
web/root/style/themes.scss
Normal file
159
web/root/style/themes.scss
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
:root {
|
||||
color-scheme: dark;
|
||||
}
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root:not([data-theme]) {
|
||||
color-scheme: light;
|
||||
}
|
||||
}
|
||||
|
||||
:root {
|
||||
--space-0: 0;
|
||||
--space-1: 4px;
|
||||
--space-2: 8px;
|
||||
--space-3: 12px;
|
||||
--space-4: 16px;
|
||||
--space-5: 24px;
|
||||
--space-6: 32px;
|
||||
|
||||
--radius-sm: 6px;
|
||||
--radius-md: 10px;
|
||||
--radius-lg: 14px;
|
||||
|
||||
--font-ui:
|
||||
ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial;
|
||||
--font-mono:
|
||||
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono";
|
||||
|
||||
--dur-fast: 80ms;
|
||||
--dur-med: 160ms;
|
||||
--dur-slow: 240ms;
|
||||
--ease-standard: cubic-bezier(0.2, 0, 0, 1);
|
||||
}
|
||||
|
||||
:root[data-theme="dark"] {
|
||||
color-scheme: dark;
|
||||
|
||||
--bg-0: #0d1117;
|
||||
--bg-1: #161b22;
|
||||
--bg-2: #21262d;
|
||||
|
||||
--fg-0: #e6edf3;
|
||||
--fg-1: #c9d1d9;
|
||||
--fg-muted: #8b949e;
|
||||
|
||||
--separator-bg: rgba(255, 255, 255, 0.06);
|
||||
--separator-hover: rgba(121, 192, 255, 0.28);
|
||||
--separator-active: rgba(121, 192, 255, 0.45);
|
||||
|
||||
--accent: #79c0ff;
|
||||
--focus: #388bfd;
|
||||
--success: #2ea043;
|
||||
--warning: #f2cc60;
|
||||
--error: #f85149;
|
||||
|
||||
--graph-bg: var(--bg-0);
|
||||
|
||||
--graph-node-bg: #1f6feb;
|
||||
--graph-node-border: #388bfd;
|
||||
--graph-node-text: #e6edf3;
|
||||
|
||||
--graph-node-active-bg: #79c0ff;
|
||||
--graph-node-active-border: #a5d6ff;
|
||||
|
||||
--graph-edge: rgba(201, 209, 217, 0.55);
|
||||
--graph-edge-hover: #79c0ff;
|
||||
--graph-edge-active: #a5d6ff;
|
||||
|
||||
|
||||
--ansi-fg-30: #0b0f14; /* black */
|
||||
--ansi-fg-31: #ff7b72; /* red */
|
||||
--ansi-fg-32: #7ee787; /* green */
|
||||
--ansi-fg-33: #f2cc60; /* yellow */
|
||||
--ansi-fg-34: #79c0ff; /* blue */
|
||||
--ansi-fg-35: #d2a8ff; /* magenta */
|
||||
--ansi-fg-36: #a5d6ff; /* cyan */
|
||||
--ansi-fg-37: #c9d1d9; /* white */
|
||||
|
||||
--ansi-fg-90: #6e7681; /* bright black / gray */
|
||||
--ansi-fg-91: #ffa198;
|
||||
--ansi-fg-92: #a6f3a6;
|
||||
--ansi-fg-93: #ffe082;
|
||||
--ansi-fg-94: #a5d6ff;
|
||||
--ansi-fg-95: #e3b8ff;
|
||||
--ansi-fg-96: #c7f0ff;
|
||||
--ansi-fg-97: #ffffff;
|
||||
|
||||
--ansi-bg-40: #0b0f14;
|
||||
--ansi-bg-41: rgba(255, 123, 114, 0.22);
|
||||
--ansi-bg-42: rgba(126, 231, 135, 0.18);
|
||||
--ansi-bg-43: rgba(242, 204, 96, 0.18);
|
||||
--ansi-bg-44: rgba(121, 192, 255, 0.18);
|
||||
--ansi-bg-45: rgba(210, 168, 255, 0.18);
|
||||
--ansi-bg-46: rgba(165, 214, 255, 0.18);
|
||||
--ansi-bg-47: rgba(201, 209, 217, 0.10);
|
||||
}
|
||||
|
||||
:root[data-theme="light"] {
|
||||
color-scheme: light;
|
||||
|
||||
--bg-0: #f6f8fa;
|
||||
--bg-1: #ffffff;
|
||||
--bg-2: #eaeef2;
|
||||
|
||||
--fg-0: #0b1220;
|
||||
--fg-1: #1f2937;
|
||||
--fg-muted: #5b6472;
|
||||
|
||||
--separator-bg: rgba(0, 0, 0, 0.06);
|
||||
--separator-hover: rgba(9, 105, 218, 0.28);
|
||||
--separator-active: rgba(9, 105, 218, 0.45);
|
||||
|
||||
--accent: #1f6feb;
|
||||
--focus: #0969da;
|
||||
--success: #1a7f37;
|
||||
--warning: #9a6700;
|
||||
--error: #cf222e;
|
||||
|
||||
--graph-bg: var(--bg-0);
|
||||
|
||||
--graph-node-bg: #1f6feb;
|
||||
--graph-node-border: #0969da;
|
||||
--graph-node-text: #ffffff;
|
||||
|
||||
--graph-node-active-bg: #54aeff;
|
||||
--graph-node-active-border: #0969da;
|
||||
|
||||
--graph-edge: rgba(31, 41, 55, 0.45);
|
||||
--graph-edge-hover: #0969da;
|
||||
--graph-edge-active: #54aeff;
|
||||
|
||||
|
||||
|
||||
--ansi-fg-30: #111827; /* black */
|
||||
--ansi-fg-31: #b42318; /* red */
|
||||
--ansi-fg-32: #1a7f37; /* green */
|
||||
--ansi-fg-33: #9a6700; /* yellow */
|
||||
--ansi-fg-34: #0969da; /* blue */
|
||||
--ansi-fg-35: #8250df; /* magenta */
|
||||
--ansi-fg-36: #0550ae; /* cyan */
|
||||
--ansi-fg-37: #1f2937; /* white (dark text) */
|
||||
|
||||
--ansi-fg-90: #6b7280; /* bright black / gray */
|
||||
--ansi-fg-91: #cf222e;
|
||||
--ansi-fg-92: #1f883d;
|
||||
--ansi-fg-93: #9a6700;
|
||||
--ansi-fg-94: #0969da;
|
||||
--ansi-fg-95: #8250df;
|
||||
--ansi-fg-96: #0550ae;
|
||||
--ansi-fg-97: #030712;
|
||||
|
||||
--ansi-bg-40: #ffffff;
|
||||
--ansi-bg-41: rgba(180, 35, 24, 0.16);
|
||||
--ansi-bg-42: rgba(26, 127, 55, 0.14);
|
||||
--ansi-bg-43: rgba(154, 103, 0, 0.14);
|
||||
--ansi-bg-44: rgba(9, 105, 218, 0.14);
|
||||
--ansi-bg-45: rgba(130, 80, 223, 0.14);
|
||||
--ansi-bg-46: rgba(5, 80, 174, 0.14);
|
||||
--ansi-bg-47: rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
|
@ -1,32 +1,37 @@
|
|||
.tipTitle.error {
|
||||
color: #d73a49;
|
||||
}
|
||||
.tipTitle {
|
||||
font-weight: 700;
|
||||
margin-bottom: 4px;
|
||||
|
||||
.tipTitle.warning {
|
||||
color: #ffd33d;
|
||||
}
|
||||
&.error {
|
||||
color: var(--error);
|
||||
}
|
||||
|
||||
.tipTitle.info {
|
||||
color: #79c0ff;
|
||||
}
|
||||
&.warning {
|
||||
color: var(--warning);
|
||||
}
|
||||
|
||||
&.info {
|
||||
color: var(--accent);
|
||||
}
|
||||
}
|
||||
|
||||
.cm-tooltip.cm-tooltip-hover {
|
||||
border: 1px solid #ddd;
|
||||
background: black;
|
||||
box-shadow: 0 8px 30px rgba(0, 0, 0, 0.12);
|
||||
border-radius: 10px;
|
||||
padding: 8px 10px;
|
||||
max-width: 420px;
|
||||
font-size: 13px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
border: 1px solid color-mix(in srgb, var(--fg-muted) 35%, transparent);
|
||||
background: var(--bg-1);
|
||||
color: var(--fg-0);
|
||||
|
||||
.tipTitle {
|
||||
font-weight: 700;
|
||||
margin-bottom: 4px;
|
||||
box-shadow:
|
||||
0 8px 30px rgba(0, 0, 0, 0.25);
|
||||
|
||||
border-radius: var(--radius-md);
|
||||
padding: 8px 10px;
|
||||
|
||||
max-width: 420px;
|
||||
font-size: 13px;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.tipBody {
|
||||
white-space: pre-wrap;
|
||||
white-space: pre-wrap;
|
||||
color: var(--fg-1);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue