create-rakta-app 0.1.7 → 0.1.9
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 +831 -1169
- package/dist/index.js.map +8 -8
- package/package.json +1 -1
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`,
|
|
@@ -1719,313 +1666,131 @@ function writeProjectFiles(projectRoot, generatedFiles) {
|
|
|
1719
1666
|
// src/generator.ts
|
|
1720
1667
|
import { readFileSync } from "fs";
|
|
1721
1668
|
|
|
1722
|
-
// src/
|
|
1723
|
-
var
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
none: "None"
|
|
1728
|
-
};
|
|
1729
|
-
var BACKEND_DISPLAY = {
|
|
1730
|
-
gaman: "Gaman.js",
|
|
1731
|
-
express: "Express.js",
|
|
1732
|
-
nest: "Nest.js",
|
|
1733
|
-
adonis: "Adonis.js"
|
|
1734
|
-
};
|
|
1735
|
-
var DATABASE_DISPLAY = {
|
|
1736
|
-
postgresql: "PostgreSQL",
|
|
1737
|
-
mysql: "MySQL",
|
|
1738
|
-
mongodb: "MongoDB",
|
|
1739
|
-
firebase: "Firebase",
|
|
1740
|
-
sqlite: "SQLite",
|
|
1741
|
-
mariadb: "MariaDB",
|
|
1742
|
-
redis: "Redis",
|
|
1743
|
-
planetscale: "PlanetScale",
|
|
1744
|
-
neon: "Neon",
|
|
1745
|
-
turso: "Turso"
|
|
1746
|
-
};
|
|
1747
|
-
var RENDER_MODE_DISPLAY = {
|
|
1748
|
-
csr: "CSR \u2014 Client-Side Rendering",
|
|
1749
|
-
ssr: "SSR \u2014 Server-Side Rendering",
|
|
1750
|
-
ssg: "SSG \u2014 Static Site Generation",
|
|
1751
|
-
csg: "CSG \u2014 Configurable Static Generation",
|
|
1752
|
-
spa: "SPA \u2014 Single Page Application",
|
|
1753
|
-
hybrid: "Hybrid \u2014 Mixed per-route"
|
|
1754
|
-
};
|
|
1755
|
-
var PROJECT_MODE_DISPLAY = {
|
|
1756
|
-
fullstack: "Fullstack app (frontend + backend + database)",
|
|
1757
|
-
"frontend-only": "Frontend only (no backend, no database)"
|
|
1758
|
-
};
|
|
1759
|
-
|
|
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;
|
|
1669
|
+
// src/starter.ts
|
|
1670
|
+
var STARTER_TYPES_CODE = `export interface SystemMetric {
|
|
1671
|
+
name: string;
|
|
1672
|
+
value: string | number;
|
|
1673
|
+
status: "nominal" | "warning" | "critical";
|
|
1770
1674
|
}
|
|
1771
1675
|
|
|
1772
|
-
|
|
1773
|
-
type SetComponentState = (state: ComponentState) => void;
|
|
1774
|
-
|
|
1775
|
-
interface ComponentItem {
|
|
1776
|
-
id: string;
|
|
1676
|
+
export interface GameHighScore {
|
|
1777
1677
|
name: string;
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
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
|
-
];
|
|
1678
|
+
score: number;
|
|
1679
|
+
date: string;
|
|
1680
|
+
}
|
|
1911
1681
|
|
|
1912
|
-
export
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
const [copiedId, setCopiedId] = useState<string | null>(null);
|
|
1682
|
+
export type AestheticUnit = "LENIS-MODERN" | "RETRO-CYBER" | "NEO-BRUTALIST";
|
|
1683
|
+
`;
|
|
1684
|
+
var STARTER_AUDIO_CODE = `let audioContext: AudioContext | null = null;
|
|
1916
1685
|
|
|
1917
|
-
|
|
1686
|
+
function getAudioContext(): AudioContext | null {
|
|
1687
|
+
if (typeof window === "undefined") return null;
|
|
1918
1688
|
|
|
1919
|
-
const
|
|
1920
|
-
if (!
|
|
1689
|
+
const AudioContextCtor = window.AudioContext ?? window.webkitAudioContext;
|
|
1690
|
+
if (!AudioContextCtor) return null;
|
|
1921
1691
|
|
|
1922
|
-
|
|
1923
|
-
|
|
1692
|
+
audioContext ??= new AudioContextCtor();
|
|
1693
|
+
return audioContext;
|
|
1694
|
+
}
|
|
1924
1695
|
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
[activeCompId]: newState
|
|
1929
|
-
});
|
|
1930
|
-
};
|
|
1696
|
+
function playTone(frequency: number, durationMs: number, type: OscillatorType): void {
|
|
1697
|
+
const context = getAudioContext();
|
|
1698
|
+
if (!context) return;
|
|
1931
1699
|
|
|
1932
|
-
const
|
|
1933
|
-
|
|
1934
|
-
setCopiedId(activeCompId);
|
|
1935
|
-
setTimeout(() => setCopiedId(null), 2000);
|
|
1936
|
-
};
|
|
1700
|
+
const oscillator = context.createOscillator();
|
|
1701
|
+
const gain = context.createGain();
|
|
1937
1702
|
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
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>
|
|
1703
|
+
oscillator.type = type;
|
|
1704
|
+
oscillator.frequency.setValueAtTime(frequency, context.currentTime);
|
|
1705
|
+
gain.gain.setValueAtTime(0.045, context.currentTime);
|
|
1706
|
+
gain.gain.exponentialRampToValueAtTime(0.001, context.currentTime + durationMs / 1000);
|
|
1962
1707
|
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
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>
|
|
1708
|
+
oscillator.connect(gain);
|
|
1709
|
+
gain.connect(context.destination);
|
|
1710
|
+
oscillator.start();
|
|
1711
|
+
oscillator.stop(context.currentTime + durationMs / 1000);
|
|
1712
|
+
}
|
|
1983
1713
|
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
<p className="text-sm text-gray-400 mt-1">{activeComp.description}</p>
|
|
1988
|
-
</div>
|
|
1714
|
+
export function playJumpSound(): void {
|
|
1715
|
+
playTone(560, 90, "triangle");
|
|
1716
|
+
}
|
|
1989
1717
|
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1718
|
+
export function playScoreSound(): void {
|
|
1719
|
+
playTone(880, 120, "square");
|
|
1720
|
+
}
|
|
1721
|
+
`;
|
|
1722
|
+
var STARTER_SHRIMP_CHARACTER_CODE = `interface ShrimpCharacterProps {
|
|
1723
|
+
status: "IDLE" | "SWIMMING" | "JUMPING" | "DEAD";
|
|
1724
|
+
playerY: number;
|
|
1725
|
+
rotation?: number;
|
|
1726
|
+
}
|
|
1994
1727
|
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
1728
|
+
export default function ShrimpCharacter({
|
|
1729
|
+
status,
|
|
1730
|
+
playerY,
|
|
1731
|
+
rotation,
|
|
1732
|
+
}: ShrimpCharacterProps) {
|
|
1733
|
+
const isDead = status === "DEAD";
|
|
1734
|
+
const isSwimming = status === "SWIMMING";
|
|
1735
|
+
const finalRotation = rotation ?? (isDead ? 180 : status === "JUMPING" ? -10 : 0);
|
|
1736
|
+
const shrimpColor = isDead ? "fill-orange-400" : "fill-rose-500";
|
|
1737
|
+
|
|
1738
|
+
return (
|
|
1739
|
+
<div
|
|
1740
|
+
className="relative flex h-16 w-16 select-none items-center justify-center transition-transform duration-75"
|
|
1741
|
+
style={{ transform: \`translateY(\${-playerY}px) rotate(\${finalRotation}deg)\` }}
|
|
1742
|
+
>
|
|
1743
|
+
<svg
|
|
1744
|
+
aria-label={isDead ? "Shrimp failed the run" : "Rakta shrimp runner"}
|
|
1745
|
+
role="img"
|
|
1746
|
+
viewBox="0 0 100 100"
|
|
1747
|
+
className={\`h-full w-full drop-shadow-[0_4px_12px_rgba(244,63,94,0.32)] \${isSwimming ? "animate-pulse" : ""}\`}
|
|
1748
|
+
>
|
|
1749
|
+
<path
|
|
1750
|
+
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"
|
|
1751
|
+
className={shrimpColor}
|
|
1752
|
+
/>
|
|
1753
|
+
<path
|
|
1754
|
+
d="M 55 30 C 65 24, 76 34, 74 46 C 72 54, 62 55, 55 48 Z"
|
|
1755
|
+
className={shrimpColor}
|
|
1756
|
+
/>
|
|
1757
|
+
<path
|
|
1758
|
+
d="M 29 51 C 21 57, 16 61, 10 62"
|
|
1759
|
+
stroke={isDead ? "#fb923c" : "#f43f5e"}
|
|
1760
|
+
strokeWidth="4"
|
|
1761
|
+
strokeLinecap="round"
|
|
1762
|
+
fill="none"
|
|
1763
|
+
/>
|
|
1764
|
+
<path
|
|
1765
|
+
d="M 31 55 C 25 64, 22 70, 17 76"
|
|
1766
|
+
stroke={isDead ? "#fb923c" : "#f43f5e"}
|
|
1767
|
+
strokeWidth="3"
|
|
1768
|
+
strokeLinecap="round"
|
|
1769
|
+
fill="none"
|
|
1770
|
+
/>
|
|
1771
|
+
<circle cx="66" cy="38" r="4.5" fill={isDead ? "#18181b" : "#000"} />
|
|
1772
|
+
{!isDead && <circle cx="67.5" cy="36.5" r="1.2" fill="#fff" />}
|
|
1773
|
+
<path
|
|
1774
|
+
d="M 62 30 C 70 21, 82 19, 90 24"
|
|
1775
|
+
stroke="#fb7185"
|
|
1776
|
+
strokeWidth="2"
|
|
1777
|
+
strokeLinecap="round"
|
|
1778
|
+
fill="none"
|
|
1779
|
+
/>
|
|
1780
|
+
<path
|
|
1781
|
+
d="M 63 33 C 73 29, 84 31, 91 39"
|
|
1782
|
+
stroke="#fb7185"
|
|
1783
|
+
strokeWidth="2"
|
|
1784
|
+
strokeLinecap="round"
|
|
1785
|
+
fill="none"
|
|
1786
|
+
/>
|
|
1787
|
+
</svg>
|
|
2023
1788
|
</div>
|
|
2024
1789
|
);
|
|
2025
1790
|
}
|
|
2026
1791
|
`;
|
|
2027
|
-
var
|
|
2028
|
-
position:
|
|
1792
|
+
var STARTER_CORAL_OBSTACLE_CODE = `interface CoralObstacleProps {
|
|
1793
|
+
position: "TOP" | "BOTTOM";
|
|
2029
1794
|
height: number;
|
|
2030
1795
|
width?: number;
|
|
2031
1796
|
paletteIndex?: number;
|
|
@@ -2033,329 +1798,542 @@ var CORAL_OBSTACLE_CODE = `interface CoralObstacleProps {
|
|
|
2033
1798
|
scaleX?: number;
|
|
2034
1799
|
}
|
|
2035
1800
|
|
|
2036
|
-
export default function CoralObstacle({
|
|
2037
|
-
position,
|
|
2038
|
-
height,
|
|
1801
|
+
export default function CoralObstacle({
|
|
1802
|
+
position,
|
|
1803
|
+
height,
|
|
2039
1804
|
width = 64,
|
|
2040
|
-
paletteIndex = 0,
|
|
2041
|
-
variant = 0,
|
|
2042
|
-
scaleX = 1
|
|
1805
|
+
paletteIndex = 0,
|
|
1806
|
+
variant = 0,
|
|
1807
|
+
scaleX = 1,
|
|
2043
1808
|
}: CoralObstacleProps) {
|
|
2044
|
-
const
|
|
1809
|
+
const fallbackPalette = {
|
|
1810
|
+
primary: "#f43f5e",
|
|
1811
|
+
secondary: "#fda4af",
|
|
1812
|
+
shadow: "#9f1239",
|
|
1813
|
+
polyps: "#ffe4e6",
|
|
1814
|
+
name: "rose",
|
|
1815
|
+
};
|
|
2045
1816
|
const palettes = [
|
|
2046
|
-
|
|
2047
|
-
{ primary:
|
|
2048
|
-
{ primary:
|
|
2049
|
-
{ primary:
|
|
2050
|
-
{ primary:
|
|
1817
|
+
fallbackPalette,
|
|
1818
|
+
{ primary: "#06b6d4", secondary: "#67e8f9", shadow: "#155e75", polyps: "#ecfeff", name: "cyan" },
|
|
1819
|
+
{ primary: "#a855f7", secondary: "#d8b4fe", shadow: "#581c87", polyps: "#faf5ff", name: "amethyst" },
|
|
1820
|
+
{ primary: "#f59e0b", secondary: "#fde047", shadow: "#78350f", polyps: "#fefce8", name: "amber" },
|
|
1821
|
+
{ primary: "#10b981", secondary: "#6ee7b7", shadow: "#065f46", polyps: "#e6fffa", name: "emerald" },
|
|
2051
1822
|
];
|
|
2052
|
-
|
|
1823
|
+
|
|
1824
|
+
const palette = palettes[paletteIndex % palettes.length] ?? fallbackPalette;
|
|
2053
1825
|
const coralId = \`coralGrad-\${palette.name}-\${variant}\`;
|
|
1826
|
+
const flipped = position === "TOP" ? "scale(1, -1) translate(0, -120)" : undefined;
|
|
2054
1827
|
|
|
2055
1828
|
return (
|
|
2056
|
-
<div
|
|
1829
|
+
<div
|
|
2057
1830
|
className="absolute flex items-center justify-center pointer-events-none"
|
|
2058
1831
|
style={{ height: \`\${height}px\`, width: \`\${width}px\`, transform: \`scaleX(\${scaleX})\` }}
|
|
2059
1832
|
>
|
|
2060
|
-
<svg
|
|
1833
|
+
<svg
|
|
2061
1834
|
aria-hidden="true"
|
|
2062
1835
|
focusable="false"
|
|
2063
|
-
viewBox="0 0 80 120"
|
|
2064
|
-
className="
|
|
1836
|
+
viewBox="0 0 80 120"
|
|
1837
|
+
className="h-full w-full drop-shadow-[0_6px_14px_rgba(0,0,0,0.7)]"
|
|
2065
1838
|
preserveAspectRatio="none"
|
|
2066
1839
|
>
|
|
2067
1840
|
<defs>
|
|
2068
1841
|
<linearGradient id={coralId} x1="0%" y1="0%" x2="100%" y2="100%">
|
|
2069
1842
|
<stop offset="0%" stopColor={palette.secondary} />
|
|
2070
|
-
<stop offset="
|
|
1843
|
+
<stop offset="52%" stopColor={palette.primary} />
|
|
2071
1844
|
<stop offset="100%" stopColor={palette.shadow} />
|
|
2072
1845
|
</linearGradient>
|
|
1846
|
+
<filter id={\`coralGlow-\${variant}\`}>
|
|
1847
|
+
<feGaussianBlur stdDeviation="1.8" result="coloredBlur" />
|
|
1848
|
+
<feMerge>
|
|
1849
|
+
<feMergeNode in="coloredBlur" />
|
|
1850
|
+
<feMergeNode in="SourceGraphic" />
|
|
1851
|
+
</feMerge>
|
|
1852
|
+
</filter>
|
|
2073
1853
|
</defs>
|
|
2074
1854
|
|
|
2075
|
-
<g transform={
|
|
2076
|
-
<path
|
|
2077
|
-
|
|
1855
|
+
<g transform={flipped} filter={\`url(#coralGlow-\${variant})\`}>
|
|
1856
|
+
<path
|
|
1857
|
+
d="M 35 120 C 30 100, 15 90, 10 70 C 5 50, 20 40, 15 25 C 10 10, 25 5, 30 15 C 35 30, 28 45, 38 65 Q 42 90, 40 120"
|
|
1858
|
+
fill={palette.shadow}
|
|
1859
|
+
opacity="0.85"
|
|
1860
|
+
/>
|
|
1861
|
+
<path
|
|
1862
|
+
d="M 45 120 C 50 105, 65 95, 70 75 C 75 55, 60 45, 65 30 C 70 15, 55 10, 50 20 C 45 35, 52 45, 42 65 Q 40 90, 45 120"
|
|
1863
|
+
fill={palette.shadow}
|
|
1864
|
+
opacity="0.85"
|
|
1865
|
+
/>
|
|
1866
|
+
<path
|
|
1867
|
+
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"
|
|
1868
|
+
fill={\`url(#\${coralId})\`}
|
|
1869
|
+
/>
|
|
1870
|
+
<path
|
|
1871
|
+
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"
|
|
1872
|
+
fill={\`url(#\${coralId})\`}
|
|
1873
|
+
/>
|
|
1874
|
+
<circle cx="22" cy="11" r="3.5" fill={palette.polyps} />
|
|
1875
|
+
<circle cx="58" cy="21" r="3.5" fill={palette.polyps} />
|
|
1876
|
+
<circle cx="30" cy="15" r="2.5" fill={palette.secondary} />
|
|
1877
|
+
<circle cx="50" cy="20" r="2.5" fill={palette.secondary} />
|
|
2078
1878
|
</g>
|
|
2079
1879
|
</svg>
|
|
2080
1880
|
</div>
|
|
2081
1881
|
);
|
|
2082
1882
|
}
|
|
2083
1883
|
`;
|
|
2084
|
-
var
|
|
2085
|
-
import {
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
1884
|
+
var STARTER_PAGE_CODE = `import { useCallback, useEffect, useRef, useState } from "react";
|
|
1885
|
+
import {
|
|
1886
|
+
LuActivity,
|
|
1887
|
+
LuCode2,
|
|
1888
|
+
LuCpu,
|
|
1889
|
+
LuGauge,
|
|
1890
|
+
LuRocket,
|
|
1891
|
+
LuShieldCheck,
|
|
1892
|
+
LuZap,
|
|
1893
|
+
} from "react-icons/lu";
|
|
1894
|
+
import CoralObstacle from "./components/CoralObstacle";
|
|
1895
|
+
import ShrimpCharacter from "./components/ShrimpCharacter";
|
|
1896
|
+
import type { AestheticUnit } from "./types";
|
|
1897
|
+
import { playJumpSound, playScoreSound } from "./utils/audio";
|
|
1898
|
+
|
|
1899
|
+
type GameStatus = "IDLE" | "SWIMMING" | "JUMPING" | "DEAD";
|
|
1900
|
+
type SimSpeed = "NORMAL" | "FAST" | "TURBO";
|
|
1901
|
+
|
|
1902
|
+
const GAME_WIDTH = 720;
|
|
1903
|
+
const GAME_HEIGHT = 280;
|
|
1904
|
+
const SHRIMP_X = 82;
|
|
1905
|
+
const SHRIMP_SIZE = 58;
|
|
1906
|
+
const GRAVITY = 0.82;
|
|
1907
|
+
const JUMP_FORCE = 15;
|
|
1908
|
+
|
|
1909
|
+
function getSpeedValue(speed: SimSpeed): number {
|
|
1910
|
+
if (speed === "TURBO") return 8.2;
|
|
1911
|
+
if (speed === "FAST") return 6.4;
|
|
1912
|
+
return 5.2;
|
|
1913
|
+
}
|
|
2104
1914
|
|
|
2105
|
-
export default function
|
|
2106
|
-
const [status, setStatus] = useState<
|
|
2107
|
-
const [
|
|
2108
|
-
const [
|
|
2109
|
-
|
|
1915
|
+
export default function WelcomePage() {
|
|
1916
|
+
const [status, setStatus] = useState<GameStatus>("IDLE");
|
|
1917
|
+
const [score, setScore] = useState(0);
|
|
1918
|
+
const [bestScore, setBestScore] = useState(() => {
|
|
1919
|
+
if (typeof localStorage === "undefined") return 0;
|
|
1920
|
+
return parseInt(localStorage.getItem("rakta_shrimprun_highscore") || "0", 10);
|
|
1921
|
+
});
|
|
1922
|
+
const [playerY, setPlayerY] = useState(0);
|
|
1923
|
+
const [rotation, setRotation] = useState(0);
|
|
1924
|
+
const [obstacleX, setObstacleX] = useState(GAME_WIDTH + 120);
|
|
1925
|
+
const [obstacleHeight, setObstacleHeight] = useState(84);
|
|
1926
|
+
const [obstacleWidth, setObstacleWidth] = useState(58);
|
|
1927
|
+
const [obstaclePalette, setObstaclePalette] = useState(0);
|
|
1928
|
+
const [obstacleVariant, setObstacleVariant] = useState(0);
|
|
1929
|
+
const [obstacleScaleX, setObstacleScaleX] = useState(1);
|
|
1930
|
+
const [aestheticUnit, setAestheticUnit] = useState<AestheticUnit>("LENIS-MODERN");
|
|
1931
|
+
const [simSpeed, setSimSpeed] = useState<SimSpeed>("NORMAL");
|
|
1932
|
+
const [lowLatencyMode, setLowLatencyMode] = useState(true);
|
|
1933
|
+
const [configToast, setConfigToast] = useState<string | null>(null);
|
|
1934
|
+
|
|
1935
|
+
const statusRef = useRef(status);
|
|
1936
|
+
const velocityRef = useRef(0);
|
|
1937
|
+
const playerYRef = useRef(0);
|
|
1938
|
+
const obstacleXRef = useRef(GAME_WIDTH + 120);
|
|
1939
|
+
const scoreRef = useRef(0);
|
|
1940
|
+
const lastTimeRef = useRef(0);
|
|
1941
|
+
const animationRef = useRef<number | null>(null);
|
|
1942
|
+
const toastTimerRef = useRef<number | null>(null);
|
|
2110
1943
|
|
|
2111
1944
|
useEffect(() => {
|
|
2112
|
-
|
|
2113
|
-
|
|
1945
|
+
statusRef.current = status;
|
|
1946
|
+
}, [status]);
|
|
1947
|
+
|
|
1948
|
+
const showConfigToast = useCallback((message: string) => {
|
|
1949
|
+
setConfigToast(message);
|
|
1950
|
+
if (toastTimerRef.current !== null) {
|
|
1951
|
+
window.clearTimeout(toastTimerRef.current);
|
|
1952
|
+
}
|
|
1953
|
+
toastTimerRef.current = window.setTimeout(() => setConfigToast(null), 1400);
|
|
1954
|
+
}, []);
|
|
1955
|
+
|
|
1956
|
+
const resetObstacle = useCallback(() => {
|
|
1957
|
+
obstacleXRef.current = GAME_WIDTH + 80 + Math.random() * 220;
|
|
1958
|
+
setObstacleX(obstacleXRef.current);
|
|
1959
|
+
setObstacleHeight(58 + Math.floor(Math.random() * 82));
|
|
1960
|
+
setObstacleWidth(42 + Math.floor(Math.random() * 32));
|
|
1961
|
+
setObstaclePalette((value) => (value + 1) % 5);
|
|
1962
|
+
setObstacleVariant((value) => (value + 1) % 3);
|
|
1963
|
+
setObstacleScaleX(Math.random() > 0.5 ? 1 : -1);
|
|
1964
|
+
}, []);
|
|
1965
|
+
|
|
1966
|
+
const startRun = useCallback(() => {
|
|
1967
|
+
if (statusRef.current === "DEAD") {
|
|
1968
|
+
scoreRef.current = 0;
|
|
1969
|
+
setScore(0);
|
|
1970
|
+
playerYRef.current = 0;
|
|
1971
|
+
velocityRef.current = 0;
|
|
1972
|
+
setPlayerY(0);
|
|
1973
|
+
resetObstacle();
|
|
2114
1974
|
}
|
|
2115
|
-
});
|
|
2116
1975
|
|
|
2117
|
-
|
|
1976
|
+
statusRef.current = "SWIMMING";
|
|
1977
|
+
setStatus("SWIMMING");
|
|
1978
|
+
}, [resetObstacle]);
|
|
2118
1979
|
|
|
2119
|
-
const
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
1980
|
+
const jump = useCallback(() => {
|
|
1981
|
+
if (statusRef.current === "IDLE" || statusRef.current === "DEAD") {
|
|
1982
|
+
startRun();
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
if (playerYRef.current <= 4) {
|
|
1986
|
+
velocityRef.current = JUMP_FORCE;
|
|
1987
|
+
setStatus("JUMPING");
|
|
1988
|
+
playJumpSound();
|
|
1989
|
+
}
|
|
1990
|
+
}, [startRun]);
|
|
1991
|
+
|
|
1992
|
+
useEffect(() => {
|
|
1993
|
+
function onKeyDown(event: KeyboardEvent): void {
|
|
1994
|
+
if (event.code === "Space") {
|
|
1995
|
+
event.preventDefault();
|
|
1996
|
+
jump();
|
|
1997
|
+
}
|
|
1998
|
+
}
|
|
1999
|
+
|
|
2000
|
+
window.addEventListener("keydown", onKeyDown);
|
|
2001
|
+
return () => window.removeEventListener("keydown", onKeyDown);
|
|
2002
|
+
}, [jump]);
|
|
2003
|
+
|
|
2004
|
+
useEffect(() => {
|
|
2005
|
+
function frame(timestamp: number): void {
|
|
2006
|
+
const delta = Math.min(timestamp - lastTimeRef.current, 32) / 16.67;
|
|
2007
|
+
lastTimeRef.current = timestamp;
|
|
2123
2008
|
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2009
|
+
if (statusRef.current === "SWIMMING" || statusRef.current === "JUMPING") {
|
|
2010
|
+
velocityRef.current -= GRAVITY * delta;
|
|
2011
|
+
playerYRef.current = Math.max(0, playerYRef.current + velocityRef.current * delta);
|
|
2012
|
+
|
|
2013
|
+
if (playerYRef.current === 0 && velocityRef.current < 0) {
|
|
2014
|
+
velocityRef.current = 0;
|
|
2015
|
+
setStatus("SWIMMING");
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
obstacleXRef.current -= getSpeedValue(simSpeed) * (lowLatencyMode ? 1.08 : 0.92) * delta;
|
|
2019
|
+
|
|
2020
|
+
if (obstacleXRef.current < -90) {
|
|
2021
|
+
resetObstacle();
|
|
2022
|
+
scoreRef.current += 7;
|
|
2023
|
+
setScore(scoreRef.current);
|
|
2024
|
+
playScoreSound();
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
scoreRef.current += 1;
|
|
2028
|
+
setScore(scoreRef.current);
|
|
2029
|
+
|
|
2030
|
+
const shrimpLeft = SHRIMP_X + 8;
|
|
2031
|
+
const shrimpRight = SHRIMP_X + SHRIMP_SIZE - 8;
|
|
2032
|
+
const shrimpBottom = GAME_HEIGHT - 54 - playerYRef.current;
|
|
2033
|
+
const shrimpTop = shrimpBottom - SHRIMP_SIZE + 12;
|
|
2034
|
+
const obstacleLeft = obstacleXRef.current + 8;
|
|
2035
|
+
const obstacleRight = obstacleXRef.current + obstacleWidth - 8;
|
|
2036
|
+
const obstacleTop = GAME_HEIGHT - 48 - obstacleHeight;
|
|
2037
|
+
const obstacleBottom = GAME_HEIGHT - 48;
|
|
2038
|
+
const didCollide =
|
|
2039
|
+
shrimpLeft < obstacleRight &&
|
|
2040
|
+
shrimpRight > obstacleLeft &&
|
|
2041
|
+
shrimpTop < obstacleBottom &&
|
|
2042
|
+
shrimpBottom > obstacleTop;
|
|
2043
|
+
|
|
2044
|
+
if (didCollide) {
|
|
2045
|
+
statusRef.current = "DEAD";
|
|
2046
|
+
setStatus("DEAD");
|
|
2047
|
+
setRotation(180);
|
|
2048
|
+
const nextBest = Math.max(bestScore, scoreRef.current);
|
|
2049
|
+
setBestScore(nextBest);
|
|
2050
|
+
localStorage.setItem("rakta_shrimprun_highscore", String(nextBest));
|
|
2051
|
+
} else {
|
|
2052
|
+
setRotation(playerYRef.current > 0 ? -8 : 0);
|
|
2131
2053
|
}
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
} else {
|
|
2136
|
-
setStatus('success');
|
|
2054
|
+
|
|
2055
|
+
setPlayerY(playerYRef.current);
|
|
2056
|
+
setObstacleX(obstacleXRef.current);
|
|
2137
2057
|
}
|
|
2058
|
+
|
|
2059
|
+
animationRef.current = requestAnimationFrame(frame);
|
|
2060
|
+
}
|
|
2061
|
+
|
|
2062
|
+
animationRef.current = requestAnimationFrame(frame);
|
|
2063
|
+
return () => {
|
|
2064
|
+
if (animationRef.current !== null) cancelAnimationFrame(animationRef.current);
|
|
2138
2065
|
};
|
|
2139
|
-
|
|
2140
|
-
};
|
|
2066
|
+
}, [bestScore, lowLatencyMode, obstacleHeight, obstacleWidth, resetObstacle, simSpeed]);
|
|
2141
2067
|
|
|
2142
|
-
const
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
setProgress(0);
|
|
2146
|
-
};
|
|
2068
|
+
const obstacleSizeClass =
|
|
2069
|
+
obstacleHeight < 82 ? "KECIL" : obstacleHeight < 120 ? "SEDANG" : "BESAR";
|
|
2070
|
+
const obstaclePos = "BOTTOM" as const;
|
|
2147
2071
|
|
|
2148
2072
|
return (
|
|
2149
|
-
<div className="
|
|
2150
|
-
<
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
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>
|
|
2073
|
+
<div className="min-h-screen bg-[#050505] text-white">
|
|
2074
|
+
<div className="scanline" />
|
|
2075
|
+
<main className="mx-auto flex w-full max-w-7xl flex-col gap-10 px-6 py-8 md:px-10">
|
|
2076
|
+
<nav className="flex items-center justify-between border-b border-surface-stroke pb-5">
|
|
2077
|
+
<div className="flex items-center gap-3 font-mono text-sm font-extrabold tracking-tight">
|
|
2078
|
+
<span className="grid h-8 w-8 place-items-center border border-brand-pink bg-brand-pink text-black">
|
|
2079
|
+
R
|
|
2080
|
+
</span>
|
|
2081
|
+
Rakta.js
|
|
2163
2082
|
</div>
|
|
2164
|
-
<
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
className="
|
|
2168
|
-
>
|
|
2169
|
-
|
|
2170
|
-
</button>
|
|
2171
|
-
</div>
|
|
2083
|
+
<div className="hidden items-center gap-6 font-mono text-[11px] font-bold uppercase tracking-wider text-[#b5b5b5] md:flex">
|
|
2084
|
+
<a href="#features" className="transition hover:text-white">Features</a>
|
|
2085
|
+
<a href="#shrimprun" className="transition hover:text-white">ShrimpRun</a>
|
|
2086
|
+
<a href="#start" className="transition hover:text-white">Start</a>
|
|
2087
|
+
</div>
|
|
2088
|
+
</nav>
|
|
2172
2089
|
|
|
2173
|
-
<
|
|
2090
|
+
<section className="grid min-h-[540px] items-center gap-10 border-b border-surface-stroke py-12 md:grid-cols-[1.08fr_0.92fr]">
|
|
2174
2091
|
<div>
|
|
2175
|
-
<
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2092
|
+
<p className="mb-5 font-mono text-[11px] font-bold uppercase tracking-[0.32em] text-brand-pink">
|
|
2093
|
+
THE RED ROUTER FRAMEWORK
|
|
2094
|
+
</p>
|
|
2095
|
+
<h1 className="max-w-4xl text-6xl font-black uppercase leading-[0.88] tracking-normal text-white md:text-8xl">
|
|
2096
|
+
Small in size. Fierce in speed.
|
|
2097
|
+
</h1>
|
|
2098
|
+
<p className="mt-7 max-w-2xl text-sm leading-7 text-[#b5b5b5] md:text-base">
|
|
2099
|
+
Rakta.js is ready. React, Bun, file routes, hot reload, metadata, favicon,
|
|
2100
|
+
and the ShrimpRun starter are already wired for your first route.
|
|
2101
|
+
</p>
|
|
2102
|
+
<div className="mt-8 flex flex-wrap gap-3">
|
|
2103
|
+
<a
|
|
2104
|
+
href="#shrimprun"
|
|
2105
|
+
className="inline-flex h-11 items-center gap-2 border border-brand-pink bg-brand-pink px-5 font-mono text-xs font-extrabold uppercase text-white transition hover:bg-white hover:text-black"
|
|
2106
|
+
>
|
|
2107
|
+
<LuRocket className="h-4 w-4" />
|
|
2108
|
+
Play ShrimpRun
|
|
2109
|
+
</a>
|
|
2110
|
+
<a
|
|
2111
|
+
href="#start"
|
|
2112
|
+
className="inline-flex h-11 items-center gap-2 border border-surface-stroke px-5 font-mono text-xs font-extrabold uppercase text-white transition hover:border-white"
|
|
2113
|
+
>
|
|
2114
|
+
<LuCode2 className="h-4 w-4" />
|
|
2115
|
+
Start Building
|
|
2116
|
+
</a>
|
|
2180
2117
|
</div>
|
|
2181
2118
|
</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
2119
|
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2120
|
+
<div className="grid gap-3 border border-surface-stroke bg-surface-card p-5">
|
|
2121
|
+
{[
|
|
2122
|
+
["Routing", "File-based routes with fast client navigation", LuZap],
|
|
2123
|
+
["Metadata", "Default title, favicon, and SEO shell included", LuShieldCheck],
|
|
2124
|
+
["Runtime", "Bun-powered dev server on localhost:3000", LuCpu],
|
|
2125
|
+
].map(([title, text, Icon]) => (
|
|
2126
|
+
<div key={String(title)} className="flex gap-4 border border-white/5 bg-black p-4">
|
|
2127
|
+
<Icon className="mt-1 h-5 w-5 text-brand-pink" />
|
|
2128
|
+
<div>
|
|
2129
|
+
<h2 className="font-mono text-xs font-extrabold uppercase">{title}</h2>
|
|
2130
|
+
<p className="mt-1 text-xs leading-5 text-[#b5b5b5]">{text}</p>
|
|
2131
|
+
</div>
|
|
2132
|
+
</div>
|
|
2133
|
+
))}
|
|
2229
2134
|
</div>
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
<
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
<
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
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 = '';
|
|
2135
|
+
</section>
|
|
2136
|
+
|
|
2137
|
+
<section id="features" className="grid grid-cols-1 gap-0 border border-surface-stroke md:grid-cols-3">
|
|
2138
|
+
{[
|
|
2139
|
+
["01", "HOT RELOAD", "Pages, components, styles, layouts, stores, and route updates refresh immediately.", LuActivity],
|
|
2140
|
+
["02", "REACT ICONS", "Starter icons use react-icons/lu only. No lucide-react dependency ships.", LuCode2],
|
|
2141
|
+
["03", "LOCAL FIRST", "The CLI prints and serves localhost:3000 for the frontend starter.", LuGauge],
|
|
2142
|
+
].map(([number, title, text, Icon]) => (
|
|
2143
|
+
<div key={String(title)} className="border-b border-surface-stroke p-7 md:border-b-0 md:border-r last:md:border-r-0">
|
|
2144
|
+
<span className="font-mono text-[10px] font-bold text-brand-pink">{number}</span>
|
|
2145
|
+
<Icon className="my-8 h-6 w-6 text-white" />
|
|
2146
|
+
<h2 className="font-mono text-xl font-extrabold uppercase">{title}</h2>
|
|
2147
|
+
<p className="mt-3 text-xs leading-6 text-[#b5b5b5]">{text}</p>
|
|
2148
|
+
</div>
|
|
2149
|
+
))}
|
|
2150
|
+
</section>
|
|
2260
2151
|
|
|
2261
|
-
|
|
2262
|
-
|
|
2152
|
+
<section id="shrimprun" className="grid gap-5">
|
|
2153
|
+
<div>
|
|
2154
|
+
<p className="font-mono text-[10px] font-bold uppercase tracking-[0.28em] text-brand-pink">
|
|
2155
|
+
SHRIMPRUN SIMULATION
|
|
2156
|
+
</p>
|
|
2157
|
+
<h2 className="mt-3 text-4xl font-black uppercase md:text-6xl">
|
|
2158
|
+
Avoid the coral.
|
|
2159
|
+
</h2>
|
|
2160
|
+
</div>
|
|
2263
2161
|
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
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";
|
|
2162
|
+
<button
|
|
2163
|
+
type="button"
|
|
2164
|
+
onClick={jump}
|
|
2165
|
+
className="relative h-[280px] overflow-hidden border-2 border-surface-stroke bg-black text-left outline-none transition focus-visible:border-brand-pink"
|
|
2166
|
+
aria-label="ShrimpRun game area. Press Space or click to jump."
|
|
2167
|
+
>
|
|
2168
|
+
<div className="bg-grid-glow absolute inset-0 opacity-80" />
|
|
2169
|
+
<div className="absolute inset-x-0 bottom-0 h-12 border-t border-cyan-400/20 bg-cyan-400/5" />
|
|
2170
|
+
<div className="seaweed-waving-left-1 absolute bottom-8 left-9 h-16 w-3 bg-emerald-500/70" />
|
|
2171
|
+
<div className="seaweed-waving-left-2 absolute bottom-8 left-16 h-24 w-3 bg-cyan-500/60" />
|
|
2172
|
+
<div className="seaweed-waving-right-1 absolute bottom-8 right-20 h-20 w-3 bg-emerald-500/70" />
|
|
2173
|
+
|
|
2174
|
+
<div className="absolute left-6 top-5 z-20 flex gap-4 font-mono text-[11px] font-bold uppercase">
|
|
2175
|
+
<span>Score: {score}</span>
|
|
2176
|
+
<span className="text-brand-pink">Best: {bestScore}</span>
|
|
2177
|
+
<span>{status}</span>
|
|
2178
|
+
</div>
|
|
2290
2179
|
|
|
2291
|
-
|
|
2292
|
-
|
|
2180
|
+
<div
|
|
2181
|
+
className="absolute z-20"
|
|
2182
|
+
style={{ left: SHRIMP_X, bottom: 48 + playerY }}
|
|
2183
|
+
>
|
|
2184
|
+
<ShrimpCharacter status={status} playerY={0} rotation={rotation} />
|
|
2185
|
+
</div>
|
|
2293
2186
|
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
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>
|
|
2187
|
+
<div className="absolute z-10" style={{ left: obstacleX, bottom: 48 }}>
|
|
2188
|
+
<CoralObstacle
|
|
2189
|
+
position={obstaclePos}
|
|
2190
|
+
height={obstacleHeight}
|
|
2191
|
+
width={obstacleWidth}
|
|
2192
|
+
paletteIndex={obstaclePalette}
|
|
2193
|
+
variant={obstacleVariant}
|
|
2194
|
+
scaleX={obstacleScaleX}
|
|
2195
|
+
/>
|
|
2196
|
+
</div>
|
|
2309
2197
|
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
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>
|
|
2198
|
+
{status === "IDLE" && (
|
|
2199
|
+
<div className="absolute inset-0 z-30 grid place-items-center bg-black/55 font-mono text-xs font-bold uppercase tracking-[0.2em] text-white">
|
|
2200
|
+
Press Space or click to start
|
|
2201
|
+
</div>
|
|
2202
|
+
)}
|
|
2203
|
+
{status === "DEAD" && (
|
|
2204
|
+
<div className="absolute inset-0 z-30 grid place-items-center bg-black/75 text-center font-mono">
|
|
2205
|
+
<div>
|
|
2206
|
+
<p className="text-3xl font-black text-brand-pink">SYSTEM IMPACT</p>
|
|
2207
|
+
<p className="mt-2 text-xs uppercase text-[#b5b5b5]">Click to reboot the run</p>
|
|
2208
|
+
</div>
|
|
2209
|
+
</div>
|
|
2210
|
+
)}
|
|
2327
2211
|
</button>
|
|
2328
2212
|
|
|
2329
|
-
<
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
</div>
|
|
2338
|
-
</div>
|
|
2213
|
+
<div className="flex flex-col justify-between gap-4 font-mono text-[10px] text-zinc-500 md:flex-row md:items-center">
|
|
2214
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
2215
|
+
<span className="font-extrabold text-white">SHRIMPRUN {aestheticUnit.replace("-", " ")}</span>
|
|
2216
|
+
<span>CORAL:</span>
|
|
2217
|
+
<span className="border border-brand-pink/30 bg-brand-pink/5 px-2 py-0.5 font-bold text-brand-pink">
|
|
2218
|
+
{obstacleSizeClass}
|
|
2219
|
+
</span>
|
|
2220
|
+
</div>
|
|
2339
2221
|
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2222
|
+
<div className="flex flex-wrap items-center gap-3">
|
|
2223
|
+
{(["LENIS-MODERN", "RETRO-CYBER", "NEO-BRUTALIST"] as AestheticUnit[]).map((unit) => (
|
|
2224
|
+
<button
|
|
2225
|
+
type="button"
|
|
2226
|
+
key={unit}
|
|
2227
|
+
onClick={() => {
|
|
2228
|
+
setAestheticUnit(unit);
|
|
2229
|
+
showConfigToast(\`STYLE SET: \${unit}\`);
|
|
2230
|
+
playScoreSound();
|
|
2231
|
+
}}
|
|
2232
|
+
className={\`px-2 py-1 font-bold transition \${aestheticUnit === unit ? "bg-white text-black" : "border border-surface-stroke text-zinc-500 hover:text-white"}\`}
|
|
2233
|
+
>
|
|
2234
|
+
{unit}
|
|
2235
|
+
</button>
|
|
2236
|
+
))}
|
|
2237
|
+
{(["NORMAL", "FAST", "TURBO"] as const).map((speed) => (
|
|
2238
|
+
<button
|
|
2239
|
+
type="button"
|
|
2240
|
+
key={speed}
|
|
2241
|
+
onClick={() => {
|
|
2242
|
+
setSimSpeed(speed);
|
|
2243
|
+
showConfigToast(\`SPEED SET: \${speed}\`);
|
|
2244
|
+
playScoreSound();
|
|
2245
|
+
}}
|
|
2246
|
+
className={\`px-2 py-1 font-bold transition \${simSpeed === speed ? "bg-brand-pink text-white" : "border border-surface-stroke text-zinc-500 hover:text-white"}\`}
|
|
2247
|
+
>
|
|
2248
|
+
{speed}
|
|
2249
|
+
</button>
|
|
2250
|
+
))}
|
|
2251
|
+
<button
|
|
2252
|
+
type="button"
|
|
2253
|
+
onClick={() => {
|
|
2254
|
+
setLowLatencyMode((value) => !value);
|
|
2255
|
+
showConfigToast(\`LATENCY MODE: \${!lowLatencyMode ? "LOW" : "STANDARD"}\`);
|
|
2256
|
+
playJumpSound();
|
|
2257
|
+
}}
|
|
2258
|
+
className={\`px-2 py-1 font-bold transition \${lowLatencyMode ? "bg-cyan-400 text-black" : "border border-surface-stroke text-zinc-500"}\`}
|
|
2259
|
+
>
|
|
2260
|
+
LOW LATENCY {lowLatencyMode ? "ON" : "OFF"}
|
|
2261
|
+
</button>
|
|
2262
|
+
</div>
|
|
2263
|
+
</div>
|
|
2343
2264
|
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2265
|
+
{configToast && (
|
|
2266
|
+
<div className="fixed bottom-8 left-1/2 z-50 -translate-x-1/2 border border-brand-pink bg-black px-4 py-2 font-mono text-[10px] font-bold uppercase tracking-widest text-brand-pink">
|
|
2267
|
+
{configToast}
|
|
2268
|
+
</div>
|
|
2269
|
+
)}
|
|
2270
|
+
</section>
|
|
2271
|
+
|
|
2272
|
+
<section id="start" className="border-t border-surface-stroke py-10">
|
|
2273
|
+
<p className="font-mono text-[10px] font-bold uppercase tracking-[0.28em] text-brand-pink">
|
|
2274
|
+
NEXT MOVE
|
|
2275
|
+
</p>
|
|
2276
|
+
<h2 className="mt-3 text-3xl font-black uppercase md:text-5xl">Edit app/page and ship.</h2>
|
|
2277
|
+
<p className="mt-4 max-w-2xl text-sm leading-7 text-[#b5b5b5]">
|
|
2278
|
+
This starter is intentionally immediate: no popups, no placeholder flow,
|
|
2279
|
+
and no hidden modal dependencies. Your first route is already alive.
|
|
2280
|
+
</p>
|
|
2281
|
+
</section>
|
|
2282
|
+
</main>
|
|
2283
|
+
</div>
|
|
2348
2284
|
);
|
|
2349
2285
|
}
|
|
2350
2286
|
`;
|
|
2351
2287
|
|
|
2288
|
+
// src/types.ts
|
|
2289
|
+
var CSS_DISPLAY = {
|
|
2290
|
+
tailwind: "Tailwind CSS",
|
|
2291
|
+
bootstrap: "Bootstrap",
|
|
2292
|
+
sass: "SASS",
|
|
2293
|
+
none: "None"
|
|
2294
|
+
};
|
|
2295
|
+
var BACKEND_DISPLAY = {
|
|
2296
|
+
gaman: "Gaman.js",
|
|
2297
|
+
express: "Express.js",
|
|
2298
|
+
nest: "Nest.js",
|
|
2299
|
+
adonis: "Adonis.js"
|
|
2300
|
+
};
|
|
2301
|
+
var DATABASE_DISPLAY = {
|
|
2302
|
+
postgresql: "PostgreSQL",
|
|
2303
|
+
mysql: "MySQL",
|
|
2304
|
+
mongodb: "MongoDB",
|
|
2305
|
+
firebase: "Firebase",
|
|
2306
|
+
sqlite: "SQLite",
|
|
2307
|
+
mariadb: "MariaDB",
|
|
2308
|
+
redis: "Redis",
|
|
2309
|
+
planetscale: "PlanetScale",
|
|
2310
|
+
neon: "Neon",
|
|
2311
|
+
turso: "Turso"
|
|
2312
|
+
};
|
|
2313
|
+
var RENDER_MODE_DISPLAY = {
|
|
2314
|
+
csr: "CSR \u2014 Client-Side Rendering",
|
|
2315
|
+
ssr: "SSR \u2014 Server-Side Rendering",
|
|
2316
|
+
ssg: "SSG \u2014 Static Site Generation",
|
|
2317
|
+
csg: "CSG \u2014 Configurable Static Generation",
|
|
2318
|
+
spa: "SPA \u2014 Single Page Application",
|
|
2319
|
+
hybrid: "Hybrid \u2014 Mixed per-route"
|
|
2320
|
+
};
|
|
2321
|
+
var PROJECT_MODE_DISPLAY = {
|
|
2322
|
+
fullstack: "Fullstack app (frontend + backend + database)",
|
|
2323
|
+
"frontend-only": "Frontend only (no backend, no database)"
|
|
2324
|
+
};
|
|
2325
|
+
var PROJECT_LANGUAGE_DISPLAY = {
|
|
2326
|
+
typescript: "TypeScript / TSX",
|
|
2327
|
+
javascript: "JavaScript / JSX"
|
|
2328
|
+
};
|
|
2329
|
+
|
|
2352
2330
|
// src/generator.ts
|
|
2353
2331
|
var DEFAULT_METADATA_TITLE = "Rakta.js | Small in size. Fierce in speed. Alive in every route";
|
|
2354
2332
|
var FAVICON_BYTES = readFileSync(new URL("../assets/favicon.ico", import.meta.url));
|
|
2355
2333
|
function getRootFiles(projectConfig) {
|
|
2356
|
-
const { projectName, projectMode } = projectConfig;
|
|
2334
|
+
const { projectName, projectMode, useTypeScript } = projectConfig;
|
|
2357
2335
|
const workspaces = projectMode === "fullstack" ? ["frontend", "backend", "shared"] : [];
|
|
2358
|
-
|
|
2336
|
+
const files = [
|
|
2359
2337
|
{
|
|
2360
2338
|
path: "package.json",
|
|
2361
2339
|
content: JSON.stringify({
|
|
@@ -2370,36 +2348,16 @@ function getRootFiles(projectConfig) {
|
|
|
2370
2348
|
"build:backend": "cd backend && bun run build",
|
|
2371
2349
|
build: "bun run build:frontend && bun run build:backend",
|
|
2372
2350
|
start: "cd backend && bun run start",
|
|
2373
|
-
|
|
2351
|
+
...useTypeScript ? {
|
|
2352
|
+
typecheck: "cd frontend && bun run typecheck && cd ../backend && bun run typecheck"
|
|
2353
|
+
} : {}
|
|
2374
2354
|
} : {
|
|
2375
2355
|
dev: "rakta dev",
|
|
2376
2356
|
build: "rakta build",
|
|
2377
2357
|
start: "rakta start",
|
|
2378
|
-
typecheck: "tsc --noEmit"
|
|
2379
|
-
},
|
|
2380
|
-
description: `${projectName} \u2014 built with Rakta.js`
|
|
2381
|
-
}, null, 2)
|
|
2382
|
-
},
|
|
2383
|
-
{
|
|
2384
|
-
path: "tsconfig.base.json",
|
|
2385
|
-
content: JSON.stringify({
|
|
2386
|
-
compilerOptions: {
|
|
2387
|
-
target: "ESNext",
|
|
2388
|
-
module: "ESNext",
|
|
2389
|
-
moduleResolution: "Bundler",
|
|
2390
|
-
jsx: "react-jsx",
|
|
2391
|
-
lib: ["ESNext", "DOM", "DOM.Iterable"],
|
|
2392
|
-
strict: true,
|
|
2393
|
-
noUncheckedIndexedAccess: true,
|
|
2394
|
-
exactOptionalPropertyTypes: true,
|
|
2395
|
-
skipLibCheck: true,
|
|
2396
|
-
esModuleInterop: true,
|
|
2397
|
-
allowSyntheticDefaultImports: true,
|
|
2398
|
-
resolveJsonModule: true,
|
|
2399
|
-
verbatimModuleSyntax: true,
|
|
2400
|
-
isolatedModules: true
|
|
2358
|
+
...useTypeScript ? { typecheck: "tsc --noEmit" } : {}
|
|
2401
2359
|
},
|
|
2402
|
-
|
|
2360
|
+
description: `${projectName} \xE2\u20AC\u201D built with Rakta.js`
|
|
2403
2361
|
}, null, 2)
|
|
2404
2362
|
},
|
|
2405
2363
|
{
|
|
@@ -2444,10 +2402,37 @@ dist/
|
|
|
2444
2402
|
content: generateProjectReadme(projectConfig)
|
|
2445
2403
|
}
|
|
2446
2404
|
];
|
|
2405
|
+
if (useTypeScript) {
|
|
2406
|
+
files.splice(1, 0, {
|
|
2407
|
+
path: "tsconfig.base.json",
|
|
2408
|
+
content: JSON.stringify({
|
|
2409
|
+
compilerOptions: {
|
|
2410
|
+
target: "ESNext",
|
|
2411
|
+
module: "ESNext",
|
|
2412
|
+
moduleResolution: "Bundler",
|
|
2413
|
+
jsx: "react-jsx",
|
|
2414
|
+
lib: ["ESNext", "DOM", "DOM.Iterable"],
|
|
2415
|
+
strict: true,
|
|
2416
|
+
noUncheckedIndexedAccess: true,
|
|
2417
|
+
exactOptionalPropertyTypes: true,
|
|
2418
|
+
skipLibCheck: true,
|
|
2419
|
+
esModuleInterop: true,
|
|
2420
|
+
allowSyntheticDefaultImports: true,
|
|
2421
|
+
resolveJsonModule: true,
|
|
2422
|
+
verbatimModuleSyntax: true,
|
|
2423
|
+
isolatedModules: true
|
|
2424
|
+
},
|
|
2425
|
+
exclude: ["node_modules", "dist", "**/dist/**"]
|
|
2426
|
+
}, null, 2)
|
|
2427
|
+
});
|
|
2428
|
+
}
|
|
2429
|
+
return files;
|
|
2447
2430
|
}
|
|
2448
2431
|
function getFrontendOnlyFiles(projectConfig) {
|
|
2449
|
-
const { projectName, cssFramework } = projectConfig;
|
|
2432
|
+
const { projectName, cssFramework, useTypeScript } = projectConfig;
|
|
2450
2433
|
const styleFileName = cssFramework === "sass" ? "globals.scss" : "globals.css";
|
|
2434
|
+
const pageExtension = useTypeScript ? "tsx" : "jsx";
|
|
2435
|
+
const scriptExtension = useTypeScript ? "ts" : "js";
|
|
2451
2436
|
const files = [
|
|
2452
2437
|
{
|
|
2453
2438
|
path: "package.json",
|
|
@@ -2461,49 +2446,27 @@ function getFrontendOnlyFiles(projectConfig) {
|
|
|
2461
2446
|
build: "rakta build",
|
|
2462
2447
|
start: "rakta start",
|
|
2463
2448
|
routes: "rakta routes",
|
|
2464
|
-
typecheck: "tsc --noEmit"
|
|
2449
|
+
...useTypeScript ? { typecheck: "tsc --noEmit" } : {}
|
|
2465
2450
|
},
|
|
2466
2451
|
dependencies: {
|
|
2467
|
-
raktajs: "^0.1.
|
|
2452
|
+
raktajs: "^0.1.9",
|
|
2468
2453
|
react: "^19.2.7",
|
|
2469
2454
|
"react-dom": "^19.2.7",
|
|
2470
|
-
"react-icons": "^5.
|
|
2471
|
-
motion: "^12.0.0",
|
|
2455
|
+
"react-icons": "^5.7.0",
|
|
2472
2456
|
...getCssDependencies(cssFramework)
|
|
2473
2457
|
},
|
|
2474
|
-
devDependencies: {
|
|
2458
|
+
devDependencies: useTypeScript ? {
|
|
2475
2459
|
"@types/react": "^19.2.17",
|
|
2476
2460
|
"@types/react-dom": "^19.2.3",
|
|
2477
2461
|
typescript: "^6.0.3",
|
|
2478
2462
|
...getCssDevDependencies(cssFramework)
|
|
2463
|
+
} : {
|
|
2464
|
+
...getCssDevDependencies(cssFramework)
|
|
2479
2465
|
}
|
|
2480
2466
|
}, null, 2)
|
|
2481
2467
|
},
|
|
2482
2468
|
{
|
|
2483
|
-
path:
|
|
2484
|
-
content: JSON.stringify({
|
|
2485
|
-
extends: "./tsconfig.base.json",
|
|
2486
|
-
compilerOptions: {
|
|
2487
|
-
outDir: "./dist",
|
|
2488
|
-
rootDir: "./",
|
|
2489
|
-
types: ["react", "react-dom"]
|
|
2490
|
-
},
|
|
2491
|
-
include: [
|
|
2492
|
-
"rakta-env.d.ts",
|
|
2493
|
-
"app/**/*",
|
|
2494
|
-
"components/**/*",
|
|
2495
|
-
"styles/**/*",
|
|
2496
|
-
"rakta.config.ts"
|
|
2497
|
-
],
|
|
2498
|
-
exclude: ["node_modules", "dist"]
|
|
2499
|
-
}, null, 2)
|
|
2500
|
-
},
|
|
2501
|
-
{
|
|
2502
|
-
path: "rakta-env.d.ts",
|
|
2503
|
-
content: generateFrontendOnlyRaktaEnv()
|
|
2504
|
-
},
|
|
2505
|
-
{
|
|
2506
|
-
path: "rakta.config.ts",
|
|
2469
|
+
path: `rakta.config.${scriptExtension}`,
|
|
2507
2470
|
content: `import { defineRaktaConfig } from "raktajs";
|
|
2508
2471
|
|
|
2509
2472
|
export default defineRaktaConfig({
|
|
@@ -2520,44 +2483,40 @@ export default defineRaktaConfig({
|
|
|
2520
2483
|
`
|
|
2521
2484
|
},
|
|
2522
2485
|
{
|
|
2523
|
-
path:
|
|
2486
|
+
path: `app/layout.${pageExtension}`,
|
|
2524
2487
|
content: generateFrontendOnlyLayout()
|
|
2525
2488
|
},
|
|
2526
2489
|
{
|
|
2527
|
-
path:
|
|
2490
|
+
path: `app/page.${pageExtension}`,
|
|
2528
2491
|
content: generateFrontendOnlyPage(projectName)
|
|
2529
2492
|
},
|
|
2530
2493
|
{
|
|
2531
|
-
path:
|
|
2494
|
+
path: `app/loading.${pageExtension}`,
|
|
2532
2495
|
content: generateFrontendOnlyLoading()
|
|
2533
2496
|
},
|
|
2534
2497
|
{
|
|
2535
|
-
path:
|
|
2498
|
+
path: `app/error.${pageExtension}`,
|
|
2536
2499
|
content: generateFrontendOnlyError()
|
|
2537
2500
|
},
|
|
2538
2501
|
{
|
|
2539
|
-
path:
|
|
2502
|
+
path: `app/notFound.${pageExtension}`,
|
|
2540
2503
|
content: generateFrontendOnlyNotFound()
|
|
2541
2504
|
},
|
|
2542
2505
|
{
|
|
2543
|
-
path:
|
|
2544
|
-
content:
|
|
2545
|
-
},
|
|
2546
|
-
{
|
|
2547
|
-
path: "app/components/CoralObstacle.tsx",
|
|
2548
|
-
content: CORAL_OBSTACLE_CODE
|
|
2506
|
+
path: `app/components/CoralObstacle.${pageExtension}`,
|
|
2507
|
+
content: STARTER_CORAL_OBSTACLE_CODE
|
|
2549
2508
|
},
|
|
2550
2509
|
{
|
|
2551
|
-
path:
|
|
2552
|
-
content:
|
|
2510
|
+
path: `app/components/ShrimpCharacter.${pageExtension}`,
|
|
2511
|
+
content: STARTER_SHRIMP_CHARACTER_CODE
|
|
2553
2512
|
},
|
|
2554
2513
|
{
|
|
2555
|
-
path:
|
|
2556
|
-
content:
|
|
2514
|
+
path: `app/utils/audio.${scriptExtension}`,
|
|
2515
|
+
content: STARTER_AUDIO_CODE
|
|
2557
2516
|
},
|
|
2558
2517
|
{
|
|
2559
|
-
path:
|
|
2560
|
-
content:
|
|
2518
|
+
path: `app/types.${scriptExtension}`,
|
|
2519
|
+
content: STARTER_TYPES_CODE
|
|
2561
2520
|
},
|
|
2562
2521
|
{
|
|
2563
2522
|
path: `styles/${styleFileName}`,
|
|
@@ -2572,12 +2531,36 @@ export default defineRaktaConfig({
|
|
|
2572
2531
|
content: FAVICON_BYTES
|
|
2573
2532
|
}
|
|
2574
2533
|
];
|
|
2575
|
-
|
|
2534
|
+
if (useTypeScript) {
|
|
2535
|
+
files.splice(1, 0, {
|
|
2536
|
+
path: "tsconfig.json",
|
|
2537
|
+
content: JSON.stringify({
|
|
2538
|
+
extends: "./tsconfig.base.json",
|
|
2539
|
+
compilerOptions: {
|
|
2540
|
+
outDir: "./dist",
|
|
2541
|
+
rootDir: "./",
|
|
2542
|
+
types: ["react", "react-dom"]
|
|
2543
|
+
},
|
|
2544
|
+
include: [
|
|
2545
|
+
"rakta-env.d.ts",
|
|
2546
|
+
"app/**/*",
|
|
2547
|
+
"components/**/*",
|
|
2548
|
+
"styles/**/*",
|
|
2549
|
+
"rakta.config.ts"
|
|
2550
|
+
],
|
|
2551
|
+
exclude: ["node_modules", "dist"]
|
|
2552
|
+
}, null, 2)
|
|
2553
|
+
}, {
|
|
2554
|
+
path: "rakta-env.d.ts",
|
|
2555
|
+
content: generateFrontendOnlyRaktaEnv()
|
|
2556
|
+
});
|
|
2557
|
+
}
|
|
2558
|
+
return processFilesForLanguage(files, useTypeScript);
|
|
2576
2559
|
}
|
|
2577
2560
|
function getFullstackFrontendFiles(projectConfig) {
|
|
2578
2561
|
const { projectName, cssFramework } = projectConfig;
|
|
2579
2562
|
const styleFileName = cssFramework === "sass" ? "globals.scss" : "globals.css";
|
|
2580
|
-
|
|
2563
|
+
return [
|
|
2581
2564
|
{
|
|
2582
2565
|
path: "frontend/package.json",
|
|
2583
2566
|
content: JSON.stringify({
|
|
@@ -2594,11 +2577,9 @@ function getFullstackFrontendFiles(projectConfig) {
|
|
|
2594
2577
|
typecheck: "tsc --noEmit"
|
|
2595
2578
|
},
|
|
2596
2579
|
dependencies: {
|
|
2597
|
-
raktajs: "^0.1.
|
|
2580
|
+
raktajs: "^0.1.9",
|
|
2598
2581
|
react: "^19.2.7",
|
|
2599
2582
|
"react-dom": "^19.2.7",
|
|
2600
|
-
"react-icons": "^5.2.1",
|
|
2601
|
-
motion: "^12.0.0",
|
|
2602
2583
|
...getCssDependencies(cssFramework)
|
|
2603
2584
|
},
|
|
2604
2585
|
devDependencies: {
|
|
@@ -2636,7 +2617,7 @@ export default defineRaktaConfig({
|
|
|
2636
2617
|
appName: "${projectName}",
|
|
2637
2618
|
seo: {
|
|
2638
2619
|
defaultTitle: "${DEFAULT_METADATA_TITLE}",
|
|
2639
|
-
defaultDescription: "Built with Rakta.js \
|
|
2620
|
+
defaultDescription: "Built with Rakta.js \xE2\u20AC\u201D Small in size. Fierce in speed. Alive in every route.",
|
|
2640
2621
|
},
|
|
2641
2622
|
render: {
|
|
2642
2623
|
defaultMode: "csr",
|
|
@@ -2892,7 +2873,7 @@ export type UserSchema = typeof userSchema;
|
|
|
2892
2873
|
},
|
|
2893
2874
|
{
|
|
2894
2875
|
path: `frontend/styles/${styleFileName}`,
|
|
2895
|
-
content:
|
|
2876
|
+
content: getFrontendOnlyCssGlobals(cssFramework)
|
|
2896
2877
|
},
|
|
2897
2878
|
{
|
|
2898
2879
|
path: "frontend/public/.gitkeep",
|
|
@@ -2903,46 +2884,35 @@ export type UserSchema = typeof userSchema;
|
|
|
2903
2884
|
content: FAVICON_BYTES
|
|
2904
2885
|
},
|
|
2905
2886
|
{
|
|
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
|
|
2887
|
+
path: "frontend/components/ui/.gitkeep",
|
|
2888
|
+
content: ""
|
|
2920
2889
|
},
|
|
2921
2890
|
{
|
|
2922
|
-
path: "frontend/components/
|
|
2923
|
-
content:
|
|
2891
|
+
path: "frontend/components/layout/.gitkeep",
|
|
2892
|
+
content: ""
|
|
2924
2893
|
}
|
|
2925
2894
|
];
|
|
2926
|
-
|
|
2895
|
+
}
|
|
2896
|
+
function stripTypeScriptSyntax(code) {
|
|
2897
|
+
return code.replace(/^import\s+type\s+.+;\r?\n/gm, "").replace(/^export\s+interface\s+\w+\s*\{[\s\S]*?\r?\n\}\r?\n/gm, "").replace(/^interface\s+\w+\s*\{[\s\S]*?\r?\n\}\r?\n/gm, "").replace(/^export\s+type\s+\w+\s*=[\s\S]*?;\r?\n/gm, "").replace(/^type\s+\w+\s*=[\s\S]*?;\r?\n/gm, "").replace(/<("[^"]+"|'[^']+'|\w+)(\s*\|\s*("[^"]+"|'[^']+'|\w+))*\s*>/g, "").replace(/\s+as\s+const/g, "").replace(/\s+as\s+[A-Za-z0-9_<>,[\]\s|&".']+/g, "").replace(/\breadonly\s+/g, "").replace(/(\(|,|\{)\s*([A-Za-z_$][\w$]*)\s*:\s*[A-Za-z_$][\w$]*(?:\[\])?(?:\s*\|\s*[A-Za-z_$][\w$]*(?:\[\])?)*/g, "$1 $2").replace(/\)\s*:\s*(?:void|string|number|boolean|Promise<[^>]+>|[A-Za-z_$][\w$]*(?:\[\])?)/g, ")").replace(/(const|let)\s+([A-Za-z_$][\w$]*)\s*:\s*[^=;]+=/g, "$1 $2 =");
|
|
2927
2898
|
}
|
|
2928
2899
|
function processFilesForLanguage(files, useTypeScript) {
|
|
2929
|
-
if (useTypeScript)
|
|
2900
|
+
if (useTypeScript) {
|
|
2930
2901
|
return files;
|
|
2931
|
-
|
|
2932
|
-
|
|
2933
|
-
|
|
2934
|
-
|
|
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 };
|
|
2902
|
+
}
|
|
2903
|
+
return files.filter((file) => !file.path.endsWith(".d.ts")).map((file) => {
|
|
2904
|
+
if (typeof file.content !== "string") {
|
|
2905
|
+
return file;
|
|
2944
2906
|
}
|
|
2945
|
-
|
|
2907
|
+
let path = file.path;
|
|
2908
|
+
if (path.endsWith(".tsx"))
|
|
2909
|
+
path = path.replace(/\.tsx$/, ".jsx");
|
|
2910
|
+
else if (path.endsWith(".ts"))
|
|
2911
|
+
path = path.replace(/\.ts$/, ".js");
|
|
2912
|
+
return {
|
|
2913
|
+
path,
|
|
2914
|
+
content: stripTypeScriptSyntax(file.content)
|
|
2915
|
+
};
|
|
2946
2916
|
});
|
|
2947
2917
|
}
|
|
2948
2918
|
function getBackendFiles(projectConfig) {
|
|
@@ -3367,430 +3337,98 @@ function getCssDevDependencies(cssFramework) {
|
|
|
3367
3337
|
}
|
|
3368
3338
|
}
|
|
3369
3339
|
function getFrontendOnlyCssGlobals(cssFramework) {
|
|
3370
|
-
const cssImport = cssFramework === "tailwind" ? `@import "
|
|
3340
|
+
const cssImport = cssFramework === "tailwind" ? `@import url("https://fonts.googleapis.com/css2?family=Geist:wght@300;400;500;600;700;800;900&family=JetBrains+Mono:wght@400;500;600;700;800&display=swap");
|
|
3341
|
+
@import "tailwindcss";
|
|
3371
3342
|
|
|
3372
3343
|
` : cssFramework === "bootstrap" ? `@import url("https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css");
|
|
3373
3344
|
|
|
3374
|
-
` : cssFramework === "sass" ? `$color-primary: #
|
|
3345
|
+
` : cssFramework === "sass" ? `$color-primary: #e11d48;
|
|
3375
3346
|
$color-background: #050505;
|
|
3376
|
-
$color-foreground: #
|
|
3347
|
+
$color-foreground: #fafafa;
|
|
3377
3348
|
|
|
3378
3349
|
` : "";
|
|
3379
|
-
return `${cssImport}
|
|
3380
|
-
--
|
|
3381
|
-
--
|
|
3382
|
-
--color-
|
|
3383
|
-
--color-
|
|
3384
|
-
--color-
|
|
3385
|
-
--color-
|
|
3386
|
-
color-
|
|
3350
|
+
return `${cssImport}@theme {
|
|
3351
|
+
--font-sans: "Geist", ui-sans-serif, system-ui, sans-serif;
|
|
3352
|
+
--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, monospace;
|
|
3353
|
+
--color-brand-pink: #e11d48;
|
|
3354
|
+
--color-brand-green: #00ff00;
|
|
3355
|
+
--color-surface-bg: #000000;
|
|
3356
|
+
--color-surface-card: #0d0d0d;
|
|
3357
|
+
--color-surface-stroke: #1f1f1f;
|
|
3387
3358
|
}
|
|
3388
3359
|
|
|
3389
|
-
|
|
3390
|
-
|
|
3360
|
+
:root {
|
|
3361
|
+
color-scheme: dark;
|
|
3362
|
+
background: #050505;
|
|
3363
|
+
color: #fafafa;
|
|
3364
|
+
font-family: var(--font-sans);
|
|
3391
3365
|
}
|
|
3392
3366
|
|
|
3367
|
+
* { box-sizing: border-box; }
|
|
3368
|
+
html { scroll-behavior: smooth; }
|
|
3393
3369
|
body {
|
|
3394
3370
|
margin: 0;
|
|
3395
3371
|
min-width: 320px;
|
|
3396
3372
|
min-height: 100vh;
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
|
3401
|
-
Roboto, sans-serif;
|
|
3373
|
+
background: #050505;
|
|
3374
|
+
color: #fafafa;
|
|
3375
|
+
overflow-x: hidden;
|
|
3402
3376
|
-webkit-font-smoothing: antialiased;
|
|
3403
3377
|
}
|
|
3378
|
+
button, a { font: inherit; }
|
|
3379
|
+
a { color: inherit; text-decoration: none; }
|
|
3380
|
+
#rakta-root { min-height: 100vh; }
|
|
3404
3381
|
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3409
|
-
}
|
|
3410
|
-
|
|
3411
|
-
@keyframes shrimpLegs {
|
|
3412
|
-
0% {
|
|
3413
|
-
transform: translateY(0);
|
|
3414
|
-
}
|
|
3415
|
-
|
|
3416
|
-
50% {
|
|
3417
|
-
transform: translateY(2px);
|
|
3418
|
-
}
|
|
3419
|
-
}
|
|
3420
|
-
|
|
3421
|
-
#rakta-root {
|
|
3422
|
-
min-height: 100vh;
|
|
3423
|
-
}
|
|
3424
|
-
|
|
3425
|
-
main {
|
|
3426
|
-
width: min(1120px, calc(100% - 32px));
|
|
3427
|
-
min-height: 100vh;
|
|
3428
|
-
margin: 0 auto;
|
|
3429
|
-
padding: 56px 0;
|
|
3430
|
-
}
|
|
3431
|
-
|
|
3432
|
-
section {
|
|
3433
|
-
margin: 0 0 24px;
|
|
3434
|
-
border: 1px solid rgba(248, 113, 113, 0.18);
|
|
3435
|
-
border-radius: 18px;
|
|
3436
|
-
background:
|
|
3437
|
-
linear-gradient(135deg, rgba(220, 38, 38, 0.16), transparent 34%),
|
|
3438
|
-
#0e111a;
|
|
3439
|
-
box-shadow: 0 24px 80px rgba(127, 29, 29, 0.2);
|
|
3440
|
-
padding: clamp(24px, 4vw, 44px);
|
|
3441
|
-
}
|
|
3442
|
-
|
|
3443
|
-
p {
|
|
3444
|
-
margin: 0 0 12px;
|
|
3445
|
-
color: #cbd5e1;
|
|
3446
|
-
line-height: 1.7;
|
|
3447
|
-
}
|
|
3448
|
-
|
|
3449
|
-
h1,
|
|
3450
|
-
h2 {
|
|
3451
|
-
margin: 0 0 12px;
|
|
3452
|
-
color: #ffffff;
|
|
3453
|
-
line-height: 1;
|
|
3454
|
-
}
|
|
3455
|
-
|
|
3456
|
-
h1 {
|
|
3457
|
-
max-width: 760px;
|
|
3458
|
-
font-size: clamp(42px, 8vw, 84px);
|
|
3459
|
-
letter-spacing: 0;
|
|
3460
|
-
}
|
|
3461
|
-
|
|
3462
|
-
h2 {
|
|
3463
|
-
font-size: clamp(28px, 4vw, 44px);
|
|
3464
|
-
}
|
|
3465
|
-
|
|
3466
|
-
click {
|
|
3467
|
-
display: inline-flex;
|
|
3468
|
-
align-items: center;
|
|
3469
|
-
justify-content: center;
|
|
3470
|
-
min-height: 42px;
|
|
3471
|
-
margin: 16px 12px 0 0;
|
|
3472
|
-
border: 1px solid rgba(248, 113, 113, 0.42);
|
|
3473
|
-
border-radius: 12px;
|
|
3474
|
-
padding: 0 16px;
|
|
3475
|
-
color: #fecaca;
|
|
3476
|
-
font-weight: 700;
|
|
3477
|
-
text-decoration: none;
|
|
3478
|
-
cursor: pointer;
|
|
3479
|
-
transition:
|
|
3480
|
-
transform 160ms ease,
|
|
3481
|
-
border-color 160ms ease,
|
|
3482
|
-
background 160ms ease;
|
|
3483
|
-
}
|
|
3484
|
-
|
|
3485
|
-
click:hover {
|
|
3486
|
-
border-color: #f87171;
|
|
3487
|
-
background: rgba(220, 38, 38, 0.12);
|
|
3488
|
-
transform: translateY(-1px);
|
|
3489
|
-
}
|
|
3490
|
-
|
|
3491
|
-
button {
|
|
3492
|
-
font: inherit;
|
|
3493
|
-
}
|
|
3494
|
-
|
|
3495
|
-
.relative {
|
|
3496
|
-
position: relative;
|
|
3497
|
-
}
|
|
3498
|
-
|
|
3499
|
-
.absolute {
|
|
3500
|
-
position: absolute;
|
|
3501
|
-
}
|
|
3502
|
-
|
|
3503
|
-
.inset-0 {
|
|
3504
|
-
inset: 0;
|
|
3505
|
-
}
|
|
3382
|
+
::-webkit-scrollbar { width: 6px; height: 6px; }
|
|
3383
|
+
::-webkit-scrollbar-track { background: #000000; }
|
|
3384
|
+
::-webkit-scrollbar-thumb { background: #e11d48; border-radius: 0; }
|
|
3385
|
+
::-webkit-scrollbar-thumb:hover { background: #be123c; }
|
|
3506
3386
|
|
|
3507
|
-
|
|
3508
|
-
|
|
3387
|
+
@keyframes scanline {
|
|
3388
|
+
0% { transform: translateY(-100%); }
|
|
3389
|
+
100% { transform: translateY(100vh); }
|
|
3509
3390
|
}
|
|
3510
|
-
|
|
3511
|
-
|
|
3391
|
+
.scanline {
|
|
3392
|
+
position: fixed;
|
|
3393
|
+
top: 0;
|
|
3512
3394
|
left: 0;
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
.top-2 {
|
|
3516
|
-
top: 8px;
|
|
3517
|
-
}
|
|
3518
|
-
|
|
3519
|
-
.right-3 {
|
|
3520
|
-
right: 12px;
|
|
3521
|
-
}
|
|
3522
|
-
|
|
3523
|
-
.block {
|
|
3524
|
-
display: block;
|
|
3525
|
-
}
|
|
3526
|
-
|
|
3527
|
-
.flex {
|
|
3528
|
-
display: flex;
|
|
3529
|
-
}
|
|
3530
|
-
|
|
3531
|
-
.flex-col {
|
|
3532
|
-
flex-direction: column;
|
|
3533
|
-
}
|
|
3534
|
-
|
|
3535
|
-
.flex-wrap {
|
|
3536
|
-
flex-wrap: wrap;
|
|
3537
|
-
}
|
|
3538
|
-
|
|
3539
|
-
.items-center {
|
|
3540
|
-
align-items: center;
|
|
3541
|
-
}
|
|
3542
|
-
|
|
3543
|
-
.items-start {
|
|
3544
|
-
align-items: flex-start;
|
|
3545
|
-
}
|
|
3546
|
-
|
|
3547
|
-
.justify-center {
|
|
3548
|
-
justify-content: center;
|
|
3549
|
-
}
|
|
3550
|
-
|
|
3551
|
-
.gap-2 {
|
|
3552
|
-
gap: 8px;
|
|
3553
|
-
}
|
|
3554
|
-
|
|
3555
|
-
.gap-4 {
|
|
3556
|
-
gap: 16px;
|
|
3557
|
-
}
|
|
3558
|
-
|
|
3559
|
-
.gap-8 {
|
|
3560
|
-
gap: 32px;
|
|
3561
|
-
}
|
|
3562
|
-
|
|
3563
|
-
.w-full {
|
|
3395
|
+
z-index: 999;
|
|
3564
3396
|
width: 100%;
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
.max-w-full {
|
|
3568
|
-
max-width: 100%;
|
|
3569
|
-
}
|
|
3570
|
-
|
|
3571
|
-
.overflow-hidden {
|
|
3572
|
-
overflow: hidden;
|
|
3573
|
-
}
|
|
3574
|
-
|
|
3575
|
-
.select-none {
|
|
3576
|
-
user-select: none;
|
|
3577
|
-
}
|
|
3578
|
-
|
|
3579
|
-
.cursor-pointer {
|
|
3580
|
-
cursor: pointer;
|
|
3581
|
-
}
|
|
3582
|
-
|
|
3583
|
-
.text-left {
|
|
3584
|
-
text-align: left;
|
|
3585
|
-
}
|
|
3586
|
-
|
|
3587
|
-
.text-sm {
|
|
3588
|
-
font-size: 14px;
|
|
3589
|
-
}
|
|
3590
|
-
|
|
3591
|
-
.text-lg {
|
|
3592
|
-
font-size: 18px;
|
|
3593
|
-
}
|
|
3594
|
-
|
|
3595
|
-
.text-xl {
|
|
3596
|
-
font-size: 20px;
|
|
3597
|
-
}
|
|
3598
|
-
|
|
3599
|
-
.font-bold {
|
|
3600
|
-
font-weight: 800;
|
|
3601
|
-
}
|
|
3602
|
-
|
|
3603
|
-
.font-semibold {
|
|
3604
|
-
font-weight: 700;
|
|
3605
|
-
}
|
|
3606
|
-
|
|
3607
|
-
.font-mono {
|
|
3608
|
-
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
3609
|
-
}
|
|
3610
|
-
|
|
3611
|
-
.text-white {
|
|
3612
|
-
color: #fff;
|
|
3613
|
-
}
|
|
3614
|
-
|
|
3615
|
-
.text-slate-400 {
|
|
3616
|
-
color: #94a3b8;
|
|
3617
|
-
}
|
|
3618
|
-
|
|
3619
|
-
.text-red-600 {
|
|
3620
|
-
color: #dc2626;
|
|
3621
|
-
}
|
|
3622
|
-
|
|
3623
|
-
.bg-red-600 {
|
|
3624
|
-
background: #dc2626;
|
|
3625
|
-
}
|
|
3626
|
-
|
|
3627
|
-
.bg-black\\/75 {
|
|
3628
|
-
background: rgba(0, 0, 0, 0.75);
|
|
3629
|
-
}
|
|
3630
|
-
|
|
3631
|
-
.pointer-events-none {
|
|
3397
|
+
height: 120px;
|
|
3632
3398
|
pointer-events: none;
|
|
3399
|
+
background: linear-gradient(0deg, rgba(225, 29, 72, 0.08) 0%, rgba(225, 29, 72, 0) 100%);
|
|
3400
|
+
opacity: 0.8;
|
|
3401
|
+
animation: scanline 8s linear infinite;
|
|
3633
3402
|
}
|
|
3634
3403
|
|
|
3635
|
-
.
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
border-radius: 8px;
|
|
3641
|
-
}
|
|
3642
|
-
|
|
3643
|
-
.min-h-5 {
|
|
3644
|
-
min-height: 20px;
|
|
3645
|
-
}
|
|
3646
|
-
|
|
3647
|
-
.tabular-nums {
|
|
3648
|
-
font-variant-numeric: tabular-nums;
|
|
3649
|
-
}
|
|
3650
|
-
|
|
3651
|
-
button[aria-label^="ShrimpRun"] {
|
|
3652
|
-
border: 2px solid rgba(248, 113, 113, 0.28);
|
|
3653
|
-
border-radius: 16px;
|
|
3654
|
-
background:
|
|
3655
|
-
linear-gradient(180deg, rgba(15, 23, 42, 0.96), rgba(7, 10, 18, 0.98)),
|
|
3656
|
-
radial-gradient(circle at 18% 22%, rgba(248, 113, 113, 0.16), transparent 30%);
|
|
3657
|
-
box-shadow:
|
|
3658
|
-
inset 0 0 0 1px rgba(255, 255, 255, 0.04),
|
|
3659
|
-
0 20px 60px rgba(0, 0, 0, 0.28);
|
|
3660
|
-
}
|
|
3661
|
-
|
|
3662
|
-
button[aria-label^="ShrimpRun"] > .bottom-0.left-0 {
|
|
3663
|
-
background: linear-gradient(90deg, #7f1d1d, #dc2626, #fecaca);
|
|
3664
|
-
}
|
|
3665
|
-
|
|
3666
|
-
button:not([aria-label]) {
|
|
3667
|
-
border: 0;
|
|
3668
|
-
border-radius: 10px;
|
|
3669
|
-
background: #dc2626;
|
|
3670
|
-
color: #fff;
|
|
3671
|
-
cursor: pointer;
|
|
3672
|
-
font-weight: 800;
|
|
3673
|
-
}
|
|
3674
|
-
|
|
3675
|
-
.shrimp-sprite {
|
|
3676
|
-
filter: drop-shadow(0 8px 12px rgba(0, 0, 0, 0.32));
|
|
3404
|
+
.bg-grid-glow {
|
|
3405
|
+
background-size: 40px 40px;
|
|
3406
|
+
background-image:
|
|
3407
|
+
linear-gradient(to right, rgba(255, 255, 255, 0.04) 1px, transparent 1px),
|
|
3408
|
+
linear-gradient(to bottom, rgba(255, 255, 255, 0.04) 1px, transparent 1px);
|
|
3677
3409
|
}
|
|
3678
3410
|
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
linear-gradient(135deg, #fb7185 0%, #dc2626 46%, #7f1d1d 100%);
|
|
3684
|
-
box-shadow:
|
|
3685
|
-
inset -6px -8px 0 rgba(69, 10, 10, 0.32),
|
|
3686
|
-
0 0 0 2px rgba(254, 202, 202, 0.18);
|
|
3411
|
+
@keyframes seaweed-wave-1 {
|
|
3412
|
+
0% { transform: skewX(-14deg) rotate(-8deg) scaleY(0.96); }
|
|
3413
|
+
50% { transform: skewX(0deg) rotate(0deg) scaleY(1.04); }
|
|
3414
|
+
100% { transform: skewX(14deg) rotate(8deg) scaleY(0.96); }
|
|
3687
3415
|
}
|
|
3688
|
-
|
|
3689
|
-
.
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
bottom: 8px;
|
|
3693
|
-
width: 10px;
|
|
3694
|
-
height: 22px;
|
|
3695
|
-
border-radius: 999px 999px 4px 4px;
|
|
3696
|
-
background: #ef4444;
|
|
3697
|
-
content: "";
|
|
3698
|
-
}
|
|
3699
|
-
|
|
3700
|
-
.shrimp-run-obstacle::before {
|
|
3701
|
-
left: -7px;
|
|
3702
|
-
transform: rotate(-18deg);
|
|
3703
|
-
}
|
|
3704
|
-
|
|
3705
|
-
.shrimp-run-obstacle::after {
|
|
3706
|
-
right: -7px;
|
|
3707
|
-
transform: rotate(18deg);
|
|
3708
|
-
}
|
|
3709
|
-
|
|
3710
|
-
@media (max-width: 720px) {
|
|
3711
|
-
main {
|
|
3712
|
-
width: min(100% - 20px, 1120px);
|
|
3713
|
-
padding: 24px 0;
|
|
3714
|
-
}
|
|
3715
|
-
|
|
3716
|
-
section {
|
|
3717
|
-
padding: 20px;
|
|
3718
|
-
}
|
|
3719
|
-
}
|
|
3720
|
-
`;
|
|
3721
|
-
}
|
|
3722
|
-
function getCssGlobals(cssFramework) {
|
|
3723
|
-
const sharedBase = `
|
|
3724
|
-
:root {
|
|
3725
|
-
--color-primary: #dc2626;
|
|
3726
|
-
--color-background: #050505;
|
|
3727
|
-
--color-foreground: #f8fafc;
|
|
3728
|
-
}
|
|
3729
|
-
|
|
3730
|
-
* {
|
|
3731
|
-
box-sizing: border-box;
|
|
3416
|
+
@keyframes seaweed-wave-2 {
|
|
3417
|
+
0% { transform: skewX(10deg) rotate(6deg) scaleY(1.04); }
|
|
3418
|
+
50% { transform: skewX(-2deg) rotate(-2deg) scaleY(0.96); }
|
|
3419
|
+
100% { transform: skewX(-10deg) rotate(-6deg) scaleY(1.04); }
|
|
3732
3420
|
}
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
background: var(--color-background);
|
|
3738
|
-
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
3739
|
-
}
|
|
3740
|
-
|
|
3741
|
-
.page-shell {
|
|
3742
|
-
width: min(100% - 2rem, 960px);
|
|
3743
|
-
margin: 0 auto;
|
|
3744
|
-
padding: 4rem 0;
|
|
3745
|
-
}
|
|
3746
|
-
|
|
3747
|
-
.hero-card,
|
|
3748
|
-
.feature-card {
|
|
3749
|
-
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
3750
|
-
background: #0e111a;
|
|
3751
|
-
border-radius: 24px;
|
|
3752
|
-
padding: 2rem;
|
|
3753
|
-
margin-bottom: 1.5rem;
|
|
3754
|
-
}
|
|
3755
|
-
|
|
3756
|
-
.eyebrow {
|
|
3757
|
-
color: #dc2626;
|
|
3758
|
-
font-weight: 700;
|
|
3759
|
-
letter-spacing: 0.12em;
|
|
3760
|
-
font-size: 0.75rem;
|
|
3761
|
-
text-transform: uppercase;
|
|
3762
|
-
}
|
|
3763
|
-
|
|
3764
|
-
.button-row {
|
|
3765
|
-
display: flex;
|
|
3766
|
-
gap: 0.75rem;
|
|
3767
|
-
flex-wrap: wrap;
|
|
3768
|
-
margin-top: 1rem;
|
|
3769
|
-
}
|
|
3770
|
-
|
|
3771
|
-
a {
|
|
3772
|
-
color: #dc2626;
|
|
3421
|
+
.seaweed-waving-left-1,
|
|
3422
|
+
.seaweed-waving-right-1 {
|
|
3423
|
+
transform-origin: bottom center !important;
|
|
3424
|
+
animation: seaweed-wave-1 3.2s infinite ease-in-out alternate !important;
|
|
3773
3425
|
}
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3426
|
+
.seaweed-waving-left-2,
|
|
3427
|
+
.seaweed-waving-right-2 {
|
|
3428
|
+
transform-origin: bottom center !important;
|
|
3429
|
+
animation: seaweed-wave-2 3.8s infinite ease-in-out alternate !important;
|
|
3777
3430
|
}
|
|
3778
3431
|
`;
|
|
3779
|
-
switch (cssFramework) {
|
|
3780
|
-
case "tailwind":
|
|
3781
|
-
return `@import "tailwindcss";
|
|
3782
|
-
${sharedBase}`;
|
|
3783
|
-
case "bootstrap":
|
|
3784
|
-
return `@import url("https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css");
|
|
3785
|
-
${sharedBase}`;
|
|
3786
|
-
case "sass":
|
|
3787
|
-
return `$color-primary: #dc2626;
|
|
3788
|
-
$color-background: #050505;
|
|
3789
|
-
$color-foreground: #f8fafc;
|
|
3790
|
-
${sharedBase}`;
|
|
3791
|
-
case "none":
|
|
3792
|
-
return sharedBase;
|
|
3793
|
-
}
|
|
3794
3432
|
}
|
|
3795
3433
|
function generateFrontendOnlyRaktaEnv() {
|
|
3796
3434
|
return `declare module "*.css";
|
|
@@ -3810,17 +3448,6 @@ declare global {
|
|
|
3810
3448
|
click: RaktaClickAttributes;
|
|
3811
3449
|
}
|
|
3812
3450
|
}
|
|
3813
|
-
|
|
3814
|
-
const useCallback: typeof import("react").useCallback;
|
|
3815
|
-
const useEffect: typeof import("react").useEffect;
|
|
3816
|
-
const useRef: typeof import("react").useRef;
|
|
3817
|
-
const useState: typeof import("react").useState;
|
|
3818
|
-
|
|
3819
|
-
const ComponentsModal: typeof import("./app/components/ComponentsModal").default;
|
|
3820
|
-
const CoralObstacle: typeof import("./app/components/CoralObstacle").default;
|
|
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;
|
|
3824
3451
|
}
|
|
3825
3452
|
|
|
3826
3453
|
declare module "react/jsx-runtime" {
|
|
@@ -3864,7 +3491,7 @@ export default function RootLayout({ children }: RootLayoutProps) {
|
|
|
3864
3491
|
`;
|
|
3865
3492
|
}
|
|
3866
3493
|
function generateFrontendOnlyPage(_projectName) {
|
|
3867
|
-
return
|
|
3494
|
+
return STARTER_PAGE_CODE;
|
|
3868
3495
|
}
|
|
3869
3496
|
function generateFrontendOnlyLoading() {
|
|
3870
3497
|
return `export default function Loading() {
|
|
@@ -3934,8 +3561,27 @@ function generateFrontendOnlyNotFound() {
|
|
|
3934
3561
|
}
|
|
3935
3562
|
`;
|
|
3936
3563
|
}
|
|
3937
|
-
function generateFullstackHomePage(
|
|
3938
|
-
return
|
|
3564
|
+
function generateFullstackHomePage(projectName) {
|
|
3565
|
+
return `import React from "react";
|
|
3566
|
+
|
|
3567
|
+
export default function HomePage() {
|
|
3568
|
+
return (
|
|
3569
|
+
<main className="page-shell">
|
|
3570
|
+
<section className="hero-card">
|
|
3571
|
+
<p className="eyebrow">THE RED ROUTER FRAMEWORK</p>
|
|
3572
|
+
<h1>Welcome to ${projectName}</h1>
|
|
3573
|
+
<p>
|
|
3574
|
+
Built with Rakta.js \xE2\u20AC\u201D Small in size. Fierce in speed. Alive in every route.
|
|
3575
|
+
</p>
|
|
3576
|
+
<div className="button-row">
|
|
3577
|
+
<a href="/about">About</a>
|
|
3578
|
+
<a href="/blog">Blog</a>
|
|
3579
|
+
</div>
|
|
3580
|
+
</section>
|
|
3581
|
+
</main>
|
|
3582
|
+
);
|
|
3583
|
+
}
|
|
3584
|
+
`;
|
|
3939
3585
|
}
|
|
3940
3586
|
function getDatabaseDependencies(selectedDatabase) {
|
|
3941
3587
|
switch (selectedDatabase) {
|
|
@@ -4136,7 +3782,7 @@ function generateProjectReadme(projectConfig) {
|
|
|
4136
3782
|
if (projectMode === "frontend-only") {
|
|
4137
3783
|
return `# ${projectName}
|
|
4138
3784
|
|
|
4139
|
-
Built with Rakta.js \
|
|
3785
|
+
Built with Rakta.js \xE2\u20AC\u201D Small in size. Fierce in speed. Alive in every route.
|
|
4140
3786
|
|
|
4141
3787
|
## Stack
|
|
4142
3788
|
|
|
@@ -4155,12 +3801,12 @@ bun run dev
|
|
|
4155
3801
|
|
|
4156
3802
|
## ShrimpRun
|
|
4157
3803
|
|
|
4158
|
-
Your starter includes ShrimpRun \
|
|
3804
|
+
Your starter includes ShrimpRun \xE2\u20AC\u201D an interactive game where a shrimp dodges obstacles. Press Space or click to jump!
|
|
4159
3805
|
`;
|
|
4160
3806
|
}
|
|
4161
3807
|
return `# ${projectName}
|
|
4162
3808
|
|
|
4163
|
-
Built with Rakta.js \
|
|
3809
|
+
Built with Rakta.js \xE2\u20AC\u201D Small in size. Fierce in speed. Alive in every route.
|
|
4164
3810
|
|
|
4165
3811
|
## Stack
|
|
4166
3812
|
|
|
@@ -4249,13 +3895,6 @@ async function promptProjectMode() {
|
|
|
4249
3895
|
});
|
|
4250
3896
|
return getPromptValue(promptResult);
|
|
4251
3897
|
}
|
|
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
3898
|
async function promptCssFramework() {
|
|
4260
3899
|
const promptResult = await select({
|
|
4261
3900
|
message: "Choose a CSS framework:",
|
|
@@ -4282,6 +3921,24 @@ async function promptCssFramework() {
|
|
|
4282
3921
|
});
|
|
4283
3922
|
return getPromptValue(promptResult);
|
|
4284
3923
|
}
|
|
3924
|
+
async function promptProjectLanguage() {
|
|
3925
|
+
const promptResult = await select({
|
|
3926
|
+
message: "Choose a language:",
|
|
3927
|
+
options: [
|
|
3928
|
+
{
|
|
3929
|
+
value: "typescript",
|
|
3930
|
+
label: PROJECT_LANGUAGE_DISPLAY.typescript,
|
|
3931
|
+
hint: "recommended"
|
|
3932
|
+
},
|
|
3933
|
+
{
|
|
3934
|
+
value: "javascript",
|
|
3935
|
+
label: PROJECT_LANGUAGE_DISPLAY.javascript
|
|
3936
|
+
}
|
|
3937
|
+
],
|
|
3938
|
+
initialValue: "typescript"
|
|
3939
|
+
});
|
|
3940
|
+
return getPromptValue(promptResult);
|
|
3941
|
+
}
|
|
4285
3942
|
async function promptRenderMode() {
|
|
4286
3943
|
const promptResult = await select({
|
|
4287
3944
|
message: "Choose a render mode:",
|
|
@@ -4394,13 +4051,15 @@ async function promptDatabase() {
|
|
|
4394
4051
|
}
|
|
4395
4052
|
async function runPrompts(projectName) {
|
|
4396
4053
|
const projectMode = await promptProjectMode();
|
|
4397
|
-
const
|
|
4054
|
+
const language = await promptProjectLanguage();
|
|
4398
4055
|
const cssFramework = await promptCssFramework();
|
|
4399
4056
|
const renderMode = await promptRenderMode();
|
|
4057
|
+
const useTypeScript = language === "typescript";
|
|
4400
4058
|
if (projectMode === "frontend-only") {
|
|
4401
4059
|
return {
|
|
4402
4060
|
projectName,
|
|
4403
4061
|
projectMode,
|
|
4062
|
+
language,
|
|
4404
4063
|
useTypeScript,
|
|
4405
4064
|
cssFramework,
|
|
4406
4065
|
renderMode,
|
|
@@ -4413,6 +4072,7 @@ async function runPrompts(projectName) {
|
|
|
4413
4072
|
return {
|
|
4414
4073
|
projectName,
|
|
4415
4074
|
projectMode,
|
|
4075
|
+
language,
|
|
4416
4076
|
useTypeScript,
|
|
4417
4077
|
cssFramework,
|
|
4418
4078
|
renderMode,
|
|
@@ -4449,6 +4109,7 @@ function formatFrontendOnlyCommands(projectName) {
|
|
|
4449
4109
|
}
|
|
4450
4110
|
function printSuccessMessage(projectConfig) {
|
|
4451
4111
|
const modeLabel = PROJECT_MODE_DISPLAY[projectConfig.projectMode];
|
|
4112
|
+
const languageLabel = PROJECT_LANGUAGE_DISPLAY[projectConfig.language];
|
|
4452
4113
|
const cssLabel = CSS_DISPLAY[projectConfig.cssFramework];
|
|
4453
4114
|
const renderLabel = RENDER_MODE_DISPLAY[projectConfig.renderMode];
|
|
4454
4115
|
const isFullstack = projectConfig.projectMode === "fullstack";
|
|
@@ -4459,6 +4120,7 @@ function printSuccessMessage(projectConfig) {
|
|
|
4459
4120
|
${import_picocolors.default.bold(import_picocolors.default.green("Project created!"))}
|
|
4460
4121
|
|
|
4461
4122
|
${import_picocolors.default.dim("Mode:")} ${modeLabel}
|
|
4123
|
+
${import_picocolors.default.dim("Lang:")} ${languageLabel}
|
|
4462
4124
|
${import_picocolors.default.dim("CSS:")} ${cssLabel}
|
|
4463
4125
|
${import_picocolors.default.dim("Render:")} ${renderLabel}
|
|
4464
4126
|
${backendLine}
|
|
@@ -4506,4 +4168,4 @@ Error: ${errorMessage}
|
|
|
4506
4168
|
process.exit(1);
|
|
4507
4169
|
});
|
|
4508
4170
|
|
|
4509
|
-
//# debugId=
|
|
4171
|
+
//# debugId=AC42DEC47A292C4F64756E2164756E21
|