Polish company introduction landing page

This commit is contained in:
2026-06-24 11:34:56 +09:00
parent edacb08141
commit 253291922b
3 changed files with 564 additions and 72 deletions

157
script.js
View File

@@ -1,8 +1,14 @@
const caseData = {
election: {
kicker: "2.1 선거방송",
title: "선거방송 데이터 방송 그래픽 솔루션 개발",
kicker: "2.1 Election Broadcast",
title: "선거방송 데이터 방송 그래픽 솔루션",
client: "Public Broadcast",
field: "Real-time CG",
status: "LIVE DATA",
description: "선관위 데이터와 방송 그래픽을 실시간으로 연동해 자막 송출 시스템을 구성합니다.",
tags: ["선관위 연동", "스케줄러", "송출 컨트롤"],
statValue: "Real-time",
statLabel: "선관위 데이터와 방송 그래픽 실시간 연동",
bullets: [
"선관위 데이터 실시간 연동 코드 적용",
"데이터 스케줄러와 송출 컨트롤러 시스템 개발",
@@ -15,9 +21,15 @@ const caseData = {
]
},
virtual: {
kicker: "2.2 버추얼 그래픽 시스템",
title: "버추얼 그래픽 솔루션 개발",
kicker: "2.2 Virtual Graphic",
title: "버추얼 그래픽 솔루션",
client: "Broadcast Studio",
field: "VR / AR System",
status: "3D READY",
description: "Unreal Engine과 Unity를 활용해 VR, AR 기반의 실시간 3D 그래픽 시스템을 개발합니다.",
tags: ["Unreal Engine", "Unity", "3D Data"],
statValue: "VR / AR",
statLabel: "실시간 데이터 기반 3D 그래픽 처리",
bullets: [
"Unreal Engine 기반 VR/AR 시스템 개발",
"실시간 데이터 3D 그래픽 처리 시스템 적용",
@@ -30,9 +42,15 @@ const caseData = {
]
},
finance: {
kicker: "2.3 데이터베이스 / 경제 방송",
kicker: "2.3 Finance Broadcast",
title: "증권 데이터 및 경제 방송 송출 그래픽 시스템",
client: "Finance Media",
field: "Database / API",
status: "MARKET API",
description: "코스콤 데이터와 해외 지수 데이터를 연계해 방송 송출 그래픽과 API 서버를 구현합니다.",
tags: ["Koscom", "REST API", "DB Server"],
statValue: "API",
statLabel: "국내외 지수 데이터 연계 및 송출",
bullets: [
"코스콤 데이터와 해외 지수 연계 데이터베이스 서버 구축",
"실시간 그래픽 연계 데이터 송수신 프로그램 개발",
@@ -45,9 +63,15 @@ const caseData = {
]
},
ai: {
kicker: "2.4 AI 학습 솔루션",
kicker: "2.4 AI Vision",
title: "AI 기반 거리 측정 시스템",
client: "KPGA",
field: "AI Learning",
status: "AI RESULT",
description: "KPGA 공식 기록에 사용되는 그린 홀 컵 간 거리 측정 솔루션을 개발했습니다.",
tags: ["CUDA", "Deep Learning", "Coordinate"],
statValue: "KPGA",
statLabel: "공식 기록 환경에 적용된 거리 측정 솔루션",
bullets: [
"CUDA 기반 딥러닝 모델 학습",
"카메라 영상 신호 수신 후 공 위치와 좌표 변환",
@@ -60,9 +84,15 @@ const caseData = {
]
},
shopping: {
kicker: "2.5 홈쇼핑 송출 시스템",
kicker: "2.5 Commerce Broadcast",
title: "홈쇼핑 자동화 송출 시스템 / 라이브 톡",
client: "Commerce Channel",
field: "Automation",
status: "CONTROL",
description: "송출 스케줄과 원격 제어, 모바일 연동을 포함한 홈쇼핑 방송 운영 시스템을 개발합니다.",
tags: ["Remote Control", "Mobile", "Schedule"],
statValue: "Automation",
statLabel: "송출 스케줄과 모바일 운영 흐름 연동",
bullets: [
"송출 스케줄 관리 데이터 송수신 모듈 개발",
"홈쇼핑 송출 프로그램 원격제어 로컬/모바일 연동",
@@ -75,9 +105,15 @@ const caseData = {
]
},
sports: {
kicker: "2.6 스포츠 TV Graphic",
title: "스포츠 / 게임 데이터 솔루션 송출 시스템 개발",
kicker: "2.6 Sports TV Graphic",
title: "스포츠 / 게임 데이터 솔루션 송출 시스템",
client: "Sports & Game",
field: "TV Graphic",
status: "UHD CG",
description: "Live Data를 자막시스템에서 사용 가능한 형태로 변환하고 다양한 종목의 TVG 시스템을 제작합니다.",
tags: ["HD/UHD", "International Signal", "Scorebug"],
statValue: "IS",
statLabel: "국제신호 제작과 고해상도 CG 송출 대응",
bullets: [
"HD/UHD급 CG 제작과 국제신호 제작",
"3D/2D Cut, 애니메이션 등 특수 효과 적용",
@@ -98,29 +134,44 @@ const menuToggle = document.querySelector(".menu-toggle");
const cursor = document.querySelector(".cursor");
const loaderBar = document.getElementById("loaderBar");
const loaderValue = document.getElementById("loaderValue");
const caseStage = document.querySelector(".case-stage");
const caseImage = document.getElementById("caseImage");
const caseKicker = document.getElementById("caseKicker");
const caseName = document.getElementById("caseName");
const caseDescription = document.getElementById("caseDescription");
const caseBullets = document.getElementById("caseBullets");
const caseThumbs = document.getElementById("caseThumbs");
const caseClient = document.getElementById("caseClient");
const caseField = document.getElementById("caseField");
const caseStatus = document.getElementById("caseStatus");
const caseTags = document.getElementById("caseTags");
const caseStatValue = document.getElementById("caseStatValue");
const caseStatLabel = document.getElementById("caseStatLabel");
const navLinks = [...document.querySelectorAll(".primary-nav a")];
let loaderProgress = 0;
const loaderTimer = window.setInterval(() => {
loaderProgress = Math.min(loaderProgress + Math.ceil(Math.random() * 12), 100);
loaderBar.style.width = `${loaderProgress}%`;
loaderValue.textContent = String(loaderProgress).padStart(2, "0");
if (loaderProgress >= 100) {
window.clearInterval(loaderTimer);
window.setTimeout(() => {
body.classList.remove("is-loading");
body.classList.add("is-ready");
}, 220);
}
}, 55);
const finishLoading = () => {
body.classList.remove("is-loading");
body.classList.add("is-ready");
};
if (loaderBar && loaderValue) {
const loaderTimer = window.setInterval(() => {
loaderProgress = Math.min(loaderProgress + Math.ceil(Math.random() * 12), 100);
loaderBar.style.width = `${loaderProgress}%`;
loaderValue.textContent = String(loaderProgress).padStart(2, "0");
if (loaderProgress >= 100) {
window.clearInterval(loaderTimer);
window.setTimeout(finishLoading, 220);
}
}, 55);
} else {
finishLoading();
}
const updateScrollState = () => {
if (!header) return;
const scrollTop = window.scrollY || document.documentElement.scrollTop;
const scrollable = document.documentElement.scrollHeight - window.innerHeight;
const progress = scrollable > 0 ? (scrollTop / scrollable) * 100 : 0;
@@ -158,6 +209,8 @@ const revealObserver = new IntersectionObserver(
document.querySelectorAll(".reveal").forEach(element => revealObserver.observe(element));
const setMenuOpen = open => {
if (!menuToggle || !mobilePanel) return;
menuToggle.setAttribute("aria-expanded", String(open));
menuToggle.setAttribute("aria-label", open ? "메뉴 닫기" : "메뉴 열기");
mobilePanel.classList.toggle("is-open", open);
@@ -173,32 +226,58 @@ if (menuToggle) {
});
}
mobilePanel.querySelectorAll("a").forEach(link => {
mobilePanel?.querySelectorAll("a").forEach(link => {
link.addEventListener("click", () => setMenuOpen(false));
});
const fillList = (element, items, tagName = "span") => {
if (!element) return;
const nodes = items.map(item => {
const node = document.createElement(tagName);
node.textContent = item;
return node;
});
element.replaceChildren(...nodes);
};
const renderCase = (key, imageIndex = 0) => {
const data = caseData[key];
const image = data.images[imageIndex];
if (!data || !caseImage) return;
const image = data.images[imageIndex] || data.images[0];
caseStage?.classList.add("is-switching");
window.setTimeout(() => caseStage?.classList.remove("is-switching"), 180);
caseImage.src = image.src;
caseImage.alt = image.alt;
caseKicker.textContent = data.kicker;
caseName.textContent = data.title;
caseDescription.textContent = data.description;
caseBullets.innerHTML = data.bullets.map(item => `<li>${item}</li>`).join("");
caseThumbs.innerHTML = data.images
.map(
(item, index) => `
<button type="button" class="${index === imageIndex ? "is-active" : ""}" data-case-thumb="${index}" aria-label="${item.alt}">
<img src="${item.src}" alt="">
</button>
`
)
.join("");
if (caseKicker) caseKicker.textContent = data.kicker;
if (caseName) caseName.textContent = data.title;
if (caseDescription) caseDescription.textContent = data.description;
if (caseClient) caseClient.textContent = data.client;
if (caseField) caseField.textContent = data.field;
if (caseStatus) caseStatus.textContent = data.status;
if (caseStatValue) caseStatValue.textContent = data.statValue;
if (caseStatLabel) caseStatLabel.textContent = data.statLabel;
fillList(caseTags, data.tags);
fillList(caseBullets, data.bullets, "li");
caseThumbs.querySelectorAll("button").forEach(button => {
button.addEventListener("click", () => renderCase(key, Number(button.dataset.caseThumb)));
});
if (caseThumbs) {
const buttons = data.images.map((item, index) => {
const button = document.createElement("button");
button.type = "button";
button.className = index === imageIndex ? "is-active" : "";
button.dataset.caseThumb = String(index);
button.setAttribute("aria-label", item.alt);
const thumb = document.createElement("img");
thumb.src = item.src;
thumb.alt = "";
button.appendChild(thumb);
button.addEventListener("click", () => renderCase(key, index));
return button;
});
caseThumbs.replaceChildren(...buttons);
}
};
document.querySelectorAll(".case-tabs button").forEach(button => {