new featuregit status dark mode

This commit is contained in:
Joeri Exelmans 2025-11-13 19:25:43 +01:00
parent 9646d716c6
commit 6efc27453e
14 changed files with 189 additions and 149 deletions

View file

@ -1,61 +1,44 @@
/* details {
padding-left: 20;
}
summary {
margin-left: -20;
} */
details:has(+ details) {
border-bottom: 1px lightgrey solid;
border-bottom: 1px var(--separator-color) solid;
}
.runtimeState {
padding: 4px;
/* padding-left: 4px;
padding-right: 4px;
padding-top: 2px;
padding-bottom: 2px; */
}
.runtimeState:has(+.runtimeState) {
border-bottom: 1px lightgrey solid;
border-bottom: 1px var(--separator-color) solid;
}
.runtimeState:has(+.runtimeState.active) {
border-bottom: 0;
}
.runtimeState:hover {
/* background-color: rgba(255, 140, 0, 0.2); */
background-color: rgba(0,0,255,0.2);
background-color: var(--light-accent-color);
cursor: pointer;
}
.runtimeState.active {
background-color: rgba(0,0,255,0.2);
border: solid blue 1px;
background-color: var(--light-accent-color);
border: solid var(--accent-border-color) 1px;
}
.runtimeState.plantStep:not(.active) {
background-color: #f7f7f7;
background-color: var(--inactive-bg-color);
}
.runtimeState.plantStep * {
color: grey;
color: var(--inactive-fg-color);
}
.runtimeState.runtimeError {
background-color: lightpink;
color: darkred;
background-color: var(--error-bg-color);
color: var(--error-color); /* used to be darkred, but this one's a bit lighter */
}
.runtimeState.runtimeError.active {
border-color: darkred;
border-color: var(--error-color);/* used to be darkred, but this one's a bit lighter */
}
/* details:not(:has(details)) > summary::marker {
color: white;
} */
.readonlyTextBox {
width: 56;
background-color:"#eee";
text-align: "right";
}
@ -78,20 +61,23 @@ details:has(+ details) {
}
button {
background-color: #fcfcfc;
border: 1px lightgrey solid;
background-color: var(--button-bg-color);
border: 1px var(--separator-color) solid;
}
button:not(:disabled):hover {
background-color: rgba(0, 0, 255, 0.2);
background-color: var(--light-accent-color);
}
button:disabled {
background-color: var(--inactive-bg-color);
color: var(--inactive-fg-color);
}
button.active {
border: solid blue 1px;
background-color: rgba(0,0,255,0.2);
/* margin-right: 1px; */
/* margin-left: 0; */
color: black;
border: solid var(--accent-border-color) 1px;
background-color: var(--light-accent-color);
color: var(--text-color);
}
.modalOuter {
@ -102,7 +88,7 @@ button.active {
justify-content: center;
align-items: center;
text-align: center;
background-color: rgba(200,200,200,0.7);
background-color: var(--modal-backdrop-color);
z-index: 1000;
}
@ -110,7 +96,7 @@ button.active {
position: relative;
text-align: center;
display: inline-block;
background-color: white;
background-color: var(--background-color);
max-height: 100vh;
overflow: auto;
}
@ -128,7 +114,7 @@ div.stackHorizontal {
div.status {
display: inline-block;
vertical-align: middle;
background-color: grey;
background-color: var(--status-inactive-color);
border-radius: 50%;
height: 12px;
width: 12px;
@ -141,6 +127,6 @@ div.status.violated {
}
div.status.satisfied {
background-color: forestgreen;
background-color: var(--status-ok-color);
}

View file

@ -17,6 +17,7 @@ import { useUrlHashState } from "../hooks/useUrlHashState";
import { plants } from "./plants";
import { emptyState } from "@/statecharts/concrete_syntax";
import { ModalOverlay } from "./Modals/ModalOverlay";
import { usePersistentState } from "@/hooks/usePersistentState";
export type EditHistory = {
current: VisualEditorState,
@ -38,10 +39,14 @@ const defaultAppState: AppState = {
...defaultSideBarState,
}
export type LightMode = "light" | "auto" | "dark";
export function App() {
const [editHistory, setEditHistory] = useState<EditHistory|null>(null);
const [modal, setModal] = useState<ReactElement|null>(null);
const [lightMode, setLightMode] = usePersistentState<LightMode>("lightMode", "auto");
const {makeCheckPoint, onRedo, onUndo, onRotate} = useEditor(setEditHistory);
const editorState = editHistory && editHistory.current;
@ -135,7 +140,8 @@ export function App() {
const plantState = currentBigStep && currentBigStep.state.plant || plant.execution.initial()[1];
return <ModalOverlay modal={modal} setModal={setModal}>
return <div style={{height:'100%', colorScheme: lightMode!=="auto"?lightMode:undefined}}>
<ModalOverlay modal={modal} setModal={setModal}>
{/* top-to-bottom: everything -> bottom panel */}
<div className="stackVertical" style={{height:'100%'}}>
@ -150,7 +156,7 @@ export function App() {
style={{flex: '0 0 content'}}
>
{editHistory && <TopPanel
{...{onUndo, onRedo, onRotate, setModal, editHistory, ...simulator, ...setters, ...appState}}
{...{onUndo, onRedo, onRotate, lightMode, setLightMode, setModal, editHistory, ...simulator, ...setters, ...appState}}
/>}
</div>
{/* Editor */}
@ -179,7 +185,8 @@ export function App() {
{syntaxErrors && <BottomPanel {...{errors: syntaxErrors}}/>}
</div>
</div>
</ModalOverlay>;
</ModalOverlay>
</div>;
}
export default App;

View file

@ -5,6 +5,6 @@
}
.bottom {
border-top: 1px lightgrey solid;
background-color: rgb(255, 249, 235);
border-top: 1px var(--separator-color) solid;
background-color: var(--bottom-panel-bg-color);
}

View file

@ -3,14 +3,14 @@ import { TraceableError } from "../../statecharts/parser";
import "./BottomPanel.css";
import logo from "../../../artwork/logo-playful.svg";
import { PersistentDetailsLocalStorage } from "../PersistentDetails";
import { Logo } from "@/Logo/Logo";
export function BottomPanel(props: {errors: TraceableError[]}) {
const [greeting, setGreeting] = useState(
<div style={{textAlign:'center'}}>
<span style={{fontSize: 18, fontStyle: 'italic'}}>
Welcome to <img src={logo} style={{maxWidth:'100%'}}/>
Welcome to <Logo/>
</span>
</div>);

View file

@ -1,9 +1,9 @@
import { Logo } from "@/Logo/Logo";
import { Dispatch, ReactElement, SetStateAction } from "react";
import logo from "../../../artwork/logo-playful.svg";
export function About(props: {setModal: Dispatch<SetStateAction<ReactElement|null>>}) {
return <div style={{maxWidth: '500px', padding: 4}}>
<p><img src={logo} style={{maxWidth:'100%'}}/></p>
<p><Logo/></p>
<p>StateBuddy is an <a target="_blank" href="https://deemz.org/git/research/statebuddy">open source</a> tool for <a target="_blank" href="https://dl.acm.org/doi/10.1016/0167-6423(87)90035-9">Statechart</a> editing, simulation, (omniscient) debugging and testing.</p>

View file

@ -1,78 +1,51 @@
details.active {
border: rgb(192, 125, 0);
background-color:rgb(255, 251, 244);
filter: drop-shadow( 0px 0px 3px rgba(192, 125, 0, 0.856));
}
details > summary {
padding: 2px;
}
/* these two rules add a bit of padding to an opened <details> node */
/* details:open > summary:has(+ *) {
margin-bottom: 4px;
}
details:open:has(>summary:has(+ *)) {
padding-bottom: 8px;
} */
details > summary:hover {
background-color: #eee;
background-color: var(--summary-hover-bg-color);
cursor: default;
}
.errorStatus details > summary:hover {
background-color: rgb(102, 0, 0);
background-color: var(--error-hover-bg-color);
}
.stateTree > * {
padding-left: 10px;
/* border: 1px black solid; */
background-color: white;
/* margin-bottom: 4px; */
/* padding-right: 2px; */
/* padding-top: 2px; */
/* padding-bottom: 2px; */
/* color: black; */
/* width: fit-content; */
/* border-radius: 10px; */
}
/* if <details> has no children (besides the obvious <summary> child), then hide the marker */
/* details:not(:has(:not(summary))) > summary::marker {
content: " ";
} */
.outputEvent {
border: 1px black solid;
cursor: default;
border: 1px var(--separator-color) solid;
border-radius: 6px;
/* margin-left: 4px; */
padding-left: 2px;
padding-right: 2px;
background-color: rgb(230, 249, 255);
color: black;
background-color: var(--output-event-bg-color);
color: var(--text-color);
display: inline-block;
}
.internalEvent {
border: 1px black solid;
cursor: default;
border: 1px var(--separator-color) solid;
border-radius: 6px;
/* margin-left: 4px; */
padding-left: 2px;
padding-right: 2px;
background-color: rgb(255, 218, 252);
color: black;
background-color: var(--internal-event-bg-color);
color: var(--text-color);
display: inline-block;
}
.inputEvent {
border: 1px black solid;
border: 1px var(--separator-color) solid;
border-radius: 6px;
/* margin-left: 4px; */
padding-left: 2px;
padding-right: 2px;
background-color: rgb(224, 247, 209);
color: black;
background-color: var(--input-event-bg-color);
color: var(--text-color);
display: inline-block;
}
.inputEvent:disabled {
@ -82,21 +55,24 @@ details > summary:hover {
vertical-align: middle;
}
button.inputEvent:hover:not(:disabled) {
background-color: rgb(195, 224, 176);
background-color: var(--input-event-hover-bg-color);
}
button.inputEvent:active:not(:disabled) {
background-color: rgb(176, 204, 158);
background-color: var(--input-event-active-bg-color);
}
.activeState {
border: rgb(192, 125, 0);
background-color:rgb(255, 251, 244);
filter: drop-shadow( 0px 0px 3px rgba(192, 125, 0, 0.856));
/* border: rgb(192, 125, 0); */
/* background-color:rgb(255, 251, 244); */
border: var(--active-state-border-color) 1px solid;
background-color: var(--active-state-bg-color);
/* filter: drop-shadow( 0px 0px 3px rgba(192, 125, 0, 0.856)); */
border-radius: 6px;
margin-left: 4px;
padding-left: 2px;
padding-right: 2px;
display: inline-block;
color: var(--text-color);
}
/* hr {

View file

@ -2,8 +2,8 @@ import { RT_Statechart } from "@/statecharts/runtime_types";
import { Plant } from "../Plant/Plant";
import { TraceItem } from "../hooks/useSimulator";
// const endpoint = "http://localhost:15478/check_property";
const endpoint = "https://deemz.org/apis/mtl-aas/check_property";
const endpoint = "http://localhost:15478/check_property";
// const endpoint = "https://deemz.org/apis/mtl-aas/check_property";
export type PropertyTrace = {
timestamp: number,

View file

@ -18,12 +18,19 @@ export function PseudoStateIcon(props: {}) {
${w - 1} ${h / 2},
${w / 2} ${h - 1},
${1} ${h / 2},
`} fill="white" stroke="black" strokeWidth={1.2} />
`}
style={{
fill: 'var(--and-state-bg-color',
stroke: 'var(--rountangle-stroke-color',
}} strokeWidth={1.2} />
</svg>;
}
export function HistoryIcon(props: { kind: "shallow" | "deep"; }) {
const w = 20, h = 20;
const text = props.kind === "shallow" ? "H" : "H*";
return <svg width={w} height={h}><circle cx={w / 2} cy={h / 2} r={Math.min(w, h) / 2 - 1} fill="white" stroke="black" /><text x={w / 2} y={h / 2 + 4} textAnchor="middle" fontSize={11} fontWeight={400}>{text}</text></svg>;
return <svg width={w} height={h}><circle cx={w / 2} cy={h / 2} r={Math.min(w, h) / 2 - 1} style={{
fill: 'var(--and-state-bg-color',
stroke: 'var(--rountangle-stroke-color',
}}/><text x={w / 2} y={h / 2 + 4} style={{fill: 'var(--rountangle-stroke-color'}} textAnchor="middle" fontSize={11} fontWeight={400}>{text}</text></svg>;
}

View file

@ -3,12 +3,16 @@ import { TimerElapseEvent, Timers } from "../../statecharts/runtime_types";
import { getSimTime, setPaused, setRealtime, TimeMode } from "../../statecharts/time";
import { InsertMode } from "./InsertModes";
import { About } from "../Modals/About";
import { EditHistory } from "../App";
import { EditHistory, LightMode } from "../App";
import { KeyInfoHidden, KeyInfoVisible } from "./KeyInfo";
import { UndoRedoButtons } from "./UndoRedoButtons";
import { ZoomButtons } from "./ZoomButtons";
import { formatTime } from "../../util/util";
import DarkModeIcon from '@mui/icons-material/DarkMode';
import LightModeIcon from '@mui/icons-material/LightMode';
import BrightnessAutoIcon from '@mui/icons-material/BrightnessAuto';
import AccessAlarmIcon from '@mui/icons-material/AccessAlarm';
import CachedIcon from '@mui/icons-material/Cached';
import InfoOutlineIcon from '@mui/icons-material/InfoOutline';
@ -33,6 +37,8 @@ export type TopPanelProps = {
onInit: () => void,
onClear: () => void,
onBack: () => void,
lightMode: LightMode,
setLightMode: Dispatch<SetStateAction<LightMode>>,
insertMode: InsertMode,
setInsertMode: Dispatch<SetStateAction<InsertMode>>,
setModal: Dispatch<SetStateAction<ReactElement|null>>,
@ -45,7 +51,7 @@ export type TopPanelProps = {
const ShortCutShowKeys = <kbd>~</kbd>;
export const TopPanel = memo(function TopPanel({trace, time, setTime, onUndo, onRedo, onRotate, onInit, onClear, onBack, insertMode, setInsertMode, setModal, zoom, setZoom, showKeys, setShowKeys, editHistory}: TopPanelProps) {
export const TopPanel = memo(function TopPanel({trace, time, setTime, onUndo, onRedo, onRotate, onInit, onClear, onBack, lightMode, setLightMode, insertMode, setInsertMode, setModal, zoom, setZoom, showKeys, setShowKeys, editHistory}: TopPanelProps) {
const [displayTime, setDisplayTime] = useState("0.000");
const [timescale, setTimescale] = usePersistentState("timescale", 1);
@ -146,6 +152,20 @@ export const TopPanel = memo(function TopPanel({trace, time, setTime, onUndo, on
}, [config, time, onInit, onChangePaused, setShowKeys, onSkip, onBack, onClear]);
return <div className="toolbar">
{/* light / dark mode */}
<div className="toolbarGroup">
<button title="force light mode" className={lightMode==="light"?"active":""} onClick={() => setLightMode("light")}>
<LightModeIcon fontSize="small"/>
</button>
<button title="auto light / dark mode (follows system theme)" className={lightMode==="auto"?"active":""} onClick={() => setLightMode("auto")}>
<BrightnessAutoIcon fontSize="small"/>
</button>
<button title="force dark mode" className={lightMode==="dark"?"active":""} onClick={() => setLightMode("dark")}>
<DarkModeIcon fontSize="small"/>
</button>
&emsp;
</div>
{/* shortcuts / about */}
<div className="toolbarGroup">
<KeyInfo keyInfo={ShortCutShowKeys}>

View file

@ -14,7 +14,8 @@ export const DiamondShape = memo(function DiamondShape(props: {size: Vec2D, extr
${minSize.x/2} ${minSize.y},
${0} ${minSize.y/2}
`}
fill="white"
style={{fill: 'var(--and-state-bg-color', stroke: 'var(--rountangle-stroke-color)'}}
// fill="white"
stroke="black"
strokeWidth={2}
{...props.extraAttrs}

View file

@ -1,6 +1,6 @@
.svgCanvas {
cursor: crosshair;
background-color: #eee;
background-color: var(--or-state-bg-color);
}
.svgCanvas.dragging {
@ -19,16 +19,15 @@
/* rectangle drawn while a selection is being made */
.selecting {
fill: blue;
fill-opacity: 0.2;
fill: var(--light-accent-color);
stroke-width: 1px;
stroke:black;
stroke: var(--accent-border-color);
stroke-dasharray: 7 6;
}
.rountangle {
fill: white;
stroke: black;
fill: var(--and-state-bg-color);
stroke: var(--rountangle-stroke-color);
stroke-width: 2px;
}
@ -39,9 +38,10 @@
stroke: var(--error-color);
}
.rountangle.active {
stroke: rgb(205, 133, 0);
stroke: var(--active-state-border-color);
/* stroke: none; */
fill:rgb(255, 240, 214);
/* fill:rgb(255, 240, 214); */
fill: var(--active-state-bg-color);
/* filter: drop-shadow( 2px 2px 2px rgba(124, 37, 10, 0.729)); */
}
@ -54,8 +54,7 @@ line.helper {
stroke-width: 16px;
}
line.helper:hover:not(:active) {
stroke: blue;
stroke-opacity: 0.2;
stroke: var(--light-accent-color);
cursor: grab;
}
@ -65,8 +64,7 @@ path.helper {
stroke-width: 16px;
}
path.helper:hover:not(:active) {
stroke: blue;
stroke-opacity: 0.2;
stroke: var(--light-accent-color);
cursor: grab;
}
@ -74,23 +72,22 @@ circle.helper {
fill: rgba(0, 0, 0, 0);
}
circle.helper:hover:not(:active) {
fill: blue;
fill-opacity: 0.2;
fill: var(--light-accent-color);
cursor: grab;
}
.rountangle.or {
stroke-dasharray: 7 6;
fill: #eee;
fill: var(--or-state-bg-color);
}
.arrow {
fill: none;
stroke: black;
stroke: var(--rountangle-stroke-color);
stroke-width: 2px;
}
.arrow.selected {
stroke: blue;
stroke: var(--accent-border-color);
stroke-width: 3px;
}
@ -110,34 +107,29 @@ circle.helper:hover:not(:active) {
}
line.selected, circle.selected {
fill: rgba(0, 0, 255, 0.2);
/* stroke-dasharray: 7 6; */
stroke: blue;
fill: var(--light-accent-color);
stroke: var(--accent-border-color);
stroke-width: 4px;
}
.draggableText.selected, .draggableText.selected:hover {
fill: blue;
fill: var(--accent-border-color);
font-weight: 600;
}
.draggableText:hover:not(:active) {
/* fill: blue; */
/* cursor: grab; */
}
text.helper {
fill: rgba(0,0,0,0);
stroke: rgba(0,0,0,0);
stroke-width: 6px;
}
text.helper:hover {
stroke: blue;
stroke-opacity: 0.2;
stroke: var(--light-accent-color);
cursor: grab;
}
.draggableText, .draggableText.highlight {
paint-order: stroke;
stroke: white;
fill: var(--text-color);
stroke: var(--background-color);
stroke-width: 4px;
stroke-linecap: butt;
stroke-linejoin: miter;
@ -146,12 +138,16 @@ text.helper:hover {
}
.draggableText.highlight:not(.selected) {
fill: green;
fill: var(--associated-color);
font-weight: 600;
}
.draggableText.selected {
fill: var(--accent-border-color);
}
.highlight:not(.selected):not(text) {
stroke: green;
stroke: var(--associated-color);
stroke-width: 3px;
fill: none;
}
@ -160,19 +156,19 @@ text.helper:hover {
stroke: var(--error-color);
}
.arrow.fired {
stroke: rgb(160 0 168);
stroke: var(--fired-transition-color);
stroke-width: 3px;
animation: blinkTransition 1s;
}
@keyframes blinkTransition {
0% {
stroke: rgb(255, 128, 9);
stroke: var(--firing-transition-color);
stroke-width: 6px;
filter: drop-shadow(0 0 5px rgba(255, 128, 9, 1));
filter: drop-shadow(0 0 5px var(--firing-transition-color));
}
100% {
stroke: rgb(160 0 168);
stroke: var(--fired-transition-color);
}
}
@ -191,5 +187,5 @@ g:hover > .errorHover {
}
text.uid {
fill: lightgrey;
fill: var(--separator-color);
}

View file

@ -11,8 +11,6 @@ export function useMouse(makeCheckPoint: () => void, insertMode: InsertMode, zoo
const [dragging, setDragging] = useState(false);
const [shiftOrCtrlPressed, setShiftOrCtrlPressed] = useState(false);
console.log(shiftOrCtrlPressed);
// not null while the user is making a selection
const [selectingState, setSelectingState] = useState<SelectingState>(null);
@ -347,9 +345,9 @@ export function useMouse(makeCheckPoint: () => void, insertMode: InsertMode, zoo
...state,
// @ts-ignore
selection: [
...state.rountangles.map(r => ["left", "top", "right", "bottom"].map(part => ({uid: r.uid, part}))),
...state.diamonds.map(d => ["left", "top", "right", "bottom"].map(part => ({uid: d.uid, part}))),
...state.arrows.map(a => ["start", "end"].map(part => ({uid: a.uid, part}))),
...state.rountangles.flatMap(r => ["left", "top", "right", "bottom"].map(part => ({uid: r.uid, part}))),
...state.diamonds.flatMap(d => ["left", "top", "right", "bottom"].map(part => ({uid: d.uid, part}))),
...state.arrows.flatMap(a => ["start", "end"].map(part => ({uid: a.uid, part}))),
...state.texts.map(t => ({uid: t.uid, part: "text"})),
...state.history.map(h => ({uid: h.uid, part: "history"})),
]
@ -358,6 +356,8 @@ export function useMouse(makeCheckPoint: () => void, insertMode: InsertMode, zoo
}
}, [makeCheckPoint, deleteSelection, setState, setDragging]);
console.log(state.selection);
useEffect(() => {
// mousemove and mouseup are global event handlers so they keep working when pointer is outside of browser window
window.addEventListener("mouseup", onMouseUp);

11
src/Logo/Logo.tsx Normal file

File diff suppressed because one or more lines are too long

View file

@ -5,9 +5,44 @@ html, body {
font-size: 10pt;
}
body {
/* --error-color: darkred; */
--error-color: rgb(163, 0, 0);
:root {
color-scheme: light dark;
--background-color: light-dark(white, rgb(26, 26, 26));
--text-color: light-dark(black, white);
--error-color: light-dark(rgb(163, 0, 0), rgb(255, 82, 82));
--error-bg-color: light-dark(lightpink, rgb(75, 0, 0));
--error-hover-bg-color: light-dark(rgb(102, 0, 0), rgb(102, 0, 0));
--light-accent-color: light-dark(rgba(0,0,255,0.2), rgba(78, 186, 248, 0.377));
--accent-border-color: light-dark(blue, rgb(64, 185, 255));
--separator-color: light-dark(lightgrey, rgb(44, 44, 44));
--inactive-bg-color: light-dark(#f7f7f7, rgb(29, 29, 29));
--inactive-fg-color: light-dark(grey, rgb(70, 70, 70));
--button-bg-color: light-dark(#fcfcfc, rgb(20, 20, 20));
--modal-backdrop-color: light-dark(rgba(200,200,200,0.7), rgba(59, 7, 7, 0.849));
--status-inactive-color: light-dark(grey, grey);
--status-ok-color: light-dark(forestgreen, forestgreen);
--or-state-bg-color: light-dark(#eee, #1a1a1a);
--and-state-bg-color: light-dark(white, rgb(0, 0, 0));
--rountangle-stroke-color: light-dark(black, #d4d4d4);
--active-state-bg-color: light-dark(rgb(255, 240, 214), rgb(53, 37, 18));
--active-state-border-color: light-dark(rgb(205, 133, 0), rgb(235, 124, 21));
--fired-transition-color: light-dark(rgb(160, 0, 168), rgb(160, 0, 168));
--firing-transition-color: light-dark(rgba(255, 128, 9, 1), rgba(255, 128, 9, 1));
--associated-color: light-dark(green, rgb(186, 245, 119));
--bottom-panel-bg-color: light-dark(rgb(255, 249, 235), rgb(41, 4, 4));
--summary-hover-bg-color: light-dark(#eee, #242424);
--internal-event-bg-color: light-dark(rgb(255, 218, 252), rgb(99, 27, 94));
--input-event-bg-color: light-dark(rgb(224, 247, 209), rgb(59, 95, 37));
--input-event-hover-bg-color: light-dark(rgb(195, 224, 176), rgb(59, 88, 40));
--input-event-active-bg-color: light-dark(rgb(176, 204, 158), rgb(77, 117, 53));
--output-event-bg-color: light-dark(rgb(230, 249, 255), rgb(28, 83, 104));
}
/* for some reason i need to add this or some elements are rendered in OS color rather than 'forced' color */
div {
color: var(--text-color);
background-color: var(--background-color);
}
div#root {
@ -27,17 +62,18 @@ kbd {
box-shadow: inset 0 -1.5px 0 #aaa;
vertical-align: middle;
user-select: none;
color: black;
}
kbd:active { transform: translateY(1px); }
input {
/* border: solid blue 2px; */
accent-color: rgba(0,0,255,0.2);
accent-color: var(--light-accent-color);
}
::selection {
background-color: rgba(0,0,255,0.2);
background-color: var(--light-accent-color);
}
label {