parent
c015b7d49f
commit
dd23a3c76e
9 changed files with 257 additions and 108 deletions
BIN
public/about/vrchat.webp
Normal file
BIN
public/about/vrchat.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.7 MiB |
|
|
@ -1,12 +1,11 @@
|
|||
---
|
||||
import AboutRow from "@/components/AboutRow.astro";
|
||||
import DDay from "./DDay.astro";
|
||||
---
|
||||
|
||||
<section
|
||||
id="about"
|
||||
data-node-id="4:104"
|
||||
class="min-h-full snap-start snap-always bg-background text-foreground"
|
||||
class="min-h-[calc(100svh-64px)] snap-start bg-background text-foreground"
|
||||
aria-labelledby="about-title"
|
||||
>
|
||||
<div class="grid min-h-full grid-cols-1 lg:grid-cols-2">
|
||||
|
|
@ -14,10 +13,12 @@ import DDay from "./DDay.astro";
|
|||
class="relative z-10 flex flex-col justify-center px-6 py-16 sm:px-10 lg:px-[clamp(52px,5.208vw,100px)] lg:py-[clamp(58px,9.26vh,100px)]"
|
||||
>
|
||||
<h2 id="about-title" class="max-w-xl text-3xl font-semibold tracking-tight sm:text-4xl">
|
||||
👋
|
||||
About<span class="text-foreground/35" aria-hidden="true">.</span>
|
||||
</h2>
|
||||
<p class="mt-6 max-w-lg text-base leading-8 text-foreground">
|
||||
안녕하세요. 정보보안과 소프트웨어 엔지니어링을 공부하는 암냥입니다.
|
||||
새로운 기술을 배우고, 머릿속의 아이디어를 실제로 작동하는 것으로
|
||||
만드는 일을 좋아해요.
|
||||
</p>
|
||||
<section class="mt-16" aria-labelledby="experience-title">
|
||||
<div class="mb-5 flex items-baseline justify-between gap-4">
|
||||
|
|
@ -70,11 +71,11 @@ import DDay from "./DDay.astro";
|
|||
</AboutRow>
|
||||
</div>
|
||||
</section>
|
||||
<div class="mt-6 flex flex-row items-center text-sm text-foreground/60">
|
||||
<div class="mt-8 flex flex-row items-center text-sm text-foreground/60">
|
||||
<a
|
||||
href="#experience"
|
||||
class="flex flex-row items-center underline underline-offset-4"
|
||||
><span>등등...</span>
|
||||
class="flex flex-row items-center underline underline-offset-4 hover:text-foreground"
|
||||
><span>전체 타임라인 보기</span>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="h-4 text-foreground/40"
|
||||
|
|
@ -89,21 +90,26 @@ import DDay from "./DDay.astro";
|
|||
<path d="M7 7h10v10"></path>
|
||||
</svg></a
|
||||
>
|
||||
<p class="ml-1"> 많은 여러가지의 활동을 해왔어요.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="relative order-first h-[min(150vw,720px)] min-h-[600px] overflow-hidden lg:sticky lg:top-0 lg:order-none lg:h-[calc(100svh-64px)] lg:min-h-0 lg:self-start"
|
||||
class="relative order-last h-[min(100vw,480px)] min-h-0 overflow-hidden bg-muted lg:sticky lg:top-0 lg:order-none lg:h-[calc(100svh-64px)] lg:min-h-0 lg:self-start"
|
||||
aria-label="활동 사진"
|
||||
>
|
||||
<!-- <img
|
||||
class="absolute inset-0 h-full w-full select-none pointer-events-none object-contain"
|
||||
src="/side.avif"
|
||||
alt="활동 사진 콜라주"
|
||||
width="3840"
|
||||
height="4320"
|
||||
/> -->
|
||||
<img
|
||||
class="absolute inset-0 h-full w-full select-none object-cover object-center"
|
||||
src="/about/vrchat.webp"
|
||||
alt="VRChat에서 촬영한 캐릭터 스크린샷"
|
||||
width="1920"
|
||||
height="1080"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
/>
|
||||
<div
|
||||
class="pointer-events-none absolute inset-0 bg-gradient-to-t from-background/35 via-transparent to-black/5"
|
||||
aria-hidden="true"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -105,14 +105,14 @@ const groups = [
|
|||
{
|
||||
name: "Project Sekai KR",
|
||||
value: "7354623251148069648",
|
||||
href: "#",
|
||||
copy: true,
|
||||
icon: "project-sekai",
|
||||
tone: "blue",
|
||||
},
|
||||
{
|
||||
name: "Project Sekai JP",
|
||||
value: "729310364018688008",
|
||||
href: "#",
|
||||
copy: true,
|
||||
icon: "project-sekai",
|
||||
tone: "blue",
|
||||
},
|
||||
|
|
@ -152,7 +152,7 @@ const paths: Record<string, string> = {
|
|||
};
|
||||
---
|
||||
|
||||
<section id="contact" class="contact-section" aria-labelledby="contact-title">
|
||||
<section id="contact" class="contact-section snap-start" aria-labelledby="contact-title">
|
||||
<div class="contact-content">
|
||||
<header class="contact-heading">
|
||||
<h2 id="contact-title">Contact<span aria-hidden="true">.</span></h2>
|
||||
|
|
@ -174,6 +174,90 @@ const paths: Record<string, string> = {
|
|||
<ul class="contact-list">
|
||||
{group.items.map((item) => (
|
||||
<li class="contact-row">
|
||||
{"copy" in item ? (
|
||||
<button
|
||||
class="contact-link contact-copy-link"
|
||||
type="button"
|
||||
data-contact-copy={item.value}
|
||||
aria-label={`${item.name} ${item.value} 복사`}
|
||||
>
|
||||
<span
|
||||
class={`contact-icon tone-${item.tone}`}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{imageIcons[item.icon] ? (
|
||||
<img
|
||||
src={
|
||||
imageIcons[item.icon]
|
||||
.src
|
||||
}
|
||||
width="22"
|
||||
height="22"
|
||||
alt=""
|
||||
class:list={{
|
||||
"mono-icon":
|
||||
imageIcons[
|
||||
item.icon
|
||||
].monochrome,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<svg
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.7"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path
|
||||
d={paths[item.icon]}
|
||||
/>
|
||||
</svg>
|
||||
)}
|
||||
</span>
|
||||
<span class="contact-name">
|
||||
{item.name}
|
||||
</span>
|
||||
<span class="contact-value">
|
||||
{item.value}
|
||||
</span>
|
||||
<span
|
||||
class="contact-copy-indicator"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<svg
|
||||
class="copy-symbol"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<rect
|
||||
x="8"
|
||||
y="8"
|
||||
width="12"
|
||||
height="12"
|
||||
rx="2"
|
||||
/>
|
||||
<path d="M16 8V4a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h4" />
|
||||
</svg>
|
||||
<svg
|
||||
class="copied-symbol"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.8"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<path d="m5 12 4 4L19 6" />
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
) : (
|
||||
<a
|
||||
class="contact-link"
|
||||
href={item.href}
|
||||
|
|
@ -244,6 +328,7 @@ const paths: Record<string, string> = {
|
|||
</svg>
|
||||
)}
|
||||
</a>
|
||||
)}
|
||||
{"email" in item && (
|
||||
<button
|
||||
class="contact-copy"
|
||||
|
|
@ -309,9 +394,7 @@ const paths: Record<string, string> = {
|
|||
<style>
|
||||
.contact-section {
|
||||
--contact-top-spacing: clamp(48px, 7vw, 88px);
|
||||
min-height: 100%;
|
||||
/* A viewport-spanning snap area permits free scrolling through the links. */
|
||||
scroll-snap-align: start;
|
||||
min-height: calc(100svh - 64px);
|
||||
padding: var(--contact-top-spacing) clamp(24px, 5.208vw, 100px) 32px;
|
||||
}
|
||||
.contact-content {
|
||||
|
|
@ -320,10 +403,6 @@ const paths: Record<string, string> = {
|
|||
}
|
||||
.contact-heading {
|
||||
display: flow-root;
|
||||
/* Keep the snap target smaller than the viewport so entry stops at the title. */
|
||||
scroll-snap-align: start;
|
||||
scroll-snap-stop: always;
|
||||
scroll-margin-top: var(--contact-top-spacing);
|
||||
padding-bottom: 32px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
|
@ -393,6 +472,15 @@ const paths: Record<string, string> = {
|
|||
border-radius: 8px;
|
||||
text-decoration: none;
|
||||
}
|
||||
.contact-copy-link {
|
||||
width: 100%;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
font: inherit;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
}
|
||||
.contact-row:has(.contact-copy) .contact-link {
|
||||
grid-template-columns: 22px auto minmax(0, 1fr);
|
||||
padding-right: 0;
|
||||
|
|
@ -463,6 +551,22 @@ const paths: Record<string, string> = {
|
|||
color: var(--muted-foreground);
|
||||
cursor: pointer;
|
||||
}
|
||||
.contact-copy-indicator {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: var(--muted-foreground);
|
||||
transition: color 160ms;
|
||||
}
|
||||
.contact-copy-link:hover .contact-copy-indicator {
|
||||
color: var(--foreground);
|
||||
}
|
||||
.contact-copy-indicator svg {
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
grid-area: 1 / 1;
|
||||
}
|
||||
.contact-copy:hover {
|
||||
color: var(--foreground);
|
||||
background: color-mix(in srgb, var(--foreground) 7%, transparent);
|
||||
|
|
@ -472,10 +576,12 @@ const paths: Record<string, string> = {
|
|||
height: 17px;
|
||||
}
|
||||
.copied-symbol,
|
||||
.contact-copy[data-copied] .copy-symbol {
|
||||
.contact-copy[data-copied] .copy-symbol,
|
||||
.contact-copy-link[data-copied] .copy-symbol {
|
||||
display: none;
|
||||
}
|
||||
.contact-copy[data-copied] .copied-symbol {
|
||||
.contact-copy[data-copied] .copied-symbol,
|
||||
.contact-copy-link[data-copied] .copied-symbol {
|
||||
display: block;
|
||||
color: #46916c;
|
||||
}
|
||||
|
|
@ -540,6 +646,10 @@ const paths: Record<string, string> = {
|
|||
grid-column: 3;
|
||||
grid-row: 1 / 3;
|
||||
}
|
||||
.contact-copy-link .contact-copy-indicator {
|
||||
grid-column: 3;
|
||||
grid-row: 1 / 3;
|
||||
}
|
||||
}
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.contact-row,
|
||||
|
|
@ -556,27 +666,30 @@ const paths: Record<string, string> = {
|
|||
const contactStatus =
|
||||
document.querySelector<HTMLElement>(".contact-status");
|
||||
document
|
||||
.querySelectorAll<HTMLButtonElement>("[data-contact-email]")
|
||||
.querySelectorAll<HTMLButtonElement>(
|
||||
"[data-contact-email], [data-contact-copy]",
|
||||
)
|
||||
.forEach((button) => {
|
||||
let resetTimer: ReturnType<typeof setTimeout>;
|
||||
button.addEventListener("click", async () => {
|
||||
const email = button.dataset.contactEmail;
|
||||
if (!email) return;
|
||||
const value =
|
||||
button.dataset.contactEmail ?? button.dataset.contactCopy;
|
||||
if (!value) return;
|
||||
try {
|
||||
await navigator.clipboard.writeText(email);
|
||||
await navigator.clipboard.writeText(value);
|
||||
clearTimeout(resetTimer);
|
||||
button.dataset.copied = "true";
|
||||
button.setAttribute("aria-label", `${email} 복사 완료`);
|
||||
button.setAttribute("aria-label", `${value} 복사 완료`);
|
||||
if (contactStatus)
|
||||
contactStatus.textContent = `${email} 주소를 복사했어요.`;
|
||||
contactStatus.textContent = `${value}를 복사했어요.`;
|
||||
resetTimer = setTimeout(() => {
|
||||
delete button.dataset.copied;
|
||||
button.setAttribute("aria-label", `${email} 복사`);
|
||||
button.setAttribute("aria-label", `${value} 복사`);
|
||||
}, 2000);
|
||||
} catch {
|
||||
if (contactStatus)
|
||||
contactStatus.textContent =
|
||||
"복사하지 못했어요. 이메일 주소를 직접 선택해 복사해주세요.";
|
||||
"복사하지 못했어요. 값을 직접 선택해 복사해주세요.";
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ const roles = ["Broadcast Manager", "ARG Creator", "Developer"];
|
|||
|
||||
<section
|
||||
id="projects"
|
||||
class="min-h-full snap-start bg-background text-foreground"
|
||||
class="min-h-[calc(100svh-64px)] snap-start bg-background text-foreground"
|
||||
aria-labelledby="projects-title"
|
||||
>
|
||||
<div class="grid min-h-full grid-cols-1 lg:grid-cols-2">
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ const daysUntilYearEnd = Math.ceil(
|
|||
<section
|
||||
id="experience"
|
||||
data-node-id="4:104"
|
||||
class="min-h-full snap-start snap-always bg-background text-foreground"
|
||||
class="min-h-[calc(100svh-64px)] snap-start bg-background text-foreground"
|
||||
aria-labelledby="experience-title"
|
||||
>
|
||||
<div class="grid min-h-full grid-cols-1 lg:grid-cols-2">
|
||||
|
|
@ -130,7 +130,7 @@ const daysUntilYearEnd = Math.ceil(
|
|||
</div>
|
||||
|
||||
<div
|
||||
class="relative order-first h-[min(150vw,720px)] min-h-[600px] overflow-hidden lg:sticky lg:top-0 lg:order-none lg:h-[calc(100svh-64px)] lg:min-h-0 lg:self-start"
|
||||
class="relative order-last h-[min(100vw,480px)] min-h-0 overflow-hidden lg:sticky lg:top-0 lg:order-none lg:h-[calc(100svh-64px)] lg:min-h-0 lg:self-start"
|
||||
aria-label="활동 사진"
|
||||
>
|
||||
<img
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ const navigationItems = [
|
|||
{ label: "Home", href: "#home" },
|
||||
{ label: "About", href: "#about" },
|
||||
{ label: "Projects", href: "#projects" },
|
||||
{ label: "Timeline", href: "#timeline" },
|
||||
{ label: "Timeline", href: "#experience" },
|
||||
{ label: "Contact", href: "#contact" },
|
||||
] as const;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
id="home"
|
||||
data-node-id="1:3"
|
||||
data-name="imnyang"
|
||||
class="flex h-full min-h-0 snap-start snap-always flex-col overflow-hidden"
|
||||
class="flex h-[calc(100svh-64px)] min-h-0 snap-start flex-col overflow-hidden"
|
||||
>
|
||||
<section
|
||||
class="flex h-[250px] min-h-[250px] min-w-0 flex-none basis-[250px] items-center overflow-clip px-[clamp(26px,2.5vw,48px)] max-md:px-6"
|
||||
|
|
|
|||
|
|
@ -80,3 +80,37 @@
|
|||
--mono-filter: invert(1);
|
||||
}
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: smooth;
|
||||
scroll-padding-top: 64px;
|
||||
}
|
||||
|
||||
html::-webkit-scrollbar {
|
||||
width: 19px;
|
||||
height: 11px;
|
||||
}
|
||||
|
||||
html::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
html::-webkit-scrollbar-thumb {
|
||||
background: var(--scrollbar);
|
||||
border: 5px solid var(--background);
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
html::-webkit-scrollbar-thumb:hover {
|
||||
background: var(--scrollbar-hover);
|
||||
}
|
||||
|
||||
html {
|
||||
scrollbar-color: var(--scrollbar) var(--background);
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
html {
|
||||
scroll-behavior: auto;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,18 +56,14 @@ import "@/globals.css";
|
|||
target.offsetTop - (scrollContainer?.offsetTop ?? 0);
|
||||
|
||||
if (scrollContainer && window.location.hash) {
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => {
|
||||
const initialTarget = document.querySelector<HTMLElement>(
|
||||
window.location.hash,
|
||||
);
|
||||
|
||||
if (initialTarget) {
|
||||
scrollContainer.scrollTop =
|
||||
getTargetScrollTop(initialTarget);
|
||||
scrollContainer.scrollTop = getTargetScrollTop(initialTarget);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
inPageLinks.forEach((link) => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue