create-rakta-app 0.1.6 → 0.1.8

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