From 4dbaba75293eefc61833fe8fbf79f0e389a695c4 Mon Sep 17 00:00:00 2001 From: asepharyana Date: Thu, 23 Jul 2026 15:53:56 +0700 Subject: [PATCH] feat: update global styles and layout with new font and theme support; enhance dashboard header with theme toggle --- src/app/globals.css | 141 +++++++++++---------- src/app/layout.tsx | 10 +- src/app/page.tsx | 185 +++++++++++++++------------- src/components/dashboard/header.tsx | 13 ++ src/components/ui/card.tsx | 2 +- 5 files changed, 193 insertions(+), 158 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index 6a25b1b..9fc4131 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -7,9 +7,9 @@ @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); - --font-sans: var(--font-sans); + --font-sans: var(--font-geist-sans); --font-mono: var(--font-geist-mono); - --font-heading: var(--font-sans); + --font-heading: var(--font-geist-sans); --color-sidebar-ring: var(--sidebar-ring); --color-sidebar-border: var(--sidebar-border); --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); @@ -49,72 +49,72 @@ } :root { - --background: oklch(1 0 0); - --foreground: oklch(0.145 0 0); - --card: oklch(1 0 0); - --card-foreground: oklch(0.145 0 0); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.205 0 0); - --primary-foreground: oklch(0.985 0 0); - --secondary: oklch(0.97 0 0); - --secondary-foreground: oklch(0.205 0 0); - --muted: oklch(0.97 0 0); - --muted-foreground: oklch(0.556 0 0); - --accent: oklch(0.97 0 0); - --accent-foreground: oklch(0.205 0 0); - --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.922 0 0); - --input: oklch(0.922 0 0); - --ring: oklch(0.708 0 0); - --chart-1: oklch(0.87 0 0); - --chart-2: oklch(0.556 0 0); - --chart-3: oklch(0.439 0 0); - --chart-4: oklch(0.371 0 0); - --chart-5: oklch(0.269 0 0); - --radius: 0.625rem; - --sidebar: oklch(0.985 0 0); - --sidebar-foreground: oklch(0.145 0 0); - --sidebar-primary: oklch(0.205 0 0); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.97 0 0); - --sidebar-accent-foreground: oklch(0.205 0 0); - --sidebar-border: oklch(0.922 0 0); - --sidebar-ring: oklch(0.708 0 0); + --background: oklch(0.985 0.005 85); + --foreground: oklch(0.15 0.025 265); + --card: oklch(0.99 0.003 85); + --card-foreground: oklch(0.15 0.025 265); + --popover: oklch(0.99 0.003 85); + --popover-foreground: oklch(0.15 0.025 265); + --primary: oklch(0.45 0.13 265); + --primary-foreground: oklch(0.985 0.005 85); + --secondary: oklch(0.93 0.02 265); + --secondary-foreground: oklch(0.25 0.03 265); + --muted: oklch(0.95 0.01 265); + --muted-foreground: oklch(0.55 0.02 265); + --accent: oklch(0.7 0.15 75); + --accent-foreground: oklch(0.2 0.02 265); + --destructive: oklch(0.6 0.2 25); + --border: oklch(0.88 0.015 265); + --input: oklch(0.88 0.015 265); + --ring: oklch(0.45 0.13 265); + --chart-1: oklch(0.7 0.15 75); + --chart-2: oklch(0.45 0.13 265); + --chart-3: oklch(0.6 0.12 195); + --chart-4: oklch(0.5 0.18 330); + --chart-5: oklch(0.75 0.1 155); + --radius: 0.5rem; + --sidebar: oklch(0.98 0.005 85); + --sidebar-foreground: oklch(0.15 0.025 265); + --sidebar-primary: oklch(0.45 0.13 265); + --sidebar-primary-foreground: oklch(0.985 0.005 85); + --sidebar-accent: oklch(0.95 0.01 265); + --sidebar-accent-foreground: oklch(0.15 0.025 265); + --sidebar-border: oklch(0.88 0.015 265); + --sidebar-ring: oklch(0.45 0.13 265); } .dark { - --background: oklch(0.145 0 0); - --foreground: oklch(0.985 0 0); - --card: oklch(0.205 0 0); - --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.205 0 0); - --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.922 0 0); - --primary-foreground: oklch(0.205 0 0); - --secondary: oklch(0.269 0 0); - --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.269 0 0); - --muted-foreground: oklch(0.708 0 0); - --accent: oklch(0.269 0 0); - --accent-foreground: oklch(0.985 0 0); - --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --ring: oklch(0.556 0 0); - --chart-1: oklch(0.87 0 0); - --chart-2: oklch(0.556 0 0); - --chart-3: oklch(0.439 0 0); - --chart-4: oklch(0.371 0 0); - --chart-5: oklch(0.269 0 0); - --sidebar: oklch(0.205 0 0); - --sidebar-foreground: oklch(0.985 0 0); - --sidebar-primary: oklch(0.488 0.243 264.376); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.269 0 0); - --sidebar-accent-foreground: oklch(0.985 0 0); - --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.556 0 0); + --background: oklch(0.13 0.02 265); + --foreground: oklch(0.95 0.008 85); + --card: oklch(0.17 0.025 265); + --card-foreground: oklch(0.95 0.008 85); + --popover: oklch(0.17 0.025 265); + --popover-foreground: oklch(0.95 0.008 85); + --primary: oklch(0.7 0.15 75); + --primary-foreground: oklch(0.15 0.02 265); + --secondary: oklch(0.22 0.03 265); + --secondary-foreground: oklch(0.9 0.01 85); + --muted: oklch(0.2 0.02 265); + --muted-foreground: oklch(0.6 0.02 265); + --accent: oklch(0.6 0.12 195); + --accent-foreground: oklch(0.95 0.008 85); + --destructive: oklch(0.65 0.2 25); + --border: oklch(0.25 0.03 265); + --input: oklch(0.25 0.03 265); + --ring: oklch(0.7 0.15 75); + --chart-1: oklch(0.7 0.15 75); + --chart-2: oklch(0.65 0.12 265); + --chart-3: oklch(0.6 0.12 195); + --chart-4: oklch(0.65 0.15 330); + --chart-5: oklch(0.7 0.1 155); + --sidebar: oklch(0.15 0.02 265); + --sidebar-foreground: oklch(0.95 0.008 85); + --sidebar-primary: oklch(0.7 0.15 75); + --sidebar-primary-foreground: oklch(0.15 0.02 265); + --sidebar-accent: oklch(0.2 0.02 265); + --sidebar-accent-foreground: oklch(0.9 0.01 85); + --sidebar-border: oklch(0.25 0.03 265); + --sidebar-ring: oklch(0.7 0.15 75); } @layer base { @@ -128,3 +128,14 @@ @apply font-sans; } } + +/* Signature: glowing "server LED" focus ring */ +.dark *:focus-visible { + @apply ring-[3px]; + outline: none; + box-shadow: 0 0 8px oklch(0.7 0.15 75 / 0.3), 0 0 0 3px oklch(0.7 0.15 75 / 0.5); +} +html:not(.dark) *:focus-visible { + @apply ring-[3px]; + outline: none; +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index bfec96c..ae22ee1 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,5 +1,6 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; +import { ThemeProvider } from "next-themes"; import { DashboardHeader } from "@/components/dashboard/header"; import "./globals.css"; @@ -16,7 +17,7 @@ const geistMono = Geist_Mono({ export const metadata: Metadata = { title: "Asep Haryana Saputra", description: - "Backend & infrastructure engineer — portfolio, projects, and live monitoring dashboard by Asep Haryana Saputra.", + "Teknik Informatika student exploring backend, infrastructure & DevOps — portfolio and projects by Asep Haryana Saputra.", }; export default function RootLayout({ @@ -28,10 +29,13 @@ export default function RootLayout({ - - {children} + + + {children} + ); diff --git a/src/app/page.tsx b/src/app/page.tsx index dd33a48..d494573 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,6 +1,5 @@ import { ArrowRight, - BookOpen, Container, Cpu, Database, @@ -8,6 +7,7 @@ import { GitBranch, GitFork, HardDrive, + Layers, } from "lucide-react"; import { Badge } from "@/components/ui/badge"; @@ -15,13 +15,14 @@ import { buttonVariants } from "@/components/ui/button"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { cn } from "@/lib/utils"; +// ponytail: static data, fine for a personal portfolio. Move to CMS/content layer if > 50 items. const skills = [ { icon: HardDrive, label: "Backend (Bun, Elysia, Axum)" }, { icon: Container, label: "Docker & Traefik" }, { icon: Database, label: "PostgreSQL, Redis, NATS" }, { icon: Cpu, label: "Rust, TypeScript, Python" }, { icon: GitBranch, label: "CI/CD, GitHub Actions" }, - { icon: BookOpen, label: "Dapr, Prometheus, Jaeger" }, + { icon: Layers, label: "Dapr, Prometheus, Jaeger" }, ]; const techTags = [ @@ -50,19 +51,25 @@ const techTags = [ export default function Home() { return (
- {/* Hero */} -
-
- AH + {/* Hero — terminal-inspired */} +
+
+
+

+ ~ whoami +

+

+ $ + Asep Haryana Saputra + +

+

+ # + Teknik Informatika student exploring backend systems, distributed + infrastructure, and DevOps — still learning, still building. +

-

- Asep Haryana Saputra -

-

- Backend & infrastructure engineer who builds distributed systems, - RESTful APIs, and production-grade observability stacks. -

-
- {/* About */} -
- - - About - - + {/* Content — staggered sections with alternating visual weight */} +
+ {/* About — minimal, just text */} +
+

+ ~ about +

+

- I build backend systems and infrastructure for personal projects, - research collaborations, and production deployments. My work spans - RESTful API design, container orchestration with Docker & - Traefik, distributed tracing with OpenTelemetry & Jaeger, and - event-driven architectures on Dapr & NATS. + I’m a Teknik Informatika student passionate about backend + engineering, distributed systems, and infrastructure. I spend my + time building RESTful APIs, learning container orchestration with + Docker & Traefik, and exploring observability with OpenTelemetry + & Jaeger.

-

- I was the Back-End lead on the ZeaVis Edu capstone team ("AI - for Smart Education" by Pijak × IBM SkillsBuild), where - I designed the system architecture, API contracts, and deployment - pipeline for a computer vision-based corn leaf disease detection - platform. +

+ I was the Back-End lead on the ZeaVis Edu capstone team (“AI + for Smart Education” by Pijak × IBM SkillsBuild), + a 5-person team with task division across Back-End, Front-End, + and Machine Learning. I handled the system architecture, API + design, RESTful API development, and Docker deployment on VPS.

- - +
+
- {/* Skills */} - - - Skills & Tools - - -
- {skills.map((s) => { - const Icon = s.icon; - return ( -
- - {s.label} -
- ); - })} -
-
- {techTags.map((t) => ( - +

+ ~ skills +

+
+ {skills.map((s) => { + const Icon = s.icon; + return ( +
- {t} - - ))} -
- - + + {s.label} +
+ ); + })} +
+
+ {techTags.map((t) => ( + + {t} + + ))} +
+
- {/* Projects */} -
-

- Featured Project -

- + {/* Featured Project — elevated */} +
+

+ ~ featured + project +

+
- ZeaVis Edu -

+ ZeaVis Edu +

Asisten Edukasi Interaktif untuk Deteksi Penyakit Daun Jagung

@@ -179,19 +186,19 @@ export default function Home() {
-

+

A Computer Vision-powered educational platform that helps farmers, agricultural students, and field extension officers identify corn leaf diseases by uploading a photo. Built as a capstone project for the Pijak × IBM SkillsBuild program.

-
-
-

+

+
+

What it does

-
    +
    • Upload a photo of a corn leaf
    • AI classifies the disease — Blight, Rust, Leaf Spot, @@ -200,14 +207,14 @@ export default function Home() {
    • Get treatment & prevention recommendations
-
-

+

+

My role

-
    +
    • System architecture & API design
    • RESTful API (Bun + Elysia + Drizzle + PostgreSQL)
    • -
    • Rust inference engine (Axum + ONNX Runtime)
    • +
    • Secure upload handling
    • Docker deployment on VPS with Traefik & Tailscale
    • @@ -234,7 +241,7 @@ export default function Home() { ].map((t) => ( {t} @@ -245,16 +252,16 @@ export default function Home() {
- {/* Infrastructure CTA */} - - + {/* Infrastructure CTA — clean card */} + +

This site doubles as a live infrastructure monitor.

The dashboard tracks Docker services, Traefik request metrics, - Prometheus system resources, and Jaeger traces across the - deployment. + Prometheus system resources, and Jaeger traces — all running on + my VPS deployment.

Live Dashboard diff --git a/src/components/dashboard/header.tsx b/src/components/dashboard/header.tsx index b203581..7719f1d 100644 --- a/src/components/dashboard/header.tsx +++ b/src/components/dashboard/header.tsx @@ -1,13 +1,17 @@ "use client"; +import { Moon, Sun } from "lucide-react"; +import { useTheme } from "next-themes"; import { useEffect, useState } from "react"; import { Badge } from "@/components/ui/badge"; +import { Button } from "@/components/ui/button"; import type { DashboardData } from "@/lib/dashboard/types"; export function DashboardHeader() { const [degraded, setDegraded] = useState(0); const [total, setTotal] = useState(0); const [time, setTime] = useState(""); + const { theme, setTheme } = useTheme(); useEffect(() => { const fetchStatus = async () => { @@ -74,6 +78,15 @@ export function DashboardHeader() { ? `${degraded} degraded` : "All Systems Operational"} + {time}
diff --git a/src/components/ui/card.tsx b/src/components/ui/card.tsx index b712c9e..bf67ccb 100644 --- a/src/components/ui/card.tsx +++ b/src/components/ui/card.tsx @@ -12,7 +12,7 @@ function Card({ data-slot="card" data-size={size} className={cn( - "group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl bg-card py-(--card-spacing) text-sm text-card-foreground ring-1 ring-foreground/10 [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl", + "group/card flex flex-col gap-(--card-spacing) overflow-hidden rounded-xl border border-border bg-card py-(--card-spacing) text-sm text-card-foreground [--card-spacing:--spacing(4)] has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:[--card-spacing:--spacing(3)] data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl", className, )} {...props}