aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMetroWind <chris.corsair@gmail.com>2025-09-27 13:05:27 -0700
committerMetroWind <chris.corsair@gmail.com>2025-09-27 13:05:27 -0700
commit81b539d6a63dd4921686fb50f05daf1b1d725e3b (patch)
tree5d6d832d73f7f946ebf40924221ffcda980f9837
parent925b5fcea3b85df08eea6574caf76639018a1937 (diff)
Add button maker. Support basic editing.
-rw-r--r--button-maker/index.html28
-rw-r--r--button-maker/scripts/lib.js79
-rw-r--r--button-maker/scripts/main.js279
-rw-r--r--button-maker/scripts/tiles.js94
-rw-r--r--button-maker/styles.css326
5 files changed, 806 insertions, 0 deletions
diff --git a/button-maker/index.html b/button-maker/index.html
new file mode 100644
index 0000000..6e5aba0
--- /dev/null
+++ b/button-maker/index.html
@@ -0,0 +1,28 @@
1<!doctype html>
2<html lang="en-US">
3 <head>
4 <meta charset="utf-8" />
5 <meta name="viewport"
6 content="width=device-width, initial-scale=1" />
7 <link rel="stylesheet" type="text/css" href="styles.css" media="screen" />
8 <link rel="preconnect" href="https://fonts.googleapis.com">
9 <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10 <link href="https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&display=swap" rel="stylesheet">
11
12 <link rel="stylesheet" href="styles.css" />
13 <script src="https://cdnjs.cloudflare.com/ajax/libs/preact/10.27.1/preact.min.js"
14 crossorigin="anonymous"
15 referrerpolicy="no-referrer"></script>
16 <script src="https://cdnjs.cloudflare.com/ajax/libs/preact/10.27.1/hooks.umd.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
17 <script src="scripts/lib.js"></script>
18 <script type="module" defer src="scripts/main.js"></script>
19 <title>Button maker</title>
20 </head>
21 <body>
22 <div class="Window" id="MainWindow">
23 <h1 class="Titlebar">Button Maker</h1>
24 <div id="App">
25 </div>
26 </div>
27 </body>
28</html>
diff --git a/button-maker/scripts/lib.js b/button-maker/scripts/lib.js
new file mode 100644
index 0000000..372d9d7
--- /dev/null
+++ b/button-maker/scripts/lib.js
@@ -0,0 +1,79 @@
1// In Firefox (on Mac only?), for <input type="color">, setting value
2// to abbreviated hex form (e.g. #123) doesn’t work. So all colors
3// should be specified in full form.
4const DEFAULT_NETSCAPE_PARAMS = {
5 color_bg: "#c0c0c0",
6 logo_url: "https://upload.wikimedia.org/wikipedia/commons/0/08/Netscape_icon.svg",
7 text_top: {
8 content: "Netscape",
9 pos: [31, 11],
10 font_size: 10.5,
11 color: "#000000",
12 },
13 text_bottom: {
14 content: "Now!",
15 pos: [30, 28],
16 font_size: 17.5,
17 color: "#ff0000",
18 },
19 banner: {
20 style: "corner_bottom_right", // corner_bottom_right or right
21 color: "#008080",
22 text: {
23 content: "5.0",
24 pos: [65, 46],
25 font_size: 8,
26 color: "#ffffff",
27 },
28 },
29};
30
31const DEFAULT_APP_STATE = {
32 button_type: "netscape",
33 button_params: DEFAULT_NETSCAPE_PARAMS,
34}
35
36const SVG_ATTRIBUTES = {viewBox: "0 0 88 31", width: 88, height: 31};
37
38// Return three integers as an array.
39function parseHexColor(color_str)
40{
41 console.debug("Parsing", color_str);
42 m = color_str.match(/^#([0-9a-f]{3})$/i);
43 if(m)
44 {
45 // in three-character format, each value is multiplied by 0x11 to give an
46 // even scale from 0x00 to 0xff
47 return [
48 parseInt(m[1].charAt(0),16)*0x11,
49 parseInt(m[1].charAt(1),16)*0x11,
50 parseInt(m[1].charAt(2),16)*0x11
51 ];
52 }
53
54 m = color_str.match(/^#([0-9a-f]{6})$/i);
55 if(m)
56 {
57 return [
58 parseInt(m[1].substr(0,2),16),
59 parseInt(m[1].substr(2,2),16),
60 parseInt(m[1].substr(4,2),16)
61 ];
62 }
63 return null;
64}
65
66function lighten(rgb, delta)
67{
68 return [rgb[0] + delta, rgb[1] + delta, rgb[2] + delta];
69}
70
71function darken(rgb, delta)
72{
73 return [rgb[0] - delta, rgb[1] - delta, rgb[2] - delta];
74}
75
76function color2Str(color)
77{
78 return `rgb(${color[0]} ${color[1]} ${color[2]})`;
79}
diff --git a/button-maker/scripts/main.js b/button-maker/scripts/main.js
new file mode 100644
index 0000000..7232064
--- /dev/null
+++ b/button-maker/scripts/main.js
@@ -0,0 +1,279 @@
1const h = preact.h;
2
3function ButtonViewNetscape({app_state})
4{
5 const params = app_state.button_params;
6 console.debug("Rendering netscape params", params);
7 let color_bg = parseHexColor(params.color_bg);
8 let color_hilight_1 = color2Str(lighten(color_bg, 63));
9 let color_hilight_2 = color2Str(lighten(color_bg, 31));
10 let color_shadow_1 = color2Str(darken(color_bg, 0xc0 - 0xa));
11 let color_shadow_2 = color2Str(darken(color_bg, 0xc0 - 0x54));
12
13 let ribbon = null;
14 let ribbon_text = null;
15 if(app_state.button_params.banner.style == "corner_bottom_right")
16 {
17 ribbon = h("polygon", {points: "86,8 86,24 81,29 65,29",
18 fill: params.banner.color});
19 ribbon_text = h("text", {
20 x: params.banner.text.pos[0], y: params.banner.text.pos[1],
21 "font-family": "sans-serif",
22 "dominant-baseline": "middle", "text-anchor": "middle",
23 "transform-origin": "center", "transform":"rotate(-45)",
24 "font-weight": "bold", "font-size": params.banner.text.font_size,
25 fill: params.banner.text.color, "stroke-width": 0},
26 params.banner.text.content);
27 }
28 else if(app_state.button_params.banner.style == "right")
29 {
30 ribbon = h("rect", {x: 78, y: 2, width: 8, height: 27,
31 fill: app_state.button_params.banner.color});
32 ribbon_text = h("text", {
33 x: params.banner.text.pos[0], y: params.banner.text.pos[1],
34 "font-family": "sans-serif", "dominant-baseline": "middle",
35 "text-anchor": "middle", "transform-origin": "center",
36 transform: "rotate(-90)", "font-weight": "bold",
37 "font-size": params.banner.text.font_size,
38 fill: params.banner.text.color, "stroke-width": 0},
39 params.banner.text.content);
40 }
41
42 return h("svg", SVG_ATTRIBUTES,
43 h("g", {"stroke-width": 0},
44 h("polygon", {points: "0,0 88,0 87,1 1,1",
45 fill: color_hilight_1}),
46 h("polygon", {points: "1,1 87,1 86,2 2,2",
47 fill: color_hilight_2}),
48 h("polygon", {points: "88,0 88,31 87,30 87,1",
49 fill: color_shadow_1}),
50 h("polygon", {points: "87,1 87,30 86,29 86,2",
51 fill: color_shadow_2}),
52 h("polygon", {points: "88,31 0,31 1,30 87,30",
53 fill: color_shadow_1}),
54 h("polygon", {points: "87,30 1,30 2,29,86,29",
55 fill: color_shadow_2}),
56 h("polygon", {points: "0,0 1,1 1,30 0,31",
57 fill: color_hilight_1}),
58 h("polygon", {points: "1,1 2,2 2,29 1,30",
59 fill: color_hilight_2}),
60 h("rect", {x: 2, y: 2, width: 84, height: 27,
61 fill: params.color_bg}),
62 ribbon),
63 h("image", {href: params.logo_url, x: 3, y: 3, width: 25,
64 height: 25}),
65 h("text", {x: params.text_bottom.pos[0],
66 y: params.text_bottom.pos[1], "font-family": "Kalam",
67 "font-size": params.text_bottom.font_size,
68 fill: params.text_bottom.color, "stroke-width": 0},
69 "Now!"),
70 h("text", {x: params.text_top.pos[0], y: params.text_top.pos[1],
71 "font-family": "sans-serif",
72 "font-size": params.text_top.font_size,
73 fill: params.text_top.color, "stroke-width": 0},
74 params.text_top.content),
75 ribbon_text);
76}
77
78function TextControlView({params, on_change})
79{
80 console.debug("Rendering text params", params);
81 function onContentChange(e)
82 {
83 let new_params = structuredClone(params);
84 new_params.content = e.target.value;
85 on_change(new_params);
86 }
87
88 function onPosXChange(e)
89 {
90 let new_params = structuredClone(params);
91 new_params.pos[0] = parseFloat(e.target.value);
92 on_change(new_params);
93 }
94
95 function onPosYChange(e)
96 {
97 let new_params = structuredClone(params);
98 new_params.pos[1] = parseFloat(e.target.value);
99 on_change(new_params);
100 }
101
102 function onFontSizeChange(e)
103 {
104 let new_params = structuredClone(params);
105 new_params.font_size = parseFloat(e.target.value);
106 on_change(new_params);
107 }
108
109 function onColorChange(e)
110 {
111 let new_params = structuredClone(params);
112 new_params.color = e.target.value;
113 on_change(new_params);
114 }
115
116 return h(preact.Fragment, {},
117 h("div", {"class": "ButtonRowLeft"},
118 h("label", {}, "Content"),
119 h("input", {type: "text", value: params.content,
120 onchange: onContentChange}),
121 h("label", {}, "Color"),
122 h("input", {type: "color",
123 value: params.color,
124 onchange: onColorChange})),
125 h("div", {"class": "ButtonRowLeft"},
126 h("label", {}, "x"),
127 h("input", {type: "number", value: params.pos[0],
128 onchange: onPosXChange}),
129 h("label", {}, "y"),
130 h("input", {type: "number", value: params.pos[1],
131 onchange: onPosYChange}),
132 h("label", {}, "Font size"),
133 h("input", {type: "number", value: params.font_size,
134 min: 0, max: 88, step: 0.5,
135 onchange: onFontSizeChange})));
136}
137
138function EditorViewNetscape({app_state, on_state_change})
139{
140 const params = app_state.button_params;
141 function onLogoURLChange(e)
142 {
143 let new_state = structuredClone(app_state);
144 new_state.button_params.logo_url = e.target.value;
145 on_state_change(new_state);
146 }
147
148 function onBGColorChange(e)
149 {
150 let new_state = structuredClone(app_state);
151 new_state.button_params.color_bg = e.target.value;
152 on_state_change(new_state);
153 }
154
155 function onTextTopChange(new_params)
156 {
157 let new_state = structuredClone(app_state);
158 new_state.button_params.text_top = new_params;
159 on_state_change(new_state);
160 }
161
162 function onTextBottomChange(new_params)
163 {
164 let new_state = structuredClone(app_state);
165 new_state.button_params.text_bottom = new_params;
166 on_state_change(new_state);
167 }
168
169 function onRibbonStyleChange(e)
170 {
171 if(app_state.button_params.banner.style == e.target.value)
172 {
173 return;
174 }
175 let new_state = structuredClone(app_state);
176 let banner = new_state.button_params.banner;
177 banner.style = e.target.value;
178
179 // With the style change, the text also needs adjustments. We
180 // will just set it to the style’s default.
181 if(banner.style == "corner_bottom_right")
182 {
183 banner.text.pos = [65, 46];
184 }
185 else if(banner.style == "right")
186 {
187 banner.text.pos = [44, 54];
188 }
189
190 on_state_change(new_state);
191 }
192
193 function onRibbonColorChange(e)
194 {
195 let new_state = structuredClone(app_state);
196 new_state.button_params.banner.color = e.target.value;
197 on_state_change(new_state);
198 }
199
200 function onRibbonTextChange(new_text)
201 {
202 let new_state = structuredClone(app_state);
203 new_state.button_params.banner.text = new_text;
204 on_state_change(new_state);
205 }
206
207 return h("div", {},
208 h("div", {"class": "ButtonRowLeft"},
209 h("label", {}, "Background color"),
210 h("input", {type: "color",
211 value: params.color_bg,
212 onchange: onBGColorChange})),
213 h("div", {"class": "ButtonRowLeft"},
214 h("label", {}, "Logo URL"),
215 h("input", {type: "url", value: params.logo_url,
216 onchange: onLogoURLChange})),
217 h("div", {"class": "LabeledPanel"},
218 h("h2", {}, "Main text"),
219 h(TextControlView, {params: params.text_top,
220 on_change: onTextTopChange})),
221 h("div", {"class": "LabeledPanel"},
222 h("h2", {}, "Flavor text"),
223 h(TextControlView, {params: params.text_bottom,
224 on_change: onTextBottomChange})),
225 h("div", {"class": "LabeledPanel"},
226 h("h2", {}, "Ribbon"),
227 h("div", {"class": "ButtonRowLeft"},
228 h("label", {}, "Style"),
229 h("select", {value: params.banner.style,
230 onchange: onRibbonStyleChange},
231 h("option", {value: "corner_bottom_right"}, "Bottom right"),
232 h("option", {value: "right"}, "Vertical right")),
233 h("label", {}, "Background color"),
234 h("input", {type: "color",
235 value: params.banner.color,
236 onchange: onRibbonColorChange})),
237 h("div", {"class": "LabeledPanel"},
238 h("h2", {}, "Text"),
239 h(TextControlView, {params: params.banner.text,
240 on_change: onRibbonTextChange})),
241
242 ),
243
244
245 );
246}
247
248function EditorView({app_state})
249{
250 const [state, setState] = preactHooks.useState(app_state);
251 let preview_view = null;
252 let editor_view = null;
253 if(app_state.button_type == "netscape")
254 {
255 preview_view = ButtonViewNetscape;
256 editor_view = EditorViewNetscape;
257 }
258
259 function onStateChange(new_state)
260 {
261 console.debug("New state is", new_state);
262 setState(new_state);
263 }
264
265 return h("div", {},
266 h(preview_view, {app_state: state}),
267 h("select", {},
268 h("option", {value: "netscape"}, "Netscape"),
269 ),
270 h(editor_view, {app_state: state, on_state_change: onStateChange}));
271}
272
273function App()
274{
275 return h("div", {},
276 h(EditorView, {app_state: DEFAULT_APP_STATE}));
277}
278
279preact.render(h(App, {}), document.getElementById("App"));
diff --git a/button-maker/scripts/tiles.js b/button-maker/scripts/tiles.js
new file mode 100644
index 0000000..f35887a
--- /dev/null
+++ b/button-maker/scripts/tiles.js
@@ -0,0 +1,94 @@
1const TILES = [
2 null,
3 {
4 id: "blank",
5 name: "black",
6 inner_svg: null,
7 }, {
8 id: "teleporter",
9 name: "teleporter",
10 inner_svg: h("circle", {
11 "cx": CELL_SIZE * 0.5, "cy": CELL_SIZE * 0.5,
12 "r": (CELL_SIZE - 10) * 0.5, "stroke": "black",
13 "fill": "transparent", "stroke-width": 6,
14 }),
15 }, {
16 id: "fleet_command",
17 name: "fleet command",
18 inner_svg: h("polyline", {
19 "points": `3, ${CELL_SIZE} \
203, ${CELL_SIZE - 15} \
2113, ${CELL_SIZE - 25} \
2213, ${CELL_SIZE - 5} \
23${CELL_SIZE - 13}, ${CELL_SIZE - 5} \
24${CELL_SIZE - 13}, ${CELL_SIZE - 25} \
25${CELL_SIZE - 3}, ${CELL_SIZE - 15} \
26${CELL_SIZE - 3}, ${CELL_SIZE}`,
27 "fill": "black", "stroke-width": 0,
28 }),
29 }, {
30 id: "scanner",
31 name: "scanner",
32 inner_svg:
33 h(preact.Fragment, {},
34 h("circle", {"cx": CELL_SIZE * 0.5, "cy": CELL_SIZE * 0.5,
35 "r": CELL_SIZE * 0.5 - 2, "fill": "transparent",
36 "stroke": "black", "stroke-width": 2}),
37 h("circle", {"cx": CELL_SIZE * 0.5, "cy": CELL_SIZE * 0.5,
38 "r": CELL_SIZE * 0.25, "fill": "transparent",
39 "stroke": "black", "stroke-width": 2}),
40 h("line", {"x1": 2, "y1": CELL_SIZE * 0.5,
41 "x2": CELL_SIZE - 2, "y2": CELL_SIZE * 0.5,
42 "stroke": "black", "stroke-width": 2}),
43 h("line", {"y1": 2, "x1": CELL_SIZE * 0.5,
44 "y2": CELL_SIZE - 2, "x2": CELL_SIZE * 0.5,
45 "stroke": "black", "stroke-width": 2}),
46 h("line", {"x1": CELL_SIZE * 0.5, "y1": CELL_SIZE * 0.5,
47 "x2": CELL_SIZE * 0.5 + (CELL_SIZE * 0.5 - 2) * Math.cos(Math.PI / 6),
48 "y2": CELL_SIZE * 0.5 - (CELL_SIZE * 0.5 - 2) * Math.sin(Math.PI / 6),
49 "stroke": "black", "stroke-width": 2}),
50 h("circle", {"cx": 22, "cy": 6, "r": 2, "fill": "black",
51 "stroke-width": 0}),
52 h("circle", {"cx": 10, "cy": 22, "r": 4, "fill": "black",
53 "stroke-width": 0}),
54
55 ),
56 }, {
57 id: "extractor",
58 name: "stellar extractor",
59 inner_svg:
60 h(preact.Fragment, {},
61 h("circle", {"cx": CELL_SIZE * 0.5, "cy": 21,
62 "r": 10, "fill": "black", "stroke-width": 0}),
63 h("polygon", {"points": `${CELL_SIZE * 0.5 - 4}, 20 \
64${CELL_SIZE * 0.5 - 4}, 10 \
65${CELL_SIZE * 0.5 - 8}, 10 \
66${CELL_SIZE * 0.5}, 3 \
67${CELL_SIZE * 0.5 + 8}, 10 \
68${CELL_SIZE * 0.5 + 4}, 10 \
69${CELL_SIZE * 0.5 + 4}, 20`,
70 "stroke": "black", "stroke-width": 2,
71 "fill": "white"}),
72 ),
73 }, {
74 id: "storage",
75 name: "storage",
76 inner_svg:
77 h(preact.Fragment, {},
78 h("rect", {"x": 4, "y": 4, "width": CELL_SIZE - 8,
79 "height": CELL_SIZE - 8, "rx": 2, "ry": 2,
80 "fill": "black", "stroke-width": 0}),
81 h("line", {"x1": 4, "y1": 14, "x2": CELL_SIZE - 4, "y2": 14,
82 "stroke": "white", "stroke-width": 2}),
83 h("rect", {"x": CELL_SIZE * 0.5 - 4, "y": 10, "width": 8, "height": 8,
84 "fill": "white", "stroke-width": 0}),
85 ),
86 }, {
87 id: "plant",
88 name: "double cultivation chamber",
89 inner_svg: h("path", {"d": "m3.02 17.7a13 13 0 0013 13 13 13 0 00-13-13m13 13a13 13 0 0013-13 13 13 0 00-13 13m8.66-27.4v7.21a8.66 8.66 0 01-8.66 8.66 8.66 8.66 0 01-8.66-8.66v-7.21c1.07 0 2.12.173 3.12.563.793.332 1.5.821 2.09 1.44l3.43-3.43 3.43 3.43c.591-.622 1.3-1.11 2.09-1.44.996-.388 2.05-.563 3.12-.563z",
90 "fill": "black", "stroke-width": 0}),
91 }
92];
93
94const TILE_MAP = Object.fromEntries(TILES.slice(1).map(t => [t.id, t]));
diff --git a/button-maker/styles.css b/button-maker/styles.css
new file mode 100644
index 0000000..bee2d4b
--- /dev/null
+++ b/button-maker/styles.css
@@ -0,0 +1,326 @@
1/* http://meyerweb.com/eric/tools/css/reset/
2 v2.0 | 20110126
3 License: none (public domain)
4*/
5
6html, body, div, span, applet, object, iframe,
7h1, h2, h3, h4, h5, h6, p, blockquote, pre,
8a, abbr, acronym, address, big, cite, code,
9del, dfn, em, img, ins, kbd, q, s, samp,
10small, strike, strong, sub, sup, tt, var,
11b, u, i, center,
12dl, dt, dd, ol, ul, li,
13fieldset, form, label, legend,
14table, caption, tbody, tfoot, thead, tr, th, td,
15article, aside, canvas, details, embed,
16figure, figcaption, footer, header, hgroup,
17menu, nav, output, ruby, section, summary,
18time, mark, audio, video {
19 margin: 0;
20 padding: 0;
21 border: 0;
22 font-size: unset;
23 font: inherit;
24 vertical-align: baseline;
25}
26/* HTML5 display-role reset for older browsers */
27article, aside, details, figcaption, figure,
28footer, header, hgroup, menu, nav, section {
29 display: block;
30}
31body {
32 line-height: 1;
33}
34ol, ul {
35 list-style: none;
36}
37blockquote, q {
38 quotes: none;
39}
40blockquote:before, blockquote:after,
41q:before, q:after {
42 content: '';
43 content: none;
44}
45table {
46 border-collapse: collapse;
47 border-spacing: 0;
48}
49
50/* ========== Actual styles ======================================> */
51
52:root
53{
54 --color-bg: #c0c0c0;
55 --color-fg: black;
56 --color-border-dark-1: #808080;
57 --color-border-dark-2: black;
58 --color-border-light-1: #dfdfdf;
59 --color-border-light-2: white;
60 --color-titlebar-1: navy;
61 --color-titlebar-2: #1084d0;
62}
63
64*
65{
66 box-sizing: border-box;
67}
68
69html
70{
71 background: #008080;
72 color: var(--color-fg);
73 font-family: sans-serif;
74 font-size: 12px;
75 padding: 12px;
76}
77
78.Window
79{
80 box-shadow: inset -1px -1px var(--color-border-dark-2),
81 inset 1px 1px var(--color-border-light-1),
82 inset -2px -2px var(--color-border-dark-1),
83 inset 2px 2px var(--color-border-light-2);
84 background: var(--color-bg);
85 padding: 10px;
86
87 .Titlebar
88 {
89 background: linear-gradient(90deg, var(--color-titlebar-1), var(--color-titlebar-2));
90 margin: -6px -6px 10px -6px;
91 color: white;
92 padding: 3px;
93 }
94}
95
96.Dialog
97{
98 max-width: 910px;
99 margin: 0px auto;
100}
101
102.LabeledPanel
103{
104 border-top: solid var(--color-border-light-1) 1px;
105 border-right: solid var(--color-border-dark-1) 1px;
106 border-bottom: solid var(--color-border-dark-1) 1px;
107 border-left: solid var(--color-border-light-1) 1px;
108
109 box-shadow: 1px 1px var(--color-border-light-1),
110 -1px -1px var(--color-border-dark-1);
111 padding: 1ex;
112 padding-top: 0px;
113 margin: 2ex 0px;
114}
115
116.LabeledPanel > h3, .LabeledPanel > h2
117{
118 font-weight: normal;
119 display: inline-block;
120 position: relative;
121 top: calc(-0.5em);
122 left: 1ex;
123 background-color: var(--color-bg);
124 padding: 0px 1ex 0px 1ex;
125 margin: 0px;
126}
127
128input[type="number"]
129{
130 appearance: none;
131 border: none;
132 outline: none;
133 box-shadow: inset -1px -1px var(--color-border-light-2),
134 inset 1px 1px var(--color-border-dark-1),
135 inset -2px -2px var(--color-border-light-1),
136 inset 2px 2px var(--color-border-dark-2);
137 padding: 4px 0px 4px 6px;
138 font-family: monospace;
139 font-size: 0.9rem;
140}
141
142input[type="text"], input[type="url"], input[type="email"], textarea
143{
144 appearance: none;
145 border: none;
146 outline: none;
147 box-shadow: inset -1px -1px var(--color-border-light-2),
148 inset 1px 1px var(--color-border-dark-1),
149 inset -2px -2px var(--color-border-light-1),
150 inset 2px 2px var(--color-border-dark-2);
151 width: 100%;
152 padding: 4px 6px;
153 font-family: monospace;
154 font-size: 0.9rem;
155}
156
157a.Button, a.FloatButton
158{
159 color: black;
160 text-decoration: none;
161}
162
163button, input[type="submit"], input[type="color"]
164{
165 appearance: none;
166 border: none;
167 border-radius: 0px;
168 outline: none;
169 box-shadow: inset -1px -1px var(--color-border-dark-2),
170 inset 1px 1px var(--color-border-light-1),
171 inset -2px -2px var(--color-border-dark-1),
172 inset 2px 2px var(--color-border-light-2);
173 background: var(--color-bg);
174 text-align: center;
175}
176
177button, input[type="submit"]
178{
179 min-height: 23px;
180 min-width: 75px;
181 padding: 0 12px;
182}
183
184input[type="color"]
185{
186 padding: 4px;
187 width: 3em;
188 height: 2em;
189}
190
191.IconButton
192{
193 padding: 4px;
194 min-width: unset;
195 min-height: unset;
196}
197
198.FloatButton
199{
200 padding: 4px;
201 min-width: unset;
202 min-height: unset;
203 box-shadow: none;
204}
205
206.FloatButton:hover
207{
208 padding: 4px;
209 min-width: unset;
210 min-height: unset;
211 box-shadow: inset 1px 1px var(--color-border-light-1),
212 inset -1px -1px var(--color-border-dark-1);
213}
214
215button:active, .FloatButton:active
216{
217 box-shadow: inset -1px -1px var(--color-border-light-2),
218 inset 1px 1px var(--color-border-dark-1),
219 inset -2px -2px var(--color-border-light-1),
220 inset 2px 2px var(--color-border-dark-2);
221}
222
223hr
224{
225 border: none;
226 border-top: solid var(--color-border-dark-1) 1px;
227 border-bottom: solid var(--color-border-light-1) 1px;
228}
229
230.ButtonRow, .ButtonRowLeft
231{
232 display: flex;
233 gap: 10px;
234 margin: 10px 0;
235 align-items: baseline;
236}
237
238.ButtonRow
239{
240 justify-content: right;
241}
242
243.Toolbar
244{
245 display: flex;
246 border-bottom: solid var(--color-border-dark-1) 1px;
247 box-shadow: 0px 1px var(--color-border-light-1);
248 position: relative;
249 left: -10px;
250 top: -10px;
251 width: calc(100% + 2ex);
252 padding: 2px 8px;
253}
254
255table.InputFields
256{
257 border-collapse: collapse;
258 width: 100%;
259 table-layout: auto;
260
261 td
262 {
263 padding: 2px 2px;
264 }
265
266 td:nth-child(2)
267 {
268 width: 100%;
269 }
270
271}
272
273.StatusBar
274{
275 margin: 10px -6px -7px -6px;
276 display: flex;
277}
278
279.StatusCell
280{
281 box-shadow: inset -2px -2px var(--color-border-light-2),
282 inset 2px 2px var(--color-border-dark-1);
283 padding: 4px;
284 flex-grow: 1;
285}
286
287#Links
288{
289 box-shadow: inset -1px -1px var(--color-border-light-2),
290 inset 1px 1px var(--color-border-dark-1),
291 inset -2px -2px var(--color-border-light-1),
292 inset 2px 2px var(--color-border-dark-2);
293 padding: 2px;
294}
295
296table.TableView
297{
298 background-color: white;
299 border-collapse: collapse;
300 width: 100%;
301
302 thead th
303 {
304 box-shadow: inset -1px -1px var(--color-border-dark-2),
305 inset 1px 1px var(--color-border-light-1),
306 inset -2px -2px var(--color-border-dark-1),
307 inset 2px 2px var(--color-border-light-2);
308 background-color: var(--color-bg);
309 padding: 2px 4px 4px 4px;
310 text-align: left;
311 }
312
313 td
314 {
315 padding: 2px;
316 }
317}
318
319/* ========== App-specific =======================================> */
320
321#MainWindow
322{
323 max-width: 600px;
324 margin-left: auto;
325 margin-right: auto;
326}