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

View File

@@ -8,7 +8,7 @@
name="description" name="description"
content="컴트로피 회사소개 웹페이지. 방송 그래픽, 실시간 데이터, AI 솔루션, 웹/앱 개발 역량 소개." content="컴트로피 회사소개 웹페이지. 방송 그래픽, 실시간 데이터, AI 솔루션, 웹/앱 개발 역량 소개."
> >
<title>컴트로피 | Total IT Service</title> <title>컴트로피 | Broadcast Data Graphics &amp; AI Solutions</title>
<script> <script>
document.documentElement.classList.remove("no-js"); document.documentElement.classList.remove("no-js");
document.documentElement.classList.add("js"); document.documentElement.classList.add("js");
@@ -48,6 +48,7 @@
<a href="#about">회사</a> <a href="#about">회사</a>
<a href="#clients">고객사</a> <a href="#clients">고객사</a>
<a href="#services">사업</a> <a href="#services">사업</a>
<a href="#cases">사례</a>
<a href="#contact">문의</a> <a href="#contact">문의</a>
</nav> </nav>
<a class="header__contact" href="mailto:ecoropy@naver.com"> <a class="header__contact" href="mailto:ecoropy@naver.com">
@@ -66,7 +67,8 @@
<a href="#about"><span>01</span>회사</a> <a href="#about"><span>01</span>회사</a>
<a href="#clients"><span>02</span>고객사</a> <a href="#clients"><span>02</span>고객사</a>
<a href="#services"><span>03</span>사업</a> <a href="#services"><span>03</span>사업</a>
<a href="#contact"><span>04</span>문의</a> <a href="#cases"><span>04</span>사례</a>
<a href="#contact"><span>05</span>문의</a>
</nav> </nav>
<p>서울특별시 금천구 서부샛길 606 대성지식산업센터 B동 1601호</p> <p>서울특별시 금천구 서부샛길 606 대성지식산업센터 B동 1601호</p>
</div> </div>
@@ -80,14 +82,22 @@
<div class="shell hero__inner"> <div class="shell hero__inner">
<div class="hero__copy reveal"> <div class="hero__copy reveal">
<p class="eyebrow">Company Introduction</p> <p class="eyebrow">Company Introduction</p>
<h1 id="heroTitle">고객과 함께<br>성장하는<br>Total IT Service</h1> <h1 id="heroTitle">COMTROPY</h1>
<p class="hero__subhead">Broadcast Data Graphics &amp; AI Solutions</p>
<p class="hero__lead"> <p class="hero__lead">
컴트로피는 실시간 방송 자막 송출 시스템, 방송 TV Graphic 솔루션, 방송 현장의 실시간 데이터를 안정적인 화면 언어로 바꾸는 개발 파트너.
웹/앱 구축과 AI 솔루션을 개발하는 IT 솔루션 전문기업입니다. 컴트로피는 선거, 스포츠, 경제, 홈쇼핑, AI 영상 분석까지 운영 흐름에 맞춘
송출 시스템과 그래픽 솔루션을 만듭니다.
</p> </p>
<div class="hero__proof" aria-label="핵심 검증 분야">
<span>선관위 실시간 연동</span>
<span>KPGA 공식 기록</span>
<span>HD/UHD TV Graphic</span>
<span>Unreal / Unity</span>
</div>
<div class="hero__actions"> <div class="hero__actions">
<a class="button button--hot" href="#services"> <a class="button button--hot" href="#cases">
<span>주요 사업 보기</span> <span>프로젝트 보기</span>
<i data-lucide="arrow-up-right"></i> <i data-lucide="arrow-up-right"></i>
</a> </a>
<a class="button" href="#contact"> <a class="button" href="#contact">
@@ -97,20 +107,36 @@
</div> </div>
</div> </div>
<aside class="hero__console reveal" data-delay="1" aria-label="핵심 역량"> <aside class="hero__console reveal" data-delay="1" aria-label="컴트로피 핵심 솔루션 화면">
<div class="console__top"> <div class="console__top">
<span>LIVE SYSTEM</span> <span>COMMAND CENTER</span>
<strong>ON AIR</strong> <strong>ON AIR</strong>
</div> </div>
<div class="hero__metrics" aria-label="주요 역량">
<div>
<strong>LIVE</strong>
<span>Data Interface</span>
</div>
<div>
<strong>CG</strong>
<span>Broadcast Output</span>
</div>
<div>
<strong>AI</strong>
<span>Vision Solution</span>
</div>
</div>
<div class="hero__screen">
<img src="assets/media/election-candidate-screen.webp" alt="선거방송 후보자 득표 그래픽 화면">
</div>
<div class="signal-grid"> <div class="signal-grid">
<span>Election</span> <span>Election</span>
<span>Home Shopping</span> <span>Sports TVG</span>
<span>Finance</span> <span>Finance</span>
<span>Sports</span> <span>Shopping</span>
<span>Virtual Graphic</span> <span>Virtual Graphic</span>
<span>AI Vision</span> <span>AI Vision</span>
</div> </div>
<img src="assets/media/election-candidate-screen.webp" alt="선거방송 후보자 득표 그래픽 화면">
</aside> </aside>
</div> </div>
</section> </section>
@@ -133,13 +159,13 @@
<div class="shell about__grid"> <div class="shell about__grid">
<div class="section-heading reveal"> <div class="section-heading reveal">
<p class="eyebrow">01 / About</p> <p class="eyebrow">01 / About</p>
<h2 id="aboutTitle">고객 감동 실현을 기업의 사명으로 삼습니다.</h2> <h2 id="aboutTitle">방송사의 기술 연구소 역할을 수행하는 개발 파트너</h2>
</div> </div>
<div class="about__body reveal" data-delay="1"> <div class="about__body reveal" data-delay="1">
<p> <p>
최고의 기술력과 서비스를 지향하며 고객 중심의 사업경쟁력을 강화합니다. 컴트로피는 실시간 방송 자막 송출 시스템, 방송 TV Graphic 솔루션,
핵심 서비스 분야의 품질을 보장하고, 고객의 가치 향상을 극대화할 수 웹/앱 구축과 AI 솔루션을 개발하는 IT 솔루션 전문기업입니다.
있도록 끊임없이 개선합니다. 현장의 속도와 안정성을 이해하고, 운영자가 믿고 다룰 수 있는 시스템을 설계합니다.
</p> </p>
<div class="value-strip" aria-label="핵심 가치"> <div class="value-strip" aria-label="핵심 가치">
<strong>Best People</strong> <strong>Best People</strong>
@@ -176,17 +202,20 @@
</div> </div>
<div class="intro-band__copy reveal" data-delay="1"> <div class="intro-band__copy reveal" data-delay="1">
<p class="eyebrow">Company Profile</p> <p class="eyebrow">Company Profile</p>
<h2>방송사의 기술 연구소 역할을 대행하는 개발 파트너</h2> <h2>실시간 데이터와 화면 출력 사이를 책임집니다.</h2>
<p> <p>
선거방송, 증권, 홈쇼핑, 스포츠와 게임 방송의 자막 송출 솔루션을 선거방송, 증권, 홈쇼핑, 스포츠와 게임 방송의 자막 송출 솔루션을 제공하고,
제공하고, 실시간 데이터 기반 방송 그래픽과 AI 솔루션, 웹 사이트 구축 데이터 기반 방송 그래픽과 AI 솔루션, 웹 사이트 구축 역량을 강화하고 있습니다.
사업역량을 강화하고 있습니다.
</p> </p>
<dl class="profile-list"> <dl class="profile-list">
<div> <div>
<dt>사업 분야</dt> <dt>사업 분야</dt>
<dd>소프트웨어 개발 및 공급, 방송 TV Graphic 솔루션, 웹 콘텐츠 개발</dd> <dd>소프트웨어 개발 및 공급, 방송 TV Graphic 솔루션, 웹 콘텐츠 개발</dd>
</div> </div>
<div>
<dt>핵심 기술</dt>
<dd>실시간 데이터 연동, 송출 제어, API 서버, AI 영상 분석, VR/AR 그래픽</dd>
</div>
<div> <div>
<dt>위치</dt> <dt>위치</dt>
<dd>서울특별시 금천구 서부샛길 606 대성지식산업센터 B동 1601호</dd> <dd>서울특별시 금천구 서부샛길 606 대성지식산업센터 B동 1601호</dd>
@@ -197,9 +226,15 @@
</section> </section>
<section class="section clients" id="clients" aria-labelledby="clientsTitle"> <section class="section clients" id="clients" aria-labelledby="clientsTitle">
<div class="shell section-heading section-heading--wide reveal"> <div class="shell clients__head reveal">
<p class="eyebrow">02 / Clients</p> <div class="section-heading section-heading--wide">
<h2 id="clientsTitle">방송, 스포츠, 커머스, 게임 산업의 현장과 함께했습니다.</h2> <p class="eyebrow">02 / Clients</p>
<h2 id="clientsTitle">방송, 스포츠, 커머스, 게임 산업의 현장과 함께했습니다.</h2>
</div>
<p class="client-note">
공공 방송부터 스포츠 중계, 홈쇼핑, 게임 방송까지 다양한 운영 환경에서
안정성과 정확도가 필요한 화면 시스템을 구축했습니다.
</p>
</div> </div>
<div class="shell logo-grid reveal" data-delay="1"> <div class="shell logo-grid reveal" data-delay="1">
<figure><img src="assets/media/client-01-kpga.png" alt="KPGA"></figure> <figure><img src="assets/media/client-01-kpga.png" alt="KPGA"></figure>
@@ -229,46 +264,70 @@
<div class="shell service-grid"> <div class="shell service-grid">
<article class="service-card reveal"> <article class="service-card reveal">
<span>01</span> <div class="service-card__top">
<span>01</span>
<i data-lucide="captions"></i>
</div>
<h3>방송 그래픽 자막 송출 솔루션</h3> <h3>방송 그래픽 자막 송출 솔루션</h3>
<p>선거방송, 홈쇼핑, 증권, 스포츠와 게임 데이터를 방송 그래픽으로 연결합니다.</p> <p>선거방송, 홈쇼핑, 증권, 스포츠와 게임 데이터를 방송 그래픽으로 연결합니다.</p>
<div class="service-card__tags"><span>CG</span><span>Live Data</span><span>Output</span></div>
</article> </article>
<article class="service-card reveal" data-delay="1"> <article class="service-card reveal" data-delay="1">
<span>02</span> <div class="service-card__top">
<span>02</span>
<i data-lucide="satellite-dish"></i>
</div>
<h3>국제신호 제작</h3> <h3>국제신호 제작</h3>
<p>아시안게임, 동남아시안게임, 월드컵 등 국제경기 중계의 IS 제작 경험을 보유합니다.</p> <p>아시안게임, 동남아시안게임, 월드컵 등 국제경기 중계의 IS 제작 경험을 보유합니다.</p>
<div class="service-card__tags"><span>IS</span><span>HD/UHD</span><span>Sports</span></div>
</article> </article>
<article class="service-card reveal" data-delay="2"> <article class="service-card reveal" data-delay="2">
<span>03</span> <div class="service-card__top">
<span>03</span>
<i data-lucide="scan-search"></i>
</div>
<h3>AI 솔루션 연구 개발</h3> <h3>AI 솔루션 연구 개발</h3>
<p>딥러닝 기반 영상 분석과 거리 측정 시스템 등 현장형 AI 솔루션을 개발합니다.</p> <p>딥러닝 기반 영상 분석과 거리 측정 시스템 등 현장형 AI 솔루션을 개발합니다.</p>
<div class="service-card__tags"><span>CUDA</span><span>Vision</span><span>Tracking</span></div>
</article> </article>
<article class="service-card reveal"> <article class="service-card reveal">
<span>04</span> <div class="service-card__top">
<span>04</span>
<i data-lucide="database-zap"></i>
</div>
<h3>데이터베이스 시스템 구축</h3> <h3>데이터베이스 시스템 구축</h3>
<p>실시간 데이터 연계, API, 데이터 송수신 프로그램과 방송 송출 서버를 설계합니다.</p> <p>실시간 데이터 연계, API, 데이터 송수신 프로그램과 방송 송출 서버를 설계합니다.</p>
<div class="service-card__tags"><span>API</span><span>DB</span><span>Scheduler</span></div>
</article> </article>
<article class="service-card reveal" data-delay="1"> <article class="service-card reveal" data-delay="1">
<span>05</span> <div class="service-card__top">
<span>05</span>
<i data-lucide="panel-top"></i>
</div>
<h3>웹 사이트 / 앱 개발</h3> <h3>웹 사이트 / 앱 개발</h3>
<p>방송 운영 시스템과 연동되는 웹 페이지, 모바일 앱, 관리 도구를 구현합니다.</p> <p>방송 운영 시스템과 연동되는 웹 페이지, 모바일 앱, 관리 도구를 구현합니다.</p>
<div class="service-card__tags"><span>Web</span><span>Mobile</span><span>Admin</span></div>
</article> </article>
<article class="service-card reveal" data-delay="2"> <article class="service-card reveal" data-delay="2">
<span>06</span> <div class="service-card__top">
<span>06</span>
<i data-lucide="box"></i>
</div>
<h3>버추얼 그래픽 시스템</h3> <h3>버추얼 그래픽 시스템</h3>
<p>Unreal Engine, Unity 기반 VR/AR 시스템과 실시간 3D 그래픽 처리를 제공합니다.</p> <p>Unreal Engine, Unity 기반 VR/AR 시스템과 실시간 3D 그래픽 처리를 제공합니다.</p>
<div class="service-card__tags"><span>VR/AR</span><span>3D</span><span>Unity</span></div>
</article> </article>
</div> </div>
</section> </section>
<section class="section case-lab" aria-labelledby="caseTitle"> <section class="section case-lab" id="cases" aria-labelledby="caseTitle">
<div class="shell case-lab__head reveal"> <div class="shell case-lab__head reveal">
<div> <div>
<p class="eyebrow">Project Fields</p> <p class="eyebrow">04 / Project Fields</p>
<h2 id="caseTitle">현장에서 검증된 여섯 가지 솔루션</h2> <h2 id="caseTitle">현장에서 검증된 여섯 가지 솔루션</h2>
</div> </div>
<p> <p>
데이터 연동, 그래픽 송출, AI 분석, 운영 UI까지 현장 흐름에 맞춰 설계합니다. 데이터 연동, 그래픽 송출, AI 분석, 운영 UI까지 실제 방송 흐름에 맞춰 설계합니다.
</p> </p>
</div> </div>
@@ -296,15 +355,28 @@
<article class="case-stage reveal" data-delay="1" aria-live="polite"> <article class="case-stage reveal" data-delay="1" aria-live="polite">
<div class="case-stage__media"> <div class="case-stage__media">
<div class="case-stage__bar" aria-hidden="true">
<span></span><span></span><span></span>
<strong id="caseStatus">LIVE READY</strong>
</div>
<img id="caseImage" src="assets/media/election-candidate-screen.webp" alt="선거방송 후보자 득표 그래픽 화면"> <img id="caseImage" src="assets/media/election-candidate-screen.webp" alt="선거방송 후보자 득표 그래픽 화면">
<div class="case-stage__thumbs" id="caseThumbs"></div> <div class="case-stage__thumbs" id="caseThumbs"></div>
</div> </div>
<div class="case-stage__copy"> <div class="case-stage__copy">
<p class="case-stage__kicker" id="caseKicker">2.1 선거방송</p> <p class="case-stage__kicker" id="caseKicker">2.1 선거방송</p>
<div class="case-stage__meta" aria-label="프로젝트 정보">
<span id="caseClient">Election Broadcast</span>
<span id="caseField">Real-time CG</span>
</div>
<h3 id="caseName">선거방송 데이터 방송 그래픽 솔루션 개발</h3> <h3 id="caseName">선거방송 데이터 방송 그래픽 솔루션 개발</h3>
<p id="caseDescription"> <p id="caseDescription">
선관위 데이터와 방송 그래픽을 실시간으로 연동해 자막 송출 시스템을 구성합니다. 선관위 데이터와 방송 그래픽을 실시간으로 연동해 자막 송출 시스템을 구성합니다.
</p> </p>
<div class="case-stage__chips" id="caseTags" aria-label="핵심 기술"></div>
<div class="case-stat" aria-label="대표 성과">
<strong id="caseStatValue">Real-time</strong>
<span id="caseStatLabel">선관위 데이터와 방송 그래픽 연동</span>
</div>
<ul id="caseBullets"></ul> <ul id="caseBullets"></ul>
</div> </div>
</article> </article>
@@ -334,7 +406,7 @@
<section class="section contact" id="contact" aria-labelledby="contactTitle"> <section class="section contact" id="contact" aria-labelledby="contactTitle">
<div class="shell contact__grid"> <div class="shell contact__grid">
<div class="contact__copy reveal"> <div class="contact__copy reveal">
<p class="eyebrow">04 / Contact</p> <p class="eyebrow">05 / Contact</p>
<h2 id="contactTitle">방송 현장의 데이터를 안정적인 화면으로 연결합니다.</h2> <h2 id="contactTitle">방송 현장의 데이터를 안정적인 화면으로 연결합니다.</h2>
<p> <p>
실시간 송출 시스템, 방송 그래픽, AI 영상 분석, 웹/앱 개발이 필요한 프로젝트를 실시간 송출 시스템, 방송 그래픽, AI 영상 분석, 웹/앱 개발이 필요한 프로젝트를

