@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/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();
@@ -0,0 +1,10 @@
1
+ export {};
2
+
3
+ declare global {
4
+ interface Window {
5
+ __ready: {
6
+ a: Promise<void>;
7
+ _resolveA: () => void;
8
+ };
9
+ }
10
+ }
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
- switch (this.op) {
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
- console.log(`Saved: ${abs}`);
160
- return abs;
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>
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@pajh/buldng",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "type": "module",
5
- "main": "dist/init.js",
5
+ "main": "dist/buldng-lib.js",
6
6
  "files": ["dist"],
7
7
  "scripts": {
8
8
  "build": "node build.js"