frogoe 0.1.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.
Files changed (32) hide show
  1. package/bin/frogoe.mjs +11 -0
  2. package/dist/cli.js +3124 -0
  3. package/dist/contract/contract.js +236 -0
  4. package/dist/registry/blocks/coin-counter/coin-counter.html +101 -0
  5. package/dist/registry/blocks/coin-counter/registry-item.json +27 -0
  6. package/dist/registry/blocks/combo-counter/combo-counter.html +106 -0
  7. package/dist/registry/blocks/combo-counter/registry-item.json +27 -0
  8. package/dist/registry/blocks/fuel-gauge/fuel-gauge.html +130 -0
  9. package/dist/registry/blocks/fuel-gauge/registry-item.json +29 -0
  10. package/dist/registry/blocks/game-over-card/game-over-card.html +185 -0
  11. package/dist/registry/blocks/game-over-card/registry-item.json +30 -0
  12. package/dist/registry/blocks/hearts-row/hearts-row.html +92 -0
  13. package/dist/registry/blocks/hearts-row/registry-item.json +27 -0
  14. package/dist/registry/blocks/mini-board/mini-board.html +125 -0
  15. package/dist/registry/blocks/mini-board/registry-item.json +28 -0
  16. package/dist/registry/blocks/objective-chip/objective-chip.html +101 -0
  17. package/dist/registry/blocks/objective-chip/registry-item.json +29 -0
  18. package/dist/registry/blocks/ready-gate/ready-gate.html +124 -0
  19. package/dist/registry/blocks/ready-gate/registry-item.json +28 -0
  20. package/dist/registry/blocks/ready-hint/ready-hint.html +104 -0
  21. package/dist/registry/blocks/ready-hint/registry-item.json +29 -0
  22. package/dist/registry/blocks/score-card/registry-item.json +27 -0
  23. package/dist/registry/blocks/score-card/score-card.html +92 -0
  24. package/dist/registry/blocks/timer-ring/registry-item.json +28 -0
  25. package/dist/registry/blocks/timer-ring/timer-ring.html +123 -0
  26. package/dist/registry/registry.json +51 -0
  27. package/dist/registry/schema/registry-item.schema.json +36 -0
  28. package/dist/registry/schema/registry.schema.json +22 -0
  29. package/dist/runtimeVersion.js +10 -0
  30. package/dist/templates/_shared/AGENTS.md +105 -0
  31. package/dist/templates/_shared/CLAUDE.md +105 -0
  32. package/package.json +42 -0
