@cosmicdrift/kumiko-headless 0.123.3 → 0.124.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.
- package/package.json +2 -2
- package/src/apex/__tests__/render.test.ts +19 -0
- package/src/apex/css.ts +11 -1
- package/src/apex/index.ts +5 -0
- package/src/apex/lightbox.ts +41 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cosmicdrift/kumiko-headless",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.124.0",
|
|
4
4
|
"description": "Headless UI logic for Kumiko — Dispatcher contract, Form-Controller, View-Model, Nav-Resolver. Plattform- und React-frei; jeder Renderer (renderer, renderer-web, renderer-native, …) komponiert darauf.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
}
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@cosmicdrift/kumiko-framework": "0.
|
|
35
|
+
"@cosmicdrift/kumiko-framework": "0.124.0",
|
|
36
36
|
"zod": "^4.4.3"
|
|
37
37
|
},
|
|
38
38
|
"publishConfig": {
|
|
@@ -251,6 +251,25 @@ describe("renderApexPage", () => {
|
|
|
251
251
|
expect(html).toContain('<link rel="preconnect" href="https://api.example.com" />');
|
|
252
252
|
});
|
|
253
253
|
|
|
254
|
+
test("hero screenshot includes apex lightbox chrome", () => {
|
|
255
|
+
const html = renderApexPage(
|
|
256
|
+
page({
|
|
257
|
+
sections: [
|
|
258
|
+
{
|
|
259
|
+
kind: "hero",
|
|
260
|
+
title: "h",
|
|
261
|
+
tagline: "t",
|
|
262
|
+
screenshot: { src: "/shots/demo.png", alt: "Dashboard" },
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
}),
|
|
266
|
+
);
|
|
267
|
+
expect(html).toContain('class="shot-frame"');
|
|
268
|
+
expect(html).toContain('<dialog id="apex-lightbox"');
|
|
269
|
+
expect(html).toContain("apex-lightbox");
|
|
270
|
+
expect(html).toContain(".shot-frame img");
|
|
271
|
+
});
|
|
272
|
+
|
|
254
273
|
test("renders schemaJson as json-ld script tag", () => {
|
|
255
274
|
const html = renderApexPage(
|
|
256
275
|
page({
|
package/src/apex/css.ts
CHANGED
|
@@ -59,10 +59,20 @@ const HERO = `
|
|
|
59
59
|
.hero-meta { margin-top: 1.5rem; font-size: 0.875rem; color: var(--fg-subtle); }
|
|
60
60
|
.hero-meta strong { color: var(--fg-muted); font-weight: 600; }
|
|
61
61
|
.shot-frame { border-radius: 0.75rem; border: 1px solid var(--border); background: var(--bg-card);
|
|
62
|
-
box-shadow: var(--shadow); overflow: hidden; }
|
|
62
|
+
box-shadow: var(--shadow); overflow: hidden; cursor: zoom-in; }
|
|
63
63
|
.shot-bar { display: flex; gap: 0.4rem; padding: 0.6rem 0.85rem; border-bottom: 1px solid var(--border); background: var(--bg-muted); }
|
|
64
64
|
.shot-bar span { width: 0.65rem; height: 0.65rem; border-radius: 50%; background: var(--border); }
|
|
65
65
|
.shot-frame img { display: block; width: 100%; height: auto; }
|
|
66
|
+
.apex-lightbox { border: none; padding: 0; margin: auto; max-width: 95vw; max-height: 90vh;
|
|
67
|
+
background: transparent; overflow: visible; }
|
|
68
|
+
.apex-lightbox::backdrop { background: rgba(15, 23, 42, 0.72); }
|
|
69
|
+
.apex-lightbox__img { display: block; max-width: 90vw; max-height: 85vh; width: auto; height: auto;
|
|
70
|
+
border-radius: 0.75rem; border: 1px solid var(--border); box-shadow: var(--shadow); }
|
|
71
|
+
.apex-lightbox__close { position: fixed; top: 1rem; right: 1rem; z-index: 1; width: 2.5rem; height: 2.5rem;
|
|
72
|
+
border: 1px solid var(--border); border-radius: 0.5rem; background: var(--bg-card); color: var(--fg);
|
|
73
|
+
font-size: 1.5rem; line-height: 1; cursor: pointer; }
|
|
74
|
+
.apex-lightbox__close:hover { background: var(--bg-muted); }
|
|
75
|
+
.apex-dark .apex-lightbox__close { background: var(--on-dark); color: var(--primary); border-color: var(--on-dark-border); }
|
|
66
76
|
`;
|
|
67
77
|
|
|
68
78
|
const FEATURES = `
|
package/src/apex/index.ts
CHANGED
|
@@ -6,8 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
import { escapeHtml } from "../format";
|
|
8
8
|
import { APEX_STRUCTURAL_CSS } from "./css";
|
|
9
|
+
import { APEX_LIGHTBOX_HTML, APEX_LIGHTBOX_SCRIPT } from "./lightbox";
|
|
9
10
|
|
|
10
11
|
export { APEX_NAV_MENU_CSS, APEX_STRUCTURAL_CSS } from "./css";
|
|
12
|
+
export { APEX_LIGHTBOX_HTML, APEX_LIGHTBOX_SCRIPT } from "./lightbox";
|
|
11
13
|
|
|
12
14
|
export type ApexTheme = "light" | "dark";
|
|
13
15
|
|
|
@@ -522,6 +524,9 @@ export function renderApexPage(page: ApexPage): string {
|
|
|
522
524
|
${sections}
|
|
523
525
|
|
|
524
526
|
${renderFooter(page.footer)}
|
|
527
|
+
|
|
528
|
+
${APEX_LIGHTBOX_HTML}
|
|
529
|
+
${APEX_LIGHTBOX_SCRIPT}
|
|
525
530
|
</body>
|
|
526
531
|
</html>`;
|
|
527
532
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// Vanilla lightbox for Apex marketing pages — click .shot-frame img to enlarge.
|
|
2
|
+
// Injected by renderApexPage; no React, no per-app wiring.
|
|
3
|
+
|
|
4
|
+
export const APEX_LIGHTBOX_HTML = `<dialog id="apex-lightbox" class="apex-lightbox" aria-label="Screenshot preview">
|
|
5
|
+
<button type="button" class="apex-lightbox__close" aria-label="Close">×</button>
|
|
6
|
+
<img class="apex-lightbox__img" alt="" />
|
|
7
|
+
</dialog>`;
|
|
8
|
+
|
|
9
|
+
/** ponytail: one delegated listener; no-op when no .shot-frame on the page. */
|
|
10
|
+
export const APEX_LIGHTBOX_SCRIPT = `<script>
|
|
11
|
+
(function () {
|
|
12
|
+
var dlg = document.getElementById("apex-lightbox");
|
|
13
|
+
if (!dlg) return;
|
|
14
|
+
var img = dlg.querySelector(".apex-lightbox__img");
|
|
15
|
+
var closeBtn = dlg.querySelector(".apex-lightbox__close");
|
|
16
|
+
if (!img || !closeBtn) return;
|
|
17
|
+
function open(src, alt) {
|
|
18
|
+
img.src = src;
|
|
19
|
+
img.alt = alt || "";
|
|
20
|
+
if (typeof dlg.showModal === "function") dlg.showModal();
|
|
21
|
+
}
|
|
22
|
+
function close() {
|
|
23
|
+
if (dlg.open) dlg.close();
|
|
24
|
+
}
|
|
25
|
+
document.addEventListener("click", function (e) {
|
|
26
|
+
var t = e.target;
|
|
27
|
+
if (!(t instanceof HTMLImageElement)) return;
|
|
28
|
+
if (!t.closest(".shot-frame")) return;
|
|
29
|
+
e.preventDefault();
|
|
30
|
+
open(t.currentSrc || t.src, t.alt);
|
|
31
|
+
});
|
|
32
|
+
closeBtn.addEventListener("click", close);
|
|
33
|
+
dlg.addEventListener("click", function (e) {
|
|
34
|
+
if (e.target === dlg) close();
|
|
35
|
+
});
|
|
36
|
+
dlg.addEventListener("cancel", function (e) {
|
|
37
|
+
e.preventDefault();
|
|
38
|
+
close();
|
|
39
|
+
});
|
|
40
|
+
})();
|
|
41
|
+
</script>`;
|