@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
package/dist/layout.ts
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/* --------------------------------------------------
|
|
2
|
+
GLOBAL VIEWPORT STATE
|
|
3
|
+
-------------------------------------------------- */
|
|
4
|
+
function applyCameraSize(mode: string) {
|
|
5
|
+
const cam = document.getElementById("b-camera");
|
|
6
|
+
if (!cam) return;
|
|
7
|
+
|
|
8
|
+
const vw = window.innerWidth;
|
|
9
|
+
const vh = window.innerHeight;
|
|
10
|
+
|
|
11
|
+
let width, height;
|
|
12
|
+
|
|
13
|
+
if (mode === "PORTRAIT") {
|
|
14
|
+
height = vh;
|
|
15
|
+
width = vw;
|
|
16
|
+
} else if (mode === "LANDSCAPE_A") {
|
|
17
|
+
height = vh;
|
|
18
|
+
width = vh * 4/3;
|
|
19
|
+
} else {
|
|
20
|
+
// LANDSCAPE_B
|
|
21
|
+
width = vw;
|
|
22
|
+
height = vw * 3/4;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
cam.style.width = `${width}px`;
|
|
26
|
+
cam.style.height = `${height}px`;
|
|
27
|
+
|
|
28
|
+
// Center WITHOUT transform (no containing-block issues)
|
|
29
|
+
cam.style.position = "absolute";
|
|
30
|
+
cam.style.top = "0";
|
|
31
|
+
cam.style.bottom = "0";
|
|
32
|
+
cam.style.left = "0";
|
|
33
|
+
cam.style.right = "0";
|
|
34
|
+
cam.style.margin = "auto";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const buldrViewport = {
|
|
38
|
+
width: window.innerWidth,
|
|
39
|
+
height: window.innerHeight,
|
|
40
|
+
aspect: window.innerWidth / window.innerHeight, // w/h
|
|
41
|
+
mode: "PORTRAIT", // PORTRAIT | LANDSCAPE_A | LANDSCAPE_B
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/* --------------------------------------------------
|
|
45
|
+
MODE COMPUTATION
|
|
46
|
+
-------------------------------------------------- */
|
|
47
|
+
|
|
48
|
+
function computeMode() {
|
|
49
|
+
const a = buldrViewport.aspect;
|
|
50
|
+
|
|
51
|
+
if (a >= 4/3) return "LANDSCAPE_A";
|
|
52
|
+
if (a <= 3/4) return "PORTRAIT";
|
|
53
|
+
return "LANDSCAPE_B";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* --------------------------------------------------
|
|
57
|
+
APPLY CAMERA MODE CLASS
|
|
58
|
+
-------------------------------------------------- */
|
|
59
|
+
|
|
60
|
+
function applyCameraMode(mode: string) {
|
|
61
|
+
const cam = document.getElementById("b-camera");
|
|
62
|
+
if (!cam) return;
|
|
63
|
+
const style = document.getElementById("b-camera-style");
|
|
64
|
+
if (!style) return;
|
|
65
|
+
|
|
66
|
+
// Remove all camera classes
|
|
67
|
+
cam.classList.remove("CAM-PORTRAIT", "CAM-LANDSCAPE-A", "CAM-LANDSCAPE-B");
|
|
68
|
+
style.classList.remove("CAM-PORTRAIT", "CAM-LANDSCAPE-A", "CAM-LANDSCAPE-B");
|
|
69
|
+
// Add the new one
|
|
70
|
+
if (mode === "PORTRAIT") {
|
|
71
|
+
cam.classList.add("CAM-PORTRAIT");
|
|
72
|
+
style.classList.add("CAM-PORTRAIT");
|
|
73
|
+
} else if (mode === "LANDSCAPE_A") {
|
|
74
|
+
cam.classList.add("CAM-LANDSCAPE-A");
|
|
75
|
+
style.classList.add("CAM-LANDSCAPE-A");
|
|
76
|
+
} else {
|
|
77
|
+
cam.classList.add("CAM-LANDSCAPE-B");
|
|
78
|
+
style.classList.add("CAM-LANDSCAPE-B");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/* --------------------------------------------------
|
|
83
|
+
APPLY GLOBAL CSS MODE (PORTRAIT / LANDSCAPE)
|
|
84
|
+
-------------------------------------------------- */
|
|
85
|
+
function applyCssMode(mode: string) {
|
|
86
|
+
const html = document.documentElement;
|
|
87
|
+
|
|
88
|
+
if (mode === "PORTRAIT") {
|
|
89
|
+
html.classList.remove("LANDSCAPE");
|
|
90
|
+
html.classList.add("PORTRAIT");
|
|
91
|
+
} else {
|
|
92
|
+
html.classList.remove("PORTRAIT");
|
|
93
|
+
html.classList.add("LANDSCAPE");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/* --------------------------------------------------
|
|
97
|
+
FONT SIZE COMPUTATION (NEW)
|
|
98
|
+
-------------------------------------------------- */
|
|
99
|
+
|
|
100
|
+
const FACTOR_PORTRAIT = 0.035;
|
|
101
|
+
const FACTOR_LANDSCAPE_B = 0.035;
|
|
102
|
+
const FACTOR_LANDSCAPE_A = 0.020;
|
|
103
|
+
|
|
104
|
+
function applyFontSize(mode: string) {
|
|
105
|
+
const cam = document.getElementById("b-camera");
|
|
106
|
+
if (!cam) return;
|
|
107
|
+
|
|
108
|
+
const camWidth = cam.getBoundingClientRect().width;
|
|
109
|
+
|
|
110
|
+
let factor = FACTOR_PORTRAIT;
|
|
111
|
+
if (mode === "LANDSCAPE_A") factor = FACTOR_LANDSCAPE_A;
|
|
112
|
+
else if (mode === "LANDSCAPE_B") factor = FACTOR_LANDSCAPE_B;
|
|
113
|
+
|
|
114
|
+
const size = camWidth * factor;
|
|
115
|
+
|
|
116
|
+
document.documentElement.style.setProperty("--std-font-size", `${size}px`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/* --------------------------------------------------
|
|
120
|
+
UPDATE + DEBUG
|
|
121
|
+
-------------------------------------------------- */
|
|
122
|
+
|
|
123
|
+
function updateViewportInfo() {
|
|
124
|
+
buldrViewport.width = window.innerWidth;
|
|
125
|
+
buldrViewport.height = window.innerHeight;
|
|
126
|
+
buldrViewport.aspect = window.innerWidth / window.innerHeight;
|
|
127
|
+
|
|
128
|
+
const mode = computeMode();
|
|
129
|
+
buldrViewport.mode = mode;
|
|
130
|
+
|
|
131
|
+
console.log(
|
|
132
|
+
`[BULDR] viewport ${buldrViewport.width}x${buldrViewport.height}, `
|
|
133
|
+
+ `aspect=${buldrViewport.aspect.toFixed(3)}, `
|
|
134
|
+
+ `mode=${mode}`
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
applyCameraMode(mode);
|
|
138
|
+
applyCssMode(mode);
|
|
139
|
+
applyCameraSize(mode);
|
|
140
|
+
applyFontSize(mode);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* --------------------------------------------------
|
|
144
|
+
THEME
|
|
145
|
+
-------------------------------------------------- */
|
|
146
|
+
|
|
147
|
+
function initTheme() {
|
|
148
|
+
const btn = document.getElementById("theme-toggle");
|
|
149
|
+
if (!btn) return;
|
|
150
|
+
|
|
151
|
+
const current = localStorage.getItem("theme") || "light";
|
|
152
|
+
document.documentElement.dataset.theme = current;
|
|
153
|
+
|
|
154
|
+
btn.addEventListener("click", () => {
|
|
155
|
+
const next = document.documentElement.dataset.theme === "light" ? "dark" : "light";
|
|
156
|
+
document.documentElement.dataset.theme = next;
|
|
157
|
+
localStorage.setItem("theme", next);
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/* --------------------------------------------------
|
|
162
|
+
VIEWPORT LISTENER
|
|
163
|
+
-------------------------------------------------- */
|
|
164
|
+
|
|
165
|
+
function initViewportListener() {
|
|
166
|
+
updateViewportInfo(); // initial
|
|
167
|
+
|
|
168
|
+
window.addEventListener("resize", () => {
|
|
169
|
+
updateViewportInfo();
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/* --------------------------------------------------
|
|
174
|
+
LAYOUT INIT
|
|
175
|
+
-------------------------------------------------- */
|
|
176
|
+
|
|
177
|
+
function initLayout() {
|
|
178
|
+
initTheme();
|
|
179
|
+
initViewportListener();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Run layout immediately
|
|
183
|
+
initLayout();
|
|
184
|
+
window.__ready._resolveA();
|
package/dist/workfile.js
CHANGED
|
@@ -6,142 +6,28 @@ import fs from "node:fs";
|
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import esbuild from "esbuild";
|
|
8
8
|
import { assertDestWrite } from "./buldng-lib.js";
|
|
9
|
+
import { OPS } from "./buldng-ops.js";
|
|
9
10
|
|
|
10
11
|
export class WorkFile {
|
|
11
|
-
constructor(op, config = {}, children = []) {
|
|
12
|
+
constructor(op, config = {}, children = [], tag = null) {
|
|
13
|
+
if (!OPS[op]) {
|
|
14
|
+
throw new Error(`Invalid WorkFile op "${op}" — not one of: ${Object.keys(OPS).join(", ")}`);
|
|
15
|
+
}
|
|
12
16
|
this.op = op; // "file", "literal", "append", "replace", "compile"
|
|
13
17
|
this.config = Object.freeze(config);
|
|
14
18
|
this.children = Object.freeze(children);
|
|
15
|
-
|
|
19
|
+
this.tag = tag; // optional tag
|
|
16
20
|
Object.freeze(this); // full immutability
|
|
17
21
|
}
|
|
18
|
-
|
|
22
|
+
|
|
19
23
|
// --------------------------------------------------
|
|
20
24
|
// execute(): recursively produce output string
|
|
21
25
|
// --------------------------------------------------
|
|
22
26
|
execute() {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
// ----------------------------------------------
|
|
26
|
-
// FILE — lazy read from disk
|
|
27
|
-
// ----------------------------------------------
|
|
28
|
-
case "file": {
|
|
29
|
-
const abs = this.config.absPath;
|
|
30
|
-
return fs.readFileSync(abs, "utf8");
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// ----------------------------------------------
|
|
34
|
-
// LITERAL — inline text
|
|
35
|
-
// ----------------------------------------------
|
|
36
|
-
case "literal": {
|
|
37
|
-
return this.config.value;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// ----------------------------------------------
|
|
41
|
-
// APPEND — concat children in order
|
|
42
|
-
// ----------------------------------------------
|
|
43
|
-
case "append": {
|
|
44
|
-
let out = "";
|
|
45
|
-
|
|
46
|
-
for (const child of this.children) {
|
|
47
|
-
const chunk = child.execute();
|
|
48
|
-
|
|
49
|
-
if (out.length > 0 &&
|
|
50
|
-
!out.endsWith("\n") &&
|
|
51
|
-
!chunk.startsWith("\n")) {
|
|
52
|
-
out += "\n";
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
out += chunk;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return out;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ----------------------------------------------
|
|
62
|
-
// REPLACE — replace tag with replacement
|
|
63
|
-
// ----------------------------------------------
|
|
64
|
-
case "replace": {
|
|
65
|
-
if (this.children.length !== 3) {
|
|
66
|
-
throw new Error(`replace(): expected 3 children, got ${this.children.length}`);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const [srcWF, tagWF, repWF] = this.children;
|
|
70
|
-
|
|
71
|
-
const src = srcWF.execute();
|
|
72
|
-
const tag = tagWF.execute();
|
|
73
|
-
const rep = repWF.execute();
|
|
74
|
-
|
|
75
|
-
if (!src.includes(tag)) {
|
|
76
|
-
throw new Error(`replace(): tag "${tag}" not found in source`);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
return src.replace(tag, rep);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// ----------------------------------------------
|
|
83
|
-
// COMPILE — esbuild wrapper
|
|
84
|
-
// ----------------------------------------------
|
|
85
|
-
case "compile": {
|
|
86
|
-
if (this.children.length !== 1) {
|
|
87
|
-
throw new Error(`compile(): expected 1 child, got ${this.children.length}`);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const child = this.children[0];
|
|
91
|
-
|
|
92
|
-
// If child is a file WorkFile, use its absolute path directly
|
|
93
|
-
if (child.op === "file" && child.config.absPath) {
|
|
94
|
-
const entry = child.config.absPath;
|
|
95
|
-
|
|
96
|
-
const result = esbuild.buildSync({
|
|
97
|
-
entryPoints: [entry],
|
|
98
|
-
bundle: true,
|
|
99
|
-
minify: true,
|
|
100
|
-
sourcemap: true,
|
|
101
|
-
format: "esm",
|
|
102
|
-
target: "esnext",
|
|
103
|
-
write: false
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
if (result.errors?.length) {
|
|
107
|
-
throw new Error("Build failed:\n" + result.errors.map(e => e.text).join("\n"));
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return result.outputFiles[0].text;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// Otherwise compile from stdin
|
|
114
|
-
const srcText = child.execute();
|
|
115
|
-
|
|
116
|
-
const result = esbuild.buildSync({
|
|
117
|
-
stdin: {
|
|
118
|
-
contents: srcText,
|
|
119
|
-
resolveDir: process.cwd(),
|
|
120
|
-
sourcefile: "input.ts"
|
|
121
|
-
},
|
|
122
|
-
bundle: true,
|
|
123
|
-
minify: true,
|
|
124
|
-
sourcemap: true,
|
|
125
|
-
format: "esm",
|
|
126
|
-
target: "esnext",
|
|
127
|
-
write: false
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
if (result.errors?.length) {
|
|
131
|
-
throw new Error("Build failed:\n" + result.errors.map(e => e.text).join("\n"));
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return result.outputFiles[0].text;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// ----------------------------------------------
|
|
138
|
-
// Unknown op
|
|
139
|
-
// ----------------------------------------------
|
|
140
|
-
default:
|
|
141
|
-
throw new Error(`WorkFile.execute(): unknown op "${this.op}"`);
|
|
142
|
-
}
|
|
27
|
+
return OPS[this.op](this.config, this.children);
|
|
28
|
+
|
|
143
29
|
}
|
|
144
|
-
|
|
30
|
+
|
|
145
31
|
// --------------------------------------------------
|
|
146
32
|
// save(): write output to disk
|
|
147
33
|
// --------------------------------------------------
|
|
@@ -149,14 +35,35 @@ export class WorkFile {
|
|
|
149
35
|
if (!target) {
|
|
150
36
|
throw new Error("WorkFile.save(): target filename required");
|
|
151
37
|
}
|
|
152
|
-
|
|
38
|
+
|
|
153
39
|
const abs = assertDestWrite(target);
|
|
154
40
|
const out = this.execute();
|
|
155
|
-
|
|
41
|
+
|
|
42
|
+
// If file exists, enforce integrity rule
|
|
43
|
+
if (fs.existsSync(abs)) {
|
|
44
|
+
const existing = fs.readFileSync(abs, "utf8");
|
|
45
|
+
|
|
46
|
+
if (existing === out) {
|
|
47
|
+
// Byte-identical → warn but allow
|
|
48
|
+
console.warn(
|
|
49
|
+
`WorkFile.save(): attempted to overwrite '${target}' with identical content — continuing`
|
|
50
|
+
);
|
|
51
|
+
return "/" + target;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Different → fatal error
|
|
55
|
+
throw new Error(
|
|
56
|
+
`WorkFile.save(): cannot overwrite '${target}' — existing file differs from generated output`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// File does not exist → write it
|
|
156
61
|
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
|
157
62
|
fs.writeFileSync(abs, out, "utf8");
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
63
|
+
// NEW: mark file read-only
|
|
64
|
+
fs.chmodSync(abs, 0o444);
|
|
65
|
+
|
|
66
|
+
console.log(`save: ${abs}`);
|
|
67
|
+
return "/" + target;
|
|
161
68
|
}
|
|
162
|
-
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
6
|
+
<title>BULDR.IO</title>
|
|
7
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
8
|
+
<link rel="stylesheet" href="/buldng.css">
|
|
9
|
+
|
|
10
|
+
<!--CSS-->
|
|
11
|
+
<script>
|
|
12
|
+
window.__ready = {};
|
|
13
|
+
window.__ready.a = new Promise((resolve) => { window.__ready._resolveA = resolve; });
|
|
14
|
+
</script>
|
|
15
|
+
<script type="module" src="/layout.js"></script>
|
|
16
|
+
<!--SCRIPTS-->
|
|
17
|
+
</head>
|
|
18
|
+
<body>
|
|
19
|
+
<div id="b-camera">
|
|
20
|
+
<!-- Project outline (camera-style) will be stitched here -->
|
|
21
|
+
<!--CAMERA-STYLE-->
|
|
22
|
+
</div>
|
|
23
|
+
</body>
|
|
24
|
+
</html>
|