@@ -0,0 +1,124 @@
1
+ <!doctype html>
2
+ <!-- frogoe block source: ready-gate — this file IS viewable in a browser (default
3
+ state on a neutral backdrop). Copy ONLY between the markers into your
4
+ game's blocks/ layer; the interactive playground is demo.html. -->
5
+ <html lang="en">
6
+ <head>
7
+ <meta charset="utf-8" />
8
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
9
+ <title>frogoe block — ready-gate</title>
10
+ <style>
11
+ body {
12
+ margin: 0;
13
+ min-height: 100vh;
14
+ display: grid;
15
+ place-items: center;
16
+ background: radial-gradient(120% 120% at 50% 0%, #182031 0%, #0d1220 60%, #080b13 100%);
17
+ }
18
+ </style>
19
+ </head>
20
+ <body>
21
+ <!-- ===================== COPY FROM HERE ===================== -->
22
+ <!--
23
+ frogoe block: ready-gate
24
+ The tap-to-start overlay: a full-screen dim + title + chunky CTA that
25
+ gates the run behind ONE discrete tap. That tap is the audio unlock
26
+ (iOS honors a discrete tap best) and the run start — the first swipe
27
+ of the game already sounds and counts.
28
+
29
+ Bind from game.js (vanilla, no helper):
30
+ const gate = document.querySelector("[data-block-gate]");
31
+ let started = false;
32
+ input.on("down", () => {
33
+ if (started) return;
34
+ started = true; // gate physics/spawning on this
35
+ gate.setAttribute("data-hidden", ""); // fades out; remove after 320ms
36
+ gate.querySelector("[data-block-cta]").textContent = "TAP"; // your verb
37
+
38
+ Theme: --block-accent, --block-fg, --block-font, --block-outline,
39
+ --block-cta, --block-cta-label, --block-cta-shadow, --block-gate-bg.
40
+ -->
41
+ <style>
42
+ .block-ready-gate {
43
+ --block-accent: #ffd166;
44
+ --block-cta: #ffd166;
45
+ --block-cta-label: #26180a;
46
+ --block-cta-shadow: #a8842c;
47
+ --block-fg: #fffdf7;
48
+ --block-font: "Fredoka", "Baloo 2", system-ui, sans-serif;
49
+ --block-gate-bg: rgba(10, 12, 16, 0.66);
50
+ --block-outline: #26180a;
51
+
52
+ position: absolute;
53
+ inset: 0;
54
+ display: grid;
55
+ place-items: center;
56
+ background: var(--block-gate-bg);
57
+ backdrop-filter: blur(8px);
58
+ color: var(--block-fg);
59
+ font-family: var(--block-font);
60
+ cursor: pointer;
61
+ transition: opacity 260ms ease;
62
+ -webkit-user-select: none;
63
+ user-select: none;
64
+ }
65
+ .block-ready-gate[data-hidden] {
66
+ opacity: 0;
67
+ pointer-events: none;
68
+ }
69
+ .block-ready-gate__inner {
70
+ display: grid;
71
+ justify-items: center;
72
+ padding: 1rem 2rem;
73
+ text-align: center;
74
+ }
75
+ .block-ready-gate__title {
76
+ font-size: clamp(1.8rem, 8vmin, 2.8rem);
77
+ font-weight: 700;
78
+ letter-spacing: 0.03em;
79
+ text-shadow: 0 3px 0 var(--block-outline);
80
+ }
81
+ .block-ready-gate__cta {
82
+ margin-top: 0.9em;
83
+ font-family: inherit;
84
+ font-size: clamp(0.95rem, 3.4vmin, 1.2rem);
85
+ font-weight: 700;
86
+ letter-spacing: 0.3em;
87
+ text-indent: 0.3em;
88
+ text-transform: uppercase;
89
+ color: var(--block-cta-label);
90
+ background: var(--block-cta);
91
+ text-shadow: 0 2px 0 rgba(0, 0, 0, 0.25);
92
+ border: none;
93
+ border-radius: 999px;
94
+ padding: 0.8em 2.4em;
95
+ box-shadow: 0 5px 0 var(--block-cta-shadow);
96
+ animation: block-gate-breathe 1.4s ease-in-out infinite alternate;
97
+ }
98
+ @keyframes block-gate-breathe {
99
+ from {
100
+ transform: translateY(0);
101
+ }
102
+ to {
103
+ transform: translateY(-6px);
104
+ }
105
+ }
106
+ @media (prefers-reduced-motion: reduce) {
107
+ .block-ready-gate__cta {
108
+ animation: none;
109
+ }
110
+ .block-ready-gate {
111
+ transition: none;
112
+ }
113
+ }
114
+ </style>
115
+
116
+ <div class="block-ready-gate" data-block-gate>
117
+ <div class="block-ready-gate__inner">
118
+ <div class="block-ready-gate__title">Game Title</div>
119
+ <button class="block-ready-gate__cta" type="button" data-block-cta>Tap to Play</button>
120
+ </div>
121
+ </div>
122
+ <!-- ===================== COPY TO HERE ======================= -->
123
+ </body>
124
+ </html>
@@ -0,0 +1,28 @@
1
+ {
2
+ "$schema": "../../schema/registry-item.schema.json",
3
+ "name": "ready-gate",
4
+ "type": "frogoe:block",
5
+ "title": "Ready Gate",
6
+ "description": "Tap-to-start overlay: one discrete tap unlocks audio (the gesture iOS honors best) and starts the run — the first swipe already sounds; themeable via --block-*",
7
+ "tags": [
8
+ "hud",
9
+ "ready",
10
+ "start",
11
+ "audio-unlock"
12
+ ],
13
+ "files": [
14
+ {
15
+ "path": "ready-gate.html",
16
+ "target": "blocks/ready-gate.html",
17
+ "type": "frogoe:markup"
18
+ }
19
+ ],
20
+ "bindings": [
21
+ "data-block-gate",
22
+ "data-block-cta"
23
+ ],
24
+ "placement": "overlay-fullscreen",
25
+ "preview": {
26
+ "demo": "demo.html"
27
+ }
28
+ }
@@ -0,0 +1,104 @@
1
+ <!doctype html>
2
+ <!-- frogoe block source: ready-hint — this file IS viewable in a browser (default
3
+ state on a neutral backdrop). Copy ONLY between the markers into your
4
+ game's blocks/ layer; the interactive playground is demo.html. -->
5
+ <html lang="en">
6
+ <head>
7
+ <meta charset="utf-8" />
8
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
9
+ <title>frogoe block — ready-hint</title>
10
+ <style>
11
+ body {
12
+ margin: 0;
13
+ min-height: 100vh;
14
+ display: grid;
15
+ place-items: center;
16
+ background: radial-gradient(120% 120% at 50% 0%, #182031 0%, #0d1220 60%, #080b13 100%);
17
+ }
18
+ </style>
19
+ </head>
20
+ <body>
21
+ <!-- ===================== COPY FROM HERE ===================== -->
22
+ <!--
23
+ frogoe block: ready-hint
24
+ The living-ready-state affordance: a giant verb instruction that breathes
25
+ while it waits, and vanishes the moment play starts. This is what replaces
26
+ a start menu — the world is alive, this tells the player their one move.
27
+
28
+ Bind from game.js (vanilla, no helper):
29
+ const hint = document.querySelector("[data-block-ready]");
30
+ hint.toggleAttribute("data-hidden", phase !== "ready");
31
+ hint.querySelector("[data-block-verb]").textContent = "TAP"; // or HOLD / DRAG
32
+
33
+ Theme: --block-accent, --block-fg, --block-outline, --block-font.
34
+ -->
35
+ <style>
36
+ .block-ready {
37
+ --block-accent: #ffd166;
38
+ --block-fg: #fffdf7;
39
+ --block-font: "Fredoka", "Baloo 2", system-ui, sans-serif;
40
+ --block-outline: #26180a;
41
+
42
+ display: grid;
43
+ justify-items: center;
44
+ gap: 0.5em;
45
+ color: var(--block-fg);
46
+ font-family: var(--block-font);
47
+ text-align: center;
48
+ pointer-events: none;
49
+ -webkit-user-select: none;
50
+ user-select: none;
51
+ transition:
52
+ opacity 200ms ease,
53
+ scale 200ms ease;
54
+ }
55
+ .block-ready[data-hidden] {
56
+ opacity: 0;
57
+ scale: 0.9;
58
+ }
59
+ .block-ready__verb {
60
+ font-size: clamp(2.2rem, 11vmin, 4rem);
61
+ font-weight: 700;
62
+ letter-spacing: 0.04em;
63
+ line-height: 1.1; /* descender-safe for italics */
64
+ text-transform: uppercase;
65
+ text-shadow:
66
+ 0 4px 0 var(--block-outline),
67
+ 0 8px 16px rgba(0, 0, 0, 0.35);
68
+ }
69
+ .block-ready__sub {
70
+ font-size: 0.95rem;
71
+ font-weight: 600;
72
+ letter-spacing: 0.3em;
73
+ text-transform: uppercase;
74
+ opacity: 0.75;
75
+ }
76
+ /* the breath: motivated ambient motion — it signals "alive, waiting" */
77
+ .block-ready:not([data-hidden]) .block-ready__verb {
78
+ animation: block-ready-breathe 1.8s ease-in-out infinite alternate;
79
+ }
80
+ @keyframes block-ready-breathe {
81
+ from {
82
+ transform: scale(1);
83
+ }
84
+ to {
85
+ transform: scale(1.05);
86
+ }
87
+ }
88
+ @media (prefers-reduced-motion: reduce) {
89
+ .block-ready:not([data-hidden]) .block-ready__verb {
90
+ animation: none;
91
+ }
92
+ .block-ready {
93
+ transition: none;
94
+ }
95
+ }
96
+ </style>
97
+
98
+ <div class="block-ready" data-block-ready>
99
+ <span class="block-ready__verb" data-block-verb>TAP</span>
100
+ <span class="block-ready__sub" data-block-sub>to start</span>
101
+ </div>
102
+ <!-- ===================== COPY TO HERE ======================= -->
103
+ </body>
104
+ </html>
@@ -0,0 +1,29 @@
1
+ {
2
+ "$schema": "../../schema/registry-item.schema.json",
3
+ "name": "ready-hint",
4
+ "type": "frogoe:block",
5
+ "title": "Ready Hint",
6
+ "description": "The living-ready-state verb affordance: breathing giant instruction that hides the instant play starts; replaces start menus",
7
+ "tags": [
8
+ "hud",
9
+ "ready",
10
+ "affordance",
11
+ "onboarding"
12
+ ],
13
+ "files": [
14
+ {
15
+ "path": "ready-hint.html",
16
+ "target": "blocks/ready-hint.html",
17
+ "type": "frogoe:markup"
18
+ }
19
+ ],
20
+ "bindings": [
21
+ "data-block-ready",
22
+ "data-block-verb",
23
+ "data-block-sub"
24
+ ],
25
+ "placement": "overlay",
26
+ "preview": {
27
+ "demo": "demo.html"
28
+ }
29
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "$schema": "../../schema/registry-item.schema.json",
3
+ "name": "score-card",
4
+ "type": "frogoe:block",
5
+ "title": "Score Card",
6
+ "description": "Big chunky score number with sticker depth and a bump animation on change; themeable via --block-* custom properties",
7
+ "tags": [
8
+ "hud",
9
+ "score",
10
+ "number",
11
+ "typography"
12
+ ],
13
+ "files": [
14
+ {
15
+ "path": "score-card.html",
16
+ "target": "blocks/score-card.html",
17
+ "type": "frogoe:markup"
18
+ }
19
+ ],
20
+ "bindings": [
21
+ "data-block-score"
22
+ ],
23
+ "placement": "overlay",
24
+ "preview": {
25
+ "demo": "demo.html"
26
+ }
27
+ }
@@ -0,0 +1,92 @@
1
+ <!doctype html>
2
+ <!-- frogoe block source: score-card — this file IS viewable in a browser (default
3
+ state on a neutral backdrop). Copy ONLY between the markers into your
4
+ game's blocks/ layer; the interactive playground is demo.html. -->
5
+ <html lang="en">
6
+ <head>
7
+ <meta charset="utf-8" />
8
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
9
+ <title>frogoe block — score-card</title>
10
+ <style>
11
+ body {
12
+ margin: 0;
13
+ min-height: 100vh;
14
+ display: grid;
15
+ place-items: center;
16
+ background: radial-gradient(120% 120% at 50% 0%, #182031 0%, #0d1220 60%, #080b13 100%);
17
+ }
18
+ </style>
19
+ </head>
20
+ <body>
21
+ <!-- ===================== COPY FROM HERE ===================== -->
22
+ <!--
23
+ frogoe block: score-card
24
+ Big chunky score number + label. Reads on any background.
25
+
26
+ Bind from game.js (vanilla, no helper):
27
+ document.querySelector("[data-block-score]").textContent = 1240;
28
+
29
+ Theme: every visual is a custom property — retheme by overriding
30
+ --block-* on .block-score-card (or on a parent, e.g. body or .block-layer).
31
+ -->
32
+ <style>
33
+ .block-score-card {
34
+ /* theme — override these */
35
+ --block-accent: #ffd166;
36
+ --block-fg: #fffdf7;
37
+ --block-font: "Fredoka", "Baloo 2", system-ui, sans-serif;
38
+ --block-outline: #26180a;
39
+ --block-size: clamp(2.4rem, 9vmin, 3.6rem);
40
+
41
+ color: var(--block-fg);
42
+ font-family: var(--block-font);
43
+ font-size: var(--block-size);
44
+ font-weight: 700;
45
+ line-height: 1;
46
+ text-align: center;
47
+ -webkit-user-select: none;
48
+ user-select: none;
49
+ pointer-events: none;
50
+ }
51
+ .block-score-card__label {
52
+ display: block;
53
+ font-size: 0.32em;
54
+ font-weight: 600;
55
+ letter-spacing: 0.35em;
56
+ text-transform: uppercase;
57
+ opacity: 0.85;
58
+ }
59
+ .block-score-card__value {
60
+ display: block;
61
+ font-variant-numeric: tabular-nums;
62
+ /* sticker depth: hard offset shadow, no blur */
63
+ text-shadow:
64
+ 0 0.09em 0 var(--block-outline),
65
+ 0 0.16em 0.24em rgba(0, 0, 0, 0.35);
66
+ }
67
+ .block-score-card__value[data-bump] {
68
+ /* score changed this frame — game toggles the attribute to fire the pop */
69
+ animation: block-score-bump 240ms cubic-bezier(0.34, 1.8, 0.5, 1);
70
+ }
71
+ @keyframes block-score-bump {
72
+ from {
73
+ transform: scale(1.28);
74
+ }
75
+ to {
76
+ transform: scale(1);
77
+ }
78
+ }
79
+ @media (prefers-reduced-motion: reduce) {
80
+ .block-score-card__value[data-bump] {
81
+ animation: none;
82
+ }
83
+ }
84
+ </style>
85
+
86
+ <div class="block-score-card">
87
+ <span class="block-score-card__label">Score</span>
88
+ <span class="block-score-card__value" data-block-score>0</span>
89
+ </div>
90
+ <!-- ===================== COPY TO HERE ======================= -->
91
+ </body>
92
+ </html>
@@ -0,0 +1,28 @@
1
+ {
2
+ "$schema": "../../schema/registry-item.schema.json",
3
+ "name": "timer-ring",
4
+ "type": "frogoe:block",
5
+ "title": "Timer Ring",
6
+ "description": "Circular countdown dial with smooth drain and a pulsing data-low danger state; themeable via --block-*",
7
+ "tags": [
8
+ "hud",
9
+ "timer",
10
+ "countdown",
11
+ "ring"
12
+ ],
13
+ "files": [
14
+ {
15
+ "path": "timer-ring.html",
16
+ "target": "blocks/timer-ring.html",
17
+ "type": "frogoe:markup"
18
+ }
19
+ ],
20
+ "bindings": [
21
+ "data-block-timer",
22
+ "data-block-timer-label"
23
+ ],
24
+ "placement": "overlay",
25
+ "preview": {
26
+ "demo": "demo.html"
27
+ }
28
+ }
@@ -0,0 +1,123 @@
1
+ <!doctype html>
2
+ <!-- frogoe block source: timer-ring — this file IS viewable in a browser (default
3
+ state on a neutral backdrop). Copy ONLY between the markers into your
4
+ game's blocks/ layer; the interactive playground is demo.html. -->
5
+ <html lang="en">
6
+ <head>
7
+ <meta charset="utf-8" />
8
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
9
+ <title>frogoe block — timer-ring</title>
10
+ <style>
11
+ body {
12
+ margin: 0;
13
+ min-height: 100vh;
14
+ display: grid;
15
+ place-items: center;
16
+ background: radial-gradient(120% 120% at 50% 0%, #182031 0%, #0d1220 60%, #080b13 100%);
17
+ }
18
+ </style>
19
+ </head>
20
+ <body>
21
+ <!-- ===================== COPY FROM HERE ===================== -->
22
+ <!--
23
+ frogoe block: timer-ring
24
+ Circular countdown ring (fuel/time/boost as a dial instead of a bar).
25
+ Ring drains smoothly; data-low switches to danger and pulses — semantic
26
+ urgency, never guessed from hue alone.
27
+
28
+ Bind from game.js (vanilla, no helper):
29
+ const ring = document.querySelector("[data-block-timer]");
30
+ ring.style.setProperty("--block-left", String(fraction)); // 0..1 remaining
31
+ ring.toggleAttribute("data-low", fraction < 0.25);
32
+ ring.querySelector("[data-block-timer-label]").textContent = secs;
33
+
34
+ Theme: --block-accent (ring), --block-danger, --block-fg, --block-outline, --block-track.
35
+ -->
36
+ <style>
37
+ .block-timer-ring {
38
+ --block-accent: #ffd166;
39
+ --block-danger: #ff5d5d;
40
+ --block-fg: #fffdf7;
41
+ --block-font: "Fredoka", "Baloo 2", system-ui, sans-serif;
42
+ --block-left: 1;
43
+ --block-outline: #26180a;
44
+ --block-ring-color: var(--block-accent);
45
+ --block-size: clamp(3.4rem, 13vmin, 4.6rem);
46
+ --block-track: rgba(10, 14, 26, 0.5);
47
+
48
+ position: relative;
49
+ width: var(--block-size);
50
+ height: var(--block-size);
51
+ color: var(--block-fg);
52
+ font-family: var(--block-font);
53
+ pointer-events: none;
54
+ -webkit-user-select: none;
55
+ user-select: none;
56
+ }
57
+ .block-timer-ring svg {
58
+ width: 100%;
59
+ height: 100%;
60
+ transform: rotate(-90deg); /* dial starts at 12 o'clock */
61
+ }
62
+ .block-timer-ring__track {
63
+ fill: var(--block-track);
64
+ stroke: rgba(255, 255, 255, 0.14);
65
+ stroke-width: 6;
66
+ }
67
+ .block-timer-ring__arc {
68
+ fill: none;
69
+ stroke: var(--block-ring-color);
70
+ stroke-width: 6;
71
+ stroke-linecap: round;
72
+ /* dash math: circumference 100 — --block-left is the remaining fraction */
73
+ stroke-dasharray: 100;
74
+ stroke-dashoffset: calc(100 - (100 * var(--block-left)));
75
+ transition:
76
+ stroke-dashoffset 240ms ease-out,
77
+ stroke 160ms ease;
78
+ filter: drop-shadow(0 2px 0 var(--block-outline));
79
+ }
80
+ .block-timer-ring__label {
81
+ position: absolute;
82
+ inset: 0;
83
+ display: grid;
84
+ place-items: center;
85
+ font-size: calc(var(--block-size) * 0.3);
86
+ font-weight: 700;
87
+ font-variant-numeric: tabular-nums;
88
+ text-shadow: 0 2px 0 var(--block-outline);
89
+ }
90
+ .block-timer-ring[data-low] {
91
+ --block-ring-color: var(--block-danger);
92
+ }
93
+ .block-timer-ring[data-low] .block-timer-ring__label {
94
+ animation: block-ring-low 640ms ease-in-out infinite alternate;
95
+ }
96
+ @keyframes block-ring-low {
97
+ from {
98
+ opacity: 1;
99
+ }
100
+ to {
101
+ opacity: 0.55;
102
+ }
103
+ }
104
+ @media (prefers-reduced-motion: reduce) {
105
+ .block-timer-ring__arc {
106
+ transition: none;
107
+ }
108
+ .block-timer-ring[data-low] .block-timer-ring__label {
109
+ animation: none;
110
+ }
111
+ }
112
+ </style>
113
+
114
+ <div class="block-timer-ring" data-block-timer>
115
+ <svg viewBox="0 0 40 40" aria-hidden="true">
116
+ <circle class="block-timer-ring__track" cx="20" cy="20" r="16"></circle>
117
+ <circle class="block-timer-ring__arc" cx="20" cy="20" r="16"></circle>
118
+ </svg>
119
+ <span class="block-timer-ring__label" data-block-timer-label>45</span>
120
+ </div>
121
+ <!-- ===================== COPY TO HERE ======================= -->
122
+ </body>
123
+ </html>
@@ -0,0 +1,51 @@
1
+ {
2
+ "$schema": "./schema/registry.schema.json",
3
+ "name": "frogoe",
4
+ "homepage": "https://frogoe.com",
5
+ "items": [
6
+ {
7
+ "name": "score-card",
8
+ "type": "frogoe:block"
9
+ },
10
+ {
11
+ "name": "coin-counter",
12
+ "type": "frogoe:block"
13
+ },
14
+ {
15
+ "name": "combo-counter",
16
+ "type": "frogoe:block"
17
+ },
18
+ {
19
+ "name": "hearts-row",
20
+ "type": "frogoe:block"
21
+ },
22
+ {
23
+ "name": "fuel-gauge",
24
+ "type": "frogoe:block"
25
+ },
26
+ {
27
+ "name": "timer-ring",
28
+ "type": "frogoe:block"
29
+ },
30
+ {
31
+ "name": "objective-chip",
32
+ "type": "frogoe:block"
33
+ },
34
+ {
35
+ "name": "mini-board",
36
+ "type": "frogoe:block"
37
+ },
38
+ {
39
+ "name": "ready-hint",
40
+ "type": "frogoe:block"
41
+ },
42
+ {
43
+ "name": "ready-gate",
44
+ "type": "frogoe:block"
45
+ },
46
+ {
47
+ "name": "game-over-card",
48
+ "type": "frogoe:block"
49
+ }
50
+ ]
51
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://frogoe.com/schema/registry-item.schema.json",
4
+ "title": "frogoe registry item",
5
+ "type": "object",
6
+ "required": ["name", "type", "title", "description", "tags", "files", "placement"],
7
+ "properties": {
8
+ "name": { "type": "string", "pattern": "^[a-z0-9-]+$" },
9
+ "type": { "const": "frogoe:block" },
10
+ "title": { "type": "string" },
11
+ "description": { "type": "string", "maxLength": 240 },
12
+ "tags": { "type": "array", "items": { "type": "string" }, "minItems": 1 },
13
+ "files": {
14
+ "type": "array",
15
+ "minItems": 1,
16
+ "items": {
17
+ "type": "object",
18
+ "required": ["path", "target", "type"],
19
+ "properties": {
20
+ "path": { "type": "string" },
21
+ "target": { "type": "string" },
22
+ "type": { "enum": ["frogoe:markup", "frogoe:style", "frogoe:script"] }
23
+ }
24
+ }
25
+ },
26
+ "bindings": { "type": "array", "items": { "type": "string", "pattern": "^data-block-[a-z-]+$" } },
27
+ "placement": { "enum": ["overlay", "overlay-fullscreen"] },
28
+ "preview": {
29
+ "type": "object",
30
+ "properties": {
31
+ "poster": { "type": "string" },
32
+ "demo": { "type": "string" }
33
+ }
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://frogoe.com/schema/registry.schema.json",
4
+ "title": "frogoe registry index",
5
+ "type": "object",
6
+ "required": ["name", "items"],
7
+ "properties": {
8
+ "name": { "type": "string" },
9
+ "homepage": { "type": "string" },
10
+ "items": {
11
+ "type": "array",
12
+ "items": {
13
+ "type": "object",
14
+ "required": ["name", "type"],
15
+ "properties": {
16
+ "name": { "type": "string", "pattern": "^[a-z0-9-]+$" },
17
+ "type": { "const": "frogoe:block" }
18
+ }
19
+ }
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,10 @@
1
+ // src/runtimeVersion.ts
2
+ var MINIMUM_NODE_MAJOR = 22;
3
+ function runtimeVersionError(version) {
4
+ const major = Number.parseInt(version.split(".", 1)[0] ?? "", 10);
5
+ if (Number.isFinite(major) && major >= MINIMUM_NODE_MAJOR) return null;
6
+ return `frogoe requires Node.js >= ${MINIMUM_NODE_MAJOR} (current: ${version}). Switch Node versions and retry.`;
7
+ }
8
+ export {
9
+ runtimeVersionError
10
+ };