니디걸오버도즈

This commit is contained in:
암냥 2025-12-19 00:54:03 +09:00
commit 5be9625af3
No known key found for this signature in database
25 changed files with 208 additions and 29 deletions

View file

@ -182,4 +182,18 @@
.image-scale:hover {
z-index:10;
box-shadow:0 15px 45px #0006
}
/* Fullscreen Scroll Snap */
html {
scroll-behavior: smooth;
}
body {
scroll-snap-type: y mandatory;
}
.snap-section {
scroll-snap-align: start;
scroll-snap-stop: always;
}

View file

@ -3,45 +3,46 @@ import Projects from "@/components/Projects";
import TimelineComponent from "@/components/timeline";
import Top from "@/components/Top";
import Image from "next/image";
import DraggableWindow from "@/components/DraggableWindow";
import ReadmeWindow from "@/components/ReadmeWindow";
export default async function BlogIndex() {
return (
<main className="min-h-screen w-screen">
{/* <section id="top" className="max-w-3xl w-full mx-auto px-4">
<div className="flex flex-row items-center w-full h-full gap-6 mt-8">
<Image
src="/Frame.svg"
alt="logo"
className="w-fit h-fit"
width={30}
height={30}
/>
<h1 className="font-ntype text-3xl">
<a href="mailto:me@imnya.ng">me@imnya.ng</a>
</h1>
</div>
<figure className="my-8 w-full h-auto">
<picture className="block bg-gray-100 rounded-xl aspect-3-2 overflow-hidden image-scale object-shadowed mt-8">
<Image src="/1500x500.jpg" alt="test image" className="object-cover object-center transition-transform duration-300 hover:scale-105" width={3168} height={1344} />
</picture>
</figure>
</section> */}
<Top />
<section id="about" className="h-screen max-w-4xl mx-auto px-4">
<div className=" mt-8">
<h1 className="text-2xl font-bold mb-4 w-full">💕 About</h1>
<p> <strong> </strong> <strong>, </strong> .</p>
<main className="min-h-screen w-screen overflow-y-scroll snap-y snap-mandatory">
<div className="max-w-4xl w-full flex flex-row h-auto mx-auto gap-4 items-center my-8 lg:px-0 px-8">
<Image
src="/Frame.svg"
alt="logo"
className="w-fit h-fit"
width={30}
height={30}
/>
<h1 className="font-ntype text-3xl text-foreground/70">
<a href="mailto:me@imnya.ng">me@imnya.ng</a>
</h1>
</div>
<section id="about" className="w-full snap-start snap-always flex flex-col items-center justify-center px-8 lg:px-0">
<DraggableWindow />
<div className="max-w-4xl w-full">
<p> <strong></strong> <strong></strong> <strong>, </strong> .</p>
<p><strong> </strong> , .</p>
<p><strong> 4 Python</strong> , <strong>TypeScript</strong> .</p>
<p> <strong> </strong> .</p>
<br />
<p> .</p>
<ReadmeWindow />
<br />
<p> .</p>
<Projects />
<br />
<TimelineComponent />
<div className="text-muted-foreground text-sm">
<br />
<p>© 2025 HyunSuk Nam. All rights reserved.</p>
<p> .</p>
<br />
</div>
</div>
<TimelineComponent />
</section>
</main>
);

View file

@ -0,0 +1,95 @@
'use client';
import { useState, useRef, useEffect } from 'react';
import Image from 'next/image';
import { useIsMobile } from '@/hooks/use-mobile';
export default function DraggableWindow() {
const isMobile = useIsMobile();
const [isVisible, setIsVisible] = useState(true);
const [position, setPosition] = useState({ x: 100, y: 100 });
const [isDragging, setIsDragging] = useState(false);
const [dragOffset, setDragOffset] = useState({ x: 0, y: 0 });
const windowRef = useRef<HTMLDivElement>(null);
const handleMouseDown = (e: React.MouseEvent) => {
setIsDragging(true);
if (windowRef.current) {
const rect = windowRef.current.getBoundingClientRect();
setDragOffset({
x: e.clientX - rect.left,
y: e.clientY - rect.top,
});
}
};
useEffect(() => {
const handleMouseMove = (e: MouseEvent) => {
if (!isDragging) return;
setPosition({
x: e.clientX - dragOffset.x,
y: e.clientY - dragOffset.y,
});
};
const handleMouseUp = () => {
setIsDragging(false);
};
if (isDragging) {
document.addEventListener('mousemove', handleMouseMove);
document.addEventListener('mouseup', handleMouseUp);
}
return () => {
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
}, [isDragging, dragOffset]);
return (
isMobile ? (
<figure className="mb-8 w-full h-auto">
<picture className="block bg-gray-100 rounded-xl aspect-3-2 overflow-hidden image-scale object-shadowed">
<Image
src="/full.webp"
alt="Banner"
width={1200}
height={400}
priority
className="object-cover object-center transition-transform duration-300 hover:scale-105"
/>
</picture>
</figure >
) : (
isVisible && (
<div
ref={windowRef}
className="fixed cursor-move select-none"
style={{
left: `${position.x}px`,
top: `${position.y}px`,
}}
onMouseDown={handleMouseDown}
>
<div className="relative w-fit h-fit">
<Image
src="/window.png"
alt="Draggable Window"
width={500}
height={400}
priority
draggable={false}
/>
<button
onClick={() => setIsVisible(false)}
className="absolute top-1 right-2 w-5 h-5 cursor-pointer"
aria-label="Close window"
/>
</div>
</div>
)
)
);
}

View file

@ -1,4 +1,5 @@
import { SquareArrowOutUpRight } from 'lucide-react';
import { url } from 'node:inspector';
{/* <a href="https://www.youtube.com/playlist?list=PLZeYZotn5_IOJDek6e35NKzUtJm09yxZD">Effect Playing Contest 2025 Broadcast Develop</a><br />
<a href="https://github.com/imnyang/today.isangjeong">today.isangjeong</a> */}
@ -18,6 +19,13 @@ const projects = [
detail: '오늘의 급식을 사진으로 공유하는 인스타그램 계정입니다. 매일 학교 급식을 자동으로 정리하여 제공합니다.',
tags: ['TypeScript', 'igramapi', '@napi-rs/canvas'],
},
{
name: 'NYL',
url: 'https://nyl.ny64.kr',
desc: '상위 호환',
detail: '전국의 중고등학교의 시간표와 급식을 쉽게 확인할 수 있는 앱을 유지보수하고 있습니다.',
tags: ['React Native', 'ElysiaJS']
}
];
export default function Projects() {

View file

@ -0,0 +1,21 @@
'use client';
import { useState, useRef, useEffect } from 'react';
import { useReadmeWaka } from '@/hooks/use-readme-waka';
export default function ReadmeWindow() {
const { wakaContent, isLoading, error } = useReadmeWaka();
if (!wakaContent) return null;
return (
<div className="font-mono rounded-b-lg">
<div className="py-4">
<h3 className="text-lg font-bold">WakaTime Stats</h3>
<pre className="text-sm overflow-auto max-h-96 whitespace-pre-wrap break-words">
{wakaContent.replace(/```(txt)?/g, '')}
</pre>
</div>
</div>
);
}

View file

@ -0,0 +1,40 @@
import { useState, useEffect } from 'react';
export function useReadmeWaka() {
const [wakaContent, setWakaContent] = useState<string>('');
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
useEffect(() => {
const fetchWakaContent = async () => {
try {
const response = await fetch(
'https://raw.githubusercontent.com/imnyang/imnyang/refs/heads/main/README.md'
);
if (!response.ok) throw new Error('Failed to fetch README');
const text = await response.text();
const startMarker = '<!--START_SECTION:waka-->';
const endMarker = '<!--END_SECTION:waka-->';
const startIdx = text.indexOf(startMarker);
const endIdx = text.indexOf(endMarker);
if (startIdx !== -1 && endIdx !== -1) {
const content = text.slice(startIdx + startMarker.length, endIdx).trim();
setWakaContent(content);
} else {
setError('Could not find waka section');
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
} finally {
setIsLoading(false);
}
};
fetchWakaContent();
}, []);
return { wakaContent, isLoading, error };
}