-
- {project.projectName}
-
-
{
+ if (!items?.length) {
+ return null;
+ }
+ return (
+
+
+
Quick Summary
+
+ {items.map((item, index) => (
+
+
{item.icon}
+
+
{item.title}
+
{item.desc}
+
+
+ ))}
+
+
+
+ );
+ };
+
+ const SecuritySection = () => (
+ }
+ title="JWT 이중 쿠키 + Rotation"
+ bullets={[
+
+
ONE-LINER
+
+
+ Refresh Token 재사용을 원천 차단하는 Rotation 기반 인증 설계
+
+
+
,
+
+
HOW
+
+ Access(15m) / Refresh(14d) 분리 + Refresh DB 저장 및 재발급 시
+ 즉시 폐기
+
+
,
+
+
RESULT
+
+ HttpOnly 쿠키 + Role 기반 보호로 세션 안정성 강화
+
+
+ ]}
+ />
+ }
+ proof={proofImages.jwt}
+ caption="Rotation 로직과 Refresh 저장 구조 증명"
+ />
+ );
+
+ const AiSection = () => (
+
}
+ title="상태 기반 히스토리 저장 AI"
+ chips={["analyze", "plan", "chat"]}
+ bullets={[
+
+
ONE-LINER
+
+
+ 상태 기반 AI 코치로 반복 사용 흐름 유지
+
+
+
,
+
+
HOW
+
+ analyze -> plan -> chat 단계 분리, 대화 히스토리 DB 저장
+
+
,
+
+
RESULT
+
+ 사용자 맥락 유지로 루틴 수정/재생성이 가능한 제품 형태
+
+
+ ]}
+ />
+ }
+ proof={proofImages.ai}
+ caption="AI 엔드포인트 분리 + 히스토리 저장 증명"
+ />
+ );
+
+ const DbSection = () => (
+
}
+ title="도메인 분리 ERD"
+ bullets={[
+
+
ONE-LINER
+
+
+ 사용자/커뮤니티/AI 분리로 확장 가능한 스키마
+
+
+
,
+
+
HOW
+
+ FK 무결성 + 조회 중심 인덱스/페이지네이션 기준 설계
+
+
,
+
+
RESULT
+
+ users, brag_post, ai_chat_messages, refresh_tokens 중심 운영
+
+
+ ]}
+ />
+ }
+ proof={proofImages.erd}
+ caption="핵심 테이블 분리와 관계 설계 증명"
+ />
+ );
+
+ const AwsSection = () => (
+
}
+ title="AWS 운영 이슈 해결"
+ bullets={[
+
+
ONE-LINER
+
+
+ 운영 이슈를 재현 -> 해결 -> 검증까지 수행
+
+
+
,
+
+
HOW
+
+ CloudFront/S3 HTTPS 통일 + CORS allowlist/credentials 유지
+
+
,
+
+
RESULT
+
+ 배포 안정성, 보안, 세션 유지 이슈를 운영 관점에서 안정화
+
+
+ ]}
+ issueLines={[
+ {label: "Issue", text: "ACM us-east-1 필요 + Mixed Content 발생"},
+ {label: "Fix", text: "CloudFront Invalidation + HTTPS 통일"},
+ {label: "Result", text: "CORS credentials 유지, 배포 정상화"}
+ ]}
+ variant="ops"
+ />
+ }
+ proof={proofImages.aws}
+ caption="ACM/CloudFront/CORS 설정 증명"
+ />
+ );
+
+ const muscleupQuickSummaryItems = [
+ {
+ icon: "🔐",
+ title: "JWT Rotation",
+ desc: "탈취 Refresh 재사용 차단"
+ },
+ {
+ icon: "🤖",
+ title: "상태 기반 AI 코칭",
+ desc: "히스토리 저장으로 맥락 유지"
+ },
+ {
+ icon: "☁️",
+ title: "AWS 실서비스 운영",
+ desc: "HTTPS/CORS/ACM 이슈 해결"
+ },
+ {
+ icon: "🧩",
+ title: "도메인 분리 설계",
+ desc: "User / Community / AI 확장 구조"
+ },
+ {
+ icon: "🛠️",
+ title: "운영 안정성 확보",
+ desc: "배포·인증 이슈 재현-해결-검증"
+ }
+ ];
+
+ const buildQuickSummaryItems = (details, project) => {
+ if (!details) {
+ return [];
+ }
+ if (details.quickSummary?.length) {
+ return details.quickSummary;
+ }
+ const items = [];
+ const addItem = (icon, title, desc) => {
+ if (title && desc) {
+ items.push({icon, title, desc});
+ }
+ };
+ if (details.highlights?.length) {
+ addItem("KEY", "핵심 설계", details.highlights[0]);
+ }
+ if (details.coreFeatures?.length) {
+ addItem("FEAT", "핵심 기능", details.coreFeatures[0]);
+ }
+ if (details.authSecurity?.length) {
+ addItem("SEC", "보안/인증", details.authSecurity[0]);
+ }
+ if (details.deployment?.length) {
+ addItem("OPS", "운영 이슈", details.deployment[0]);
+ }
+ if (details.role) {
+ addItem("ROLE", "담당 범위", details.role);
+ }
+ if (!items.length && project?.projectDesc) {
+ addItem("INFO", "프로젝트 요약", project.projectDesc);
+ }
+ return items.slice(0, 5);
+ };
+
+ const buildIntroLines = (details, project) => {
+ const fallbackSummary = details?.summary || project?.projectDesc || "";
+ const problem = details?.problemSolution?.problem?.[0] || fallbackSummary;
+ const solution =
+ details?.problemSolution?.solution ||
+ details?.highlights?.[0] ||
+ details?.coreFeatures?.[0] ||
+ "";
+ const outcome =
+ details?.problemSolution?.outcome ||
+ details?.role ||
+ details?.deployment?.[0] ||
+ "";
+ return {problem, solution, outcome};
+ };
+
+ const buildCoreDesignItems = details => {
+ if (!details) {
+ return [];
+ }
+ if (details.coreDesign?.length) {
+ return details.coreDesign;
+ }
+ const items = [];
+ const stackSnippet = details.stack
+ ? details.stack.split(",").slice(0, 4).join(" · ")
+ : "";
+ const summary = details.summary || "";
+ const source = [
+ ...(details.highlights || []),
+ ...(details.coreFeatures || []),
+ ...(details.authSecurity || []),
+ ...(details.architecture || [])
+ ];
+ source.forEach(entry => {
+ if (!entry) {
+ return;
+ }
+ items.push({
+ title: entry,
+ oneLiner: entry,
+ how: stackSnippet ? `How: ${stackSnippet}` : "",
+ result: summary ? `Result: ${summary}` : ""
+ });
+ });
+ return items.slice(0, 4);
+ };
+
+ const buildOpsItem = details => {
+ if (details?.ops) {
+ return {
+ oneLiner: details.ops.oneLiner,
+ how: details.ops.how,
+ result: details.ops.result
+ };
+ }
+ if (!details?.deployment?.length) {
+ return null;
+ }
+ const [first, second, third] = details.deployment;
+ return {
+ oneLiner: first,
+ how: second,
+ result: third
+ };
+ };
+
+ const ProjectIntroSection = ({project}) => {
+ if (!project) {
+ return null;
+ }
+ const details = project.details || {};
+ const intro = details.intro || {};
+ const {problem, solution, outcome} = buildIntroLines(details, project);
+ const introProblem = intro.problem || problem;
+ const introSolution = intro.solution || solution;
+ const introOutcome = intro.outcome || outcome;
+ const headline = intro.headline || "What is This Project?";
+ const highlight = intro.highlight || details.summary;
+ const caption = intro.caption || "대표 화면/결과물";
+ const images =
+ intro.images && intro.images.length
+ ? intro.images
+ : [
+ details.overview?.image ||
+ project.image ||
+ require("../../assets/images/saayaHealthLogo.webp")
+ ];
+ return (
+
+
+
+
{headline}
+ {highlight ? (
+
+ {highlight}
+
+ ) : null}
+
+ {introProblem ? (
+
+ P
+
+ Problem {introProblem}
+
+
+ ) : null}
+ {introSolution ? (
+
+ S
+
+ Solution {introSolution}
+
+
+ ) : null}
+ {introOutcome ? (
+
+ O
+
+ Outcome {introOutcome}
+
+
+ ) : null}
+
+
+
+
+ {images.map((img, index) => (
+
+ ))}
+
+
{caption}
+
+
+
+ );
+ };
+ const normalizeCategory = label => {
+ const key = label.toLowerCase().trim();
+ if (key.includes("front")) return "Frontend";
+ if (key.includes("back")) return "Backend";
+ if (key.includes("db") || key.includes("data")) return "Database";
+ if (key.includes("infra")) return "Infrastructure";
+ if (key.includes("ai")) return "AI";
+ if (key.includes("analytics")) return "Analytics";
+ if (key.includes("deploy")) return "Deploy";
+ if (key.includes("tooling") || key.includes("tools")) return "Tooling";
+ if (key.includes("content")) return "Content Ops";
+ if (key.includes("promotion")) return "Promotion";
+ return null;
+ };
+
+ const techCategoryOrder = [
+ "Frontend",
+ "Backend",
+ "Database",
+ "Infrastructure",
+ "AI",
+ "Analytics",
+ "Deploy",
+ "Tooling",
+ "Content Ops",
+ "Promotion"
+ ];
+
+ const buildTechCategories = (techStack, fallbackStack) => {
+ const stackList = Array.isArray(techStack) ? techStack : [];
+ if (!stackList.length) {
+ if (fallbackStack) {
+ return [
+ {
+ category: "Frontend",
+ items: [fallbackStack]
+ }
+ ];
+ }
+ return [];
+ }
+ const buckets = techCategoryOrder.reduce((acc, key) => {
+ acc[key] = [];
+ return acc;
+ }, {});
+ stackList.forEach(item => {
+ const parts = item.split(":");
+ const label = parts[0] ? parts[0].trim() : "";
+ const category = normalizeCategory(label);
+ const list = parts[1] ? parts[1].trim() : item.trim();
+ if (category && list) {
+ buckets[category].push(list);
+ }
+ });
+ return techCategoryOrder
+ .map(category => ({
+ category,
+ items: buckets[category]
+ }))
+ .filter(group => group.items.length);
+ };
+
+ const techCategoryIcons = {
+ Frontend: "FE",
+ Backend: "BE",
+ Database: "DB",
+ Infrastructure: "INF",
+ AI: "AI",
+ Analytics: "AN",
+ Deploy: "DEP",
+ Tooling: "TL",
+ "Content Ops": "OPS",
+ Promotion: "PR"
+ };
+
+ const getProjectBannerClass = project => {
+ const name = (project?.projectName || "").toLowerCase();
+ const desc = (project?.projectDesc || "").toLowerCase();
+ const value = `${name} ${desc}`;
+ const hasOps =
+ value.includes("aws") ||
+ value.includes("deploy") ||
+ value.includes("https") ||
+ value.includes("cors") ||
+ value.includes("infra") ||
+ value.includes("acm") ||
+ value.includes("cloudfront");
+ if (value.includes("muscleup") || value.includes("득근득근")) {
+ return "project-banner-web-ai";
+ }
+ if (hasOps && (value.includes("web") || value.includes("react"))) {
+ return "project-banner-web-ops";
+ }
+ if (value.includes("tserof") || value.includes("game")) {
+ return "project-banner-game";
+ }
+ if (
+ value.includes("monster") ||
+ value.includes("ar") ||
+ value.includes("xr")
+ ) {
+ return "project-banner-ar";
+ }
+ if (value.includes("ajou campus") || value.includes("foodmap")) {
+ return "project-banner-web";
+ }
+ if (value.includes("club") || value.includes("ajouchong")) {
+ return "project-banner-web";
+ }
+ if (value.includes("other side") || value.includes("vr")) {
+ return "project-banner-game";
+ }
+ if (hasOps) {
+ return "project-banner-ops";
+ }
+ return "project-banner-web";
+ };
+
+ const buildGenericBullets = (item, keyPrefix) => {
+ const groups = [];
+ if (item.oneLiner) {
+ groups.push(
+
+
ONE-LINER
+
+ {item.oneLiner}
+
+
+ );
+ }
+ if (item.how) {
+ groups.push(
+
+ );
+ }
+ if (item.result) {
+ groups.push(
+
+
RESULT
+
{item.result}
+
+ );
+ }
+ return groups;
+ };
+
+ const quickSummaryItems = isMuscleUp
+ ? muscleupQuickSummaryItems
+ : buildQuickSummaryItems(selectedProject?.details, selectedProject);
+ const coreDesignItems = !isMuscleUp
+ ? buildCoreDesignItems(selectedProject?.details)
+ : [];
+ const opsItem = !isMuscleUp ? buildOpsItem(selectedProject?.details) : null;
+ const genericProofImage =
+ selectedProject?.details?.overview?.image ||
+ selectedProject?.image ||
+ require("../../assets/images/saayaHealthLogo.webp");
+ const techCategories = buildTechCategories(
+ selectedProject?.details?.overview?.techStack,
+ selectedProject?.details?.stack
+ );
+ const linkItems =
+ selectedProject?.details?.overview?.links ||
+ selectedProject?.details?.links ||
+ selectedProject?.footerLink ||
+ [];
+
+ const buildOverviewTechStack = techStack => {
+ const items = Array.isArray(techStack) ? techStack : [];
+ return items
+ .map(entry => {
+ const [rawCategory, rawItems] = entry.split(":");
+ if (!rawItems) {
+ return {category: "", items: [entry.trim()]};
+ }
+ const category = rawCategory.trim();
+ const stackItems = rawItems
+ .split(",")
+ .map(value => value.trim())
+ .filter(Boolean);
+ return {category, items: stackItems};
+ })
+ .filter(item => item.items.length);
+ };
+ const overviewTechStack = buildOverviewTechStack(
+ selectedProject?.details?.overview?.techStack
+ );
+ return (
+ <>
+
+
+
+
+
{bigProjects.title}
+
+
+
+ Blue/Teal: Web Service · Full-Stack 중심의 실서비스 개발
+
+
+
+ Indigo/Violet: Data · AI 기반 로직 설계 및 분석 중심 프로젝트
+
+
+
+ Emerald/Cyan: Game · Unity 기반 인터랙티브 콘텐츠 개발
+
+
+
+ Amber/Orange: AR · XR 기반 실감형 사용자 경험 구현
+
+
+
+ Gray: Ops · 배포, 운영, 인증, 인프라 안정화 중심 프로젝트
+
+
+
+
+ {bigProjects.subtitle}
+
+
+
+ {bigProjects.projects.map((project, i) => {
+ return (
+
setSelectedProject(project)}
+ onKeyDown={event => {
+ if (event.key === "Enter") {
+ setSelectedProject(project);
}
+ }}
+ >
+
- {project.projectDesc}
-
- {project.footerLink ? (
-
- {project.footerLink.map((link, i) => {
- return (
-
openUrlInNewTab(link.url)}
- >
- {link.name}
-
- );
- })}
+
+
+
+ {project.projectName}
+
+ {project.image ? (
+
+ ) : null}
+
+
+
+
+ {project.projectName}
+
+
+ {project.recommendation || project.projectDesc}
+
+ {project.tags?.length ? (
+
+ {project.tags.map((tag, tagIndex) => (
+
+ {tag}
+
+ ))}
+
+ ) : null}
+
+
+ {
+ event.stopPropagation();
+ setSelectedProject(project);
+ }}
+ >
+ 자세히 보기
+
+
+
+
+ );
+ })}
+
+
+
+
+
+ {selectedProject && (
+
setSelectedProject(null)}
+ >
+
event.stopPropagation()}
+ >
+
setSelectedProject(null)}
+ aria-label="Close"
+ >
+ X
+
+ {lightbox && typeof document !== "undefined"
+ ? createPortal(
+
setLightbox(null)}
+ onPrev={() =>
+ setLightbox(prev => ({
+ ...prev,
+ index:
+ (prev.index - 1 + prev.items.length) %
+ prev.items.length
+ }))
+ }
+ onNext={() =>
+ setLightbox(prev => ({
+ ...prev,
+ index: (prev.index + 1) % prev.items.length
+ }))
+ }
+ />,
+ document.body
+ )
+ : null}
+ {selectedProject.details?.overview ? (
+
+
+
+ {selectedProject.details.overview.title}
+
+
+ {selectedProject.details.overview.subtitle}
+
+
+ <>
+
+ {selectedProject.details.overview.image ? (
+
) : null}
+ {selectedProject.details.overview.caption && (
+
+ {selectedProject.details.overview.caption}
+
+ )}
+
+
+
+ {selectedProject.details.overview.role && (
+
+
ROLE
+
+ {selectedProject.details.overview.role}
+
+
+ )}
+ {selectedProject.details.overview.period && (
+
+
+ PERIOD
+
+
+ {selectedProject.details.overview.period}
+
+
+ )}
+ {selectedProject.details.overview.coreValue && (
+
+
+ CORE VALUE
+
+
+ {selectedProject.details.overview.coreValue}
+
+
+ )}
+
+ {overviewTechStack.length ? (
+
+
+
+ TECH STACK
+
+
+ {overviewTechStack.map((group, i) => (
+
+ {group.category ? (
+
+ {group.category}
+
+ ) : null}
+
+ {group.items.join(" · ")}
+
+
+ ))}
+
+
+
+ ) : null}
+
+ {selectedProject.details.overview.links?.length ? (
+
+
링크
+
+ {selectedProject.details.overview.links.map(
+ (link, i) => (
+
+
+ {link.name}
+
+
+ )
+ )}
+
+
+ ) : null}
+ >
+
+ ) : (
+ <>
+
+ {selectedProject.image ? (
+
+ ) : null}
+
+
+ {selectedProject.projectName}
+
+
+ {selectedProject.projectDesc}
+
- );
- })}
+ {selectedProject.details?.stack && (
+
+ Stack
+
+ {selectedProject.details.stack}
+
+
+ )}
+ >
+ )}
+ {quickSummaryItems.length ? (
+
+ ) : null}
+ {isMuscleUp ? (
+
+ ) : (
+
+ )}
+ {isMuscleUp ? (
+ <>
+
+
+
+
+
+
+ >
+ ) : (
+ <>
+ {coreDesignItems.length ? (
+ <>
+
+ {coreDesignItems.map((item, index) => (
+
+ }
+ title={item.title}
+ bullets={buildGenericBullets(item, `core-${index}`)}
+ />
+ }
+ proof={{
+ src: item.proofImage || genericProofImage,
+ alt:
+ item.proofAlt ||
+ `${selectedProject.projectName} proof`
+ }}
+ caption={item.proofCaption || `${item.title} 증명`}
+ />
+ ))}
+ >
+ ) : null}
+ {opsItem ? (
+ <>
+
+
+ }
+ title="운영 이슈 대응"
+ bullets={buildGenericBullets(opsItem, "ops")}
+ variant="ops"
+ />
+ }
+ proof={{
+ src: genericProofImage,
+ alt: `${selectedProject.projectName} ops proof`
+ }}
+ caption="운영 이슈 증명"
+ />
+ >
+ ) : null}
+ >
+ )}
+
+
+ Tech + Links (Accordion)
+
+
+ {techCategories.length ? (
+
+ {techCategories.map(group => (
+
+
+
+ {techCategoryIcons[group.category]}
+
+
+ {group.category}
+
+
+
+ {group.items.join(" · ")}
+
+
+ ))}
+
+ ) : (
+
+ 기술 스택 정보를 업데이트해 주세요.
+
+ )}
+
+
+ {linkItems.length ? (
+
+ ) : (
+
+ 링크 정보를 업데이트해 주세요.
+
+ )}
+
+
-
-
+ )}
+ >
);
}
diff --git a/src/containers/StartupProjects/StartupProjects.scss b/src/containers/StartupProjects/StartupProjects.scss
index 1f6be1be3f..e834b02960 100644
--- a/src/containers/StartupProjects/StartupProjects.scss
+++ b/src/containers/StartupProjects/StartupProjects.scss
@@ -1,5 +1,27 @@
@import "../../_globalColor";
+@keyframes modalEnter {
+ from {
+ opacity: 0;
+ transform: translateY(12px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+@keyframes fadeUp {
+ from {
+ opacity: 0;
+ transform: translateY(10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
.card-image {
max-width: 100%;
height: auto;
@@ -43,6 +65,39 @@
.project-detail {
text-align: center;
}
+
+.project-card-content {
+ display: flex;
+ flex-direction: column;
+ gap: 0.6rem;
+}
+
+.project-recommendation {
+ margin-top: 0.25rem;
+}
+
+.project-card-tags {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.4rem;
+ justify-content: center;
+}
+
+.project-card-tag {
+ border: 1px solid rgba(255, 255, 255, 0.12);
+ color: $subTitle;
+ font-size: 12px;
+ padding: 0.2rem 0.6rem;
+ border-radius: 999px;
+ background: rgba(255, 255, 255, 0.04);
+}
+
+.project-card-actions {
+ margin-top: auto;
+ display: flex;
+ justify-content: center;
+ padding-top: 0.75rem;
+}
.project-card-light:hover {
box-shadow: $lightBoxShadowDark 0px 20px 30px -10px;
}
@@ -64,27 +119,1649 @@
gap: 1rem 1rem;
}
-.project-card-footer span.project-tag {
- background: $buttonColor;
- color: $lightBackground3;
- vertical-align: middle;
- align-items: center;
- border-radius: 4px;
+.project-detail-button {
+ margin-top: 0.75rem;
+ border: 1px solid $subTitle;
+ background: transparent;
+ color: $subTitle;
+ padding: 0.5rem 0.9rem;
+ border-radius: 999px;
+ cursor: pointer;
+ opacity: 0.45;
+ transition: opacity 0.2s ease;
+}
+
+.experience-card,
+.experience-card-dark {
+ transition: transform 0.22s ease, box-shadow 0.22s ease;
+}
+
+.experience-card:hover,
+.experience-card-dark:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 16px 30px rgba(0, 0, 0, 0.2);
+}
+
+.experience-card:hover .project-detail-button,
+.experience-card-dark:hover .project-detail-button {
+ opacity: 1;
+}
+
+.project-header-row {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 1.5rem;
+}
+
+.project-legend {
+ display: grid;
+ gap: 0.35rem;
+ margin: 0.35rem 0 0;
+ color: $subTitle;
+ font-size: 12px;
+ max-width: 520px;
+}
+
+.project-legend-item {
display: inline-flex;
- font-size: 0.75rem;
- height: 2em;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.project-legend-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 999px;
+ display: inline-block;
+}
+
+.project-legend-web {
+ background: rgba(48, 167, 196, 0.8);
+}
+
+.project-legend-ai {
+ background: rgba(118, 96, 214, 0.8);
+}
+
+.project-legend-game {
+ background: rgba(40, 190, 154, 0.8);
+}
+
+.project-legend-ar {
+ background: rgba(234, 154, 64, 0.8);
+}
+
+.project-legend-ops {
+ background: rgba(140, 150, 160, 0.8);
+}
+
+.project-modal-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.6);
+ display: flex;
justify-content: center;
- white-space: nowrap;
+ align-items: center;
+ padding: 1.5rem;
+ z-index: 999;
+}
+
+.project-modal {
+ background: $lightBackground1;
+ color: $textColor;
+ width: min(1600px, 100%);
+ max-height: 100vh;
+ overflow: auto;
+ border-radius: 16px;
+ padding: 2.25rem;
+ position: relative;
+ opacity: 0;
+ transform: translateY(12px) scale(0.98);
+ animation: modalEnter 0.5s ease-out forwards;
+}
+
+.project-modal-close {
+ position: absolute;
+ top: 0.75rem;
+ right: 1rem;
+ border: none;
+ background: transparent;
+ color: $subTitle;
+ font-size: 28px;
+ cursor: pointer;
+}
+
+.project-modal-header {
+ display: grid;
+ grid-template-columns: minmax(220px, 320px) 1fr;
+ gap: 1.5rem;
+ align-items: center;
+ margin-bottom: 1.5rem;
+}
+
+.project-modal-image {
+ width: 100%;
+ height: auto;
+ border-radius: 12px;
+}
+
+.project-modal-title {
+ font-size: 32px;
+ margin: 0 0 0.5rem 0;
+}
+
+.project-modal-subtitle {
+ margin: 0;
+}
+
+.project-modal-stack {
+ display: flex;
+ gap: 0.75rem;
+ flex-wrap: wrap;
+ align-items: center;
+ margin-bottom: 1rem;
+}
+
+.project-modal-label {
+ font-weight: 600;
+}
+
+.project-modal-section {
+ margin: 0 0 96px;
+ padding-top: 0;
+ border-top: none;
+}
+
+.project-modal-section-title {
+ font-size: 20px;
+ padding-bottom: 0.5rem;
+ border-bottom: 2px solid $lightBorder1;
+ margin-bottom: 0.75rem;
+}
+
+.project-modal-paragraph {
+ line-height: 1.7;
+}
+
+.project-modal-list {
+ padding-left: 1.2rem;
+}
+
+.project-modal-list-item {
+ margin-bottom: 0.5rem;
+}
+
+.project-modal-links {
+ display: flex;
+ gap: 0.75rem;
+ flex-wrap: wrap;
+}
+
+.project-modal-link {
+ padding: 0.5rem 0.9rem;
+ border-radius: 999px;
+ border: 1px solid $subTitle;
+ color: $subTitle;
+ text-decoration: none;
+}
+
+.project-overview {
+ margin-bottom: 2rem;
+ padding-bottom: 1.5rem;
+ border-bottom: 1px solid $lightBorder1;
+}
+
+.project-overview-header {
+ margin-bottom: 1rem;
+}
+
+.project-overview-title {
+ font-size: 28px;
+ margin: 0 0 0.4rem 0;
+}
+
+.project-overview-subtitle {
+ margin: 0 0 1.25rem 0;
+ font-size: 18px;
+ color: $subTitle;
+}
+
+.project-overview-media {
+ text-align: center;
+ margin-bottom: 1.5rem;
+}
+
+.project-overview-image {
+ max-width: 100%;
+ height: auto;
+ border-radius: 16px;
+ box-shadow: $lightBoxShadowDark 0px 10px 30px -15px;
+}
+
+.project-overview-caption {
+ margin-top: 0.5rem;
+ font-size: 14px;
+ color: $subTitle;
+}
+
+.project-overview-grid {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
+ gap: 1.75rem;
+ margin-bottom: 1.5rem;
+}
+
+.project-overview-col {
+ display: flex;
+ flex-direction: column;
+ gap: 1.1rem;
+}
+
+.project-overview-block {
+ background: rgba(255, 255, 255, 0.04);
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ border-radius: 12px;
+ padding: 1rem 1.2rem;
+}
+
+.project-overview-block-title {
+ font-size: 11px;
+ text-transform: uppercase;
+ letter-spacing: 0.14em;
+ margin: 0 0 0.5rem 0;
+ color: rgba(255, 255, 255, 0.55);
+}
+
+.project-overview-block-text {
+ margin: 0;
+ font-size: 15px;
+ line-height: 1.6;
+ color: $textColorDark;
+}
+
+.project-overview-core {
+ background: rgba(255, 255, 255, 0.06);
+ border-left: 3px solid rgba(118, 96, 214, 0.7);
+}
+
+.project-overview-tech-grid {
+ display: grid;
+ gap: 0.85rem;
+}
+
+.project-overview-tech-title {
+ font-size: 12px;
+ color: rgba(255, 255, 255, 0.6);
+ margin-bottom: 0.2rem;
+}
+
+.project-overview-tech-items {
+ font-size: 14px;
+ color: $textColorDark;
line-height: 1.5;
- margin: 0 0.5rem 0.5rem 0;
- padding: 0 0.75em;
+}
+
+.project-overview-list {
+ margin: 0;
+ padding-left: 1.2rem;
+}
+
+.project-overview-list-item {
+ margin-bottom: 0.4rem;
+}
+
+.project-overview-link {
+ color: $skillsColor;
+ text-decoration: underline;
+}
+
+.project-overview-links {
+ margin-top: 1rem;
+}
+
+.project-quick-summary {
+ margin-top: 2.5rem;
+ margin-bottom: 3rem;
+}
+
+.project-quick-summary .project-detail-card {
+ background: #151a24;
+ color: $textColorDark;
+}
+
+.project-quick-summary .project-detail-card-item {
+ color: $textColorDark;
+}
+
+.project-hero-proof {
+ margin: 2.5rem 0;
+ display: grid;
+ gap: 0.75rem;
+}
+
+.project-hero-image {
+ width: 100%;
+ aspect-ratio: 16 / 9;
+ border-radius: 16px;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ object-fit: cover;
+}
+
+.project-hero-caption {
+ font-size: 14px;
+ color: $subTitle;
+}
+
+.project-detail-card-grid {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 2rem;
+}
+
+.project-detail-card {
+ border-radius: 18px;
+ padding: 30px;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ background: #1a2030;
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
+}
+
+.project-detail-card-header {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ margin-bottom: 0.75rem;
+ flex-wrap: wrap;
+}
+
+.project-detail-card-title {
+ font-size: 19px;
+ font-weight: 700;
+ margin: 0 0 0.5rem 0;
+}
+
+.project-detail-card-list {
+ margin: 0;
+ padding-left: 1.2rem;
+}
+
+.project-detail-card-item {
+ margin-bottom: 0.6rem;
+ line-height: 1.7;
+ font-size: 14px;
+}
+
+.project-badge {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.35rem;
+ padding: 6px 10px;
+ border-radius: 999px;
+ font-size: 12px;
+ font-weight: 600;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ background: rgba(255, 255, 255, 0.08);
+ color: $textColorDark;
+}
+
+.project-badge-icon {
+ font-size: 0.85rem;
+}
+
+.project-badge-star {
+ background: rgba(255, 215, 0, 0.15);
+}
+
+.project-badge-fire {
+ background: rgba(255, 120, 60, 0.14);
+}
+
+.project-badge-check {
+ background: rgba(0, 255, 170, 0.12);
+}
+
+.project-proof {
+ margin-top: 1rem;
+ display: grid;
+ gap: 0.5rem;
+}
+
+.project-proof-label {
+ font-size: 12px;
+ font-weight: 600;
+ color: $textColorDark;
+ text-transform: uppercase;
+}
+
+.project-proof-thumb {
+ width: 100%;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ border-radius: 14px;
+ background: transparent;
+ padding: 0;
+ overflow: hidden;
cursor: pointer;
- transition: 0.2s ease-in;
+ position: relative;
}
-@media (max-width: 768px) {
- .project-subtitle {
- font-size: 16px;
- text-align: center;
- }
+.project-proof-thumb img {
+ width: 100%;
+ aspect-ratio: 16 / 9;
+ display: block;
+ object-fit: cover;
+ transition: filter 0.2s ease;
+ position: relative;
+ z-index: 0;
+}
+
+.project-proof-thumb:hover img {
+ filter: brightness(1.04);
+}
+
+.project-proof-thumb::before {
+ content: "🔍";
+ position: absolute;
+ inset: 0;
+ display: grid;
+ place-items: center;
+ font-size: 100px;
+ color: rgba(255, 255, 255, 0.85);
+ opacity: 0;
+ transition: opacity 0.2s ease;
+ pointer-events: none;
+ z-index: 2;
+}
+
+.project-proof-thumb:hover::before {
+ opacity: 1;
+}
+
+.project-proof-caption {
+ font-size: 13px;
+ color: $subTitle;
+}
+
+.project-card-accordion {
+ margin-top: 1rem;
+ border-radius: 14px;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ padding: 0.5rem 0.75rem;
+ background: rgba(255, 255, 255, 0.04);
+}
+
+.project-card-accordion-summary {
+ cursor: pointer;
+ font-size: 13px;
+ font-weight: 600;
+}
+
+.project-card-accordion-list {
+ margin: 0.5rem 0 0 0;
+ padding-left: 1.1rem;
+}
+
+.project-card-accordion-item {
+ margin-bottom: 0.5rem;
+ line-height: 1.65;
+ font-size: 13px;
+}
+
+.project-proof-button {
+ margin-left: auto;
+ border: none;
+ background: rgba(255, 255, 255, 0.08);
+ color: $textColorDark;
+ font-size: 12px;
+ padding: 4px 8px;
+ border-radius: 999px;
+ cursor: pointer;
+}
+
+.project-proof-button:hover {
+ text-decoration: underline;
+}
+
+.project-lightbox-overlay {
+ position: fixed;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.75);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 1001;
+}
+
+.project-lightbox {
+ position: relative;
+ width: min(1200px, 100vw);
+ background: #10151f;
+ border-radius: 16px;
+ padding: 1.5rem;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+}
+
+.project-lightbox-image {
+ width: 100%;
+ border-radius: 14px;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+}
+
+.project-lightbox-caption {
+ margin-top: 0.75rem;
+ color: $textColorDark;
+ font-size: 14px;
+}
+
+.project-lightbox-close {
+ position: absolute;
+ top: 12px;
+ right: 16px;
+ border: none;
+ background: transparent;
+ color: $textColorDark;
+ font-size: 20px;
+ cursor: pointer;
+}
+
+.project-lightbox-nav {
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ border: none;
+ background: rgba(255, 255, 255, 0.08);
+ color: $textColorDark;
+ width: 36px;
+ height: 36px;
+ border-radius: 50%;
+ cursor: pointer;
+}
+
+.project-lightbox-prev {
+ left: 8px;
+}
+
+.project-lightbox-next {
+ right: 8px;
+}
+
+.project-accordion {
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ border-radius: 14px;
+ padding: 0.75rem 1rem;
+ background: #151a24;
+ color: $textColorDark;
+ margin-bottom: 0.75rem;
+}
+
+.project-accordion-summary {
+ cursor: pointer;
+ font-weight: 600;
+}
+
+.project-accordion-body {
+ margin-top: 0.75rem;
+ max-height: 0;
+ opacity: 0;
+ overflow: hidden;
+ transition: max-height 0.3s ease, opacity 0.3s ease;
+}
+
+.project-accordion[open] .project-accordion-body {
+ max-height: 800px;
+ opacity: 1;
+}
+
+.muscleup-tech-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 1rem;
+}
+
+.muscleup-tech-card {
+ padding: 16px 18px;
+ border-radius: 14px;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ background: rgba(255, 255, 255, 0.04);
+}
+
+.muscleup-tech-header {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ margin-bottom: 0.5rem;
+}
+
+.muscleup-tech-icon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ padding: 2px 8px;
+ border-radius: 999px;
+ font-size: 11px;
+ font-weight: 700;
+ background: rgba(202, 255, 92, 0.16);
+ color: #eaffb0;
+ text-transform: uppercase;
+}
+
+.muscleup-tech-title {
+ font-weight: 700;
+ font-size: 14px;
+ color: $textColorDark;
+}
+
+.muscleup-tech-items {
+ color: $subTitle;
+ font-size: 13px;
+ line-height: 1.6;
+}
+
+.muscleup-core-stack {
+ display: grid;
+ gap: 2rem;
+}
+
+.muscleup-intro {
+ padding: 32px 28px;
+ background: linear-gradient(
+ 150deg,
+ rgba(38, 48, 70, 0.92),
+ rgba(21, 26, 36, 0.96)
+ ),
+ #151a24;
+ border-radius: 20px;
+}
+
+.muscleup-intro-grid {
+ display: grid;
+ grid-template-columns: 1.1fr 0.9fr;
+ gap: 2rem;
+ align-items: center;
+}
+
+.muscleup-intro-copy {
+ display: grid;
+ gap: 1.25rem;
+}
+
+.muscleup-intro-title {
+ margin: 0;
+ font-size: 24px;
+}
+
+.muscleup-intro-hero {
+ margin: 0;
+ font-size: 16px;
+}
+
+.muscleup-intro-points {
+ display: grid;
+ gap: 0.65rem;
+ color: $subTitle;
+ font-size: 14px;
+}
+
+.muscleup-intro-point {
+ display: grid;
+ grid-template-columns: 20px 1fr;
+ gap: 0.5rem;
+ align-items: start;
+}
+
+.muscleup-intro-icon {
+ width: 24px;
+ height: 24px;
+ border-radius: 999px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 12px;
+ font-weight: 700;
+ background: rgba(202, 255, 92, 0.18);
+ color: #eaffb0;
+}
+
+.muscleup-intro-media {
+ display: grid;
+ gap: 0.75rem;
+ text-align: center;
+}
+
+.muscleup-intro-media-list {
+ display: grid;
+ gap: 0.75rem;
+}
+
+.muscleup-intro-media img {
+ width: 100%;
+ max-width: 70%;
+ margin: 0 auto;
+ border-radius: 16px;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ object-fit: cover;
+}
+
+.muscleup-section {
+ display: grid;
+ gap: 1.5rem;
+}
+
+.muscleup-row {
+ display: grid;
+ grid-template-columns: 1.1fr 0.9fr;
+ gap: 1.5rem;
+ align-items: center;
+}
+
+.muscleup-row.reverse {
+ grid-template-columns: 0.9fr 1.1fr;
+}
+
+.muscleup-hero {
+ padding: 1.5rem;
+ border-radius: 18px;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ background: #1a2030;
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
+}
+
+.muscleup-hero .muscleup-desc,
+.muscleup-hero .muscleup-proof {
+ padding: 0;
+ border: none;
+ background: transparent;
+}
+
+.muscleup-split {
+ grid-template-columns: 1fr 1fr;
+ gap: 2rem;
+}
+
+.muscleup-desc {
+ display: grid;
+ gap: 0.75rem;
+ padding: 1.5rem;
+ border-radius: 18px;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ background: #1a2030;
+}
+
+.muscleup-card-header {
+ display: flex;
+ gap: 0.5rem;
+ align-items: center;
+ flex-wrap: wrap;
+}
+
+.muscleup-keypoint {
+ font-size: 12px;
+ font-weight: 600;
+ padding: 4px 8px;
+ border-radius: 999px;
+ border: 1px solid rgba(255, 255, 255, 0.1);
+ background: rgba(255, 255, 255, 0.08);
+ color: $textColorDark;
+}
+
+.muscleup-card-title {
+ font-size: 20px;
+ font-weight: 700;
+ margin: 0;
+}
+
+.muscleup-highlight {
+ font-size: 15px;
+ font-weight: 700;
+ padding: 6px 10px;
+ border-radius: 8px;
+ background: linear-gradient(
+ 120deg,
+ rgba(255, 255, 0, 0.18),
+ rgba(255, 255, 0, 0.05)
+ );
+ color: $textColorDark;
+ display: inline-flex;
+ width: fit-content;
+}
+
+.muscleup-card-list {
+ margin: 0;
+ padding-left: 1.2rem;
+}
+
+.muscleup-card-item {
+ margin-bottom: 0.6rem;
+ line-height: 1.7;
+ font-size: 14px;
+}
+
+.muscleup-proof,
+.muscleup-proof-card {
+ display: grid;
+ gap: 0.5rem;
+ padding: 1rem;
+ border-radius: 18px;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ background: #151a24;
+}
+
+.muscleup-proof-card {
+ padding: 1.25rem;
+}
+
+.muscleup-proof-label {
+ font-size: 12px;
+ font-weight: 600;
+ color: $textColorDark;
+ text-transform: uppercase;
+}
+
+.muscleup-proof-caption {
+ font-size: 13px;
+ color: $subTitle;
+}
+
+@media (max-width: 900px) {
+ .muscleup-intro-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .muscleup-summary-row {
+ grid-template-columns: 1fr;
+ }
+
+ .muscleup-intro-media img {
+ max-width: 100%;
+ }
+
+ .muscleup-proof-thumb img {
+ max-width: 100%;
+ }
+
+ .muscleup-row,
+ .muscleup-row.reverse,
+ .muscleup-split {
+ grid-template-columns: 1fr;
+ }
+}
+
+.muscleup-card-surface,
+.muscleup-proof-surface,
+.muscleup-text-block {
+ border-radius: 18px;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ background: #1a2030;
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
+ height: 100%;
+}
+
+.muscleup-text-block,
+.muscleup-proof-block {
+ transition: transform 0.22s ease, box-shadow 0.22s ease,
+ border-color 0.22s ease;
+}
+
+.muscleup-text-block:hover,
+.muscleup-proof-block:hover {
+ transform: translateY(-4px);
+ box-shadow: 0 16px 36px rgba(0, 0, 0, 0.32);
+ border-color: rgba(202, 255, 92, 0.28);
+}
+
+.muscleup-text-block-ops {
+ background: #1b1e2a;
+ border-color: rgba(255, 148, 88, 0.3);
+}
+
+.muscleup-card-surface-intro {
+ background: #1d2536;
+}
+
+.muscleup-card-surface,
+.muscleup-text-block {
+ padding: 28px 30px;
+}
+
+.muscleup-proof-surface {
+ padding: 18px;
+ background: #151a24;
+}
+
+.muscleup-proof-surface img {
+ width: 100%;
+ border-radius: 14px;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ display: block;
+ object-fit: cover;
+}
+
+.muscleup-section {
+ display: grid;
+ gap: 6px;
+ margin-bottom: 140px;
+}
+
+.muscleup-section-heading {
+ padding: 0;
+ margin-bottom: 56px;
+}
+
+.muscleup-section-heading-row {
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+}
+
+.muscleup-section-heading-icon {
+ font-size: 18px;
+}
+
+.muscleup-section-heading-title {
+ margin: 0;
+ font-size: 22px;
+}
+
+.muscleup-section-heading-subtitle {
+ margin: 0.35rem 0 0;
+ color: $subTitle;
+ font-size: 14px;
+}
+
+.muscleup-pattern-a,
+.muscleup-pattern-b,
+.muscleup-pattern-d {
+ display: grid;
+ grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
+ gap: 1.75rem;
+ align-items: stretch;
+}
+
+.muscleup-pattern-b {
+ grid-template-columns: minmax(0, 0.95fr) minmax(0, 1.05fr);
+}
+
+.muscleup-pattern-c {
+ display: grid;
+ gap: 1.5rem;
+}
+
+.muscleup-highlight-text {
+ font-weight: 700;
+ font-size: 15px;
+ padding: 4px 6px;
+ border-radius: 6px;
+ background: linear-gradient(transparent 55%, rgba(202, 255, 92, 0.4) 0);
+ color: #eaffb0;
+}
+
+.muscleup-keyword {
+ font-weight: 700;
+ color: #caff5a;
+}
+
+.muscleup-one-liner {
+ font-weight: 700;
+ padding: 4px 6px;
+ border-radius: 6px;
+ background: linear-gradient(transparent 55%, rgba(202, 255, 92, 0.35) 0);
+ color: #eaffb0;
+ display: inline-block;
+}
+
+.muscleup-bullet-group {
+ display: grid;
+ gap: 0.4rem;
+}
+
+.muscleup-bullet-label {
+ font-size: 11px;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+ font-weight: 700;
+ color: rgba(255, 255, 255, 0.55);
+}
+
+.muscleup-bullet-line {
+ position: relative;
+ padding-left: 16px;
+ font-size: 14px;
+ color: $textColorDark;
+ line-height: 1.55;
+}
+
+.muscleup-bullet-line::before {
+ content: "•";
+ position: absolute;
+ left: 0;
+ top: 0;
+ color: rgba(255, 255, 255, 0.45);
+}
+
+.muscleup-card-header {
+ margin-bottom: 0.75rem;
+}
+
+.muscleup-card-title {
+ font-size: 20px;
+ margin-bottom: 0.5rem;
+}
+
+.muscleup-card-list {
+ margin: 0;
+ padding-left: 1.2rem;
+}
+
+.muscleup-card-item {
+ margin-bottom: 0.6rem;
+ line-height: 1.65;
+}
+
+.muscleup-chip-row {
+ display: flex;
+ gap: 0.5rem;
+ flex-wrap: wrap;
+ margin: 0.5rem 0 0.75rem;
+}
+
+.muscleup-chip {
+ padding: 4px 10px;
+ border-radius: 999px;
+ border: 1px solid rgba(255, 255, 255, 0.12);
+ background: rgba(180, 130, 255, 0.12);
+ font-size: 12px;
+}
+
+.muscleup-proof-block {
+ display: grid;
+ gap: 0.75rem;
+ padding: 22px 24px;
+ border-radius: 18px;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ background: #151a24;
+ height: 100%;
+}
+
+.muscleup-proof-block-large {
+ padding: 26px 28px;
+}
+
+.muscleup-proof-label {
+ font-size: 12px;
+ font-weight: 600;
+ text-transform: uppercase;
+ color: $textColorDark;
+}
+
+.muscleup-proof-thumb {
+ border: none;
+ padding: 0;
+ border-radius: 14px;
+ overflow: hidden;
+ cursor: pointer;
+ background: transparent;
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex: 1;
+ position: relative;
+}
+
+.muscleup-proof-thumb img {
+ width: 100%;
+ max-width: 88%;
+ max-height: 500px;
+ aspect-ratio: 16 / 9;
+ border-radius: 14px;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ object-fit: contain;
+ display: block;
+ transition: transform 0.2s ease, filter 0.2s ease;
+ position: relative;
+ z-index: 0;
+}
+
+.muscleup-proof-thumb:hover img {
+ transform: scale(1.02);
+ filter: brightness(1.04);
+}
+
+.muscleup-proof-thumb::after {
+ content: "";
+ position: absolute;
+ inset: 0;
+ background: rgba(0, 0, 0, 0.12);
+ opacity: 0;
+ transition: opacity 0.2s ease;
+ z-index: 1;
+}
+
+.muscleup-proof-thumb::before {
+ content: "🔍";
+ position: absolute;
+ inset: 0;
+ display: grid;
+ place-items: center;
+ font-size: 60px;
+ color: rgba(255, 255, 255, 0.85);
+ opacity: 0;
+ transition: opacity 0.2s ease;
+ pointer-events: none;
+ z-index: 2;
+}
+
+.muscleup-proof-thumb:hover::after {
+ opacity: 1;
+}
+
+.muscleup-proof-thumb:hover::before {
+ opacity: 1;
+}
+
+.muscleup-proof-caption {
+ font-size: 13px;
+ color: $subTitle;
+}
+
+.muscleup-issue-list {
+ display: grid;
+ gap: 0.4rem;
+ font-size: 13px;
+ color: $subTitle;
+}
+
+.muscleup-issue-item {
+ display: flex;
+ gap: 0.4rem;
+ align-items: baseline;
+}
+
+.muscleup-issue-label {
+ font-weight: 700;
+ margin-right: 0.4rem;
+ color: $textColorDark;
+}
+
+.muscleup-issue-text {
+ color: $subTitle;
+}
+
+.muscleup-summary-box {
+ padding: 18px 22px;
+ border-radius: 18px;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ background: linear-gradient(
+ 150deg,
+ rgba(38, 48, 70, 0.92),
+ rgba(21, 26, 36, 0.96)
+ ),
+ #151a24;
+ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
+}
+
+.muscleup-summary-wide {
+ width: 100%;
+}
+
+.muscleup-summary-row {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 0.85rem;
+ margin-top: 0.75rem;
+}
+
+.muscleup-summary-item {
+ display: grid;
+ grid-template-columns: 44px 1fr;
+ gap: 0.75rem;
+ align-items: center;
+ padding: 10px 12px;
+ border-radius: 14px;
+ border: 1px solid rgba(255, 255, 255, 0.08);
+ background: rgba(21, 26, 36, 0.2);
+ font-size: 13px;
+ color: $subTitle;
+ opacity: 0;
+ transform: translateY(8px);
+ animation: fadeUp 0.4s ease-out forwards;
+}
+
+.muscleup-summary-item:nth-child(1) {
+ animation-delay: 0.05s;
+}
+
+.muscleup-summary-item:nth-child(2) {
+ animation-delay: 0.12s;
+}
+
+.muscleup-summary-item:nth-child(3) {
+ animation-delay: 0.19s;
+}
+
+.muscleup-summary-item:nth-child(4) {
+ animation-delay: 0.26s;
+}
+
+.muscleup-summary-item:nth-child(5) {
+ animation-delay: 0.33s;
+}
+
+.muscleup-summary-icon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 40px;
+ height: 28px;
+ border-radius: 999px;
+ font-size: 10px;
+ font-weight: 700;
+ background: rgba(202, 255, 92, 0.16);
+ color: #eaffb0;
+ text-transform: uppercase;
+}
+
+.muscleup-summary-title {
+ font-size: 13px;
+ font-weight: 700;
+ color: $textColorDark;
+}
+
+.muscleup-summary-desc {
+ font-size: 12px;
+ color: $subTitle;
+}
+
+.muscleup-summary-list {
+ margin: 0;
+ padding-left: 1.2rem;
+ line-height: 1.65;
+}
+
+.muscleup-summary-list li {
+ margin-bottom: 0.45rem;
+}
+
+.project-hero-image {
+ min-height: 320px;
+ max-height: 420px;
+ object-fit: cover;
+}
+
+@media (max-width: 900px) {
+ .muscleup-pattern-a,
+ .muscleup-pattern-b,
+ .muscleup-pattern-d {
+ grid-template-columns: 1fr;
+ }
+}
+
+.project-ps-grid {
+ display: grid;
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ gap: 1.5rem;
+ margin-bottom: 1.5rem;
+}
+
+.project-ps-card {
+ border: 2px solid #bfeee3;
+ border-radius: 12px;
+ padding: 1.25rem;
+ background: $lightBackground1;
+}
+
+.project-ps-title {
+ font-size: 18px;
+ margin: 0 0 0.75rem 0;
+ color: $titleColor;
+}
+
+.project-ps-text {
+ margin: 0;
+ line-height: 1.7;
+ color: $textColor;
+}
+
+.project-ps-list {
+ margin: 0;
+ padding-left: 1.2rem;
+}
+
+.project-ps-list-item {
+ margin-bottom: 0.5rem;
+ line-height: 1.6;
+}
+
+.project-ps-outcome {
+ border-color: #d7f4ed;
+ background: $lightBackground3;
+}
+
+.project-strategy-list {
+ display: grid;
+ gap: 2rem;
+}
+
+.project-strategy-item {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
+ gap: 2rem;
+ align-items: center;
+}
+
+.project-strategy-step {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 36px;
+ height: 36px;
+ border-radius: 10px;
+ background: #dff6f1;
+ color: #2f4a65;
+ font-weight: 700;
+ margin-bottom: 0.75rem;
+}
+
+.project-strategy-title {
+ font-size: 20px;
+ margin: 0 0 0.5rem 0;
+ color: $titleColor;
+}
+
+.project-strategy-desc {
+ margin: 0;
+ line-height: 1.7;
+ color: $textColor;
+}
+
+.project-strategy-media {
+ text-align: center;
+}
+
+.project-strategy-image {
+ width: 100%;
+ height: auto;
+ border-radius: 12px;
+ box-shadow: $lightBoxShadowDark 0px 10px 24px -16px;
+}
+
+.project-strategy-caption {
+ margin-top: 0.5rem;
+ font-size: 14px;
+ color: $subTitle;
+}
+
+.project-feature-grid {
+ display: grid;
+ gap: 2rem;
+}
+
+.project-feature-item {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
+ gap: 2rem;
+ align-items: center;
+}
+
+.project-feature-title {
+ font-size: 20px;
+ margin: 0 0 0.75rem 0;
+ color: $titleColor;
+}
+
+.project-feature-list {
+ margin: 0;
+ padding-left: 1.2rem;
+}
+
+.project-feature-list-item {
+ margin-bottom: 0.5rem;
+ line-height: 1.6;
+}
+
+.project-feature-media {
+ text-align: center;
+}
+
+.project-feature-image {
+ width: 100%;
+ height: auto;
+ border-radius: 12px;
+ box-shadow: $lightBoxShadowDark 0px 10px 24px -16px;
+}
+
+.project-feature-caption {
+ margin-top: 0.5rem;
+ font-size: 14px;
+ color: $subTitle;
+}
+
+.dark-mode .project-modal {
+ background: $darkBackground;
+ color: $textColorDark;
+}
+
+.dark-mode .project-modal-link,
+.dark-mode .project-modal-close,
+.dark-mode .project-detail-button,
+.dark-mode .project-modal-back {
+ color: $textColorDark;
+ border-color: $textColorDark;
+}
+
+.dark-mode .project-overview-subtitle,
+.dark-mode .project-overview-caption {
+ color: $textColorDark;
+}
+
+.dark-mode .project-overview-link {
+ color: $textColorDark;
+}
+
+.dark-mode .project-modal-section {
+ border-top-color: rgba(255, 255, 255, 0.2);
+}
+
+.dark-mode .project-modal-section-title {
+ border-bottom-color: rgba(255, 255, 255, 0.2);
+}
+
+.dark-mode .project-overview {
+ border-bottom-color: rgba(255, 255, 255, 0.2);
+}
+
+.dark-mode .project-ps-card {
+ border-color: rgba(255, 255, 255, 0.2);
+ background: rgba(255, 255, 255, 0.04);
+}
+
+.dark-mode .project-ps-title,
+.dark-mode .project-ps-text,
+.dark-mode .project-strategy-title,
+.dark-mode .project-strategy-desc,
+.dark-mode .project-feature-title {
+ color: $textColorDark;
+}
+
+.dark-mode .project-ps-outcome {
+ background: rgba(255, 255, 255, 0.08);
+}
+
+.dark-mode .project-strategy-step {
+ background: rgba(255, 255, 255, 0.16);
+ color: $textColorDark;
+}
+
+.dark-mode .project-strategy-caption,
+.dark-mode .project-feature-caption {
+ color: $textColorDark;
+}
+
+.dark-mode .project-detail-card {
+ background: rgba(255, 255, 255, 0.04);
+ border-color: rgba(255, 255, 255, 0.2);
+}
+
+.dark-mode .project-detail-card-title,
+.dark-mode .project-detail-card-item {
+ color: $textColorDark;
+}
+
+.dark-mode .project-badge {
+ background: rgba(255, 255, 255, 0.08);
+ border-color: rgba(255, 255, 255, 0.2);
+ color: $textColorDark;
+}
+
+.dark-mode .project-badge-star,
+.dark-mode .project-badge-fire,
+.dark-mode .project-badge-check {
+ background: rgba(255, 255, 255, 0.12);
+ color: $textColorDark;
+}
+
+@media (max-width: 768px) {
+ .project-modal-header {
+ grid-template-columns: 1fr;
+ }
+
+ .project-modal-title {
+ font-size: 24px;
+ }
+
+ .project-overview-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .project-ps-grid,
+ .project-strategy-item,
+ .project-feature-item {
+ grid-template-columns: 1fr;
+ }
+
+ .project-detail-card-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .muscleup-tech-grid {
+ grid-template-columns: 1fr;
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .project-modal {
+ animation: none;
+ opacity: 1;
+ transform: none;
+ }
+
+ .muscleup-summary-item {
+ animation: none;
+ opacity: 1;
+ transform: none;
+ }
+
+ .muscleup-text-block,
+ .muscleup-proof-block,
+ .muscleup-proof-thumb img,
+ .experience-card,
+ .experience-card-dark {
+ transition: none;
+ }
+
+ .project-accordion-body {
+ transition: none;
+ max-height: none;
+ opacity: 1;
+ }
+}
+
+.project-card-footer span.project-tag {
+ background: $buttonColor;
+ color: $lightBackground3;
+ vertical-align: middle;
+ align-items: center;
+ border-radius: 4px;
+ display: inline-flex;
+ font-size: 0.75rem;
+ height: 2em;
+ justify-content: center;
+ white-space: nowrap;
+ line-height: 1.5;
+ margin: 0 0.5rem 0.5rem 0;
+ padding: 0 0.75em;
+ cursor: pointer;
+ transition: 0.2s ease-in;
+}
+
+@media (max-width: 768px) {
+ .project-subtitle {
+ font-size: 16px;
+ text-align: center;
+ }
+}
+
+.projects-container .experience-blurred_div.project-banner-web {
+ background: linear-gradient(
+ 180deg,
+ rgba(48, 167, 196, 0.22) 0%,
+ rgba(48, 167, 196, 0.16) 70%,
+ rgba(48, 167, 196, 0) 100%
+ );
+}
+
+.projects-container .experience-blurred_div.project-banner-ai {
+ background: linear-gradient(
+ 180deg,
+ rgba(118, 96, 214, 0.22) 0%,
+ rgba(118, 96, 214, 0.16) 70%,
+ rgba(118, 96, 214, 0) 100%
+ );
+}
+
+.projects-container .experience-blurred_div.project-banner-game {
+ background: linear-gradient(
+ 180deg,
+ rgba(40, 190, 154, 0.22) 0%,
+ rgba(40, 190, 154, 0.16) 70%,
+ rgba(40, 190, 154, 0) 100%
+ );
+}
+
+.projects-container .experience-blurred_div.project-banner-ar {
+ background: linear-gradient(
+ 180deg,
+ rgba(234, 154, 64, 0.22) 0%,
+ rgba(234, 154, 64, 0.16) 70%,
+ rgba(234, 154, 64, 0) 100%
+ );
+}
+
+.projects-container .experience-blurred_div.project-banner-web-ai {
+ background: linear-gradient(
+ 180deg,
+ rgba(48, 167, 196, 0.22) 0%,
+ rgba(48, 167, 196, 0.16) 70%,
+ rgba(118, 96, 214, 0.16) 85%,
+ rgba(118, 96, 214, 0) 100%
+ );
+}
+
+.projects-container .experience-blurred_div.project-banner-ops {
+ background: linear-gradient(
+ 180deg,
+ rgba(140, 150, 160, 0.22) 0%,
+ rgba(140, 150, 160, 0.16) 70%,
+ rgba(140, 150, 160, 0) 100%
+ );
+}
+
+.projects-container .experience-blurred_div.project-banner-web-ops {
+ background: linear-gradient(
+ 180deg,
+ rgba(48, 167, 196, 0.22) 0%,
+ rgba(48, 167, 196, 0.16) 70%,
+ rgba(140, 150, 160, 0.16) 85%,
+ rgba(140, 150, 160, 0) 100%
+ );
}
diff --git a/src/containers/skills/Skills.js b/src/containers/skills/Skills.js
index 1302abe385..9c34ba9e5e 100644
--- a/src/containers/skills/Skills.js
+++ b/src/containers/skills/Skills.js
@@ -12,6 +12,34 @@ export default function Skills() {
if (!skillsSection.display) {
return null;
}
+ const renderSkillText = skill => {
+ let skillText = null;
+ if (typeof skill === "string") {
+ skillText = skill;
+ } else if (
+ Array.isArray(skill) &&
+ skill.length > 0 &&
+ skill.every(item => typeof item === "string")
+ ) {
+ skillText = skill.join("");
+ }
+ if (!skillText) {
+ return skill;
+ }
+ const separatorIndex = skillText.indexOf(":");
+ if (separatorIndex === -1) {
+ return skillText;
+ }
+ const label = skillText.slice(0, separatorIndex).trim();
+ const detail = skillText.slice(separatorIndex + 1).trim();
+ return (
+ <>
+
{label}
+
:
+
{detail}
+ >
+ );
+ };
return (
@@ -55,7 +83,7 @@ export default function Skills() {
: "subTitle skills-text"
}
>
- {skills}
+ {renderSkillText(skills)}
);
})}
diff --git a/src/containers/skills/Skills.scss b/src/containers/skills/Skills.scss
index 0bfa2ef4a3..59fb2c7f0e 100644
--- a/src/containers/skills/Skills.scss
+++ b/src/containers/skills/Skills.scss
@@ -24,6 +24,39 @@
font-size: 56px;
font-weight: 400;
}
+
+.skills-text-subtitle {
+ font-size: 22px;
+ font-weight: 600;
+ line-height: 1.5;
+ color: $textColor;
+}
+
+.skills-text {
+ font-size: 18px;
+ line-height: 1.7;
+}
+
+.skills-text-label,
+.skills-text-separator {
+ font-weight: 700;
+ font-size: 19px;
+ color: $textColor;
+}
+
+.skills-text-detail {
+ color: $subTitle;
+ font-weight: 400;
+}
+
+.dark-mode .skills-text-subtitle {
+ color: $textColorDark;
+}
+
+.dark-mode .skills-text-label,
+.dark-mode .skills-text-separator {
+ color: $textColorDark;
+}
.subTitle {
color: $subTitle;
}
@@ -54,7 +87,7 @@
font-size: 16px;
}
.skills-text-subtitle {
- font-size: 16px;
+ font-size: 18px;
text-align: center;
}
.skills-image-div {
diff --git a/src/containers/topbutton/Top.js b/src/containers/topbutton/Top.js
index 574f716e74..4655095743 100644
--- a/src/containers/topbutton/Top.js
+++ b/src/containers/topbutton/Top.js
@@ -1,4 +1,4 @@
-import React from "react";
+import React, {useEffect} from "react";
import "./Top.scss";
export default function Top() {
@@ -8,21 +8,26 @@ export default function Top() {
}
// When the user scrolls down 20px from the top of the document, show the button
function scrollFunction() {
+ const button = document.getElementById("topButton");
+ if (!button) {
+ return;
+ }
if (
document.body.scrollTop > 20 ||
document.documentElement.scrollTop > 20
) {
- document.getElementById("topButton").style.visibility = "visible";
+ button.style.visibility = "visible";
} else {
- document.getElementById("topButton").style.visibility = "hidden";
+ button.style.visibility = "hidden";
}
}
- window.onscroll = function () {
- scrollFunction();
- };
- window.onload = function () {
+ useEffect(() => {
+ const handleScroll = () => scrollFunction();
+ window.addEventListener("scroll", handleScroll);
scrollFunction();
- }; //To make sure that this button is not visible at starting.
+ return () => window.removeEventListener("scroll", handleScroll);
+ }, []);
+
// When the user clicks on the button, scroll to the top of the document
return (
diff --git a/src/portfolio.js b/src/portfolio.js
index f37660fdf0..5535f9f2d7 100644
--- a/src/portfolio.js
+++ b/src/portfolio.js
@@ -1,4 +1,4 @@
-/* Change this file to get your personal Portfolio */
+/* Change this file to get your personal Portfolio */
// To change portfolio colors globally go to the _globalColor.scss file
@@ -20,26 +20,25 @@ const illustration = {
};
const greeting = {
- username: "Saad Pasta",
- title: "Hi all, I'm Saad",
+ username: "정재훈",
+ title: "웹을 주력으로, 운영 문제까지 해결하는 개발자 정재훈",
subTitle: emoji(
- "A passionate Full Stack Software Developer 🚀 having an experience of building Web and Mobile applications with JavaScript / Reactjs / Nodejs / React Native and some other cool libraries and frameworks."
+ "React · Spring Boot 기반 Full-Stack(Web) 개발 | 인증·보안(Refresh Rotation)과 운영 이슈(HTTPS·CORS) 해결 경험 | Unity XR·게임 개발 경험 보유"
),
- resumeLink:
- "https://drive.google.com/file/d/1ofFdKF_mqscH8WvXkSObnVvC9kK7Ldlu/view?usp=sharing", // Set to empty to hide the button
+ resumeLink: "", // Set to empty to hide the button
displayGreeting: true // Set false to hide this section, defaults to true
};
// Social Media Links
const socialMediaLinks = {
- github: "https://github.com/saadpasta",
- linkedin: "https://www.linkedin.com/in/saadpasta/",
- gmail: "saadpasta70@gmail.com",
- gitlab: "https://gitlab.com/saadpasta",
- facebook: "https://www.facebook.com/saad.pasta7",
- medium: "https://medium.com/@saadpasta",
- stackoverflow: "https://stackoverflow.com/users/10422806/saad-pasta",
+ github: "https://github.com/toadsam",
+ linkedin: "",
+ gmail: "toadsam@naver.com",
+ gitlab: "",
+ facebook: "",
+ medium: "",
+ stackoverflow: "",
// Instagram, Twitter and Kaggle are also supported in the links!
// To customize icons and social links, tweak src/components/SocialMedia
display: true // Set true to display this section, defaults to false
@@ -48,16 +47,15 @@ const socialMediaLinks = {
// Skills Section
const skillsSection = {
- title: "What I do",
- subTitle: "CRAZY FULL STACK DEVELOPER WHO WANTS TO EXPLORE EVERY TECH STACK",
+ title: "Skills",
+ subTitle: "웹 주력 + 운영 이슈 해결 중심",
skills: [
- emoji(
- "⚡ Develop highly interactive Front end / User Interfaces for your web and mobile applications"
- ),
- emoji("⚡ Progressive Web Applications ( PWA ) in normal and SPA Stacks"),
- emoji(
- "⚡ Integration of third party services such as Firebase/ AWS / Digital Ocean"
- )
+ emoji("Frontend: React/TypeScript 기반 SPA 설계 및 구현"),
+ emoji("컴포넌트 구조화, 상태 흐름 설계, API 연동"),
+ emoji("Backend: Spring Boot + JPA로 REST API 설계/구현"),
+ emoji("Auth: JWT 인증/인가 + Refresh Token Rotation"),
+ emoji("Infra: AWS 배포 및 HTTPS/Mixed Content/CORS 해결"),
+ emoji("Unity XR/AR: 인터랙션 및 상태/AI 제어 경험")
],
/* Make Sure to include correct Font Awesome Classname to view your icon
@@ -65,56 +63,32 @@ https://fontawesome.com/icons?d=gallery */
softwareSkills: [
{
- skillName: "html-5",
+ skillName: "HTML5",
fontAwesomeClassname: "fab fa-html5"
},
{
- skillName: "css3",
+ skillName: "CSS3",
fontAwesomeClassname: "fab fa-css3-alt"
},
- {
- skillName: "sass",
- fontAwesomeClassname: "fab fa-sass"
- },
{
skillName: "JavaScript",
fontAwesomeClassname: "fab fa-js"
},
{
- skillName: "reactjs",
+ skillName: "React",
fontAwesomeClassname: "fab fa-react"
},
{
- skillName: "nodejs",
+ skillName: "Node.js",
fontAwesomeClassname: "fab fa-node"
},
{
- skillName: "swift",
- fontAwesomeClassname: "fab fa-swift"
- },
- {
- skillName: "npm",
- fontAwesomeClassname: "fab fa-npm"
- },
- {
- skillName: "sql-database",
+ skillName: "Database",
fontAwesomeClassname: "fas fa-database"
},
{
- skillName: "aws",
+ skillName: "AWS",
fontAwesomeClassname: "fab fa-aws"
- },
- {
- skillName: "firebase",
- fontAwesomeClassname: "fas fa-fire"
- },
- {
- skillName: "python",
- fontAwesomeClassname: "fab fa-python"
- },
- {
- skillName: "docker",
- fontAwesomeClassname: "fab fa-docker"
}
],
display: true // Set false to hide this section, defaults to true
@@ -126,23 +100,23 @@ const educationInfo = {
display: true, // Set false to hide this section, defaults to true
schools: [
{
- schoolName: "Harvard University",
- logo: require("./assets/images/harvardLogo.png"),
- subHeader: "Master of Science in Computer Science",
- duration: "September 2017 - April 2019",
- desc: "Participated in the research of XXX and published 3 papers.",
+ schoolName: "아주대학교",
+ logo: require("./assets/images/아주대로고.png"),
+ subHeader: "디지털미디어학과 (전공)",
+ duration: "2021.03 ~ 2026.02 (예정)",
+ desc: "웹/소프트웨어 엔지니어링 중심으로 학습하며 서비스 구조 설계와 구현 역량을 확장했습니다.",
descBullets: [
- "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
- "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
+ "웹(React/Spring Boot) 중심 프로젝트 경험",
+ "Unity XR/AR 프로젝트로 인터랙션 경험 확장"
]
},
{
- schoolName: "Stanford University",
- logo: require("./assets/images/stanfordLogo.png"),
- subHeader: "Bachelor of Science in Computer Science",
- duration: "September 2013 - April 2017",
- desc: "Ranked top 10% in the program. Took courses about Software Engineering, Web Security, Operating Systems, ...",
- descBullets: ["Lorem ipsum dolor sit amet, consectetur adipiscing elit"]
+ schoolName: "아주대학교",
+ logo: require("./assets/images/아주대로고.png"),
+ subHeader: "인공지능 융합학과 (복수전공)",
+ duration: "2021.03 ~ 2026.02 (예정)",
+ desc: "AI/데이터 기반 개발 역량을 함께 확장하고 있습니다.",
+ descBullets: ["웹 개발과의 융합 관점으로 프로젝트 경험"]
}
]
};
@@ -153,16 +127,16 @@ const techStack = {
viewSkillBars: true, //Set it to true to show Proficiency Section
experience: [
{
- Stack: "Frontend/Design", //Insert stack or technology you have experience in
- progressPercentage: "90%" //Insert relative proficiency in percentage
+ Stack: "Web (Frontend)",
+ progressPercentage: "60%"
},
{
- Stack: "Backend",
- progressPercentage: "70%"
+ Stack: "Web (Backend)",
+ progressPercentage: "80%"
},
{
- Stack: "Programming",
- progressPercentage: "60%"
+ Stack: "Game/XR",
+ progressPercentage: "80%"
}
],
displayCodersrank: false // Set true to display codersrank badges section need to changes your username in src/containers/skillProgress/skillProgress.js:17:62, defaults to false
@@ -174,29 +148,15 @@ const workExperiences = {
display: true, //Set it to true to show workExperiences Section
experience: [
{
- role: "Software Engineer",
- company: "Facebook",
+ role: "개인/팀 프로젝트",
+ company: "웹 · 게임 · XR",
companylogo: require("./assets/images/facebookLogo.png"),
- date: "June 2018 – Present",
- desc: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
+ date: "재학 중",
+ desc: "웹 주력 개발과 XR 서브 경험을 바탕으로 서비스 구조와 UX를 동시에 고려한 개발을 수행.",
descBullets: [
- "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
- "Lorem ipsum dolor sit amet, consectetur adipiscing elit"
+ "다수의 개인·팀 프로젝트 경험",
+ "기획 → 개발 → 배포 → 운영 전 과정 수행"
]
- },
- {
- role: "Front-End Developer",
- company: "Quora",
- companylogo: require("./assets/images/quoraLogo.png"),
- date: "May 2017 – May 2018",
- desc: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
- },
- {
- role: "Software Engineer Intern",
- company: "Airbnb",
- companylogo: require("./assets/images/airbnbLogo.png"),
- date: "Jan 2015 – Sep 2015",
- desc: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
}
]
};
@@ -212,31 +172,874 @@ const openSource = {
// Some big projects you have worked on
const bigProjects = {
- title: "Big Projects",
- subtitle: "SOME STARTUPS AND COMPANIES THAT I HELPED TO CREATE THEIR TECH",
+ title: "Main Projects",
+
projects: [
{
- image: require("./assets/images/saayaHealthLogo.webp"),
- projectName: "Saayahealth",
- projectDesc: "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
+ image: require("./assets/images/득근득근/득근득근로고.png"),
+ projectName: "득근득근 (MuscleUp)",
+ projectDesc:
+ "React + Spring Boot 기반 Full-Stack 웹. AWS 배포/HTTPS·CORS 이슈 해결, Refresh Token Rotation 적용.",
+ recommendation:
+ "운영을 전제로 인증·보안·배포까지 설계한 피트니스 커뮤니티 풀스택 서비스",
+ tags: ["#FullStack", "#JWT", "#AWS", "#Security"],
+ footerLink: [],
+ details: {
+ overview: {
+ title: "득근득근 - 프로젝트 개요",
+ subtitle: "AI 피트니스 커뮤니티 실서비스 풀스택 개발",
+ image: require("./assets/images/득근득근/득근득근메인화면.png"),
+ caption: "득근득근 서비스 메인 화면",
+ role: "기획, UI 설계, 백엔드 API 개발, 인증/권한 구현, 배포 담당 (개인 개발)",
+ period: "2025.09 - (진행중)",
+ coreValue:
+ "운동 기록-커뮤니티-AI 코치를 하나의 실서비스 흐름으로 통합한 풀스택 시스템 구현",
+ techStack: [
+ "Frontend: React, Vite, Axios",
+ "Backend: Spring Boot, Spring Security, JWT",
+ "Database: MySQL, JPA",
+ "Infrastructure: AWS (S3, CloudFront, Route53, ACM, RDS)",
+ "AI: OpenAI GPT API"
+ ]
+ },
+ problemSolution: {
+ problem: [
+ "운동 루틴/식단/기록이 흩어져 '오늘 뭐 하지?'에서 멈춤",
+ "혼자 하면 지속률이 낮아 성과 공유/피드백이 필요"
+ ],
+ solution:
+ "AI 분석 → 4주 루틴 생성 → 대화 히스토리 저장, 커뮤니티 자랑글/댓글/좋아요 강화",
+ outcome:
+ "인증·보안·배포·운영까지 고려한 실사용 가능한 풀스택 서비스 완성"
+ },
+ strategySteps: [
+ {
+ step: "1",
+ title: "AI 기반 개인 맞춤 운동 루틴 제공",
+ description:
+ "사용자의 운동 수준, 목표, 신체 데이터를 분석해 최적화된 루틴을 제공하여 운동 효과를 극대화",
+ image: require("./assets/images/saayaHealthLogo.webp"),
+ caption: "AI 상담 화면"
+ },
+ {
+ step: "2",
+ title: "운동 성과 공유 중심 커뮤니티 설계",
+ description:
+ "기록 공유, 챌린지 참여, 소셜 인터랙션을 통해 사용자 간 동기 부여와 지속적인 습관 형성",
+ image: require("./assets/images/nextuLogo.webp"),
+ caption: "자랑방 화면"
+ },
+ {
+ step: "3",
+ title: "초보자 기준 UX로 정보 과부하 최소화",
+ description:
+ "직관적인 인터페이스와 쉬운 가이드라인으로 운동 초보자도 쉽게 참여할 수 있도록 설계",
+ image: require("./assets/images/googleAssistantLogo.webp"),
+ caption: "소개 화면"
+ }
+ ],
+ coreFeatureShots: [
+ {
+ title: "JWT 이중 쿠키 + Refresh Token 로테이션",
+ bullets: [
+ "Threat: 로컬스토리지 토큰은 XSS에 취약",
+ "Design: Access/Refresh 수명 분리 + DB 저장",
+ "Control: 재발급 시 기존 Refresh 폐기",
+ "Hardening: HttpOnly 쿠키 + Role 기반 보호"
+ ],
+ image: require("./assets/images/pwaLogo.webp"),
+ caption: "JWT 보안 인증 및 DB 설계 코드"
+ },
+ {
+ title: "소셜/이메일 인증",
+ bullets: [
+ "SMTP 연동을 통한 이메일 인증 플로우",
+ "구글 소셜 로그인 OAuth2 연동",
+ "회원가입/로그인 UX 일관성 유지"
+ ],
+ image: require("./assets/images/saayaHealthLogo.webp"),
+ caption: "인증 화면"
+ }
+ ],
+ summary:
+ "JWT 인증 + AI 코치 + 커뮤니티 운영까지 완성한 피트니스 서비스.",
+ role: "기획, 프론트엔드, 백엔드, 배포/운영 전 과정",
+ highlights: [
+ "JWT + Refresh Token Rotation 적용",
+ "AI 코치 루틴 생성 + 대화 히스토리 저장",
+ "커뮤니티 CRUD/댓글/좋아요/권한 체크",
+ "AWS 배포 환경 문제 재현-해결-검증"
+ ],
+ stack:
+ "React, TypeScript, Spring Boot, Spring Security, JPA, MySQL, AWS, Nginx",
+ problemGoal: [
+ "운동 기록/인증/커뮤니티가 분산되어 지속 동기 유지가 어려움",
+ "로그인/갱신 불안정은 서비스 신뢰 하락으로 직결",
+ "배포 후 HTTPS/CORS 문제로 오류가 반복 발생",
+ "안정적인 인증을 기준으로 설계하고 운영 체크리스트화"
+ ],
+ architecture: [
+ "React SPA → Spring Boot REST API → MySQL(RDS)",
+ "Access/Refresh 수명 분리 + HttpOnly Cookie 기반 인증",
+ "Refresh Rotation으로 탈취 토큰 재사용 차단",
+ "파일 업로드는 UUID/경로 검증으로 안전 처리"
+ ],
+ authSecurity: [
+ "Access 만료 시 401 처리",
+ "Refresh 쿠키로 재발급",
+ "Rotation으로 기존 Refresh 폐기 + 신규 저장",
+ "Role 기반 접근 제어 및 표준화된 에러 처리"
+ ],
+ coreFeatures: [
+ "AI 코치: 분석 → 4주 루틴 생성 → 히스토리 저장",
+ "커뮤니티: 자랑글 CRUD + 댓글/좋아요 + 권한 체크",
+ "파일: 업로드/목록/삭제, UUID/경로 검증"
+ ],
+ deployment: [
+ "EC2 + RDS(MySQL) 운영 환경 구축",
+ "S3/CloudFront 정적 리소스 구성",
+ "HTTPS 통일 및 Mixed Content 방지",
+ "CORS allowlist + credentials 설정"
+ ],
+ links: [
+ {name: "배포 주소 (Deployment)", url: "https://muscle-up.click"},
+ {
+ name: "GitHub",
+ url: "https://github.com/toadsam/Ajou_MuscleUp"
+ },
+ {
+ name: "시연 영상 (Demo Video)",
+ url: "https://www.youtube.com/watch?v=y6pbAoxveQM&feature=youtu.be"
+ }
+ ]
+ }
+ },
+
+ {
+ image: require("./assets/images/AjouCampusFood/ACF로고.png"),
+ projectName: "Ajou Campus Foodmap",
+ projectDesc:
+ "세션 기반 OAuth 로그인 + 맛집 등록 워크플로우를 포함한 Full-Stack 서비스",
+ recommendation:
+ "세션 기반 OAuth 로그인과 맛집 등록 플로우를 구현한 캠퍼스 지도 서비스",
+ tags: ["#FullStack", "#OAuth", "#Workflow"],
footerLink: [
{
- name: "Visit Website",
- url: "http://saayahealth.com/"
+ name: "GitHub Client",
+ url: "https://github.com/toadsam/pwd-week6-client"
+ },
+ {
+ name: "GitHub Server",
+ url: "https://github.com/toadsam/pwd-week6-server"
}
- // you can add extra buttons here.
- ]
+ ],
+ details: {
+ overview: {
+ title: "Ajou Campus Foodmap",
+ subtitle:
+ "React(Vite) + Express + MongoDB 기반 맛집 지도/등록 서비스 (Local/OAuth 로그인, 운영 배포 포함)",
+ image: require("./assets/images/AjouCampusFood/ACF메인화면.png"),
+ caption: "서비스 메인/맛집 목록/등록 흐름(대표 화면)",
+ role: "Full-Stack 개발",
+ period: "2024.10 - 2024.12",
+ techStack: [
+ "Frontend: React(Vite) / React Query / Axios",
+ "Backend: Express / Passport / Session / Mongoose",
+ "Database: MongoDB Atlas",
+ "Infra: Vercel + Render"
+ ]
+ },
+ intro: {
+ headline: "What is Ajou Campus Foodmap?",
+ highlight:
+ "맛집 등록/승인 흐름과 인증을 통합한 캠퍼스 맛집 지도 서비스",
+ problem:
+ "맛집 정보가 흩어져 있고, 사용자 참여형 등록/승인 흐름이 없으면 데이터 품질 관리가 어려움",
+ solution:
+ "맛집 등록 → 승인(pending/approved/rejected) 워크플로우를 스키마/권한 기반으로 설계",
+ outcome:
+ "배포 환경에서도 로그인 유지/등록 흐름이 안정적으로 동작하고, 운영 관점까지 포함한 실서비스 구조 완성",
+ caption: "서비스 메인/맛집 목록/등록 흐름(대표 화면)",
+ images: [require("./assets/images/AjouCampusFood/ACF수락화면.png")]
+ },
+ quickSummary: [
+ {
+ icon: "🌍",
+ title: "배포 환경 분기",
+ desc: "로컬/운영 URL·DB 설정 충돌을 환경변수 기반으로 분리"
+ },
+ {
+ icon: "🍪",
+ title: "교차 도메인 세션 유지",
+ desc: "withCredentials + CORS allowlist/credentials로 로그인 유지"
+ },
+ {
+ icon: "🗃️",
+ title: "Session DB 저장",
+ desc: "MongoStore로 세션을 저장해 재시작/HTTPS에서도 지속"
+ },
+ {
+ icon: "🔐",
+ title: "인증(Local + OAuth)",
+ desc: "Passport Local + Google/Naver OAuth를 단일 흐름으로 통합"
+ },
+ {
+ icon: "🛡️",
+ title: "운영/관리 + 권한",
+ desc: "관리자 권한 분리 + 초기 계정 시드로 운영 효율 확보"
+ }
+ ],
+ coreDesign: [
+ {
+ title: "배포 환경 분기 — URL/DB 설정 분리 운영",
+ oneLiner:
+ "운영/로컬 환경 차이로 생기는 URL·DB 연결 오류를 환경변수 분기로 차단",
+ how: "How: Client 환경별 API URL 분기 + Server mongoose 옵션/운영 DB 분리 + 배포 환경변수 매핑",
+ result:
+ "Result: 배포 환경에서도 설정 충돌/연결 불안정 감소, 운영 안정성 확보",
+ proofCaption: "환경변수/DB 커넥션/배포 설정 캡처",
+ proofImage: require("./assets/images/AjouCampusFood/ACF환경분리코드.png")
+ },
+ {
+ title: "교차 도메인 세션 유지 (SPA + API 분리)",
+ oneLiner:
+ "서버/클라이언트 분리 환경에서도 쿠키 기반 로그인 세션이 끊기지 않게 유지",
+ how: "How: axios withCredentials + CORS allowlist/credentials=true 적용",
+ result: "Result: 운영에서도 로그인 상태 유지/세션 기반 기능 안정화",
+ proofCaption: "withCredentials 코드 + CORS 설정 캡처",
+ proofImage: require("./assets/images/nextuLogo.webp")
+ },
+ {
+ title: "Session + MongoStore — 세션 DB 저장",
+ oneLiner:
+ "세션을 메모리가 아닌 DB에 저장해 서버 재시작/HTTPS에서도 로그인 지속",
+ how: "How: session store를 MongoStore로 구성 + credentials/origin 정책 적용",
+ result: "Result: 배포/재시작 환경에서도 세션 유지, 운영 이슈 감소",
+ proofCaption: "MongoStore 코드 + DB 저장 확인 캡처",
+ proofImage: require("./assets/images/saayaHealthLogo.webp")
+ },
+ {
+ title: "Passport Local + bcrypt — 기본 로그인 보안",
+ oneLiner:
+ "Passport Local 인증 + bcrypt 해시로 비밀번호 저장/검증을 표준화",
+ how: "How: serialize/deserialize + UserSchema pre-save bcrypt 적용",
+ result: "Result: 보안/인증 흐름 명확화, 세션 기반 UX 안정화",
+ proofCaption: "serialize/deserialize + bcrypt 코드 캡처",
+ proofImage: require("./assets/images/googleAssistantLogo.webp")
+ },
+ {
+ title: "OAuth(Google/Naver) + 운영/권한(관리자)",
+ oneLiner:
+ "OAuth 로그인과 관리자 권한 제어를 붙여 ‘운영 가능한 서비스’로 완성",
+ how: "How: Passport Strategy + req.login() 세션 생성 + 관리자 미들웨어/시드 스크립트",
+ result:
+ "Result: 운영 측면(관리자 기능 보안/계정 관리)까지 포함한 실서비스 구조",
+ proofCaption: "권한 미들웨어 코드 + admin seed 캡처",
+ proofImage: require("./assets/images/saayaHealthLogo.webp")
+ }
+ ],
+ ops: {
+ oneLiner:
+ "Issue: 배포 환경에서 URL/DB 불일치, 교차 도메인 세션 쿠키 미전달",
+ how: "Fix: 환경변수 분리 + CORS allowlist/credentials + MongoStore 세션 저장",
+ result: "Result: 운영에서도 로그인/세션/DB 연결이 안정적으로 동작"
+ },
+ links: [
+ {
+ name: "GitHub (Client)",
+ url: "https://github.com/toadsam/pwd-week6-client"
+ },
+ {
+ name: "GitHub (Server)",
+ url: "https://github.com/toadsam/pwd-week6-server"
+ }
+ ]
+ }
+ },
+
+ {
+ image: require("./assets/images/saayaHealthLogo.webp"),
+ projectName: "ajouchong",
+ projectDesc: "총학생회 운영형 홍보·정보 제공 웹 + 유입/행동 분석",
+ recommendation:
+ "실사용 조직 운영을 위한 정보 제공·홍보 흐름 중심 웹 서비스",
+ tags: ["#WebService", "#Operations", "#UX"],
+ footerLink: [
+ {name: "aClub", url: "https://ajouclub.co.kr"},
+ {name: "ajouchong", url: "https://ajouchong.com"}
+ ],
+ details: {
+ overview: {
+ title: "ajouchong - 프로젝트 개요",
+ subtitle: "총학생회 운영형 홍보·정보 제공 웹 + 유입/행동 분석",
+ image: require("./assets/images/saayaHealthLogo.webp"),
+ caption: "서비스 메인/공지/자료/상세 화면",
+ role: "Frontend 개발, 운영 구조 설계 및 분석 연계",
+ period: "2025.03 - 2025.08",
+ techStack: [
+ "Frontend: React, Vite, Axios",
+ "Analytics: GA4, Google Search Console",
+ "Deploy: Vercel, Cloudflare",
+ "Tooling: Notion, Figma"
+ ]
+ },
+ intro: {
+ headline: "What is ajouchong?",
+ highlight:
+ "총학생회 운영 정보를 사용자 흐름 기준으로 재구성한 운영형 웹",
+ problem:
+ "유입 이후 ‘무엇을 어디서 해야 하는지’가 분산되어 문의/이탈이 반복됨",
+ solution:
+ "공지·신청·안내를 사용자 흐름 기준으로 재구성 + 외부 채널 유입 연결",
+ outcome:
+ "정보 탐색 시간이 줄고, 참여 동선이 명확해져 운영 효율이 개선됨",
+ caption: "서비스 메인/공지/자료/상세 화면",
+ images: [
+ require("./assets/images/saayaHealthLogo.webp"),
+ require("./assets/images/nextuLogo.webp")
+ ]
+ },
+ quickSummary: [
+ {
+ icon: "📣",
+ title: "운영형 홍보",
+ desc: "공지/이벤트/지원 정보를 ‘탐색→확인→참여’ 흐름으로 정리"
+ },
+ {
+ icon: "🔍",
+ title: "정보 구조·UX",
+ desc: "메뉴/카테고리 구조를 단순화해 필요한 정보를 빠르게 찾게 설계"
+ },
+ {
+ icon: "📈",
+ title: "유입·행동 분석",
+ desc: "GA4/GSC로 유입·클릭·체류 데이터를 수집해 개선 포인트 도출"
+ },
+ {
+ icon: "🤝",
+ title: "채널 연동 운영",
+ desc: "Everytime·카카오톡 등 외부 채널과 연결해 실사용 유입을 설계"
+ },
+ {
+ icon: "⚙️",
+ title: "Ops · Deploy",
+ desc: "배포/운영 관점에서 안정적으로 유지·개선 가능한 구조로 관리"
+ }
+ ],
+ coreDesign: [
+ {
+ title: "정보 구조·탐색 UX",
+ oneLiner: "공지/지원 정보를 사용 흐름 기준으로 재구성",
+ how: "How: 메뉴/카테고리/CTA 동선을 ‘찾기→확인→신청’으로 정리",
+ result: "Result: 반복 문의 감소 + 필요한 정보 도달 속도 향상",
+ proofCaption: "메인 화면/메뉴 구조/CTA 영역 캡처",
+ proofImage: require("./assets/images/saayaHealthLogo.webp")
+ },
+ {
+ title: "유입·행동 분석(운영 개선 루프)",
+ oneLiner: "데이터로 개선 포인트를 확인하는 운영 구조",
+ how: "How: GA4로 페이지뷰/체류/이벤트, GSC로 검색 유입/CTR 확인",
+ result: "Result: 콘텐츠/동선 개선의 근거 확보",
+ proofCaption: "GA4/GSC 화면 또는 지표 캡처",
+ proofImage: require("./assets/images/nextuLogo.webp")
+ },
+ {
+ title: "홍보 채널 연동 운영",
+ oneLiner: "외부 채널 유입을 서비스 참여로 연결",
+ how: "How: Everytime/카카오톡에서 들어온 사용자가 바로 행동할 수 있게 링크/CTA 설계",
+ result: "Result: 홍보→유입→참여 전환 흐름 강화",
+ proofCaption: "채널 홍보/링크 구조/랜딩 캡처",
+ proofImage: require("./assets/images/pwaLogo.webp")
+ }
+ ],
+ links: [
+ {name: "aClub", url: "https://ajouclub.co.kr"},
+ {name: "ajouchong", url: "https://ajouchong.com"}
+ ]
+ }
+ },
+
+ {
+ image: require("./assets/images/nextuLogo.webp"),
+ projectName: "TSEROF",
+ projectDesc: "출시/배포까지 완료한 3D 액션 플랫폼 게임",
+ recommendation: "출시·배포까지 완주한 3D 액션 플랫폼 게임 프로젝트",
+ tags: ["#Unity", "#GameDev", "#3D"],
+ footerLink: [],
+ details: {
+ overview: {
+ title: "TSEROF — 프로젝트 개요",
+ subtitle: "출시/배포까지 완료한 3D 액션 플랫폼 게임",
+ image: require("./assets/images/nextuLogo.webp"),
+ caption: "게임 플레이/스테이지 선택 화면",
+ role: "게임 시스템/플레이 로직 구현",
+ period: "2024.09 - 2025.02",
+ techStack: [
+ "Engine: Unity",
+ "Language: C#",
+ "Tools/Etc: Unity Profiler, Addressables"
+ ]
+ },
+ intro: {
+ headline: "What is TSEROF?",
+ highlight: "출시/배포까지 완료한 3D 액션 플랫폼 게임",
+ problem:
+ "플랫폼 게임에서 반복 실패가 잦으면 이탈이 빨라지고, 저장/성능 문제가 있으면 완성도가 급락한다.",
+ solution:
+ "스테이지 선택 + 진행 저장으로 반복 도전을 유도하고, 충돌/레이캐스트/오브젝트 생성 비용을 최적화해 플레이 흐름을 안정화했다.",
+ outcome:
+ "출시/배포까지 완료된 3D 액션 플랫폼 게임을 구현하고, 저장 안정성(XOR)과 성능(충돌/레이캐스트/풀링)을 개선해 완성도를 확보했다.",
+ caption: "플레이 화면/스테이지 선택 화면",
+ images: [
+ require("./assets/images/nextuLogo.webp"),
+ require("./assets/images/pwaLogo.webp")
+ ]
+ },
+ quickSummary: [
+ {
+ icon: "🎮",
+ title: "Steam 출시/배포 완료",
+ desc: "스토어 공개 및 외부 사용자 플레이 가능 상태"
+ },
+ {
+ icon: "🧭",
+ title: "스테이지 선택 + 진행 저장",
+ desc: "반복 도전 구조로 이탈 방지"
+ },
+ {
+ icon: "⚡",
+ title: "충돌/콜라이더 최적화",
+ desc: "불필요 충돌 체크 제거로 프레임 안정화"
+ },
+ {
+ icon: "🎯",
+ title: "Raycast 최적화",
+ desc: "RaycastAll → NonAlloc + LayerMask로 비용 절감"
+ },
+ {
+ icon: "🔐",
+ title: "저장 데이터 XOR 암호화",
+ desc: "세이브 변조/삭제 리스크 완화"
+ },
+ {
+ icon: "🧺",
+ title: "Object Pooling + Caching",
+ desc: "반복 생성/GC 부담 감소"
+ }
+ ],
+ coreDesign: [
+ {
+ title: "플레이 흐름 — 이탈 방지",
+ oneLiner:
+ "세이브/스테이지 선택으로 “처음부터 다시” 스트레스를 제거",
+ how: "How: 스테이지 선택/진행 저장 구조로 반복 도전 UX 구성",
+ result: "Result: 플레이 지속성 확보 + 난이도 구간에서 이탈 완화",
+ proofCaption: "스테이지 선택 화면 / 진행 저장 UI",
+ proofImage: require("./assets/images/nextuLogo.webp")
+ },
+ {
+ title: "레벨/장애물 — 학습 곡선 설계",
+ oneLiner: "관찰 → 학습 → 응용의 난이도 곡선으로 재미 유지",
+ how: "How: 장애물 패턴을 단계적으로 복잡하게 설계",
+ result: "Result: 단순 조작에서도 ‘판단하는 재미’ 강화",
+ proofCaption: "스테이지/장애물 플레이 캡처",
+ proofImage: require("./assets/images/saayaHealthLogo.webp")
+ },
+ {
+ title: "충돌 최적화 (Collision Optimization)",
+ oneLiner: "Collider 단순화 + 불필요 충돌 체크 제거로 성능 회복",
+ how: "How: Collider 구조 정리 / ignore 조건으로 충돌 체크 최소화",
+ result: "Result: 프레임 안정화 + 입력/조작감 개선",
+ proofCaption: "Collision 최적화 관련 코드 캡처",
+ proofImage: require("./assets/images/pwaLogo.webp")
+ },
+ {
+ title: "Object Pooling + Caching",
+ oneLiner: "반복 생성 대신 재사용으로 GC/CPU 부담 감소",
+ how: "How: Object Pooling 적용 + WaitForSeconds 등 코루틴 객체 캐싱",
+ result: "Result: 끊김 감소 + 장시간 플레이 안정화",
+ proofCaption: "풀링/캐싱 구조 설명 캡처",
+ proofImage: require("./assets/images/googleAssistantLogo.webp")
+ },
+ {
+ title: "Raycast 최적화",
+ oneLiner: "RaycastAll 비용 제거 → NonAlloc + LayerMask로 최적화",
+ how: "How: RaycastNonAlloc 사용 + 필요한 Layer만 감지",
+ result: "Result: 메모리 할당 감소 + CPU 비용 절감",
+ proofCaption: "Raycast 개선 전/후 코드 캡처",
+ proofImage: require("./assets/images/nextuLogo.webp")
+ },
+ {
+ title: "XOR 저장 데이터 보호",
+ oneLiner: "세이브 데이터 변조/삭제 리스크를 낮추는 XOR 암호화",
+ how: "How: JSON 저장 시 XOR 암호화 적용",
+ result: "Result: 데이터 변경 가능성을 시각적으로/구조적으로 낮춤",
+ proofCaption: "XOR 저장 로직 캡처",
+ proofImage: require("./assets/images/saayaHealthLogo.webp")
+ }
+ ],
+ ops: {
+ oneLiner: "Optimization & Stability",
+ how: "How: 충돌/풀링/레이캐스트 최적화 및 저장 안정화 적용",
+ result: "Result: 프레임 안정화와 장시간 플레이 안정성을 확보"
+ },
+ links: [
+ {
+ name: "Steam",
+ url: "https://store.steampowered.com/app/2743860/TSEROF/?l=koreana"
+ },
+ {name: "GitHub", url: "https://github.com/KimEoJin24/TSEROF"},
+ {
+ name: "YouTube",
+ url: "https://www.youtube.com/watch?v=1Lm-lpVsmq8"
+ }
+ ]
+ }
},
+
{
image: require("./assets/images/nextuLogo.webp"),
- projectName: "Nextu",
- projectDesc: "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
+ projectName: "아주대탐험 (Ajou Indie Game)",
+ projectDesc:
+ "대학 캠퍼스를 배경으로 한 캐주얼 액션 어드벤처: Player·Skill·AI·Event 시스템을 Unity로 통합 구현",
+ recommendation:
+ "스킬·AI·이벤트 시스템을 통합 구현한 캐주얼 액션 어드벤처 게임",
+ tags: ["#Unity", "#AI", "#GameSystem"],
footerLink: [
+ {name: "GitHub", url: "https://github.com/toadsam/Ajou_IndiGame"},
{
- name: "Visit Website",
- url: "http://nextu.se/"
+ name: "Demo Video",
+ url: "https://www.youtube.com/watch?v=mtIiIWmrSdg&feature=youtu.be"
}
- ]
+ ],
+ details: {
+ overview: {
+ title: "아주대탐험 (Ajou Indie Game)",
+ subtitle:
+ "대학 캠퍼스를 배경으로 한 캐주얼 액션 어드벤처: Player·Skill·AI·Event 시스템을 Unity로 통합 구현",
+ image: require("./assets/images/nextuLogo.webp"),
+ caption: "플레이 화면 기반 결과물(인게임 UI·전투·이벤트 흐름)",
+ role: "Player/Skill/UI/AI/Event 구현",
+ period: "2024.08 - 2024.12",
+ techStack: [
+ "Engine: Unity",
+ "Language: C#",
+ "Key Features: NavMesh, UI System, Event System, Skill System"
+ ]
+ },
+ intro: {
+ headline: "What is Ajou Indie Game?",
+ highlight:
+ "캠퍼스 탐험형 액션/성장 게임의 코어 루프를 통합 설계한 인디 프로젝트",
+ problem:
+ "캠퍼스 탐험형 액션/성장 게임에서 “플레이 흐름(이동-전투-성장-진행)”이 끊기지 않게 시스템을 연결해야 했다.",
+ solution:
+ "Core Loop를 기준으로 Player/Skill/UI/AI/Event를 모듈화하고, 서로 연결되는 지점을 명확히 설계했다.",
+ outcome:
+ "플레이어 조작·성장·전투·이벤트가 하나의 루프로 자연스럽게 이어지는 구조를 완성했다.",
+ caption: "플레이 화면 기반 결과물(인게임 UI·전투·이벤트 흐름)",
+ images: [require("./assets/images/nextuLogo.webp")]
+ },
+ quickSummary: [
+ {
+ icon: "🎮",
+ title: "Player Mode Switching",
+ desc: "1인칭/3인칭 전환을 안정적으로 연결해 조작 일관성 유지"
+ },
+ {
+ icon: "🧭",
+ title: "Level-Up & Random Skill",
+ desc: "랜덤 3스킬 선택 + 일시정지로 성장 순간을 명확히 분리"
+ },
+ {
+ icon: "🤖",
+ title: "Enemy AI + Boss Pattern",
+ desc: "NavMesh 기반 추적과 패턴 설계로 전투 밀도 강화"
+ },
+ {
+ icon: "🧩",
+ title: "UI ↔ Game World",
+ desc: "상태/퀘스트/상호작용 UI가 월드 진행과 동기화"
+ },
+ {
+ icon: "✨",
+ title: "Event Systems",
+ desc: "Special Quest/Portal/Summon Skill 이벤트로 루프 확장"
+ }
+ ],
+ coreDesign: [
+ {
+ title: "Player Mode Switching (1인칭/3인칭)",
+ oneLiner: "모드 충돌 없는 컨트롤 전환으로 조작 일관성 확보",
+ how: "How: 컨트롤러 enable/disable, 카메라 parent 기준 정리, enum/switch로 상태 전환",
+ result: "Result: 전환 시 입력 꼬임 없이 동일한 UX 유지",
+ proofCaption: "Mode Switching 구조 증명",
+ proofImage: require("./assets/images/saayaHealthLogo.webp")
+ },
+ {
+ title: "Level-Up & Random Skill Selection",
+ oneLiner: "레벨업 순간 ‘정지-선택-재개’ 루프로 성장 경험 강화",
+ how: "How: 랜덤 3개 스킬 제시, UI 슬롯 세팅, Time.timeScale=0으로 일시정지 처리",
+ result: "Result: 전투 흐름은 유지하면서 선택 UX는 명확하게 분리",
+ proofCaption: "Random Skill UI 증명",
+ proofImage: require("./assets/images/pwaLogo.webp")
+ },
+ {
+ title: "Enemy AI (NavMesh + Boss Pattern)",
+ oneLiner: "NavMesh 기반 추적 + 보스 패턴으로 전투 밀도 설계",
+ how: "How: detectRange/attackRange로 상태 분기, AI 상태(추적/공격 등) 구성",
+ result:
+ "Result: 일반 몬스터/보스 모두 예측 가능한 규칙 위에서 난이도 조절",
+ proofCaption: "NavMesh AI / Boss Pattern 증명",
+ proofImage: require("./assets/images/nextuLogo.webp")
+ },
+ {
+ title: "UI ↔ Game World",
+ oneLiner:
+ "UI가 ‘정보 표시’가 아니라 ‘게임 진행의 일부’가 되게 설계",
+ how: "How: 퀘스트/상태/상호작용 UI가 월드 오브젝트/진행 상태와 동기화",
+ result: "Result: 플레이 중 ‘다음 행동’이 UI로 자연스럽게 안내됨",
+ proofCaption: "UI-월드 동기화 증명",
+ proofImage: require("./assets/images/googleAssistantLogo.webp")
+ }
+ ],
+ links: [
+ {name: "GitHub", url: "https://github.com/toadsam/Ajou_IndiGame"},
+ {
+ name: "Demo Video",
+ url: "https://www.youtube.com/watch?v=mtIiIWmrSdg&feature=youtu.be"
+ }
+ ]
+ }
+ },
+
+ {
+ image: require("./assets/images/googleAssistantLogo.webp"),
+ projectName: "The Other Side (VR Horror Game)",
+ projectDesc: "XR Interaction Toolkit 기반 VR 공포 퍼즐 탈출 게임",
+ recommendation: "XR Interaction Toolkit 기반 VR 공포 퍼즐 탈출 게임",
+ tags: ["#VR", "#XR", "#Unity"],
+ footerLink: [],
+ details: {
+ overview: {
+ title: "The Other Side",
+ subtitle:
+ "Unity XR 기반 1인칭 VR 공포 퍼즐 게임 (상태 기반 AI, 인터랙션 중심 설계)",
+ image: require("./assets/images/googleAssistantLogo.webp"),
+ caption: "VR 플레이 화면 및 퍼즐/추적 장면",
+ role: "VR 인터랙션/AI 설계",
+ period: "2024.07 - 2024.10",
+ techStack: [
+ "Engine: Unity",
+ "VR: XR Interaction Toolkit",
+ "AI: NavMesh, State Machine",
+ "Platform: PC VR (Meta / SteamVR)"
+ ]
+ },
+ intro: {
+ headline: "What is The Other Side?",
+ highlight: "상호작용과 추적 패턴을 중심으로 설계한 VR 공포 퍼즐 게임",
+ problem:
+ "VR 공포 게임에서 단순 점프 스케어 위주의 연출은 몰입도가 빠르게 떨어짐",
+ solution:
+ "XR Interaction Toolkit 기반 자연스러운 오브젝트 상호작용 설계 + 상태 머신 AI + 퍼즐 연계 레벨 흐름 구성",
+ outcome:
+ "VR 환경에서도 조작 부담이 적은 퍼즐 중심 공포 게임 구현 및 몰입형 플레이 경험 제공",
+ caption: "VR 플레이 화면 및 퍼즐/추적 장면",
+ images: [
+ require("./assets/images/googleAssistantLogo.webp"),
+ require("./assets/images/saayaHealthLogo.webp")
+ ]
+ },
+ quickSummary: [
+ {
+ icon: "🕶️",
+ title: "VR 상호작용 중심 설계",
+ desc: "Grab/Ray/Socket 기반 퍼즐 인터랙션 구현"
+ },
+ {
+ icon: "👾",
+ title: "상태 기반 몬스터 AI",
+ desc: "거리·시야 조건으로 대기/추적/공격 상태 전환"
+ },
+ {
+ icon: "🧩",
+ title: "퍼즐 연계 월드 구조",
+ desc: "오브젝트 조합·트리거로 이어지는 퍼즐 흐름"
+ },
+ {
+ icon: "🎢",
+ title: "VR UX 최적화",
+ desc: "시점 흔들림 최소화 + 상호작용 피드백 강화"
+ },
+ {
+ icon: "🧱",
+ title: "확장 가능한 구조",
+ desc: "스테이지/퍼즐/AI 로직 확장에 유리한 설계"
+ }
+ ],
+ coreDesign: [
+ {
+ title: "XR Interaction Toolkit 기반 상호작용",
+ oneLiner:
+ "VR 환경에 맞춘 Grab / Ray / Socket 상호작용으로 퍼즐 조작을 직관화",
+ how: "How: XR Grab Interactable로 집기 + Ray Interactor로 원거리 선택 + Socket 조합 설계",
+ result: "Result: VR 초보자도 이해하기 쉬운 상호작용 UX 완성",
+ proofCaption: "XR Interactor 설정 화면 및 퍼즐 오브젝트 캡처",
+ proofImage: require("./assets/images/nextuLogo.webp")
+ },
+ {
+ title: "상태 기반 몬스터 AI",
+ oneLiner:
+ "플레이어 조건에 따라 행동이 달라지는 상태 머신 기반 AI 구현",
+ how: "How: Idle/Chase/Attack 상태 분리 + 거리/시야 조건 전환 + NavMesh 추적 이동",
+ result: "Result: 예측 불가능한 추적 패턴으로 공포 몰입도 상승",
+ proofCaption: "몬스터 AI 스크립트 및 추적 장면 캡처",
+ proofImage: require("./assets/images/pwaLogo.webp")
+ },
+ {
+ title: "퍼즐 중심 레벨 구조",
+ oneLiner:
+ "단서 수집과 환경 상호작용으로 진행되는 퍼즐 기반 스테이지 설계",
+ how: "How: 트리거 이벤트로 단계 관리 + 조건 충족 시 공간 개방 + 실패 시 긴장 요소 연계",
+ result:
+ "Result: 단순 이동이 아닌 사고를 요구하는 VR 플레이 경험 제공",
+ proofCaption: "퍼즐 오브젝트 및 이벤트 흐름 캡처",
+ proofImage: require("./assets/images/saayaHealthLogo.webp")
+ },
+ {
+ title: "VR UX 안정화 설계",
+ oneLiner: "VR 멀미를 줄이기 위한 시점·이동·인터랙션 설계",
+ how: "How: 카메라 이동 최소화 + 즉각 피드백 제공 + 순간 이동/안전 거리 유지",
+ result: "Result: 장시간 플레이에도 부담이 적은 VR UX 확보",
+ proofCaption: "플레이 시점 설정 및 이동 방식 캡처",
+ proofImage: require("./assets/images/googleAssistantLogo.webp")
+ }
+ ],
+ links: []
+ }
+ },
+
+ {
+ image: require("./assets/images/pwaLogo.webp"),
+ projectName: "AR Monster Shooter",
+ projectDesc:
+ "AR Plane Scan 기반 전투 공간 자동 생성 + 웨이브/보스 트리거 슈터",
+ recommendation: "AR Plane 스캔 기반 전투 공간 생성 슈터 데모 프로젝트",
+ tags: ["#AR", "#Unity", "#Shooter"],
+ footerLink: [],
+ details: {
+ overview: {
+ title: "AR Monster Shooter",
+ subtitle:
+ "Unity AR Foundation 기반 AR 전투 게임 — Plane Scan으로 공간 고정 후 웨이브/보스 전투 진행",
+ image: require("./assets/images/pwaLogo.webp"),
+ caption: "AR Plane Scan 기반 전투 공간 생성 및 전투 진행 화면",
+ role: "AR 전투 로직/스폰/무기 시스템 구현",
+ period: "2024.06 - 2024.08",
+ techStack: [
+ "Engine: Unity",
+ "Platform: AR (AR Foundation)",
+ "Language: C#"
+ ]
+ },
+ intro: {
+ headline: "What is AR Monster Shooter?",
+ highlight: "AR Plane Scan 기반 전투 공간을 자동 생성하는 슈터 게임",
+ problem:
+ "AR 환경에서 전투를 진행하려면 현실 공간 기준의 전투 영역 고정이 필요함",
+ solution:
+ "Plane Detection으로 전투 공간을 스캔하고, boundary 기반 바닥/벽 자동 생성 + 웨이브/보스 트리거를 적용",
+ outcome:
+ "현실 공간 크기에 맞춰 전투 맵이 자동 생성되어 몰입도가 높아지고, 웨이브→보스 전환 구조가 안정화됨",
+ caption: "AR Plane Scan 기반 전투 공간 생성 및 전투 진행 화면",
+ images: [
+ require("./assets/images/pwaLogo.webp"),
+ require("./assets/images/saayaHealthLogo.webp")
+ ]
+ },
+ quickSummary: [
+ {
+ icon: "🧭",
+ title: "Plane Scan 전투 공간 고정",
+ desc: "Plane Detection으로 전투 영역을 고정하고 전투 진행"
+ },
+ {
+ icon: "🧱",
+ title: "boundary 기반 바닥/벽 자동 생성",
+ desc: "boundary를 월드 좌표로 변환해 바닥 스케일 + 4방향 벽 생성"
+ },
+ {
+ icon: "📍",
+ title: "스폰 포인트 자동 생성 + 스포너 이동",
+ desc: "floorCorners 기반 스폰 포인트 배열 생성 + 랜덤 스포너 이동"
+ },
+ {
+ icon: "🌊",
+ title: "웨이브 스폰 + 보스 트리거",
+ desc: "spawnInterval 웨이브 진행 + counterBoss 조건에서 보스 전환"
+ },
+ {
+ icon: "🎮",
+ title: "무기 발사 + 잠금/쿨타임",
+ desc: "조이스틱 발사 + isUnlocked/nextFire로 전투 템포 제어"
+ }
+ ],
+ coreDesign: [
+ {
+ title: "Plane Scan → 전투 공간 고정",
+ oneLiner:
+ "Plane Detection으로 전투 공간을 ‘고정’하고 이후 전투를 해당 영역 기준으로 진행",
+ how: "How: 평면 탐지 ON/OFF 토글 + boundary 월드 좌표 변환 + 스캔 종료 시 탐지/시각화 비활성화",
+ result:
+ "Result: 스캔 이후 전투 영역이 흔들리지 않고 현실 공간 기준으로 안정적인 전투 진행",
+ proofCaption: "Plane Scan 코드 캡처 + AR 스캔 전/후 화면",
+ proofImage: require("./assets/images/pwaLogo.webp")
+ },
+ {
+ title: "boundary 기반 바닥/벽 생성 로직",
+ oneLiner:
+ "boundary를 수집해 min/max 범위를 계산하고 바닥 스케일링 + 4방향 벽 생성",
+ how: "How: allWorldPoints 수집 + min/max 계산 + 바닥 스케일 조정 + CreateWall 로직",
+ result:
+ "Result: 사용자 공간 크기에 맞는 전투 맵이 자동 구성되어 플레이 일관성 향상",
+ proofCaption: "boundary 기반 맵 확장 코드 캡처 + 바닥/벽 결과",
+ proofImage: require("./assets/images/nextuLogo.webp")
+ },
+ {
+ title: "스폰 포인트 자동 생성 + 스포너 이동",
+ oneLiner:
+ "바닥 꼭짓점(floorCorners) 기반으로 스폰 포인트를 만들고 스포너를 랜덤 이동",
+ how: "How: floorCorners 기반 스폰 배열 생성 + isGameStart 이후 랜덤 위치 갱신",
+ result:
+ "Result: AR 공간 변화에도 스폰이 분산되고 전투 리듬이 단조롭지 않게 진행",
+ proofCaption: "스포너 이동/랜덤 스폰 코드 캡처 + 스폰 장면",
+ proofImage: require("./assets/images/saayaHealthLogo.webp")
+ },
+ {
+ title: "웨이브 스폰 + 보스 트리거",
+ oneLiner:
+ "spawnInterval로 웨이브를 진행하고 counterBoss 조건 달성 시 보스 소환",
+ how: "How: 일반 몬스터 스폰 + 처치 카운트 누적 → bossOn 트리거",
+ result:
+ "Result: 스폰→처치 누적→보스 등장 흐름이 명확해져 게임 구조가 선명해짐",
+ proofCaption: "웨이브/보스 트리거 코드 캡처 + 보스 등장 화면",
+ proofImage: require("./assets/images/pwaLogo.webp")
+ },
+ {
+ title: "무기 발사(조이스틱) + 잠금/쿨타임 제어",
+ oneLiner:
+ "조이스틱 입력 기반 발사에 잠금/쿨타임을 붙여 조작감과 전략적 깊이 향상",
+ how: "How: 입력 방향 감지 + nextFire 쿨타임 + isUnlocked 잠금 + 카메라 기준 조준",
+ result:
+ "Result: 무기별 전투 템포가 살아나고 상황에 맞는 전략 운용 가능",
+ proofCaption:
+ "WeaponShooterWithJoystick 코드 캡처 + 무기(활/칼/마법/총) UI/플레이",
+ proofImage: require("./assets/images/googleAssistantLogo.webp")
+ }
+ ],
+ links: []
+ }
}
],
display: true // Set false to hide this section, defaults to true
@@ -246,58 +1049,32 @@ const bigProjects = {
// Include certificates, talks etc
const achievementSection = {
- title: emoji("Achievements And Certifications 🏆 "),
- subtitle:
- "Achievements, Certifications, Award Letters and Some Cool Stuff that I have done !",
+ title: emoji("Sub Projects (Game / XR)"),
+ subtitle: "프로젝트 요약 (한 줄씩)",
achievementsCards: [
{
- title: "Google Code-In Finalist",
- subtitle:
- "First Pakistani to be selected as Google Code-in Finalist from 4000 students from 77 different countries.",
+ title: "TSEROF",
+ subtitle: "Unity 게임 프로젝트 (스팀 출시 경험 포함).",
image: require("./assets/images/codeInLogo.webp"),
- imageAlt: "Google Code-In Logo",
- footerLink: [
- {
- name: "Certification",
- url: "https://drive.google.com/file/d/0B7kazrtMwm5dYkVvNjdNWjNybWJrbndFSHpNY2NFV1p4YmU0/view?usp=sharing"
- },
- {
- name: "Award Letter",
- url: "https://drive.google.com/file/d/0B7kazrtMwm5dekxBTW5hQkg2WXUyR3QzQmR0VERiLXlGRVdF/view?usp=sharing"
- },
- {
- name: "Google Code-in Blog",
- url: "https://opensource.googleblog.com/2019/01/google-code-in-2018-winners.html"
- }
- ]
+ imageAlt: "TSEROF",
+ footerLink: []
},
{
- title: "Google Assistant Action",
+ title: "The Other Side (VR)",
subtitle:
- "Developed a Google Assistant Action JavaScript Guru that is available on 2 Billion devices world wide.",
+ "Unity XR Interaction Toolkit 기반 VR 게임, NavMesh 기반 AI/상태 설계.",
image: require("./assets/images/googleAssistantLogo.webp"),
- imageAlt: "Google Assistant Action Logo",
- footerLink: [
- {
- name: "View Google Assistant Action",
- url: "https://assistant.google.com/services/a/uid/000000100ee688ee?hl=en"
- }
- ]
+ imageAlt: "The Other Side VR",
+ footerLink: []
},
-
{
- title: "PWA Web App Developer",
- subtitle: "Completed Certifcation from SMIT for PWA Web App Development",
+ title: "AR Monster Shooter / INTO MONSTER POINT",
+ subtitle:
+ "Unity AR Foundation 기반 AR 프로젝트 (Plane Scan, boundary 등).",
image: require("./assets/images/pwaLogo.webp"),
- imageAlt: "PWA Logo",
- footerLink: [
- {name: "Certification", url: ""},
- {
- name: "Final Project",
- url: "https://pakistan-olx-1.firebaseapp.com/"
- }
- ]
+ imageAlt: "AR Monster Shooter",
+ footerLink: []
}
],
display: true // Set false to hide this section, defaults to true
@@ -306,22 +1083,29 @@ const achievementSection = {
// Blogs Section
const blogSection = {
- title: "Blogs",
- subtitle:
- "With Love for Developing cool stuff, I love to write and teach others what I have learnt.",
- displayMediumBlogs: "true", // Set true to display fetched medium blogs instead of hardcoded ones
+ title: "Development Principles",
+ subtitle: "개발 철학",
+ displayMediumBlogs: "false", // Set true to display fetched medium blogs instead of hardcoded ones
blogs: [
{
- url: "https://blog.usejournal.com/create-a-google-assistant-action-and-win-a-google-t-shirt-and-cloud-credits-4a8d86d76eae",
- title: "Win a Google Assistant Tshirt and $200 in Google Cloud Credits",
- description:
- "Do you want to win $200 and Google Assistant Tshirt by creating a Google Assistant Action in less then 30 min?"
+ url: "",
+ title: "기능보다 구조를 먼저 설계",
+ description: "확장성과 유지보수를 고려한 아키텍처를 우선합니다."
+ },
+ {
+ url: "",
+ title: "역할 분리와 유지보수 가능한 코드",
+ description: "비즈니스 로직과 표현 계층을 분리해 변경에 강합니다."
},
{
- url: "https://medium.com/@saadpasta/why-react-is-the-best-5a97563f423e",
- title: "Why REACT is The Best?",
- description:
- "React is a JavaScript library for building User Interface. It is maintained by Facebook and a community of individual developers and companies."
+ url: "",
+ title: "설계 의사결정을 설명할 수 있는 코드",
+ description: "왜 이 구조가 필요한지 근거를 남깁니다."
+ },
+ {
+ url: "",
+ title: "사용자 흐름 기반 UX",
+ description: "동선을 기준으로 인터페이스를 설계합니다."
}
],
display: true // Set false to hide this section, defaults to true
@@ -330,17 +1114,27 @@ const blogSection = {
// Talks Sections
const talkSection = {
- title: "TALKS",
- subtitle: emoji(
- "I LOVE TO SHARE MY LIMITED KNOWLEDGE AND GET A SPEAKER BADGE 😅"
- ),
+ title: "Experience & Activities",
+ subtitle: "학업 및 활동",
talks: [
{
- title: "Build Actions For Google Assistant",
- subtitle: "Codelab at GDG DevFest Karachi 2019",
- slides_url: "https://bit.ly/saadpasta-slides",
- event_url: "https://www.facebook.com/events/2339906106275053/"
+ title: "디지털미디어학과 전공",
+ subtitle: "웹/소프트웨어 엔지니어링 중심 학습",
+ slides_url: "",
+ event_url: ""
+ },
+ {
+ title: "인공지능 융합학과 복수전공",
+ subtitle: "AI·데이터 기반 개발 경험",
+ slides_url: "",
+ event_url: ""
+ },
+ {
+ title: "다수의 개인·팀 프로젝트 수행",
+ subtitle: "웹/게임/XR 융합적 개발 경험",
+ slides_url: "",
+ event_url: ""
}
],
display: true // Set false to hide this section, defaults to true
@@ -349,13 +1143,12 @@ const talkSection = {
// Podcast Section
const podcastSection = {
- title: emoji("Podcast 🎙️"),
- subtitle: "I LOVE TO TALK ABOUT MYSELF AND TECHNOLOGY",
+ title: "About Me",
+ subtitle:
+ "React·TypeScript와 Spring Boot를 중심으로 서비스 설계부터 배포/운영까지 책임져온 Full-Stack(Web) 개발자입니다. HTTPS/Mixed Content, CORS, 인증 갱신 등 운영 이슈를 직접 해결하며 원인 분석 → 재현 → 수정 → 검증 흐름을 체득했습니다. 웹을 주력으로 하되 Unity XR/AR 경험을 통해 인터랙션과 실시간 시스템 감각도 함께 키우고 있습니다.",
// Please Provide with Your Podcast embeded Link
- podcast: [
- "https://anchor.fm/codevcast/embed/episodes/DevStory---Saad-Pasta-from-Karachi--Pakistan-e9givv/a-a15itvo"
- ],
+ podcast: [],
display: true // Set false to hide this section, defaults to true
};
@@ -365,22 +1158,22 @@ const resumeSection = {
subtitle: "Feel free to download my resume",
// Please Provide with Your Podcast embeded Link
- display: true // Set false to hide this section, defaults to true
+ display: false // Set false to hide this section, defaults to true
};
const contactInfo = {
- title: emoji("Contact Me ☎️"),
+ title: emoji("Contact"),
subtitle:
- "Discuss a project or just want to say hi? My Inbox is open for all.",
- number: "+92-0000000000",
- email_address: "saadpasta70@gmail.com"
+ "협업/인턴/프로젝트 제안 모두 환영합니다. 가장 빠른 연락은 이메일로 부탁드립니다.",
+ number: "010-6428-6247",
+ email_address: "toadsam@naver.com"
};
// Twitter Section
const twitterDetails = {
- userName: "twitter", //Replace "twitter" with your twitter username without @
- display: true // Set true to display this section, defaults to false
+ userName: "", //Replace "twitter" with your twitter username without @
+ display: false // Set true to display this section, defaults to false
};
const isHireable = false; // Set false if you are not looking for a job. Also isHireable will be display as Open for opportunities: Yes/No in the GitHub footer