diff options
Diffstat (limited to 'button-maker')
-rw-r--r-- | button-maker/index.html | 28 | ||||
-rw-r--r-- | button-maker/scripts/lib.js | 79 | ||||
-rw-r--r-- | button-maker/scripts/main.js | 279 | ||||
-rw-r--r-- | button-maker/scripts/tiles.js | 94 | ||||
-rw-r--r-- | button-maker/styles.css | 326 |
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. | ||
4 | const 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 | |||
31 | const DEFAULT_APP_STATE = { | ||
32 | button_type: "netscape", | ||
33 | button_params: DEFAULT_NETSCAPE_PARAMS, | ||
34 | } | ||
35 | |||
36 | const SVG_ATTRIBUTES = {viewBox: "0 0 88 31", width: 88, height: 31}; | ||
37 | |||
38 | // Return three integers as an array. | ||
39 | function 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 | |||
66 | function lighten(rgb, delta) | ||
67 | { | ||
68 | return [rgb[0] + delta, rgb[1] + delta, rgb[2] + delta]; | ||
69 | } | ||
70 | |||
71 | function darken(rgb, delta) | ||
72 | { | ||
73 | return [rgb[0] - delta, rgb[1] - delta, rgb[2] - delta]; | ||
74 | } | ||
75 | |||
76 | function 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 @@ | |||
1 | const h = preact.h; | ||
2 | |||
3 | function 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 | |||
78 | function 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 | |||
138 | function 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 | |||
248 | function 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 | |||
273 | function App() | ||
274 | { | ||
275 | return h("div", {}, | ||
276 | h(EditorView, {app_state: DEFAULT_APP_STATE})); | ||
277 | } | ||
278 | |||
279 | preact.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 @@ | |||
1 | const 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} \ | ||
20 | 3, ${CELL_SIZE - 15} \ | ||
21 | 13, ${CELL_SIZE - 25} \ | ||
22 | 13, ${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 | |||
94 | const 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 | |||
6 | html, body, div, span, applet, object, iframe, | ||
7 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, | ||
8 | a, abbr, acronym, address, big, cite, code, | ||
9 | del, dfn, em, img, ins, kbd, q, s, samp, | ||
10 | small, strike, strong, sub, sup, tt, var, | ||
11 | b, u, i, center, | ||
12 | dl, dt, dd, ol, ul, li, | ||
13 | fieldset, form, label, legend, | ||
14 | table, caption, tbody, tfoot, thead, tr, th, td, | ||
15 | article, aside, canvas, details, embed, | ||
16 | figure, figcaption, footer, header, hgroup, | ||
17 | menu, nav, output, ruby, section, summary, | ||
18 | time, 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 */ | ||
27 | article, aside, details, figcaption, figure, | ||
28 | footer, header, hgroup, menu, nav, section { | ||
29 | display: block; | ||
30 | } | ||
31 | body { | ||
32 | line-height: 1; | ||
33 | } | ||
34 | ol, ul { | ||
35 | list-style: none; | ||
36 | } | ||
37 | blockquote, q { | ||
38 | quotes: none; | ||
39 | } | ||
40 | blockquote:before, blockquote:after, | ||
41 | q:before, q:after { | ||
42 | content: ''; | ||
43 | content: none; | ||
44 | } | ||
45 | table { | ||
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 | |||
69 | html | ||
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 | |||
128 | input[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 | |||
142 | input[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 | |||
157 | a.Button, a.FloatButton | ||
158 | { | ||
159 | color: black; | ||
160 | text-decoration: none; | ||
161 | } | ||
162 | |||
163 | button, 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 | |||
177 | button, input[type="submit"] | ||
178 | { | ||
179 | min-height: 23px; | ||
180 | min-width: 75px; | ||
181 | padding: 0 12px; | ||
182 | } | ||
183 | |||
184 | input[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 | |||
215 | button: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 | |||
223 | hr | ||
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 | |||
255 | table.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 | |||
296 | table.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 | } | ||