Compare commits

..
53 changed files with 93 additions and 462 deletions

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 339 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 267 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 MiB

View file

@ -2,14 +2,11 @@
import AboutRow from "@/components/AboutRow.astro"; import AboutRow from "@/components/AboutRow.astro";
const cards = [ const cards = [
{ { src: "/about/cards/0.avif", alt: "활동 사진 카드" },
src: "/about/cards/0.avif", { src: "/about/cards/1.avif", alt: "활동 사진 카드" },
alt: "2026 사이버 가디언즈 CMX 대회 활동 사진", { src: "/about/cards/2.avif", alt: "활동 일러스트 카드" },
}, { src: "/about/cards/3.avif", alt: "활동 일러스트 카드" },
{ src: "/about/cards/1.avif", alt: "화이트햇스쿨 활동 사진" }, { src: "/about/cards/4.avif", alt: "활동 사진 카드" },
{ src: "/about/cards/2.avif", alt: "글로벌 스타트업 부트캠프 명찰 사진" },
{ src: "/about/cards/3.avif", alt: "글로벌 스타트업 부트캠프 활동 사진" },
{ src: "/about/cards/4.avif", alt: "손그림 일러스트" },
]; ];
--- ---
@ -27,7 +24,7 @@ const cards = [
About<span class="text-foreground/35" aria-hidden="true">.</span> About<span class="text-foreground/35" aria-hidden="true">.</span>
</h2> </h2>
<p class="mt-6 max-w-lg text-base leading-8 text-foreground"> <p class="mt-6 max-w-lg text-base leading-8 text-foreground">
안녕하세요. 정보보안과 소프트웨어 엔지니어링을 공부하는 남현석입니다.<br/> 안녕하세요. 정보보안과 소프트웨어 엔지니어링을 공부하는 암냥입니다.<br/>
새로운 기술을 배우고, 머릿속의 아이디어를 실제로 작동하는 것으로<br/> 새로운 기술을 배우고, 머릿속의 아이디어를 실제로 작동하는 것으로<br/>
만드는 일을 좋아해요. 만드는 일을 좋아해요.
</p> </p>
@ -107,15 +104,11 @@ const cards = [
<div class="about-card-panel order-last min-h-[min(100vw,540px)] overflow-hidden bg-muted lg:sticky lg:top-0 lg:order-none lg:h-[calc(100svh-64px)] lg:min-h-0" aria-label="활동 사진 선택"> <div class="about-card-panel order-last min-h-[min(100vw,540px)] overflow-hidden bg-muted lg:sticky lg:top-0 lg:order-none lg:h-[calc(100svh-64px)] lg:min-h-0" aria-label="활동 사진 선택">
<div class="about-card-stage" data-card-stage> <div class="about-card-stage" data-card-stage>
<canvas class="about-card-canvas" data-card-canvas aria-hidden="true"></canvas> <canvas class="about-card-canvas" data-card-canvas aria-hidden="true"></canvas>
<div class="about-card-meta" aria-hidden="true">
<span>MOMENTS / 05</span>
<span>click to reveal</span>
</div>
<div class="about-card-access" role="group" aria-label="활동 카드 선택"> <div class="about-card-access" role="group" aria-label="활동 카드 선택">
{cards.map((card, index) => ( {cards.map((card, index) => (
<button <button
type="button" type="button"
aria-label={`${card.alt} 선택`} aria-label={`활동 카드 ${index + 1} 선택`}
aria-pressed="false" aria-pressed="false"
data-card-control={index} data-card-control={index}
data-card-src={card.src} data-card-src={card.src}
@ -435,17 +428,6 @@ const cards = [
transform: translateX(-50%); transform: translateX(-50%);
} }
.about-card-stage::after {
position: absolute;
inset: 0;
background:
linear-gradient(120deg, transparent 30%, rgb(255 255 255 / 4%) 50%, transparent 70%),
repeating-linear-gradient(90deg, transparent 0 88px, rgb(255 255 255 / 2%) 89px, transparent 90px);
content: "";
opacity: .45;
pointer-events: none;
}
.about-card-canvas { .about-card-canvas {
display: block; display: block;
width: 100%; width: 100%;
@ -456,30 +438,6 @@ const cards = [
z-index: 1; z-index: 1;
} }
.about-card-meta {
position: absolute;
inset: 28px 30px auto;
z-index: 2;
display: flex;
align-items: center;
justify-content: space-between;
color: color-mix(in srgb, var(--foreground) 52%, transparent);
font-size: 10px;
font-weight: 700;
letter-spacing: .16em;
pointer-events: none;
text-transform: uppercase;
}
.about-card-meta span:last-child {
padding: 8px 12px;
border: 1px solid color-mix(in srgb, var(--foreground) 14%, transparent);
border-radius: 999px;
background: color-mix(in srgb, var(--background) 42%, transparent);
backdrop-filter: blur(10px);
letter-spacing: .1em;
}
.about-card-access { .about-card-access {
position: absolute; position: absolute;
width: 1px; width: 1px;

View file

@ -1,11 +1,10 @@
--- ---
const navigationItems = [ const navigationItems = [
{ label: "Home", href: "/#home" }, { label: "Home", href: "#home" },
{ label: "About", href: "/#about" }, { label: "About", href: "#about" },
{ label: "Projects", href: "/#projects" }, { label: "Projects", href: "#projects" },
{ label: "Experience", href: "/#experience" }, { label: "Experience", href: "#experience" },
{ label: "Carrer", href: "/Carrer" }, { label: "Contact", href: "#contact" },
{ label: "Contact", href: "/#contact" },
] as const; ] as const;
const desktopLinkClass = const desktopLinkClass =
@ -20,11 +19,9 @@ const mobileLinkClass =
> >
<a <a
class="shrink-0 text-2xl font-bold leading-[normal] text-foreground no-underline max-md:text-xl" class="shrink-0 text-2xl font-bold leading-[normal] text-foreground no-underline max-md:text-xl"
href="/#home" href="#home"
data-node-id="43:28" data-node-id="43:28">@imnya.ng</a
> >
@imnya.ng
</a>
<div id="navigation" class="relative flex shrink-0 items-center"> <div id="navigation" class="relative flex shrink-0 items-center">
<nav <nav
@ -32,11 +29,16 @@ const mobileLinkClass =
aria-label="주요 메뉴" aria-label="주요 메뉴"
data-node-id="43:40" data-node-id="43:40"
> >
{navigationItems.map((item) => ( {
<a class={desktopLinkClass} href={item.href}> navigationItems.map((item) => (
<a
class={desktopLinkClass}
href={item.href}
>
{item.label} {item.label}
</a> </a>
))} ))
}
</nav> </nav>
<button <button
@ -54,8 +56,7 @@ const mobileLinkClass =
></span> ></span>
<span <span
aria-hidden="true" aria-hidden="true"
class="absolute h-px w-5 bg-foreground" class="absolute h-px w-5 bg-foreground"></span>
></span>
<span <span
aria-hidden="true" aria-hidden="true"
class="absolute h-px w-5 translate-y-1.5 bg-foreground" class="absolute h-px w-5 translate-y-1.5 bg-foreground"
@ -67,11 +68,13 @@ const mobileLinkClass =
class="absolute right-0 top-[calc(100%+12px)] z-50 hidden min-w-40 flex-col gap-1 rounded-2xl border border-border bg-background p-2 shadow-[0_12px_30px_rgba(0,0,0,0.18)] md:hidden" class="absolute right-0 top-[calc(100%+12px)] z-50 hidden min-w-40 flex-col gap-1 rounded-2xl border border-border bg-background p-2 shadow-[0_12px_30px_rgba(0,0,0,0.18)] md:hidden"
aria-label="모바일 메뉴" aria-label="모바일 메뉴"
> >
{navigationItems.map((item) => ( {
navigationItems.map((item) => (
<a class={mobileLinkClass} href={item.href}> <a class={mobileLinkClass} href={item.href}>
{item.label} {item.label}
</a> </a>
))} ))
}
</nav> </nav>
</div> </div>
</header> </header>
@ -106,10 +109,7 @@ const mobileLinkClass =
}); });
document.addEventListener("click", (event) => { document.addEventListener("click", (event) => {
if ( if (event.target instanceof Node && !navigation.contains(event.target)) {
event.target instanceof Node &&
!navigation.contains(event.target)
) {
setMenuOpen(false); setMenuOpen(false);
} }
}); });

View file

@ -15,17 +15,19 @@
class="m-0 flex h-[250px] w-full max-w-full flex-col justify-center break-words whitespace-nowrap text-[clamp(34px,3.333333vw,64px)] font-bold leading-[normal] tracking-[0] max-md:whitespace-normal max-md:text-[clamp(24px,8vw,48px)] max-md:leading-[1.12]" class="m-0 flex h-[250px] w-full max-w-full flex-col justify-center break-words whitespace-nowrap text-[clamp(34px,3.333333vw,64px)] font-bold leading-[normal] tracking-[0] max-md:whitespace-normal max-md:text-[clamp(24px,8vw,48px)] max-md:leading-[1.12]"
> >
<span class="block" data-node-id="43:14"> <span class="block" data-node-id="43:14">
<span class="font-bold">항상 꿈꾸던 것</span> <span class="font-bold">항상 꿈꾸던 것</span><span
<span class="font-normal text-foreground/50">들을</span> class="font-normal text-foreground/50">들을</span
>
</span> </span>
<span class="block" data-node-id="43:16"> <span class="block" data-node-id="43:16">
<span class="font-bold">현실</span> <span class="font-bold">현실</span><span
<span class="font-normal text-foreground/50"> class="font-normal text-foreground/50"
{"로 만들어나가는 "} >{"로 만들어나가는 "}
</span> </span><br class="hidden max-md:block md:hidden" /><span
<br class="hidden max-md:block md:hidden" /> class="font-bold"
<span class="font-bold">남현석</span> >
<span class="font-normal text-foreground/50">입니다.</span> 암냥</span
><span class="font-normal text-foreground/50">입니다.</span>
</span> </span>
</h1> </h1>
</div> </div>
@ -40,10 +42,16 @@
data-node-id="42:10" data-node-id="42:10"
> >
<picture class="contents"> <picture class="contents">
<source
id="hero-theme-source"
media="(prefers-color-scheme: dark)"
srcset="/hero-dark.avif"
type="image/png"
/>
<img <img
class="block h-full w-full object-cover object-center transition-[scale,border-radius,box-shadow] duration-500 ease-[var(--ease-out-expo)] hover:z-10 hover:scale-105 hover:rounded-lg hover:shadow-[0_15px_45px_#0006] motion-reduce:transition-none" class="block h-full w-full object-cover object-center transition-[scale,border-radius,box-shadow] duration-500 ease-[var(--ease-out-expo)] hover:z-10 hover:scale-105 hover:rounded-lg hover:shadow-[0_15px_45px_#0006] motion-reduce:transition-none"
src="/hero-dark.avif" src="/hero-light.avif"
alt="벚꽃이 핀 수로에서 보트를 타고 있는 캐릭터" alt="벚꽃이 핀 수로에서 보트를 타고 있는 암냥"
width="3840" width="3840"
height="2160" height="2160"
fetchpriority="high" fetchpriority="high"
@ -52,7 +60,36 @@
<div <div
aria-hidden="true" aria-hidden="true"
class="pointer-events-none absolute inset-x-0 bottom-0 z-20 h-[min(150px,35%)] bg-gradient-to-b from-transparent via-background/20 to-background backdrop-blur-[2px] [mask-image:linear-gradient(to_bottom,transparent,black_42%)]" class="pointer-events-none absolute inset-x-0 bottom-0 z-20 h-[min(150px,35%)] bg-gradient-to-b from-transparent via-background/20 to-background backdrop-blur-[2px] [mask-image:linear-gradient(to_bottom,transparent,black_42%)]"
></div> >
</div>
</div> </div>
</section> </section>
</div> </div>
<script>
const heroThemeSource =
document.querySelector<HTMLSourceElement>("#hero-theme-source");
if (heroThemeSource) {
const syncHeroTheme = () => {
const root = document.documentElement;
if (root.classList.contains("dark")) {
heroThemeSource.media = "all";
heroThemeSource.srcset = "/hero-dark.avif";
} else if (root.classList.contains("light")) {
heroThemeSource.media = "all";
heroThemeSource.srcset = "/hero-light.avif";
} else {
heroThemeSource.media = "(prefers-color-scheme: dark)";
heroThemeSource.srcset = "/hero-dark.avif";
}
};
syncHeroTheme();
new MutationObserver(syncHeroTheme).observe(document.documentElement, {
attributes: true,
attributeFilter: ["class"],
});
}
</script>

View file

@ -1,368 +0,0 @@
---
import Header from "@/components/Header.astro";
import "@/globals.css";
const portfolios = [
{
subject: "디자인일반 · 2학기",
title: "시각 언어로 정리한 주제 발표",
description:
"주제를 조사하고 핵심 내용을 한 장의 발표 자료로 구성했습니다.",
image: "/portfolio/design/design-02.jpg",
href: "/portfolio/pdfs/design-report.pdf",
label: "발표 학습지 보기",
},
{
subject: "프로그래밍",
title: "C 언어로 익힌 문제 해결",
description:
"자료형, 조건문, 반복문부터 배열·포인터·함수까지 단계적으로 실습했습니다.",
},
{
subject: "정보통신",
title: "네트워크 구성과 연결 확인",
description:
"라우터 설정, PC 간 Ping, SSH 연결을 직접 구성하고 검증했습니다.",
image: "/portfolio/network/ping-ssh.png",
href: "/portfolio/network/all-ping.png",
label: "실습 결과 보기",
},
{
subject: "통합과학",
title: "선천성 대사 이상 탐구",
description:
"질환의 원인과 유전적 배경을 조사해 보고서와 발표 자료로 정리했습니다.",
href: "/portfolio/pdfs/science-report.pdf",
label: "탐구 보고서 보기",
},
{
subject: "웹 프로그래밍 · 2학기",
title: "HTML로 만든 작은 웹 페이지들",
description:
"시맨틱 HTML, CSS 레이아웃, 링크와 미디어를 활용해 여러 페이지를 제작했습니다.",
},
{
subject: "Layer7",
title: "학기말 프로젝트",
description:
"정보보안 동아리에서 팀 프로젝트를 기획하고 결과를 문서화했습니다.",
href: "/portfolio/pdfs/layer7-project.pdf",
label: "프로젝트 문서 보기",
},
] as const;
const securityNotes = [
{
label: "Layer7 학습 기록 전체 보기",
href: "https://blog.imnya.ng/tags/layer7",
},
{ label: "C언어 과제", href: "https://blog.imnya.ng/layer7/01" },
{ label: "리버싱 학습 기록", href: "https://blog.imnya.ng/layer7/10" },
{ label: "시스템 해킹 학습 기록", href: "https://blog.imnya.ng/layer7/15" },
{
label: "Dreamhack 풀이 모아보기",
href: "https://blog.imnya.ng/tags/dreamhack",
},
] as const;
const portfolioDetails = [
{
subject: "디자인일반 · 2학기",
summary: "주제 조사부터 발표 자료 구성까지",
items: [
{ title: "주제 발표 학습지", detail: "조사한 내용을 시각적으로 정리한 발표 자료", href: "/portfolio/pdfs/design-report.pdf" },
{ title: "8월 25일 작업 기록", detail: "디자인 실습 결과물", href: "/portfolio/design/design-01.jpg" },
{ title: "9월 8일 작업 기록", detail: "디자인 실습 결과물", href: "/portfolio/design/design-02.jpg" },
],
},
{
subject: "프로그래밍",
summary: "C 언어 기초에서 자료구조와 프로젝트까지",
items: [
{ title: "프로그래밍의 개요", detail: "개발 도구와 프로그래밍 환경 비교" },
{ title: "자료형·표준 입출력", detail: "변수, 자료형, 형식 지정자, 기본 입출력" },
{ title: "연산자", detail: "산술·관계·논리 연산자와 표현식" },
{ title: "선택 구조", detail: "if, else, switch를 활용한 조건 분기" },
{ title: "반복 구조", detail: "for, while, do-while과 반복 제어" },
{ title: "배열·포인터·함수", detail: "배열을 이용한 데이터 처리와 함수 분리" },
{ title: "구조체·중첩 구조체", detail: "관련 데이터를 하나의 자료형으로 묶기" },
{ title: "배열 기반 프로젝트", detail: "배운 문법을 조합해 작은 프로그램으로 구현" },
],
},
{
subject: "정보통신",
summary: "Packet Tracer로 구성하고 Ping과 SSH로 검증",
items: [
{ title: "라우터 설정", detail: "라우터 인터페이스와 네트워크 기본 설정" },
{ title: "PC 간 연결 확인", detail: "모든 PC의 Ping 테스트로 연결 상태 검증", href: "/portfolio/network/all-ping.png" },
{ title: "라우터 Ping 테스트", detail: "라우터 간 통신과 경로 확인", href: "/portfolio/network/router-ping.png" },
{ title: "Ping and SSH", detail: "원격 접속을 포함한 네트워크 실습", href: "/portfolio/network/ping-ssh.png" },
{ title: "수행평가와 토폴로지", detail: "Packet Tracer 파일로 네트워크 토폴로지 구성" },
],
},
{
subject: "통합과학",
summary: "생명과학 주제를 조사하고 보고서로 정리",
items: [
{ title: "선천성 대사 이상", detail: "질환의 원인과 유전적 배경을 조사한 탐구 보고서", href: "/portfolio/pdfs/science-report.pdf" },
{ title: "주제 발표 자료", detail: "탐구 내용을 발표용 자료로 재구성" },
],
},
{
subject: "웹 프로그래밍 · 2학기",
summary: "HTML 문서 구조와 CSS·미디어 활용",
items: [
{ title: "시맨틱 웹 페이지", detail: "header, main, section 등 의미 있는 HTML 구조" },
{ title: "제주도 소개 페이지", detail: "이미지·링크·목록을 활용한 정보 페이지" },
{ title: "선린인터넷고등학교 페이지", detail: "학교 소개형 화면 구성" },
{ title: "Notepad 실습", detail: "텍스트 편집기에서 HTML 문서 작성" },
{ title: "VS Code 실습", detail: "개발 도구를 활용한 웹 문서 작성" },
],
},
{
subject: "Layer7 · Dreamhack",
summary: "보안 동아리 과제와 문제 풀이 기록",
items: [
{ title: "C언어 과제", detail: "조건문·반복문·배열을 이용한 CodeUp 문제 풀이", href: "https://blog.imnya.ng/layer7/01" },
{ title: "컴퓨터 구조", detail: "구조와 실행 흐름을 이해하기 위한 과제", href: "https://blog.imnya.ng/layer7/06" },
{ title: "리버싱", detail: "바이너리 분석과 디버깅 학습 기록", href: "https://blog.imnya.ng/layer7/10" },
{ title: "시스템 해킹", detail: "메모리 구조와 취약점 실습", href: "https://blog.imnya.ng/layer7/15" },
{ title: "웹 해킹", detail: "웹 취약점 분석과 과제 풀이", href: "https://blog.imnya.ng/layer7/19" },
{ title: "Dreamhack 풀이", detail: "웹·리버싱·시스템 분야 문제 풀이 모음", href: "https://blog.imnya.ng/tags/dreamhack" },
{ title: "학기말 프로젝트", detail: "팀 프로젝트 기획과 결과 문서", href: "/portfolio/pdfs/layer7-project.pdf" },
],
},
] as const;
const programmingMaterials = [
{ title: "(주제)작품설명서(배열을_이용한_프로젝트)(1)", file: "10104남현석 - (주제)작품설명서(배열을_이용한_프로젝트)(1).pdf" },
{ title: "(주제)작품설명서(배열을_이용한_프로젝트)", file: "10104남현석 - (주제)작품설명서(배열을_이용한_프로젝트).pdf" },
{ title: "(주제)탐구보고서(Duet_Debug _ Deep_Dive)", file: "10104남현석 - (주제)탐구보고서(Duet_Debug _ Deep_Dive).pdf" },
{ title: "01_포인터", file: "10104남현석 - 01_포인터.pdf" },
{ title: "01_프로그래밍의 개요(1)", file: "10104남현석 - 01_프로그래밍의 개요(1).pdf" },
{ title: "01_프로그래밍의 개요", file: "10104남현석 - 01_프로그래밍의 개요.pdf" },
{ title: "02_1_다양한 포인터 실습(기본, 배열, 함수)", file: "10104남현석 - 02_1_다양한 포인터 실습(기본, 배열, 함수).pdf" },
{ title: "02_2_다양한 포인터 실습(문자열)", file: "10104남현석 - 02_2_다양한 포인터 실습(문자열).pdf" },
{ title: "02_프로그래밍의 기초_자료형_표준입출력(1)", file: "10104남현석 - 02_프로그래밍의 기초_자료형_표준입출력(1).pdf" },
{ title: "02_프로그래밍의 기초_자료형_표준입출력", file: "10104남현석 - 02_프로그래밍의 기초_자료형_표준입출력.pdf" },
{ title: "03_구조체", file: "10104남현석 - 03_구조체.pdf" },
{ title: "03_프로그래밍의 기초_연산자(1)", file: "10104남현석 - 03_프로그래밍의 기초_연산자(1).pdf" },
{ title: "03_프로그래밍의 기초_연산자(2)", file: "10104남현석 - 03_프로그래밍의 기초_연산자(2).pdf" },
{ title: "03_프로그래밍의 기초_연산자", file: "10104남현석 - 03_프로그래밍의 기초_연산자.pdf" },
{ title: "04_구조체", file: "10104남현석 - 04_구조체.pdf" },
{ title: "04_구조체_배열,포인터,함수,중첩구조체", file: "10104남현석 - 04_구조체_배열,포인터,함수,중첩구조체.pdf" },
{ title: "04_프로그래밍의 기초_선택구조(1)", file: "10104남현석 - 04_프로그래밍의 기초_선택구조(1).pdf" },
{ title: "04_프로그래밍의 기초_선택구조", file: "10104남현석 - 04_프로그래밍의 기초_선택구조.pdf" },
{ title: "05_프로그래밍의 기초_반복구조", file: "10104남현석 - 05_프로그래밍의 기초_반복구조.pdf" },
{ title: "06_중첩된_제어구조", file: "10104남현석 - 06_중첩된_제어구조.pdf" },
{ title: "07_1차원배열", file: "10104남현석 - 07_1차원배열.pdf" },
{ title: "08_함수", file: "10104남현석 - 08_함수.pdf" },
{ title: "15_재귀함수_라이브러리함수", file: "10104남현석 - 15_재귀함수_라이브러리함수.pdf" },
{ title: "18_재귀호출_보충_문제지", file: "10104남현석 - 18_재귀호출_보충_문제지.pdf" },
{ title: "나의 프로젝트와 한권의 책", file: "10104남현석 - 나의 프로젝트와 한권의 책.pdf" },
{ title: "배열을_이용한_프로젝트_개발노트(1)", file: "10104남현석 - 배열을_이용한_프로젝트_개발노트(1).pdf" },
{ title: "배열을_이용한_프로젝트_개발노트(2)", file: "10104남현석 - 배열을_이용한_프로젝트_개발노트(2).pdf" },
{ title: "배열을_이용한_프로젝트_개발노트", file: "10104남현석 - 배열을_이용한_프로젝트_개발노트.pdf" },
{ title: "수행평가1차시_연산자까지(1)", file: "10104남현석 - 수행평가1차시_연산자까지(1).pdf" },
{ title: "수행평가1차시_연산자까지", file: "10104남현석 - 수행평가1차시_연산자까지.pdf" },
{ title: "수행평가2차시_제어구조", file: "10104남현석 - 수행평가2차시_제어구조.pdf" },
{ title: "프로그래밍 개발 도구 비교하기(1)", file: "10104남현석 - 프로그래밍 개발 도구 비교하기(1).pdf" },
{ title: "프로그래밍 개발 도구 비교하기", file: "10104남현석 - 프로그래밍 개발 도구 비교하기.pdf" },
] as const;
---
<html lang="ko">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width" />
<meta name="color-scheme" content="light dark" />
<meta name="theme-color" content="#fcf8f9" />
<title>Carrer | 남현석</title>
<meta name="description" content="남현석의 경력 및 활동 기록" />
</head>
<body class="m-0 min-w-[320px] bg-background font-sans text-foreground">
<Header />
<main class="min-h-svh px-6 pb-12 pt-[104px] sm:px-10 lg:px-16">
<div class="mx-auto flex max-w-6xl flex-col gap-8">
<div class="flex items-end justify-between gap-6">
<div>
<h1 class="m-0 text-4xl font-bold tracking-tight sm:text-6xl">
Carrer Portfolio
<span class="text-foreground/30">.</span>
</h1>
</div>
<a
href="/Carrer.pdf"
target="_blank"
rel="noreferrer"
class="shrink-0 rounded-full border border-border px-4 py-2 text-sm font-semibold text-foreground no-underline transition-colors hover:bg-foreground hover:text-background"
>
새 창에서 열기
</a>
</div>
<div class="overflow-hidden rounded-3xl border border-border bg-muted p-2 shadow-[0_24px_80px_rgba(44,20,35,0.12)] sm:p-4">
<object
data="/Carrer.pdf#view=FitH"
type="application/pdf"
class="h-[calc(100svh-220px)] min-h-[560px] w-full rounded-2xl bg-white"
>
<div class="flex min-h-[560px] flex-col items-center justify-center gap-4 rounded-2xl bg-background p-8 text-center">
<p class="m-0 text-foreground/70">
이 브라우저에서는 PDF 미리보기를 지원하지
않습니다.
</p>
<a
href="/Carrer.pdf"
download
class="rounded-full bg-foreground px-5 py-3 text-sm font-semibold text-background no-underline"
>
PDF 다운로드
</a>
</div>
</object>
</div>
<section aria-labelledby="school-portfolio-title" class="pt-8">
<div class="mb-6 flex items-end justify-between gap-4">
<div>
<p class="mb-2 text-sm font-semibold tracking-[0.18em] text-foreground/45 uppercase">
2026 · 교과 활동
</p>
<h2
id="school-portfolio-title"
class="m-0 text-2xl font-bold tracking-tight sm:text-3xl"
>
교과 포트폴리오
<span class="text-foreground/30">.</span>
</h2>
</div>
<span class="text-sm text-foreground/45">6개 분야</span>
</div>
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{portfolios.map((portfolio) => (
<article class="group flex min-h-56 flex-col overflow-hidden rounded-3xl border border-border bg-background transition-[transform,box-shadow] duration-200 hover:-translate-y-1 hover:shadow-[0_16px_45px_rgba(44,20,35,0.12)]">
{portfolio.image && (
<img
src={portfolio.image}
alt=""
class="h-36 w-full object-cover transition-transform duration-500 group-hover:scale-105"
loading="lazy"
/>
)}
<div class="flex flex-1 flex-col p-5">
<p class="m-0 text-xs font-semibold tracking-[0.12em] text-foreground/45 uppercase">
{portfolio.subject}
</p>
<h3 class="mt-3 mb-0 text-lg font-bold tracking-tight">
{portfolio.title}
</h3>
<p class="mt-2 mb-0 text-sm leading-6 text-foreground/60">
{portfolio.description}
</p>
{portfolio.href && (
<a
href={portfolio.href}
target="_blank"
rel="noreferrer"
class="mt-5 inline-flex w-fit items-center gap-1 text-sm font-semibold text-foreground underline decoration-foreground/25 underline-offset-4 transition-colors hover:decoration-foreground"
>
{portfolio.label}
<span aria-hidden="true">↗</span>
</a>
)}
</div>
</article>
))}
</div>
<div class="mt-4 flex flex-col gap-5 rounded-3xl border border-border bg-muted p-6 sm:flex-row sm:items-center sm:justify-between">
<div>
<p class="m-0 text-xs font-semibold tracking-[0.12em] text-foreground/45 uppercase">
Security notes
</p>
<h3 class="mt-2 mb-0 text-lg font-bold tracking-tight">
Layer7 · Dreamhack 학습 기록
</h3>
<p class="mt-2 mb-0 text-sm text-foreground/60">
C언어부터 컴퓨터 구조, 리버싱, 시스템·웹
해킹까지의 풀이와 과제를 블로그에 기록하고
있습니다.
</p>
</div>
<div class="flex flex-wrap gap-x-5 gap-y-2 sm:max-w-[48%] sm:justify-end">
{securityNotes.map((note) => (
<a
href={note.href}
target="_blank"
rel="noreferrer"
class="text-sm font-semibold text-foreground underline decoration-foreground/25 underline-offset-4 transition-colors hover:decoration-foreground"
>
{note.label}{" "}
<span aria-hidden="true">↗</span>
</a>
))}
</div>
</div>
<div class="mt-6 flex flex-col gap-3">
{portfolioDetails.map((section) => (
<details class="group rounded-2xl border border-border bg-background open:bg-muted">
<summary class="flex cursor-pointer list-none items-center justify-between gap-4 px-5 py-4 font-semibold [&::-webkit-details-marker]:hidden">
<span>
{section.subject}
<span class="ml-3 text-sm font-normal text-foreground/50">{section.summary}</span>
</span>
<span class="text-xl font-normal text-foreground/40 transition-transform group-open:rotate-45">+</span>
</summary>
<div class="grid gap-px border-t border-border bg-border sm:grid-cols-2 lg:grid-cols-3">
{section.items.map((item) => (
<div class="bg-background p-4">
<p class="m-0 font-semibold">{item.title}</p>
<p class="mt-1 mb-0 text-sm leading-6 text-foreground/55">{item.detail}</p>
{item.href && (
<a
href={item.href}
target="_blank"
rel="noreferrer"
class="mt-3 inline-block text-sm font-semibold underline decoration-foreground/25 underline-offset-4 hover:decoration-foreground"
>
자료 보기 ↗
</a>
)}
</div>
))}
</div>
</details>
))}
</div>
<details class="group mt-3 rounded-2xl border border-border bg-background open:bg-muted">
<summary class="flex cursor-pointer list-none items-center justify-between gap-4 px-5 py-4 font-semibold [&::-webkit-details-marker]:hidden">
<span>
프로그래밍 원본 자료 PDF
<span class="ml-3 text-sm font-normal text-foreground/50">33개 문서</span>
</span>
<span class="text-xl font-normal text-foreground/40 transition-transform group-open:rotate-45">+</span>
</summary>
<div class="grid gap-px border-t border-border bg-border sm:grid-cols-2 lg:grid-cols-3">
{programmingMaterials.map((material) => (
<a
href={`/portfolio/programming/${encodeURIComponent(material.file)}`}
target="_blank"
rel="noreferrer"
class="bg-background p-4 text-sm font-semibold text-foreground no-underline transition-colors hover:bg-muted"
>
{material.title} <span aria-hidden="true">↗</span>
</a>
))}
</div>
</details>
</section>
</div>
</main>
</body>
</html>

View file

@ -24,16 +24,20 @@ import "@/globals.css";
media="(prefers-color-scheme: dark)" media="(prefers-color-scheme: dark)"
content="#191014" content="#191014"
/> />
<title>남현석 | imnya.ng</title> <title>암냥 | imnya.ng</title>
<meta <meta
name="description" name="description"
content="항상 꿈꾸던 것들을 현실로 만들어나가기 위해..." content="항상 꿈꾸던 것들을 현실로 만들어나가기 위해..."
/> />
</head> </head>
<body class="m-0 min-w-[320px] overflow-hidden bg-background font-sans text-foreground transition-colors duration-300"> <body
class="m-0 min-w-[320px] overflow-hidden bg-background font-sans text-foreground transition-colors duration-300"
>
<Header /> <Header />
<main class="mt-[64px] h-[calc(100svh-64px)] snap-y snap-proximity overflow-x-hidden overflow-y-auto scroll-smooth overscroll-y-contain bg-background transition-colors duration-300 [&::-webkit-scrollbar]:h-[11px] [&::-webkit-scrollbar]:w-[19px] [&::-webkit-scrollbar-thumb]:rounded-2xl [&::-webkit-scrollbar-thumb]:border-[5px] [&::-webkit-scrollbar-thumb]:border-background [&::-webkit-scrollbar-thumb]:bg-[var(--scrollbar)] [&::-webkit-scrollbar-thumb:hover]:bg-[var(--scrollbar-hover)] [&::-webkit-scrollbar-track]:bg-transparent"> <main
class="mt-[64px] h-[calc(100svh-64px)] snap-y snap-proximity overflow-x-hidden overflow-y-auto scroll-smooth overscroll-y-contain bg-background transition-colors duration-300 [&::-webkit-scrollbar]:h-[11px] [&::-webkit-scrollbar]:w-[19px] [&::-webkit-scrollbar-thumb]:rounded-2xl [&::-webkit-scrollbar-thumb]:border-[5px] [&::-webkit-scrollbar-thumb]:border-background [&::-webkit-scrollbar-thumb]:bg-[var(--scrollbar)] [&::-webkit-scrollbar-thumb:hover]:bg-[var(--scrollbar-hover)] [&::-webkit-scrollbar-track]:bg-transparent"
>
<Hero /> <Hero />
<About /> <About />
<EPCProjects /> <EPCProjects />
@ -46,7 +50,7 @@ import "@/globals.css";
<script> <script>
const scrollContainer = document.querySelector<HTMLElement>("main"); const scrollContainer = document.querySelector<HTMLElement>("main");
const inPageLinks = const inPageLinks =
document.querySelectorAll<HTMLAnchorElement>('a[href^="/#"]'); document.querySelectorAll<HTMLAnchorElement>('a[href^="#"]');
const getTargetScrollTop = (target: HTMLElement) => const getTargetScrollTop = (target: HTMLElement) =>
target.offsetTop - (scrollContainer?.offsetTop ?? 0); target.offsetTop - (scrollContainer?.offsetTop ?? 0);
@ -64,7 +68,7 @@ import "@/globals.css";
inPageLinks.forEach((link) => { inPageLinks.forEach((link) => {
link.addEventListener("click", (event) => { link.addEventListener("click", (event) => {
const hash = new URL(link.href).hash; const hash = link.getAttribute("href");
const target = hash const target = hash
? document.querySelector<HTMLElement>(hash) ? document.querySelector<HTMLElement>(hash)
: null; : null;