create-rakta-app 0.1.7 → 0.1.8
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/index.js +694 -779
- package/dist/index.js.map +7 -8
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -886,34 +886,16 @@ var T$1 = class T extends V {
|
|
|
886
886
|
}
|
|
887
887
|
}
|
|
888
888
|
};
|
|
889
|
-
|
|
890
|
-
class r extends V {
|
|
891
|
-
get cursor() {
|
|
892
|
-
return this.value ? 0 : 1;
|
|
893
|
-
}
|
|
894
|
-
get _value() {
|
|
895
|
-
return this.cursor === 0;
|
|
896
|
-
}
|
|
897
|
-
constructor(t2) {
|
|
898
|
-
super(t2, false), this.value = !!t2.initialValue, this.on("userInput", () => {
|
|
899
|
-
this.value = this._value;
|
|
900
|
-
}), this.on("confirm", (i) => {
|
|
901
|
-
this.output.write(import_sisteransi.cursor.move(0, -1)), this.value = i, this.state = "submit", this.close();
|
|
902
|
-
}), this.on("cursor", () => {
|
|
903
|
-
this.value = !this.value;
|
|
904
|
-
});
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
889
|
var _ = {
|
|
908
890
|
Y: { type: "year", len: 4 },
|
|
909
891
|
M: { type: "month", len: 2 },
|
|
910
892
|
D: { type: "day", len: 2 }
|
|
911
893
|
};
|
|
912
|
-
function M(
|
|
913
|
-
return [...
|
|
894
|
+
function M(r) {
|
|
895
|
+
return [...r].map((t2) => _[t2]);
|
|
914
896
|
}
|
|
915
|
-
function P(
|
|
916
|
-
const i = new Intl.DateTimeFormat(
|
|
897
|
+
function P(r) {
|
|
898
|
+
const i = new Intl.DateTimeFormat(r, {
|
|
917
899
|
year: "numeric",
|
|
918
900
|
month: "2-digit",
|
|
919
901
|
day: "2-digit"
|
|
@@ -923,32 +905,32 @@ function P(r2) {
|
|
|
923
905
|
e.type === "literal" ? n = e.value.trim() || e.value : (e.type === "year" || e.type === "month" || e.type === "day") && s.push({ type: e.type, len: e.type === "year" ? 4 : 2 });
|
|
924
906
|
return { segments: s, separator: n };
|
|
925
907
|
}
|
|
926
|
-
function p(
|
|
927
|
-
return Number.parseInt((
|
|
908
|
+
function p(r) {
|
|
909
|
+
return Number.parseInt((r || "0").replace(/_/g, "0"), 10) || 0;
|
|
928
910
|
}
|
|
929
|
-
function f(
|
|
911
|
+
function f(r) {
|
|
930
912
|
return {
|
|
931
|
-
year: p(
|
|
932
|
-
month: p(
|
|
933
|
-
day: p(
|
|
913
|
+
year: p(r.year),
|
|
914
|
+
month: p(r.month),
|
|
915
|
+
day: p(r.day)
|
|
934
916
|
};
|
|
935
917
|
}
|
|
936
|
-
function c(
|
|
937
|
-
return new Date(
|
|
918
|
+
function c(r, t2) {
|
|
919
|
+
return new Date(r || 2001, t2 || 1, 0).getDate();
|
|
938
920
|
}
|
|
939
|
-
function b(
|
|
940
|
-
const { year: t2, month: i, day: s } = f(
|
|
921
|
+
function b(r) {
|
|
922
|
+
const { year: t2, month: i, day: s } = f(r);
|
|
941
923
|
if (!t2 || t2 < 0 || t2 > 9999 || !i || i < 1 || i > 12 || !s || s < 1)
|
|
942
924
|
return;
|
|
943
925
|
const n = new Date(Date.UTC(t2, i - 1, s));
|
|
944
926
|
if (!(n.getUTCFullYear() !== t2 || n.getUTCMonth() !== i - 1 || n.getUTCDate() !== s))
|
|
945
927
|
return { year: t2, month: i, day: s };
|
|
946
928
|
}
|
|
947
|
-
function C(
|
|
948
|
-
const t2 = b(
|
|
929
|
+
function C(r) {
|
|
930
|
+
const t2 = b(r);
|
|
949
931
|
return t2 ? new Date(Date.UTC(t2.year, t2.month - 1, t2.day)) : undefined;
|
|
950
932
|
}
|
|
951
|
-
function T2(
|
|
933
|
+
function T2(r, t2, i, s) {
|
|
952
934
|
const n = i ? {
|
|
953
935
|
year: i.getUTCFullYear(),
|
|
954
936
|
month: i.getUTCMonth() + 1,
|
|
@@ -958,7 +940,7 @@ function T2(r2, t2, i, s) {
|
|
|
958
940
|
month: s.getUTCMonth() + 1,
|
|
959
941
|
day: s.getUTCDate()
|
|
960
942
|
} : null;
|
|
961
|
-
return
|
|
943
|
+
return r === "year" ? { min: n?.year ?? 1, max: e?.year ?? 9999 } : r === "month" ? {
|
|
962
944
|
min: n && t2.year === n.year ? n.month : 1,
|
|
963
945
|
max: e && t2.year === e.year ? e.month : 12
|
|
964
946
|
} : {
|
|
@@ -1131,26 +1113,26 @@ var u$1 = class u extends V {
|
|
|
1131
1113
|
cursor = 0;
|
|
1132
1114
|
#t;
|
|
1133
1115
|
getGroupItems(t2) {
|
|
1134
|
-
return this.options.filter((
|
|
1116
|
+
return this.options.filter((r) => r.group === t2);
|
|
1135
1117
|
}
|
|
1136
1118
|
isGroupSelected(t2) {
|
|
1137
|
-
const
|
|
1138
|
-
return e === undefined ? false :
|
|
1119
|
+
const r = this.getGroupItems(t2), e = this.value;
|
|
1120
|
+
return e === undefined ? false : r.every((s) => e.includes(s.value));
|
|
1139
1121
|
}
|
|
1140
1122
|
toggleValue() {
|
|
1141
1123
|
const t2 = this.options[this.cursor];
|
|
1142
1124
|
if (this.value === undefined && (this.value = []), t2.group === true) {
|
|
1143
|
-
const
|
|
1144
|
-
this.isGroupSelected(
|
|
1125
|
+
const r = t2.value, e = this.getGroupItems(r);
|
|
1126
|
+
this.isGroupSelected(r) ? this.value = this.value.filter((s) => e.findIndex((i) => i.value === s) === -1) : this.value = [...this.value, ...e.map((s) => s.value)], this.value = Array.from(new Set(this.value));
|
|
1145
1127
|
} else {
|
|
1146
|
-
const
|
|
1147
|
-
this.value =
|
|
1128
|
+
const r = this.value.includes(t2.value);
|
|
1129
|
+
this.value = r ? this.value.filter((e) => e !== t2.value) : [...this.value, t2.value];
|
|
1148
1130
|
}
|
|
1149
1131
|
}
|
|
1150
1132
|
constructor(t2) {
|
|
1151
1133
|
super(t2, false);
|
|
1152
|
-
const { options:
|
|
1153
|
-
this.#t = t2.selectableGroups !== false, this.options = Object.entries(
|
|
1134
|
+
const { options: r } = t2;
|
|
1135
|
+
this.#t = t2.selectableGroups !== false, this.options = Object.entries(r).flatMap(([e, s]) => [
|
|
1154
1136
|
{ value: e, group: true, label: e },
|
|
1155
1137
|
...s.map((i) => ({ ...i, group: e }))
|
|
1156
1138
|
]), this.value = [...t2.initialValues ?? []], this.cursor = Math.max(this.options.findIndex(({ value: e }) => e === t2.cursorAt), this.#t ? 0 : 1), this.on("cursor", (e) => {
|
|
@@ -1188,10 +1170,10 @@ class h extends V {
|
|
|
1188
1170
|
const t2 = this.userInput;
|
|
1189
1171
|
if (this.cursor >= t2.length)
|
|
1190
1172
|
return `${t2}\u2588`;
|
|
1191
|
-
const s = t2.slice(0, this.cursor),
|
|
1192
|
-
return
|
|
1173
|
+
const s = t2.slice(0, this.cursor), r = t2[this.cursor], i = t2.slice(this.cursor + 1);
|
|
1174
|
+
return r === `
|
|
1193
1175
|
` ? `${s}\u2588
|
|
1194
|
-
${i}` : `${s}${styleText("inverse",
|
|
1176
|
+
${i}` : `${s}${styleText("inverse", r)}${i}`;
|
|
1195
1177
|
}
|
|
1196
1178
|
get cursor() {
|
|
1197
1179
|
return this._cursor;
|
|
@@ -1224,8 +1206,8 @@ ${i}` : `${s}${styleText("inverse", r2)}${i}`;
|
|
|
1224
1206
|
if (this.#s)
|
|
1225
1207
|
return this.focused === "submit" ? true : (this.#r(`
|
|
1226
1208
|
`), this._cursor++, false);
|
|
1227
|
-
const
|
|
1228
|
-
return this.#t = true,
|
|
1209
|
+
const r = this.#t;
|
|
1210
|
+
return this.#t = true, r && this.cursor === this.userInput.length ? (this.userInput[this.cursor - 1] === `
|
|
1229
1211
|
` && (this._setUserInput(this.userInput.slice(0, this.cursor - 1) + this.userInput.slice(this.cursor)), this._cursor--), true) : (this.#r(`
|
|
1230
1212
|
`), this._cursor++, false);
|
|
1231
1213
|
}
|
|
@@ -1234,12 +1216,12 @@ ${i}` : `${s}${styleText("inverse", r2)}${i}`;
|
|
|
1234
1216
|
super({
|
|
1235
1217
|
...t2,
|
|
1236
1218
|
initialUserInput: s
|
|
1237
|
-
}, false), s !== undefined && (this._cursor = s.length), this.#s = t2.showSubmit ?? false, this.on("key", (
|
|
1219
|
+
}, false), s !== undefined && (this._cursor = s.length), this.#s = t2.showSubmit ?? false, this.on("key", (r, i) => {
|
|
1238
1220
|
if (i?.name && o$1.has(i.name)) {
|
|
1239
1221
|
this.#t = false, this.#i(i.name);
|
|
1240
1222
|
return;
|
|
1241
1223
|
}
|
|
1242
|
-
if (
|
|
1224
|
+
if (r === "\t" && this.#s) {
|
|
1243
1225
|
this.focused = this.focused === "editor" ? "submit" : "editor";
|
|
1244
1226
|
return;
|
|
1245
1227
|
}
|
|
@@ -1252,10 +1234,10 @@ ${i}` : `${s}${styleText("inverse", r2)}${i}`;
|
|
|
1252
1234
|
this._setUserInput(this.userInput.slice(0, this.cursor) + this.userInput.slice(this.cursor + 1));
|
|
1253
1235
|
return;
|
|
1254
1236
|
}
|
|
1255
|
-
|
|
1237
|
+
r && (this.#s && this.focused === "submit" && (this.focused = "editor"), this.#r(r ?? ""), this._cursor++);
|
|
1256
1238
|
}
|
|
1257
|
-
}), this.on("userInput", (
|
|
1258
|
-
this._setValue(
|
|
1239
|
+
}), this.on("userInput", (r) => {
|
|
1240
|
+
this._setValue(r);
|
|
1259
1241
|
}), this.on("finalize", () => {
|
|
1260
1242
|
this.value || (this.value = t2.defaultValue), this.value === undefined && (this.value = "");
|
|
1261
1243
|
});
|
|
@@ -1295,8 +1277,8 @@ class n extends V {
|
|
|
1295
1277
|
const t2 = this.userInput;
|
|
1296
1278
|
if (this.cursor >= t2.length)
|
|
1297
1279
|
return `${this.userInput}\u2588`;
|
|
1298
|
-
const e = t2.slice(0, this.cursor), [s, ...
|
|
1299
|
-
return `${e}${styleText("inverse", s)}${
|
|
1280
|
+
const e = t2.slice(0, this.cursor), [s, ...r] = t2.slice(this.cursor);
|
|
1281
|
+
return `${e}${styleText("inverse", s)}${r.join("")}`;
|
|
1300
1282
|
}
|
|
1301
1283
|
get cursor() {
|
|
1302
1284
|
return this._cursor;
|
|
@@ -1430,41 +1412,6 @@ var limitOptions = ({
|
|
|
1430
1412
|
b2.push(n2);
|
|
1431
1413
|
return u3 && b2.push(C2), b2;
|
|
1432
1414
|
};
|
|
1433
|
-
var confirm = (i) => {
|
|
1434
|
-
const a2 = i.active ?? "Yes", s = i.inactive ?? "No";
|
|
1435
|
-
return new r({
|
|
1436
|
-
active: a2,
|
|
1437
|
-
inactive: s,
|
|
1438
|
-
signal: i.signal,
|
|
1439
|
-
input: i.input,
|
|
1440
|
-
output: i.output,
|
|
1441
|
-
initialValue: i.initialValue ?? true,
|
|
1442
|
-
render() {
|
|
1443
|
-
const e = i.withGuide ?? settings.withGuide, u3 = `${symbol(this.state)} `, l2 = e ? `${styleText2("gray", S_BAR)} ` : "", f2 = wrapTextWithPrefix(i.output, i.message, l2, u3), o2 = `${e ? `${styleText2("gray", S_BAR)}
|
|
1444
|
-
` : ""}${f2}
|
|
1445
|
-
`, c2 = this.value ? a2 : s;
|
|
1446
|
-
switch (this.state) {
|
|
1447
|
-
case "submit": {
|
|
1448
|
-
const r2 = e ? `${styleText2("gray", S_BAR)} ` : "";
|
|
1449
|
-
return `${o2}${r2}${styleText2("dim", c2)}`;
|
|
1450
|
-
}
|
|
1451
|
-
case "cancel": {
|
|
1452
|
-
const r2 = e ? `${styleText2("gray", S_BAR)} ` : "";
|
|
1453
|
-
return `${o2}${r2}${styleText2(["strikethrough", "dim"], c2)}${e ? `
|
|
1454
|
-
${styleText2("gray", S_BAR)}` : ""}`;
|
|
1455
|
-
}
|
|
1456
|
-
default: {
|
|
1457
|
-
const r2 = e ? `${styleText2("cyan", S_BAR)} ` : "", g2 = e ? styleText2("cyan", S_BAR_END) : "";
|
|
1458
|
-
return `${o2}${r2}${this.value ? `${styleText2("green", S_RADIO_ACTIVE)} ${a2}` : `${styleText2("dim", S_RADIO_INACTIVE)} ${styleText2("dim", a2)}`}${i.vertical ? e ? `
|
|
1459
|
-
${styleText2("cyan", S_BAR)} ` : `
|
|
1460
|
-
` : ` ${styleText2("dim", "/")} `}${this.value ? `${styleText2("dim", S_RADIO_INACTIVE)} ${styleText2("dim", s)}` : `${styleText2("green", S_RADIO_ACTIVE)} ${s}`}
|
|
1461
|
-
${g2}
|
|
1462
|
-
`;
|
|
1463
|
-
}
|
|
1464
|
-
}
|
|
1465
|
-
}
|
|
1466
|
-
}).prompt();
|
|
1467
|
-
};
|
|
1468
1415
|
var MULTISELECT_INSTRUCTIONS = [
|
|
1469
1416
|
`${styleText2("dim", "\u2191/\u2193")} to navigate`,
|
|
1470
1417
|
`${styleText2("dim", "Space:")} select`,
|
|
@@ -1757,598 +1704,6 @@ var PROJECT_MODE_DISPLAY = {
|
|
|
1757
1704
|
"frontend-only": "Frontend only (no backend, no database)"
|
|
1758
1705
|
};
|
|
1759
1706
|
|
|
1760
|
-
// src/welcomeComponents.ts
|
|
1761
|
-
var COMPONENTS_MODAL_CODE = `import type React from 'react';
|
|
1762
|
-
import { useState } from 'react';
|
|
1763
|
-
import { LuX as X, LuCopy as Copy, LuCheck as Check, LuCode as Code, LuCpu as Cpu } from 'react-icons/lu';
|
|
1764
|
-
import { motion } from 'motion/react';
|
|
1765
|
-
// import { playJumpSound, playScoreSound } from '../utils/audio'; // Removed to prevent error if not exists
|
|
1766
|
-
|
|
1767
|
-
interface ComponentsModalProps {
|
|
1768
|
-
isOpen: boolean;
|
|
1769
|
-
onClose: () => void;
|
|
1770
|
-
}
|
|
1771
|
-
|
|
1772
|
-
type ComponentState = Record<string, string | number | boolean | undefined>;
|
|
1773
|
-
type SetComponentState = (state: ComponentState) => void;
|
|
1774
|
-
|
|
1775
|
-
interface ComponentItem {
|
|
1776
|
-
id: string;
|
|
1777
|
-
name: string;
|
|
1778
|
-
description: string;
|
|
1779
|
-
preview: (state: ComponentState, setState: SetComponentState) => React.ReactNode;
|
|
1780
|
-
code: string;
|
|
1781
|
-
}
|
|
1782
|
-
|
|
1783
|
-
const BRUTALIST_COMPONENTS: ComponentItem[] = [
|
|
1784
|
-
{
|
|
1785
|
-
id: 'button',
|
|
1786
|
-
name: 'Brutalist Button',
|
|
1787
|
-
description: 'A solid, high-contrast action trigger with raw borders and active scaling.',
|
|
1788
|
-
code: \`<button class="bg-rose-600 hover:bg-white text-white hover:text-black px-6 py-3 font-mono text-xs font-bold uppercase transition-all duration-150 border border-transparent hover:border-black active:scale-95 shadow-[4px_4px_0px_0px_rgba(255,255,255,0.25)]">
|
|
1789
|
-
TRIGGER PIPELINE
|
|
1790
|
-
</button>\`,
|
|
1791
|
-
preview: (state, setState) => (
|
|
1792
|
-
<button
|
|
1793
|
-
type="button"
|
|
1794
|
-
onClick={() => {
|
|
1795
|
-
const clickCount = typeof state.clickCount === 'number' ? state.clickCount : 0;
|
|
1796
|
-
setState({ ...state, clickCount: clickCount + 1 });
|
|
1797
|
-
}}
|
|
1798
|
-
className="bg-brand-pink hover:bg-white text-white hover:text-black px-6 py-3 font-mono text-xs font-bold uppercase transition-all duration-150 border border-transparent hover:border-black active:scale-95 shadow-[4px_4px_0px_0px_rgba(255,255,255,0.25)] cursor-pointer"
|
|
1799
|
-
>
|
|
1800
|
-
TRIGGER PIPELINE ({typeof state.clickCount === 'number' ? state.clickCount : 0})
|
|
1801
|
-
</button>
|
|
1802
|
-
)
|
|
1803
|
-
},
|
|
1804
|
-
{
|
|
1805
|
-
id: 'badge',
|
|
1806
|
-
name: 'Performance Badge',
|
|
1807
|
-
description: 'A high-visibility mono badge with a pulsing live status indicator.',
|
|
1808
|
-
code: \`<div class="flex items-center gap-2 border border-emerald-500/30 bg-emerald-950/20 px-3 py-1 font-mono text-xs text-emerald-400">
|
|
1809
|
-
<span class="w-1.5 h-1.5 bg-emerald-500 rounded-full animate-ping"></span>
|
|
1810
|
-
OPERATIONAL :: 100%
|
|
1811
|
-
</div>\`,
|
|
1812
|
-
preview: (_state, _setState) => (
|
|
1813
|
-
<div className="flex items-center gap-2 border border-emerald-500/30 bg-emerald-950/20 px-3 py-1.5 font-mono text-xs text-emerald-400">
|
|
1814
|
-
<span className="relative flex h-2 w-2">
|
|
1815
|
-
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
|
1816
|
-
<span className="relative inline-flex rounded-full h-2 w-2 bg-emerald-500"></span>
|
|
1817
|
-
</span>
|
|
1818
|
-
OPERATIONAL :: 100%
|
|
1819
|
-
</div>
|
|
1820
|
-
)
|
|
1821
|
-
},
|
|
1822
|
-
{
|
|
1823
|
-
id: 'switch',
|
|
1824
|
-
name: 'Monochrome Toggle',
|
|
1825
|
-
description: 'A structural, rectangular switch that flips states with tactile audio feedback.',
|
|
1826
|
-
code: \`<button class="flex items-center border border-white p-1 w-16 h-8 bg-zinc-900">
|
|
1827
|
-
<div class="w-6 h-6 bg-white transition-all transform translate-x-8"></div>
|
|
1828
|
-
</button>\`,
|
|
1829
|
-
preview: (state, setState) => {
|
|
1830
|
-
const isChecked = state.isChecked ?? false;
|
|
1831
|
-
const handleToggle = () => {
|
|
1832
|
-
setState({ ...state, isChecked: !isChecked });
|
|
1833
|
-
};
|
|
1834
|
-
return (
|
|
1835
|
-
<div className="flex items-center gap-4">
|
|
1836
|
-
<button
|
|
1837
|
-
type="button"
|
|
1838
|
-
onClick={handleToggle}
|
|
1839
|
-
className={\`flex items-center border-2 border-white p-0.5 w-16 h-8 transition-colors cursor-pointer \${isChecked ? 'bg-brand-pink' : 'bg-zinc-900'}\`}
|
|
1840
|
-
>
|
|
1841
|
-
<div className={\`w-6 h-6 bg-white transition-transform \${isChecked ? 'translate-x-8' : 'translate-x-0'}\`}></div>
|
|
1842
|
-
</button>
|
|
1843
|
-
<span className="font-mono text-xs uppercase text-gray-400">
|
|
1844
|
-
STATE: <span className="text-white font-bold">{isChecked ? 'ENABLED' : 'DISABLED'}</span>
|
|
1845
|
-
</span>
|
|
1846
|
-
</div>
|
|
1847
|
-
);
|
|
1848
|
-
}
|
|
1849
|
-
},
|
|
1850
|
-
{
|
|
1851
|
-
id: 'slider',
|
|
1852
|
-
name: 'Frequency Slider',
|
|
1853
|
-
description: 'A pixelated level adjuster with raw visual increments.',
|
|
1854
|
-
code: \`<div class="w-full max-w-xs font-mono">
|
|
1855
|
-
<input type="range" class="accent-rose-600 bg-zinc-800 h-2 w-full border border-zinc-700 appearance-none cursor-pointer" />
|
|
1856
|
-
</div>\`,
|
|
1857
|
-
preview: (state, setState) => {
|
|
1858
|
-
const value = typeof state.sliderVal === 'number' ? state.sliderVal : 60;
|
|
1859
|
-
return (
|
|
1860
|
-
<div className="w-full max-w-sm font-mono text-xs">
|
|
1861
|
-
<div className="flex justify-between mb-2">
|
|
1862
|
-
<span className="text-gray-400 uppercase">SYS_SPEED</span>
|
|
1863
|
-
<span className="text-brand-green font-bold">{value} MHz</span>
|
|
1864
|
-
</div>
|
|
1865
|
-
<input
|
|
1866
|
-
type="range"
|
|
1867
|
-
min="10"
|
|
1868
|
-
max="200"
|
|
1869
|
-
value={value}
|
|
1870
|
-
onChange={(e) => setState({ ...state, sliderVal: parseInt(e.target.value, 10) })}
|
|
1871
|
-
className="accent-brand-pink bg-zinc-900 h-2 w-full border border-zinc-700 appearance-none cursor-pointer"
|
|
1872
|
-
/>
|
|
1873
|
-
<div className="flex justify-between mt-1 text-[9px] text-gray-600">
|
|
1874
|
-
<span>MIN</span>
|
|
1875
|
-
<span>MID_GRID</span>
|
|
1876
|
-
<span>MAX</span>
|
|
1877
|
-
</div>
|
|
1878
|
-
</div>
|
|
1879
|
-
);
|
|
1880
|
-
}
|
|
1881
|
-
},
|
|
1882
|
-
{
|
|
1883
|
-
id: 'input',
|
|
1884
|
-
name: 'Brutalist Input',
|
|
1885
|
-
description: 'Minimal text box with bright validation focus indicators.',
|
|
1886
|
-
code: \`<div class="relative font-mono">
|
|
1887
|
-
<input type="text" placeholder="ENTER NODE NAME..." class="bg-black border border-zinc-700 focus:border-rose-600 text-white px-4 py-2 w-full outline-none" />
|
|
1888
|
-
</div>\`,
|
|
1889
|
-
preview: (state, setState) => {
|
|
1890
|
-
const text = typeof state.inputValue === 'string' ? state.inputValue : '';
|
|
1891
|
-
return (
|
|
1892
|
-
<div className="w-full max-w-xs font-mono text-xs">
|
|
1893
|
-
<label className="block text-gray-500 uppercase mb-1.5">
|
|
1894
|
-
Node Configuration
|
|
1895
|
-
<input
|
|
1896
|
-
type="text"
|
|
1897
|
-
placeholder="ENTER NODE NAME..."
|
|
1898
|
-
value={text}
|
|
1899
|
-
onChange={(e) => setState({ ...state, inputValue: e.target.value })}
|
|
1900
|
-
className="mt-1.5 bg-black border border-zinc-700 focus:border-brand-pink text-white px-4 py-2 w-full outline-none"
|
|
1901
|
-
/>
|
|
1902
|
-
</label>
|
|
1903
|
-
{text && (
|
|
1904
|
-
<p className="text-[10px] text-brand-green mt-1">\u2713 Validating: {text.toUpperCase()}.local</p>
|
|
1905
|
-
)}
|
|
1906
|
-
</div>
|
|
1907
|
-
);
|
|
1908
|
-
}
|
|
1909
|
-
}
|
|
1910
|
-
];
|
|
1911
|
-
|
|
1912
|
-
export default function ComponentsModal({ isOpen, onClose }: ComponentsModalProps) {
|
|
1913
|
-
const [activeCompId, setActiveCompId] = useState('button');
|
|
1914
|
-
const [componentStates, setComponentStates] = useState<Record<string, ComponentState>>({});
|
|
1915
|
-
const [copiedId, setCopiedId] = useState<string | null>(null);
|
|
1916
|
-
|
|
1917
|
-
if (!isOpen) return null;
|
|
1918
|
-
|
|
1919
|
-
const fallbackComponent = BRUTALIST_COMPONENTS[0];
|
|
1920
|
-
if (!fallbackComponent) return null;
|
|
1921
|
-
|
|
1922
|
-
const activeComp = BRUTALIST_COMPONENTS.find(c => c.id === activeCompId) ?? fallbackComponent;
|
|
1923
|
-
const activeState = componentStates[activeCompId] || {};
|
|
1924
|
-
|
|
1925
|
-
const handleSetState = (newState: ComponentState) => {
|
|
1926
|
-
setComponentStates({
|
|
1927
|
-
...componentStates,
|
|
1928
|
-
[activeCompId]: newState
|
|
1929
|
-
});
|
|
1930
|
-
};
|
|
1931
|
-
|
|
1932
|
-
const handleCopyCode = (code: string) => {
|
|
1933
|
-
navigator.clipboard.writeText(code);
|
|
1934
|
-
setCopiedId(activeCompId);
|
|
1935
|
-
setTimeout(() => setCopiedId(null), 2000);
|
|
1936
|
-
};
|
|
1937
|
-
|
|
1938
|
-
return (
|
|
1939
|
-
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/85 backdrop-blur-sm" id="components-modal-container">
|
|
1940
|
-
<motion.div
|
|
1941
|
-
initial={{ opacity: 0, scale: 0.95, y: 15 }}
|
|
1942
|
-
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
1943
|
-
exit={{ opacity: 0, scale: 0.95, y: 15 }}
|
|
1944
|
-
transition={{ duration: 0.2 }}
|
|
1945
|
-
className="w-full max-w-4xl h-[75vh] bg-black border-2 border-white flex flex-col relative"
|
|
1946
|
-
>
|
|
1947
|
-
<div className="flex items-center justify-between border-b border-white p-5">
|
|
1948
|
-
<div className="flex items-center gap-3">
|
|
1949
|
-
<Cpu className="w-5 h-5 text-rose-500" />
|
|
1950
|
-
<h2 className="text-xl font-bold font-mono tracking-tight uppercase">
|
|
1951
|
-
Rakta <span className="text-rose-500">Component Library</span>
|
|
1952
|
-
</h2>
|
|
1953
|
-
</div>
|
|
1954
|
-
<button
|
|
1955
|
-
type="button"
|
|
1956
|
-
onClick={onClose}
|
|
1957
|
-
className="p-2 border border-white hover:bg-rose-500 hover:text-white transition-colors cursor-pointer"
|
|
1958
|
-
>
|
|
1959
|
-
<X className="w-5 h-5" />
|
|
1960
|
-
</button>
|
|
1961
|
-
</div>
|
|
1962
|
-
|
|
1963
|
-
<div className="flex-1 flex flex-col md:flex-row overflow-hidden">
|
|
1964
|
-
<div className="w-full md:w-56 border-r border-white overflow-y-auto bg-[#080808] divide-y divide-white/20">
|
|
1965
|
-
{BRUTALIST_COMPONENTS.map((comp) => {
|
|
1966
|
-
const isSelected = comp.id === activeCompId;
|
|
1967
|
-
return (
|
|
1968
|
-
<button
|
|
1969
|
-
type="button"
|
|
1970
|
-
key={comp.id}
|
|
1971
|
-
onClick={() => setActiveCompId(comp.id)}
|
|
1972
|
-
className={\`w-full text-left p-4 transition-colors font-mono text-xs uppercase cursor-pointer \${
|
|
1973
|
-
isSelected
|
|
1974
|
-
? 'bg-rose-500 text-white font-bold'
|
|
1975
|
-
: 'text-gray-400 hover:bg-white/5 hover:text-white'
|
|
1976
|
-
}\`}
|
|
1977
|
-
>
|
|
1978
|
-
{comp.name}
|
|
1979
|
-
</button>
|
|
1980
|
-
);
|
|
1981
|
-
})}
|
|
1982
|
-
</div>
|
|
1983
|
-
|
|
1984
|
-
<div className="flex-1 flex flex-col overflow-y-auto bg-black p-6 md:p-8">
|
|
1985
|
-
<div className="mb-6">
|
|
1986
|
-
<h3 className="text-xl font-bold text-white uppercase font-mono tracking-tight">{activeComp.name}</h3>
|
|
1987
|
-
<p className="text-sm text-gray-400 mt-1">{activeComp.description}</p>
|
|
1988
|
-
</div>
|
|
1989
|
-
|
|
1990
|
-
<div className="border border-white/30 bg-[#050505] p-12 flex items-center justify-center min-h-[160px] relative">
|
|
1991
|
-
<span className="absolute top-2 left-2 text-[8px] font-mono text-gray-600 uppercase tracking-widest">LIVE PLAYGROUND</span>
|
|
1992
|
-
{activeComp.preview(activeState, handleSetState)}
|
|
1993
|
-
</div>
|
|
1994
|
-
|
|
1995
|
-
<div className="mt-8 flex-1 flex flex-col min-h-[150px]">
|
|
1996
|
-
<div className="flex items-center justify-between bg-zinc-900 border-t border-x border-white/20 px-4 py-2">
|
|
1997
|
-
<span className="text-[10px] font-mono text-gray-500 uppercase tracking-widest flex items-center gap-1.5">
|
|
1998
|
-
<Code className="w-3.5 h-3.5" /> HTML/Tailwind Markup
|
|
1999
|
-
</span>
|
|
2000
|
-
<button
|
|
2001
|
-
type="button"
|
|
2002
|
-
onClick={() => handleCopyCode(activeComp.code)}
|
|
2003
|
-
className="flex items-center gap-1.5 text-[10px] font-mono text-rose-500 hover:text-white transition-colors cursor-pointer"
|
|
2004
|
-
>
|
|
2005
|
-
{copiedId === activeComp.id ? (
|
|
2006
|
-
<>
|
|
2007
|
-
<Check className="w-3.5 h-3.5 text-green-500" /> COPIED!
|
|
2008
|
-
</>
|
|
2009
|
-
) : (
|
|
2010
|
-
<>
|
|
2011
|
-
<Copy className="w-3.5 h-3.5" /> COPY TO CLIPBOARD
|
|
2012
|
-
</>
|
|
2013
|
-
)}
|
|
2014
|
-
</button>
|
|
2015
|
-
</div>
|
|
2016
|
-
<pre className="flex-1 bg-zinc-950 border border-white/20 p-4 font-mono text-xs text-green-400 overflow-x-auto whitespace-pre leading-5 select-all">
|
|
2017
|
-
<code>{activeComp.code}</code>
|
|
2018
|
-
</pre>
|
|
2019
|
-
</div>
|
|
2020
|
-
</div>
|
|
2021
|
-
</div>
|
|
2022
|
-
</motion.div>
|
|
2023
|
-
</div>
|
|
2024
|
-
);
|
|
2025
|
-
}
|
|
2026
|
-
`;
|
|
2027
|
-
var CORAL_OBSTACLE_CODE = `interface CoralObstacleProps {
|
|
2028
|
-
position: 'TOP' | 'BOTTOM';
|
|
2029
|
-
height: number;
|
|
2030
|
-
width?: number;
|
|
2031
|
-
paletteIndex?: number;
|
|
2032
|
-
variant?: number;
|
|
2033
|
-
scaleX?: number;
|
|
2034
|
-
}
|
|
2035
|
-
|
|
2036
|
-
export default function CoralObstacle({
|
|
2037
|
-
position,
|
|
2038
|
-
height,
|
|
2039
|
-
width = 64,
|
|
2040
|
-
paletteIndex = 0,
|
|
2041
|
-
variant = 0,
|
|
2042
|
-
scaleX = 1
|
|
2043
|
-
}: CoralObstacleProps) {
|
|
2044
|
-
const defaultPalette = { primary: '#f43f5e', secondary: '#fda4af', shadow: '#9f1239', polyps: '#ffe4e6', name: 'rose' };
|
|
2045
|
-
const palettes = [
|
|
2046
|
-
defaultPalette,
|
|
2047
|
-
{ primary: '#06b6d4', secondary: '#67e8f9', shadow: '#155e75', polyps: '#ecfeff', name: 'cyan' },
|
|
2048
|
-
{ primary: '#a855f7', secondary: '#d8b4fe', shadow: '#581c87', polyps: '#faf5ff', name: 'amethyst' },
|
|
2049
|
-
{ primary: '#f59e0b', secondary: '#fde047', shadow: '#78350f', polyps: '#fefce8', name: 'amber' },
|
|
2050
|
-
{ primary: '#10b981', secondary: '#6ee7b7', shadow: '#065f46', polyps: '#e6fffa', name: 'emerald' },
|
|
2051
|
-
];
|
|
2052
|
-
const palette = palettes[paletteIndex % palettes.length] ?? defaultPalette;
|
|
2053
|
-
const coralId = \`coralGrad-\${palette.name}-\${variant}\`;
|
|
2054
|
-
|
|
2055
|
-
return (
|
|
2056
|
-
<div
|
|
2057
|
-
className="absolute flex items-center justify-center pointer-events-none"
|
|
2058
|
-
style={{ height: \`\${height}px\`, width: \`\${width}px\`, transform: \`scaleX(\${scaleX})\` }}
|
|
2059
|
-
>
|
|
2060
|
-
<svg
|
|
2061
|
-
aria-hidden="true"
|
|
2062
|
-
focusable="false"
|
|
2063
|
-
viewBox="0 0 80 120"
|
|
2064
|
-
className="w-full h-full drop-shadow-[0_6px_14px_rgba(0,0,0,0.7)]"
|
|
2065
|
-
preserveAspectRatio="none"
|
|
2066
|
-
>
|
|
2067
|
-
<defs>
|
|
2068
|
-
<linearGradient id={coralId} x1="0%" y1="0%" x2="100%" y2="100%">
|
|
2069
|
-
<stop offset="0%" stopColor={palette.secondary} />
|
|
2070
|
-
<stop offset="50%" stopColor={palette.primary} />
|
|
2071
|
-
<stop offset="100%" stopColor={palette.shadow} />
|
|
2072
|
-
</linearGradient>
|
|
2073
|
-
</defs>
|
|
2074
|
-
|
|
2075
|
-
<g transform={position === 'TOP' ? 'scale(1, -1) translate(0, -120)' : undefined}>
|
|
2076
|
-
<path d="M 40 120 C 35 90, 20 80, 24 55 C 28 30, 15 20, 22 10 C 29 0, 37 15, 35 30 C 33 45, 42 55, 40 75 C 38 95, 42 110, 40 120 Z" fill={\`url(#\${coralId})\`} />
|
|
2077
|
-
<path d="M 40 120 C 45 95, 55 85, 52 65 C 49 45, 62 35, 58 20 C 54 5, 46 12, 48 25 C 50 38, 42 50, 44 70 C 46 90, 42 105, 40 120 Z" fill={\`url(#\${coralId})\`} />
|
|
2078
|
-
</g>
|
|
2079
|
-
</svg>
|
|
2080
|
-
</div>
|
|
2081
|
-
);
|
|
2082
|
-
}
|
|
2083
|
-
`;
|
|
2084
|
-
var DEPLOY_MODAL_CODE = `import { useState, useEffect, useRef } from 'react';
|
|
2085
|
-
import { LuX as X, LuPlay as Play, LuRotateCcw as RotateCcw, LuCloud as Cloud } from 'react-icons/lu';
|
|
2086
|
-
import { motion } from 'motion/react';
|
|
2087
|
-
|
|
2088
|
-
interface DeployModalProps {
|
|
2089
|
-
isOpen: boolean;
|
|
2090
|
-
onClose: () => void;
|
|
2091
|
-
}
|
|
2092
|
-
|
|
2093
|
-
const DEPLOY_LOGS = [
|
|
2094
|
-
{ text: ':: RAKTA CORE PIPELINE INITIALIZED ::', type: 'system' },
|
|
2095
|
-
{ text: '[SYS] Resolving package.json manifest dependencies...', type: 'info' },
|
|
2096
|
-
{ text: '[VITE] Starting asset compilation & typescript check...', type: 'info' },
|
|
2097
|
-
{ text: '[VITE] \u2713 Production dist/ folder generated successfully [384ms]', type: 'success' },
|
|
2098
|
-
{ text: '[ESBUILD] Compiling full-stack backend entrypoint: server.ts', type: 'info' },
|
|
2099
|
-
{ text: '[ESBUILD] \u2713 Bundled server.ts into dist/server.cjs [120ms]', type: 'success' },
|
|
2100
|
-
{ text: '[CLOUD RUN] Pushing build payload to regional edge registry...', type: 'info' },
|
|
2101
|
-
{ text: '[CLOUD RUN] Ingress configured: routing all traffic exclusively to Port 3000', type: 'info' },
|
|
2102
|
-
{ text: ':: SYSTEM DEPLOYMENT COMPLETED ON EDGE NODES ::', type: 'success' },
|
|
2103
|
-
];
|
|
2104
|
-
|
|
2105
|
-
export default function DeployModal({ isOpen, onClose }: DeployModalProps) {
|
|
2106
|
-
const [status, setStatus] = useState<'idle' | 'building' | 'success' | 'error'>('idle');
|
|
2107
|
-
const [logs, setLogs] = useState<Array<{ text: string; type: string }>>([]);
|
|
2108
|
-
const [progress, setProgress] = useState(0);
|
|
2109
|
-
const terminalEndRef = useRef<HTMLDivElement>(null);
|
|
2110
|
-
|
|
2111
|
-
useEffect(() => {
|
|
2112
|
-
if (terminalEndRef.current) {
|
|
2113
|
-
terminalEndRef.current.scrollIntoView({ behavior: 'smooth' });
|
|
2114
|
-
}
|
|
2115
|
-
});
|
|
2116
|
-
|
|
2117
|
-
if (!isOpen) return null;
|
|
2118
|
-
|
|
2119
|
-
const startDeployment = () => {
|
|
2120
|
-
setStatus('building');
|
|
2121
|
-
setLogs([]);
|
|
2122
|
-
setProgress(0);
|
|
2123
|
-
|
|
2124
|
-
let currentLogIndex = 0;
|
|
2125
|
-
|
|
2126
|
-
const runStep = () => {
|
|
2127
|
-
if (currentLogIndex < DEPLOY_LOGS.length) {
|
|
2128
|
-
const nextLog = DEPLOY_LOGS[currentLogIndex];
|
|
2129
|
-
if (nextLog) {
|
|
2130
|
-
setLogs(prev => [...prev, nextLog]);
|
|
2131
|
-
}
|
|
2132
|
-
setProgress(Math.round(((currentLogIndex + 1) / DEPLOY_LOGS.length) * 100));
|
|
2133
|
-
currentLogIndex++;
|
|
2134
|
-
setTimeout(runStep, Math.random() * 250 + 150);
|
|
2135
|
-
} else {
|
|
2136
|
-
setStatus('success');
|
|
2137
|
-
}
|
|
2138
|
-
};
|
|
2139
|
-
setTimeout(runStep, 400);
|
|
2140
|
-
};
|
|
2141
|
-
|
|
2142
|
-
const resetDeploy = () => {
|
|
2143
|
-
setStatus('idle');
|
|
2144
|
-
setLogs([]);
|
|
2145
|
-
setProgress(0);
|
|
2146
|
-
};
|
|
2147
|
-
|
|
2148
|
-
return (
|
|
2149
|
-
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/85 backdrop-blur-sm" id="deploy-modal-container">
|
|
2150
|
-
<motion.div
|
|
2151
|
-
initial={{ opacity: 0, scale: 0.95, y: 15 }}
|
|
2152
|
-
animate={{ opacity: 1, scale: 1, y: 0 }}
|
|
2153
|
-
exit={{ opacity: 0, scale: 0.95, y: 15 }}
|
|
2154
|
-
transition={{ duration: 0.2 }}
|
|
2155
|
-
className="w-full max-w-3xl h-[70vh] bg-black border-2 border-white flex flex-col relative"
|
|
2156
|
-
>
|
|
2157
|
-
<div className="flex items-center justify-between border-b border-white/30 p-5">
|
|
2158
|
-
<div className="flex items-center gap-3">
|
|
2159
|
-
<Cloud className="w-5 h-5 text-rose-500" />
|
|
2160
|
-
<h2 className="text-xl font-bold font-mono tracking-tight uppercase">
|
|
2161
|
-
Rakta <span className="text-rose-500">Edge Deployment</span>
|
|
2162
|
-
</h2>
|
|
2163
|
-
</div>
|
|
2164
|
-
<button
|
|
2165
|
-
type="button"
|
|
2166
|
-
onClick={onClose}
|
|
2167
|
-
className="p-2 border border-white/30 hover:bg-rose-500 hover:text-white transition-colors cursor-pointer"
|
|
2168
|
-
>
|
|
2169
|
-
<X className="w-5 h-5" />
|
|
2170
|
-
</button>
|
|
2171
|
-
</div>
|
|
2172
|
-
|
|
2173
|
-
<div className="bg-zinc-900 p-6 border-b border-white/20 flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
|
2174
|
-
<div>
|
|
2175
|
-
<span className="text-[10px] font-mono text-gray-500 uppercase tracking-widest block">PIPELINE STATUS</span>
|
|
2176
|
-
<div className="flex items-center gap-2 mt-1">
|
|
2177
|
-
{status === 'idle' && <span className="font-mono text-sm text-yellow-500 font-bold uppercase">IDLE - READY TO LAUNCH</span>}
|
|
2178
|
-
{status === 'building' && <span className="font-mono text-sm text-rose-500 font-bold uppercase">COMPILES IN PROGRESS ({progress}%)</span>}
|
|
2179
|
-
{status === 'success' && <span className="font-mono text-sm text-green-500 font-bold uppercase">LIVE - PRODUCTION DEPLOYED</span>}
|
|
2180
|
-
</div>
|
|
2181
|
-
</div>
|
|
2182
|
-
<div className="flex items-center gap-3">
|
|
2183
|
-
{status === 'idle' && (
|
|
2184
|
-
<button type="button" onClick={startDeployment} className="bg-rose-500 hover:bg-white text-white hover:text-black px-6 py-2.5 font-mono text-xs font-bold uppercase transition-all duration-150 border border-transparent active:scale-95 flex items-center gap-2 cursor-pointer">
|
|
2185
|
-
<Play className="w-4 h-4 fill-current" /> INITIATE LAUNCH
|
|
2186
|
-
</button>
|
|
2187
|
-
)}
|
|
2188
|
-
{status === 'success' && (
|
|
2189
|
-
<button type="button" onClick={resetDeploy} className="border border-white hover:bg-white hover:text-black px-4 py-2.5 font-mono text-xs uppercase transition-all duration-150 flex items-center gap-1.5 cursor-pointer">
|
|
2190
|
-
<RotateCcw className="w-3.5 h-3.5" /> RE-BUILD PIPELINE
|
|
2191
|
-
</button>
|
|
2192
|
-
)}
|
|
2193
|
-
</div>
|
|
2194
|
-
</div>
|
|
2195
|
-
|
|
2196
|
-
<div className="flex-1 bg-black p-5 font-mono text-xs overflow-y-auto flex flex-col gap-2 select-text selection:bg-rose-500/30">
|
|
2197
|
-
{logs.map((log) => {
|
|
2198
|
-
let textColor = 'text-gray-400';
|
|
2199
|
-
if (log.type === 'system') textColor = 'text-rose-500 font-bold border-b border-rose-500/20 pb-1 mb-2';
|
|
2200
|
-
if (log.type === 'success') textColor = 'text-green-500 font-bold';
|
|
2201
|
-
if (log.type === 'info') textColor = 'text-gray-300';
|
|
2202
|
-
return <div key={\`\${log.type}-\${log.text}\`} className={\`leading-relaxed whitespace-pre-wrap \${textColor}\`}>{log.text}</div>;
|
|
2203
|
-
})}
|
|
2204
|
-
{status === 'idle' && <div className="text-gray-600 italic mt-4 text-center">Click 'INITIATE LAUNCH' to begin.</div>}
|
|
2205
|
-
<div ref={terminalEndRef} />
|
|
2206
|
-
</div>
|
|
2207
|
-
</motion.div>
|
|
2208
|
-
</div>
|
|
2209
|
-
);
|
|
2210
|
-
}
|
|
2211
|
-
`;
|
|
2212
|
-
var DOCS_MODAL_CODE = `import { LuX as X, LuBook as Book } from 'react-icons/lu';
|
|
2213
|
-
import { motion } from 'motion/react';
|
|
2214
|
-
|
|
2215
|
-
interface DocsModalProps {
|
|
2216
|
-
isOpen: boolean;
|
|
2217
|
-
onClose: () => void;
|
|
2218
|
-
}
|
|
2219
|
-
|
|
2220
|
-
export default function DocsModal({ isOpen, onClose }: DocsModalProps) {
|
|
2221
|
-
if (!isOpen) return null;
|
|
2222
|
-
return (
|
|
2223
|
-
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm">
|
|
2224
|
-
<motion.div initial={{ opacity: 0, y: 15 }} animate={{ opacity: 1, y: 0 }} className="w-full max-w-5xl h-[80vh] bg-black border-2 border-white flex flex-col relative">
|
|
2225
|
-
<div className="flex items-center justify-between border-b border-white/30 p-5">
|
|
2226
|
-
<div className="flex items-center gap-3">
|
|
2227
|
-
<Book className="w-5 h-5 text-rose-500" />
|
|
2228
|
-
<h2 className="text-xl font-bold font-mono tracking-tight uppercase">Rakta.js <span className="text-rose-500">System Manual</span></h2>
|
|
2229
|
-
</div>
|
|
2230
|
-
<button type="button" onClick={onClose} className="p-2 border border-white/30 hover:bg-rose-500 hover:text-white transition-colors cursor-pointer"><X className="w-5 h-5" /></button>
|
|
2231
|
-
</div>
|
|
2232
|
-
<div className="p-10 text-white font-mono text-sm leading-relaxed max-w-3xl">
|
|
2233
|
-
<h3 className="text-3xl font-extrabold text-rose-500 mb-6">Welcome to Rakta.js</h3>
|
|
2234
|
-
<p className="mb-4">You have successfully generated your project.</p>
|
|
2235
|
-
<p className="mb-4">Use the CLI generator to expand your codebase, or start editing files in the app/ directory.</p>
|
|
2236
|
-
<ul className="list-disc pl-5 mt-4 space-y-2 text-gray-400">
|
|
2237
|
-
<li>Instant Engine Boot: Sub-1ms load time.</li>
|
|
2238
|
-
<li>Brutalist Aesthetics natively supported.</li>
|
|
2239
|
-
<li>Hot Reload available globally via WebSockets.</li>
|
|
2240
|
-
</ul>
|
|
2241
|
-
</div>
|
|
2242
|
-
</motion.div>
|
|
2243
|
-
</div>
|
|
2244
|
-
);
|
|
2245
|
-
}
|
|
2246
|
-
`;
|
|
2247
|
-
var SHRIMP_CHARACTER_CODE = `interface ShrimpCharacterProps {
|
|
2248
|
-
status: 'IDLE' | 'SWIMMING' | 'JUMPING' | 'DEAD';
|
|
2249
|
-
playerY: number;
|
|
2250
|
-
rotation?: number;
|
|
2251
|
-
}
|
|
2252
|
-
|
|
2253
|
-
export default function ShrimpCharacter({ status, playerY, rotation }: ShrimpCharacterProps) {
|
|
2254
|
-
let bodyClass = '';
|
|
2255
|
-
const isDead = status === 'DEAD';
|
|
2256
|
-
|
|
2257
|
-
if (isDead) bodyClass = 'animate-bounce';
|
|
2258
|
-
else if (status === 'SWIMMING') bodyClass = 'animate-pulse';
|
|
2259
|
-
else bodyClass = '';
|
|
2260
|
-
|
|
2261
|
-
const finalRotation = rotation !== undefined ? rotation : (isDead ? 180 : 0);
|
|
2262
|
-
const shrimpColor = isDead ? 'fill-orange-400' : 'fill-rose-500';
|
|
2263
|
-
|
|
2264
|
-
return (
|
|
2265
|
-
<div
|
|
2266
|
-
className="relative w-16 h-16 flex items-center justify-center select-none pointer-events-none transition-transform duration-75"
|
|
2267
|
-
style={{ transform: \`translateY(\${-playerY}px) rotate(\${finalRotation}deg)\` }}
|
|
2268
|
-
>
|
|
2269
|
-
<svg aria-hidden="true" focusable="false" viewBox="0 0 100 100" className={\`w-full h-full drop-shadow-[0_4px_12px_rgba(244,63,94,0.3)] \${bodyClass}\`}>
|
|
2270
|
-
<path d="M 30 46 C 25 32, 45 22, 58 28 C 65 31, 62 48, 55 54 C 44 58, 35 55, 30 46 Z" className={shrimpColor} />
|
|
2271
|
-
<path d="M 55 30 C 65 24, 76 34, 74 46 C 72 54, 62 55, 55 48 Z" className={shrimpColor} />
|
|
2272
|
-
<circle cx="66" cy="38" r="4.5" fill={isDead ? '#18181b' : '#000'} />
|
|
2273
|
-
</svg>
|
|
2274
|
-
<style>{\`
|
|
2275
|
-
@keyframes bubble-up {
|
|
2276
|
-
0% { transform: translateY(0) scale(1); opacity: 0.8; }
|
|
2277
|
-
100% { transform: translateY(-20px) scale(1.5); opacity: 0; }
|
|
2278
|
-
}
|
|
2279
|
-
\`}</style>
|
|
2280
|
-
</div>
|
|
2281
|
-
);
|
|
2282
|
-
}
|
|
2283
|
-
`;
|
|
2284
|
-
var WELCOME_PAGE_CODE = `import { useState } from "react";
|
|
2285
|
-
import { LuBook as Book, LuCloud as Cloud, LuCpu as Cpu } from "react-icons/lu";
|
|
2286
|
-
import ComponentsModal from "./components/ComponentsModal";
|
|
2287
|
-
import DeployModal from "./components/DeployModal";
|
|
2288
|
-
import DocsModal from "./components/DocsModal";
|
|
2289
|
-
import ShrimpCharacter from "./components/ShrimpCharacter";
|
|
2290
|
-
|
|
2291
|
-
export default function WelcomePage() {
|
|
2292
|
-
const [modalOpen, setModalOpen] = useState<'components' | 'deploy' | 'docs' | null>(null);
|
|
2293
|
-
|
|
2294
|
-
return (
|
|
2295
|
-
<main className="min-h-screen bg-[#050505] flex flex-col items-center justify-center text-white font-mono p-4">
|
|
2296
|
-
<div className="absolute top-10 pointer-events-none opacity-20">
|
|
2297
|
-
<ShrimpCharacter status="IDLE" playerY={0} />
|
|
2298
|
-
</div>
|
|
2299
|
-
|
|
2300
|
-
<div className="text-center z-10 max-w-2xl">
|
|
2301
|
-
<h1 className="text-5xl md:text-7xl font-extrabold uppercase tracking-tighter mb-4">
|
|
2302
|
-
Welcome to <span className="text-rose-500">Rakta.js</span>
|
|
2303
|
-
</h1>
|
|
2304
|
-
<p className="text-gray-400 text-sm md:text-base mb-10 leading-relaxed">
|
|
2305
|
-
Small in size. Fierce in speed. Alive in every route.
|
|
2306
|
-
<br/>
|
|
2307
|
-
Your neo-brutalist web application is ready for action.
|
|
2308
|
-
</p>
|
|
2309
|
-
|
|
2310
|
-
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
2311
|
-
<button
|
|
2312
|
-
type="button"
|
|
2313
|
-
onClick={() => setModalOpen('components')}
|
|
2314
|
-
className="flex flex-col items-center justify-center gap-3 p-6 border-2 border-zinc-800 bg-zinc-900/50 hover:border-rose-500 hover:bg-rose-500/10 transition-colors cursor-pointer group"
|
|
2315
|
-
>
|
|
2316
|
-
<Cpu className="w-8 h-8 text-gray-400 group-hover:text-rose-500" />
|
|
2317
|
-
<span className="font-bold text-sm uppercase">Components</span>
|
|
2318
|
-
</button>
|
|
2319
|
-
|
|
2320
|
-
<button
|
|
2321
|
-
type="button"
|
|
2322
|
-
onClick={() => setModalOpen('docs')}
|
|
2323
|
-
className="flex flex-col items-center justify-center gap-3 p-6 border-2 border-zinc-800 bg-zinc-900/50 hover:border-blue-500 hover:bg-blue-500/10 transition-colors cursor-pointer group"
|
|
2324
|
-
>
|
|
2325
|
-
<Book className="w-8 h-8 text-gray-400 group-hover:text-blue-500" />
|
|
2326
|
-
<span className="font-bold text-sm uppercase">Documentation</span>
|
|
2327
|
-
</button>
|
|
2328
|
-
|
|
2329
|
-
<button
|
|
2330
|
-
type="button"
|
|
2331
|
-
onClick={() => setModalOpen('deploy')}
|
|
2332
|
-
className="flex flex-col items-center justify-center gap-3 p-6 border-2 border-zinc-800 bg-zinc-900/50 hover:border-green-500 hover:bg-green-500/10 transition-colors cursor-pointer group"
|
|
2333
|
-
>
|
|
2334
|
-
<Cloud className="w-8 h-8 text-gray-400 group-hover:text-green-500" />
|
|
2335
|
-
<span className="font-bold text-sm uppercase">Deployment</span>
|
|
2336
|
-
</button>
|
|
2337
|
-
</div>
|
|
2338
|
-
</div>
|
|
2339
|
-
|
|
2340
|
-
<div className="absolute bottom-6 text-[10px] text-gray-600 uppercase tracking-widest text-center">
|
|
2341
|
-
Running on http://localhost:3000 \u2022 Hot Reload Active
|
|
2342
|
-
</div>
|
|
2343
|
-
|
|
2344
|
-
{modalOpen === 'components' && <ComponentsModal isOpen={true} onClose={() => setModalOpen(null)} />}
|
|
2345
|
-
{modalOpen === 'deploy' && <DeployModal isOpen={true} onClose={() => setModalOpen(null)} />}
|
|
2346
|
-
{modalOpen === 'docs' && <DocsModal isOpen={true} onClose={() => setModalOpen(null)} />}
|
|
2347
|
-
</main>
|
|
2348
|
-
);
|
|
2349
|
-
}
|
|
2350
|
-
`;
|
|
2351
|
-
|
|
2352
1707
|
// src/generator.ts
|
|
2353
1708
|
var DEFAULT_METADATA_TITLE = "Rakta.js | Small in size. Fierce in speed. Alive in every route";
|
|
2354
1709
|
var FAVICON_BYTES = readFileSync(new URL("../assets/favicon.ico", import.meta.url));
|
|
@@ -2448,7 +1803,7 @@ dist/
|
|
|
2448
1803
|
function getFrontendOnlyFiles(projectConfig) {
|
|
2449
1804
|
const { projectName, cssFramework } = projectConfig;
|
|
2450
1805
|
const styleFileName = cssFramework === "sass" ? "globals.scss" : "globals.css";
|
|
2451
|
-
|
|
1806
|
+
return [
|
|
2452
1807
|
{
|
|
2453
1808
|
path: "package.json",
|
|
2454
1809
|
content: JSON.stringify({
|
|
@@ -2464,11 +1819,9 @@ function getFrontendOnlyFiles(projectConfig) {
|
|
|
2464
1819
|
typecheck: "tsc --noEmit"
|
|
2465
1820
|
},
|
|
2466
1821
|
dependencies: {
|
|
2467
|
-
raktajs: "^0.1.
|
|
1822
|
+
raktajs: "^0.1.8",
|
|
2468
1823
|
react: "^19.2.7",
|
|
2469
1824
|
"react-dom": "^19.2.7",
|
|
2470
|
-
"react-icons": "^5.2.1",
|
|
2471
|
-
motion: "^12.0.0",
|
|
2472
1825
|
...getCssDependencies(cssFramework)
|
|
2473
1826
|
},
|
|
2474
1827
|
devDependencies: {
|
|
@@ -2540,24 +1893,12 @@ export default defineRaktaConfig({
|
|
|
2540
1893
|
content: generateFrontendOnlyNotFound()
|
|
2541
1894
|
},
|
|
2542
1895
|
{
|
|
2543
|
-
path: "app/components/
|
|
2544
|
-
content:
|
|
2545
|
-
},
|
|
2546
|
-
{
|
|
2547
|
-
path: "app/components/CoralObstacle.tsx",
|
|
2548
|
-
content: CORAL_OBSTACLE_CODE
|
|
2549
|
-
},
|
|
2550
|
-
{
|
|
2551
|
-
path: "app/components/DeployModal.tsx",
|
|
2552
|
-
content: DEPLOY_MODAL_CODE
|
|
2553
|
-
},
|
|
2554
|
-
{
|
|
2555
|
-
path: "app/components/DocsModal.tsx",
|
|
2556
|
-
content: DOCS_MODAL_CODE
|
|
1896
|
+
path: "app/components/raktaShrimpMascot.tsx",
|
|
1897
|
+
content: generateShrimpMascotComponent()
|
|
2557
1898
|
},
|
|
2558
1899
|
{
|
|
2559
|
-
path: "app/components/
|
|
2560
|
-
content:
|
|
1900
|
+
path: "app/components/shrimpRunGame.tsx",
|
|
1901
|
+
content: generateShrimpRunGameComponent()
|
|
2561
1902
|
},
|
|
2562
1903
|
{
|
|
2563
1904
|
path: `styles/${styleFileName}`,
|
|
@@ -2572,12 +1913,11 @@ export default defineRaktaConfig({
|
|
|
2572
1913
|
content: FAVICON_BYTES
|
|
2573
1914
|
}
|
|
2574
1915
|
];
|
|
2575
|
-
return processFilesForLanguage(files, projectConfig.useTypeScript);
|
|
2576
1916
|
}
|
|
2577
1917
|
function getFullstackFrontendFiles(projectConfig) {
|
|
2578
1918
|
const { projectName, cssFramework } = projectConfig;
|
|
2579
1919
|
const styleFileName = cssFramework === "sass" ? "globals.scss" : "globals.css";
|
|
2580
|
-
|
|
1920
|
+
return [
|
|
2581
1921
|
{
|
|
2582
1922
|
path: "frontend/package.json",
|
|
2583
1923
|
content: JSON.stringify({
|
|
@@ -2594,11 +1934,9 @@ function getFullstackFrontendFiles(projectConfig) {
|
|
|
2594
1934
|
typecheck: "tsc --noEmit"
|
|
2595
1935
|
},
|
|
2596
1936
|
dependencies: {
|
|
2597
|
-
raktajs: "^0.1.
|
|
1937
|
+
raktajs: "^0.1.8",
|
|
2598
1938
|
react: "^19.2.7",
|
|
2599
1939
|
"react-dom": "^19.2.7",
|
|
2600
|
-
"react-icons": "^5.2.1",
|
|
2601
|
-
motion: "^12.0.0",
|
|
2602
1940
|
...getCssDependencies(cssFramework)
|
|
2603
1941
|
},
|
|
2604
1942
|
devDependencies: {
|
|
@@ -2903,47 +2241,14 @@ export type UserSchema = typeof userSchema;
|
|
|
2903
2241
|
content: FAVICON_BYTES
|
|
2904
2242
|
},
|
|
2905
2243
|
{
|
|
2906
|
-
path: "frontend/components/
|
|
2907
|
-
content:
|
|
2908
|
-
},
|
|
2909
|
-
{
|
|
2910
|
-
path: "frontend/components/CoralObstacle.tsx",
|
|
2911
|
-
content: CORAL_OBSTACLE_CODE
|
|
2912
|
-
},
|
|
2913
|
-
{
|
|
2914
|
-
path: "frontend/components/DeployModal.tsx",
|
|
2915
|
-
content: DEPLOY_MODAL_CODE
|
|
2916
|
-
},
|
|
2917
|
-
{
|
|
2918
|
-
path: "frontend/components/DocsModal.tsx",
|
|
2919
|
-
content: DOCS_MODAL_CODE
|
|
2244
|
+
path: "frontend/components/ui/.gitkeep",
|
|
2245
|
+
content: ""
|
|
2920
2246
|
},
|
|
2921
2247
|
{
|
|
2922
|
-
path: "frontend/components/
|
|
2923
|
-
content:
|
|
2248
|
+
path: "frontend/components/layout/.gitkeep",
|
|
2249
|
+
content: ""
|
|
2924
2250
|
}
|
|
2925
2251
|
];
|
|
2926
|
-
return processFilesForLanguage(files, projectConfig.useTypeScript);
|
|
2927
|
-
}
|
|
2928
|
-
function processFilesForLanguage(files, useTypeScript) {
|
|
2929
|
-
if (useTypeScript)
|
|
2930
|
-
return files;
|
|
2931
|
-
return files.map((file) => {
|
|
2932
|
-
if (typeof file.content === "string") {
|
|
2933
|
-
let path = file.path;
|
|
2934
|
-
if (path.endsWith(".tsx"))
|
|
2935
|
-
path = path.replace(".tsx", ".jsx");
|
|
2936
|
-
else if (path.endsWith(".ts"))
|
|
2937
|
-
path = path.replace(".ts", ".js");
|
|
2938
|
-
let code = file.content;
|
|
2939
|
-
code = code.replace(/^interface\s+\w+\s*\{[\s\S]*?\n\}\n/gm, "");
|
|
2940
|
-
code = code.replace(/^type\s+\w+\s*=[\s\S]*?;\n/gm, "");
|
|
2941
|
-
code = code.replace(/:\s*[A-Z]\w*(Props|State)\b/g, "");
|
|
2942
|
-
code = code.replace(/:\s*(Request|Response|Promise<[^>]+>|string|number|boolean|any|unknown)/g, "");
|
|
2943
|
-
return { ...file, path, content: code };
|
|
2944
|
-
}
|
|
2945
|
-
return file;
|
|
2946
|
-
});
|
|
2947
2252
|
}
|
|
2948
2253
|
function getBackendFiles(projectConfig) {
|
|
2949
2254
|
return [
|
|
@@ -3816,11 +3121,8 @@ declare global {
|
|
|
3816
3121
|
const useRef: typeof import("react").useRef;
|
|
3817
3122
|
const useState: typeof import("react").useState;
|
|
3818
3123
|
|
|
3819
|
-
const
|
|
3820
|
-
const
|
|
3821
|
-
const DeployModal: typeof import("./app/components/DeployModal").default;
|
|
3822
|
-
const DocsModal: typeof import("./app/components/DocsModal").default;
|
|
3823
|
-
const ShrimpCharacter: typeof import("./app/components/ShrimpCharacter").default;
|
|
3124
|
+
const ShrimpRunGame: typeof import("./app/components/shrimpRunGame").default;
|
|
3125
|
+
const RaktaShrimpMascot: typeof import("./app/components/raktaShrimpMascot").default;
|
|
3824
3126
|
}
|
|
3825
3127
|
|
|
3826
3128
|
declare module "react/jsx-runtime" {
|
|
@@ -3863,8 +3165,54 @@ export default function RootLayout({ children }: RootLayoutProps) {
|
|
|
3863
3165
|
}
|
|
3864
3166
|
`;
|
|
3865
3167
|
}
|
|
3866
|
-
function generateFrontendOnlyPage(
|
|
3867
|
-
return
|
|
3168
|
+
function generateFrontendOnlyPage(projectName) {
|
|
3169
|
+
return `import ShrimpRunGame from "./components/shrimpRunGame";
|
|
3170
|
+
|
|
3171
|
+
export default function HomePage() {
|
|
3172
|
+
return (
|
|
3173
|
+
<main className="mx-auto flex min-h-screen w-full max-w-4xl flex-col gap-6 px-4 py-16 sm:px-6 lg:px-8">
|
|
3174
|
+
<section className="rounded-3xl border border-white/10 bg-[#0e111a] p-8 shadow-2xl shadow-red-950/20">
|
|
3175
|
+
<p className="mb-2 text-xs font-bold uppercase tracking-[0.3em] text-red-600">
|
|
3176
|
+
THE RED ROUTER FRAMEWORK
|
|
3177
|
+
</p>
|
|
3178
|
+
<h1 className="mb-3 text-4xl font-extrabold leading-tight text-white md:text-6xl">
|
|
3179
|
+
Welcome to ${projectName}
|
|
3180
|
+
</h1>
|
|
3181
|
+
<p className="max-w-2xl text-base leading-7 text-slate-400">
|
|
3182
|
+
Built with Rakta.js \u2014 Small in size. Fierce in speed. Alive in every route.
|
|
3183
|
+
</p>
|
|
3184
|
+
|
|
3185
|
+
<div className="mt-6 flex flex-wrap gap-3">
|
|
3186
|
+
<click
|
|
3187
|
+
to="/offline"
|
|
3188
|
+
className="rounded-xl border border-red-500/40 px-4 py-2 text-sm font-semibold text-red-400 transition hover:border-red-400 hover:text-red-300"
|
|
3189
|
+
>
|
|
3190
|
+
Open offline page
|
|
3191
|
+
</click>
|
|
3192
|
+
<click
|
|
3193
|
+
to="/dashboard"
|
|
3194
|
+
className="rounded-xl bg-red-600 px-4 py-2 text-sm font-semibold text-white transition hover:bg-red-700"
|
|
3195
|
+
>
|
|
3196
|
+
View dashboard
|
|
3197
|
+
</click>
|
|
3198
|
+
</div>
|
|
3199
|
+
</section>
|
|
3200
|
+
|
|
3201
|
+
<section className="rounded-3xl border border-white/10 bg-[#0e111a] p-8 shadow-2xl shadow-red-950/20">
|
|
3202
|
+
<p className="mb-2 text-xs font-bold uppercase tracking-[0.3em] text-red-600">
|
|
3203
|
+
SHRIMPRUN
|
|
3204
|
+
</p>
|
|
3205
|
+
<h2 className="mb-1 text-2xl font-bold text-white">Play ShrimpRun</h2>
|
|
3206
|
+
<p className="mb-6 text-sm leading-6 text-slate-400">
|
|
3207
|
+
Press Space or click the game area to jump. Avoid the red obstacles!
|
|
3208
|
+
</p>
|
|
3209
|
+
|
|
3210
|
+
<ShrimpRunGame />
|
|
3211
|
+
</section>
|
|
3212
|
+
</main>
|
|
3213
|
+
);
|
|
3214
|
+
}
|
|
3215
|
+
`;
|
|
3868
3216
|
}
|
|
3869
3217
|
function generateFrontendOnlyLoading() {
|
|
3870
3218
|
return `export default function Loading() {
|
|
@@ -3934,8 +3282,585 @@ function generateFrontendOnlyNotFound() {
|
|
|
3934
3282
|
}
|
|
3935
3283
|
`;
|
|
3936
3284
|
}
|
|
3937
|
-
function
|
|
3938
|
-
return
|
|
3285
|
+
function generateShrimpMascotComponent() {
|
|
3286
|
+
return `interface RaktaShrimpMascotProps {
|
|
3287
|
+
readonly isJumping: boolean;
|
|
3288
|
+
readonly isDead: boolean;
|
|
3289
|
+
readonly style?: import("react").CSSProperties;
|
|
3290
|
+
}
|
|
3291
|
+
|
|
3292
|
+
/**
|
|
3293
|
+
* RaktaShrimpMascot \u2014 The animated shrimp hero of ShrimpRun.
|
|
3294
|
+
* Drawn entirely with inline SVG. No external assets required.
|
|
3295
|
+
*/
|
|
3296
|
+
export default function RaktaShrimpMascot({
|
|
3297
|
+
isJumping,
|
|
3298
|
+
isDead,
|
|
3299
|
+
style,
|
|
3300
|
+
}: RaktaShrimpMascotProps) {
|
|
3301
|
+
const bodyColor = isDead ? "#6b7280" : "#dc2626";
|
|
3302
|
+
const eyeColor = isDead ? "#374151" : "#fff";
|
|
3303
|
+
const legAnimation =
|
|
3304
|
+
isJumping || isDead ? "none" : "shrimpLegs 0.3s steps(2) infinite";
|
|
3305
|
+
|
|
3306
|
+
return (
|
|
3307
|
+
<svg
|
|
3308
|
+
viewBox="0 0 48 48"
|
|
3309
|
+
width="48"
|
|
3310
|
+
height="48"
|
|
3311
|
+
style={{ display: "block", ...style }}
|
|
3312
|
+
aria-label={
|
|
3313
|
+
isDead ? "dead shrimp" : isJumping ? "shrimp jumping" : "running shrimp"
|
|
3314
|
+
}
|
|
3315
|
+
role="img"
|
|
3316
|
+
>
|
|
3317
|
+
<style>{\`
|
|
3318
|
+
@keyframes shrimpLegs {
|
|
3319
|
+
0% { transform: translateY(0); }
|
|
3320
|
+
50% { transform: translateY(2px); }
|
|
3321
|
+
}
|
|
3322
|
+
\`}</style>
|
|
3323
|
+
|
|
3324
|
+
{/* Body */}
|
|
3325
|
+
<path
|
|
3326
|
+
d="M8 30 Q10 14 24 12 Q38 10 40 22 Q42 32 32 36 Q20 40 8 30Z"
|
|
3327
|
+
fill={bodyColor}
|
|
3328
|
+
/>
|
|
3329
|
+
|
|
3330
|
+
{/* Shell segments */}
|
|
3331
|
+
<path
|
|
3332
|
+
d="M12 28 Q16 20 24 18 Q30 17 34 22"
|
|
3333
|
+
stroke="#b91c1c"
|
|
3334
|
+
strokeWidth="1.5"
|
|
3335
|
+
fill="none"
|
|
3336
|
+
opacity={isDead ? 0.3 : 0.6}
|
|
3337
|
+
/>
|
|
3338
|
+
<path
|
|
3339
|
+
d="M14 32 Q19 24 28 22 Q33 21 36 26"
|
|
3340
|
+
stroke="#b91c1c"
|
|
3341
|
+
strokeWidth="1.5"
|
|
3342
|
+
fill="none"
|
|
3343
|
+
opacity={isDead ? 0.3 : 0.6}
|
|
3344
|
+
/>
|
|
3345
|
+
|
|
3346
|
+
{/* Eye */}
|
|
3347
|
+
<circle cx="34" cy="18" r="4" fill="#1e293b" />
|
|
3348
|
+
<circle cx="35" cy="17" r="2" fill={eyeColor} />
|
|
3349
|
+
|
|
3350
|
+
{isDead && (
|
|
3351
|
+
<>
|
|
3352
|
+
<line
|
|
3353
|
+
x1="32"
|
|
3354
|
+
y1="16"
|
|
3355
|
+
x2="36"
|
|
3356
|
+
y2="20"
|
|
3357
|
+
stroke="#374151"
|
|
3358
|
+
strokeWidth="1.5"
|
|
3359
|
+
/>
|
|
3360
|
+
<line
|
|
3361
|
+
x1="36"
|
|
3362
|
+
y1="16"
|
|
3363
|
+
x2="32"
|
|
3364
|
+
y2="20"
|
|
3365
|
+
stroke="#374151"
|
|
3366
|
+
strokeWidth="1.5"
|
|
3367
|
+
/>
|
|
3368
|
+
</>
|
|
3369
|
+
)}
|
|
3370
|
+
|
|
3371
|
+
{/* Antennae */}
|
|
3372
|
+
<line
|
|
3373
|
+
x1="34"
|
|
3374
|
+
y1="14"
|
|
3375
|
+
x2="40"
|
|
3376
|
+
y2="6"
|
|
3377
|
+
stroke={bodyColor}
|
|
3378
|
+
strokeWidth="1.5"
|
|
3379
|
+
strokeLinecap="round"
|
|
3380
|
+
/>
|
|
3381
|
+
<line
|
|
3382
|
+
x1="32"
|
|
3383
|
+
y1="13"
|
|
3384
|
+
x2="36"
|
|
3385
|
+
y2="4"
|
|
3386
|
+
stroke={bodyColor}
|
|
3387
|
+
strokeWidth="1.5"
|
|
3388
|
+
strokeLinecap="round"
|
|
3389
|
+
/>
|
|
3390
|
+
|
|
3391
|
+
{/* Tail */}
|
|
3392
|
+
<path
|
|
3393
|
+
d="M10 30 Q4 26 6 20"
|
|
3394
|
+
stroke={bodyColor}
|
|
3395
|
+
strokeWidth="2"
|
|
3396
|
+
fill="none"
|
|
3397
|
+
strokeLinecap="round"
|
|
3398
|
+
/>
|
|
3399
|
+
<path
|
|
3400
|
+
d="M10 30 Q2 30 4 36"
|
|
3401
|
+
stroke={bodyColor}
|
|
3402
|
+
strokeWidth="2"
|
|
3403
|
+
fill="none"
|
|
3404
|
+
strokeLinecap="round"
|
|
3405
|
+
/>
|
|
3406
|
+
<path
|
|
3407
|
+
d="M10 30 Q6 34 8 40"
|
|
3408
|
+
stroke={bodyColor}
|
|
3409
|
+
strokeWidth="2"
|
|
3410
|
+
fill="none"
|
|
3411
|
+
strokeLinecap="round"
|
|
3412
|
+
/>
|
|
3413
|
+
|
|
3414
|
+
{/* Legs */}
|
|
3415
|
+
<g
|
|
3416
|
+
style={{
|
|
3417
|
+
animation: legAnimation,
|
|
3418
|
+
transformOrigin: "24px 36px",
|
|
3419
|
+
}}
|
|
3420
|
+
>
|
|
3421
|
+
<line
|
|
3422
|
+
x1="18"
|
|
3423
|
+
y1="36"
|
|
3424
|
+
x2="14"
|
|
3425
|
+
y2="44"
|
|
3426
|
+
stroke={bodyColor}
|
|
3427
|
+
strokeWidth="1.5"
|
|
3428
|
+
strokeLinecap="round"
|
|
3429
|
+
/>
|
|
3430
|
+
<line
|
|
3431
|
+
x1="22"
|
|
3432
|
+
y1="38"
|
|
3433
|
+
x2="18"
|
|
3434
|
+
y2="46"
|
|
3435
|
+
stroke={bodyColor}
|
|
3436
|
+
strokeWidth="1.5"
|
|
3437
|
+
strokeLinecap="round"
|
|
3438
|
+
/>
|
|
3439
|
+
<line
|
|
3440
|
+
x1="26"
|
|
3441
|
+
y1="38"
|
|
3442
|
+
x2="24"
|
|
3443
|
+
y2="46"
|
|
3444
|
+
stroke={bodyColor}
|
|
3445
|
+
strokeWidth="1.5"
|
|
3446
|
+
strokeLinecap="round"
|
|
3447
|
+
/>
|
|
3448
|
+
<line
|
|
3449
|
+
x1="30"
|
|
3450
|
+
y1="37"
|
|
3451
|
+
x2="28"
|
|
3452
|
+
y2="45"
|
|
3453
|
+
stroke={bodyColor}
|
|
3454
|
+
strokeWidth="1.5"
|
|
3455
|
+
strokeLinecap="round"
|
|
3456
|
+
/>
|
|
3457
|
+
</g>
|
|
3458
|
+
</svg>
|
|
3459
|
+
);
|
|
3460
|
+
}
|
|
3461
|
+
`;
|
|
3462
|
+
}
|
|
3463
|
+
function generateShrimpRunGameComponent() {
|
|
3464
|
+
return `import { useCallback, useEffect, useRef, useState } from "react";
|
|
3465
|
+
import RaktaShrimpMascot from "./raktaShrimpMascot";
|
|
3466
|
+
// \u2500\u2500\u2500 Types \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3467
|
+
|
|
3468
|
+
type GameStatus = "idle" | "running" | "dead";
|
|
3469
|
+
|
|
3470
|
+
interface ObstacleState {
|
|
3471
|
+
readonly id: number;
|
|
3472
|
+
readonly xPosition: number;
|
|
3473
|
+
readonly width: number;
|
|
3474
|
+
readonly height: number;
|
|
3475
|
+
}
|
|
3476
|
+
|
|
3477
|
+
interface ShrimpState {
|
|
3478
|
+
readonly yPosition: number;
|
|
3479
|
+
readonly velocityY: number;
|
|
3480
|
+
readonly isJumping: boolean;
|
|
3481
|
+
}
|
|
3482
|
+
|
|
3483
|
+
// \u2500\u2500\u2500 Constants \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3484
|
+
|
|
3485
|
+
const CANVAS_WIDTH = 640;
|
|
3486
|
+
const CANVAS_HEIGHT = 160;
|
|
3487
|
+
const GROUND_STRIP_HEIGHT = 4;
|
|
3488
|
+
const SHRIMP_START_X = 60;
|
|
3489
|
+
const SHRIMP_WIDTH = 48;
|
|
3490
|
+
const SHRIMP_HEIGHT = 48;
|
|
3491
|
+
const GRAVITY = 1.4;
|
|
3492
|
+
const JUMP_VELOCITY = -18;
|
|
3493
|
+
const INITIAL_OBSTACLE_SPEED = 5;
|
|
3494
|
+
const SPEED_INCREMENT_PER_SCORE = 0.003;
|
|
3495
|
+
const OBSTACLE_SPAWN_INTERVAL_MS = 1600;
|
|
3496
|
+
const SCORE_TICK_MS = 80;
|
|
3497
|
+
const COLLISION_MARGIN = 8;
|
|
3498
|
+
const MAX_CONCURRENT_OBSTACLES = 3;
|
|
3499
|
+
const FRAME_SKIP_THRESHOLD_MS = 100;
|
|
3500
|
+
|
|
3501
|
+
// \u2500\u2500\u2500 Physics helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3502
|
+
|
|
3503
|
+
function getObstacleSpeed(currentScore: number): number {
|
|
3504
|
+
return INITIAL_OBSTACLE_SPEED + currentScore * SPEED_INCREMENT_PER_SCORE;
|
|
3505
|
+
}
|
|
3506
|
+
|
|
3507
|
+
function checkCollision(
|
|
3508
|
+
shrimpYPosition: number,
|
|
3509
|
+
obstacle: ObstacleState,
|
|
3510
|
+
): boolean {
|
|
3511
|
+
const shrimpLeft = SHRIMP_START_X + COLLISION_MARGIN;
|
|
3512
|
+
const shrimpRight = SHRIMP_START_X + SHRIMP_WIDTH - COLLISION_MARGIN;
|
|
3513
|
+
const shrimpTop =
|
|
3514
|
+
CANVAS_HEIGHT -
|
|
3515
|
+
GROUND_STRIP_HEIGHT -
|
|
3516
|
+
shrimpYPosition -
|
|
3517
|
+
SHRIMP_HEIGHT +
|
|
3518
|
+
COLLISION_MARGIN;
|
|
3519
|
+
const shrimpBottom = CANVAS_HEIGHT - GROUND_STRIP_HEIGHT - shrimpYPosition;
|
|
3520
|
+
|
|
3521
|
+
const obstacleLeft = obstacle.xPosition + COLLISION_MARGIN;
|
|
3522
|
+
const obstacleRight = obstacle.xPosition + obstacle.width - COLLISION_MARGIN;
|
|
3523
|
+
const obstacleTop = CANVAS_HEIGHT - GROUND_STRIP_HEIGHT - obstacle.height;
|
|
3524
|
+
const obstacleBottom = CANVAS_HEIGHT - GROUND_STRIP_HEIGHT;
|
|
3525
|
+
|
|
3526
|
+
return (
|
|
3527
|
+
shrimpLeft < obstacleRight &&
|
|
3528
|
+
shrimpRight > obstacleLeft &&
|
|
3529
|
+
shrimpTop < obstacleBottom &&
|
|
3530
|
+
shrimpBottom > obstacleTop
|
|
3531
|
+
);
|
|
3532
|
+
}
|
|
3533
|
+
|
|
3534
|
+
// \u2500\u2500\u2500 Component \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
3535
|
+
|
|
3536
|
+
/**
|
|
3537
|
+
* ShrimpRun \u2014 Default Rakta.js interactive starter game.
|
|
3538
|
+
*
|
|
3539
|
+
* Like the Chrome offline Dino game, but the dinosaur is an animated shrimp.
|
|
3540
|
+
* Press Space or click the game canvas to jump. Avoid the red obstacles!
|
|
3541
|
+
*
|
|
3542
|
+
* Features:
|
|
3543
|
+
* - React state only \u2014 no external game library
|
|
3544
|
+
* - requestAnimationFrame game loop
|
|
3545
|
+
* - Physics: gravity + jump velocity
|
|
3546
|
+
* - Score that increases over time
|
|
3547
|
+
* - Speed ramps up as score grows
|
|
3548
|
+
* - Collision detection with margin
|
|
3549
|
+
* - High score tracked in component state
|
|
3550
|
+
* - Keyboard (Space) and click/tap support
|
|
3551
|
+
* - Accessible button game canvas
|
|
3552
|
+
* - SVG shrimp mascot \u2014 no external assets
|
|
3553
|
+
*/
|
|
3554
|
+
export default function ShrimpRunGame() {
|
|
3555
|
+
const [gameStatus, setGameStatus] = useState<GameStatus>("idle");
|
|
3556
|
+
const [score, setScore] = useState(0);
|
|
3557
|
+
const [highScore, setHighScore] = useState(0);
|
|
3558
|
+
const [obstacles, setObstacles] = useState<ObstacleState[]>([]);
|
|
3559
|
+
const [shrimp, setShrimp] = useState<ShrimpState>({
|
|
3560
|
+
yPosition: 0,
|
|
3561
|
+
velocityY: 0,
|
|
3562
|
+
isJumping: false,
|
|
3563
|
+
});
|
|
3564
|
+
|
|
3565
|
+
const gameStatusRef = useRef<GameStatus>("idle");
|
|
3566
|
+
const scoreRef = useRef(0);
|
|
3567
|
+
const shrimpRef = useRef<ShrimpState>({
|
|
3568
|
+
yPosition: 0,
|
|
3569
|
+
velocityY: 0,
|
|
3570
|
+
isJumping: false,
|
|
3571
|
+
});
|
|
3572
|
+
const obstaclesRef = useRef<ObstacleState[]>([]);
|
|
3573
|
+
const obstacleIdRef = useRef(0);
|
|
3574
|
+
const animationFrameRef = useRef<number>(0);
|
|
3575
|
+
const lastObstacleTimeRef = useRef(0);
|
|
3576
|
+
const lastScoreTickRef = useRef(0);
|
|
3577
|
+
|
|
3578
|
+
const jump = useCallback((): void => {
|
|
3579
|
+
if (gameStatusRef.current === "dead") {
|
|
3580
|
+
return;
|
|
3581
|
+
}
|
|
3582
|
+
|
|
3583
|
+
if (gameStatusRef.current === "idle") {
|
|
3584
|
+
gameStatusRef.current = "running";
|
|
3585
|
+
setGameStatus("running");
|
|
3586
|
+
}
|
|
3587
|
+
|
|
3588
|
+
if (!shrimpRef.current.isJumping) {
|
|
3589
|
+
const nextShrimp: ShrimpState = {
|
|
3590
|
+
...shrimpRef.current,
|
|
3591
|
+
velocityY: JUMP_VELOCITY,
|
|
3592
|
+
isJumping: true,
|
|
3593
|
+
};
|
|
3594
|
+
|
|
3595
|
+
shrimpRef.current = nextShrimp;
|
|
3596
|
+
setShrimp(nextShrimp);
|
|
3597
|
+
}
|
|
3598
|
+
}, []);
|
|
3599
|
+
|
|
3600
|
+
const resetGame = useCallback((): void => {
|
|
3601
|
+
const freshShrimp: ShrimpState = {
|
|
3602
|
+
yPosition: 0,
|
|
3603
|
+
velocityY: 0,
|
|
3604
|
+
isJumping: false,
|
|
3605
|
+
};
|
|
3606
|
+
|
|
3607
|
+
shrimpRef.current = freshShrimp;
|
|
3608
|
+
obstaclesRef.current = [];
|
|
3609
|
+
obstacleIdRef.current = 0;
|
|
3610
|
+
scoreRef.current = 0;
|
|
3611
|
+
gameStatusRef.current = "idle";
|
|
3612
|
+
lastObstacleTimeRef.current = 0;
|
|
3613
|
+
lastScoreTickRef.current = 0;
|
|
3614
|
+
|
|
3615
|
+
setShrimp(freshShrimp);
|
|
3616
|
+
setObstacles([]);
|
|
3617
|
+
setScore(0);
|
|
3618
|
+
setGameStatus("idle");
|
|
3619
|
+
}, []);
|
|
3620
|
+
|
|
3621
|
+
useEffect(() => {
|
|
3622
|
+
let previousTimestamp = 0;
|
|
3623
|
+
|
|
3624
|
+
function gameTick(timestamp: number): void {
|
|
3625
|
+
if (gameStatusRef.current !== "running") {
|
|
3626
|
+
animationFrameRef.current = requestAnimationFrame(gameTick);
|
|
3627
|
+
return;
|
|
3628
|
+
}
|
|
3629
|
+
|
|
3630
|
+
const deltaTime = timestamp - previousTimestamp;
|
|
3631
|
+
previousTimestamp = timestamp;
|
|
3632
|
+
|
|
3633
|
+
if (deltaTime > FRAME_SKIP_THRESHOLD_MS) {
|
|
3634
|
+
animationFrameRef.current = requestAnimationFrame(gameTick);
|
|
3635
|
+
return;
|
|
3636
|
+
}
|
|
3637
|
+
|
|
3638
|
+
const currentShrimp = shrimpRef.current;
|
|
3639
|
+
let nextVelocityY = currentShrimp.velocityY + GRAVITY;
|
|
3640
|
+
let nextYPosition = currentShrimp.yPosition - nextVelocityY;
|
|
3641
|
+
|
|
3642
|
+
if (nextYPosition <= 0) {
|
|
3643
|
+
nextYPosition = 0;
|
|
3644
|
+
nextVelocityY = 0;
|
|
3645
|
+
}
|
|
3646
|
+
|
|
3647
|
+
const nextShrimp: ShrimpState = {
|
|
3648
|
+
yPosition: nextYPosition,
|
|
3649
|
+
velocityY: nextVelocityY,
|
|
3650
|
+
isJumping: nextYPosition > 0,
|
|
3651
|
+
};
|
|
3652
|
+
|
|
3653
|
+
shrimpRef.current = nextShrimp;
|
|
3654
|
+
setShrimp(nextShrimp);
|
|
3655
|
+
|
|
3656
|
+
const obstacleSpeed = getObstacleSpeed(scoreRef.current);
|
|
3657
|
+
|
|
3658
|
+
const movedObstacles = obstaclesRef.current
|
|
3659
|
+
.map(
|
|
3660
|
+
(obstacle): ObstacleState => ({
|
|
3661
|
+
...obstacle,
|
|
3662
|
+
xPosition: obstacle.xPosition - obstacleSpeed,
|
|
3663
|
+
}),
|
|
3664
|
+
)
|
|
3665
|
+
.filter((obstacle) => obstacle.xPosition + obstacle.width > -10);
|
|
3666
|
+
|
|
3667
|
+
if (
|
|
3668
|
+
timestamp - lastObstacleTimeRef.current > OBSTACLE_SPAWN_INTERVAL_MS &&
|
|
3669
|
+
movedObstacles.length < MAX_CONCURRENT_OBSTACLES
|
|
3670
|
+
) {
|
|
3671
|
+
const obstacleHeight = 30 + Math.floor(Math.random() * 30);
|
|
3672
|
+
const obstacleWidth = 20 + Math.floor(Math.random() * 20);
|
|
3673
|
+
|
|
3674
|
+
movedObstacles.push({
|
|
3675
|
+
id: obstacleIdRef.current,
|
|
3676
|
+
xPosition: CANVAS_WIDTH + 20,
|
|
3677
|
+
width: obstacleWidth,
|
|
3678
|
+
height: obstacleHeight,
|
|
3679
|
+
});
|
|
3680
|
+
|
|
3681
|
+
obstacleIdRef.current += 1;
|
|
3682
|
+
lastObstacleTimeRef.current = timestamp;
|
|
3683
|
+
}
|
|
3684
|
+
|
|
3685
|
+
obstaclesRef.current = movedObstacles;
|
|
3686
|
+
setObstacles([...movedObstacles]);
|
|
3687
|
+
|
|
3688
|
+
for (const obstacle of movedObstacles) {
|
|
3689
|
+
if (checkCollision(nextShrimp.yPosition, obstacle)) {
|
|
3690
|
+
gameStatusRef.current = "dead";
|
|
3691
|
+
setGameStatus("dead");
|
|
3692
|
+
setHighScore((previousHighScore: number) =>
|
|
3693
|
+
Math.max(previousHighScore, scoreRef.current),
|
|
3694
|
+
);
|
|
3695
|
+
animationFrameRef.current = requestAnimationFrame(gameTick);
|
|
3696
|
+
return;
|
|
3697
|
+
}
|
|
3698
|
+
}
|
|
3699
|
+
|
|
3700
|
+
if (timestamp - lastScoreTickRef.current > SCORE_TICK_MS) {
|
|
3701
|
+
scoreRef.current += 1;
|
|
3702
|
+
setScore(scoreRef.current);
|
|
3703
|
+
lastScoreTickRef.current = timestamp;
|
|
3704
|
+
}
|
|
3705
|
+
|
|
3706
|
+
animationFrameRef.current = requestAnimationFrame(gameTick);
|
|
3707
|
+
}
|
|
3708
|
+
|
|
3709
|
+
animationFrameRef.current = requestAnimationFrame(gameTick);
|
|
3710
|
+
|
|
3711
|
+
return () => {
|
|
3712
|
+
cancelAnimationFrame(animationFrameRef.current);
|
|
3713
|
+
};
|
|
3714
|
+
}, []);
|
|
3715
|
+
|
|
3716
|
+
useEffect(() => {
|
|
3717
|
+
function handleKeyDown(keyboardEvent: KeyboardEvent): void {
|
|
3718
|
+
if (keyboardEvent.code === "Space") {
|
|
3719
|
+
keyboardEvent.preventDefault();
|
|
3720
|
+
jump();
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
|
|
3724
|
+
window.addEventListener("keydown", handleKeyDown);
|
|
3725
|
+
|
|
3726
|
+
return () => {
|
|
3727
|
+
window.removeEventListener("keydown", handleKeyDown);
|
|
3728
|
+
};
|
|
3729
|
+
}, [jump]);
|
|
3730
|
+
|
|
3731
|
+
const shrimpBottomOffset = GROUND_STRIP_HEIGHT + shrimp.yPosition;
|
|
3732
|
+
const isDead = gameStatus === "dead";
|
|
3733
|
+
const isIdle = gameStatus === "idle";
|
|
3734
|
+
const isRunning = gameStatus === "running";
|
|
3735
|
+
|
|
3736
|
+
return (
|
|
3737
|
+
<div className="flex flex-col items-start gap-4 py-4">
|
|
3738
|
+
<div className="flex flex-wrap items-center gap-8">
|
|
3739
|
+
<span className="font-mono text-xl font-bold tabular-nums text-red-600">
|
|
3740
|
+
Score: {score}
|
|
3741
|
+
</span>
|
|
3742
|
+
{highScore > 0 && (
|
|
3743
|
+
<span className="text-sm text-slate-400">Best: {highScore}</span>
|
|
3744
|
+
)}
|
|
3745
|
+
</div>
|
|
3746
|
+
|
|
3747
|
+
<button
|
|
3748
|
+
type="button"
|
|
3749
|
+
className="relative block max-w-full cursor-pointer select-none overflow-hidden rounded-2xl border-2 border-red-600/30 bg-[#0e111a] p-0 text-left focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500"
|
|
3750
|
+
aria-label="ShrimpRun game area. Click or press Space to jump."
|
|
3751
|
+
onClick={jump}
|
|
3752
|
+
onKeyDown={(keyboardEvent: import("react").KeyboardEvent) => {
|
|
3753
|
+
if (keyboardEvent.code === "Space") {
|
|
3754
|
+
keyboardEvent.preventDefault();
|
|
3755
|
+
jump();
|
|
3756
|
+
}
|
|
3757
|
+
}}
|
|
3758
|
+
style={{
|
|
3759
|
+
width: CANVAS_WIDTH,
|
|
3760
|
+
height: CANVAS_HEIGHT,
|
|
3761
|
+
}}
|
|
3762
|
+
>
|
|
3763
|
+
<span
|
|
3764
|
+
className="absolute bottom-0 left-0 w-full rounded-sm bg-red-600"
|
|
3765
|
+
style={{
|
|
3766
|
+
height: GROUND_STRIP_HEIGHT,
|
|
3767
|
+
}}
|
|
3768
|
+
/>
|
|
3769
|
+
|
|
3770
|
+
<span
|
|
3771
|
+
className="absolute shrimp-sprite"
|
|
3772
|
+
style={{
|
|
3773
|
+
left: SHRIMP_START_X,
|
|
3774
|
+
bottom: shrimpBottomOffset,
|
|
3775
|
+
width: SHRIMP_WIDTH,
|
|
3776
|
+
height: SHRIMP_HEIGHT,
|
|
3777
|
+
transform: isDead
|
|
3778
|
+
? "rotate(18deg) translateY(8px)"
|
|
3779
|
+
: shrimp.isJumping
|
|
3780
|
+
? "rotate(-8deg) translateY(-4px)"
|
|
3781
|
+
: "rotate(0deg)",
|
|
3782
|
+
transition: "transform 120ms ease",
|
|
3783
|
+
}}
|
|
3784
|
+
>
|
|
3785
|
+
<RaktaShrimpMascot isJumping={shrimp.isJumping} isDead={isDead} />
|
|
3786
|
+
</span>
|
|
3787
|
+
|
|
3788
|
+
{obstacles.map((obstacle) => (
|
|
3789
|
+
<span
|
|
3790
|
+
key={obstacle.id}
|
|
3791
|
+
className="absolute shrimp-run-obstacle"
|
|
3792
|
+
style={{
|
|
3793
|
+
left: obstacle.xPosition,
|
|
3794
|
+
bottom: GROUND_STRIP_HEIGHT,
|
|
3795
|
+
width: obstacle.width,
|
|
3796
|
+
height: obstacle.height,
|
|
3797
|
+
}}
|
|
3798
|
+
/>
|
|
3799
|
+
))}
|
|
3800
|
+
|
|
3801
|
+
{isIdle && (
|
|
3802
|
+
<span className="pointer-events-none absolute inset-0 flex items-center justify-center text-sm text-slate-400">
|
|
3803
|
+
Press Space or click to start
|
|
3804
|
+
</span>
|
|
3805
|
+
)}
|
|
3806
|
+
|
|
3807
|
+
{isDead && (
|
|
3808
|
+
<span className="absolute inset-0 flex flex-col items-center justify-center gap-2 bg-black/75">
|
|
3809
|
+
<span className="text-lg font-bold tracking-widest text-red-600">
|
|
3810
|
+
GAME OVER
|
|
3811
|
+
</span>
|
|
3812
|
+
<span className="text-sm text-slate-400">Score: {score}</span>
|
|
3813
|
+
</span>
|
|
3814
|
+
)}
|
|
3815
|
+
|
|
3816
|
+
{isRunning && score > 0 && score % 50 === 0 && (
|
|
3817
|
+
<span className="absolute right-3 top-2 text-xs font-bold tracking-widest text-red-600 opacity-80">
|
|
3818
|
+
{score}!
|
|
3819
|
+
</span>
|
|
3820
|
+
)}
|
|
3821
|
+
</button>
|
|
3822
|
+
|
|
3823
|
+
<p className="min-h-5 text-sm text-slate-400">
|
|
3824
|
+
{isIdle && "\uD83E\uDD90 Click or press Space to make the shrimp jump!"}
|
|
3825
|
+
{isRunning && "\uD83E\uDD90 Don't hit the obstacles!"}
|
|
3826
|
+
{isDead && "The shrimp got cooked. Try again!"}
|
|
3827
|
+
</p>
|
|
3828
|
+
|
|
3829
|
+
{isDead && (
|
|
3830
|
+
<button
|
|
3831
|
+
type="button"
|
|
3832
|
+
className="w-fit rounded-lg bg-red-600 px-6 py-2 font-semibold text-white transition hover:bg-red-700 active:bg-red-800 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500"
|
|
3833
|
+
onClick={resetGame}
|
|
3834
|
+
>
|
|
3835
|
+
Restart
|
|
3836
|
+
</button>
|
|
3837
|
+
)}
|
|
3838
|
+
</div>
|
|
3839
|
+
);
|
|
3840
|
+
}
|
|
3841
|
+
`;
|
|
3842
|
+
}
|
|
3843
|
+
function generateFullstackHomePage(projectName) {
|
|
3844
|
+
return `import React from "react";
|
|
3845
|
+
|
|
3846
|
+
export default function HomePage() {
|
|
3847
|
+
return (
|
|
3848
|
+
<main className="page-shell">
|
|
3849
|
+
<section className="hero-card">
|
|
3850
|
+
<p className="eyebrow">THE RED ROUTER FRAMEWORK</p>
|
|
3851
|
+
<h1>Welcome to ${projectName}</h1>
|
|
3852
|
+
<p>
|
|
3853
|
+
Built with Rakta.js \u2014 Small in size. Fierce in speed. Alive in every route.
|
|
3854
|
+
</p>
|
|
3855
|
+
<div className="button-row">
|
|
3856
|
+
<a href="/about">About</a>
|
|
3857
|
+
<a href="/blog">Blog</a>
|
|
3858
|
+
</div>
|
|
3859
|
+
</section>
|
|
3860
|
+
</main>
|
|
3861
|
+
);
|
|
3862
|
+
}
|
|
3863
|
+
`;
|
|
3939
3864
|
}
|
|
3940
3865
|
function getDatabaseDependencies(selectedDatabase) {
|
|
3941
3866
|
switch (selectedDatabase) {
|
|
@@ -4249,13 +4174,6 @@ async function promptProjectMode() {
|
|
|
4249
4174
|
});
|
|
4250
4175
|
return getPromptValue(promptResult);
|
|
4251
4176
|
}
|
|
4252
|
-
async function promptTypeScript() {
|
|
4253
|
-
const promptResult = await confirm({
|
|
4254
|
-
message: "Would you like to use TypeScript? (recommended)",
|
|
4255
|
-
initialValue: true
|
|
4256
|
-
});
|
|
4257
|
-
return getPromptValue(promptResult);
|
|
4258
|
-
}
|
|
4259
4177
|
async function promptCssFramework() {
|
|
4260
4178
|
const promptResult = await select({
|
|
4261
4179
|
message: "Choose a CSS framework:",
|
|
@@ -4394,14 +4312,12 @@ async function promptDatabase() {
|
|
|
4394
4312
|
}
|
|
4395
4313
|
async function runPrompts(projectName) {
|
|
4396
4314
|
const projectMode = await promptProjectMode();
|
|
4397
|
-
const useTypeScript = await promptTypeScript();
|
|
4398
4315
|
const cssFramework = await promptCssFramework();
|
|
4399
4316
|
const renderMode = await promptRenderMode();
|
|
4400
4317
|
if (projectMode === "frontend-only") {
|
|
4401
4318
|
return {
|
|
4402
4319
|
projectName,
|
|
4403
4320
|
projectMode,
|
|
4404
|
-
useTypeScript,
|
|
4405
4321
|
cssFramework,
|
|
4406
4322
|
renderMode,
|
|
4407
4323
|
backendFramework: "gaman",
|
|
@@ -4413,7 +4329,6 @@ async function runPrompts(projectName) {
|
|
|
4413
4329
|
return {
|
|
4414
4330
|
projectName,
|
|
4415
4331
|
projectMode,
|
|
4416
|
-
useTypeScript,
|
|
4417
4332
|
cssFramework,
|
|
4418
4333
|
renderMode,
|
|
4419
4334
|
backendFramework,
|
|
@@ -4431,21 +4346,21 @@ function getProjectNameFromArgs(cliArgs) {
|
|
|
4431
4346
|
}
|
|
4432
4347
|
function formatFullstackCommands() {
|
|
4433
4348
|
return [
|
|
4434
|
-
import_picocolors.default.dim("# Terminal 1")
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4349
|
+
`${import_picocolors.default.dim("# Terminal 1")}`,
|
|
4350
|
+
"cd frontend",
|
|
4351
|
+
"bun install",
|
|
4352
|
+
"bun run dev",
|
|
4438
4353
|
"",
|
|
4439
|
-
import_picocolors.default.dim("# Terminal 2")
|
|
4440
|
-
|
|
4441
|
-
|
|
4442
|
-
|
|
4443
|
-
].
|
|
4444
|
-
`);
|
|
4354
|
+
`${import_picocolors.default.dim("# Terminal 2")}`,
|
|
4355
|
+
"cd backend",
|
|
4356
|
+
"bun install",
|
|
4357
|
+
"bun run dev"
|
|
4358
|
+
].join(`
|
|
4359
|
+
`);
|
|
4445
4360
|
}
|
|
4446
4361
|
function formatFrontendOnlyCommands(projectName) {
|
|
4447
|
-
return [`cd ${projectName}`, "bun install", "bun run dev"].
|
|
4448
|
-
`);
|
|
4362
|
+
return [`cd ${projectName}`, "bun install", "bun run dev"].join(`
|
|
4363
|
+
`);
|
|
4449
4364
|
}
|
|
4450
4365
|
function printSuccessMessage(projectConfig) {
|
|
4451
4366
|
const modeLabel = PROJECT_MODE_DISPLAY[projectConfig.projectMode];
|
|
@@ -4466,7 +4381,7 @@ function printSuccessMessage(projectConfig) {
|
|
|
4466
4381
|
|
|
4467
4382
|
${import_picocolors.default.bold("Next steps:")}
|
|
4468
4383
|
|
|
4469
|
-
${nextSteps}
|
|
4384
|
+
${nextSteps}
|
|
4470
4385
|
|
|
4471
4386
|
${import_picocolors.default.bold("Frontend:")} ${import_picocolors.default.cyan("http://localhost:3000")}
|
|
4472
4387
|
${isFullstack ? `${import_picocolors.default.bold("Backend:")} ${import_picocolors.default.cyan("http://localhost:4000")}` : ""}
|
|
@@ -4506,4 +4421,4 @@ Error: ${errorMessage}
|
|
|
4506
4421
|
process.exit(1);
|
|
4507
4422
|
});
|
|
4508
4423
|
|
|
4509
|
-
//# debugId=
|
|
4424
|
+
//# debugId=5A42EB5C435F19ED64756E2164756E21
|