blumenjs 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +127 -0
- package/dist/cli/blumen.js +697 -0
- package/dist/cli/commands/build.js +85 -0
- package/dist/cli/commands/create.js +384 -0
- package/dist/cli/commands/dev.js +163 -0
- package/dist/cli/commands/start.js +129 -0
- package/dist/cli/utils.js +85 -0
- package/dist/templates/app/client/entry.tsx +41 -0
- package/dist/templates/app/pages/BlumenStarter.tsx +398 -0
- package/dist/templates/app/pages/NotFound.tsx +22 -0
- package/dist/templates/app/shared/DefaultApp.tsx +5 -0
- package/dist/templates/app/shared/DefaultDocument.tsx +76 -0
- package/dist/templates/app/shared/Link.tsx +73 -0
- package/dist/templates/app/shared/RouterContext.tsx +176 -0
- package/dist/templates/app/shared/router.ts +23 -0
- package/dist/templates/go-server/main.go +175 -0
- package/dist/templates/node-ssr/server.ts +141 -0
- package/dist/templates/scripts/generate-routes.ts +220 -0
- package/package.json +77 -0
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
// cli/commands/start.ts
|
|
2
|
+
import { spawn } from "child_process";
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
|
|
5
|
+
// cli/utils.ts
|
|
6
|
+
var c = {
|
|
7
|
+
reset: "\x1B[0m",
|
|
8
|
+
bold: "\x1B[1m",
|
|
9
|
+
dim: "\x1B[2m",
|
|
10
|
+
red: "\x1B[31m",
|
|
11
|
+
green: "\x1B[32m",
|
|
12
|
+
yellow: "\x1B[33m",
|
|
13
|
+
blue: "\x1B[34m",
|
|
14
|
+
magenta: "\x1B[35m",
|
|
15
|
+
cyan: "\x1B[36m",
|
|
16
|
+
white: "\x1B[37m",
|
|
17
|
+
gray: "\x1B[90m"
|
|
18
|
+
};
|
|
19
|
+
var log = {
|
|
20
|
+
info: (msg) => console.log(` ${c.magenta}\u25CF${c.reset} ${msg}`),
|
|
21
|
+
success: (msg) => console.log(` ${c.green}\u2713${c.reset} ${msg}`),
|
|
22
|
+
error: (msg) => console.error(` ${c.red}\u2717${c.reset} ${msg}`),
|
|
23
|
+
warn: (msg) => console.log(` ${c.yellow}\u26A0${c.reset} ${msg}`),
|
|
24
|
+
step: (msg) => console.log(` ${c.dim}\u2192${c.reset} ${msg}`),
|
|
25
|
+
blank: () => console.log("")
|
|
26
|
+
};
|
|
27
|
+
function banner() {
|
|
28
|
+
console.log("");
|
|
29
|
+
console.log(
|
|
30
|
+
` ${c.magenta}${c.bold}\u{1F338} Blumen${c.reset} ${c.dim}v0.1.0${c.reset}`
|
|
31
|
+
);
|
|
32
|
+
console.log(
|
|
33
|
+
` ${c.dim}The React framework powered by Go${c.reset}`
|
|
34
|
+
);
|
|
35
|
+
console.log("");
|
|
36
|
+
}
|
|
37
|
+
function divider() {
|
|
38
|
+
console.log(` ${c.dim}${"\u2500".repeat(48)}${c.reset}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// cli/commands/start.ts
|
|
42
|
+
async function start() {
|
|
43
|
+
banner();
|
|
44
|
+
if (!fs.existsSync("dist/ssr-server.js")) {
|
|
45
|
+
log.error("Production build not found.");
|
|
46
|
+
log.info(
|
|
47
|
+
`Run ${c.bold}blumen build${c.reset} first to create a production build.`
|
|
48
|
+
);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
log.info("Starting production server...");
|
|
52
|
+
log.blank();
|
|
53
|
+
const services = [
|
|
54
|
+
{
|
|
55
|
+
name: "ssr",
|
|
56
|
+
label: "ssr",
|
|
57
|
+
color: c.cyan,
|
|
58
|
+
cmd: "node",
|
|
59
|
+
args: ["dist/ssr-server.js"],
|
|
60
|
+
readyPattern: /SSR server running|listening/i
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: "go",
|
|
64
|
+
label: " go",
|
|
65
|
+
color: c.green,
|
|
66
|
+
cmd: "go",
|
|
67
|
+
args: ["run", "go-server/main.go"],
|
|
68
|
+
readyPattern: /Go server starting/
|
|
69
|
+
}
|
|
70
|
+
];
|
|
71
|
+
const children = [];
|
|
72
|
+
const ready = /* @__PURE__ */ new Set();
|
|
73
|
+
for (const svc of services) {
|
|
74
|
+
const child = spawn(svc.cmd, svc.args, {
|
|
75
|
+
cwd: process.cwd(),
|
|
76
|
+
env: { ...process.env, NODE_ENV: "production" },
|
|
77
|
+
stdio: ["inherit", "pipe", "pipe"]
|
|
78
|
+
});
|
|
79
|
+
const prefix = ` ${svc.color}\u2502${c.reset} ${svc.color}${svc.label}${c.reset} `;
|
|
80
|
+
const handleOutput = (data) => {
|
|
81
|
+
for (const line of data.toString().split("\n")) {
|
|
82
|
+
const trimmed = line.replace(/\r$/, "");
|
|
83
|
+
if (!trimmed)
|
|
84
|
+
continue;
|
|
85
|
+
console.log(`${prefix}${trimmed}`);
|
|
86
|
+
if (svc.readyPattern && svc.readyPattern.test(trimmed) && !ready.has(svc.name)) {
|
|
87
|
+
ready.add(svc.name);
|
|
88
|
+
if (ready.size === services.length) {
|
|
89
|
+
log.blank();
|
|
90
|
+
divider();
|
|
91
|
+
log.blank();
|
|
92
|
+
log.success(
|
|
93
|
+
`${c.bold}Production server running.${c.reset}`
|
|
94
|
+
);
|
|
95
|
+
console.log(
|
|
96
|
+
` ${c.dim}\u279C${c.reset} ${c.cyan}http://localhost:3000${c.reset}`
|
|
97
|
+
);
|
|
98
|
+
log.blank();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
child.stdout?.on("data", handleOutput);
|
|
104
|
+
child.stderr?.on("data", handleOutput);
|
|
105
|
+
children.push(child);
|
|
106
|
+
}
|
|
107
|
+
const shutdown = () => {
|
|
108
|
+
log.blank();
|
|
109
|
+
log.info("Shutting down...");
|
|
110
|
+
for (const child of children) {
|
|
111
|
+
if (!child.killed)
|
|
112
|
+
child.kill("SIGTERM");
|
|
113
|
+
}
|
|
114
|
+
setTimeout(() => {
|
|
115
|
+
for (const child of children) {
|
|
116
|
+
if (!child.killed)
|
|
117
|
+
child.kill("SIGKILL");
|
|
118
|
+
}
|
|
119
|
+
process.exit(0);
|
|
120
|
+
}, 3e3);
|
|
121
|
+
};
|
|
122
|
+
process.on("SIGINT", shutdown);
|
|
123
|
+
process.on("SIGTERM", shutdown);
|
|
124
|
+
await new Promise(() => {
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
export {
|
|
128
|
+
start
|
|
129
|
+
};
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// cli/utils.ts
|
|
2
|
+
var c = {
|
|
3
|
+
reset: "\x1B[0m",
|
|
4
|
+
bold: "\x1B[1m",
|
|
5
|
+
dim: "\x1B[2m",
|
|
6
|
+
red: "\x1B[31m",
|
|
7
|
+
green: "\x1B[32m",
|
|
8
|
+
yellow: "\x1B[33m",
|
|
9
|
+
blue: "\x1B[34m",
|
|
10
|
+
magenta: "\x1B[35m",
|
|
11
|
+
cyan: "\x1B[36m",
|
|
12
|
+
white: "\x1B[37m",
|
|
13
|
+
gray: "\x1B[90m"
|
|
14
|
+
};
|
|
15
|
+
var log = {
|
|
16
|
+
info: (msg) => console.log(` ${c.magenta}\u25CF${c.reset} ${msg}`),
|
|
17
|
+
success: (msg) => console.log(` ${c.green}\u2713${c.reset} ${msg}`),
|
|
18
|
+
error: (msg) => console.error(` ${c.red}\u2717${c.reset} ${msg}`),
|
|
19
|
+
warn: (msg) => console.log(` ${c.yellow}\u26A0${c.reset} ${msg}`),
|
|
20
|
+
step: (msg) => console.log(` ${c.dim}\u2192${c.reset} ${msg}`),
|
|
21
|
+
blank: () => console.log("")
|
|
22
|
+
};
|
|
23
|
+
function banner() {
|
|
24
|
+
console.log("");
|
|
25
|
+
console.log(
|
|
26
|
+
` ${c.magenta}${c.bold}\u{1F338} Blumen${c.reset} ${c.dim}v0.1.0${c.reset}`
|
|
27
|
+
);
|
|
28
|
+
console.log(
|
|
29
|
+
` ${c.dim}The React framework powered by Go${c.reset}`
|
|
30
|
+
);
|
|
31
|
+
console.log("");
|
|
32
|
+
}
|
|
33
|
+
function divider() {
|
|
34
|
+
console.log(` ${c.dim}${"\u2500".repeat(48)}${c.reset}`);
|
|
35
|
+
}
|
|
36
|
+
async function select(question, options) {
|
|
37
|
+
const readline = await import("readline");
|
|
38
|
+
const rl = readline.createInterface({
|
|
39
|
+
input: process.stdin,
|
|
40
|
+
output: process.stdout
|
|
41
|
+
});
|
|
42
|
+
return new Promise((resolve) => {
|
|
43
|
+
console.log(`
|
|
44
|
+
${c.bold}${question}${c.reset}`);
|
|
45
|
+
options.forEach((opt, i) => {
|
|
46
|
+
console.log(` ${c.cyan}${i + 1}${c.reset}) ${opt}`);
|
|
47
|
+
});
|
|
48
|
+
rl.question(`
|
|
49
|
+
${c.dim}Enter choice [1-${options.length}]:${c.reset} `, (answer) => {
|
|
50
|
+
rl.close();
|
|
51
|
+
const idx = parseInt(answer, 10) - 1;
|
|
52
|
+
if (idx >= 0 && idx < options.length) {
|
|
53
|
+
resolve(options[idx]);
|
|
54
|
+
} else {
|
|
55
|
+
resolve(options[0]);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
async function confirm(question, defaultYes = true) {
|
|
61
|
+
const readline = await import("readline");
|
|
62
|
+
const rl = readline.createInterface({
|
|
63
|
+
input: process.stdin,
|
|
64
|
+
output: process.stdout
|
|
65
|
+
});
|
|
66
|
+
const hint = defaultYes ? "Y/n" : "y/N";
|
|
67
|
+
return new Promise((resolve) => {
|
|
68
|
+
rl.question(` ${c.bold}${question}${c.reset} ${c.dim}(${hint})${c.reset} `, (answer) => {
|
|
69
|
+
rl.close();
|
|
70
|
+
const a = answer.trim().toLowerCase();
|
|
71
|
+
if (a === "")
|
|
72
|
+
resolve(defaultYes);
|
|
73
|
+
else
|
|
74
|
+
resolve(a === "y" || a === "yes");
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
export {
|
|
79
|
+
banner,
|
|
80
|
+
c,
|
|
81
|
+
confirm,
|
|
82
|
+
divider,
|
|
83
|
+
log,
|
|
84
|
+
select
|
|
85
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { hydrateRoot } from "react-dom/client";
|
|
3
|
+
import NotFoundPage from "../pages/NotFound";
|
|
4
|
+
|
|
5
|
+
// Auto-generated route map (run `npm run routes` to regenerate)
|
|
6
|
+
import { routes, App } from "./generated-routes";
|
|
7
|
+
import { RouterProvider } from "../shared/RouterContext";
|
|
8
|
+
|
|
9
|
+
function init() {
|
|
10
|
+
const container = document.getElementById("root");
|
|
11
|
+
if (!container) {
|
|
12
|
+
console.error("Root element not found");
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Get props from server (used for the initial SSR hydration)
|
|
17
|
+
const propsElement = document.getElementById("ssr-props");
|
|
18
|
+
const propsText = propsElement?.textContent?.trim();
|
|
19
|
+
const initialProps = propsText ? JSON.parse(propsText) : {};
|
|
20
|
+
|
|
21
|
+
// Hydrate the app with the RouterProvider.
|
|
22
|
+
// The provider handles route matching, page rendering, and SPA navigation.
|
|
23
|
+
hydrateRoot(
|
|
24
|
+
container,
|
|
25
|
+
<RouterProvider
|
|
26
|
+
routes={routes}
|
|
27
|
+
App={App}
|
|
28
|
+
notFoundComponent={NotFoundPage}
|
|
29
|
+
initialProps={initialProps}
|
|
30
|
+
/>,
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
console.log("Hydrated with client-side router:", window.location.pathname);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Initialize when DOM is ready
|
|
37
|
+
if (document.readyState === "loading") {
|
|
38
|
+
document.addEventListener("DOMContentLoaded", init);
|
|
39
|
+
} else {
|
|
40
|
+
init();
|
|
41
|
+
}
|
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
interface HomeProps {
|
|
4
|
+
timestamp?: number;
|
|
5
|
+
serverRendered?: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const HomePage: React.FC<HomeProps> = () => {
|
|
9
|
+
return (
|
|
10
|
+
<>
|
|
11
|
+
<style
|
|
12
|
+
dangerouslySetInnerHTML={{
|
|
13
|
+
__html: `
|
|
14
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap');
|
|
15
|
+
|
|
16
|
+
@keyframes float {
|
|
17
|
+
0%, 100% { transform: translateY(0px) rotate(0deg); opacity: 0.3; }
|
|
18
|
+
50% { transform: translateY(-30px) rotate(5deg); opacity: 0.6; }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@keyframes pulse-glow {
|
|
22
|
+
0%, 100% { box-shadow: 0 0 40px rgba(168, 85, 247, 0.15); }
|
|
23
|
+
50% { box-shadow: 0 0 80px rgba(168, 85, 247, 0.3); }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@keyframes gradient-shift {
|
|
27
|
+
0% { background-position: 0% 50%; }
|
|
28
|
+
50% { background-position: 100% 50%; }
|
|
29
|
+
100% { background-position: 0% 50%; }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@keyframes fade-up {
|
|
33
|
+
from { opacity: 0; transform: translateY(30px); }
|
|
34
|
+
to { opacity: 1; transform: translateY(0); }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
@keyframes shimmer {
|
|
38
|
+
0% { background-position: -200% center; }
|
|
39
|
+
100% { background-position: 200% center; }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
.blumen-home * { box-sizing: border-box; margin: 0; padding: 0; }
|
|
43
|
+
|
|
44
|
+
.blumen-home {
|
|
45
|
+
min-height: 100vh;
|
|
46
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
47
|
+
background: #0a0612;
|
|
48
|
+
color: #e2e8f0;
|
|
49
|
+
overflow-x: hidden;
|
|
50
|
+
position: relative;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.blumen-bg {
|
|
54
|
+
position: fixed;
|
|
55
|
+
inset: 0;
|
|
56
|
+
background:
|
|
57
|
+
radial-gradient(ellipse 80% 60% at 50% 0%, rgba(88, 28, 135, 0.25), transparent),
|
|
58
|
+
radial-gradient(ellipse 60% 50% at 80% 50%, rgba(139, 92, 246, 0.1), transparent),
|
|
59
|
+
radial-gradient(ellipse 50% 40% at 20% 80%, rgba(168, 85, 247, 0.08), transparent);
|
|
60
|
+
pointer-events: none;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.blumen-orb {
|
|
64
|
+
position: absolute;
|
|
65
|
+
border-radius: 50%;
|
|
66
|
+
filter: blur(80px);
|
|
67
|
+
pointer-events: none;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.blumen-orb-1 {
|
|
71
|
+
width: 500px; height: 500px;
|
|
72
|
+
background: rgba(139, 92, 246, 0.12);
|
|
73
|
+
top: -100px; right: -100px;
|
|
74
|
+
animation: float 12s ease-in-out infinite;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.blumen-orb-2 {
|
|
78
|
+
width: 400px; height: 400px;
|
|
79
|
+
background: rgba(168, 85, 247, 0.08);
|
|
80
|
+
bottom: -80px; left: -80px;
|
|
81
|
+
animation: float 15s ease-in-out infinite 3s;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.blumen-orb-3 {
|
|
85
|
+
width: 300px; height: 300px;
|
|
86
|
+
background: rgba(192, 132, 252, 0.06);
|
|
87
|
+
top: 40%; left: 60%;
|
|
88
|
+
animation: float 18s ease-in-out infinite 6s;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.blumen-grid-overlay {
|
|
92
|
+
position: fixed;
|
|
93
|
+
inset: 0;
|
|
94
|
+
background-image:
|
|
95
|
+
linear-gradient(rgba(139, 92, 246, 0.03) 1px, transparent 1px),
|
|
96
|
+
linear-gradient(90deg, rgba(139, 92, 246, 0.03) 1px, transparent 1px);
|
|
97
|
+
background-size: 60px 60px;
|
|
98
|
+
pointer-events: none;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.blumen-content {
|
|
102
|
+
position: relative;
|
|
103
|
+
z-index: 1;
|
|
104
|
+
display: flex;
|
|
105
|
+
flex-direction: column;
|
|
106
|
+
align-items: center;
|
|
107
|
+
justify-content: center;
|
|
108
|
+
min-height: 100vh;
|
|
109
|
+
padding: 4rem 2rem;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.blumen-badge {
|
|
113
|
+
display: inline-flex;
|
|
114
|
+
align-items: center;
|
|
115
|
+
gap: 8px;
|
|
116
|
+
padding: 6px 16px;
|
|
117
|
+
border-radius: 100px;
|
|
118
|
+
background: rgba(139, 92, 246, 0.1);
|
|
119
|
+
border: 1px solid rgba(139, 92, 246, 0.2);
|
|
120
|
+
font-size: 0.8rem;
|
|
121
|
+
font-weight: 500;
|
|
122
|
+
color: #c084fc;
|
|
123
|
+
letter-spacing: 0.05em;
|
|
124
|
+
text-transform: uppercase;
|
|
125
|
+
margin-bottom: 2rem;
|
|
126
|
+
animation: fade-up 0.8s ease-out both;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.blumen-badge-dot {
|
|
130
|
+
width: 6px; height: 6px;
|
|
131
|
+
background: #a855f7;
|
|
132
|
+
border-radius: 50%;
|
|
133
|
+
animation: pulse-glow 2s ease-in-out infinite;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
.blumen-logo {
|
|
137
|
+
font-size: clamp(4rem, 8vw, 7rem);
|
|
138
|
+
font-weight: 900;
|
|
139
|
+
line-height: 1.05;
|
|
140
|
+
letter-spacing: -0.03em;
|
|
141
|
+
background: linear-gradient(135deg, #e2e8f0 0%, #c084fc 40%, #a855f7 60%, #7c3aed 100%);
|
|
142
|
+
background-size: 200% 200%;
|
|
143
|
+
-webkit-background-clip: text;
|
|
144
|
+
-webkit-text-fill-color: transparent;
|
|
145
|
+
background-clip: text;
|
|
146
|
+
animation: fade-up 0.8s ease-out 0.15s both, gradient-shift 8s ease infinite;
|
|
147
|
+
margin-bottom: 1.5rem;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.blumen-tagline {
|
|
151
|
+
font-size: clamp(1.1rem, 2vw, 1.4rem);
|
|
152
|
+
font-weight: 300;
|
|
153
|
+
color: #94a3b8;
|
|
154
|
+
line-height: 1.7;
|
|
155
|
+
max-width: 560px;
|
|
156
|
+
text-align: center;
|
|
157
|
+
animation: fade-up 0.8s ease-out 0.3s both;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.blumen-tagline strong {
|
|
161
|
+
color: #c084fc;
|
|
162
|
+
font-weight: 500;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.blumen-cta-row {
|
|
166
|
+
display: flex;
|
|
167
|
+
gap: 16px;
|
|
168
|
+
margin-top: 3rem;
|
|
169
|
+
animation: fade-up 0.8s ease-out 0.45s both;
|
|
170
|
+
flex-wrap: wrap;
|
|
171
|
+
justify-content: center;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.blumen-btn-primary {
|
|
175
|
+
display: inline-flex;
|
|
176
|
+
align-items: center;
|
|
177
|
+
gap: 8px;
|
|
178
|
+
padding: 14px 32px;
|
|
179
|
+
border-radius: 12px;
|
|
180
|
+
background: linear-gradient(135deg, #7c3aed, #a855f7);
|
|
181
|
+
color: white;
|
|
182
|
+
font-family: inherit;
|
|
183
|
+
font-size: 0.95rem;
|
|
184
|
+
font-weight: 600;
|
|
185
|
+
border: none;
|
|
186
|
+
cursor: pointer;
|
|
187
|
+
transition: all 0.3s ease;
|
|
188
|
+
text-decoration: none;
|
|
189
|
+
letter-spacing: 0.01em;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
.blumen-btn-primary:hover {
|
|
193
|
+
transform: translateY(-2px);
|
|
194
|
+
box-shadow: 0 8px 30px rgba(139, 92, 246, 0.4);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.blumen-btn-secondary {
|
|
198
|
+
display: inline-flex;
|
|
199
|
+
align-items: center;
|
|
200
|
+
gap: 8px;
|
|
201
|
+
padding: 14px 32px;
|
|
202
|
+
border-radius: 12px;
|
|
203
|
+
background: rgba(139, 92, 246, 0.08);
|
|
204
|
+
color: #c084fc;
|
|
205
|
+
font-family: inherit;
|
|
206
|
+
font-size: 0.95rem;
|
|
207
|
+
font-weight: 500;
|
|
208
|
+
border: 1px solid rgba(139, 92, 246, 0.2);
|
|
209
|
+
cursor: pointer;
|
|
210
|
+
transition: all 0.3s ease;
|
|
211
|
+
text-decoration: none;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.blumen-btn-secondary:hover {
|
|
215
|
+
background: rgba(139, 92, 246, 0.15);
|
|
216
|
+
border-color: rgba(139, 92, 246, 0.4);
|
|
217
|
+
transform: translateY(-2px);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.blumen-code-block {
|
|
221
|
+
margin-top: 3.5rem;
|
|
222
|
+
padding: 20px 28px;
|
|
223
|
+
border-radius: 14px;
|
|
224
|
+
background: rgba(15, 10, 25, 0.8);
|
|
225
|
+
border: 1px solid rgba(139, 92, 246, 0.15);
|
|
226
|
+
font-family: 'SF Mono', 'Fira Code', 'JetBrains Mono', monospace;
|
|
227
|
+
font-size: 0.9rem;
|
|
228
|
+
color: #94a3b8;
|
|
229
|
+
animation: fade-up 0.8s ease-out 0.6s both;
|
|
230
|
+
backdrop-filter: blur(12px);
|
|
231
|
+
display: flex;
|
|
232
|
+
align-items: center;
|
|
233
|
+
gap: 12px;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.blumen-code-block .prompt { color: #7c3aed; }
|
|
237
|
+
.blumen-code-block .cmd { color: #c084fc; }
|
|
238
|
+
.blumen-code-block .arg { color: #e2e8f0; }
|
|
239
|
+
|
|
240
|
+
.blumen-features {
|
|
241
|
+
display: grid;
|
|
242
|
+
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
|
|
243
|
+
gap: 20px;
|
|
244
|
+
max-width: 900px;
|
|
245
|
+
width: 100%;
|
|
246
|
+
margin-top: 5rem;
|
|
247
|
+
animation: fade-up 0.8s ease-out 0.75s both;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.blumen-feature {
|
|
251
|
+
padding: 28px;
|
|
252
|
+
border-radius: 16px;
|
|
253
|
+
background: rgba(15, 10, 25, 0.6);
|
|
254
|
+
border: 1px solid rgba(139, 92, 246, 0.1);
|
|
255
|
+
backdrop-filter: blur(12px);
|
|
256
|
+
transition: all 0.3s ease;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.blumen-feature:hover {
|
|
260
|
+
border-color: rgba(139, 92, 246, 0.3);
|
|
261
|
+
transform: translateY(-4px);
|
|
262
|
+
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.3);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.blumen-feature-icon {
|
|
266
|
+
width: 44px; height: 44px;
|
|
267
|
+
border-radius: 12px;
|
|
268
|
+
background: rgba(139, 92, 246, 0.12);
|
|
269
|
+
display: flex;
|
|
270
|
+
align-items: center;
|
|
271
|
+
justify-content: center;
|
|
272
|
+
font-size: 1.3rem;
|
|
273
|
+
margin-bottom: 16px;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.blumen-feature h3 {
|
|
277
|
+
font-size: 1.05rem;
|
|
278
|
+
font-weight: 600;
|
|
279
|
+
color: #e2e8f0;
|
|
280
|
+
margin-bottom: 8px;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
.blumen-feature p {
|
|
284
|
+
font-size: 0.88rem;
|
|
285
|
+
color: #64748b;
|
|
286
|
+
line-height: 1.6;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
.blumen-footer {
|
|
290
|
+
margin-top: 5rem;
|
|
291
|
+
padding: 2rem;
|
|
292
|
+
text-align: center;
|
|
293
|
+
color: #475569;
|
|
294
|
+
font-size: 0.82rem;
|
|
295
|
+
animation: fade-up 0.8s ease-out 0.9s both;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
.blumen-footer a {
|
|
299
|
+
color: #7c3aed;
|
|
300
|
+
text-decoration: none;
|
|
301
|
+
transition: color 0.2s;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.blumen-footer a:hover { color: #a855f7; }
|
|
305
|
+
`,
|
|
306
|
+
}}
|
|
307
|
+
/>
|
|
308
|
+
<div className="blumen-home">
|
|
309
|
+
{/* Background effects */}
|
|
310
|
+
<div className="blumen-bg" />
|
|
311
|
+
<div className="blumen-orb blumen-orb-1" />
|
|
312
|
+
<div className="blumen-orb blumen-orb-2" />
|
|
313
|
+
<div className="blumen-orb blumen-orb-3" />
|
|
314
|
+
<div className="blumen-grid-overlay" />
|
|
315
|
+
|
|
316
|
+
{/* Main content */}
|
|
317
|
+
<div className="blumen-content">
|
|
318
|
+
<div className="blumen-badge">
|
|
319
|
+
<span className="blumen-badge-dot" />
|
|
320
|
+
Framework v0.1.0
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
<h1 className="blumen-logo">๐ธ Blumen</h1>
|
|
324
|
+
|
|
325
|
+
<p className="blumen-tagline">
|
|
326
|
+
The <strong>React framework</strong> powered by{" "}
|
|
327
|
+
<strong>Go</strong>. Lightning-fast server-side
|
|
328
|
+
rendering with the developer experience you deserve.
|
|
329
|
+
</p>
|
|
330
|
+
|
|
331
|
+
<div className="blumen-cta-row">
|
|
332
|
+
<button className="blumen-btn-primary">
|
|
333
|
+
๐ Read the Docs
|
|
334
|
+
</button>
|
|
335
|
+
<button className="blumen-btn-secondary">
|
|
336
|
+
โก Quick Start
|
|
337
|
+
</button>
|
|
338
|
+
</div>
|
|
339
|
+
|
|
340
|
+
<div className="blumen-code-block">
|
|
341
|
+
<span className="prompt">$</span>
|
|
342
|
+
<span className="cmd">npx</span>
|
|
343
|
+
<span className="arg">
|
|
344
|
+
create-blumen-app my-project
|
|
345
|
+
</span>
|
|
346
|
+
</div>
|
|
347
|
+
|
|
348
|
+
{/* Feature cards */}
|
|
349
|
+
<div className="blumen-features">
|
|
350
|
+
<div className="blumen-feature">
|
|
351
|
+
<div className="blumen-feature-icon">โก</div>
|
|
352
|
+
<h3>Go-Powered SSR</h3>
|
|
353
|
+
<p>
|
|
354
|
+
Your server runs on Go โ blazing fast data
|
|
355
|
+
fetching, auth, and API calls at native speed.
|
|
356
|
+
</p>
|
|
357
|
+
</div>
|
|
358
|
+
<div className="blumen-feature">
|
|
359
|
+
<div className="blumen-feature-icon">๐ฅ</div>
|
|
360
|
+
<h3>React Fast Refresh</h3>
|
|
361
|
+
<p>
|
|
362
|
+
Edit a component, see it update instantly.
|
|
363
|
+
No page reload, no lost state. Pure speed.
|
|
364
|
+
</p>
|
|
365
|
+
</div>
|
|
366
|
+
<div className="blumen-feature">
|
|
367
|
+
<div className="blumen-feature-icon">๐</div>
|
|
368
|
+
<h3>File-Based Routing</h3>
|
|
369
|
+
<p>
|
|
370
|
+
Drop a file in{" "}
|
|
371
|
+
<code
|
|
372
|
+
style={{
|
|
373
|
+
color: "#c084fc",
|
|
374
|
+
fontSize: "0.82rem",
|
|
375
|
+
}}
|
|
376
|
+
>
|
|
377
|
+
app/pages/
|
|
378
|
+
</code>
|
|
379
|
+
. It's a route. Dynamic params, nested
|
|
380
|
+
layouts โ just works.
|
|
381
|
+
</p>
|
|
382
|
+
</div>
|
|
383
|
+
</div>
|
|
384
|
+
|
|
385
|
+
<div className="blumen-footer">
|
|
386
|
+
<p>
|
|
387
|
+
Built with ๐ ยท{" "}
|
|
388
|
+
<a href="https://github.com">GitHub</a> ยท MIT
|
|
389
|
+
License
|
|
390
|
+
</p>
|
|
391
|
+
</div>
|
|
392
|
+
</div>
|
|
393
|
+
</div>
|
|
394
|
+
</>
|
|
395
|
+
);
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
export default HomePage;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Link } from '../shared/Link';
|
|
3
|
+
|
|
4
|
+
interface NotFoundProps {
|
|
5
|
+
path?: string;
|
|
6
|
+
status?: number;
|
|
7
|
+
serverRendered?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const NotFoundPage: React.FC<NotFoundProps> = () => {
|
|
11
|
+
return (
|
|
12
|
+
<div className="not-found">
|
|
13
|
+
<h1>404 - Page Not Found</h1>
|
|
14
|
+
<p>The page you are looking for does not exist.</p>
|
|
15
|
+
<nav className="page-nav">
|
|
16
|
+
<Link href="/" className="nav-link">โ Go Home</Link>
|
|
17
|
+
</nav>
|
|
18
|
+
</div>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default NotFoundPage;
|