CHORE(style): add responsive Tailwind CSS

- Implement responsive design
- Add custom Tailwind styles
This commit is contained in:
2025-11-30 01:37:23 +09:00
parent 60aa253572
commit 21ce616024
11 changed files with 170 additions and 167 deletions

View File

@@ -13,7 +13,7 @@ commonLabels:
# 이미지 태그 설정
images:
- name: ghcr.io/mayne0213/portfolio
newTag: main-sha-02f540cea01f2abd0a572d2baf8042958cd0f318
newTag: main-sha-3ced4c6a71df7e56b99b22c5aa7399b186d6311d
patchesStrategicMerge:
- deployment-patch.yaml

View File

@@ -5,8 +5,9 @@
@custom-variant dark (&:is(.dark *));
@theme {
--breakpoint-tablet: 768px;
--breakpoint-pc: 1024px;
--breakpoint-smalltablet: 600px;
--breakpoint-tablet: 990px;
--breakpoint-desktop: 1200px;
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
@@ -25,8 +26,9 @@
--color-ring: var(--ring);
}
@custom-variant tablet (@media (width >= 768px));
@custom-variant pc (@media (width >= 1024px));
@custom-variant smalltablet (@media (width >= 600px));
@custom-variant tablet (@media (width >= 990px));
@custom-variant desktop (@media (width >= 1200px));
:root {
--radius: 0.625rem;
@@ -110,7 +112,7 @@
}
.page-container {
@apply flex flex-col items-center justify-center gap-16 p-4 tablet:p-8 py-20 mt-[70px];
@apply flex flex-col items-center justify-center gap-12 smalltablet:gap-14 tablet:gap-16 p-4 smalltablet:p-6 tablet:p-8 py-16 smalltablet:py-18 tablet:py-20 mt-[70px];
}
.theme-background {

View File

@@ -11,13 +11,13 @@ interface InfoItemProps {
function InfoItem({ icon: Icon, label, value }: InfoItemProps) {
return (
<div className="flex items-start gap-4">
<div className="flex items-center justify-center min-w-10 min-h-10 rounded-full theme-background">
<Icon className="w-5 h-5" />
<div className="flex items-start gap-3 smalltablet:gap-4">
<div className="flex items-center justify-center min-w-8 min-h-8 smalltablet:min-w-10 smalltablet:min-h-10 rounded-full theme-background">
<Icon className="w-4 h-4 smalltablet:w-5 smalltablet:h-5" />
</div>
<div className="flex flex-col">
<span className="font-semibold">{label}</span>
<span className="text-muted-foreground">{value}</span>
<span className="text-sm smalltablet:text-base font-semibold">{label}</span>
<span className="text-xs smalltablet:text-sm text-muted-foreground">{value}</span>
</div>
</div>
);
@@ -35,19 +35,19 @@ const PERSONAL_INFO = [
export default function About() {
return (
<div className="bg-muted">
<main className="flex flex-col items-center justify-center gap-16 p-4 tablet:p-8 py-20">
<main className="flex flex-col items-center justify-center gap-12 smalltablet:gap-14 tablet:gap-16 p-4 smalltablet:p-6 tablet:p-8 py-16 smalltablet:py-18 tablet:py-20">
<SectionHeader
title="About Me"
description="Passionate full-stack developer with expertise in building scalable web applications and cloud infrastructure"
/>
<section className="grid grid-cols-1 tablet:grid-cols-2 gap-8 max-w-7xl w-full">
<section className="grid grid-cols-1 tablet:grid-cols-2 gap-6 smalltablet:gap-7 tablet:gap-8 max-w-7xl w-full">
{/* Personal Info */}
<div className="flex flex-col gap-4 h-full">
<Card className="p-6 flex-1 gap-4">
<h3 className="text-2xl font-bold">Personal Info</h3>
<div className="flex flex-col gap-3 smalltablet:gap-4 h-full">
<Card className="p-4 smalltablet:p-5 tablet:p-6 flex-1 gap-3 smalltablet:gap-4">
<h3 className="text-xl smalltablet:text-2xl font-bold">Personal Info</h3>
<Separator/>
<div className="grid grid-cols-2 gap-6 items-center h-full">
<div className="grid grid-cols-1 smalltablet:grid-cols-2 gap-4 smalltablet:gap-5 tablet:gap-6 items-center h-full">
{PERSONAL_INFO.map((info, index) => (
<InfoItem
key={index}
@@ -61,21 +61,21 @@ export default function About() {
</div>
{/* Who I Am */}
<div className="flex flex-col gap-4 h-full">
<Card className="p-6 flex-1">
<div className="flex flex-col gap-4">
<h3 className="text-2xl font-bold">Who I Am</h3>
<div className="flex flex-col gap-3 smalltablet:gap-4 h-full">
<Card className="p-4 smalltablet:p-5 tablet:p-6 flex-1">
<div className="flex flex-col gap-3 smalltablet:gap-4">
<h3 className="text-xl smalltablet:text-2xl font-bold">Who I Am</h3>
<Separator/>
<p className="text-muted-foreground tablet:text-justify">
<p className="text-sm smalltablet:text-base text-muted-foreground tablet:text-justify leading-relaxed">
I&apos;m Minjo Kim, a Full-Stack Developer and Bachelor in Computer Science at Seoul National University, Korea.
I have experience in web development and cloud infrastructure, passionate about building scalable and efficient systems.
</p>
<p className="text-muted-foreground tablet:text-justify">
<p className="text-sm smalltablet:text-base text-muted-foreground tablet:text-justify leading-relaxed">
My academic background gave me solid knowledge in Algorithms, Data Structures, System Design, and Databases
which I apply to create efficient and modern systems. I&apos;ve delivered impactful solutions using TypeScript,
React, Next.js, and Tailwind CSS.
</p>
<p className="text-muted-foreground tablet:text-justify">
<p className="text-sm smalltablet:text-base text-muted-foreground tablet:text-justify leading-relaxed">
I&apos;m responsible for building full-stack applications, setting up Kubernetes clusters with ArgoCD for GitOps workflows,
and implementing monitoring systems with Prometheus and Grafana. I focus on creating maintainable code and
improving system performance and scalability.

View File

@@ -62,52 +62,52 @@ export default function Contact() {
return (
<div className="bg-muted">
<main className="flex flex-col items-center justify-center gap-16 p-4 tablet:p-8 py-20">
<main className="flex flex-col items-center justify-center gap-12 smalltablet:gap-14 tablet:gap-16 p-4 smalltablet:p-6 tablet:p-8 py-16 smalltablet:py-18 tablet:py-20">
<SectionHeader
title="Get In Touch"
description="Have a project in mind or want to collaborate? I'd love to hear from you. Feel free to reach out through any of the channels below."
/>
<div className="grid pc:grid-cols-2 gap-8 max-w-6xl mx-auto">
<div className="grid tablet:grid-cols-2 gap-6 smalltablet:gap-7 tablet:gap-8 max-w-6xl mx-auto w-full">
{/* Contact Info & Social Links */}
<Card className="p-8 hover:shadow-lg transition-shadow">
<h3 className="font-bold text-xl mb-6 flex items-center gap-2">
<MapPin className="w-5 h-5 text-primary" />
<Card className="p-5 smalltablet:p-6 tablet:p-8 hover:shadow-lg transition-shadow">
<h3 className="font-bold text-lg smalltablet:text-xl mb-4 smalltablet:mb-5 tablet:mb-6 flex items-center gap-2">
<MapPin className="w-4 h-4 smalltablet:w-5 smalltablet:h-5 text-primary" />
Contact Information
</h3>
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-3 smalltablet:gap-4">
{contactMethods.map((method, index) => (
<a
key={index}
href={method.link}
target={method.link.startsWith('http') ? '_blank' : undefined}
rel={method.link.startsWith('http') ? 'noopener noreferrer' : undefined}
className="flex items-center gap-4 p-4 rounded-lg hover:bg-accent transition-colors group"
className="flex items-center gap-3 smalltablet:gap-4 p-3 smalltablet:p-4 rounded-lg hover:bg-accent transition-colors group"
>
<div className={`flex items-center justify-center w-12 h-12 rounded-full bg-muted group-hover:scale-110 transition-transform ${method.color}`}>
<method.icon className="w-6 h-6" />
<div className={`flex items-center justify-center w-10 h-10 smalltablet:w-12 smalltablet:h-12 rounded-full bg-muted group-hover:scale-110 transition-transform ${method.color}`}>
<method.icon className="w-5 h-5 smalltablet:w-6 smalltablet:h-6" />
</div>
<div className="flex-1 min-w-0">
<p className="text-sm text-muted-foreground">{method.label}</p>
<p className="font-medium truncate">{method.value}</p>
<p className="text-xs smalltablet:text-sm text-muted-foreground">{method.label}</p>
<p className="text-sm smalltablet:text-base font-medium truncate">{method.value}</p>
</div>
<ExternalLink className="w-4 h-4 text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity" />
<ExternalLink className="w-3.5 h-3.5 smalltablet:w-4 smalltablet:h-4 text-muted-foreground opacity-0 group-hover:opacity-100 transition-opacity" />
</a>
))}
</div>
</Card>
{/* Contact Form */}
<Card className="p-8 hover:shadow-xl transition-shadow">
<h3 className="font-bold text-2xl mb-2">Send Me a Message</h3>
<p className="text-muted-foreground mb-8">
<Card className="p-5 smalltablet:p-6 tablet:p-8 hover:shadow-xl transition-shadow">
<h3 className="font-bold text-xl smalltablet:text-2xl mb-1.5 smalltablet:mb-2">Send Me a Message</h3>
<p className="text-sm smalltablet:text-base text-muted-foreground mb-6 smalltablet:mb-8">
Fill out the form below and I&apos;ll get back to you as soon as possible.
</p>
<form onSubmit={handleSubmit} className="flex flex-col gap-6">
<div className="grid tablet:grid-cols-2 gap-6">
<div className="flex flex-col gap-2">
<label htmlFor="name" className="text-sm font-medium">
<form onSubmit={handleSubmit} className="flex flex-col gap-4 smalltablet:gap-5 tablet:gap-6">
<div className="grid tablet:grid-cols-2 gap-4 smalltablet:gap-5 tablet:gap-6">
<div className="flex flex-col gap-1.5 smalltablet:gap-2">
<label htmlFor="name" className="text-xs smalltablet:text-sm font-medium">
Your Name <span className="text-red-500">*</span>
</label>
<Input
@@ -117,12 +117,12 @@ export default function Contact() {
value={formData.name}
onChange={handleChange}
required
className="h-12"
className="h-10 smalltablet:h-12"
/>
</div>
<div className="flex flex-col gap-2">
<label htmlFor="email" className="text-sm font-medium">
<div className="flex flex-col gap-1.5 smalltablet:gap-2">
<label htmlFor="email" className="text-xs smalltablet:text-sm font-medium">
Your Email <span className="text-red-500">*</span>
</label>
<Input
@@ -133,13 +133,13 @@ export default function Contact() {
value={formData.email}
onChange={handleChange}
required
className="h-12"
className="h-10 smalltablet:h-12"
/>
</div>
</div>
<div className="flex flex-col gap-2">
<label htmlFor="subject" className="text-sm font-medium">
<div className="flex flex-col gap-1.5 smalltablet:gap-2">
<label htmlFor="subject" className="text-xs smalltablet:text-sm font-medium">
Subject <span className="text-red-500">*</span>
</label>
<Input
@@ -149,12 +149,12 @@ export default function Contact() {
value={formData.subject}
onChange={handleChange}
required
className="h-12"
className="h-10 smalltablet:h-12"
/>
</div>
<div className="flex flex-col gap-2">
<label htmlFor="message" className="text-sm font-medium">
<div className="flex flex-col gap-1.5 smalltablet:gap-2">
<label htmlFor="message" className="text-xs smalltablet:text-sm font-medium">
Message <span className="text-red-500">*</span>
</label>
<Textarea
@@ -164,15 +164,15 @@ export default function Contact() {
value={formData.message}
onChange={handleChange}
required
rows={8}
className="resize-none"
rows={6}
className="resize-none smalltablet:rows-8"
/>
</div>
<Button type="submit" size="lg" className="w-full tablet:w-auto tablet:self-end group">
<span className="flex items-center gap-2">
Send Message
<Send className="w-4 h-4 group-hover:translate-x-1 transition-transform" />
<Send className="w-3.5 h-3.5 smalltablet:w-4 smalltablet:h-4 group-hover:translate-x-1 transition-transform" />
</span>
</Button>
</form>

View File

@@ -1,6 +1,6 @@
"use client";
import { useState, useRef } from "react";
import { useState, useRef, useEffect } from "react";
import { useTheme } from "next-themes";
import SectionHeader from "@/components/landing/section-header";
import { Button } from "@/components/ui/button";
@@ -14,6 +14,16 @@ const sections: { id: Section; label: string; panels: number }[] = [
{ id: "network", label: "Network", panels: 6 },
];
// Loading Skeleton 컴포넌트
const LoadingSkeleton = () => (
<div className="absolute inset-0 flex items-center justify-center bg-gray-100 dark:bg-gray-800 animate-pulse">
<div className="flex flex-col items-center gap-2">
<div className="w-8 h-8 border-4 border-gray-300 dark:border-gray-600 border-t-gray-600 dark:border-t-gray-300 rounded-full animate-spin" />
<div className="text-sm text-gray-500 dark:text-gray-400">Loading dashboard...</div>
</div>
</div>
);
export default function GrafanaPage() {
const [activeSection, setActiveSection] = useState<Section>("overview");
const { theme } = useTheme();
@@ -30,15 +40,6 @@ export default function GrafanaPage() {
const GrafanaPanel = ({ panelId }: { panelId: number }) => {
const [loaded, setLoaded] = useState(false);
// Grafana 섹션이 화면에 보이지 않으면 로딩하지 않음
// if (!isVisible) {
// return (
// <div className="relative w-full h-full">
// <LoadingSkeleton />
// </div>
// );
// }
return (
<div className="relative w-full h-full">
{/* {(!loaded || isLoading) && <LoadingSkeleton />} */}
@@ -46,7 +47,7 @@ export default function GrafanaPage() {
<iframe
key={`${panelId}-dark-${theme}`}
src={getGrafanaUrl(panelId, "dark")}
className="w-full h-full"
className="w-full h-full border"
style={{ display: loaded ? 'block' : 'none' }}
onLoad={() => setLoaded(true)}
/>
@@ -64,23 +65,23 @@ export default function GrafanaPage() {
};
return (
<main className="page-container" ref={sectionRef}>
<main className="flex flex-col items-center justify-center gap-12 smalltablet:gap-14 tablet:gap-16 p-4 smalltablet:p-6 tablet:p-8 py-16 smalltablet:py-18 tablet:py-20" ref={sectionRef}>
<SectionHeader
title="Monitoring"
description="Real-time Kubernetes cluster monitoring with Grafana dashboards"
/>
{/* 섹션 선택 버튼 */}
<div className="flex gap-2 flex-wrap justify-center mb-4">
<div className="flex gap-1.5 smalltablet:gap-2 flex-wrap justify-center">
{sections.map((section) => (
<Button
key={section.id}
onClick={() => setActiveSection(section.id)}
variant={activeSection === section.id ? "default" : "outline"}
className="transition-all text-xs sm:text-sm"
className="transition-all text-[10px] smalltablet:text-xs tablet:text-sm px-2 smalltablet:px-3 py-1 smalltablet:py-2 h-auto"
>
{section.label}
<span className="ml-1 sm:ml-2 text-xs opacity-60">
<span className="ml-1 smalltablet:ml-1.5 tablet:ml-2 text-[9px] smalltablet:text-xs opacity-60">
({section.panels})
</span>
</Button>
@@ -89,29 +90,29 @@ export default function GrafanaPage() {
{/* Overview 섹션 */}
{activeSection === "overview" && (
<div className="flex flex-col lg:flex-row gap-2 justify-center w-full h-full">
<div className="flex flex-col desktop:flex-row gap-1.5 smalltablet:gap-2 justify-center w-full h-full">
{/* 왼쪽 2개 열 (CPU, RAM) */}
<div className="flex flex-col sm:flex-row gap-2 w-full lg:flex-1">
<div className="flex flex-col smalltablet:flex-row gap-1.5 smalltablet:gap-2 w-full desktop:flex-1">
{/* Global CPU Usage 열 */}
<div className="flex flex-col gap-2 w-full sm:flex-1">
<div className="flex flex-col gap-1.5 smalltablet:gap-2 w-full smalltablet:flex-1">
{/* Global CPU Usage (id:77) - w:6, h:8 */}
<div className="w-full aspect-5/4 theme-background rounded-lg overflow-hidden">
<div className="w-full aspect-5/4 theme-background rounded-md smalltablet:rounded-lg overflow-hidden">
<GrafanaPanel panelId={77} />
</div>
{/* CPU Usage (id:37) - w:6, h:4 */}
<div className="w-full aspect-5/2 theme-background rounded-lg overflow-hidden">
<div className="w-full aspect-5/2 theme-background rounded-md smalltablet:rounded-lg overflow-hidden">
<GrafanaPanel panelId={37} />
</div>
</div>
{/* Global RAM Usage 열 */}
<div className="flex flex-col gap-2 w-full sm:flex-1">
<div className="flex flex-col gap-1.5 smalltablet:gap-2 w-full smalltablet:flex-1">
{/* Global RAM Usage (id:78) - w:6, h:8 */}
<div className="w-full aspect-5/4 theme-background rounded-lg overflow-hidden">
<div className="w-full aspect-5/4 theme-background rounded-md smalltablet:rounded-lg overflow-hidden">
<GrafanaPanel panelId={78} />
</div>
{/* RAM Usage (id:39) - w:6, h:4 */}
<div className="w-full aspect-5/2 theme-background rounded-lg overflow-hidden">
<div className="w-full aspect-5/2 theme-background rounded-md smalltablet:rounded-lg overflow-hidden">
<GrafanaPanel panelId={39} />
</div>
</div>
@@ -119,7 +120,7 @@ export default function GrafanaPage() {
{/* 오른쪽 넓은 열 (Kubernetes Resource Count) */}
{/* Kubernetes Resource Count (id:52) - w:10, h:12 */}
<div className="w-full lg:flex-1 lg:max-w-[40%] theme-background rounded-lg overflow-hidden flex items-stretch">
<div className="w-full aspect-5/7 smalltablet:aspect-video desktop:flex-1 desktop:max-w-[40%] theme-background rounded-md smalltablet:rounded-lg overflow-hidden flex items-stretch">
<GrafanaPanel panelId={52} />
</div>
</div>
@@ -127,51 +128,51 @@ export default function GrafanaPage() {
{/* Resources 섹션 */}
{activeSection === "resources" && (
<div className="flex flex-col gap-2 w-full">
<div className="flex flex-col gap-1.5 smalltablet:gap-2 w-full">
{/* Row 1 */}
<div className="flex flex-col lg:flex-row gap-2 justify-center w-full">
<div className="flex flex-col desktop:flex-row gap-1.5 smalltablet:gap-2 justify-center w-full">
{/* Cluster CPU Utilization (id:72) - w:12, h:8 */}
<div className="w-full lg:flex-1 aspect-video theme-background rounded-lg overflow-hidden">
<div className="w-full desktop:flex-1 aspect-5/4 smalltablet:aspect-video theme-background rounded-md smalltablet:rounded-lg overflow-hidden">
<GrafanaPanel panelId={72} />
</div>
{/* Cluster Memory Utilization (id:55) - w:12, h:8 */}
<div className="w-full lg:flex-1 aspect-video theme-background rounded-lg overflow-hidden">
<div className="w-full desktop:flex-1 aspect-5/4 smalltablet:aspect-video theme-background rounded-md smalltablet:rounded-lg overflow-hidden">
<GrafanaPanel panelId={55} />
</div>
</div>
{/* Row 2 */}
<div className="flex flex-col lg:flex-row gap-2 justify-center w-full">
<div className="flex flex-col desktop:flex-row gap-1.5 smalltablet:gap-2 justify-center w-full">
{/* CPU Utilization by namespace (id:46) - w:12, h:8 */}
<div className="w-full lg:flex-1 aspect-video theme-background rounded-lg overflow-hidden">
<div className="w-full desktop:flex-1 aspect-5/4 smalltablet:aspect-video theme-background rounded-md smalltablet:rounded-lg overflow-hidden">
<GrafanaPanel panelId={46} />
</div>
{/* Memory Utilization by namespace (id:50) - w:12, h:8 */}
<div className="w-full lg:flex-1 aspect-video theme-background rounded-lg overflow-hidden">
<div className="w-full desktop:flex-1 aspect-5/4 smalltablet:aspect-video theme-background rounded-md smalltablet:rounded-lg overflow-hidden">
<GrafanaPanel panelId={50} />
</div>
</div>
{/* Row 3 */}
<div className="flex flex-col lg:flex-row gap-2 justify-center w-full">
<div className="flex flex-col desktop:flex-row gap-1.5 smalltablet:gap-2 justify-center w-full">
{/* CPU Utilization by instance (id:54) - w:12, h:8 */}
<div className="w-full lg:flex-1 aspect-video theme-background rounded-lg overflow-hidden">
<div className="w-full desktop:flex-1 aspect-5/4 smalltablet:aspect-video theme-background rounded-md smalltablet:rounded-lg overflow-hidden">
<GrafanaPanel panelId={54} />
</div>
{/* Memory Utilization by instance (id:73) - w:12, h:8 */}
<div className="w-full lg:flex-1 aspect-video theme-background rounded-lg overflow-hidden">
<div className="w-full desktop:flex-1 aspect-5/4 smalltablet:aspect-video theme-background rounded-md smalltablet:rounded-lg overflow-hidden">
<GrafanaPanel panelId={73} />
</div>
</div>
{/* Row 4 */}
<div className="flex flex-col lg:flex-row gap-2 justify-center w-full">
<div className="flex flex-col desktop:flex-row gap-1.5 smalltablet:gap-2 justify-center w-full">
{/* CPU Throttled seconds by namespace (id:82) - w:12, h:8 */}
<div className="w-full lg:flex-1 aspect-video theme-background rounded-lg overflow-hidden">
<div className="w-full desktop:flex-1 aspect-5/4 smalltablet:aspect-video theme-background rounded-md smalltablet:rounded-lg overflow-hidden">
<GrafanaPanel panelId={82} />
</div>
{/* CPU Core Throttled by instance (id:83) - w:12, h:8 */}
<div className="w-full lg:flex-1 aspect-video theme-background rounded-lg overflow-hidden">
<div className="w-full desktop:flex-1 aspect-5/4 smalltablet:aspect-video theme-background rounded-md smalltablet:rounded-lg overflow-hidden">
<GrafanaPanel panelId={83} />
</div>
</div>
@@ -180,27 +181,27 @@ export default function GrafanaPage() {
{/* Kubernetes 섹션 */}
{activeSection === "kubernetes" && (
<div className="flex flex-col gap-2 w-full">
<div className="flex flex-col gap-1.5 smalltablet:gap-2 w-full">
{/* Row 1 */}
<div className="flex flex-col lg:flex-row gap-2 justify-center w-full">
<div className="flex flex-col desktop:flex-row gap-1.5 smalltablet:gap-2 justify-center w-full">
{/* Kubernetes Pods QoS classes (id:84) - w:12, h:9 */}
<div className="w-full lg:flex-1 aspect-video theme-background rounded-lg overflow-hidden">
<div className="w-full desktop:flex-1 aspect-5/4 smalltablet:aspect-video theme-background rounded-md smalltablet:rounded-lg overflow-hidden">
<GrafanaPanel panelId={84} />
</div>
{/* Kubernetes Pods Status Reason (id:85) - w:12, h:9 */}
<div className="w-full lg:flex-1 aspect-video theme-background rounded-lg overflow-hidden">
<div className="w-full desktop:flex-1 aspect-5/4 smalltablet:aspect-video theme-background rounded-md smalltablet:rounded-lg overflow-hidden">
<GrafanaPanel panelId={85} />
</div>
</div>
{/* Row 2 */}
<div className="flex flex-col lg:flex-row gap-2 justify-center w-full">
<div className="flex flex-col desktop:flex-row gap-1.5 smalltablet:gap-2 justify-center w-full">
{/* OOM Events by namespace (id:87) - w:12, h:9 */}
<div className="w-full lg:flex-1 aspect-video theme-background rounded-lg overflow-hidden">
<div className="w-full desktop:flex-1 aspect-5/4 smalltablet:aspect-video theme-background rounded-md smalltablet:rounded-lg overflow-hidden">
<GrafanaPanel panelId={87} />
</div>
{/* Container Restarts by namespace (id:88) - w:12, h:9 */}
<div className="w-full lg:flex-1 aspect-video theme-background rounded-lg overflow-hidden">
<div className="w-full desktop:flex-1 aspect-5/4 smalltablet:aspect-video theme-background rounded-md smalltablet:rounded-lg overflow-hidden">
<GrafanaPanel panelId={88} />
</div>
</div>
@@ -209,39 +210,39 @@ export default function GrafanaPage() {
{/* Network 섹션 */}
{activeSection === "network" && (
<div className="flex flex-col gap-2 w-full">
<div className="flex flex-col gap-1.5 smalltablet:gap-2 w-full">
{/* Row 1 */}
<div className="flex flex-col lg:flex-row gap-2 justify-center w-full">
<div className="flex flex-col desktop:flex-row gap-1.5 smalltablet:gap-2 justify-center w-full">
{/* Global Network Utilization by device (id:44) - w:12, h:8 */}
<div className="w-full lg:flex-1 aspect-video theme-background rounded-lg overflow-hidden">
<div className="w-full desktop:flex-1 aspect-5/4 smalltablet:aspect-video theme-background rounded-md smalltablet:rounded-lg overflow-hidden">
<GrafanaPanel panelId={44} />
</div>
{/* Network Saturation - Packets dropped (id:53) - w:12, h:8 */}
<div className="w-full lg:flex-1 aspect-video theme-background rounded-lg overflow-hidden">
<div className="w-full desktop:flex-1 aspect-5/4 smalltablet:aspect-video theme-background rounded-md smalltablet:rounded-lg overflow-hidden">
<GrafanaPanel panelId={53} />
</div>
</div>
{/* Row 2 */}
<div className="flex flex-col lg:flex-row gap-2 justify-center w-full">
<div className="flex flex-col desktop:flex-row gap-1.5 smalltablet:gap-2 justify-center w-full">
{/* Network Received by namespace (id:79) - w:12, h:8 */}
<div className="w-full lg:flex-1 aspect-video theme-background rounded-lg overflow-hidden">
<div className="w-full desktop:flex-1 aspect-5/4 smalltablet:aspect-video theme-background rounded-md smalltablet:rounded-lg overflow-hidden">
<GrafanaPanel panelId={79} />
</div>
{/* Total Network Received by instance (id:80) - w:12, h:8 */}
<div className="w-full lg:flex-1 aspect-video theme-background rounded-lg overflow-hidden">
<div className="w-full desktop:flex-1 aspect-5/4 smalltablet:aspect-video theme-background rounded-md smalltablet:rounded-lg overflow-hidden">
<GrafanaPanel panelId={80} />
</div>
</div>
{/* Row 3 */}
<div className="flex flex-col lg:flex-row gap-2 justify-center w-full">
<div className="flex flex-col desktop:flex-row gap-1.5 smalltablet:gap-2 justify-center w-full">
{/* Network Received (without loopback) by instance (id:56) - w:12, h:8 */}
<div className="w-full lg:flex-1 aspect-video theme-background rounded-lg overflow-hidden">
<div className="w-full desktop:flex-1 aspect-5/4 smalltablet:aspect-video theme-background rounded-md smalltablet:rounded-lg overflow-hidden">
<GrafanaPanel panelId={56} />
</div>
{/* Network Received (loopback only) by instance (id:81) - w:12, h:8 */}
<div className="w-full lg:flex-1 aspect-video theme-background rounded-lg overflow-hidden">
<div className="w-full desktop:flex-1 aspect-5/4 smalltablet:aspect-video theme-background rounded-md smalltablet:rounded-lg overflow-hidden">
<GrafanaPanel panelId={81} />
</div>
</div>

View File

@@ -16,13 +16,13 @@ export default function Hero() {
className="absolute inset-0 -z-10"
/>
<div className="relative w-full h-full flex items-center justify-center">
<div className="flex flex-col items-center justify-center gap-12 text-center">
<div className="flex flex-col items-center justify-center gap-12 px-5">
<h2 className="font-bold text-5xl tablet:text-7xl text-center bg-linear-to-r from-gray-900 to-gray-600 dark:from-gray-100 dark:to-gray-400 bg-clip-text text-transparent">
<div className="flex flex-col items-center justify-center gap-8 smalltablet:gap-10 tablet:gap-12 text-center">
<div className="flex flex-col items-center justify-center gap-8 smalltablet:gap-10 tablet:gap-12 px-4 smalltablet:px-5">
<h2 className="font-bold text-4xl smalltablet:text-5xl tablet:text-6xl desktop:text-7xl text-center bg-linear-to-r from-gray-900 to-gray-600 dark:from-gray-100 dark:to-gray-400 bg-clip-text text-transparent">
Full Stack Developer
</h2>
<div className="max-w-3xl">
<p className="text-2xl tablet:text-3xl text-center leading-[160%]">
<div className="max-w-sm smalltablet:max-w-xl tablet:max-w-2xl desktop:max-w-3xl">
<p className="text-lg smalltablet:text-xl tablet:text-2xl desktop:text-3xl text-center leading-[150%] smalltablet:leading-[160%]">
Creating beautiful and functional web experiences with modern technologies
</p>
</div>

View File

@@ -35,56 +35,56 @@ function ProjectCard({ title, description, tags, imageSrc, githubUrl, liveUrl, d
fill
className="object-cover border-b-2 border-gray-200"
placeholder="blur"
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
sizes="(max-width: 600px) 100vw, (max-width: 990px) 100vw, (max-width: 1200px) 50vw, 50vw"
/>
</div>
<div className="px-6 pt-6 pb-4 flex flex-col gap-3">
<h3 className="font-semibold text-2xl">{title}</h3>
<p className="text-muted-foreground font-extralight">{description}</p>
<div className="flex gap-2 flex-wrap">
<div className="px-4 smalltablet:px-5 tablet:px-6 pt-4 smalltablet:pt-5 tablet:pt-6 pb-3 smalltablet:pb-4 flex flex-col gap-2 smalltablet:gap-3">
<h3 className="font-semibold text-lg smalltablet:text-xl tablet:text-2xl">{title}</h3>
<p className="text-sm smalltablet:text-base text-muted-foreground font-extralight">{description}</p>
<div className="flex gap-1.5 smalltablet:gap-2 flex-wrap">
{tags.map((tag) => (
<Button key={tag} variant="outline" className="text-xs px-3 py-1">
<Button key={tag} variant="outline" className="text-[10px] smalltablet:text-xs px-2 smalltablet:px-3 py-0.5 smalltablet:py-1 h-auto">
{tag}
</Button>
))}
</div>
</div>
<div className="px-6">
<div className="px-4 smalltablet:px-5 tablet:px-6">
<Separator />
</div>
<div className="px-6 py-4 flex gap-4 flex-wrap">
<div className="px-4 smalltablet:px-5 tablet:px-6 py-3 smalltablet:py-4 flex gap-3 smalltablet:gap-4 flex-wrap">
{liveUrl && (
<Link href={liveUrl} target="_blank" rel="noopener noreferrer" aria-label="Live Demo">
<div className="flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors cursor-pointer">
<ExternalLink className="w-5 h-5" />
<ExternalLink className="w-4 h-4 smalltablet:w-5 smalltablet:h-5" />
</div>
</Link>
)}
{githubUrl && (
<Link href={githubUrl} target="_blank" rel="noopener noreferrer" aria-label="GitHub">
<div className="flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors cursor-pointer">
<Github className="w-5 h-5" />
<Github className="w-4 h-4 smalltablet:w-5 smalltablet:h-5" />
</div>
</Link>
)}
{docsUrl && (
<Link href={docsUrl} target="_blank" rel="noopener noreferrer" aria-label="Documentation">
<div className="flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors cursor-pointer">
<BookOpen className="w-5 h-5" />
<BookOpen className="w-4 h-4 smalltablet:w-5 smalltablet:h-5" />
</div>
</Link>
)}
{docusaurusUrl && (
<Link href={docusaurusUrl} target="_blank" rel="noopener noreferrer" aria-label="Docusaurus">
<div className="flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors cursor-pointer">
<FileText className="w-5 h-5" />
<FileText className="w-4 h-4 smalltablet:w-5 smalltablet:h-5" />
</div>
</Link>
)}
{monitoringUrl && (
<Link href={monitoringUrl} target="_blank" rel="noopener noreferrer" aria-label="Monitoring">
<div className="flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors cursor-pointer">
<BarChart3 className="w-5 h-5" />
<BarChart3 className="w-4 h-4 smalltablet:w-5 smalltablet:h-5" />
</div>
</Link>
)}
@@ -95,13 +95,13 @@ function ProjectCard({ title, description, tags, imageSrc, githubUrl, liveUrl, d
export default function Projects() {
return (
<main className="flex bg-muted flex-col items-center justify-center gap-16 p-4 tablet:p-8 py-20">
<main className="flex bg-muted flex-col items-center justify-center gap-12 smalltablet:gap-14 tablet:gap-16 p-4 smalltablet:p-6 tablet:p-8 py-16 smalltablet:py-18 tablet:py-20">
<SectionHeader
title="Featured Projects"
description="Some of my recent work and side projects"
/>
<section className="grid grid-cols-1 pc:grid-cols-2 gap-6 pc:gap-8 max-w-[1440px] w-full">
<section className="grid grid-cols-1 desktop:grid-cols-2 gap-4 smalltablet:gap-5 tablet:gap-6 desktop:gap-8 max-w-[1440px] w-full">
<ProjectCard
title="Joossam English"
description="English learning platform for Korean students"

View File

@@ -5,11 +5,11 @@ interface SectionHeaderProps {
export default function SectionHeader({ title, description }: SectionHeaderProps) {
return (
<div className="flex flex-col items-center gap-4 max-w-3xl text-center">
<h2 className="font-bold text-4xl tablet:text-5xl pc:text-6xl bg-linear-to-r from-gray-900 to-gray-600 dark:from-gray-100 dark:to-gray-400 bg-clip-text text-transparent">
<div className="flex flex-col items-center gap-3 smalltablet:gap-4 max-w-xl smalltablet:max-w-2xl tablet:max-w-3xl text-center px-4">
<h2 className="font-bold text-3xl smalltablet:text-4xl tablet:text-5xl desktop:text-6xl bg-linear-to-r from-gray-900 to-gray-600 dark:from-gray-100 dark:to-gray-400 bg-clip-text text-transparent">
{title}
</h2>
<p className="text-lg tablet:text-xl text-muted-foreground max-w-2xl">
<p className="text-base smalltablet:text-lg tablet:text-xl text-muted-foreground max-w-sm smalltablet:max-w-lg tablet:max-w-2xl">
{description}
</p>
</div>

View File

@@ -19,36 +19,36 @@ function SkillCard({ icon: Icon, title, description, mainSkills, subSkills }: Sk
const [isExpanded, setIsExpanded] = useState(false);
return (
<Card className="p-5 w-full h-full gap-4 relative">
<Card className="p-4 smalltablet:p-5 w-full h-full gap-3 smalltablet:gap-4 relative">
{/* Toggle Button - Top Right Corner */}
{subSkills.length > 0 && (
<Button
variant="ghost"
size="sm"
onClick={() => setIsExpanded(!isExpanded)}
className="absolute top-6 right-6 p-2 h-8 w-8"
className="absolute top-4 right-4 smalltablet:top-6 smalltablet:right-6 p-2 h-8 w-8"
>
<ChevronDown className={`w-16 h-16 transition-transform ${isExpanded ? 'rotate-180' : ''}`} />
</Button>
)}
<div className="flex flex-col gap-4">
<div className="flex items-center justify-center min-w-12 min-h-12 rounded-full bg-muted w-fit">
<Icon className="min-w-8 min-h-8" />
<div className="flex flex-col gap-3 smalltablet:gap-4">
<div className="flex items-center justify-center min-w-10 min-h-10 smalltablet:min-w-12 smalltablet:min-h-12 rounded-full bg-muted w-fit">
<Icon className="min-w-6 min-h-6 smalltablet:min-w-8 smalltablet:min-h-8" />
</div>
<CardTitle>
<h3 className="text-lg">{title}</h3>
<h3 className="text-base smalltablet:text-lg">{title}</h3>
</CardTitle>
</div>
<div className="flex flex-col gap-6 justify-between h-full">
<p className="font-extralight">{description}</p>
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-4 smalltablet:gap-6 justify-between h-full">
<p className="text-sm smalltablet:text-base font-extralight">{description}</p>
<div className="flex flex-col gap-3 smalltablet:gap-4">
<Separator />
{/* Main Skills */}
<div className="flex gap-3 flex-wrap">
<div className="flex gap-2 smalltablet:gap-3 flex-wrap">
{mainSkills.map((skill) => (
<span key={skill} className="text-sm font-medium text-gray-800 dark:text-gray-100">
<span key={skill} className="text-xs smalltablet:text-sm font-medium text-gray-800 dark:text-gray-100">
{skill}
</span>
))}
@@ -56,10 +56,10 @@ function SkillCard({ icon: Icon, title, description, mainSkills, subSkills }: Sk
{/* Sub Skills - Toggleable */}
{subSkills.length > 0 && (
<div className={`overflow-hidden transition-all duration-300 ease-in-out ${isExpanded ? 'max-h-96' : 'max-h-0'}`}>
<Separator className="opacity-50 mb-4" />
<div className="flex gap-3 flex-wrap">
<Separator className="opacity-50 mb-3 smalltablet:mb-4" />
<div className="flex gap-2 smalltablet:gap-3 flex-wrap">
{subSkills.map((skill) => (
<span key={skill} className="text-sm font-extralight text-gray-600 dark:text-gray-300 opacity-80">
<span key={skill} className="text-xs smalltablet:text-sm font-extralight text-gray-600 dark:text-gray-300 opacity-80">
{skill}
</span>
))}
@@ -74,13 +74,13 @@ function SkillCard({ icon: Icon, title, description, mainSkills, subSkills }: Sk
export default function Skills() {
return (
<main className="flex max-w-7xl mx-auto flex-col items-center justify-center gap-16 p-4 tablet:p-8 py-20">
<main className="flex max-w-7xl mx-auto flex-col items-center justify-center gap-12 smalltablet:gap-14 tablet:gap-16 p-4 smalltablet:p-6 tablet:p-8 py-16 smalltablet:py-18 tablet:py-20">
<SectionHeader
title="Skills & Expertise"
description="Technologies and tools I work with to build amazing web applications"
/>
<div className="grid grid-cols-1 tablet:grid-cols-2 pc:grid-cols-3 gap-6">
<div className="grid grid-cols-1 smalltablet:grid-cols-2 desktop:grid-cols-3 gap-4 smalltablet:gap-5 tablet:gap-6">
<SkillCard
icon={Code}
title="Frontend Development"

View File

@@ -26,25 +26,25 @@ const Footer = () => {
const currentYear = new Date().getFullYear();
return (
<footer className="my-12 mx-6 tablet:m-12 pc:mx-24 pc:mt-20">
<div className="flex items-center gap-8 mb-6 px-4">
<footer className="my-8 mx-4 smalltablet:my-10 smalltablet:mx-6 tablet:m-12 desktop:mx-24 desktop:mt-20">
<div className="flex flex-wrap items-center gap-4 smalltablet:gap-6 tablet:gap-8 mb-4 smalltablet:mb-6 px-2 smalltablet:px-4">
{FOOTER_MENU_ITEMS.map((item) => (
<Link
key={item.name}
href={item.path}
className="text-lg font-light hover:opacity-70 transition-opacity"
className="text-sm smalltablet:text-base tablet:text-lg font-light hover:opacity-70 transition-opacity"
>
{item.name}
</Link>
))}
</div>
<Separator />
<div className="flex w-full pt-4">
<div className="w-full flex flex-col justify-between items-center gap-4 tablet:flex-row">
<p className="text-sm text-center text-muted-foreground">
<div className="flex w-full pt-3 smalltablet:pt-4">
<div className="w-full flex flex-col justify-between items-center gap-3 smalltablet:gap-4 tablet:flex-row">
<p className="text-xs smalltablet:text-sm text-center text-muted-foreground">
© {currentYear} All rights reserved
</p>
<div className="flex justify-center gap-8">
<div className="flex justify-center gap-6 smalltablet:gap-8">
{SOCIAL_MEDIA.map(({ name, icon: Icon, href }) => (
<Link
key={name}
@@ -53,7 +53,7 @@ const Footer = () => {
rel="noopener noreferrer"
className="hover:opacity-70 transition-opacity"
>
<Icon className="w-6 h-6" />
<Icon className="w-5 h-5 smalltablet:w-6 smalltablet:h-6" />
</Link>
))}
</div>

View File

@@ -31,14 +31,14 @@ const HeaderProfile = ({
href="/"
className={hasOrder ? 'order-2 tablet:order-1' : ''}
>
<div className="flex items-center gap-4">
<div className="flex items-center gap-3 smalltablet:gap-4">
{showImage && (
<div
className={`w-10 h-10 pc:w-14 pc:h-14 rounded-full bg-gray-300 dark:bg-gray-600 ${imageClassName || ''}`}
className={`w-9 h-9 smalltablet:w-10 smalltablet:h-10 desktop:w-14 desktop:h-14 rounded-full bg-gray-300 dark:bg-gray-600 ${imageClassName || ''}`}
/>
)}
{showName && (
<h1 className="text-base pc:text-lg transition-colors font-bold">
<h1 className="text-sm smalltablet:text-base desktop:text-lg transition-colors font-bold">
MINJO KIM
</h1>
)}
@@ -72,12 +72,12 @@ const HeaderMenuItemsDesktop = () => {
{HEADER_MENU_ITEMS.map((item) => (
<div
key={item.name}
className="px-2 pc:px-6 transition-all"
className="px-2 tablet:px-4 desktop:px-6 transition-all"
>
<Link
href={item.path}
onClick={(e) => handleScrollClick(e, item.path, item.isScroll)}
className="font-extralight text-sm pc:text-base duration-100 ease-in hover:border-b-2 hover:border-b-black hover:dark:border-b-white pb-1"
className="font-extralight text-xs tablet:text-sm desktop:text-base duration-100 ease-in hover:border-b-2 hover:border-b-black hover:dark:border-b-white pb-1"
>
{item.name}
</Link>
@@ -131,22 +131,22 @@ const HeaderMenuItemsMobile = () => {
<X className="w-6 h-6" />
</button>
</div>
<div className="flex flex-col items-center justify-between h-full py-16">
<div className="flex flex-col w-full h-full justify-center items-center gap-20">
<div className="flex flex-col items-center justify-between h-full py-12 smalltablet:py-16">
<div className="flex flex-col w-full h-full justify-center items-center gap-16 smalltablet:gap-20">
<HeaderProfile
showImage={false}
hasOrder={false}
/>
<div className="flex flex-col items-center gap-12">
<div className="flex flex-col items-center gap-8 smalltablet:gap-12">
{HEADER_MENU_ITEMS.map(item => (
<div
key={item.name}
className="px-4 pc:px-8 transition-all items-center"
className="px-4 smalltablet:px-6 desktop:px-8 transition-all items-center"
>
<Link
href={item.path}
onClick={(e) => handleScrollClick(e, item.path, item.isScroll)}
className="text-xl duration-100 ease-in hover:border-b-2 hover:border-b-brand-primary hover:dark:border-b-white pb-1"
className="text-lg smalltablet:text-xl duration-100 ease-in hover:border-b-2 hover:border-b-brand-primary hover:dark:border-b-white pb-1"
>
{item.name}
</Link>
@@ -167,7 +167,7 @@ const Header = () => {
<header
className="fixed h-[70px] top-0 z-50 w-full border-b border-border bg-background/95 backdrop-blur supports-backdrop-filter:bg-background/60"
>
<div className="flex justify-between items-center tracking-wider tablet:tracking-widest font-brand-book px-4 pc:px-8 py-4 max-w-[1920px] mx-auto">
<div className="flex justify-between items-center tracking-wider tablet:tracking-widest font-brand-book px-4 smalltablet:px-6 desktop:px-8 py-4 max-w-[1920px] mx-auto">
<HeaderProfile
showImage={false}
/>