157
script.js
View File

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

View File

@@ -260,6 +260,7 @@ html.no-js .loader {
} }
body.is-ready .loader { body.is-ready .loader {
display: none;
visibility: hidden; visibility: hidden;
opacity: 0; opacity: 0;
pointer-events: none; pointer-events: none;
@@ -1218,6 +1219,312 @@ body.is-ready .loader {
font-size: 1.6rem; font-size: 1.6rem;
} }
.primary-nav {
gap: 26px;
}
.hero__subhead {
max-width: 740px;
margin: 14px 0 0;
color: var(--signal);
font-family: var(--mono);
font-size: clamp(0.88rem, 1.2vw, 1.08rem);
font-weight: 600;
text-transform: uppercase;
}
.hero__proof {
display: flex;
flex-wrap: wrap;
gap: 8px;
max-width: 730px;
margin-top: 24px;
}
.hero__proof span {
display: inline-flex;
align-items: center;
min-height: 34px;
padding: 0 12px;
border: 1px solid rgba(241, 238, 230, 0.18);
border-radius: 999px;
background: rgba(7, 7, 6, 0.32);
color: rgba(241, 238, 230, 0.82);
font-family: var(--mono);
font-size: 0.72rem;
font-weight: 600;
}
.hero__metrics {
display: grid;
grid-template-columns: repeat(3, 1fr);
border-bottom: 1px solid var(--line);
background: rgba(255, 255, 255, 0.035);
}
.hero__metrics div {
min-height: 88px;
display: grid;
align-content: center;
gap: 4px;
padding: 18px;
border-right: 1px solid var(--line);
}
.hero__metrics div:last-child {
border-right: 0;
}
.hero__metrics strong {
color: var(--paper);
font-family: var(--display);
font-size: 2.05rem;
line-height: 0.9;
}
.hero__metrics span {
color: var(--muted);
font-family: var(--mono);
font-size: 0.68rem;
font-weight: 600;
text-transform: uppercase;
}
.hero__screen {
position: relative;
background: #050505;
}
.hero__screen::before {
content: "";
position: absolute;
inset: 12px;
pointer-events: none;
border: 1px solid rgba(200, 255, 73, 0.28);
box-shadow: inset 0 0 0 1px rgba(7, 7, 6, 0.4);
}
.clients__head {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(280px, 430px);
gap: 56px;
align-items: end;
}
.client-note {
margin: 0;
color: var(--paper-2);
word-break: keep-all;
}
.logo-grid figure {
position: relative;
background: #e7e1d6;
overflow: hidden;
transition: background 0.3s var(--ease), transform 0.3s var(--ease);
}
.logo-grid figure::after {
content: "";
position: absolute;
inset: 0;
pointer-events: none;
background:
linear-gradient(90deg, rgba(7, 7, 6, 0.06) 1px, transparent 1px) 0 0 / 28px 28px,
linear-gradient(180deg, rgba(7, 7, 6, 0.06) 1px, transparent 1px) 0 0 / 28px 28px;
opacity: 0;
transition: opacity 0.3s var(--ease);
}
.logo-grid figure:hover {
background: var(--paper);
transform: translateY(-3px);
}
.logo-grid figure:hover::after {
opacity: 1;
}
.logo-grid img {
position: relative;
z-index: 1;
filter: grayscale(1) saturate(0) contrast(0.92);
opacity: 0.72;
transition: filter 0.35s var(--ease), opacity 0.35s var(--ease), transform 0.35s var(--ease);
}
.logo-grid figure:hover img {
filter: none;
opacity: 1;
transform: scale(1.04);
}
.service-card {
position: relative;
display: flex;
flex-direction: column;
overflow: hidden;
}
.service-card::after {
content: "";
position: absolute;
inset: auto 24px 0 24px;
height: 3px;
background: linear-gradient(90deg, var(--hot), var(--signal));
transform: scaleX(0);
transform-origin: left;
transition: transform 0.34s var(--ease);
}
.service-card:hover::after {
transform: scaleX(1);
}
.service-card__top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 18px;
}
.service-card__top svg {
width: 28px;
height: 28px;
color: rgba(241, 238, 230, 0.78);
stroke-width: 1.7;
}
.service-card__tags {
display: flex;
flex-wrap: wrap;
gap: 7px;
margin-top: auto;
padding-top: 28px;
}
.service-card__tags span {
min-height: 28px;
display: inline-flex;
align-items: center;
padding: 0 9px;
border: 1px solid var(--line);
color: var(--paper-2);
font-size: 0.68rem;
text-transform: uppercase;
}
.case-stage {
box-shadow: 0 28px 90px rgba(0, 0, 0, 0.25);
}
.case-stage__bar {
position: absolute;
top: 14px;
left: 14px;
right: 14px;
z-index: 2;
display: flex;
align-items: center;
gap: 7px;
min-height: 34px;
padding: 0 12px;
border: 1px solid rgba(241, 238, 230, 0.18);
border-radius: 4px;
background: rgba(7, 7, 6, 0.68);
backdrop-filter: blur(10px);
}
.case-stage__bar span {
width: 7px;
height: 7px;
border-radius: 999px;
background: var(--muted-dark);
}
.case-stage__bar span:first-child {
background: var(--hot-2);
}
.case-stage__bar span:nth-child(2) {
background: var(--signal);
}
.case-stage__bar strong {
margin-left: auto;
color: var(--paper-2);
font-family: var(--mono);
font-size: 0.68rem;
font-weight: 600;
}
.case-stage__media > img {
transition: opacity 0.18s var(--ease), transform 0.18s var(--ease);
}
.case-stage.is-switching .case-stage__media > img {
opacity: 0.42;
transform: scale(0.992);
}
.case-stage__meta {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 16px;
}
.case-stage__meta span,
.case-stage__chips span {
display: inline-flex;
align-items: center;
min-height: 28px;
padding: 0 9px;
border: 1px solid var(--line);
color: var(--paper-2);
font-family: var(--mono);
font-size: 0.68rem;
font-weight: 600;
text-transform: uppercase;
}
.case-stage__chips {
display: flex;
flex-wrap: wrap;
gap: 7px;
margin-top: 22px;
}
.case-stage__chips span {
border-color: rgba(200, 255, 73, 0.24);
color: var(--signal);
}
.case-stat {
display: grid;
grid-template-columns: minmax(96px, 0.36fr) 1fr;
gap: 18px;
align-items: center;
margin-top: 22px;
padding: 18px;
border: 1px solid var(--line);
background: rgba(7, 7, 6, 0.24);
}
.case-stat strong {
color: var(--paper);
font-family: var(--display);
font-size: 1.78rem;
line-height: 0.95;
text-transform: uppercase;
}
.case-stat span {
color: var(--muted);
font-size: 0.9rem;
word-break: keep-all;
}
html.js .reveal { html.js .reveal {
opacity: 0; opacity: 0;
transform: translateY(34px); transform: translateY(34px);
@@ -1257,6 +1564,11 @@ html.js .reveal[data-delay="2"] {
font-size: 5.2rem; font-size: 5.2rem;
} }
.clients__head {
grid-template-columns: 1fr;
gap: 26px;
}
.hero__inner, .hero__inner,
.about__grid, .about__grid,
.intro-band__grid, .intro-band__grid,
@@ -1346,6 +1658,15 @@ html.js .reveal[data-delay="2"] {
font-size: 1rem; font-size: 1rem;
} }
.hero__proof {
margin-top: 20px;
}
.hero__proof span {
min-height: 32px;
font-size: 0.68rem;
}
.hero__actions { .hero__actions {
align-items: stretch; align-items: stretch;
} }
@@ -1417,6 +1738,17 @@ html.js .reveal[data-delay="2"] {
padding: 28px; padding: 28px;
} }
.case-stage__bar {
top: 10px;
left: 10px;
right: 10px;
}
.case-stat {
grid-template-columns: 1fr;
gap: 8px;
}
.case-stage h3 { .case-stage h3 {
font-size: 1.55rem; font-size: 1.55rem;
} }
@@ -1452,6 +1784,15 @@ html.js .reveal[data-delay="2"] {
display: none; display: none;
} }
.hero__subhead {
font-size: 0.82rem;
}
.hero__proof {
display: grid;
grid-template-columns: 1fr;
}
.mission-panel, .mission-panel,
.service-card { .service-card {
padding: 24px; padding: 24px;