Redesign
keypair34 committed
Apr 5, 2026 at 18:56 UTC
d316915f51dbb071645889eed73de9eabe8e9eaf
15 files changed
+1098
-646
app/[...slug]/page.tsx
+51
-22
@@ -3,6 +3,7 @@ import { Metadata } from "next";
3
import { allPages } from "contentlayer/generated";
4
import { Mdx } from "@/components/mdx-components";
5
import AboutMedia from "@/components/about-media";
6
+import Link from "next/link";
7
8
interface PageProps {
9
params: Promise<{
@@ -12,23 +13,13 @@ interface PageProps {
13
14
async function getPageFromParams(params: Awaited<PageProps["params"]>) {
15
const slug = params?.slug?.join("/");
15
- const page = allPages.find((page) => page.slugAsParams === slug);
16
-
17
- if (!page) {
18
- null;
19
- }
20
-
21
- return page;
16
+ return allPages.find((page) => page.slugAsParams === slug) ?? null;
17
}
18
19
export async function generateMetadata(props: PageProps): Promise<Metadata> {
20
const params = await props.params;
21
const page = await getPageFromParams(params);
27
-
28
- if (!page) {
29
- return {};
30
- }
31
-
22
+ if (!page) return {};
23
return {
24
title: page.title,
25
description: page.description,
@@ -45,17 +36,55 @@ export default async function PagePage(props: PageProps) {
36
const params = await props.params;
37
const page = await getPageFromParams(params);
38
48
- if (!page) {
49
- notFound();
50
- }
39
+ if (!page) notFound();
40
41
return (
53
- <article className="py-6 prose dark:prose-invert">
54
- <h1>{page.title}</h1>
55
- {page.description && <p className="text-xl">{page.description}</p>}
56
- <hr />
57
- {page.slugAsParams === "about" && <AboutMedia />}
58
- <Mdx code={page.body.code} />
59
- </article>
42
+ <div className="mx-auto max-w-3xl px-6 py-16 lg:px-8">
43
+ {/* ── Breadcrumb ──────────────────────────────────── */}
44
+ <nav className="mb-10 flex items-center gap-2 text-xs text-slate-400 dark:text-slate-600">
45
+ <Link
46
+ href="/"
47
+ className="transition-colors hover:text-slate-700 dark:hover:text-slate-300"
48
+ >
49
+ Home
50
+ </Link>
51
+ <span>/</span>
52
+ <span className="text-slate-600 dark:text-slate-400">{page.title}</span>
53
+ </nav>
54
+
55
+ {/* ── Page header ─────────────────────────────────── */}
56
+ <div className="mb-12">
57
+ <h1 className="text-4xl font-bold tracking-tight text-slate-900 dark:text-white sm:text-5xl">
58
+ {page.title}
59
+ </h1>
60
+ {page.description && (
61
+ <p className="mt-4 text-lg leading-relaxed text-slate-500 dark:text-slate-400">
62
+ {page.description}
63
+ </p>
64
+ )}
65
+ </div>
66
+
67
+ {/* ── About media block ───────────────────────────── */}
68
+ {page.slugAsParams === "about" && (
69
+ <div className="mb-12">
70
+ <AboutMedia />
71
+ </div>
72
+ )}
73
+
74
+ {/* ── MDX body ────────────────────────────────────── */}
75
+ <article className="prose dark:prose-invert prose-slate max-w-none prose-headings:font-semibold prose-headings:tracking-tight prose-a:text-indigo-600 dark:prose-a:text-indigo-400 prose-a:no-underline hover:prose-a:underline prose-code:rounded prose-code:bg-slate-100 dark:prose-code:bg-white/[0.06] prose-code:px-1 prose-code:py-0.5 prose-code:text-slate-800 dark:prose-code:text-slate-200 prose-code:before:content-none prose-code:after:content-none prose-pre:rounded-xl prose-pre:border prose-pre:border-slate-200 dark:prose-pre:border-white/[0.06] prose-pre:bg-[#0D1117] prose-hr:border-slate-200 dark:prose-hr:border-white/[0.06] prose-strong:text-slate-900 dark:prose-strong:text-white">
76
+ <Mdx code={page.body.code} />
77
+ </article>
78
+
79
+ {/* ── Last updated ────────────────────────────────── */}
80
+ <p className="mt-16 border-t border-slate-200 dark:border-white/[0.06] pt-6 text-xs text-slate-400 dark:text-slate-600">
81
+ Last updated:{" "}
82
+ {new Date(page.lastmod).toLocaleDateString("en-US", {
83
+ year: "numeric",
84
+ month: "long",
85
+ day: "numeric",
86
+ })}
87
+ </p>
88
+ </div>
89
);
90
}
app/components/TabbedRunner.tsx
+96
-77
@@ -1,88 +1,107 @@
1
"use client";
2
3
-import { JSX, useState } from "react";
3
+import { useState } from "react";
4
5
-const runnerSnippets = {
6
- NodeJS: `$ smb
7
-✔ Valid config
8
-✔ NodeJs 🟩 runner detected
9
-✔ Valid project
10
-✔ Valid deployment setup
11
-☱ Deploying > Use key path: ~/.ssh/id_11@smbcloud
12
-☱ Deploying > Building the app ✔
13
-☲ Deploying > App restart ✔
14
-☱ Deploying > App is running ✔
15
-✔ Deployment complete.`,
16
- Ruby: `$ smb
17
-✔ Valid config
18
-✔ Ruby 🟥 runner detected
19
-✔ Valid project
20
-✔ Valid deployment setup
21
-☱ Deploying > Use key path: ~/.ssh/id_11@smbcloud
22
-☱ Deploying > Building the app ✔
23
-☲ Deploying > App restart ✔
24
-☱ Deploying > App is running ✔
25
-✔ Deployment complete.`,
26
- Swift: `$ smb
27
-✔ Valid config
28
-✔ Swift 🟧 runner detected
29
-✔ Valid project
30
-✔ Valid deployment setup
31
-☱ Deploying > Use key path: ~/.ssh/id_11@smbcloud
32
-☱ Deploying > Building the app ✔
33
-☲ Deploying > App restart ✔
34
-☱ Deploying > App is running ✔
35
-✔ Deployment complete.`,
5
+type Runtime = "NodeJS" | "Swift" | "Ruby";
6
+
7
+interface TerminalLine {
8
+ text: string;
9
+ type: "command" | "success" | "progress" | "muted";
10
+}
11
+
12
+const runnerSnippets: Record<Runtime, TerminalLine[]> = {
13
+ NodeJS: [
14
+ { text: "$ smb", type: "command" },
15
+ { text: "✔ Valid config", type: "success" },
16
+ { text: "✔ NodeJS 🟩 runner detected", type: "success" },
17
+ { text: "✔ Valid project", type: "success" },
18
+ { text: "✔ Valid deployment setup", type: "success" },
19
+ { text: "☱ Deploying › Use key path: ~/.ssh/id_11@smbcloud", type: "progress" },
20
+ { text: "☱ Deploying › Building the app ✔", type: "progress" },
21
+ { text: "☲ Deploying › App restart ✔", type: "progress" },
22
+ { text: "☱ Deploying › App is running ✔", type: "progress" },
23
+ { text: "✔ Deployment complete.", type: "success" },
24
+ ],
25
+ Swift: [
26
+ { text: "$ smb", type: "command" },
27
+ { text: "✔ Valid config", type: "success" },
28
+ { text: "✔ Swift 🟧 runner detected", type: "success" },
29
+ { text: "✔ Valid project", type: "success" },
30
+ { text: "✔ Valid deployment setup", type: "success" },
31
+ { text: "☱ Deploying › Use key path: ~/.ssh/id_11@smbcloud", type: "progress" },
32
+ { text: "☱ Deploying › Building the app ✔", type: "progress" },
33
+ { text: "☲ Deploying › App restart ✔", type: "progress" },
34
+ { text: "☱ Deploying › App is running ✔", type: "progress" },
35
+ { text: "✔ Deployment complete.", type: "success" },
36
+ ],
37
+ Ruby: [
38
+ { text: "$ smb", type: "command" },
39
+ { text: "✔ Valid config", type: "success" },
40
+ { text: "✔ Ruby 🟥 runner detected", type: "success" },
41
+ { text: "✔ Valid project", type: "success" },
42
+ { text: "✔ Valid deployment setup", type: "success" },
43
+ { text: "☱ Deploying › Use key path: ~/.ssh/id_11@smbcloud", type: "progress" },
44
+ { text: "☱ Deploying › Building the app ✔", type: "progress" },
45
+ { text: "☲ Deploying › App restart ✔", type: "progress" },
46
+ { text: "☱ Deploying › App is running ✔", type: "progress" },
47
+ { text: "✔ Deployment complete.", type: "success" },
48
+ ],
49
+};
50
+
51
+const lineClass: Record<TerminalLine["type"], string> = {
52
+ command: "text-slate-200",
53
+ success: "text-emerald-400",
54
+ progress: "text-sky-400",
55
+ muted: "text-slate-500",
56
};
57
58
export default function TabbedRunner() {
39
- const [tab, setTab] = useState<keyof typeof runnerSnippets>("NodeJS");
40
- const tabs: { key: keyof typeof runnerSnippets; label: string }[] = [
41
- { key: "NodeJS", label: "NodeJS" },
42
- { key: "Swift", label: "Swift" },
43
- { key: "Ruby", label: "Ruby" },
44
- ];
59
+ const [tab, setTab] = useState<Runtime>("NodeJS");
60
+ const tabs: Runtime[] = ["NodeJS", "Swift", "Ruby"];
61
+ const lines = runnerSnippets[tab];
62
+
63
return (
46
- <div>
47
- <div style={{ display: "flex", gap: 8, marginBottom: 8 }}>
48
- {tabs.map((t) => (
49
- <button
50
- key={t.key}
51
- onClick={() => setTab(t.key)}
52
- style={{
53
- border: "none",
54
- background: tab === t.key ? "#222" : "#eee",
55
- color: tab === t.key ? "#fff" : "#222",
56
- padding: "4px 12px",
57
- borderRadius: 4,
58
- cursor: "pointer",
59
- fontWeight: tab === t.key ? "bold" : "normal",
60
- }}
61
- >
62
- {t.label}
63
- </button>
64
- ))}
64
+ <div className="overflow-hidden rounded-xl border border-slate-200 dark:border-white/[0.08] bg-white dark:bg-[#0D1117] shadow-xl shadow-slate-900/5 dark:shadow-none">
65
+ {/* Window chrome */}
66
+ <div className="flex items-center justify-between border-b border-slate-200 dark:border-white/[0.06] bg-slate-50 dark:bg-[#161B22] px-4 py-3">
67
+ {/* Traffic lights */}
68
+ <div className="flex items-center gap-1.5">
69
+ <div className="h-3 w-3 rounded-full bg-[#FF5F57]" />
70
+ <div className="h-3 w-3 rounded-full bg-[#FEBC2E]" />
71
+ <div className="h-3 w-3 rounded-full bg-[#28C840]" />
72
+ </div>
73
+
74
+ {/* Runtime tabs */}
75
+ <div className="flex gap-1">
76
+ {tabs.map((t) => (
77
+ <button
78
+ key={t}
79
+ onClick={() => setTab(t)}
80
+ className={`rounded px-3 py-1 text-xs font-medium transition-all ${
81
+ tab === t
82
+ ? "bg-white dark:bg-slate-700 text-slate-900 dark:text-slate-100 shadow-sm"
83
+ : "text-slate-400 dark:text-slate-500 hover:text-slate-600 dark:hover:text-slate-400"
84
+ }`}
85
+ >
86
+ {t}
87
+ </button>
88
+ ))}
89
+ </div>
90
+
91
+ {/* Spacer to balance traffic lights */}
92
+ <div className="w-16" />
93
+ </div>
94
+
95
+ {/* Terminal body */}
96
+ <div className="p-5 font-mono text-sm leading-relaxed">
97
+ <div className="space-y-0.5">
98
+ {lines.map((line, idx) => (
99
+ <div key={idx} className={lineClass[line.type]}>
100
+ {line.text}
101
+ </div>
102
+ ))}
103
+ </div>
104
</div>
66
- <pre>
67
- {runnerSnippets[tab].split("\n").map((line, idx) => (
68
- <span key={idx}>
69
- {line.split("✔").reduce(
70
- (acc, part, i, arr) => {
71
- if (i === arr.length - 1) return [...acc, part];
72
- return [
73
- ...acc,
74
- part,
75
- <span key={`check-${i}`} style={{ color: "green" }}>
76
- ✔
77
- </span>,
78
- ];
79
- },
80
- [] as (string | JSX.Element)[],
81
- )}
82
- {"\n"}
83
- </span>
84
- ))}
85
- </pre>
105
</div>
106
);
107
}
app/globals.css
+7
@@ -1,3 +1,10 @@
1
@tailwind base;
2
@tailwind components;
3
@tailwind utilities;
4
+
5
+@layer base {
6
+ html {
7
+ -webkit-font-smoothing: antialiased;
8
+ -moz-osx-font-smoothing: grayscale;
9
+ }
10
+}
app/kti/[term]/page.tsx
+162
-161
@@ -19,9 +19,7 @@ export async function generateMetadata({
19
const { term } = await params;
20
const kti = allKtis.find((kti) => kti.term === term);
21
22
- if (!kti) {
23
- return {};
24
- }
22
+ if (!kti) return {};
23
24
return {
25
...genPageMetadata({
@@ -54,6 +52,29 @@ export async function generateMetadata({
52
};
53
}
54
55
+const difficultyConfig = {
56
+ pemula: {
57
+ label: "Pemula",
58
+ badge:
59
+ "bg-emerald-50 dark:bg-emerald-500/10 text-emerald-700 dark:text-emerald-400 border border-emerald-200 dark:border-emerald-500/20",
60
+ tag: "bg-emerald-50 dark:bg-emerald-500/10 text-emerald-700 dark:text-emerald-400 hover:bg-emerald-100 dark:hover:bg-emerald-500/20",
61
+ },
62
+ menengah: {
63
+ label: "Menengah",
64
+ badge:
65
+ "bg-amber-50 dark:bg-amber-500/10 text-amber-700 dark:text-amber-400 border border-amber-200 dark:border-amber-500/20",
66
+ tag: "bg-amber-50 dark:bg-amber-500/10 text-amber-700 dark:text-amber-400 hover:bg-amber-100 dark:hover:bg-amber-500/20",
67
+ },
68
+ lanjut: {
69
+ label: "Lanjut",
70
+ badge:
71
+ "bg-indigo-50 dark:bg-indigo-500/10 text-indigo-700 dark:text-indigo-400 border border-indigo-200 dark:border-indigo-500/20",
72
+ tag: "bg-indigo-50 dark:bg-indigo-500/10 text-indigo-700 dark:text-indigo-400 hover:bg-indigo-100 dark:hover:bg-indigo-500/20",
73
+ },
74
+} as const;
75
+
76
+type Difficulty = keyof typeof difficultyConfig;
77
+
78
export default async function KtiTermPage({
79
params,
80
}: {
@@ -62,11 +83,8 @@ export default async function KtiTermPage({
83
const { term } = await params;
84
const kti = allKtis.find((kti) => kti.term === term);
85
65
- if (!kti || !kti.published) {
66
- notFound();
67
- }
86
+ if (!kti || !kti.published) notFound();
87
69
- // Get related terms
88
const relatedKtis = kti.relatedTerms
89
? allKtis.filter(
90
(k) =>
@@ -76,7 +94,6 @@ export default async function KtiTermPage({
94
)
95
: [];
96
79
- // Get same category terms
97
const sameCategoryKtis = allKtis
98
.filter(
99
(k) =>
@@ -85,206 +102,190 @@ export default async function KtiTermPage({
102
k.category === kti.category &&
103
!kti.relatedTerms?.includes(k.term),
104
)
88
- .slice(0, 6);
105
+ .slice(0, 8);
106
90
- const difficultyColor = {
91
- pemula: "bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300",
92
- menengah: "bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300",
93
- lanjut:
94
- "bg-purple-100 dark:bg-purple-900 text-purple-700 dark:text-purple-300",
95
- };
96
-
97
- const difficultyLabel = {
98
- pemula: "🌱 Pemula",
99
- menengah: "📈 Menengah",
100
- lanjut: "🚀 Lanjut",
101
- };
107
+ const difficulty = kti.difficulty as Difficulty;
108
+ const config = difficultyConfig[difficulty] ?? difficultyConfig.pemula;
109
110
return (
104
- <div className="prose dark:prose-invert max-w-none">
105
- {/* Breadcrumbs */}
106
- <nav className="text-sm mb-6 not-prose">
107
- <ol className="flex items-center space-x-2 text-slate-600 dark:text-slate-400">
108
- <li>
109
- <Link
110
- href="/"
111
- className="hover:text-blue-600 dark:hover:text-blue-400"
112
- >
113
- Home
114
- </Link>
115
- </li>
116
- <li>/</li>
117
- <li>
118
- <Link
119
- href="/kti"
120
- className="hover:text-blue-600 dark:hover:text-blue-400"
121
- >
122
- KTI
123
- </Link>
124
- </li>
125
- <li>/</li>
126
- <li className="text-slate-900 dark:text-slate-100 font-medium">
127
- {kti.term}
128
- </li>
129
- </ol>
111
+ <div className="mx-auto max-w-3xl px-6 py-16 lg:px-8">
112
+ {/* ── Breadcrumb ──────────────────────────────────── */}
113
+ <nav className="mb-10 flex items-center gap-2 text-xs text-slate-400 dark:text-slate-600">
114
+ <Link
115
+ href="/"
116
+ className="transition-colors hover:text-slate-700 dark:hover:text-slate-300"
117
+ >
118
+ Home
119
+ </Link>
120
+ <span>/</span>
121
+ <Link
122
+ href="/kti"
123
+ className="transition-colors hover:text-slate-700 dark:hover:text-slate-300"
124
+ >
125
+ KTI
126
+ </Link>
127
+ <span>/</span>
128
+ <span className="text-slate-600 dark:text-slate-400">{kti.title}</span>
129
</nav>
130
132
- {/* Header */}
133
- <div className="mb-8">
134
- <div className="flex flex-wrap items-start justify-between gap-4 mb-4">
135
- <div>
136
- <h1 className="mb-2">{kti.title}</h1>
137
- {kti.englishTerm && (
138
- <p className="text-lg text-slate-600 dark:text-slate-400 italic mt-0">
139
- {kti.englishTerm}
140
- </p>
141
- )}
142
- {kti.acronym && (
143
- <p className="text-sm text-slate-500 dark:text-slate-500 mt-1">
144
- Akronim:{" "}
145
- <span className="font-mono font-bold">{kti.acronym}</span>
146
- </p>
147
- )}
148
- </div>
149
- <div className="flex flex-wrap gap-2 not-prose">
150
- <span
151
- className={`px-3 py-1 rounded-full text-sm font-medium ${difficultyColor[kti.difficulty]}`}
152
- >
153
- {difficultyLabel[kti.difficulty]}
131
+ {/* ── Page header ─────────────────────────────────── */}
132
+ <div className="mb-10">
133
+ <div className="mb-5 flex flex-wrap items-center gap-2">
134
+ <span
135
+ className={`rounded-full px-2.5 py-0.5 text-xs font-semibold ${config.badge}`}
136
+ >
137
+ {config.label}
138
+ </span>
139
+ <span className="rounded-full border border-slate-200 dark:border-white/[0.08] bg-slate-100 dark:bg-white/[0.04] px-2.5 py-0.5 text-xs font-medium text-slate-600 dark:text-slate-400">
140
+ {kti.category}
141
+ </span>
142
+ {kti.acronym && (
143
+ <span className="rounded-full border border-slate-200 dark:border-white/[0.08] bg-slate-100 dark:bg-white/[0.04] px-2.5 py-0.5 font-mono text-xs text-slate-500 dark:text-slate-500">
144
+ {kti.acronym}
145
</span>
155
- <span className="px-3 py-1 rounded-full text-sm font-medium bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300">
156
- {kti.category}
157
- </span>
158
- </div>
146
+ )}
147
</div>
148
161
- {/* Description */}
162
- <div className="bg-blue-50 dark:bg-blue-950 p-4 rounded-lg border-l-4 border-blue-500">
163
- <p className="text-lg font-medium text-slate-900 dark:text-slate-100 mb-0">
149
+ <h1 className="text-4xl font-bold tracking-tight text-slate-900 dark:text-white sm:text-5xl">
150
+ {kti.title}
151
+ </h1>
152
+ {kti.englishTerm && (
153
+ <p className="mt-2 text-lg text-slate-400 dark:text-slate-500">
154
+ {kti.englishTerm}
155
+ </p>
156
+ )}
157
+
158
+ {/* Description callout */}
159
+ <div className="mt-6 rounded-xl border border-slate-200 dark:border-white/[0.06] bg-white dark:bg-white/[0.02] p-5">
160
+ <p className="text-base leading-relaxed text-slate-700 dark:text-slate-300">
161
{kti.description}
162
</p>
163
</div>
167
- </div>
164
169
- {/* Tags */}
170
- {kti.tags && kti.tags.length > 0 && (
171
- <div className="mb-8 not-prose">
172
- <div className="flex flex-wrap gap-2 items-center">
173
- <span className="text-sm text-slate-600 dark:text-slate-400 font-medium">
174
- Tags:
175
- </span>
165
+ {/* Tags */}
166
+ {kti.tags && kti.tags.length > 0 && (
167
+ <div className="mt-4 flex flex-wrap items-center gap-1.5">
168
{kti.tags.map((tag) => (
169
<span
170
key={tag}
179
- className="px-2 py-1 bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 rounded text-sm"
171
+ className="rounded-md bg-slate-100 dark:bg-white/[0.05] px-2 py-0.5 text-xs text-slate-500 dark:text-slate-500"
172
>
173
#{tag}
174
</span>
175
))}
176
</div>
185
- </div>
186
- )}
177
+ )}
178
+ </div>
179
188
- {/* Main Content */}
189
- <article className="mb-12">
180
+ {/* ── MDX body ────────────────────────────────────── */}
181
+ <article className="prose dark:prose-invert prose-slate max-w-none prose-headings:font-semibold prose-headings:tracking-tight prose-a:text-indigo-600 dark:prose-a:text-indigo-400 prose-code:rounded prose-code:bg-slate-100 dark:prose-code:bg-white/[0.06] prose-code:px-1 prose-code:py-0.5 prose-code:text-slate-800 dark:prose-code:text-slate-200 prose-code:before:content-none prose-code:after:content-none prose-pre:rounded-xl prose-pre:border prose-pre:border-slate-200 dark:prose-pre:border-white/[0.06] prose-pre:bg-[#0D1117] mb-16">
182
<Mdx code={kti.body.code} />
183
</article>
184
193
- {/* Related Terms */}
185
+ {/* ── Related terms ───────────────────────────────── */}
186
{relatedKtis.length > 0 && (
195
- <div className="mb-8 border-t border-slate-200 dark:border-slate-800 pt-8">
196
- <h2 className="text-2xl font-bold mb-4">🔗 Istilah Terkait</h2>
197
- <div className="grid grid-cols-1 md:grid-cols-2 gap-4 not-prose">
198
- {relatedKtis.map((relatedKti) => (
199
- <Link
200
- key={relatedKti.term}
201
- href={relatedKti.slug}
202
- className="group p-4 border border-slate-200 dark:border-slate-800 rounded-lg hover:border-blue-500 dark:hover:border-blue-500 hover:shadow-md transition-all"
203
- >
204
- <div className="flex justify-between items-start">
205
- <div>
206
- <h3 className="font-bold text-slate-900 dark:text-slate-100 group-hover:text-blue-600 dark:group-hover:text-blue-400 mb-1">
207
- {relatedKti.title}
187
+ <section className="mb-12 border-t border-slate-200 dark:border-white/[0.06] pt-10">
188
+ <p className="mb-6 text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600">
189
+ Istilah Terkait
190
+ </p>
191
+ <div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
192
+ {relatedKtis.map((rel) => {
193
+ const relConfig =
194
+ difficultyConfig[rel.difficulty as Difficulty] ??
195
+ difficultyConfig.pemula;
196
+ return (
197
+ <Link
198
+ key={rel.term}
199
+ href={rel.slug}
200
+ className="group rounded-xl border border-slate-200 dark:border-white/[0.06] bg-white dark:bg-white/[0.02] p-4 transition-colors hover:border-indigo-300 dark:hover:border-indigo-700/50 hover:bg-slate-50 dark:hover:bg-white/[0.04]"
201
+ >
202
+ <div className="mb-2 flex items-start justify-between gap-2">
203
+ <h3 className="font-semibold text-slate-900 dark:text-white transition-colors group-hover:text-indigo-600 dark:group-hover:text-indigo-400">
204
+ {rel.title}
205
</h3>
209
- {relatedKti.englishTerm && (
210
- <p className="text-xs text-slate-500 mb-2">
211
- {relatedKti.englishTerm}
212
- </p>
213
- )}
214
- <p className="text-sm text-slate-600 dark:text-slate-400 line-clamp-2">
215
- {relatedKti.description}
216
- </p>
206
+ <span
207
+ className={`shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold ${relConfig.badge}`}
208
+ >
209
+ {relConfig.label}
210
+ </span>
211
</div>
218
- <span
219
- className={`text-xs px-2 py-1 rounded-full ml-2 flex-shrink-0 ${difficultyColor[relatedKti.difficulty]}`}
220
- >
221
- {relatedKti.difficulty}
222
- </span>
223
- </div>
224
- </Link>
225
- ))}
212
+ {rel.englishTerm && (
213
+ <p className="mb-1 text-xs text-slate-400 dark:text-slate-600">
214
+ {rel.englishTerm}
215
+ </p>
216
+ )}
217
+ <p className="line-clamp-2 text-sm leading-relaxed text-slate-500 dark:text-slate-400">
218
+ {rel.description}
219
+ </p>
220
+ </Link>
221
+ );
222
+ })}
223
</div>
227
- </div>
224
+ </section>
225
)}
226
230
- {/* Same Category Terms */}
227
+ {/* ── Same category ───────────────────────────────── */}
228
{sameCategoryKtis.length > 0 && (
232
- <div className="mb-8 border-t border-slate-200 dark:border-slate-800 pt-8">
233
- <h2 className="text-2xl font-bold mb-4">
234
- 📚 Istilah Lainnya dalam {kti.category}
235
- </h2>
236
- <div className="flex flex-wrap gap-2 not-prose">
237
- {sameCategoryKtis.map((categoryKti) => (
238
- <Link
239
- key={categoryKti.term}
240
- href={categoryKti.slug}
241
- className="px-4 py-2 bg-slate-100 dark:bg-slate-800 text-slate-700 dark:text-slate-300 rounded-lg hover:bg-blue-100 dark:hover:bg-blue-900 hover:text-blue-700 dark:hover:text-blue-300 transition-colors"
242
- >
243
- {categoryKti.term}
244
- </Link>
245
- ))}
229
+ <section className="mb-12 border-t border-slate-200 dark:border-white/[0.06] pt-10">
230
+ <p className="mb-5 text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600">
231
+ Lainnya dalam {kti.category}
232
+ </p>
233
+ <div className="flex flex-wrap gap-1.5">
234
+ {sameCategoryKtis.map((cat) => {
235
+ const catConfig =
236
+ difficultyConfig[cat.difficulty as Difficulty] ??
237
+ difficultyConfig.pemula;
238
+ return (
239
+ <Link
240
+ key={cat.term}
241
+ href={cat.slug}
242
+ className={`rounded-md px-2.5 py-1 text-xs font-medium transition-colors ${catConfig.tag}`}
243
+ >
244
+ {cat.title}
245
+ </Link>
246
+ );
247
+ })}
248
</div>
247
- </div>
249
+ </section>
250
)}
251
250
- {/* Navigation */}
251
- <div className="border-t border-slate-200 dark:border-slate-800 pt-8 not-prose">
252
+ {/* ── Back link ───────────────────────────────────── */}
253
+ <div className="mb-12 border-t border-slate-200 dark:border-white/[0.06] pt-8">
254
<Link
255
href="/kti"
254
- className="inline-flex items-center text-blue-600 dark:text-blue-400 hover:text-blue-700 dark:hover:text-blue-300 font-medium"
256
+ className="inline-flex items-center gap-1.5 text-sm font-medium text-slate-500 dark:text-slate-400 transition-colors hover:text-slate-900 dark:hover:text-white"
257
>
258
← Kembali ke Daftar Istilah
259
</Link>
260
</div>
261
260
- {/* CTA Section */}
261
- <div className="mt-8 bg-gradient-to-r from-blue-500 to-purple-600 text-white p-6 rounded-lg not-prose">
262
- <h3 className="text-xl font-bold mb-2 text-white">
263
- Deploy Aplikasi Anda dengan smbCloud
264
- </h3>
265
- <p className="mb-4 text-blue-50">
266
- Platform cloud terbaik untuk developer Indonesia. Deploy NodeJS,
267
- Swift, dan Ruby dengan mudah.
262
+ {/* ── CTA ─────────────────────────────────────────── */}
263
+ <div className="relative overflow-hidden rounded-2xl bg-indigo-600 px-8 py-12 text-center shadow-2xl shadow-indigo-500/20">
264
+ <div className="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_top,rgba(255,255,255,0.1),transparent)]" />
265
+ <h2 className="relative text-xl font-bold tracking-tight text-white">
266
+ Siap membangun dengan smbCloud?
267
+ </h2>
268
+ <p className="relative mt-2 text-sm text-indigo-200">
269
+ Deploy NodeJS, Swift, dan Ruby dengan satu perintah.
270
</p>
271
<Link
270
- href="/"
271
- className="inline-block bg-white text-blue-600 px-4 py-2 rounded-lg font-medium hover:bg-blue-50 transition-colors"
272
+ href="https://console.smbcloud.xyz"
273
+ target="_blank"
274
+ className="relative mt-6 inline-block rounded-lg bg-white px-6 py-2.5 text-sm font-semibold text-indigo-700 shadow-sm transition-colors hover:bg-indigo-50"
275
>
273
- Pelajari Lebih Lanjut →
276
+ Mulai dengan smbCloud →
277
</Link>
278
</div>
279
277
- {/* Metadata Footer */}
278
- <div className="mt-8 text-xs text-slate-500 dark:text-slate-500 not-prose">
279
- <p>
280
- Terakhir diperbarui:{" "}
281
- {new Date(kti.lastmod).toLocaleDateString("id-ID", {
282
- year: "numeric",
283
- month: "long",
284
- day: "numeric",
285
- })}
286
- </p>
287
- </div>
280
+ {/* ── Last updated ────────────────────────────────── */}
281
+ <p className="mt-8 text-center text-xs text-slate-400 dark:text-slate-600">
282
+ Terakhir diperbarui:{" "}
283
+ {new Date(kti.lastmod).toLocaleDateString("id-ID", {
284
+ year: "numeric",
285
+ month: "long",
286
+ day: "numeric",
287
+ })}
288
+ </p>
289
</div>
290
);
291
}
app/kti/page.tsx
+170
-209
@@ -32,21 +32,43 @@ export const metadata = {
32
},
33
};
34
35
+const difficultyConfig = {
36
+ pemula: {
37
+ label: "Pemula",
38
+ badge:
39
+ "bg-emerald-50 dark:bg-emerald-500/10 text-emerald-700 dark:text-emerald-400 border border-emerald-200 dark:border-emerald-500/20",
40
+ bar: "bg-emerald-500",
41
+ tag: "bg-emerald-50 dark:bg-emerald-500/10 text-emerald-700 dark:text-emerald-400 hover:bg-emerald-100 dark:hover:bg-emerald-500/20",
42
+ },
43
+ menengah: {
44
+ label: "Menengah",
45
+ badge:
46
+ "bg-amber-50 dark:bg-amber-500/10 text-amber-700 dark:text-amber-400 border border-amber-200 dark:border-amber-500/20",
47
+ bar: "bg-amber-500",
48
+ tag: "bg-amber-50 dark:bg-amber-500/10 text-amber-700 dark:text-amber-400 hover:bg-amber-100 dark:hover:bg-amber-500/20",
49
+ },
50
+ lanjut: {
51
+ label: "Lanjut",
52
+ badge:
53
+ "bg-indigo-50 dark:bg-indigo-500/10 text-indigo-700 dark:text-indigo-400 border border-indigo-200 dark:border-indigo-500/20",
54
+ bar: "bg-indigo-500",
55
+ tag: "bg-indigo-50 dark:bg-indigo-500/10 text-indigo-700 dark:text-indigo-400 hover:bg-indigo-100 dark:hover:bg-indigo-500/20",
56
+ },
57
+} as const;
58
+
59
+type Difficulty = keyof typeof difficultyConfig;
60
+
61
export default function KtiPage() {
36
- // Filter published KTI entries
62
let publishedKtis = allKtis.filter((kti) => kti.published);
63
39
- // Sort alphabetically by term
64
publishedKtis = publishedKtis.sort((a, b) =>
65
a.term.toLowerCase().localeCompare(b.term.toLowerCase()),
66
);
67
44
- // Group by category
68
const categories = Array.from(
69
new Set(publishedKtis.map((kti) => kti.category)),
70
).sort();
71
49
- // Group by difficulty
72
const difficultyGroups = {
73
pemula: publishedKtis.filter((kti) => kti.difficulty === "pemula"),
74
menengah: publishedKtis.filter((kti) => kti.difficulty === "menengah"),
@@ -54,72 +76,49 @@ export default function KtiPage() {
76
};
77
78
return (
57
- <div className="prose dark:prose-invert max-w-none">
58
- {/* Hero Section */}
59
- <div className="mb-8">
60
- <h1 className="text-4xl font-bold mb-4">
61
- 📚 Kamus Teknologi Informasi (KTI)
79
+ <div className="mx-auto max-w-5xl px-6 py-16 lg:px-8">
80
+ {/* ── Page header ─────────────────────────────────── */}
81
+ <div className="mb-16">
82
+ <p className="mb-4 text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600">
83
+ Referensi
84
+ </p>
85
+ <h1 className="text-4xl font-bold tracking-tight text-slate-900 dark:text-white sm:text-5xl">
86
+ Kamus Teknologi Informasi
87
</h1>
63
- <p className="text-xl text-slate-600 dark:text-slate-400">
88
+ <p className="mt-4 max-w-2xl text-lg text-slate-500 dark:text-slate-400">
89
Panduan lengkap istilah teknologi informasi dalam Bahasa Indonesia
90
untuk tenaga kerja muda dan profesional IT.
91
</p>
92
</div>
93
69
- {/* Introduction */}
70
- <div className="bg-blue-50 dark:bg-blue-950 p-6 rounded-lg mb-8 border-l-4 border-blue-500">
71
- <h2 className="mt-0 text-2xl">Mengapa KTI?</h2>
72
- <p className="mb-2">
73
- Dalam era transformasi digital, memahami istilah teknologi informasi
74
- adalah kunci sukses karir IT Anda. KTI hadir untuk:
75
- </p>
76
- <ul className="mb-0">
77
- <li>
78
- 🎯 <strong>Mempercepat pembelajaran</strong> - Pahami konsep IT
79
- dengan cepat
80
- </li>
81
- <li>
82
- 💼 <strong>Tingkatkan karir</strong> - Kuasai terminologi
83
- profesional
84
- </li>
85
- <li>
86
- 🌐 <strong>Bahasa Indonesia</strong> - Penjelasan mudah dipahami
87
- </li>
88
- <li>
89
- 🚀 <strong>Selalu update</strong> - Mengikuti perkembangan teknologi
90
- terkini
91
- </li>
92
- </ul>
93
- </div>
94
-
95
- {/* Statistics */}
96
- <div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8 not-prose">
97
- <div className="bg-gradient-to-br from-purple-500 to-purple-600 text-white p-4 rounded-lg shadow-lg">
98
- <div className="text-3xl font-bold">{publishedKtis.length}</div>
99
- <div className="text-sm">Total Istilah</div>
100
- </div>
101
- <div className="bg-gradient-to-br from-green-500 to-green-600 text-white p-4 rounded-lg shadow-lg">
102
- <div className="text-3xl font-bold">{categories.length}</div>
103
- <div className="text-sm">Kategori</div>
104
- </div>
105
- <div className="bg-gradient-to-br from-blue-500 to-blue-600 text-white p-4 rounded-lg shadow-lg">
106
- <div className="text-3xl font-bold">
107
- {difficultyGroups.pemula.length}
108
- </div>
109
- <div className="text-sm">Level Pemula</div>
110
- </div>
111
- <div className="bg-gradient-to-br from-orange-500 to-orange-600 text-white p-4 rounded-lg shadow-lg">
112
- <div className="text-3xl font-bold">
113
- {difficultyGroups.lanjut.length}
94
+ {/* ── Stats row ───────────────────────────────────── */}
95
+ <div className="mb-16 grid grid-cols-2 gap-4 sm:grid-cols-4">
96
+ {[
97
+ { value: publishedKtis.length, label: "Total Istilah" },
98
+ { value: categories.length, label: "Kategori" },
99
+ { value: difficultyGroups.pemula.length, label: "Level Pemula" },
100
+ { value: difficultyGroups.lanjut.length, label: "Level Lanjut" },
101
+ ].map((stat) => (
102
+ <div
103
+ key={stat.label}
104
+ className="rounded-xl border border-slate-200 dark:border-white/[0.06] bg-white dark:bg-white/[0.02] p-5"
105
+ >
106
+ <p className="text-3xl font-bold tracking-tight text-slate-900 dark:text-white">
107
+ {stat.value}
108
+ </p>
109
+ <p className="mt-1 text-xs text-slate-500 dark:text-slate-500">
110
+ {stat.label}
111
+ </p>
112
</div>
115
- <div className="text-sm">Level Lanjut</div>
116
- </div>
113
+ ))}
114
</div>
115
119
- {/* Browse by Category */}
120
- <div className="mb-12">
121
- <h2>🗂️ Telusuri berdasarkan Kategori</h2>
122
- <div className="grid grid-cols-1 md:grid-cols-2 gap-6 not-prose">
116
+ {/* ── Browse by category ──────────────────────────── */}
117
+ <section className="mb-16">
118
+ <p className="mb-8 text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600">
119
+ Berdasarkan Kategori
120
+ </p>
121
+ <div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
122
{categories.map((category) => {
123
const categoryKtis = publishedKtis.filter(
124
(kti) => kti.category === category,
@@ -127,26 +126,28 @@ export default function KtiPage() {
126
return (
127
<div
128
key={category}
130
- className="border border-slate-200 dark:border-slate-800 rounded-lg p-4 hover:shadow-lg transition-shadow"
129
+ className="rounded-xl border border-slate-200 dark:border-white/[0.06] bg-white dark:bg-white/[0.02] p-5 transition-colors hover:border-indigo-300 dark:hover:border-indigo-700/50"
130
>
132
- <h3 className="text-xl font-bold mb-3 text-slate-900 dark:text-slate-100">
133
- {category}
134
- </h3>
135
- <p className="text-sm text-slate-600 dark:text-slate-400 mb-3">
136
- {categoryKtis.length} istilah
137
- </p>
138
- <div className="flex flex-wrap gap-2">
131
+ <div className="mb-4 flex items-center justify-between">
132
+ <h3 className="font-semibold text-slate-900 dark:text-white">
133
+ {category}
134
+ </h3>
135
+ <span className="text-xs text-slate-400 dark:text-slate-600">
136
+ {categoryKtis.length} istilah
137
+ </span>
138
+ </div>
139
+ <div className="flex flex-wrap gap-1.5">
140
{categoryKtis.slice(0, 6).map((kti) => (
141
<Link
142
key={kti.term}
143
href={kti.slug}
143
- className="text-sm bg-slate-100 dark:bg-slate-800 px-3 py-1 rounded-full hover:bg-blue-100 dark:hover:bg-blue-900 transition-colors"
144
+ className="rounded-md bg-slate-100 dark:bg-white/[0.05] px-2.5 py-1 text-xs text-slate-600 dark:text-slate-400 transition-colors hover:bg-indigo-50 dark:hover:bg-indigo-500/10 hover:text-indigo-700 dark:hover:text-indigo-400"
145
>
146
{kti.term}
147
</Link>
148
))}
149
{categoryKtis.length > 6 && (
149
- <span className="text-sm text-slate-500 px-3 py-1">
150
+ <span className="px-2.5 py-1 text-xs text-slate-400 dark:text-slate-600">
151
+{categoryKtis.length - 6} lainnya
152
</span>
153
)}
@@ -155,153 +156,113 @@ export default function KtiPage() {
156
);
157
})}
158
</div>
158
- </div>
159
+ </section>
160
160
- {/* Browse by Difficulty */}
161
- <div className="mb-12">
162
- <h2>📊 Telusuri berdasarkan Level</h2>
161
+ {/* ── Browse by difficulty ────────────────────────── */}
162
+ <section className="mb-16">
163
+ <p className="mb-8 text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600">
164
+ Berdasarkan Level
165
+ </p>
166
<div className="space-y-6">
164
- {/* Pemula */}
165
- <div className="border-l-4 border-green-500 pl-4">
166
- <h3 className="text-lg font-bold text-green-600 dark:text-green-400">
167
- 🌱 Pemula ({difficultyGroups.pemula.length})
168
- </h3>
169
- <p className="text-sm text-slate-600 dark:text-slate-400 mb-2">
170
- Istilah dasar yang wajib dipahami untuk memulai karir IT
171
- </p>
172
- <div className="flex flex-wrap gap-2 not-prose">
173
- {difficultyGroups.pemula.slice(0, 10).map((kti) => (
174
- <Link
175
- key={kti.term}
176
- href={kti.slug}
177
- className="text-sm bg-green-50 dark:bg-green-950 text-green-700 dark:text-green-300 px-3 py-1 rounded-md hover:bg-green-100 dark:hover:bg-green-900 transition-colors"
178
- >
179
- {kti.term}
180
- </Link>
181
- ))}
182
- </div>
183
- </div>
184
-
185
- {/* Menengah */}
186
- <div className="border-l-4 border-blue-500 pl-4">
187
- <h3 className="text-lg font-bold text-blue-600 dark:text-blue-400">
188
- 📈 Menengah ({difficultyGroups.menengah.length})
189
- </h3>
190
- <p className="text-sm text-slate-600 dark:text-slate-400 mb-2">
191
- Konsep intermediate untuk meningkatkan keahlian teknis
192
- </p>
193
- <div className="flex flex-wrap gap-2 not-prose">
194
- {difficultyGroups.menengah.slice(0, 10).map((kti) => (
195
- <Link
196
- key={kti.term}
197
- href={kti.slug}
198
- className="text-sm bg-blue-50 dark:bg-blue-950 text-blue-700 dark:text-blue-300 px-3 py-1 rounded-md hover:bg-blue-100 dark:hover:bg-blue-900 transition-colors"
199
- >
200
- {kti.term}
201
- </Link>
202
- ))}
203
- </div>
204
- </div>
205
-
206
- {/* Lanjut */}
207
- <div className="border-l-4 border-purple-500 pl-4">
208
- <h3 className="text-lg font-bold text-purple-600 dark:text-purple-400">
209
- 🚀 Lanjut ({difficultyGroups.lanjut.length})
210
- </h3>
211
- <p className="text-sm text-slate-600 dark:text-slate-400 mb-2">
212
- Konsep advanced untuk profesional berpengalaman
213
- </p>
214
- <div className="flex flex-wrap gap-2 not-prose">
215
- {difficultyGroups.lanjut.slice(0, 10).map((kti) => (
216
- <Link
217
- key={kti.term}
218
- href={kti.slug}
219
- className="text-sm bg-purple-50 dark:bg-purple-950 text-purple-700 dark:text-purple-300 px-3 py-1 rounded-md hover:bg-purple-100 dark:hover:bg-purple-900 transition-colors"
220
- >
221
- {kti.term}
222
- </Link>
223
- ))}
224
- </div>
225
- </div>
226
- </div>
227
- </div>
228
-
229
- {/* All Terms Alphabetically */}
230
- <div className="mb-12">
231
- <h2>📖 Semua Istilah (A-Z)</h2>
232
- <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 not-prose">
233
- {publishedKtis.map((kti) => (
234
- <Link
235
- key={kti.term}
236
- href={kti.slug}
237
- className="group p-4 border border-slate-200 dark:border-slate-800 rounded-lg hover:border-blue-500 dark:hover:border-blue-500 hover:shadow-md transition-all"
238
- >
239
- <div className="flex items-start justify-between">
240
- <div className="flex-1">
241
- <h3 className="font-bold text-slate-900 dark:text-slate-100 group-hover:text-blue-600 dark:group-hover:text-blue-400 mb-1">
242
- {kti.term}
243
- </h3>
244
- {kti.englishTerm && (
245
- <p className="text-xs text-slate-500 dark:text-slate-500 mb-2">
246
- {kti.englishTerm}
247
- </p>
167
+ {(["pemula", "menengah", "lanjut"] as Difficulty[]).map((level) => {
168
+ const config = difficultyConfig[level];
169
+ const group = difficultyGroups[level];
170
+ return (
171
+ <div
172
+ key={level}
173
+ className="rounded-xl border border-slate-200 dark:border-white/[0.06] bg-white dark:bg-white/[0.02] p-5"
174
+ >
175
+ <div className="mb-4 flex items-center gap-3">
176
+ <span
177
+ className={`rounded-full px-2.5 py-0.5 text-xs font-semibold ${config.badge}`}
178
+ >
179
+ {config.label}
180
+ </span>
181
+ <span className="text-xs text-slate-400 dark:text-slate-600">
182
+ {group.length} istilah
183
+ </span>
184
+ </div>
185
+ <div className="flex flex-wrap gap-1.5">
186
+ {group.slice(0, 12).map((kti) => (
187
+ <Link
188
+ key={kti.term}
189
+ href={kti.slug}
190
+ className={`rounded-md px-2.5 py-1 text-xs font-medium transition-colors ${config.tag}`}
191
+ >
192
+ {kti.term}
193
+ </Link>
194
+ ))}
195
+ {group.length > 12 && (
196
+ <span className="px-2.5 py-1 text-xs text-slate-400 dark:text-slate-600">
197
+ +{group.length - 12} lainnya
198
+ </span>
199
)}
249
- <p className="text-sm text-slate-600 dark:text-slate-400 line-clamp-2">
250
- {kti.description}
251
- </p>
200
</div>
253
- <span
254
- className={`text-xs px-2 py-1 rounded-full ml-2 flex-shrink-0 ${
255
- kti.difficulty === "pemula"
256
- ? "bg-green-100 dark:bg-green-900 text-green-700 dark:text-green-300"
257
- : kti.difficulty === "menengah"
258
- ? "bg-blue-100 dark:bg-blue-900 text-blue-700 dark:text-blue-300"
259
- : "bg-purple-100 dark:bg-purple-900 text-purple-700 dark:text-purple-300"
260
- }`}
261
- >
262
- {kti.difficulty}
263
- </span>
201
</div>
265
- </Link>
266
- ))}
202
+ );
203
+ })}
204
</div>
268
- </div>
205
+ </section>
206
270
- {/* CTA Section */}
271
- <div className="bg-gradient-to-r from-blue-500 to-purple-600 text-white p-8 rounded-lg text-center not-prose">
272
- <h2 className="text-2xl font-bold mb-4 text-white">
273
- Siap Tingkatkan Karir IT Anda?
274
- </h2>
275
- <p className="mb-6 text-blue-50">
276
- Bergabunglah dengan ribuan profesional IT Indonesia yang menggunakan
277
- smbCloud untuk mengembangkan aplikasi mereka.
207
+ {/* ── All terms A–Z ───────────────────────────────── */}
208
+ <section className="mb-16">
209
+ <p className="mb-8 text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600">
210
+ Semua Istilah (A–Z)
211
</p>
279
- <Link
280
- href="https://console.smbcloud.xyz/login"
281
- target="__blank"
282
- className="inline-block bg-white text-blue-600 px-6 py-3 rounded-lg font-bold hover:bg-blue-50 transition-colors"
283
- >
284
- Mulai dengan smbCloud →
285
- </Link>
286
- </div>
212
+ <div className="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
213
+ {publishedKtis.map((kti) => {
214
+ const config =
215
+ difficultyConfig[kti.difficulty as Difficulty] ??
216
+ difficultyConfig.pemula;
217
+ return (
218
+ <Link
219
+ key={kti.term}
220
+ href={kti.slug}
221
+ className="group rounded-xl border border-slate-200 dark:border-white/[0.06] bg-white dark:bg-white/[0.02] p-4 transition-colors hover:border-indigo-300 dark:hover:border-indigo-700/50 hover:bg-slate-50 dark:hover:bg-white/[0.04]"
222
+ >
223
+ <div className="mb-2 flex items-start justify-between gap-2">
224
+ <h3 className="font-semibold text-slate-900 dark:text-white transition-colors group-hover:text-indigo-600 dark:group-hover:text-indigo-400">
225
+ {kti.term}
226
+ </h3>
227
+ <span
228
+ className={`shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold ${config.badge}`}
229
+ >
230
+ {config.label}
231
+ </span>
232
+ </div>
233
+ {kti.englishTerm && (
234
+ <p className="mb-1.5 text-xs text-slate-400 dark:text-slate-600">
235
+ {kti.englishTerm}
236
+ </p>
237
+ )}
238
+ <p className="line-clamp-2 text-sm leading-relaxed text-slate-500 dark:text-slate-400">
239
+ {kti.description}
240
+ </p>
241
+ </Link>
242
+ );
243
+ })}
244
+ </div>
245
+ </section>
246
288
- {/* SEO Content */}
289
- <div className="mt-12 text-sm text-slate-600 dark:text-slate-400">
290
- <h3>Tentang Kamus Teknologi Informasi</h3>
291
- <p>
292
- Kamus Teknologi Informasi (KTI) adalah sumber referensi komprehensif
293
- untuk tenaga kerja muda Indonesia yang ingin memahami istilah-istilah
294
- penting dalam dunia IT. Kami menyediakan penjelasan dalam Bahasa
295
- Indonesia yang mudah dipahami, mencakup berbagai topik seperti cloud
296
- computing, pemrograman, DevOps, keamanan siber, dan teknologi terkini
297
- lainnya.
298
- </p>
299
- <p>
300
- Setiap istilah dilengkapi dengan definisi, contoh penggunaan, dan
301
- hubungan dengan konsep lainnya untuk membantu Anda membangun pemahaman
302
- yang mendalam tentang ekosistem teknologi informasi modern.
303
- </p>
304
- </div>
247
+ {/* ── CTA ─────────────────────────────────────────── */}
248
+ <section>
249
+ <div className="relative overflow-hidden rounded-2xl bg-indigo-600 px-8 py-14 text-center shadow-2xl shadow-indigo-500/20">
250
+ <div className="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_top,rgba(255,255,255,0.1),transparent)]" />
251
+ <h2 className="relative text-2xl font-bold tracking-tight text-white">
252
+ Siap membangun dengan smbCloud?
253
+ </h2>
254
+ <p className="relative mt-3 text-indigo-200">
255
+ Deploy aplikasi NodeJS, Swift, dan Ruby dengan satu perintah.
256
+ </p>
257
+ <Link
258
+ href="https://console.smbcloud.xyz/login"
259
+ target="_blank"
260
+ className="relative mt-8 inline-block rounded-lg bg-white px-8 py-3.5 text-sm font-semibold text-indigo-700 shadow-sm transition-colors hover:bg-indigo-50"
261
+ >
262
+ Mulai dengan smbCloud →
263
+ </Link>
264
+ </div>
265
+ </section>
266
</div>
267
);
268
}
app/layout.tsx
+15
-9
@@ -1,14 +1,20 @@
1
+import type { Metadata } from "next";
2
import "./globals.css";
3
import { Inter } from "next/font/google";
4
import { ThemeProvider } from "@/components/theme-provider";
5
import Footer from "@/components/footer";
6
import Header from "@/components/header";
7
7
-const inter = Inter({ subsets: ["latin"] });
8
+const inter = Inter({
9
+ subsets: ["latin"],
10
+ variable: "--font-inter",
11
+ display: "swap",
12
+});
13
9
-export const metadata = {
10
- title: "smbCloud",
11
- description: "The next generation cloud computing platform",
14
+export const metadata: Metadata = {
15
+ title: "smbCloud — The unified cloud platform",
16
+ description:
17
+ "Deploy NodeJS, Swift, and Ruby apps with one command. SSH-based. Zero complexity.",
18
metadataBase: new URL("https://smbcloud.xyz"),
19
};
20
@@ -18,14 +24,14 @@ interface RootLayoutProps {
24
25
export default function RootLayout({ children }: RootLayoutProps) {
26
return (
21
- <html lang="en">
27
+ <html lang="en" suppressHydrationWarning>
28
<body
23
- className={`antialiased min-h-screen bg-white dark:bg-slate-950 text-slate-900 dark:text-slate-50 ${inter.className}`}
29
+ className={`${inter.variable} font-sans antialiased min-h-screen bg-white dark:bg-[#0A0E14] text-slate-900 dark:text-slate-50`}
30
>
25
- <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
26
- <div className="max-w-2xl mx-auto py-10 px-4">
31
+ <ThemeProvider attribute="class" defaultTheme="dark" enableSystem>
32
+ <div className="flex min-h-screen flex-col">
33
<Header />
28
- <main>{children}</main>
34
+ <main className="flex-1">{children}</main>
35
<Footer />
36
</div>
37
</ThemeProvider>
app/page.tsx
+255
-83
@@ -1,98 +1,270 @@
1
import { allPosts } from "@/.contentlayer/generated";
2
import Link from "next/link";
3
-import { genPageMetadata } from "./seo";
4
-import siteMetadata from "@/content/siteMetadata";
3
import TabbedRunner from "./components/TabbedRunner";
4
+import { FaApple, FaGooglePlay, FaWindows } from "react-icons/fa";
5
7
-export const metadata = {
8
- ...genPageMetadata({ title: `${siteMetadata.title}` }),
9
- metadataBase: new URL("https://smbcloud.xyz"),
10
-};
6
+const features = [
7
+ {
8
+ icon: "⚡",
9
+ title: "One-command deploys",
10
+ description:
11
+ "Run `smb` in your project directory. That’s it. Your app is live in seconds.",
12
+ },
13
+ {
14
+ icon: "🔐",
15
+ title: "SSH-based security",
16
+ description:
17
+ "No agents, no tokens. Pure SSH key auth for every deployment. Zero attack surface.",
18
+ },
19
+ {
20
+ icon: "🔄",
21
+ title: "Instant rollbacks",
22
+ description:
23
+ "Every deployment is versioned. Roll back to any previous version in one command.",
24
+ },
25
+ {
26
+ icon: "📊",
27
+ title: "Real-time monitoring",
28
+ description:
29
+ "Watch deployments unfold live. Logs, metrics, and health checks built in.",
30
+ },
31
+ {
32
+ icon: "🌐",
33
+ title: "Multi-runtime support",
34
+ description:
35
+ "NodeJS, Swift on Vapor, and Ruby on Rails — all supported out of the box.",
36
+ },
37
+ {
38
+ icon: "⚙️",
39
+ title: "Zero config",
40
+ description:
41
+ "Auto-detects your runtime. No YAML, no Dockerfiles, no DevOps degree required.",
42
+ },
43
+];
44
45
export default function Home() {
46
let publishedPosts = allPosts.filter((post) => post.published);
47
publishedPosts = publishedPosts.sort(
48
(a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(),
49
);
50
+
51
return (
18
- <div className="prose dark:prose-invert">
19
- <h1>smbCloud</h1>
20
- <div className="mb-4">
21
- <div className="inline-block px-4 py-2 rounded-lg bg-gradient-to-r from-blue-500 to-purple-500 text-white font-bold text-xl shadow-lg">
22
- The unified cloud platform for modern app deployment.
52
+ <div>
53
+ {/* ── Hero ───────────────────────────────────────────────────────── */}
54
+ <section className="relative overflow-hidden px-6 pt-24 pb-20 sm:pt-36 sm:pb-28 lg:px-8">
55
+ {/* Soft radial glow */}
56
+ <div className="pointer-events-none absolute inset-x-0 top-0 -z-10 h-[600px] bg-[radial-gradient(ellipse_80%_50%_at_50%_0%,rgba(99,102,241,0.18),transparent)] dark:bg-[radial-gradient(ellipse_80%_50%_at_50%_0%,rgba(99,102,241,0.12),transparent)]" />
57
+
58
+ <div className="mx-auto max-w-4xl text-center">
59
+ {/* Status badge */}
60
+ <div className="mb-8 inline-flex items-center gap-2 rounded-full border border-indigo-200 dark:border-indigo-500/30 bg-indigo-50 dark:bg-indigo-500/10 px-4 py-1.5">
61
+ <span className="h-1.5 w-1.5 animate-pulse rounded-full bg-indigo-500" />
62
+ <span className="text-xs font-semibold uppercase tracking-widest text-indigo-700 dark:text-indigo-400">
63
+ Cloud infrastructure for developers
64
+ </span>
65
+ </div>
66
+
67
+ <h1 className="text-5xl font-bold tracking-tight text-slate-900 dark:text-white sm:text-7xl leading-[1.05]">
68
+ Deploy with{" "}
69
+ <span className="text-indigo-600 dark:text-indigo-400">
70
+ one command.
71
+ </span>
72
+ </h1>
73
+
74
+ <p className="mx-auto mt-8 max-w-2xl text-lg leading-8 text-slate-600 dark:text-slate-400">
75
+ The unified cloud platform for{" "}
76
+ <span className="font-semibold text-slate-800 dark:text-slate-200">
77
+ NodeJS
78
+ </span>
79
+ ,{" "}
80
+ <span className="font-semibold text-slate-800 dark:text-slate-200">
81
+ Swift
82
+ </span>
83
+ , and{" "}
84
+ <span className="font-semibold text-slate-800 dark:text-slate-200">
85
+ Ruby
86
+ </span>{" "}
87
+ apps. No Docker. No config files. Just ship.
88
+ </p>
89
+
90
+ <div className="mt-10 flex flex-col items-center justify-center gap-4 sm:flex-row">
91
+ <Link
92
+ href="https://console.smbcloud.xyz"
93
+ target="_blank"
94
+ className="w-full rounded-lg bg-indigo-600 px-8 py-3.5 text-sm font-semibold text-white shadow-lg shadow-indigo-500/20 transition-all hover:bg-indigo-500 hover:shadow-indigo-500/30 active:scale-[0.98] sm:w-auto"
95
+ >
96
+ Open Console →
97
+ </Link>
98
+ <Link
99
+ href="/posts"
100
+ className="flex w-full items-center justify-center gap-1.5 text-sm font-semibold text-slate-600 dark:text-slate-400 transition-colors hover:text-slate-900 dark:hover:text-white sm:w-auto"
101
+ >
102
+ Read the docs
103
+ </Link>
104
+ </div>
105
+ </div>
106
+ </section>
107
+
108
+ {/* ── Terminal Demo ─────────────────────────────────────────────── */}
109
+ <section className="px-6 pb-28 lg:px-8">
110
+ <div className="mx-auto max-w-3xl">
111
+ <p className="mb-8 text-center text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600">
112
+ One tool · Every runtime
113
+ </p>
114
+ <TabbedRunner />
115
+ </div>
116
+ </section>
117
+
118
+ {/* ── Features ──────────────────────────────────────────────────────────── */}
119
+ <section className="border-t border-slate-200 dark:border-white/[0.06] px-6 py-24 lg:px-8">
120
+ <div className="mx-auto max-w-5xl">
121
+ <div className="mb-16 text-center">
122
+ <h2 className="text-3xl font-bold tracking-tight text-slate-900 dark:text-white">
123
+ Everything you need to ship.
124
+ </h2>
125
+ <p className="mx-auto mt-4 max-w-xl text-slate-500 dark:text-slate-400">
126
+ Built for engineering teams who move fast and care about
127
+ reliability.
128
+ </p>
129
+ </div>
130
+
131
+ <div className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
132
+ {features.map((feature) => (
133
+ <div
134
+ key={feature.title}
135
+ className="rounded-xl border border-slate-200 dark:border-white/[0.06] bg-white dark:bg-white/[0.02] p-6 transition-colors hover:border-indigo-300 dark:hover:border-indigo-700/50 hover:bg-slate-50 dark:hover:bg-white/[0.04]"
136
+ >
137
+ <div className="mb-4 text-2xl">{feature.icon}</div>
138
+ <h3 className="mb-2 font-semibold text-slate-900 dark:text-white">
139
+ {feature.title}
140
+ </h3>
141
+ <p className="text-sm leading-relaxed text-slate-500 dark:text-slate-400">
142
+ {feature.description}
143
+ </p>
144
+ </div>
145
+ ))}
146
+ </div>
147
</div>
24
- </div>
25
- <p className="text-lg">
26
- Deploy your{" "}
27
- <a href="https://nodejs.org/" target="_blank" rel="noopener noreferrer">
28
- NodeJS
29
- </a>{" "}
30
- <em>
31
- (with{" "}
32
- <a
33
- href="https://nextjs.org/"
34
- target="_blank"
35
- rel="noopener noreferrer"
36
- >
37
- NextJS
38
- </a>{" "}
39
- or{" "}
40
- <a
41
- href="https://astro.build/"
42
- target="_blank"
43
- rel="noopener noreferrer"
44
- >
45
- Astro
46
- </a>
47
- )
48
- </em>
49
- ,{" "}
50
- <a href="https://swift.org/" target="_blank" rel="noopener noreferrer">
51
- Swift
52
- </a>{" "}
53
- <em>
54
- (via{" "}
55
- <a
56
- href="https://vapor.codes/"
57
- target="_blank"
58
- rel="noopener noreferrer"
59
- >
60
- Vapor
61
- </a>
62
- )
63
- </em>
64
- , and{" "}
65
- <a
66
- href="https://www.ruby-lang.org/"
67
- target="_blank"
68
- rel="noopener noreferrer"
69
- >
70
- Ruby
71
- </a>{" "}
72
- <em>
73
- (on{" "}
74
- <a
75
- href="https://rubyonrails.org/"
76
- target="_blank"
77
- rel="noopener noreferrer"
78
- >
79
- Rails
80
- </a>
81
- )
82
- </em>{" "}
83
- app with one command:{" "}
84
- </p>
85
- <TabbedRunner />
86
- <hr />
87
- <h2>Resources</h2>
88
- {publishedPosts.map((post) => (
89
- <article key={post._id}>
90
- <Link href={post.slug}>
91
- <h2>{post.title}</h2>
92
- </Link>
93
- {post.description && <p>{post.description}</p>}
94
- </article>
95
- ))}
148
+ </section>
149
+
150
+ {/* ── CTA banner ──────────────────────────────────────────────────── */}
151
+ <section className="px-6 py-16 lg:px-8">
152
+ <div className="mx-auto max-w-2xl">
153
+ <div className="relative overflow-hidden rounded-2xl bg-indigo-600 px-8 py-14 text-center shadow-2xl shadow-indigo-500/20">
154
+ {/* Inner glow */}
155
+ <div className="pointer-events-none absolute inset-0 bg-[radial-gradient(ellipse_at_top,rgba(255,255,255,0.1),transparent)]" />
156
+ <h2 className="relative text-3xl font-bold tracking-tight text-white">
157
+ Ready to deploy?
158
+ </h2>
159
+ <p className="relative mt-4 text-indigo-200">
160
+ Free to start. Scale as you grow. No credit card required.
161
+ </p>
162
+ <Link
163
+ href="https://console.smbcloud.xyz"
164
+ target="_blank"
165
+ className="relative mt-8 inline-block rounded-lg bg-white px-8 py-3.5 text-sm font-semibold text-indigo-700 shadow-sm transition-colors hover:bg-indigo-50"
166
+ >
167
+ Create free account →
168
+ </Link>
169
+ </div>
170
+ </div>
171
+ </section>
172
+
173
+ {/* ── siGit CTA ───────────────────────────────────────────────────────── */}
174
+ <section className="px-6 pb-16 lg:px-8">
175
+ <div className="mx-auto max-w-2xl">
176
+ <div className="relative overflow-hidden rounded-2xl bg-slate-900 dark:bg-white/[0.04] border border-slate-800 dark:border-white/[0.08] px-8 py-12">
177
+ {/* Subtle top highlight */}
178
+ <div className="pointer-events-none absolute inset-x-0 top-0 h-px bg-gradient-to-r from-transparent via-white/20 to-transparent" />
179
+
180
+ <p className="mb-5 text-xs font-bold uppercase tracking-[0.2em] text-slate-500">
181
+ Also from smbCloud
182
+ </p>
183
+
184
+ <div className="mb-4 flex items-center gap-3">
185
+ {/* siGit mark */}
186
+ <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-xl bg-white text-slate-900">
187
+ <span className="text-xs font-black tracking-tighter leading-none">
188
+ si
189
+ </span>
190
+ </div>
191
+ <h2 className="text-2xl font-bold tracking-tight text-white">
192
+ siGit Code & Deploy
193
+ </h2>
194
+ </div>
195
+
196
+ <p className="mb-2 max-w-md text-sm leading-relaxed text-slate-300 dark:text-slate-400">
197
+ <span className="font-semibold text-white">smbcloud-cli</span>,
198
+ reimagined as a beautiful native experience. Forget the friction.
199
+ Forget the command line hassle.
200
+ </p>
201
+ <p className="mb-8 text-xs italic text-slate-500">
202
+ Stay hungry, keep deploying.
203
+ </p>
204
+
205
+ {/* Platform buttons */}
206
+ <div className="flex flex-wrap gap-3">
207
+ <a
208
+ href="https://apps.apple.com/se/app/sigit-git-client-code-editor/id6753018849"
209
+ target="_blank"
210
+ rel="noopener noreferrer"
211
+ className="inline-flex items-center gap-2 rounded-lg border border-white/20 bg-white/10 px-4 py-2.5 text-xs font-semibold text-white transition-colors hover:bg-white/20"
212
+ >
213
+ <FaApple className="h-4 w-4" />
214
+ App Store
215
+ </a>
216
+ <a
217
+ href="https://play.google.com/store/apps/details?id=xyz.smbcloud.SiGit"
218
+ target="_blank"
219
+ rel="noopener noreferrer"
220
+ className="inline-flex items-center gap-2 rounded-lg border border-white/20 bg-white/10 px-4 py-2.5 text-xs font-semibold text-white transition-colors hover:bg-white/20"
221
+ >
222
+ <FaGooglePlay className="h-4 w-4" />
223
+ Google Play
224
+ </a>
225
+ <a
226
+ href="https://apps.microsoft.com/detail/xp9k321rf5mzsz?ocid=webpdpshare"
227
+ target="_blank"
228
+ rel="noopener noreferrer"
229
+ className="inline-flex items-center gap-2 rounded-lg border border-white/20 bg-white/10 px-4 py-2.5 text-xs font-semibold text-white transition-colors hover:bg-white/20"
230
+ >
231
+ <FaWindows className="h-4 w-4" />
232
+ Microsoft Store
233
+ </a>
234
+ </div>
235
+ </div>
236
+ </div>
237
+ </section>
238
+
239
+ {/* ── Blog Posts ───────────────────────────────────────────────────────── */}
240
+ {publishedPosts.length > 0 && (
241
+ <section className="border-t border-slate-200 dark:border-white/[0.06] px-6 py-24 lg:px-8">
242
+ <div className="mx-auto max-w-5xl">
243
+ <p className="mb-10 text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600">
244
+ From the blog
245
+ </p>
246
+ <div className="space-y-6">
247
+ {publishedPosts.slice(0, 3).map((post) => (
248
+ <article
249
+ key={post._id}
250
+ className="group border-b border-slate-200 dark:border-white/[0.06] pb-6 last:border-0"
251
+ >
252
+ <Link href={post.slug} className="block">
253
+ <h3 className="font-semibold text-slate-900 dark:text-white transition-colors group-hover:text-indigo-600 dark:group-hover:text-indigo-400">
254
+ {post.title}
255
+ </h3>
256
+ {post.description && (
257
+ <p className="mt-1 text-sm text-slate-500 dark:text-slate-400">
258
+ {post.description}
259
+ </p>
260
+ )}
261
+ </Link>
262
+ </article>
263
+ ))}
264
+ </div>
265
+ </div>
266
+ </section>
267
+ )}
268
</div>
269
);
270
}
app/posts/[...slug]/page.tsx
+17
-15
@@ -53,20 +53,22 @@ export default async function PostPage(props: PostProps) {
53
}
54
55
return (
56
- <article className="py-6 prose dark:prose-invert">
57
- <h1 className="mb-2">{post.title}</h1>
58
- {post.description && (
59
- <p className="text-xl mt-0 text-slate-700 dark:text-slate-200">
60
- {post.description}
61
- </p>
62
- )}
63
- {post.lastmod && (
64
- <p className="italic text-base text-gray-500 mt-2 mb-0">
65
- Last updated: {new Date(post.lastmod).toLocaleDateString()}
66
- </p>
67
- )}
68
- <hr className="my-4" />
69
- <Mdx code={post.body.code} />
70
- </article>
56
+ <div className="mx-auto max-w-3xl px-6 py-12 lg:px-8">
57
+ <article className="prose dark:prose-invert max-w-none">
58
+ <h1 className="mb-2">{post.title}</h1>
59
+ {post.description && (
60
+ <p className="text-xl mt-0 text-slate-700 dark:text-slate-200">
61
+ {post.description}
62
+ </p>
63
+ )}
64
+ {post.lastmod && (
65
+ <p className="italic text-base text-gray-500 mt-2 mb-0">
66
+ Last updated: {new Date(post.lastmod).toLocaleDateString()}
67
+ </p>
68
+ )}
69
+ <hr className="my-4" />
70
+ <Mdx code={post.body.code} />
71
+ </article>
72
+ </div>
73
);
74
}
app/posts/page.tsx
new
+81
@@ -0,0 +1,81 @@
1
+import { allPosts } from "@/.contentlayer/generated";
2
+import Link from "next/link";
3
+import { genPageMetadata } from "../seo";
4
+
5
+export const metadata = {
6
+ ...genPageMetadata({
7
+ title: "Posts",
8
+ description: "Updates, guides, and deep dives from the smbCloud team.",
9
+ }),
10
+ metadataBase: new URL("https://smbcloud.xyz"),
11
+};
12
+
13
+export default function PostsPage() {
14
+ let publishedPosts = allPosts.filter((post) => post.published);
15
+ publishedPosts = publishedPosts.sort(
16
+ (a, b) => new Date(b.date).getTime() - new Date(a.date).getTime(),
17
+ );
18
+
19
+ return (
20
+ <div className="mx-auto max-w-3xl px-6 py-16 lg:px-8">
21
+ {/* Page header */}
22
+ <div className="mb-14">
23
+ <p className="mb-3 text-xs font-bold uppercase tracking-[0.2em] text-slate-400 dark:text-slate-600">
24
+ Blog
25
+ </p>
26
+ <h1 className="text-3xl font-bold tracking-tight text-slate-900 dark:text-white">
27
+ Posts
28
+ </h1>
29
+ <p className="mt-3 text-slate-500 dark:text-slate-400">
30
+ Updates, guides, and deep dives from the smbCloud team.
31
+ </p>
32
+ </div>
33
+
34
+ {/* Post list */}
35
+ {publishedPosts.length === 0 ? (
36
+ <p className="text-sm text-slate-400 dark:text-slate-600">
37
+ No posts published yet. Check back soon.
38
+ </p>
39
+ ) : (
40
+ <div className="space-y-0">
41
+ {publishedPosts.map((post) => (
42
+ <article
43
+ key={post._id}
44
+ className="group border-b border-slate-200 dark:border-white/[0.06] py-8 first:pt-0 last:border-0"
45
+ >
46
+ <Link href={post.slug} className="block">
47
+ <div className="flex items-start justify-between gap-6">
48
+ <div className="flex-1 min-w-0">
49
+ {post.date && (
50
+ <time
51
+ dateTime={post.date}
52
+ className="mb-2 block text-xs text-slate-400 dark:text-slate-600 tabular-nums"
53
+ >
54
+ {new Date(post.date).toLocaleDateString("en-US", {
55
+ year: "numeric",
56
+ month: "long",
57
+ day: "numeric",
58
+ })}
59
+ </time>
60
+ )}
61
+ <h2 className="font-semibold text-slate-900 dark:text-white transition-colors group-hover:text-indigo-600 dark:group-hover:text-indigo-400">
62
+ {post.title}
63
+ </h2>
64
+ {post.description && (
65
+ <p className="mt-1.5 text-sm leading-relaxed text-slate-500 dark:text-slate-400 line-clamp-2">
66
+ {post.description}
67
+ </p>
68
+ )}
69
+ </div>
70
+ <span className="mt-1 shrink-0 text-slate-300 dark:text-slate-700 transition-colors group-hover:text-indigo-500 dark:group-hover:text-indigo-500">
71
+ →
72
+ </span>
73
+ </div>
74
+ </Link>
75
+ </article>
76
+ ))}
77
+ </div>
78
+ )}
79
+ </div>
80
+ );
81
+}
components/about-media.tsx
+56
-21
@@ -1,29 +1,64 @@
1
-import Image from "next/image";
1
import Link from "next/link";
3
-import { FaYoutube, FaInstagram, FaFacebook } from "react-icons/fa";
2
+import { FaYoutube, FaInstagram, FaFacebook, FaGithub } from "react-icons/fa";
3
+
4
+const socials = [
5
+ {
6
+ href: "https://github.com/smbcloudXYZ",
7
+ label: "GitHub",
8
+ icon: FaGithub,
9
+ },
10
+ {
11
+ href: "https://www.youtube.com/@smbcloudXYZ",
12
+ label: "YouTube",
13
+ icon: FaYoutube,
14
+ },
15
+ {
16
+ href: "https://instagram.com/smbcloudXYZ",
17
+ label: "Instagram",
18
+ icon: FaInstagram,
19
+ },
20
+ {
21
+ href: "https://facebook.com/smbcloudXYZ",
22
+ label: "Facebook",
23
+ icon: FaFacebook,
24
+ },
25
+];
26
27
export default function AboutMedia() {
28
return (
7
- <div className="text-center mb-10">
8
- <div className="flex justify-center">
9
- <Image
10
- src={"/logo.png"}
11
- alt={"smbCloud logo"}
12
- width={100}
13
- height={100}
14
- />
29
+ <div className="mb-12 flex flex-col items-start gap-6 rounded-xl border border-slate-200 dark:border-white/[0.06] bg-white dark:bg-white/[0.02] p-6 sm:flex-row sm:items-center">
30
+ {/* Brand mark */}
31
+ <div className="flex h-14 w-14 shrink-0 items-center justify-center rounded-xl bg-indigo-600 text-white shadow-lg shadow-indigo-500/20">
32
+ <span className="text-sm font-black tracking-tighter leading-none">
33
+ smb
34
+ </span>
35
</div>
16
- <div className="flex justify-center space-x-4">
17
- <Link href={"https://www.youtube.com/@smbcloudXYZ"} target="_blank" title="smbCloud YouTube channel">
18
- <FaYoutube size={30} />
19
- </Link>
20
- <Link href={"https://instagram.com/smbcloudXYZ"} target="_blank" title="smbCloud Instagram account">
21
- <FaInstagram size={30} />
22
- </Link>
23
- <Link href={"https://facebook.com/smbcloudXYZ"} target="_blank" title="smbCloud Facebook page">
24
- <FaFacebook size={30} />
25
- </Link>
36
+
37
+ <div className="flex-1 min-w-0">
38
+ <p className="font-semibold text-slate-900 dark:text-white tracking-tight">
39
+ smbCloud
40
+ </p>
41
+ <p className="mt-0.5 text-sm text-slate-500 dark:text-slate-400">
42
+ The unified cloud platform for modern app deployment.
43
+ </p>
44
+
45
+ {/* Social links */}
46
+ <div className="mt-4 flex flex-wrap items-center gap-2">
47
+ {socials.map(({ href, label, icon: Icon }) => (
48
+ <Link
49
+ key={label}
50
+ href={href}
51
+ target="_blank"
52
+ rel="noopener noreferrer"
53
+ title={`smbCloud on ${label}`}
54
+ className="inline-flex items-center gap-1.5 rounded-md border border-slate-200 dark:border-white/[0.08] bg-slate-50 dark:bg-white/[0.03] px-3 py-1.5 text-xs font-medium text-slate-600 dark:text-slate-400 transition-colors hover:border-indigo-300 dark:hover:border-indigo-700/50 hover:text-indigo-600 dark:hover:text-indigo-400"
55
+ >
56
+ <Icon className="h-3.5 w-3.5" />
57
+ {label}
58
+ </Link>
59
+ ))}
60
+ </div>
61
</div>
62
</div>
63
);
29
-}
\ No newline at end of file
64
+}
components/footer.tsx
+70
-37
@@ -1,46 +1,79 @@
1
-// Generate footer
2
-
1
import Link from "next/link";
2
import { FaGithub } from "react-icons/fa";
5
-import { FcDebian } from "react-icons/fc";
3
4
export default function Footer() {
5
return (
9
- <footer className="py-8 px-8 text-center text-gray-500 text-sm">
10
- <div className="flex justify-center space-x-4 m-2 mb-8">
11
- <Link
12
- href={"https://github.com/smbcloudXYZ"}
13
- target="_blank"
14
- title="smbCloud GitHub organization"
15
- >
16
- <FaGithub size={30} className="m-2" />
17
- </Link>
18
- <Link
19
- href={"https://debian.org"}
20
- target="_blank"
21
- title="smbCloud is powered by Debian"
22
- >
23
- <FcDebian size={30} className="m-2" />
24
- </Link>
25
- </div>
26
- <div className="flex justify-center space-x-4 m-2">
27
- <Link href="/about" title="smbCloud about page">
28
- About
29
- </Link>
30
- <Link href="/privacy-policy" title="smbCloud privacy policy page">
31
- Privacy Policy
32
- </Link>
33
- <Link href="/term-of-use" title="smbCloud term of use page">
34
- Term of Use
35
- </Link>
36
- <Link href="/contact" title="smbCloud contact page">
37
- Contact
38
- </Link>
6
+ <footer className="border-t border-slate-200 dark:border-white/[0.06] px-6 py-12 lg:px-8">
7
+ <div className="mx-auto max-w-5xl">
8
+ <div className="flex flex-col gap-8 sm:flex-row sm:items-start sm:justify-between">
9
+ {/* Brand */}
10
+ <div>
11
+ <Link href="/" className="mb-3 flex items-center gap-2">
12
+ <div className="flex h-6 w-[30px] items-center justify-center rounded bg-indigo-600 text-white">
13
+ <span className="text-[9px] font-bold leading-none">smb</span>
14
+ </div>
15
+ <span className="text-sm font-semibold text-slate-900 dark:text-white tracking-tight">
16
+ smbCloud
17
+ </span>
18
+ </Link>
19
+ <p className="max-w-xs text-xs leading-relaxed text-slate-500 dark:text-slate-500">
20
+ The unified cloud platform for modern app deployment.
21
+ </p>
22
+ </div>
23
+
24
+ {/* Links */}
25
+ <div className="flex flex-wrap gap-x-8 gap-y-3 text-sm text-slate-500 dark:text-slate-500">
26
+ <Link
27
+ href="/about"
28
+ className="hover:text-slate-900 dark:hover:text-slate-300 transition-colors"
29
+ >
30
+ About
31
+ </Link>
32
+ <Link
33
+ href="/privacy-policy"
34
+ className="hover:text-slate-900 dark:hover:text-slate-300 transition-colors"
35
+ >
36
+ Privacy
37
+ </Link>
38
+ <Link
39
+ href="/term-of-use"
40
+ className="hover:text-slate-900 dark:hover:text-slate-300 transition-colors"
41
+ >
42
+ Terms
43
+ </Link>
44
+ <Link
45
+ href="/contact"
46
+ className="hover:text-slate-900 dark:hover:text-slate-300 transition-colors"
47
+ >
48
+ Contact
49
+ </Link>
50
+ <Link
51
+ href="https://github.com/smbcloudXYZ"
52
+ target="_blank"
53
+ className="flex items-center gap-1.5 hover:text-slate-900 dark:hover:text-slate-300 transition-colors"
54
+ >
55
+ <FaGithub className="h-3.5 w-3.5" />
56
+ GitHub
57
+ </Link>
58
+ </div>
59
+ </div>
60
+
61
+ <div className="mt-10 flex items-center justify-between border-t border-slate-200 dark:border-white/[0.06] pt-6">
62
+ <p className="text-xs text-slate-400 dark:text-slate-600">
63
+ © {new Date().getFullYear()} smbCloud. All rights reserved.
64
+ </p>
65
+ <p className="text-xs text-slate-400 dark:text-slate-600">
66
+ Powered by{" "}
67
+ <a
68
+ href="https://debian.org"
69
+ target="_blank"
70
+ className="font-medium hover:text-slate-600 dark:hover:text-slate-400 transition-colors"
71
+ >
72
+ Debian
73
+ </a>
74
+ </p>
75
+ </div>
76
</div>
40
- <p className="m-2">
41
- © {new Date().getFullYear()}{" "}
42
- <a href="https://smbcloud.xyz">smbCloud</a>
43
- </p>
77
</footer>
78
);
79
}
components/header.tsx
+48
-9
@@ -3,16 +3,55 @@ import { ModeToggle } from "./mode-toggle";
3
4
export default function Header() {
5
return (
6
- <header className="mt-4 mb-8">
7
- <div className="flex items-center justify-between">
8
- <ModeToggle />
9
- <nav className="ml-auto text-sm font-medium space-x-6">
10
- <Link href="/">Home</Link>
11
- <Link href="/kti">KTI</Link>
12
- <Link href="https://console.smbcloud.xyz" target="__blank">
13
- Console
6
+ <header className="sticky top-0 z-50 border-b border-slate-200/60 dark:border-white/[0.06] bg-white/80 dark:bg-[#0A0E14]/90 backdrop-blur-md">
7
+ <div className="mx-auto max-w-5xl px-6 lg:px-8">
8
+ <div className="flex h-14 items-center justify-between">
9
+ {/* Logo wordmark */}
10
+ <Link href="/" className="flex items-center gap-2.5">
11
+ <div className="flex h-7 w-[38px] items-center justify-center rounded-md bg-indigo-600 text-white">
12
+ <span className="text-[10px] font-bold tracking-tight leading-none">
13
+ smb
14
+ </span>
15
+ </div>
16
+ <span className="font-semibold text-slate-900 dark:text-white tracking-tight text-sm">
17
+ smbCloud
18
+ </span>
19
</Link>
15
- </nav>
20
+
21
+ {/* Center nav */}
22
+ <nav className="hidden sm:flex items-center gap-6 text-sm font-medium text-slate-500 dark:text-slate-400">
23
+ <Link
24
+ href="/"
25
+ className="hover:text-slate-900 dark:hover:text-white transition-colors"
26
+ >
27
+ Home
28
+ </Link>
29
+ <Link
30
+ href="/kti"
31
+ className="hover:text-slate-900 dark:hover:text-white transition-colors"
32
+ >
33
+ KTI
34
+ </Link>
35
+ <Link
36
+ href="/posts"
37
+ className="hover:text-slate-900 dark:hover:text-white transition-colors"
38
+ >
39
+ Posts
40
+ </Link>
41
+ </nav>
42
+
43
+ {/* Right side */}
44
+ <div className="flex items-center gap-3">
45
+ <ModeToggle />
46
+ <Link
47
+ href="https://console.smbcloud.xyz"
48
+ target="_blank"
49
+ className="hidden sm:inline-flex items-center rounded-md bg-slate-900 dark:bg-white px-3.5 py-1.5 text-xs font-semibold text-white dark:text-slate-900 hover:bg-slate-700 dark:hover:bg-slate-100 transition-colors"
50
+ >
51
+ Console →
52
+ </Link>
53
+ </div>
54
+ </div>
55
</div>
56
</header>
57
);
content/posts/new-look.mdx
new
+47
@@ -0,0 +1,47 @@
1
+---
2
+title: "A new look for smbCloud"
3
+description: "We've redesigned smbcloud.xyz from the ground up — cleaner, faster, and built for developers."
4
+date: "2026-04-05"
5
+lastmod: "2026-04-05"
6
+published: true
7
+---
8
+
9
+Today we're shipping a full redesign of [smbcloud.xyz](https://smbcloud.xyz) and the [smbCloud Console](https://console.smbcloud.xyz).
10
+
11
+## Why we redesigned
12
+
13
+The old site was built to get something out the door quickly. It did the job, but it didn't reflect what smbCloud has grown into — a production cloud platform used by real teams deploying real apps every day.
14
+
15
+We wanted a design that felt as precise and reliable as the infrastructure underneath it.
16
+
17
+## What's new
18
+
19
+### A unified design system
20
+
21
+Both the marketing site and the console now share the same design tokens — colors, spacing, typography, and component patterns. Whether you're reading about smbCloud or deploying inside the console, it feels like the same product.
22
+
23
+### Built for dark mode
24
+
25
+The new site defaults to dark mode. It's easier on the eyes at 2am when you're debugging a deployment, which is exactly when you're likely to be here.
26
+
27
+### A cleaner homepage
28
+
29
+The new homepage cuts straight to what matters: what smbCloud does, how it works, and how to get started. The terminal demo now shows exactly what a `smb` deploy looks like across NodeJS, Swift, and Ruby — no prose needed.
30
+
31
+### Improved navigation
32
+
33
+Posts, KTI, and the Console link are all a single click from anywhere on the site. The header is sticky and stays out of your way.
34
+
35
+### siGit
36
+
37
+We also added a proper section for [siGit Code & Deploy](https://apps.apple.com/se/app/sigit-git-client-code-editor/id6753018849) — our native vibe coding app for iOS, Android, and Windows. It's `smbcloud-cli` reimagined as a beautiful, native experience. If you haven't tried it yet, you should.
38
+
39
+## What didn't change
40
+
41
+The platform itself. Deployments still work the same way. Your SSH keys, your projects, your apps — nothing moved.
42
+
43
+## What's next
44
+
45
+We're working on expanded documentation, a proper onboarding flow for new projects, and tighter integration between the console and the CLI. More soon.
46
+
47
+— The smbCloud team
\ No newline at end of file
next-env.d.ts
+1
-1
@@ -1,6 +1,6 @@
1
/// <reference types="next" />
2
/// <reference types="next/image-types/global" />
3
-import "./.next/types/routes.d.ts";
3
+import "./.next/dev/types/routes.d.ts";
4
5
// NOTE: This file should not be edited
6
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
tailwind.config.ts
+22
-2
@@ -1,4 +1,4 @@
1
-/** @type {import('tailwindcss').Config} */
1
+/** @type {import("tailwindcss").Config} */
2
module.exports = {
3
content: [
4
"./app/**/*.{ts,tsx}",
@@ -7,7 +7,27 @@ module.exports = {
7
],
8
darkMode: ["class"],
9
theme: {
10
- extend: {},
10
+ extend: {
11
+ colors: {
12
+ brand: {
13
+ DEFAULT: "#E11D48",
14
+ 50: "#FFF1F2",
15
+ 100: "#FFE4E6",
16
+ 200: "#FECDD3",
17
+ 300: "#FDA4AF",
18
+ 400: "#FB7185",
19
+ 500: "#F43F5E",
20
+ 600: "#E11D48",
21
+ 700: "#BE123C",
22
+ 800: "#9F1239",
23
+ 900: "#881337",
24
+ },
25
+ },
26
+ backgroundImage: {
27
+ "hero-glow":
28
+ "radial-gradient(ellipse 80% 50% at 50% 0%, rgba(99,102,241,0.15), transparent)",
29
+ },
30
+ },
31
},
32
plugins: [require("@tailwindcss/typography")],
33
}