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 - &copy; {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 }