@myxo-victor/chexjs 6.0.0

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.
@@ -0,0 +1,354 @@
1
+ <?php
2
+ declare(strict_types=1);
3
+
4
+ /**
5
+ * Chex 6.0.0 Secure Database Engine
6
+ *
7
+ * Configure your database credentials in CONFIG below.
8
+ * This endpoint is used by Chex.db.connect(...).
9
+ */
10
+
11
+ header('Content-Type: application/json; charset=utf-8');
12
+
13
+ // ----------------------------
14
+ // CONFIG: EDIT THESE VALUES
15
+ // ----------------------------
16
+ $CONFIG = [
17
+ // Add your database host, database name, username, and password here.
18
+ 'db_host' => '127.0.0.1',
19
+ 'db_port' => 3306,
20
+ 'db_name' => 'test', #Change this to your own database name
21
+ 'db_user' => 'root', #This too
22
+ 'db_pass' => '', #This too
23
+ 'db_charset' => 'utf8mb4',
24
+
25
+ // Create a long random secret key and use same key in Chex.db.connect({ apiKey: '...' }).
26
+ // Leave empty to disable API key enforcement (NOT recommended for production).
27
+ 'api_key' => 'CHANGE_THIS_TO_A_LONG_RANDOM_SECRET',
28
+
29
+ // Allowed frontend origins. Keep minimal in production.
30
+ 'allowed_origins' => [
31
+ 'http://localhost', #This too, this one is for localhost
32
+ 'http://127.0.0.1' #and this
33
+ ],
34
+
35
+ // Whitelist allowed tables to prevent access to unexpected DB tables.
36
+ 'allowed_tables' => [
37
+ 'users' #Add your database tables here
38
+ ],
39
+
40
+ // Set true in development to include exception details.
41
+ 'debug' => true,
42
+ ];
43
+
44
+ function respond(int $status, array $payload): void {
45
+ http_response_code($status);
46
+ echo json_encode($payload, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
47
+ exit;
48
+ }
49
+
50
+ function sanitize_identifier(string $value): string {
51
+ if (!preg_match('/^[A-Za-z_][A-Za-z0-9_]*$/', $value)) {
52
+ throw new InvalidArgumentException('Invalid identifier.');
53
+ }
54
+ return $value;
55
+ }
56
+
57
+ function sanitize_identifier_list($value): array {
58
+ if (!is_array($value) || count($value) === 0) {
59
+ throw new InvalidArgumentException('Invalid column list.');
60
+ }
61
+ $out = [];
62
+ foreach ($value as $column) {
63
+ if (!is_string($column)) {
64
+ throw new InvalidArgumentException('Invalid column in list.');
65
+ }
66
+ $out[] = sanitize_identifier($column);
67
+ }
68
+ return array_values(array_unique($out));
69
+ }
70
+
71
+ function ensure_allowed_table(string $table, array $allowedTables): string {
72
+ $safeTable = sanitize_identifier($table);
73
+ if (!in_array($safeTable, $allowedTables, true)) {
74
+ throw new InvalidArgumentException('Table is not allowed.');
75
+ }
76
+ return $safeTable;
77
+ }
78
+
79
+ function require_json_payload(): array {
80
+ $raw = file_get_contents('php://input');
81
+ $data = json_decode((string)$raw, true);
82
+ if (!is_array($data)) {
83
+ throw new InvalidArgumentException('Invalid JSON payload.');
84
+ }
85
+ return $data;
86
+ }
87
+
88
+ function build_where_clause(array $where, array &$params): string {
89
+ if (count($where) === 0) return '';
90
+ $parts = [];
91
+ $i = 0;
92
+ foreach ($where as $column => $value) {
93
+ if (!is_string($column)) {
94
+ throw new InvalidArgumentException('Invalid where column.');
95
+ }
96
+ $safeColumn = sanitize_identifier($column);
97
+ $param = ':w' . $i++;
98
+ $parts[] = "`{$safeColumn}` = {$param}";
99
+ $params[$param] = $value;
100
+ }
101
+ return ' WHERE ' . implode(' AND ', $parts);
102
+ }
103
+
104
+ try {
105
+ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
106
+ http_response_code(204);
107
+ exit;
108
+ }
109
+
110
+ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
111
+ respond(405, ['ok' => false, 'error' => 'Method not allowed. Use POST.']);
112
+ }
113
+
114
+ $origin = $_SERVER['HTTP_ORIGIN'] ?? '';
115
+ if ($origin !== '' && in_array($origin, $CONFIG['allowed_origins'], true)) {
116
+ header('Access-Control-Allow-Origin: ' . $origin);
117
+ header('Vary: Origin');
118
+ header('Access-Control-Allow-Methods: POST, OPTIONS');
119
+ header('Access-Control-Allow-Headers: Content-Type, X-Chex-Key');
120
+ } elseif ($origin !== '') {
121
+ respond(403, ['ok' => false, 'error' => 'Origin not allowed.']);
122
+ }
123
+
124
+ $apiKey = trim((string)$CONFIG['api_key']);
125
+ if ($apiKey !== '') {
126
+ $requestKey = (string)($_SERVER['HTTP_X_CHEX_KEY'] ?? '');
127
+ if ($requestKey === '' || !hash_equals($apiKey, $requestKey)) {
128
+ respond(401, ['ok' => false, 'error' => 'Unauthorized request key.']);
129
+ }
130
+ }
131
+
132
+ $payload = require_json_payload();
133
+ $op = isset($payload['op']) ? (string)$payload['op'] : '';
134
+ $table = isset($payload['table']) ? (string)$payload['table'] : '';
135
+ $safeTable = ensure_allowed_table($table, $CONFIG['allowed_tables']);
136
+
137
+ $dsn = sprintf(
138
+ 'mysql:host=%s;port=%d;dbname=%s;charset=%s',
139
+ $CONFIG['db_host'],
140
+ (int)$CONFIG['db_port'],
141
+ $CONFIG['db_name'],
142
+ $CONFIG['db_charset']
143
+ );
144
+ $pdo = new PDO($dsn, $CONFIG['db_user'], $CONFIG['db_pass'], [
145
+ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
146
+ PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
147
+ PDO::ATTR_EMULATE_PREPARES => false,
148
+ ]);
149
+
150
+ switch ($op) {
151
+ case 'read': {
152
+ $select = isset($payload['select']) ? sanitize_identifier_list($payload['select']) : ['*'];
153
+ $selectSql = $select === ['*'] ? '*' : implode(', ', array_map(fn($c) => "`{$c}`", $select));
154
+
155
+ $params = [];
156
+ $where = is_array($payload['where'] ?? null) ? $payload['where'] : [];
157
+ $whereSql = build_where_clause($where, $params);
158
+
159
+ $orderBySql = '';
160
+ if (isset($payload['orderBy']) && is_string($payload['orderBy']) && $payload['orderBy'] !== '') {
161
+ $direction = 'ASC';
162
+ $orderBy = trim($payload['orderBy']);
163
+ if (strpos($orderBy, '-') === 0) {
164
+ $direction = 'DESC';
165
+ $orderBy = substr($orderBy, 1);
166
+ }
167
+ $safeOrder = sanitize_identifier($orderBy);
168
+ $orderBySql = " ORDER BY `{$safeOrder}` {$direction}";
169
+ }
170
+
171
+ $limitSql = '';
172
+ if (isset($payload['limit'])) {
173
+ $limit = (int)$payload['limit'];
174
+ if ($limit < 1 || $limit > 500) {
175
+ throw new InvalidArgumentException('limit must be between 1 and 500.');
176
+ }
177
+ $limitSql = " LIMIT {$limit}";
178
+ }
179
+
180
+ $offsetSql = '';
181
+ if (isset($payload['offset'])) {
182
+ $offset = (int)$payload['offset'];
183
+ if ($offset < 0) {
184
+ throw new InvalidArgumentException('offset must be >= 0.');
185
+ }
186
+ $offsetSql = " OFFSET {$offset}";
187
+ }
188
+
189
+ $sql = "SELECT {$selectSql} FROM `{$safeTable}`{$whereSql}{$orderBySql}{$limitSql}{$offsetSql}";
190
+ $stmt = $pdo->prepare($sql);
191
+ $stmt->execute($params);
192
+ $rows = $stmt->fetchAll();
193
+ respond(200, ['ok' => true, 'data' => $rows, 'count' => count($rows)]);
194
+ }
195
+
196
+ case 'create': {
197
+ $data = $payload['data'] ?? null;
198
+ if (!is_array($data) || count($data) === 0) {
199
+ throw new InvalidArgumentException('create requires non-empty data object.');
200
+ }
201
+
202
+ $columns = [];
203
+ $placeholders = [];
204
+ $params = [];
205
+ $i = 0;
206
+ foreach ($data as $column => $value) {
207
+ if (!is_string($column)) {
208
+ throw new InvalidArgumentException('Invalid create column.');
209
+ }
210
+ $safeColumn = sanitize_identifier($column);
211
+ $param = ':c' . $i++;
212
+ $columns[] = "`{$safeColumn}`";
213
+ $placeholders[] = $param;
214
+ $params[$param] = $value;
215
+ }
216
+
217
+ $sql = "INSERT INTO `{$safeTable}` (" . implode(', ', $columns) . ") VALUES (" . implode(', ', $placeholders) . ")";
218
+ $stmt = $pdo->prepare($sql);
219
+ $stmt->execute($params);
220
+ respond(201, ['ok' => true, 'data' => ['id' => $pdo->lastInsertId()]]);
221
+ }
222
+
223
+ case 'register': {
224
+ $data = $payload['data'] ?? null;
225
+ if (!is_array($data) || count($data) === 0) {
226
+ throw new InvalidArgumentException('register requires non-empty data object.');
227
+ }
228
+
229
+ // Hash the password before storing it
230
+ if (isset($data['password'])) {
231
+ $data['password_hash'] = password_hash($data['password'], PASSWORD_DEFAULT);
232
+ unset($data['password']);
233
+ }
234
+
235
+ $columns = [];
236
+ $placeholders = [];
237
+ $params = [];
238
+ $i = 0;
239
+ foreach ($data as $column => $value) {
240
+ if (!is_string($column)) {
241
+ throw new InvalidArgumentException('Invalid register column.');
242
+ }
243
+ $safeColumn = sanitize_identifier($column);
244
+ $param = ':c' . $i++;
245
+ $columns[] = "`{$safeColumn}`";
246
+ $placeholders[] = $param;
247
+ $params[$param] = $value;
248
+ }
249
+ $sql = "INSERT INTO `{$safeTable}` (" . implode(', ', $columns) . ") VALUES (" . implode(', ', $placeholders) . ")";
250
+ $stmt = $pdo->prepare($sql);
251
+ $stmt->execute($params);
252
+ respond(201, ['ok' => true, 'data' => ['id' => $pdo->lastInsertId()]]);
253
+ }
254
+ case 'update': {
255
+ $where = $payload['where'] ?? null;
256
+ $data = $payload['data'] ?? null;
257
+ if (!is_array($where) || count($where) === 0) {
258
+ throw new InvalidArgumentException('update requires a non-empty where object.');
259
+ }
260
+ if (!is_array($data) || count($data) === 0) {
261
+ throw new InvalidArgumentException('update requires a non-empty data object.');
262
+ }
263
+
264
+ $sets = [];
265
+ $params = [];
266
+ $i = 0;
267
+ foreach ($data as $column => $value) {
268
+ if (!is_string($column)) {
269
+ throw new InvalidArgumentException('Invalid update column.');
270
+ }
271
+ $safeColumn = sanitize_identifier($column);
272
+ $param = ':u' . $i++;
273
+ $sets[] = "`{$safeColumn}` = {$param}";
274
+ $params[$param] = $value;
275
+ }
276
+
277
+ $whereSql = build_where_clause($where, $params);
278
+ $sql = "UPDATE `{$safeTable}` SET " . implode(', ', $sets) . $whereSql;
279
+ $stmt = $pdo->prepare($sql);
280
+ $stmt->execute($params);
281
+ respond(200, ['ok' => true, 'data' => ['affectedRows' => $stmt->rowCount()]]);
282
+ }
283
+
284
+ case 'delete': {
285
+ $where = $payload['where'] ?? null;
286
+ if (!is_array($where) || count($where) === 0) {
287
+ throw new InvalidArgumentException('delete requires a non-empty where object.');
288
+ }
289
+
290
+ $params = [];
291
+ $whereSql = build_where_clause($where, $params);
292
+ $sql = "DELETE FROM `{$safeTable}`{$whereSql}";
293
+ $stmt = $pdo->prepare($sql);
294
+ $stmt->execute($params);
295
+ respond(200, ['ok' => true, 'data' => ['affectedRows' => $stmt->rowCount()]]);
296
+ }
297
+
298
+ case 'exists': {
299
+ $where = $payload['where'] ?? null;
300
+ if (!is_array($where) || count($where) === 0) {
301
+ throw new InvalidArgumentException('exists requires a non-empty where object.');
302
+ }
303
+
304
+ $params = [];
305
+ $whereSql = build_where_clause($where, $params);
306
+ $sql = "SELECT 1 FROM `{$safeTable}`{$whereSql} LIMIT 1";
307
+ $stmt = $pdo->prepare($sql);
308
+ $stmt->execute($params);
309
+ $exists = (bool)$stmt->fetchColumn();
310
+ respond(200, ['ok' => true, 'data' => ['exists' => $exists]]);
311
+ }
312
+
313
+ case 'login': {
314
+ $credentials = $payload['credentials'] ?? null;
315
+ $loginConfig = $payload['login'] ?? null;
316
+ if (!is_array($credentials) || !is_array($loginConfig)) {
317
+ throw new InvalidArgumentException('login requires credentials and login config.');
318
+ }
319
+
320
+ $userField = sanitize_identifier((string)($loginConfig['userField'] ?? 'email'));
321
+ $passField = sanitize_identifier((string)($loginConfig['passField'] ?? 'password_hash'));
322
+ $userValue = (string)($credentials[$userField] ?? '');
323
+ $password = (string)($credentials['password'] ?? '');
324
+
325
+ if ($userValue === '' || $password === '') {
326
+ respond(400, ['ok' => false, 'error' => 'Missing login credentials.']);
327
+ }
328
+
329
+ $select = isset($payload['select']) ? sanitize_identifier_list($payload['select']) : ['id', $userField];
330
+ if (!in_array($passField, $select, true)) {
331
+ $select[] = $passField;
332
+ }
333
+ $selectSql = implode(', ', array_map(fn($c) => "`{$c}`", $select));
334
+
335
+ $sql = "SELECT {$selectSql} FROM `{$safeTable}` WHERE `{$userField}` = :u LIMIT 1";
336
+ $stmt = $pdo->prepare($sql);
337
+ $stmt->execute([':u' => $userValue]);
338
+ $row = $stmt->fetch();
339
+
340
+ if (!$row || !isset($row[$passField]) || !password_verify($password, (string)$row[$passField])) {
341
+ respond(401, ['ok' => false, 'error' => 'Invalid credentials.']);
342
+ }
343
+
344
+ unset($row[$passField]);
345
+ respond(200, ['ok' => true, 'data' => $row]);
346
+ }
347
+
348
+ default:
349
+ respond(400, ['ok' => false, 'error' => 'Unsupported operation.']);
350
+ }
351
+ } catch (Throwable $e) {
352
+ $message = $CONFIG['debug'] ? $e->getMessage() : 'Request failed.';
353
+ respond(500, ['ok' => false, 'error' => $message]);
354
+ }
@@ -0,0 +1,68 @@
1
+ <?php
2
+ /**
3
+ * @author Myxo victor
4
+ * Chex v6.0.0 Notification Handler
5
+ * Receives device subscription data from Chex and stores it for server-side push notifications.
6
+ */
7
+
8
+ header("Access-Control-Allow-Origin: *");
9
+ header("Access-Control-Allow-Methods: POST, OPTIONS");
10
+ header("Access-Control-Allow-Headers: Content-Type");
11
+ header("Content-Type: application/json");
12
+
13
+ if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
14
+ http_response_code(200);
15
+ exit;
16
+ }
17
+
18
+ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
19
+ $input = file_get_contents('php://input');
20
+ $data = json_decode($input, true);
21
+
22
+ if (isset($data['deviceId']) && !empty($data['deviceId'])) {
23
+ $deviceId = htmlspecialchars($data['deviceId']);
24
+ $type = isset($data['type']) ? htmlspecialchars($data['type']) : 'unknown';
25
+ $timestamp = date("Y-m-d H:i:s");
26
+ $ipAddress = $_SERVER['REMOTE_ADDR'];
27
+ $userAgent = $_SERVER['HTTP_USER_AGENT'];
28
+
29
+ $logEntry = "------------------------------------------------" . PHP_EOL;
30
+ $logEntry .= "Date: $timestamp" . PHP_EOL;
31
+ $logEntry .= "Device ID: $deviceId" . PHP_EOL;
32
+ $logEntry .= "Type: $type" . PHP_EOL;
33
+ $logEntry .= "IP: $ipAddress" . PHP_EOL;
34
+ $logEntry .= "User Agent: $userAgent" . PHP_EOL;
35
+
36
+ $filePath = 'notification_handler.txt';
37
+
38
+ if (file_put_contents($filePath, $logEntry, FILE_APPEND | LOCK_EX)) {
39
+ echo json_encode([
40
+ "status" => "success",
41
+ "code" => 200,
42
+ "message" => "Device successfully registered to the Chex 6.0 ecosystem."
43
+ ]);
44
+ } else {
45
+ http_response_code(500);
46
+ echo json_encode([
47
+ "status" => "error",
48
+ "code" => 500,
49
+ "message" => "Failed to write to notification_handler.txt. Check folder permissions."
50
+ ]);
51
+ }
52
+ } else {
53
+ http_response_code(400);
54
+ echo json_encode([
55
+ "status" => "error",
56
+ "code" => 400,
57
+ "message" => "Invalid payload. 'deviceId' is required."
58
+ ]);
59
+ }
60
+ } else {
61
+ http_response_code(405);
62
+ echo json_encode([
63
+ "status" => "error",
64
+ "code" => 405,
65
+ "message" => "Method Not Allowed. Use POST via Chex 6.0 notification APIs."
66
+ ]);
67
+ }
68
+ ?>
package/ChexJs/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Aximon
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,121 @@
1
+ # Chex 6.0.0
2
+
3
+ Chex is a lightweight JavaScript UI engine for fast, reactive, component-driven interfaces. It runs directly in the browser with no compiler or build step.
4
+
5
+ ## Quick start
6
+
7
+ Include the engine, then write components with the tag helpers you need:
8
+
9
+ ```html
10
+ <script src="./Chex.js"></script>
11
+ <script src="./components/main.js"></script>
12
+ ```
13
+
14
+ ```js
15
+ const { div, h1, button } = Chex;
16
+ const app = document.getElementById('app');
17
+ const count = Chex.signal(0);
18
+
19
+ const Counter = () => div({ class: 'counter' }, [
20
+ h1(`Count: ${count.value}`),
21
+ button({ onClick: () => (count.value += 1) }, 'Increment')
22
+ ]);
23
+
24
+ Chex.render(app, Counter);
25
+ ```
26
+
27
+ ## Fast element API
28
+
29
+ Chex tag helpers accept props when you have them, and accept children directly when you do not:
30
+
31
+ ```js
32
+ const { div, h1, button } = Chex;
33
+
34
+ // With props
35
+ div({ class: 'hero' }, [
36
+ h1({}, 'Title'),
37
+ button({ onClick: save }, 'Save')
38
+ ]);
39
+
40
+ // No empty props object required
41
+ div([
42
+ h1('Title'),
43
+ button('Save')
44
+ ]);
45
+ ```
46
+
47
+ You can also use `Chex.div(...)`, `Chex.h1(...)`, and any standard HTML tag directly.
48
+
49
+ ## Core features
50
+
51
+ - Fast VNode rendering and DOM patching with `Chex.render(...)`
52
+ - Concise tag helpers and custom-tag support
53
+ - Signals and effects: `Chex.signal(...)`, `Chex.effect(...)`
54
+ - Hash or history routing with `Chex.createRouter(...)`
55
+ - Fetch and cached query helpers through `Chex.api`
56
+ - Secure PHP database client through `Chex.db`
57
+ - Notifications, service-worker registration, and animation helpers
58
+ - Reusable UI components: Button, Input, AppBar, SideBar, BottomNav, and Card
59
+
60
+ ## Backend database engine
61
+
62
+ `Chex.php` is the optional secure PHP/PDO endpoint used by `Chex.db.connect(...)`.
63
+
64
+ ```js
65
+ const server = Chex.db.connect({
66
+ endpoint: '/Chex.php',
67
+ apiKey: 'YOUR_SECURE_API_KEY',
68
+ table: 'users'
69
+ });
70
+
71
+ await server.create({ email: 'demo@example.com' });
72
+ const users = await server.read({ select: ['id', 'email'], limit: 10 });
73
+ ```
74
+
75
+ Open `Chex.php` to set database credentials, allowed origins, allowed tables, and the API key. The client sends the key in the `X-Chex-Key` header.
76
+
77
+ `Chex_notify.php` receives browser notification registrations. Pair it with `Chex.notification.ask('/Chex_notify.php')` when notifications are enabled.
78
+
79
+ ## Included libraries
80
+
81
+ All libraries are standalone and dependency-free. Load them after `Chex.js` when needed.
82
+
83
+ | File | Global | Purpose |
84
+ | --- | --- | --- |
85
+ | `scrollEcho.js` | `ScrollEcho` | Scroll-triggered text and element reveals. |
86
+ | `racket.js` | `racket` | Responsive multi-panel image carousel. |
87
+ | `orbit.js` | `orbit` | Swipe-friendly banner and slider helper. |
88
+ | `rinx.js` | `rinx` | Horizontal and vertical scroll-card layouts. |
89
+ | `smooth.js` | `smooth` | Continuous ticker and infinite carousel. |
90
+ | `modal.js` | `modal` | Self-styling, zero-dependency accessible modal engine. |
91
+ | `skeleton.js` | `skeleton` | DOM-to-skeleton shimmer loader that reduces layout shift. |
92
+
93
+ Example:
94
+
95
+ ```html
96
+ <script src="./Chex.js"></script>
97
+ <script src="./libs/modal.js"></script>
98
+ <script src="./libs/skeleton.js"></script>
99
+ ```
100
+
101
+ ## Project structure
102
+
103
+ ```text
104
+ ChexJs/
105
+ Chex.js Core engine
106
+ Chex.php Optional PHP/PDO backend
107
+ Chex_notify.php Notification registration endpoint
108
+ components/ Page components and views
109
+ libs/ Standalone visual and UI helpers
110
+ index.html Welcome page launcher
111
+ index.css Welcome page styles
112
+ sw.js Service worker
113
+ ```
114
+
115
+ ## Documentation
116
+
117
+ Open `docs/index.html` through a local web server for the full API, component, backend, library, and example guides.
118
+
119
+ ## License
120
+
121
+ MIT License. Copyright (c) 2026 Aximon. Created by Myxo Victor.
@@ -0,0 +1,4 @@
1
+ Chex 6.0 API folder
2
+
3
+ Put backend API entry files for your application in this folder.
4
+ Examples: PHP, Java, Python, Ruby, Node.js, Rust, Go.
@@ -0,0 +1,4 @@
1
+ Chex 6.0 API folder
2
+
3
+ Put backend API entry files for your application in this folder.
4
+ Examples: PHP, Java, Python, Ruby, Node.js, Rust, Go.
@@ -0,0 +1,4 @@
1
+ /*
2
+ * Chex 6.0 app/ starter component entry
3
+ * Build your page components here and render to #app.
4
+ */
@@ -0,0 +1,12 @@
1
+ /* Chex 6.0 app/ starter stylesheet */
2
+
3
+ * {
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ body {
8
+ margin: 0;
9
+ font-family: Arial, sans-serif;
10
+ background: #f8fafc;
11
+ color: #0f172a;
12
+ }
@@ -0,0 +1,28 @@
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.0">
6
+ <title>Chex App</title>
7
+
8
+ <link rel="stylesheet" href="index.css">
9
+ <script src="../Chex.js"></script>
10
+
11
+ <link rel="preconnect" href="https://fonts.googleapis.com">
12
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
13
+ <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,100..700;1,100..700&display=swap" rel="stylesheet">
14
+ <link rel="icon" href="../icon/logo.png" type="image/png">
15
+ <meta name="theme-color" content="#0f766e">
16
+
17
+ <style>
18
+ h1, h2, h3, h4, h5, h6, p, span, li, a, button {
19
+ font-family: "IBM Plex Sans", sans-serif;
20
+ }
21
+ </style>
22
+ </head>
23
+ <body>
24
+ <div id="app"></div>
25
+ <script src="./components/main.js"></script>
26
+ <script src="./logic/app.js"></script>
27
+ </body>
28
+ </html>
@@ -0,0 +1,4 @@
1
+ /*
2
+ * Chex 6.0 app/ starter logic file
3
+ * Add API calls and interaction logic that your components use.
4
+ */
@@ -0,0 +1,45 @@
1
+ /* VenJS 5.0 — Demo showcase components.
2
+ * Loaded before components/main.js. Exposes a global DemoPage()
3
+ * that the router can render at the "/demo" route. */
4
+
5
+ const demoCount = venjs.signal(0);
6
+ const demoName = venjs.signal("Ada");
7
+
8
+ const DemoCounter = () => venjs.div({ class: "page" }, [
9
+ venjs.h2({ class: "page-title" }, "Signals in action"),
10
+ venjs.p({ class: "page-copy" }, "Count: " + demoCount.value),
11
+ venjs.div({ class: "nav" }, [
12
+ venjs.button({ class: "nav-btn", onclick: () => (demoCount.value += 1) }, "+1"),
13
+ venjs.button({ class: "nav-btn", onclick: () => (demoCount.value -= 1) }, "-1")
14
+ ])
15
+ ]);
16
+
17
+ const DemoBinding = () => venjs.div({ class: "page" }, [
18
+ venjs.h2({ class: "page-title" }, "Two-way input"),
19
+ venjs.input({
20
+ label: "Your name",
21
+ value: demoName.value,
22
+ oninput: (e) => (demoName.value = e.target.value)
23
+ }),
24
+ venjs.p({ class: "page-copy" }, "Hello, " + demoName.value + "!")
25
+ ]);
26
+
27
+ const DemoCards = () => venjs.div({ class: "page" }, [
28
+ venjs.h2({ class: "page-title" }, "Component kit"),
29
+ venjs.card({}, [
30
+ venjs.h3({ style: { marginTop: "0" } }, "Built with VenJS"),
31
+ venjs.p({ class: "page-copy" }, "Button, Input and Card components composed together."),
32
+ venjs.div({ class: "nav" }, [
33
+ venjs.button({ class: "nav-btn active", onclick: () => {} }, "Primary"),
34
+ venjs.button({ variant: "outline", class: "nav-btn", onclick: () => {} }, "Outline")
35
+ ])
36
+ ])
37
+ ]);
38
+
39
+ window.DemoPage = () => venjs.div({ class: "app" }, [
40
+ venjs.h1({ class: "hero-title" }, "VenJS Live Demo"),
41
+ venjs.p({ class: "hero-copy" }, "A few examples built with signals and components."),
42
+ DemoCounter(),
43
+ DemoBinding(),
44
+ DemoCards()
45
+ ]);