@pajh/buldng 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/buldng-lib.js +339 -125
- package/dist/buldng-ops.js +258 -0
- package/dist/buldng-validate.js +114 -9
- package/dist/buldng.css +148 -0
- package/dist/clean.sh +14 -0
- package/dist/layout.ts +184 -0
- package/dist/types/global.d.ts +10 -0
- package/dist/workfile.js +37 -130
- package/dist/wrapper.html +24 -0
- package/package.json +2 -2
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { WorkFile } from "./workfile.js";
|
|
2
|
+
|
|
3
|
+
// buldng-ops.js
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import esbuild from "esbuild";
|
|
7
|
+
|
|
8
|
+
import { assertSourceRead, assertDestWrite, looksLikeFilename, ensureWorkFile } from "./buldng-validate.js";
|
|
9
|
+
export const OPS = {
|
|
10
|
+
file: op_file,
|
|
11
|
+
literal: op_literal,
|
|
12
|
+
append: op_append,
|
|
13
|
+
replace: op_replace,
|
|
14
|
+
compile: op_compile,
|
|
15
|
+
materialise: op_materialise,
|
|
16
|
+
wrap: op_wrap,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// --------------------------------------------------
|
|
20
|
+
// FILE — lazy read from disk. op:"file"
|
|
21
|
+
// --------------------------------------------------
|
|
22
|
+
export function file(strings, ...values) {
|
|
23
|
+
if (values.length > 0) {
|
|
24
|
+
throw new Error("file`` does not support interpolation");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const relPath = strings.join("");
|
|
28
|
+
const absPath = assertSourceRead(relPath);
|
|
29
|
+
|
|
30
|
+
return new WorkFile("file", { relPath, absPath }, []);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// op for "file"
|
|
34
|
+
export function op_file(config, children) {
|
|
35
|
+
return fs.readFileSync(config.absPath, "utf8");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// --------------------------------------------------
|
|
39
|
+
// LITERAL — inline text
|
|
40
|
+
// --------------------------------------------------
|
|
41
|
+
export function op_literal(config, children) {
|
|
42
|
+
return config.value;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// --------------------------------------------------
|
|
46
|
+
// APPEND — concat children in order
|
|
47
|
+
// --------------------------------------------------
|
|
48
|
+
export function op_append(config, children) {
|
|
49
|
+
let out = "";
|
|
50
|
+
|
|
51
|
+
for (const child of children) {
|
|
52
|
+
const chunk = child.execute();
|
|
53
|
+
|
|
54
|
+
if (out.length > 0 &&
|
|
55
|
+
!out.endsWith("\n") &&
|
|
56
|
+
!chunk.startsWith("\n")) {
|
|
57
|
+
out += "\n";
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
out += chunk;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return out;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// --------------------------------------------------
|
|
67
|
+
// REPLACE — replace tag with replacement
|
|
68
|
+
// --------------------------------------------------
|
|
69
|
+
export function op_replace(config, children) {
|
|
70
|
+
if (children.length !== 3) {
|
|
71
|
+
throw new Error(`replace(): expected 3 children, got ${children.length}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const [srcWF, tagWF, repWF] = children;
|
|
75
|
+
|
|
76
|
+
const src = srcWF.execute();
|
|
77
|
+
const tag = tagWF.execute();
|
|
78
|
+
const rep = repWF.execute();
|
|
79
|
+
|
|
80
|
+
if (!src.includes(tag)) {
|
|
81
|
+
throw new Error(`replace(): tag "${tag}" not found in source`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return src.replace(tag, rep);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// --------------------------------------------------
|
|
88
|
+
// COMPILE — esbuild wrapper
|
|
89
|
+
// --------------------------------------------------
|
|
90
|
+
export function op_compile(config, children) {
|
|
91
|
+
if (children.length !== 1) {
|
|
92
|
+
throw new Error(`compile(): expected 1 child, got ${children.length}`);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const child = children[0];
|
|
96
|
+
|
|
97
|
+
// If child is a file WorkFile, use its absolute path directly
|
|
98
|
+
if (child.op === "file" && child.config.absPath) {
|
|
99
|
+
const entry = child.config.absPath;
|
|
100
|
+
|
|
101
|
+
const result = esbuild.buildSync({
|
|
102
|
+
entryPoints: [entry],
|
|
103
|
+
bundle: true,
|
|
104
|
+
minify: true,
|
|
105
|
+
sourcemap: true,
|
|
106
|
+
format: "esm",
|
|
107
|
+
target: "esnext",
|
|
108
|
+
write: false
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
if (result.errors?.length) {
|
|
112
|
+
throw new Error("Build failed:\n" + result.errors.map(e => e.text).join("\n"));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return result.outputFiles[0].text;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Otherwise compile from stdin
|
|
119
|
+
const srcText = child.execute();
|
|
120
|
+
|
|
121
|
+
const result = esbuild.buildSync({
|
|
122
|
+
stdin: {
|
|
123
|
+
contents: srcText,
|
|
124
|
+
resolveDir: process.cwd(),
|
|
125
|
+
sourcefile: "input.ts"
|
|
126
|
+
},
|
|
127
|
+
bundle: true,
|
|
128
|
+
minify: true,
|
|
129
|
+
sourcemap: true,
|
|
130
|
+
format: "esm",
|
|
131
|
+
target: "esnext",
|
|
132
|
+
write: false
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (result.errors?.length) {
|
|
136
|
+
throw new Error("Build failed:\n" + result.errors.map(e => e.text).join("\n"));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return result.outputFiles[0].text;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// --------------------------------------------------
|
|
143
|
+
// MATERIALISE — write file if identical or new
|
|
144
|
+
// --------------------------------------------------
|
|
145
|
+
export function materialise(child, target) {
|
|
146
|
+
const childWF = ensureWorkFile(child, "materialise");
|
|
147
|
+
return new WorkFile("materialise", { target }, [childWF]);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export function op_materialise(config, children) {
|
|
151
|
+
if (children.length !== 1) {
|
|
152
|
+
throw new Error(`materialise(): expected 1 child, got ${children.length}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const child = children[0];
|
|
156
|
+
|
|
157
|
+
const text = child.execute();
|
|
158
|
+
|
|
159
|
+
if (looksLikeFilename(text)) {
|
|
160
|
+
console.warn(
|
|
161
|
+
`materialise(): child returned a filename '${text}' but expected text.\n` +
|
|
162
|
+
`Did you accidentally pass a materialised WorkFile instead of raw text?`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const abs = assertDestWrite(config.target);
|
|
167
|
+
|
|
168
|
+
if (!fs.existsSync(abs)) {
|
|
169
|
+
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
170
|
+
fs.writeFileSync(abs, text, "utf8");
|
|
171
|
+
// NEW: mark file read-only
|
|
172
|
+
fs.chmodSync(abs, 0o444);
|
|
173
|
+
console.log(`materialise: ${abs}`);
|
|
174
|
+
return "/" + config.target;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const existing = fs.readFileSync(abs, "utf8");
|
|
178
|
+
|
|
179
|
+
if (existing === text) {
|
|
180
|
+
return "/" + config.target;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
throw new Error(
|
|
184
|
+
`materialise(): cannot overwrite '${config.target}' — existing file differs from generated output`
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// --------------------------------------------------
|
|
189
|
+
// WRAP — pre + child + post
|
|
190
|
+
// --------------------------------------------------
|
|
191
|
+
export function op_wrap(config, children) {
|
|
192
|
+
if (children.length !== 1) {
|
|
193
|
+
throw new Error(`wrap(): expected 1 child, got ${children.length}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const inner = children[0].execute();
|
|
197
|
+
const pre = config.pre ?? "";
|
|
198
|
+
const post = config.post ?? "";
|
|
199
|
+
|
|
200
|
+
return pre + inner + post;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
// --------------------------------------------------
|
|
206
|
+
// literal(text)
|
|
207
|
+
// --------------------------------------------------
|
|
208
|
+
export function literal(value) {
|
|
209
|
+
return new WorkFile("literal", { value }, []);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// --------------------------------------------------
|
|
213
|
+
// wrap(pre, child, post)
|
|
214
|
+
// --------------------------------------------------
|
|
215
|
+
export function wrap(pre, child, post, tag=null) {
|
|
216
|
+
const childWF = ensureWorkFile(child, "wrap child");
|
|
217
|
+
return new WorkFile("wrap", { pre, post }, [childWF], tag);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function append() {
|
|
221
|
+
const children = [];
|
|
222
|
+
|
|
223
|
+
for (let i = 0; i < arguments.length; i++) {
|
|
224
|
+
const item = arguments[i];
|
|
225
|
+
const wf = ensureWorkFile(item, "append item");
|
|
226
|
+
children.push(wf);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (children.length < 2) {
|
|
230
|
+
throw new Error("append(): requires at least 2 items");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return new WorkFile("append", {}, children);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
// --------------------------------------------------
|
|
238
|
+
// replace(source, tag, replacement)
|
|
239
|
+
// --------------------------------------------------
|
|
240
|
+
export function replace(source, tag, replacement) {
|
|
241
|
+
const srcWF = ensureWorkFile(source, "source");
|
|
242
|
+
const tagWF = ensureWorkFile(tag, "tag");
|
|
243
|
+
const repWF = ensureWorkFile(replacement, "replacement");
|
|
244
|
+
|
|
245
|
+
if (tagWF.op === "literal" && tagWF.config.value.trim() === "") {
|
|
246
|
+
throw new Error(`replace(): tag cannot be blank`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return new WorkFile("replace", {}, [srcWF, tagWF, repWF]);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// --------------------------------------------------
|
|
253
|
+
// compile(source)
|
|
254
|
+
// --------------------------------------------------
|
|
255
|
+
export function compile(source, flags = {}) {
|
|
256
|
+
const srcWF = ensureWorkFile(source, "source");
|
|
257
|
+
return new WorkFile("compile", { flags }, [srcWF]);
|
|
258
|
+
}
|
package/dist/buldng-validate.js
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { WorkFile } from "./workfile.js";
|
|
6
|
+
import { literal } from "./buldng-ops.js";
|
|
4
7
|
// --------------------------------------------------
|
|
5
8
|
// GLOBAL SOURCE & DEST ROOTS
|
|
6
9
|
// --------------------------------------------------
|
|
@@ -25,24 +28,84 @@ export function setSource(dir) {
|
|
|
25
28
|
export function setDest(dir) {
|
|
26
29
|
const abs = path.resolve(dir);
|
|
27
30
|
|
|
31
|
+
// ------------------------------------------------------------
|
|
32
|
+
// If dest exists, perform full safety checks
|
|
33
|
+
// ------------------------------------------------------------
|
|
28
34
|
if (fs.existsSync(abs)) {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
|
|
36
|
+
// SECURITY: full recursive scan for BULDNG_SRC
|
|
37
|
+
const stack = [abs];
|
|
38
|
+
while (stack.length) {
|
|
39
|
+
const current = stack.pop();
|
|
40
|
+
const entries = fs.readdirSync(current, { withFileTypes: true });
|
|
41
|
+
|
|
42
|
+
for (const entry of entries) {
|
|
43
|
+
const full = path.join(current, entry.name);
|
|
44
|
+
|
|
45
|
+
if (entry.isDirectory()) {
|
|
46
|
+
stack.push(full);
|
|
47
|
+
} else if (entry.name === "BULDNG_SRC") {
|
|
48
|
+
throw new Error(`SECURITY: Dest directory "${abs}" contains BULDNG_SRC sentinel`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// SECURITY: require DEST sentinel in root
|
|
54
|
+
const destSentinel = path.join(abs, "BULDNG_DEST");
|
|
55
|
+
if (!fs.existsSync(destSentinel)) {
|
|
56
|
+
throw new Error(`SECURITY: Refusing to delete "${abs}" — missing BULDNG_DEST sentinel`);
|
|
32
57
|
}
|
|
33
58
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
59
|
+
// -----------------------------------------------------------
|
|
60
|
+
// Copy clean.sh from package into dest
|
|
61
|
+
// ------------------------------------------------------------
|
|
62
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
63
|
+
const __dirname = path.dirname(__filename);
|
|
64
|
+
const cleanSrc = path.join(__dirname, "clean.sh"); // your package copy
|
|
65
|
+
const cleanDest = path.join(abs, "clean.sh");
|
|
66
|
+
|
|
67
|
+
fs.copyFileSync(cleanSrc, cleanDest);
|
|
68
|
+
fs.chmodSync(cleanDest, 0o755);
|
|
69
|
+
|
|
70
|
+
// ------------------------------------------------------------
|
|
71
|
+
// Call safe_clean.sh with full path to clean.sh
|
|
72
|
+
// ------------------------------------------------------------
|
|
73
|
+
const safeClean = path.resolve("safe_clean.sh");
|
|
74
|
+
console.log(`SECURITY: Running safe_clean.sh on "${abs}"`);
|
|
75
|
+
|
|
76
|
+
const result = spawnSync(safeClean, [cleanDest], {
|
|
77
|
+
stdio: "inherit"
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (result.status !== 0) {
|
|
81
|
+
throw new Error(`SECURITY: safe_clean.sh failed with exit code ${result.status}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// ------------------------------------------------------------
|
|
85
|
+
// Verify directory is empty after cleaning
|
|
86
|
+
// ------------------------------------------------------------
|
|
87
|
+
const remaining = fs.readdirSync(abs);
|
|
88
|
+
if (remaining.length !== 0) {
|
|
89
|
+
throw new Error(`SECURITY: Dest directory "${abs}" not empty after safe_clean`);
|
|
37
90
|
}
|
|
38
|
-
} else {
|
|
39
|
-
fs.mkdirSync(abs, { recursive: true });
|
|
40
91
|
}
|
|
41
92
|
|
|
93
|
+
// ------------------------------------------------------------
|
|
94
|
+
// Always recreate DEST fresh
|
|
95
|
+
// ------------------------------------------------------------
|
|
96
|
+
fs.mkdirSync(abs, { recursive: true });
|
|
97
|
+
|
|
98
|
+
// Create DEST sentinel and mark read-only
|
|
99
|
+
const sentinelPath = path.join(abs, "BULDNG_DEST");
|
|
100
|
+
fs.writeFileSync(sentinelPath, "");
|
|
101
|
+
fs.chmodSync(sentinelPath, 0o444);
|
|
102
|
+
|
|
42
103
|
DEST = abs;
|
|
43
104
|
validateRoots();
|
|
44
105
|
}
|
|
45
106
|
|
|
107
|
+
|
|
108
|
+
|
|
46
109
|
export function getDest() {
|
|
47
110
|
if (!DEST) throw new Error("SECURITY: Dest root not set");
|
|
48
111
|
return DEST;
|
|
@@ -99,4 +162,46 @@ export function trackInput(absPath) {
|
|
|
99
162
|
if (absPath.startsWith(SRC) && !absPath.startsWith(DEST)) {
|
|
100
163
|
INPUTS.add(absPath);
|
|
101
164
|
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function looksLikeFilename(s) {
|
|
168
|
+
return typeof s === "string" &&
|
|
169
|
+
s.length < 200 &&
|
|
170
|
+
/[A-Za-z0-9_\-\/]+\.(html|css|js)$/.test(s);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function looksLikeHTML(s) {
|
|
174
|
+
return typeof s === "string" &&
|
|
175
|
+
s.trim().startsWith("<") &&
|
|
176
|
+
s.includes(">");
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function looksLikeCSS(s) {
|
|
180
|
+
return typeof s === "string" &&
|
|
181
|
+
s.includes("{") &&
|
|
182
|
+
s.includes("}");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export function looksLikeJS(s) {
|
|
186
|
+
return typeof s === "string" &&
|
|
187
|
+
(s.includes("function") ||
|
|
188
|
+
s.includes("const ") ||
|
|
189
|
+
s.includes("let "));
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function ensureLeadingSlash(p) {
|
|
193
|
+
return p.startsWith("/") ? p : "/" + p;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// --------------------------------------------------
|
|
197
|
+
// ensureWorkFile(x)
|
|
198
|
+
// --------------------------------------------------
|
|
199
|
+
export function ensureWorkFile(x, fieldName = "value") {
|
|
200
|
+
if (x instanceof WorkFile) return x;
|
|
201
|
+
|
|
202
|
+
if (typeof x === "string") {
|
|
203
|
+
return literal(x);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
throw new Error(`${fieldName} must be WorkFile or string`);
|
|
102
207
|
}
|
package/dist/buldng.css
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/* --------------------------------------------------
|
|
2
|
+
LOCAL FONTS
|
|
3
|
+
-------------------------------------------------- */
|
|
4
|
+
@font-face {
|
|
5
|
+
font-family: Inter;
|
|
6
|
+
src: url("/assets/InterVariable.woff2") format("woff2");
|
|
7
|
+
font-weight: 100 900;
|
|
8
|
+
font-display: swap;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
@font-face {
|
|
12
|
+
font-family: "JetBrains Mono";
|
|
13
|
+
src: url("/assets/JetBrainsMono-Regular.woff2") format("woff2");
|
|
14
|
+
font-weight: 400;
|
|
15
|
+
font-display: swap;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/* --------------------------------------------------
|
|
19
|
+
CAMERA GEOMETRY — JS-SIZED, CSS-LAID-OUT
|
|
20
|
+
Matches layout.ts:
|
|
21
|
+
CAM-PORTRAIT
|
|
22
|
+
CAM-LANDSCAPE-A
|
|
23
|
+
CAM-LANDSCAPE-B
|
|
24
|
+
-------------------------------------------------- */
|
|
25
|
+
|
|
26
|
+
#b-camera {
|
|
27
|
+
position: absolute;
|
|
28
|
+
inset: 0;
|
|
29
|
+
margin: auto;
|
|
30
|
+
container-type: size;
|
|
31
|
+
overflow: hidden;
|
|
32
|
+
background: var(--back);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/* --------------------------------------------------
|
|
36
|
+
CAMERA INTERNAL AREAS
|
|
37
|
+
-------------------------------------------------- */
|
|
38
|
+
|
|
39
|
+
#b-camera-style {
|
|
40
|
+
display: grid;
|
|
41
|
+
height: 100%;
|
|
42
|
+
width: 100%;
|
|
43
|
+
grid-template-areas:
|
|
44
|
+
"header"
|
|
45
|
+
"client"
|
|
46
|
+
"footer";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
#b-top-bar {
|
|
50
|
+
grid-area: header;
|
|
51
|
+
overflow: hidden;
|
|
52
|
+
container-type: size;
|
|
53
|
+
width: 100%;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
#b-client-area {
|
|
57
|
+
grid-area: client;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
#b-bottom-bar {
|
|
61
|
+
grid-area: footer;
|
|
62
|
+
overflow: hidden;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/* --------------------------------------------------
|
|
66
|
+
PORTRAIT MODE
|
|
67
|
+
JS sets width/height; CSS sets layout only
|
|
68
|
+
-------------------------------------------------- */
|
|
69
|
+
|
|
70
|
+
.CAM-PORTRAIT#b-camera-style {
|
|
71
|
+
grid-template-rows: 20% auto 5%
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/* --------------------------------------------------
|
|
75
|
+
LANDSCAPE-A MODE
|
|
76
|
+
JS sets width/height; CSS sets layout only
|
|
77
|
+
-------------------------------------------------- */
|
|
78
|
+
|
|
79
|
+
.CAM-LANDSCAPE-A#b-camera-style {
|
|
80
|
+
grid-template-rows: 15% 80% 5%;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/* --------------------------------------------------
|
|
84
|
+
LANDSCAPE-B MODE
|
|
85
|
+
JS sets width/height; CSS sets layout only
|
|
86
|
+
-------------------------------------------------- */
|
|
87
|
+
|
|
88
|
+
.CAM-LANDSCAPE-B#b-camera-style {
|
|
89
|
+
grid-template-rows: 15% 80% 5%;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/* --------------------------------------------------
|
|
93
|
+
GLOBAL THEME VARIABLES
|
|
94
|
+
-------------------------------------------------- */
|
|
95
|
+
:root[data-theme="light"] {
|
|
96
|
+
--back: #f7f7f7;
|
|
97
|
+
--panel: #ffffff;
|
|
98
|
+
--button: #f0f0f0;
|
|
99
|
+
--text: #222222;
|
|
100
|
+
--line: #9c9c9c;
|
|
101
|
+
--highlight1: #4aa3ff;
|
|
102
|
+
--highlight2: #ffd84a;
|
|
103
|
+
--logo-bg: #e0e0e0;
|
|
104
|
+
--logo-fg: #222;
|
|
105
|
+
--logo-shadow: 0 2px 4px rgb(0 0 0 0.15);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
:root[data-theme="dark"] {
|
|
109
|
+
--back: #111111;
|
|
110
|
+
--panel: #1b1b1b;
|
|
111
|
+
--button: #4a4a4a;
|
|
112
|
+
--text: #eeeeee;
|
|
113
|
+
--line: #848484;
|
|
114
|
+
--highlight1: #4aa3ff;
|
|
115
|
+
--highlight2: #ffd84a;
|
|
116
|
+
--logo-bg: #333;
|
|
117
|
+
--logo-fg: #eee;
|
|
118
|
+
--logo-shadow: 0 2px 4px rgb(0 0 0 0.4);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/* --------------------------------------------------
|
|
122
|
+
GLOBAL BODY
|
|
123
|
+
-------------------------------------------------- */
|
|
124
|
+
html {
|
|
125
|
+
margin: 0;
|
|
126
|
+
padding: 0;
|
|
127
|
+
height:100%;
|
|
128
|
+
width:100%;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
body {
|
|
132
|
+
background: var(--back);
|
|
133
|
+
margin: 0;
|
|
134
|
+
padding: 0;
|
|
135
|
+
height: 100%;
|
|
136
|
+
width: 100%;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
html, body, button, input, select, textarea {
|
|
140
|
+
font-family: Inter, sans-serif;
|
|
141
|
+
font-size: var(--std-font-size);
|
|
142
|
+
color: var(--text);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
button {
|
|
146
|
+
background: var(--button);
|
|
147
|
+
}
|
|
148
|
+
|
package/dist/clean.sh
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
# Sentinel must exist in the current directory
|
|
5
|
+
if [ ! -f BULDNG_DEST ]; then
|
|
6
|
+
echo "clean.sh: refusing — sentinel missing"
|
|
7
|
+
exit 1
|
|
8
|
+
fi
|
|
9
|
+
|
|
10
|
+
# Delete everything in the current directory, including subdirectories
|
|
11
|
+
rm -rf ./*
|
|
12
|
+
|
|
13
|
+
# Delete the script itself
|
|
14
|
+
rm -f "$0"
|