commit ecc3807cfa66e873af8eb4279cea2431fbc3e2a6 Author: zenn Date: Wed Sep 30 11:12:10 2026 +0900 트론트 홈페이지와 포트폴리오 초기 구성 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0ba9f3f --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +# macOS 및 로컬 편집기 설정 +.DS_Store +.vscode/ + +# 로컬 환경 설정 및 임시 파일 +.env +.env.* +!.env.example +*.log diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..9ad4e4b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,3 @@ +# 작업 규칙 + +- Git 커밋 제목과 본문은 항상 한국어로 작성합니다. diff --git a/README.md b/README.md new file mode 100644 index 0000000..081c603 --- /dev/null +++ b/README.md @@ -0,0 +1,94 @@ +# TRONT 회사 홈페이지 + +`docs/(주)트론트_회사소개서_ver.1.2.0_20251202_압축.pdf`를 기반으로 만든 한국어 반응형 회사 홈페이지입니다. HTML, CSS, JavaScript만 사용하며 프레임워크, 패키지 설치, 빌드 과정이 필요하지 않습니다. + +## 실행 + +`index.html`을 브라우저에서 열면 됩니다. 로컬 서버를 사용하려면 프로젝트 폴더에서 다음 명령을 실행합니다. + +```sh +python3 -m http.server 8000 --bind 127.0.0.1 +``` + +이후 `http://127.0.0.1:8000`에 접속합니다. + +## 파일 + +- `index.html`: 회사 소개, 사업 영역, 프로젝트 8개, 연혁, 문의 정보 +- `styles.css`: 색상, 타이포그래피, 반응형 레이아웃 및 모션 +- `company.css`: 회사 개요·대표이사·조직도를 통합한 패널과 특허 영역. 기존 사업영역의 스타일과 동작은 유지합니다. +- `script.js`: Canvas 입체 그래픽, 프로젝트 필터, 연혁 탭, 메뉴, 스크롤 효과 +- `map-dialog.js`, `map-dialog.css`: 오시는 길 지도 모달, 닫기·뒤로가기 처리와 원래 스크롤 위치 복원 +- `icons.css`: 제공된 `assets/icons/arrow_right_alt.svg` 모양을 사용하는 공통 화살표 크기·방향·중앙 정렬. 파일로 직접 열어도 표시되도록 각 HTML의 인라인 심벌을 참조합니다. +- `portfolio.css`: 프로젝트별 표지 그래픽, 포트폴리오 전용 페이지, 반응형 레이아웃과 페이지 전환 +- `portfolio.js`: 포트폴리오 호버·스크롤 모션, 갤러리 탭, 읽기 진행률, 목록 복귀 +- `projects/`: 프로젝트 8개의 독립 HTML 페이지. 각 파일의 주소를 직접 열거나 공유할 수 있습니다. +- `assets/images/`: 로고, 특허증, 프로젝트 화면. 프로젝트 이미지는 `프로젝트명_01.png`, `프로젝트명_02.png` 형식이며 번호는 상세 갤러리 순서와 같습니다. 화면을 사용하는 목록 카드와 상세 표지는 같은 파일을 참조합니다. RF 자산관리·Signage·GPS는 독립 그래픽 표지를 사용하고, 제공 이미지는 상세 갤러리에 표시합니다. +- `assets/images/archive/`: 현재 화면에서 사용하지 않는 합본 이미지와 이전 버전. 원본 보관용이며 갤러리에 연결하지 않습니다. +- `docs/`: 원본 회사소개서, 소개서 다운로드 링크의 대상 + +## 연락처 교체 + +사용자 요청에 따라 아래 연락처는 **임시 정보**입니다. 실제 인물에게 연결되지 않도록 예약된 `.example` 이메일을 사용했습니다. + +- 이메일: `hello@tront.example` +- 전화번호: `053-000-0000` + +`script.js` 상단 `CONTACT`의 `email`, `phone`을 실제 정보로 바꾸고 `temporary`를 `false`로 변경합니다. JavaScript 비활성 환경에서도 같은 정보를 표시하도록 `index.html`의 이메일, 전화번호, `mailto:`, `tel:`도 함께 수정합니다. 실제 정보로 교체한 뒤 `.contact-placeholder`의 임시 연락처 문구를 삭제할 수 있습니다. + +문의 버튼은 메일 앱을 엽니다. 이메일 주소 복사는 보안 환경(HTTPS 또는 localhost)에서 작동하며, 사용할 수 없으면 주소를 선택해 수동 복사를 안내합니다. 서버가 없는 정적 사이트이므로 문의 접수나 이메일 발송을 수행하지 않습니다. + +## 콘텐츠 기준 + +- 설립일: 2008년 12월 1일 +- 대표이사: 김칠수 +- 직원 수: 소개서에 명시된 2025년 10월 기준 18명 +- 프로젝트와 개발 범위·수행 기간: 소개서 6~14페이지 +- 연혁: 소개서 2페이지 및 프로젝트 상세 페이지 +- 대표이사·조직: 회사소개서의 회사 개요와 조직도를 통합해 대표이사 김칠수, 기술지원팀, 개발1팀(Web·모바일 앱), 개발2팀(내비게이션)을 표시합니다. +- 특허: 소개서 4페이지에 수록된 특허증 2개를 별도 영역에서 소개합니다. 제10-0951362호(객체 가상 이미지 구현 방법), 제10-1099507호(액체 세제 자동 판매 장치)의 특허번호·출원일·등록일은 증서에 기재된 내용을 사용하며, 현재 권리 보유·유효 상태를 별도로 주장하지 않습니다. +- 사업 내용은 홈페이지에 맞게 재구성했으며, 프로젝트 성과 수치나 고객 추천사는 임의로 추가하지 않았습니다. + +### 프로젝트 상세 편집 기준 + +- 현재 프로젝트 8개의 근거 자료는 회사소개서 PDF의 프로젝트 수행 내역입니다. PPT의 제목·설명이 바뀌면 해당 원문을 기준으로 갱신합니다. +- 제목은 원문을 유지하고, 앱·브랜드명은 제목 위에 따로 표시합니다. 목록 카드와 다음 프로젝트 링크에도 같은 제목을 사용합니다. +- 기간은 원문의 날짜를 통일된 표기로 표시합니다. 개발 범위에서는 괄호 안 기술명을 분리하고, 플랫폼은 원문이나 화면에 확인되는 앱·웹·장비만 표시합니다. 확인되지 않은 iOS·Android 등은 추가하지 않습니다. +- 오버뷰 메인 타이틀만 프로젝트 성격에 맞게 작성합니다. 오른쪽 설명은 소개서 제목 바로 아래 문장을 그대로 사용합니다. +- 오버뷰 01 **서비스 대상**: 누구 또는 무엇을 위한 프로젝트인지 요약합니다. 02 **주요 기능**: 원문에 명시된 기능만 요약합니다. 03 **적용 기술**: 개발 범위에 명시된 기술과 사용한 부분을 문장으로 풀어 씁니다. 세 항목 모두 문장형 설명을 사용합니다. 성과·효과·미기재 기능은 덧붙이지 않습니다. +- 별도 `BUILT BY TRONT` 섹션과 회사소개서의 특정 페이지로 이동하는 프로젝트 링크는 두지 않습니다. + +| 프로젝트 | 갤러리 이미지 순서 | +| --- | --- | +| mobility | `mobility_01.png` 차량 내 HMI / `_02` 설정 / `_03` 지도 / `_04` 경치 좋은 길 | +| pharmacy | `pharmacy_01.png` 앱 메인 / `_02` 웹 메인 / `_03` 관리자 대시보드 | +| bems | `bems_01.png` 표준 대시보드 / `_02` 센서 정보 / `_03` EV 충전 | +| givon | `givon_01.png` 기부 프로젝트 / `_02` 단체 관리 / `_03` 봉사 프로젝트 | +| bispet | `bispet_01.png` 앱 메인 / `_02` 관리자 대시보드 | +| asset | `asset_01.png` 시스템 구성도 | +| signage | `signage_01.png` 콘텐츠 관리·분산 송출 시스템 구성도 | +| gps-building | `gps-building_01.png` GPS 위치확인 시스템 구성도 | + +`archive/bems_01.png`, `archive/givon_01.png`, `archive/bispet_01.png`는 기존 합본입니다. `archive/bispet_02.png`, `archive/bispet_03.png`는 교체 전 앱·관리자 이미지입니다. 화면 이미지를 교체할 때는 파일명과 함께 HTML의 `width`, `height`, 대체 텍스트·캡션도 확인합니다. + +실제 서비스 화면에 표현된 문구와 정보는 제공된 소개서의 이미지에 포함된 내용입니다. 공개 전 회사의 프로젝트 화면 공개 범위와 연락처를 확인해 주세요. + +## 동작 및 접근성 + +- 데스크톱, 태블릿, 모바일 레이아웃 +- 회사 개요·대표자·조직도 통합 배치, 독립 특허 영역과 증서 이미지 확대 보기 +- 분야별 프로젝트 필터와 독립 포트폴리오 페이지(기간·개발 범위·플랫폼, 개요와 기술, 프로젝트 화면, 다음 프로젝트) +- 키보드 화살표, Home, End를 지원하는 화면 갤러리와 원본 이미지 보기 +- 목록에서 포트폴리오를 열고 돌아오면 선택한 필터를 유지합니다. 브라우저 저장 기능이 없어도 기본 탐색은 가능합니다. +- 키보드 화살표, Home, End를 지원하는 연혁 탭 +- 사업 영역 아코디언: 높이 전환, 설명·태그 순차 등장, 선택 분야와 연동되는 도형 모션. 연속 클릭·전환 중 화면 크기 변경 시 진행 중인 모션을 정리합니다. +- 프로젝트 카드: 실제 개발 내용을 제목으로 표시하고, 프로젝트별 색상·타이포그래피·CSS 그래픽과 대표 화면을 조합합니다. 실제 개발 화면은 상세 페이지의 별도 갤러리에서 확인합니다. +- 지원 브라우저에서 페이지 간 전환 애니메이션, 포인터 반응, 스크롤에 따른 그래픽 이동 및 순차 등장 효과를 제공합니다. 모션 감소 설정에서는 움직임을 최소화합니다. +- 모바일 메뉴 +- ‘지도에서 보기’는 사이트 안의 모달로 열립니다. 구글 지도에 IT융합산업빌딩을 표시하며 닫기 버튼, 바깥 영역 클릭, 브라우저 뒤로가기로 닫을 수 있습니다. 지도는 모달을 열 때만 불러오며 인터넷 연결이 필요합니다. +- 모달 안의 ‘지도에서 열기’는 전체 지도를 새 탭에 열고 홈페이지는 유지합니다. +- 애니메이션 일시정지, 운영체제의 모션 감소 설정 지원 +- Canvas는 화면 밖이나 다른 탭에서 렌더링을 중지합니다. +- 본문 바로가기, 포커스 표시, 이미지 대체 텍스트, 의미 있는 HTML 구조 + +외부 CDN이나 폰트 라이브러리를 사용하지 않아 다운로드한 파일만으로 홈페이지 화면을 확인할 수 있습니다. 지도 모달은 외부 지도 서비스를 불러오며 이메일/전화 링크는 기기 앱으로 연결됩니다. diff --git a/assets/icons/arrow_right_alt.svg b/assets/icons/arrow_right_alt.svg new file mode 100644 index 0000000..c8dbd56 --- /dev/null +++ b/assets/icons/arrow_right_alt.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/archive/bems_01.png b/assets/images/archive/bems_01.png new file mode 100644 index 0000000..f2e60fc Binary files /dev/null and b/assets/images/archive/bems_01.png differ diff --git a/assets/images/archive/bispet_01.png b/assets/images/archive/bispet_01.png new file mode 100644 index 0000000..bf40acd Binary files /dev/null and b/assets/images/archive/bispet_01.png differ diff --git a/assets/images/archive/bispet_02.png b/assets/images/archive/bispet_02.png new file mode 100644 index 0000000..44633e3 Binary files /dev/null and b/assets/images/archive/bispet_02.png differ diff --git a/assets/images/archive/bispet_03.png b/assets/images/archive/bispet_03.png new file mode 100644 index 0000000..8e103df Binary files /dev/null and b/assets/images/archive/bispet_03.png differ diff --git a/assets/images/archive/givon_01.png b/assets/images/archive/givon_01.png new file mode 100644 index 0000000..1020976 Binary files /dev/null and b/assets/images/archive/givon_01.png differ diff --git a/assets/images/asset_01.png b/assets/images/asset_01.png new file mode 100644 index 0000000..f3c1c58 Binary files /dev/null and b/assets/images/asset_01.png differ diff --git a/assets/images/bems_01.png b/assets/images/bems_01.png new file mode 100644 index 0000000..3a8bd02 Binary files /dev/null and b/assets/images/bems_01.png differ diff --git a/assets/images/bems_02.png b/assets/images/bems_02.png new file mode 100644 index 0000000..bb96c20 Binary files /dev/null and b/assets/images/bems_02.png differ diff --git a/assets/images/bems_03.png b/assets/images/bems_03.png new file mode 100644 index 0000000..288f686 Binary files /dev/null and b/assets/images/bems_03.png differ diff --git a/assets/images/bispet_01.png b/assets/images/bispet_01.png new file mode 100644 index 0000000..3dc4b5c Binary files /dev/null and b/assets/images/bispet_01.png differ diff --git a/assets/images/bispet_02.png b/assets/images/bispet_02.png new file mode 100644 index 0000000..4f7dd69 Binary files /dev/null and b/assets/images/bispet_02.png differ diff --git a/assets/images/givon_01.png b/assets/images/givon_01.png new file mode 100644 index 0000000..f01611d Binary files /dev/null and b/assets/images/givon_01.png differ diff --git a/assets/images/givon_02.png b/assets/images/givon_02.png new file mode 100644 index 0000000..a27eaaf Binary files /dev/null and b/assets/images/givon_02.png differ diff --git a/assets/images/givon_03.png b/assets/images/givon_03.png new file mode 100644 index 0000000..86742e8 Binary files /dev/null and b/assets/images/givon_03.png differ diff --git a/assets/images/gps-building_01.png b/assets/images/gps-building_01.png new file mode 100644 index 0000000..e5dac0b Binary files /dev/null and b/assets/images/gps-building_01.png differ diff --git a/assets/images/mobility_01.png b/assets/images/mobility_01.png new file mode 100644 index 0000000..871ee1c Binary files /dev/null and b/assets/images/mobility_01.png differ diff --git a/assets/images/mobility_02.png b/assets/images/mobility_02.png new file mode 100644 index 0000000..fabe870 Binary files /dev/null and b/assets/images/mobility_02.png differ diff --git a/assets/images/mobility_03.png b/assets/images/mobility_03.png new file mode 100644 index 0000000..155b6a4 Binary files /dev/null and b/assets/images/mobility_03.png differ diff --git a/assets/images/mobility_04.png b/assets/images/mobility_04.png new file mode 100644 index 0000000..bb6a618 Binary files /dev/null and b/assets/images/mobility_04.png differ diff --git a/assets/images/patent-liquid-dispenser.png b/assets/images/patent-liquid-dispenser.png new file mode 100644 index 0000000..58d6dc9 Binary files /dev/null and b/assets/images/patent-liquid-dispenser.png differ diff --git a/assets/images/patent-virtual-image.png b/assets/images/patent-virtual-image.png new file mode 100644 index 0000000..4b0f546 Binary files /dev/null and b/assets/images/patent-virtual-image.png differ diff --git a/assets/images/pharmacy_01.png b/assets/images/pharmacy_01.png new file mode 100644 index 0000000..18bc503 Binary files /dev/null and b/assets/images/pharmacy_01.png differ diff --git a/assets/images/pharmacy_02.png b/assets/images/pharmacy_02.png new file mode 100644 index 0000000..d7c1b49 Binary files /dev/null and b/assets/images/pharmacy_02.png differ diff --git a/assets/images/pharmacy_03.png b/assets/images/pharmacy_03.png new file mode 100644 index 0000000..d7be7d9 Binary files /dev/null and b/assets/images/pharmacy_03.png differ diff --git a/assets/images/signage_01.png b/assets/images/signage_01.png new file mode 100644 index 0000000..00d8207 Binary files /dev/null and b/assets/images/signage_01.png differ diff --git a/assets/images/tront-logo.png b/assets/images/tront-logo.png new file mode 100644 index 0000000..bde5a67 Binary files /dev/null and b/assets/images/tront-logo.png differ diff --git a/company.css b/company.css new file mode 100644 index 0000000..0ef5b98 --- /dev/null +++ b/company.css @@ -0,0 +1,307 @@ +/* Company overview and patent archive. Expertise keeps its existing styles. */ +.about-intro h2 { font-size: clamp(33px, 3.2vw, 46px); letter-spacing: -2px; line-height: 1.4; } +.about-signature { display: flex; align-items: center; gap: 16px; margin-top: 35px; color: #7b8578; } +.about-signature > span:first-child { font-size: 35px; color: var(--blue); line-height: 1; } +.about-signature > span:last-child { font: 8px/1.8 Arial, sans-serif; letter-spacing: 1.2px; } +.about-copy > p:first-child { margin-top: 4px; } +@media (max-width: 850px) { + .about-layout { gap: 35px; } + .about-intro h2 { font-size: 33px; letter-spacing: -1.7px; } +} +@media (max-width: 600px) { + .about-layout { gap: 25px; } + .about-signature { margin-top: 20px; gap: 12px; } + .about-signature > span:first-child { font-size: 26px; } + .about-signature > span:last-child { font-size: 7px; } +} +.desktop-nav { + gap: clamp(20px, 2.4vw, 36px); + margin-left: clamp(0px, 3vw, 50px); +} + +.company-profile { + display: grid; + grid-template-columns: .95fr 1.25fr; + margin-top: 62px; + border: 1px solid var(--line); + border-radius: 4px; + background: #ffffff55; +} +.company-identity { padding: 35px 38px 30px; } +.company-profile .tiny-label { color: #69746b; font-size: 9px; letter-spacing: 1.3px; } +.company-identity h3 { margin: 19px 0 26px; font-size: 28px; letter-spacing: -1px; } +.company-identity h3 > span { + display: block; + margin-top: 7px; + font: 11px Arial, sans-serif; + letter-spacing: .5px; + color: var(--muted); +} +.company-data { margin: 0; font-size: 12px; line-height: 1.85; } +.company-data > div { display: grid; grid-template-columns: 62px 1fr; gap: 10px; margin-top: 12px; } +.company-data dt { color: var(--muted); } +.company-data dd { margin: 0; } +.company-patent-link { + display: flex; + align-items: center; + gap: 9px; + padding-top: 21px; + margin-top: 23px; + border-top: 1px solid var(--line); + font-size: 11px; + font-weight: 600; +} +.company-link-mark { color: var(--blue); font-size: 19px; line-height: 1; } +.company-patent-link > span:last-child { margin-left: auto; font-size: 19px; transition: transform .35s var(--ease); } +.company-patent-link:hover { color: var(--blue); } +.company-patent-link:hover > span:last-child { transform: translate(3px, -3px); } +.company-organization { + min-width: 0; + padding: 35px 38px 30px; + border-left: 1px solid var(--line); + background: radial-gradient(ellipse at 50% 42%, #2556e809, transparent 80%); +} +.organization-heading { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; } +.organization-heading h3 { font-size: 20px; letter-spacing: -.6px; margin-top: 11px; font-weight: 600; } +.organization-people { text-align: right; flex: none; } +.organization-people strong { font: 40px/.9 Arial, sans-serif; letter-spacing: -2px; } +.organization-people strong > span:last-child { font: 12px var(--font); letter-spacing: 0; margin-left: 5px; } +.organization-people small { display: block; font-size: 9px; color: var(--muted); margin-top: 9px; } +.organization-chart { margin-top: 28px; } +.organization-leader { + position: relative; + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 8px; + width: 218px; + max-width: 100%; + margin-inline: auto; + padding: 18px 20px; + background: var(--blue); + color: white; + border-radius: 3px; + box-shadow: 0 8px 24px #2556e816; +} +.organization-leader > span { font-size: 9px; letter-spacing: .1px; width: 100%; color: #d6dfff; } +.organization-leader > strong { font-size: 23px; letter-spacing: 2px; font-weight: 600; } +.organization-leader > i { font-size: 24px; font-style: normal; font-weight: 400; color: #bbcaff; } +.organization-leader::after { content: ""; position: absolute; top: 100%; left: 50%; width: 1px; height: 25px; background: #aebdd7; } +.organization-teams { + position: relative; + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 16px; + list-style: none; + margin: 25px 0 0; + padding: 0; +} +.organization-teams::before { + content: ""; + position: absolute; + top: 0; + left: calc((100% - 32px) / 6); + right: calc((100% - 32px) / 6); + height: 1px; + background: #aebdd7; + transform-origin: center; + transition: transform .85s var(--ease) .2s; +} +.organization-teams > li { position: relative; text-align: center; padding: 27px 0 13px; border-bottom: 1px solid #d9dfdc; } +.organization-teams > li::before { content: ""; position: absolute; top: 0; left: 50%; height: 17px; width: 1px; background: #aebdd7; } +.organization-teams > li::after { content: ""; position: absolute; left: calc(50% - 2px); top: 15px; width: 5px; height: 5px; border-radius: 50%; background: var(--blue); } +.team-index { font: 8px monospace; color: #8a94a4; } +.organization-teams h4 { font-size: 15px; letter-spacing: -.3px; margin: 7px 0 8px; font-weight: 600; } +.organization-teams p { font-size: 11px; line-height: 1.7; color: #737e76; } +.organization-teams > li:first-child p { font: 8px/1.8 Arial, sans-serif; letter-spacing: .2px; } +.js-ready .company-profile:not(.is-visible) .organization-teams::before { transform: scaleX(0); } + +.patents { background: #e9ede7; border-block: 1px solid #dde3d8; overflow: clip; } +.patents-heading { display: flex; justify-content: space-between; align-items: center; gap: 40px; margin-bottom: 46px; } +.patents-heading h2 > span { color: var(--blue); } +.patents-heading p { font-size: 14px; color: #687468; line-height: 1.9; margin-top: 24px; } +.patent-signature { display: flex; align-items: center; gap: 32px; padding-right: 50px; color: var(--blue); } +.patent-signature > span:last-child { font: 10px/1.8 Arial, sans-serif; letter-spacing: 1.2px; } +.patent-seal { position: relative; width: 110px; height: 110px; display: grid; place-items: center; } +.patent-seal i { position: absolute; inset: 10px; border: 1px solid #2556e844; border-radius: 5px; transition: transform 1.4s var(--ease); } +.patent-seal i:nth-child(1) { transform: rotate(0deg); } +.patent-seal i:nth-child(2) { transform: rotate(30deg); } +.patent-seal i:nth-child(3) { transform: rotate(60deg); } +.patent-seal b { font-size: 44px; font-weight: 400; line-height: 1; } +.js-ready .patents-heading:not(.is-visible) .patent-seal i { transform: rotate(-60deg) scale(.6); } +.patent-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 26px; } +.patent-card { + display: grid; + grid-template-columns: .95fr 1.15fr; + min-width: 0; + background: #f9faf6; + border: 1px solid #d9e0d4; + border-radius: 4px; + transition: border-color .4s, box-shadow .4s, opacity .85s var(--ease), transform .85s var(--ease); +} +.patent-document { + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 51px 27px 50px; + min-width: 0; + background-color: #e3e9dd; + background-image: linear-gradient(#bcc9b32b 1px, transparent 1px), linear-gradient(90deg, #bcc9b32b 1px, transparent 1px); + background-size: 25px 25px; + border-radius: 3px 0 0 3px; + overflow: hidden; +} +.patent-card:nth-child(2) .patent-document { background-color: #e0e7ee; background-image: linear-gradient(#adbdd02e 1px, transparent 1px), linear-gradient(90deg, #adbdd02e 1px, transparent 1px); } +.patent-document-label { position: absolute; top: 20px; left: 21px; font: 8px monospace; letter-spacing: 1.1px; color: #727d69; } +.patent-document img { + width: 100%; + height: auto; + max-height: 295px; + object-fit: contain; + box-shadow: 0 15px 20px -8px #27382126, 0 0 0 1px #25341c09; + transform: rotate(-4deg); + transition: transform .75s var(--ease), box-shadow .75s var(--ease); +} +.patent-card:nth-child(2) .patent-document img { transform: rotate(4deg); } +.patent-document-action { position: absolute; bottom: 18px; left: 21px; right: 21px; display: flex; justify-content: space-between; align-items: center; gap: 10px; font-size: 9px; color: #4f6050; } +.patent-document-action > span { font-size: 20px; transition: transform .35s var(--ease); } +.patent-information { padding: 29px 26px; min-width: 0; display: flex; flex-direction: column; } +.patent-kicker { display: flex; align-items: center; gap: 9px; color: var(--blue); font: 8px Arial, sans-serif; letter-spacing: .9px; white-space: nowrap; } +.patent-kicker > span { display: grid; place-items: center; width: 23px; height: 23px; flex: none; border: 1px solid #bcc9df; border-radius: 50%; font: 9px monospace; letter-spacing: 0; } +.patent-information h3 { font-size: clamp(20px, 1.85vw, 27px); letter-spacing: -.8px; line-height: 1.45; margin-top: 25px; } +.patent-title-line { display: block; } +.patent-description { font-size: 12px; line-height: 1.85; color: #727b70; margin: 16px 0 26px; } +.patent-data { font-size: 10px; margin: auto 0 0; padding-top: 18px; border-top: 1px solid #dce2d6; } +.patent-data > div { display: flex; gap: 12px; align-items: baseline; margin-bottom: 10px; } +.patent-data > div:last-child { margin-bottom: 0; } +.patent-data dt { color: #808a7b; width: 42px; flex: none; } +.patent-data dd { margin: 0; font-variant-numeric: tabular-nums; } +.patents-footnote { display: flex; justify-content: space-between; align-items: center; gap: 25px; margin-top: 27px; } +.patents-footnote > p { font-size: 10px; color: #7b8576; line-height: 1.7; } +.patents-footnote .text-link { font-size: 11px; flex: none; } +.patents-footnote .text-link > span { font-size: 19px; } +.patents-footnote .text-link:hover > span { transform: translate(2px, -2px); } + +@media (hover: hover) { + .patent-card:hover { border-color: #aabbc2; box-shadow: 0 14px 35px -20px #25382226; } + .patent-document:hover img { transform: rotate(0deg) translateY(-4px) scale(1.025); box-shadow: 0 23px 26px -12px #27382138; } + .patent-document:hover .patent-document-action > span { transform: translate(3px, -3px); } + .patents-heading:hover .patent-seal i:nth-child(1) { transform: rotate(60deg); } + .patents-heading:hover .patent-seal i:nth-child(2) { transform: rotate(90deg); } + .patents-heading:hover .patent-seal i:nth-child(3) { transform: rotate(120deg); } +} +.patent-document:focus-visible { z-index: 1; outline-offset: 4px; } +.patent-document:focus-visible img { transform: rotate(0deg) translateY(-4px); } + +@media (max-width: 1100px) { + .company-identity, .company-organization { padding: 28px; } + .company-profile { grid-template-columns: 1fr 1.15fr; } + .company-identity h3 { font-size: 25px; } + .company-data { font-size: 11px; } + .company-data > div { grid-template-columns: 50px 1fr; gap: 8px; } + .organization-heading h3 { font-size: 17px; } + .organization-heading { gap: 10px; } + .organization-people strong { font-size: 34px; } + .organization-people small { font-size: 8px; } + .organization-teams { gap: 12px; } + .organization-teams::before { left: calc((100% - 24px) / 6); right: calc((100% - 24px) / 6); } + .organization-teams h4 { font-size: 13px; } + .organization-teams p { font-size: 10px; } + .organization-teams > li:first-child p { font-size: 7px; } + .patent-card { grid-template-columns: 1fr; } + .patent-document { height: 280px; padding: 38px 40px 44px; border-radius: 3px 3px 0 0; } + .patent-document img { width: auto; height: 100%; max-height: none; } + .patent-information { padding: 27px 30px 30px; } + .patent-information h3 { margin-top: 18px; font-size: 25px; } + .patent-title-line { display: inline; } + .patent-description { margin-bottom: 23px; } + .patent-data { display: grid; grid-template-columns: repeat(2, 1fr); column-gap: 15px; row-gap: 13px; } + .patent-data > div { display: block; margin: 0; } + .patent-data > div:first-child { grid-column: 1 / -1; display: flex; } + .patent-data dt { width: auto; } + .patent-data dd { margin-top: 5px; } + .patent-data > div:first-child dd { margin-top: 0; } + .patent-signature { padding-right: 15px; gap: 20px; } +} +@media (max-width: 850px) { + .company-profile { margin-top: 45px; grid-template-columns: 1fr; } + .company-identity { display: grid; grid-template-columns: 1fr 1.3fr; column-gap: 32px; padding: 28px 32px; } + .company-identity > .tiny-label { grid-column: 1; } + .company-identity h3 { grid-column: 1; margin: 14px 0 0; } + .company-data { grid-column: 2; grid-row: 1 / span 3; font-size: 11px; } + .company-data > div:first-child { margin-top: 0; } + .company-patent-link { grid-column: 1; margin-top: 18px; padding-top: 15px; font-size: 10px; } + .company-organization { border-left: 0; border-top: 1px solid var(--line); padding: 27px 32px 30px; } + .organization-heading h3 { font-size: 20px; } + .organization-teams h4 { font-size: 15px; } + .organization-teams p { font-size: 11px; } + .organization-teams > li:first-child p { font-size: 8px; } + .organization-chart { max-width: 550px; margin-inline: auto; } + .patents-heading h2 { font-size: 39px; } + .patents-heading p { font-size: 12px; margin-top: 20px; } + .patent-signature > span:last-child { display: none; } + .patent-grid { gap: 20px; } + .patent-information { padding: 25px; } + .patent-information h3 { font-size: 22px; } + .patent-description { font-size: 11px; } +} +@media (max-width: 600px) { + .company-profile { margin-top: 36px; } + .company-identity { display: block; padding: 25px 23px; } + .company-profile .tiny-label { font-size: 8px; letter-spacing: 1px; } + .company-identity h3 { margin: 17px 0 22px; font-size: 25px; } + .company-identity h3 > span { font-size: 10px; } + .company-data { font-size: 11px; } + .company-data > div { grid-template-columns: 47px 1fr; gap: 10px; margin-top: 11px; } + .company-patent-link { margin-top: 22px; padding-top: 17px; font-size: 11px; } + .company-organization { padding: 26px 23px 25px; } + .organization-heading h3 { font-size: 17px; margin-top: 10px; } + .organization-people strong { font-size: 33px; } + .organization-people strong > span:last-child { font-size: 11px; } + .organization-people small { font-size: 7px; } + .organization-chart { margin-top: 25px; } + .organization-leader { width: 190px; padding: 15px 18px; } + .organization-leader > strong { font-size: 21px; } + .organization-teams { gap: 10px; } + .organization-teams::before { left: calc((100% - 20px) / 6); right: calc((100% - 20px) / 6); } + .organization-teams h4 { font-size: 13px; white-space: nowrap; } + .organization-teams p { font-size: 10px; } + .organization-teams > li:first-child p { font-size: 6px; letter-spacing: 0; } + .patents-heading { position: relative; gap: 0; margin-bottom: 32px; } + .patents-heading h2 { font-size: 36px; letter-spacing: -1.7px; } + .patents-heading p { font-size: 12px; position: relative; z-index: 1; } + .patent-signature { position: absolute; right: -5px; top: 7px; padding: 0; opacity: .55; } + .patent-seal { width: 65px; height: 65px; } + .patent-seal i { inset: 7px; border-radius: 2px; } + .patent-seal b { font-size: 26px; } + .patent-grid { grid-template-columns: 1fr; gap: 24px; } + .patent-document { height: 284px; padding-top: 32px; padding-bottom: 42px; } + .patent-information { padding: 24px; } + .patent-information h3 { font-size: 24px; margin-top: 16px; } + .patent-description { font-size: 12px; line-height: 1.8; margin-top: 12px; } + .patent-data { font-size: 11px; } + .patents-footnote { align-items: flex-start; flex-direction: column; gap: 16px; margin-top: 23px; } + .patents-footnote > p { font-size: 9px; } +} +@media (max-width: 360px) { + .company-identity, .company-organization { padding-inline: 19px; } + .organization-heading h3 { font-size: 15px; } + .organization-heading .tiny-label { font-size: 7px; letter-spacing: .65px; } + .organization-people strong { font-size: 29px; } + .organization-teams h4 { font-size: 12px; } + .organization-teams p { font-size: 9px; } + .organization-teams > li:first-child p { font-size: 5px; } + .patents-heading h2 { font-size: 32px; } + .patents-heading p { font-size: 11px; } + .patent-signature { right: -6px; top: 4px; } + .patent-seal { width: 51px; height: 51px; } + .patent-information { padding: 22px; } + .patent-information h3 { font-size: 22px; } +} +@media (prefers-reduced-motion: reduce) { + .company-profile *, .company-profile *::before, .patents * { transition-duration: .01ms !important; transition-delay: 0s !important; } + .patent-document img, .patent-document:hover img, .patent-document:focus-visible img, .patent-card:nth-child(2) .patent-document img { transform: none; } +} diff --git a/docs/(주)트론트_회사소개서_ver.1.2.0_20251202_압축.pdf b/docs/(주)트론트_회사소개서_ver.1.2.0_20251202_압축.pdf new file mode 100644 index 0000000..0e5a112 Binary files /dev/null and b/docs/(주)트론트_회사소개서_ver.1.2.0_20251202_압축.pdf differ diff --git a/icons.css b/icons.css new file mode 100644 index 0000000..213372c --- /dev/null +++ b/icons.css @@ -0,0 +1,38 @@ +/* The shared inline symbol uses the supplied arrow_right_alt.svg path. + Inline references also work when a page is opened directly from disk. */ +.icon-definitions { position: absolute; width: 0; height: 0; overflow: hidden; pointer-events: none; } +.arrow-icon { + display: inline-block; + width: 1.2em; + height: 1.2em; + flex: none; + fill: currentColor; + vertical-align: middle; + pointer-events: none; + transform: rotate(var(--arrow-angle, 0deg)); + transform-origin: center; +} +.arrow-icon--left { --arrow-angle: 180deg; } +.arrow-icon--up { --arrow-angle: -90deg; } +.arrow-icon--down { --arrow-angle: 90deg; } +.arrow-icon--up-right { --arrow-angle: -45deg; } +.arrow-icon--down-right { --arrow-angle: 45deg; } +.arrow-icon--up-left { --arrow-angle: -135deg; } +.arrow-icon--down-left { --arrow-angle: 135deg; } +:where(.arrow-slot) { display: inline-flex; align-items: center; justify-content: center; line-height: 0; } +.arrow-slot > .arrow-icon { display: block; } +.scene-view b, .case-next-arrow, .viewer-direction, .back-top { padding: 0; } +.scene-view b > .arrow-icon, .case-next-arrow > .arrow-icon, +.viewer-direction .arrow-icon, .back-top > .arrow-icon { width: 75%; height: 75%; } +.viewer-direction > .arrow-slot { display: grid; place-items: center; width: 100%; height: 100%; } +.mobile-menu a > .arrow-icon { margin-left: auto; width: 24px; height: 24px; } +.project-scope > span, .footer-bottom > a, .case-footer > a:last-child { + display: inline-flex; + align-items: center; + gap: 6px; +} + +@media (max-width: 600px) { + + .project-scope > span { display: inline-flex; } +} diff --git a/image-viewer.css b/image-viewer.css new file mode 100644 index 0000000..d5f42eb --- /dev/null +++ b/image-viewer.css @@ -0,0 +1,139 @@ +/* A complete preview stays in the page; detailed inspection belongs in the viewer. */ +.case-screen .screen-canvas { + display: flex; + align-items: center; + justify-content: center; + min-height: 0; + height: clamp(320px, 39vw, 560px); + overflow: hidden; + cursor: zoom-in; +} +.case-screen .screen-canvas > img { + display: block; + flex: 0 1 auto; + width: auto; + min-width: 0; + height: auto; + max-width: min(100%, var(--image-natural-width, 100%)); + max-height: 100%; + object-fit: contain; +} +.case-screen .screen-portrait { height: clamp(420px, 42vw, 570px); padding: 32px; } +.case-screen .screen-portrait > img { height: 100%; width: auto; } +.case--givon .case-screen .screen-canvas { height: auto; padding: 32px; } +.case--givon .case-screen .screen-canvas > img { width: 100%; max-height: none; } +.case-screen .screen-canvas:focus-visible { outline: 2px solid var(--case-accent, #2556e8); outline-offset: 4px; } + +html.image-viewer-open { overflow: hidden; } +.image-viewer { + position: fixed; + inset: 0; + width: 100%; + height: 100dvh; + max-width: none; + max-height: none; + margin: 0; + padding: 0; + border: 0; + color: #f3f4ee; + background: #111b19; + overflow: hidden; + overscroll-behavior: none; + color-scheme: dark; + font-family: var(--font); +} +.image-viewer[open] { display: grid; grid-template-rows: auto minmax(0, 1fr) auto; animation: viewer-enter .28s var(--ease); } +.image-viewer::backdrop { background: #111b19; } +.image-viewer button { border: 0; background: none; color: inherit; font: inherit; padding: 0; cursor: pointer; touch-action: manipulation; } +.image-viewer button:focus-visible, .image-viewer [tabindex]:focus-visible { outline: 2px solid #d5ee81; outline-offset: -3px; } +.viewer-header { display: flex; align-items: center; justify-content: space-between; gap: 24px; padding: 25px 32px 22px; border-bottom: 1px solid #ffffff16; } +.viewer-heading { min-width: 0; } +.viewer-brand { color: #9fae9f; font: 9px/1.5 Arial, sans-serif; letter-spacing: 1.3px; margin: 0 0 7px; } +.viewer-heading h2 { color: #f3f4ee; font-size: 21px; line-height: 1.4; font-weight: 500; letter-spacing: -.5px; } +.viewer-controls { display: flex; align-items: center; flex: none; gap: 8px; } +.viewer-zoom { display: flex; align-items: center; border: 1px solid #ffffff24; border-radius: 4px; } +.viewer-zoom button { width: 36px; height: 36px; font: 23px Arial, sans-serif; } +.viewer-scale { font: 10px monospace; min-width: 47px; text-align: center; color: #d9e0d4; } +.image-viewer .viewer-size { height: 38px; padding-inline: 12px; font-size: 11px; border-radius: 4px; color: #bdc8bb; } +.image-viewer .viewer-size[aria-pressed="true"] { color: #d5ee81; background: #d5ee810b; } +.image-viewer .viewer-close { display: flex; align-items: center; justify-content: center; flex: none; gap: 8px; padding: 0 14px; margin-left: 12px; height: 40px; border: 1px solid #ffffff24; border-radius: 7px; background: #ffffff04; font-size: 12px; line-height: 1; transition: color .2s, background .2s, border-color .2s; } +.image-viewer .viewer-close:focus-visible { outline: 2px solid #d5ee81; outline-offset: 3px; border-color: #d5ee8180; background: #d5ee8112; } +.viewer-close-icon { position: relative; display: block; flex: none; width: 16px; height: 16px; } +.viewer-close-icon::before, .viewer-close-icon::after { content: ""; position: absolute; left: 0; top: 7px; width: 16px; height: 2px; border-radius: 1px; background: currentColor; transform: rotate(45deg); } +.viewer-close-icon::after { transform: rotate(-45deg); } +.viewer-body { min-height: 0; min-width: 0; position: relative; background: radial-gradient(ellipse at center, #26332b80, transparent 72%); } +.viewer-stage { height: 100%; width: 100%; overflow: auto; overscroll-behavior: contain; scrollbar-width: thin; scrollbar-color: #718274 #19221e; } +.viewer-image-layout { display: flex; align-items: center; justify-content: center; } +.viewer-image { max-width: none; flex: none; display: block; user-select: none; -webkit-user-drag: none; object-fit: contain; box-shadow: 0 20px 70px #0003; } +.viewer-stage.can-pan { cursor: grab; } +.viewer-stage.is-dragging { cursor: grabbing; } +.image-viewer .viewer-direction { position: absolute; top: 50%; transform: translateY(-50%); width: 44px; height: 44px; border: 1px solid #c2d2b43d; border-radius: 50%; background: #15221deb; box-shadow: 0 4px 20px #0002; display: grid; place-items: center; font: 24px Arial, sans-serif; color: #e0edca; transition: background .2s, border-color .2s; } +.viewer-previous { left: 20px; } +.viewer-next { right: 20px; } +.viewer-message { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; font-size: 12px; color: #c5cfbd; line-height: 1.8; } +.viewer-message > span { display: block; } +.image-viewer .viewer-message button { border-bottom: 1px solid #d5ee8170; font-size: 12px; margin-top: 15px; padding-bottom: 5px; color: #d5ee81; } +.viewer-footer { padding: 19px 32px 21px; padding-bottom: max(21px, env(safe-area-inset-bottom)); border-top: 1px solid #ffffff16; } +.viewer-caption { display: flex; align-items: baseline; gap: 20px; } +.viewer-counter { flex: none; font: 11px monospace; color: #d5ee81; letter-spacing: 1px; } +.viewer-caption p { margin: 0; font-size: 12px; color: #bac6b7; line-height: 1.7; } +.viewer-keyboard-hint { margin-left: auto; flex: none; font-size: 9px; letter-spacing: .3px; color: #82927f; } +.viewer-keyboard-hint i { font-style: normal; margin-inline: 10px; } +.viewer-thumbnails { display: flex; justify-content: safe center; gap: 10px; overflow: auto; margin-top: 17px; padding: 3px; scrollbar-width: thin; } +.viewer-thumbnails button { display: grid; place-items: center; width: 68px; height: 48px; flex: none; padding: 4px; border-radius: 3px; border: 1px solid #ffffff1c; background: #b3c3a90a; opacity: .55; transition: opacity .2s, border-color .2s, background .2s; } +.viewer-thumbnails button[aria-pressed="true"] { opacity: 1; border-color: #d5ee81; background: #b3c3a91a; } +.viewer-thumbnails img { width: 100%; height: 100%; max-height: 38px; object-fit: contain; } +@keyframes viewer-enter { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: none; } } +@media (hover: hover) { + .viewer-direction:hover, .viewer-zoom button:hover, .image-viewer .viewer-size:hover { background: #d5ee811a; } + .viewer-thumbnails button:hover { opacity: 1; } + .image-viewer .viewer-close:hover { color: #d5ee81; border-color: #d5ee8160; background: #d5ee8112; } +} +@media (max-width: 850px) { + .viewer-header { padding: 20px; gap: 15px; } + .viewer-heading h2 { font-size: 18px; } + .viewer-brand { font-size: 8px; } + .viewer-footer { padding: 16px 20px; } + .viewer-keyboard-hint { display: none; } + .viewer-caption p { font-size: 11px; } +} +@media (max-width: 600px) { + .case-screen .screen-canvas:not(.screen-portrait) { height: auto; max-height: none; min-height: 0; padding: 16px; overflow: hidden; justify-content: center; } + .case-screen .screen-canvas:not(.screen-portrait) > img { width: 100%; min-width: 0; height: auto; max-height: none; } + .case-screen .screen-portrait { height: 440px; padding: 24px; } + .viewer-header { position: relative; display: block; padding: max(17px, env(safe-area-inset-top)) 16px 12px; } + .viewer-heading { padding-right: 55px; } + .viewer-brand { font-size: 7px; letter-spacing: 1px; margin-bottom: 6px; } + .viewer-heading h2 { font-size: 17px; } + .viewer-controls { margin-top: 12px; gap: 7px; } + .viewer-zoom button { width: 34px; height: 32px; font-size: 21px; } + .viewer-scale { font-size: 9px; min-width: 41px; } + .image-viewer .viewer-size { font-size: 10px; height: 34px; padding-inline: 10px; } + .image-viewer .viewer-close { position: absolute; right: 16px; top: max(17px, env(safe-area-inset-top)); width: 44px; height: 44px; border-radius: 50%; padding: 0; margin: 0; } + .viewer-close > span:last-child { display: none; } + .viewer-footer { padding: 13px 16px max(14px, env(safe-area-inset-bottom)); } + .viewer-caption { gap: 13px; } + .viewer-counter { font-size: 9px; letter-spacing: .3px; } + .viewer-caption p { font-size: 10px; } + .viewer-thumbnails { margin-top: 10px; gap: 8px; } + .viewer-thumbnails button { width: 56px; height: 40px; padding: 3px; } + .viewer-thumbnails img { max-height: 32px; } + .image-viewer .viewer-direction { width: 32px; height: 32px; font-size: 19px; } + .viewer-previous { left: 8px; } + .viewer-next { right: 8px; } +} +@media (max-height: 500px) and (orientation: landscape) { + .viewer-header { display: flex; padding: 10px 16px; } + .viewer-heading { padding: 0; } + .viewer-heading h2 { font-size: 14px; } + .viewer-brand { font-size: 6px; margin-bottom: 3px; } + .viewer-controls { margin: 0; } + .image-viewer .viewer-close { position: static; margin-left: 5px; } + .viewer-footer { padding: 8px 16px; } + .viewer-thumbnails { display: none; } + .viewer-caption p { font-size: 10px; } +} +@media (prefers-reduced-motion: reduce) { + .image-viewer[open] { animation: none; } + .image-viewer * { transition: none !important; scroll-behavior: auto !important; } +} diff --git a/image-viewer.js b/image-viewer.js new file mode 100644 index 0000000..54cf327 --- /dev/null +++ b/image-viewer.js @@ -0,0 +1,283 @@ +/* Fullscreen image galleries, derived from each page's existing images and captions. */ +(() => { + 'use strict'; + if (typeof HTMLDialogElement === 'undefined') return; + const groups = []; + const figures = [...document.querySelectorAll('.case-screen')]; + if (figures.length) { + groups.push({ + name: document.querySelector('.case-project-brand')?.textContent.trim() || 'TRONT', + items: figures.map(figure => ({ + link: figure.querySelector('figcaption a'), + preview: figure.querySelector('.screen-canvas'), + image: figure.querySelector('img'), + title: document.getElementById(figure.getAttribute('aria-labelledby'))?.textContent.trim().replace(/^\d+\s*/, '') || '개발 화면', + caption: figure.querySelector('figcaption p')?.textContent.trim() || '' + })) + }); + } + const certificates = [...document.querySelectorAll('.patent-document')]; + if (certificates.length) { + groups.push({ name: 'TRONT / PATENTS', items: certificates.map(link => ({ + link, + image: link.querySelector('img'), + title: link.closest('article').querySelector('h3').textContent.trim(), + caption: link.closest('article').querySelector('.patent-description').textContent.trim() + })) }); + } + if (!groups.length) return; + + const reduced = matchMedia('(prefers-reduced-motion: reduce)'); + const dialog = document.createElement('dialog'); + dialog.className = 'image-viewer'; + dialog.setAttribute('aria-labelledby', 'image-viewer-title'); + dialog.innerHTML = ` +
+

+
+
—
+ + + +
+
+
+
+
+
+
+ + +
+ `; + document.body.append(dialog); + const $ = selector => dialog.querySelector(selector); + const stage = $('.viewer-stage'); + const layout = $('.viewer-image-layout'); + const image = $('.viewer-image'); + const message = $('.viewer-message'); + const thumbnails = $('.viewer-thumbnails'); + const historyKey = '__trontImageViewer'; + const token = `gallery-${Date.now()}`; + let group, index = 0, opener, savedScroll, savedBody; + let naturalWidth = 0, naturalHeight = 0, scale = 1, fitted = true; + let loadId = 0, imageAnimation, drag, swipe, waitingForBack = false; + + function lockPage() { + savedScroll = { x: scrollX, y: scrollY }; + savedBody = ['position', 'top', 'left', 'width', 'paddingRight'].map(key => [key, document.body.style[key]]); + const scrollbar = innerWidth - document.documentElement.clientWidth; + const padding = parseFloat(getComputedStyle(document.body).paddingRight) || 0; + Object.assign(document.body.style, { position: 'fixed', top: `-${savedScroll.y}px`, left: `-${savedScroll.x}px`, width: '100%', paddingRight: `${padding + scrollbar}px` }); + document.documentElement.classList.add('image-viewer-open'); + } + function unlockPage() { + savedBody.forEach(([key, value]) => { document.body.style[key] = value; }); + document.documentElement.classList.remove('image-viewer-open'); + window.scrollTo({ left: savedScroll.x, top: savedScroll.y, behavior: 'instant' }); + opener?.focus({ preventScroll: true }); + } + function fitScale() { + return Math.min(1, Math.max(1, stage.clientWidth - 80) / naturalWidth, Math.max(1, stage.clientHeight - 36) / naturalHeight); + } + function setScale(value, fit = false, center = false) { + if (!naturalWidth || !dialog.open) return; + const oldWidth = parseFloat(image.style.width) || naturalWidth; + const oldHeight = parseFloat(image.style.height) || naturalHeight; + const x = Math.max(0, Math.min(1, (stage.scrollLeft + stage.clientWidth / 2 - (layout.offsetWidth - oldWidth) / 2) / oldWidth)); + const y = Math.max(0, Math.min(1, (stage.scrollTop + stage.clientHeight / 2 - (layout.offsetHeight - oldHeight) / 2) / oldHeight)); + fitted = fit; + scale = Math.max(Math.min(.1, fitScale()), Math.min(4, fit ? fitScale() : value)); + const width = Math.round(naturalWidth * scale), height = Math.round(naturalHeight * scale); + image.style.width = `${width}px`; + image.style.height = `${height}px`; + layout.style.width = `${Math.max(stage.clientWidth, width + 80)}px`; + layout.style.height = `${Math.max(stage.clientHeight, height + 36)}px`; + stage.scrollLeft = (center ? .5 : x) * width + (layout.offsetWidth - width) / 2 - stage.clientWidth / 2; + stage.scrollTop = (center ? .5 : y) * height + (layout.offsetHeight - height) / 2 - stage.clientHeight / 2; + stage.classList.toggle('can-pan', width + 80 > stage.clientWidth + 1 || height + 36 > stage.clientHeight + 1); + $('.viewer-scale').textContent = `${Math.round(scale * 100)}%`; + $('[data-action="fit"]').setAttribute('aria-pressed', String(fitted)); + $('[data-action="actual"]').setAttribute('aria-pressed', String(Math.abs(scale - 1) < .001)); + } + function showImage(next, direction = 0) { + if (!dialog.open) return; + index = (next + group.items.length) % group.items.length; + const item = group.items[index], requestId = ++loadId; + naturalWidth = naturalHeight = 0; + drag = swipe = null; + stage.classList.remove('is-dragging'); + imageAnimation?.cancel(); + dialog.classList.add('is-loading'); + image.hidden = true; + $('.viewer-scale').textContent = '—'; + $('#image-viewer-title').textContent = item.title; + $('.viewer-caption p').textContent = item.caption; + $('.viewer-counter').textContent = `${String(index + 1).padStart(2, '0')} / ${String(group.items.length).padStart(2, '0')}`; + message.hidden = false; + $('.viewer-message > span').textContent = '이미지를 불러오는 중입니다.'; + $('[data-action="retry"]').hidden = true; + [...thumbnails.children].forEach((button, i) => { + button.setAttribute('aria-pressed', String(i === index)); + if (i === index) button.scrollIntoView({ block: 'nearest', inline: 'nearest', behavior: 'instant' }); + }); + const loader = new Image(); + loader.onload = () => { + if (requestId !== loadId || !dialog.open) return; + naturalWidth = loader.naturalWidth; + naturalHeight = loader.naturalHeight; + image.src = loader.src; + image.alt = item.image.alt || item.title; + image.hidden = false; + dialog.classList.remove('is-loading'); + message.hidden = true; + setScale(1, true, true); + if (!reduced.matches) imageAnimation = image.animate([ + { opacity: 0, transform: `translateX(${direction * 18}px) scale(.985)` }, + { opacity: 1, transform: 'translateX(0) scale(1)' } + ], { duration: 280, easing: 'cubic-bezier(.22,1,.36,1)' }); + }; + loader.onerror = () => { + if (requestId !== loadId || !dialog.open) return; + dialog.classList.remove('is-loading'); + $('.viewer-message > span').textContent = '이미지를 불러오지 못했습니다.'; + $('[data-action="retry"]').hidden = false; + }; + loader.src = item.link.href; + } + function openGallery(nextGroup, nextIndex, source, addHistory = true) { + if (waitingForBack || dialog.open) return; + group = nextGroup; + opener = source; + $('.viewer-brand').textContent = `${group.name} / IMAGE GALLERY`; + thumbnails.replaceChildren(); + group.items.forEach((item, i) => { + const button = document.createElement('button'); + button.type = 'button'; + button.setAttribute('aria-label', `${i + 1}. ${item.title}`); + const thumb = document.createElement('img'); + thumb.src = item.link.href; + thumb.alt = ''; + thumb.loading = 'lazy'; + button.append(thumb); + button.addEventListener('click', () => showImage(i, Math.sign(i - index))); + thumbnails.append(button); + }); + dialog.querySelectorAll('.viewer-direction').forEach(button => { button.hidden = group.items.length < 2; }); + thumbnails.hidden = group.items.length < 2; + if (addHistory) { + try { history.pushState({ ...(typeof history.state === 'object' ? history.state : {}), [historyKey]: token }, '', location.href); } catch { /* Local file viewers still close with their controls. */ } + } + lockPage(); + dialog.showModal(); + showImage(nextIndex); + $('[data-action="close"]').focus({ preventScroll: true }); + } + function closeUI() { + if (!dialog.open) return; + ++loadId; + imageAnimation?.cancel(); + dialog.close(); + unlockPage(); + drag = swipe = null; + } + function dismiss() { + closeUI(); + if (history.state?.[historyKey] === token) { + waitingForBack = true; + history.back(); + } + } + window.addEventListener('popstate', event => { + waitingForBack = false; + if (event.state?.[historyKey] === token && group && !dialog.open) openGallery(group, index, opener, false); + else if (event.state?.[historyKey] !== token) closeUI(); + }); + dialog.addEventListener('cancel', event => { event.preventDefault(); dismiss(); }); + dialog.addEventListener('click', event => { + const action = event.target.closest('[data-action]')?.dataset.action; + if (action === 'close') dismiss(); + if (action === 'previous' && group.items.length > 1) showImage(index - 1, -1); + if (action === 'next' && group.items.length > 1) showImage(index + 1, 1); + if (action === 'in') setScale(scale * 1.25); + if (action === 'out') setScale(scale / 1.25); + if (action === 'fit') setScale(1, true, true); + if (action === 'actual') setScale(1, false, true); + if (action === 'retry') showImage(index); + }); + dialog.addEventListener('keydown', event => { + if (event.key === 'Tab') { + const controls = [...dialog.querySelectorAll('button:not(:disabled), [tabindex="0"]')].filter(element => element.getClientRects().length && !element.closest('[hidden]')); + const first = controls[0], last = controls[controls.length - 1]; + if (event.shiftKey && (document.activeElement === first || document.activeElement === dialog)) { event.preventDefault(); last.focus(); } + else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first.focus(); } + return; + } + if (event.ctrlKey || event.metaKey || event.altKey) return; + const key = event.key; + if (['ArrowLeft', 'ArrowRight', 'Home', 'End', '+', '=', '-', '0', '1'].includes(key)) event.preventDefault(); + if (group.items.length > 1) { + if (key === 'ArrowLeft') showImage(index - 1, -1); + if (key === 'ArrowRight') showImage(index + 1, 1); + if (key === 'Home') showImage(0, -1); + if (key === 'End') showImage(group.items.length - 1, 1); + } + if (key === '+' || key === '=') setScale(scale * 1.25); + if (key === '-') setScale(scale / 1.25); + if (key === '0') setScale(1, true, true); + if (key === '1') setScale(1, false, true); + }); + stage.addEventListener('dblclick', () => setScale(fitted ? Math.max(1, scale * 2) : 1, !fitted, true)); + stage.addEventListener('pointerdown', event => { + if (event.pointerType !== 'mouse' || event.button !== 0 || !stage.classList.contains('can-pan')) return; + drag = { x: event.clientX, y: event.clientY, left: stage.scrollLeft, top: stage.scrollTop }; + stage.setPointerCapture(event.pointerId); + stage.classList.add('is-dragging'); + event.preventDefault(); + }); + stage.addEventListener('pointermove', event => { + if (!drag) return; + stage.scrollLeft = drag.left - event.clientX + drag.x; + stage.scrollTop = drag.top - event.clientY + drag.y; + }); + const stopDrag = () => { drag = null; stage.classList.remove('is-dragging'); }; + stage.addEventListener('pointerup', stopDrag); + stage.addEventListener('pointercancel', stopDrag); + stage.addEventListener('lostpointercapture', stopDrag); + stage.addEventListener('touchstart', event => { + swipe = fitted && event.touches.length === 1 ? { x: event.touches[0].clientX, y: event.touches[0].clientY } : null; + }, { passive: true }); + stage.addEventListener('touchmove', event => { if (event.touches.length !== 1) swipe = null; }, { passive: true }); + stage.addEventListener('touchend', event => { + if (!swipe || !event.changedTouches.length) return; + const x = event.changedTouches[0].clientX - swipe.x, y = event.changedTouches[0].clientY - swipe.y; + swipe = null; + if (group.items.length > 1 && Math.abs(x) > 55 && Math.abs(x) > Math.abs(y) * 1.5) showImage(index + (x < 0 ? 1 : -1), x < 0 ? 1 : -1); + }, { passive: true }); + stage.addEventListener('touchcancel', () => { swipe = null; }, { passive: true }); + new ResizeObserver(() => { if (dialog.open && naturalWidth) setScale(scale, fitted); }).observe(stage); + reduced.addEventListener('change', () => imageAnimation?.cancel()); + groups.forEach(current => current.items.forEach((item, i) => { + const open = event => { + if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || (event.type === 'click' && event.button !== 0)) return; + event.preventDefault(); + openGallery(current, i, event.currentTarget); + }; + item.link.addEventListener('click', open); + item.link.setAttribute('aria-haspopup', 'dialog'); + if (item.preview) { + item.preview.setAttribute('role', 'button'); + item.preview.tabIndex = 0; + item.preview.setAttribute('aria-label', `${item.title} 전체 화면으로 보기`); + item.preview.setAttribute('aria-haspopup', 'dialog'); + item.preview.addEventListener('click', open); + item.preview.addEventListener('keydown', event => { if (event.key === 'Enter' || event.key === ' ') open(event); }); + const sizePreview = () => item.preview.style.setProperty('--image-natural-width', `${item.image.naturalWidth}px`); + if (item.image.complete && item.image.naturalWidth) sizePreview(); + item.image.addEventListener('load', sizePreview); + } + })); +})(); diff --git a/index.html b/index.html new file mode 100644 index 0000000..0e7307d --- /dev/null +++ b/index.html @@ -0,0 +1,338 @@ + + + + + + + + + + + TRONT — 기술로 연결하고, 가능성을 확장하다 + + + + + + + + + + + + + + + + + + + +
+
+
+

CONNECTING IDEAS. CREATING IMPACT.

+

기술로 연결하고,
가능성을 확장하다.

+

모빌리티부터 디지털 플랫폼까지.
비즈니스의 다음을 함께 만드는 기술 파트너, 트론트.

+ +
INNOVATION SINCE2008경험은 깊게.
시선은 더 멀리.
+
+ + SCROLL TO EXPLORE + +
+ + + +
+
기술에 대한 깊이, 사람을 향한 방향
+
+

복잡한 기술을
의미 있는 경험으로.

+

트론트는 모바일, 웹, 애플리케이션을 넘어 산업과 일상을 연결하는 IT 솔루션 전문 기업입니다. 2008년의 첫걸음부터 다양한 산업 현장에서 쌓아온 경험으로, 고객의 아이디어를 작동하는 서비스로 만듭니다.

지능형 관제, 디지털 플랫폼, 미래 모빌리티까지.
기술의 경계를 넘나들며 비즈니스에 필요한 연결을 설계합니다.

트론트 회사소개서 PDF · 9.2 MB
+
+
+
+

COMPANY AT A GLANCE

+

주식회사 트론트TRONT Co., Ltd.

+
+
설립일
+
사업분야
모바일 솔루션 · 웹 서비스 · 산업용 애플리케이션 개발
+
소재지
대구광역시 북구 경대로17길 47
IT융합산업빌딩 1203호
+
+ 기술 연구와 특허 살펴보기 +
+
+
+

PEOPLE & ORGANIZATION

함께 만드는 전문 조직

+
18명2025.10 기준
+
+
+
CEO · 대표이사김칠수
+
    +
  • 기술지원팀

    TECHNICAL SUPPORT

  • +
  • 개발1팀

    Web 개발
    모바일 앱 개발

  • +
  • 개발2팀

    내비게이션 개발

  • +
+
+
+
+
+ +
+
FIVE FIELDS. ONE CONNECTED VISION.
+

기술을 잇는
IT 전문가 그룹

주식회사 트론트는 스마트 모빌리티, 산업 관제, 자산 관리, SaaS 플랫폼,
디지털 인터페이스 분야에서 고객의 디지털 혁신을 이끕니다.

+
+
01
SMART MOBILITY & HMI

스마트 모빌리티 및 HMI 플랫폼

운전자와 차량 사이, 직관적인 연결을 만듭니다. 국내외 내비게이션 HMI와 차량 통신 기술을 바탕으로 새로운 이동 경험을 구현합니다.

차량 통신네비게이션 개발 연결성관제 시스템
+
02
INDUSTRIAL MONITORING & CONTROL

산업 통합 관제 및 제어 시스템

현장의 데이터를 한눈에, 복잡한 운영을 더 명확하게. 실시간 모니터링과 데이터 송수신, 원격 제어를 통합해 산업 현장의 효율과 안전을 지원합니다.

실시간 모니터링데이터 송수신 프로토콜시스템 통합(SI)원격 제어 및 안전 시스템
+
03
WEB3 & ASSET MANAGEMENT

고도화된 Web3 & Asset 관리 솔루션

자산의 흐름에 정확함을, 운영에 투명함을 더합니다. 블록체인 기반 서비스와 RFID·NFC 기술을 활용해 데이터와 실물 자산을 연결합니다.

블록체인 기반 투명성 확보RFID / NFC 등 무선 통신 기반 자산 관리금융 기술 융합
+
04
COMMUNITY & SAAS PLATFORM

전문 커뮤니티 및 SaaS 플랫폼

사람과 사람, 서비스와 일상을 연결합니다. 약국·교육·스포츠 등 도메인에 대한 이해를 바탕으로 모바일 앱, 웹, 관리자 시스템을 함께 구축합니다.

도메인 특화 Web/App 서비스GroupWareLMS 개발
+
05
FUTURE TECH & DIGITAL INTERFACE

미래 기술 및 디지털 인터페이스

새로운 기술이 자연스러운 경험이 되도록. 가상현실 기반 웹 환경, 디지털 사이니지, 영상 분석과 UX 기술로 디지털 접점을 확장합니다.

Virtual Reality기반 Web 환경SIGNAGE영상 분석 및 UX
+
+
+
+
+ +
+
아이디어가 현실이 되는 순간
+

기술로 만든
실제의 변화.

다양한 산업에서 증명해 온 실행력.
트론트가 함께 만든 프로젝트를 소개합니다.

+
8 PROJECTS
+
+ + + + + + + + + + + + + + + + +
+ +

소개서에 수록된 실제 프로젝트와 수행 기간을 기준으로 구성했습니다.

+
+ +
+
+
IDEAS INTO INTELLECTUAL PROPERTY
+
+

기술의 깊이를
보여주는 특허.

특정 기술에 안주하지 않는 R&D 프로세스를 핵심 과정으로 삼고 있습니다.
미래 비즈니스 환경 변화에 선제적으로 대응하기 위해 지속적인 발굴 및 지식재산권 확보에 주력하고 있습니다.
특허와 인증은 당사가 미래에도 신뢰할 수 있는 파트너임을 증명하는 잠재적 능력입니다.

+ +
+
+
+ + + 객체 가상 이미지 구현 방법 특허증, 제10-0951362호 + 특허증 크게 보기 + +
+

01 VIRTUAL IMAGE

+

객체 가상 이미지 구현 방법

+

실제 객체를 기반으로 가상 이미지를 생성하고, 현실과 통합하는 기술입니다.

+
특허번호
제10-0951362호
출원일
등록일
+
+
+
+ + + 액체 세제 자동 판매 장치 특허증, 제10-1099507호 + 특허증 크게 보기 + +
+

02 DEVICE CONTROL

+

액체 세제 자동 판매 장치

+

액체 세제의 자동 판매 및 관리를 위한 기술로 효율적인 자판기 시스템 제어에 대한 특허.

+
특허번호
제10-1099507호
출원일
등록일
+
+
+
+

회사소개서에 수록된 특허증을 기준으로 소개합니다.

소개서에서 보기
+
+
+ +
A CONTINUOUS JOURNEY OF INNOVATION

새로운 길은,
계속됩니다.

첫 서버에서 글로벌 모빌리티까지.
기술이 바뀌어도, 더 나은 답을 찾는 우리의 태도는 같습니다.

WHAT'S NEXT

더 넓은 연결, 다음의 가능성

블록체인 기반 기부 플랫폼 GivOn 관리자 개발과 소규모 기관차 관제 프로그램 개발로 기술의 쓰임을 넓혀갑니다.

Web3관제 시스템디지털 플랫폼
+ +
YOUR NEXT CHAPTER STARTS HERE.

다음의 가능성,
함께 만들까요?

새로운 서비스의 시작부터 복잡한 기술의 해답까지.
트론트와 새로운 프로젝트를 함께 만들어가요.

함께 프로젝트 만들기
EMAIL

이메일과 연락처 정보 없음.

COME FIND US
대구광역시 북구 경대로17길 47
IT융합산업빌딩 1203호
+
+ + + + +
+

COME FIND US

트론트 오시는 길

+ +
+
+
+

지도를 불러오는 중입니다.

+
+
대구광역시 북구 경대로17길 47
IT융합산업빌딩 1203호
바깥 영역을 누르면 닫힙니다.
+
+ +
+ + diff --git a/map-dialog.css b/map-dialog.css new file mode 100644 index 0000000..49d3053 --- /dev/null +++ b/map-dialog.css @@ -0,0 +1,91 @@ +.address-row .map-link { + padding: 0; + background: transparent; + text-align: left; +} +.address-row .map-link:hover { color: var(--lime); } +html.map-dialog-open { overflow: hidden; } +.map-dialog { + width: min(1000px, calc(100% - 48px)); + max-width: none; + height: min(760px, calc(100dvh - 64px)); + max-height: none; + margin: auto; + padding: 0; + border: 1px solid var(--line); + border-radius: 16px; + background: var(--paper); + color: var(--ink); + overflow: hidden; + box-shadow: 0 30px 100px #101e2440; +} +.map-dialog[open] { display: flex; flex-direction: column; } +.map-dialog::backdrop { background: #101e24b8; backdrop-filter: blur(6px); } +.map-dialog-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; + padding: 24px 30px; + flex-shrink: 0; +} +.map-dialog-header .tiny-label { font-size: 9px; color: var(--blue); } +.map-dialog-header h2 { margin-top: 8px; font-size: 25px; letter-spacing: -1px; } +.map-dialog-close { + display: flex; + align-items: center; + gap: 8px; + min-height: 44px; + padding: 0 15px; + border: 1px solid var(--line); + border-radius: 24px; + background: var(--paper); + font-size: 13px; + white-space: nowrap; +} +.map-dialog-close > span { font-size: 26px; font-weight: 300; line-height: 1; } +.map-dialog-close:hover { background: var(--ink); color: var(--paper); } +.map-dialog-body { position: relative; flex: 1; min-height: 0; background: #e5e9df; } +.map-dialog-canvas, .map-dialog-canvas iframe { display: block; width: 100%; height: 100%; border: 0; } +.map-dialog-status { + position: absolute; + inset: 0; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 18px; + padding: 24px; + background: #e5e9df; + font-size: 14px; + text-align: center; +} +.map-dialog-retry { padding: 12px 20px; border-radius: 6px; background: var(--ink); color: var(--paper); } +.map-dialog-footer { + display: flex; + align-items: center; + justify-content: space-between; + gap: 20px; + flex-shrink: 0; + padding: 20px 30px; +} +.map-dialog-footer address { font-style: normal; font-size: 13px; line-height: 1.8; } +.map-dialog-footer strong { font-weight: 600; } +.map-dialog-hint { font-size: 11px; color: var(--muted); } +@media (max-width: 600px) { + .map-dialog { width: calc(100% - 24px); height: calc(100dvh - 32px); border-radius: 12px; } + .map-dialog-header { padding: 20px 18px; gap: 12px; } + .map-dialog-header h2 { font-size: 21px; } + .map-dialog-header .tiny-label { font-size: 8px; } + .map-dialog-close { padding-inline: 12px; font-size: 12px; } + .map-dialog-footer { padding: 16px 18px; } + .map-dialog-footer address { font-size: 12px; } + .map-dialog-hint { display: none; } +} +@media (max-height: 480px) { + .map-dialog { height: calc(100dvh - 16px); } + .map-dialog-header { padding-block: 10px; } + .map-dialog-header .tiny-label, .map-dialog-hint { display: none; } + .map-dialog-header h2 { margin: 0; font-size: 20px; } + .map-dialog-footer { padding-block: 10px; } +} diff --git a/map-dialog.js b/map-dialog.js new file mode 100644 index 0000000..9e006ee --- /dev/null +++ b/map-dialog.js @@ -0,0 +1,113 @@ +/* Keep the map and its navigation inside a dismissible view. */ +(() => { + 'use strict'; + const trigger = document.querySelector('.map-link'); + const dialog = document.querySelector('#map-dialog'); + if (!trigger || !dialog) return; + + const canvas = dialog.querySelector('.map-dialog-canvas'); + const body = dialog.querySelector('.map-dialog-body'); + const status = dialog.querySelector('.map-dialog-status'); + const retry = dialog.querySelector('.map-dialog-retry'); + const closeButton = dialog.querySelector('.map-dialog-close'); + const historyKey = '__trontMapDialog'; + const token = `map-${Date.now()}`; + let frame, timer, savedScroll, savedStyle, waitingForBack = false; + let backdropPressed = false; + + function showError() { + clearTimeout(timer); + frame?.remove(); + frame = null; + body.setAttribute('aria-busy', 'false'); + status.hidden = false; + status.querySelector('p').textContent = '지도를 불러오지 못했습니다. 연결 상태를 확인하고 다시 시도해 주세요.'; + retry.hidden = false; + } + + function loadMap() { + clearTimeout(timer); + frame?.remove(); + body.setAttribute('aria-busy', 'true'); + status.hidden = false; + status.querySelector('p').textContent = '지도를 불러오는 중입니다.'; + retry.hidden = true; + frame = document.createElement('iframe'); + frame.title = '구글 지도 — 트론트, IT융합산업빌딩 위치'; + // The campus shares one street address; use the IT building's location. + frame.src = 'https://maps.google.com/maps?hl=ko&q=35.8920895,128.6141078&z=17&output=embed'; + frame.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox'); + frame.referrerPolicy = 'no-referrer-when-downgrade'; + const loadingFrame = frame; + frame.addEventListener('load', () => { + if (frame !== loadingFrame || !dialog.open) return; + clearTimeout(timer); + body.setAttribute('aria-busy', 'false'); + status.hidden = true; + }); + frame.addEventListener('error', showError); + canvas.replaceChildren(frame); + timer = setTimeout(showError, 20000); + } + + function openMap(addHistory = true) { + if (dialog.open || waitingForBack) return; + savedScroll = { x: scrollX, y: scrollY }; + // Save the page's history entry before fixing the body resets scrollY to 0. + if (addHistory) { + try { history.pushState({ ...history.state, [historyKey]: token }, '', location.href); } + catch { /* The close controls also work in local file previews. */ } + } + savedStyle = ['position', 'top', 'left', 'width', 'paddingRight'].map(key => [key, document.body.style[key]]); + const scrollbar = innerWidth - document.documentElement.clientWidth; + const padding = parseFloat(getComputedStyle(document.body).paddingRight) || 0; + Object.assign(document.body.style, { + position: 'fixed', top: `-${savedScroll.y}px`, left: `-${savedScroll.x}px`, + width: '100%', paddingRight: `${padding + scrollbar}px` + }); + document.documentElement.classList.add('map-dialog-open'); + dialog.showModal(); + closeButton.focus({ preventScroll: true }); + loadMap(); + } + + function closeMap() { + if (!dialog.open) return; + clearTimeout(timer); + frame?.remove(); + frame = null; + dialog.close(); + savedStyle.forEach(([key, value]) => { document.body.style[key] = value; }); + document.documentElement.classList.remove('map-dialog-open'); + window.scrollTo({ left: savedScroll.x, top: savedScroll.y, behavior: 'instant' }); + trigger.focus({ preventScroll: true }); + } + + function dismiss() { + closeMap(); + if (history.state?.[historyKey] === token) { + waitingForBack = true; + history.back(); + } + } + + function isBackdrop(event) { + const rect = dialog.getBoundingClientRect(); + return event.target === dialog && (event.clientX < rect.left || event.clientX > rect.right || event.clientY < rect.top || event.clientY > rect.bottom); + } + + trigger.addEventListener('click', () => openMap()); + closeButton.addEventListener('click', dismiss); + retry.addEventListener('click', loadMap); + dialog.addEventListener('cancel', event => { event.preventDefault(); dismiss(); }); + dialog.addEventListener('pointerdown', event => { backdropPressed = isBackdrop(event); }); + dialog.addEventListener('click', event => { + if (backdropPressed && isBackdrop(event)) dismiss(); + backdropPressed = false; + }); + window.addEventListener('popstate', event => { + waitingForBack = false; + if (event.state?.[historyKey] === token) openMap(false); + else closeMap(); + }); +})(); diff --git a/portfolio.css b/portfolio.css new file mode 100644 index 0000000..77e2aa1 --- /dev/null +++ b/portfolio.css @@ -0,0 +1,289 @@ +/* Shared portfolio art direction. Pure CSS scenes complement the original screens. */ +@view-transition{navigation:auto} +::view-transition-old(root){animation:portfolio-leave .22s both ease} +::view-transition-new(root){animation:portfolio-arrive .5s both cubic-bezier(.22,1,.36,1)} +@keyframes portfolio-leave{to{opacity:0;transform:translateY(-12px)}} +@keyframes portfolio-arrive{from{opacity:0;transform:translateY(24px)}to{opacity:1;transform:translateY(0)}} +.work .project-grid{gap:60px 32px;margin-top:35px} +.work .project-open{display:block} +.work .project-meta{margin:0} +.work .project-meta>span:first-child{font-size:12px;letter-spacing:.5px} +.work .project-card h3{font-size:25px;min-height:0;margin:14px 0 14px;line-height:1.4} +.work .project-scope{padding-top:15px;margin-top:18px;gap:10px} +.work .project-scope>span{color:var(--ink);white-space:nowrap} +.project-scene{--scene-bg:#213133;--scene-ink:#f4f4ed;--scene-accent:#d9f98a;--scene-line:#edfce912;aspect-ratio:1.36;position:relative;overflow:hidden;border-radius:5px;background:var(--scene-bg);color:var(--scene-ink);isolation:isolate;container-type:inline-size} +.scene-surface{position:relative;width:100%;height:100%;transform:perspective(1200px) rotateX(var(--tilt-x,0deg)) rotateY(var(--tilt-y,0deg));transition:transform .7s var(--ease);transform-style:preserve-3d} +.scene-grid{position:absolute;inset:0;background-image:linear-gradient(var(--scene-line) 1px,transparent 1px),linear-gradient(90deg,var(--scene-line) 1px,transparent 1px);background-size:52px 52px;mask-image:linear-gradient(130deg,#0009,transparent 75%)} +.scene-kicker{position:absolute;left:30px;right:30px;top:27px;display:flex;justify-content:space-between;gap:20px;font-family:monospace;font-size:8px;letter-spacing:1.3px;z-index:3} +.scene-kicker>span:last-child{opacity:.65} +.scene-wordmark{position:absolute;left:30px;top:72px;font-family:Arial,var(--font);font-size:64px;font-size:10cqw;line-height:.95;font-weight:700;letter-spacing:-3.5px;z-index:2} +.scene-wordmark em{font-style:normal;color:var(--scene-accent)} +.scene-wordmark .word-period{color:var(--scene-accent)} +.scene-phrase{position:absolute;left:33px;top:228px;font-family:monospace;font-size:8px;letter-spacing:1px;opacity:.65;z-index:2;max-width:40%;line-height:1.8} +.scene-orbit{position:absolute;inset:0;transform:translateY(var(--scene-scroll,0px));transition:transform .15s linear;pointer-events:none;z-index:1} +.scene-orbit i{position:absolute;display:block;border:1px solid var(--scene-accent);border-radius:50%;opacity:.18;transition:transform 1s var(--ease)} +.scene-orbit i:nth-child(1){width:60%;aspect-ratio:1;right:-14%;top:6%} +.scene-orbit i:nth-child(2){width:75%;aspect-ratio:1;right:-21%;top:-2%} +.scene-orbit i:nth-child(3){width:90%;aspect-ratio:1;right:-29%;top:-11%} +.scene-orbit i:nth-child(4){width:105%;aspect-ratio:1;right:-36%;top:-20%} +.scene-visual{position:absolute;z-index:2;transition:transform .9s var(--ease);will-change:transform} +.scene-visual img{display:block;width:100%;height:100%;object-fit:cover;object-position:top;max-width:none;box-shadow:0 15px 40px #0002} +.scene-annotation{position:absolute;font-family:monospace;font-size:7px;letter-spacing:1px;right:25px;top:50%;writing-mode:vertical-rl;z-index:3;opacity:.7} +.scene-bottom{position:absolute;left:30px;right:30px;bottom:24px;display:flex;justify-content:space-between;align-items:center;gap:15px;z-index:4;font-family:monospace;font-size:7px;letter-spacing:.7px} +.scene-bottom>span:first-child{opacity:.7;max-width:55%;line-height:1.6} +.scene-view{display:flex;gap:11px;align-items:center} +.scene-view b{display:grid;place-items:center;width:29px;height:29px;border-radius:50%;border:1px solid currentColor;font-family:Arial;font-size:18px;font-weight:400;transition:transform .45s var(--ease),background .3s} +.project-open:hover .scene-view b{transform:rotate(45deg);background:#ffffff15} +.project-open:hover .scene-visual{transform:translateY(-9px) scale(1.02)} +.project-open:hover .scene-orbit i{transform:scale(1.07)} +.scene--mobility .scene-visual{width:72%;height:40%;left:20%;top:51%;clip-path:polygon(0 0,100% 0,100% 100%,8% 100%);} +.scene--mobility .scene-visual img{object-position:center} +.scene--mobility .scene-phrase{top:45%;max-width:80%;font-size:7px} +.scene--pharmacy{--scene-bg:#e5ddf6;--scene-ink:#493167;--scene-accent:#8750d4;--scene-line:#5936790b} +.scene--pharmacy .scene-wordmark{font-family:var(--font);font-size:10.8cqw;line-height:1.13;letter-spacing:-4px} +.scene--pharmacy .scene-visual{width:32%;height:76%;right:11%;top:19%;border-radius:15px;overflow:hidden;box-shadow:0 20px 40px #49316720} +.scene--pharmacy .scene-visual img{height:auto} +.scene--pharmacy .scene-phrase{top:58%;max-width:42%} +.scene--pharmacy .scene-orbit i{border-width:38px;opacity:.10} +.scene--pharmacy .scene-orbit i:nth-child(n+3){display:none} +.scene--bems{--scene-bg:#172832;--scene-ink:#eef5eb;--scene-accent:#ccf37c;--scene-line:#c3efdb0c} +.scene--bems .scene-wordmark{font-size:17cqw;letter-spacing:-6px;top:76px} +.scene--bems .scene-phrase{top:36%;max-width:80%} +.scene--bems .scene-visual{width:76%;height:45%;left:12%;top:44%;box-shadow:0 20px 50px #0004} +.scene--bems .scene-visual img{object-fit:cover;object-position:top} +.scene--bems .scene-orbit i{border-radius:0;border-width:0;background:var(--scene-accent);width:7%;right:auto;aspect-ratio:auto;top:auto;bottom:5%;opacity:.055} +.scene--bems .scene-orbit i:nth-child(1){height:50%;left:11%}.scene--bems .scene-orbit i:nth-child(2){height:65%;left:22%}.scene--bems .scene-orbit i:nth-child(3){height:80%;left:33%}.scene--bems .scene-orbit i:nth-child(4){height:95%;left:44%} +.scene--givon{--scene-bg:#f0e9da;--scene-ink:#36372b;--scene-accent:#e77b34;--scene-line:#9251220b} +.scene--givon .scene-wordmark{font-size:18cqw;letter-spacing:-7px;top:76px;font-weight:600} +.scene--givon .scene-phrase{top:39%;max-width:75%} +.scene--givon .scene-orbit{left:40%;top:4%;right:-15%;bottom:12%;transform:rotate(-18deg) translateY(var(--scene-scroll,0px))} +.scene--givon .scene-orbit i{width:31%;height:57%;aspect-ratio:auto;background:var(--scene-accent);border:none;border-radius:100px;left:35%;top:8%;opacity:.13;transform-origin:center 80%} +.scene--givon .scene-orbit i:nth-child(2){transform:rotate(90deg)}.scene--givon .scene-orbit i:nth-child(3){transform:rotate(180deg)}.scene--givon .scene-orbit i:nth-child(4){transform:rotate(270deg)} +.scene--givon .scene-visual{left:18%;top:52%;width:94%;height:37%;overflow:hidden;border-top:3px solid #e77b34;box-shadow:0 20px 40px #66533215} +.scene--givon .scene-visual img{object-fit:cover;object-position:left top} +.scene--bispet{--scene-bg:#e1ebd5;--scene-ink:#33483a;--scene-accent:#769b4e;--scene-line:#39542e08} +.scene--bispet .scene-wordmark{top:82px;font-size:12cqw;letter-spacing:-4px;max-width:50%;line-height:.91} +.scene--bispet .scene-wordmark em{display:block} +.scene--bispet .scene-visual{width:32%;height:80%;right:12%;top:18%;overflow:hidden;border-radius:14px;box-shadow:0 20px 40px #3653261c} +.scene--bispet .scene-visual img{height:auto} +.scene--bispet .scene-phrase{top:60%;max-width:33%} +.scene--bispet .scene-orbit i{background:#a9c480;border:none;opacity:.24} +.scene--bispet .scene-orbit i:nth-child(1){width:40%;right:-8%;top:15%}.scene--bispet .scene-orbit i:nth-child(2){width:20%;right:35%;top:5%}.scene--bispet .scene-orbit i:nth-child(3){width:14%;right:20%;top:0}.scene--bispet .scene-orbit i:nth-child(4){width:11%;right:5%;top:5%} +.scene--asset{--scene-bg:#315de1;--scene-ink:#fff;--scene-accent:#cee990;--scene-line:#ffffff10} +.scene--asset .scene-wordmark{font-size:13cqw;letter-spacing:-4px;line-height:.97;top:78px} +.scene--asset .scene-wordmark em{display:block} +.scene--asset .scene-visual{display:none} +.scene--asset .scene-phrase{top:auto;bottom:23%;max-width:60%;font-size:9px} +.scene--asset .scene-orbit{left:25%;top:15%;transform:rotate(-30deg) translateY(var(--scene-scroll,0px))} +.scene--asset .scene-orbit i{border-radius:17%;border-width:1px;opacity:.6} +.scene--asset .scene-orbit i:nth-child(1){width:24%;right:30%;top:30%;background:#d5ee8119}.scene--asset .scene-orbit i:nth-child(2){width:42%;right:21%;top:19%;opacity:.4}.scene--asset .scene-orbit i:nth-child(3){width:60%;right:12%;top:8%;opacity:.25}.scene--asset .scene-orbit i:nth-child(4){width:78%;right:3%;top:-3%;opacity:.16} +/* Original typographic covers; source diagrams live in the case-study galleries. */ +.scene--signage{--scene-bg:#302639;--scene-ink:#fbf1e7;--scene-accent:#ffb28e;--scene-line:#fbe5dc12} +.scene--gps-building{--scene-bg:#dceae9;--scene-ink:#183e43;--scene-accent:#327c79;--scene-line:#245c5910} +.scene--signage .scene-wordmark,.scene--gps-building .scene-wordmark{top:17%;font-size:12cqw;letter-spacing:-.055em;line-height:1} +.scene--signage .scene-wordmark em,.scene--gps-building .scene-wordmark em{display:block} +.scene--signage .scene-phrase,.scene--gps-building .scene-phrase{top:auto;bottom:21%;max-width:45%;font-size:8px} +.scene-graphic{position:absolute;right:-3%;top:27%;width:64%;height:58%;z-index:1;transform:translateY(var(--scene-scroll,0px));transition:transform .7s var(--ease);pointer-events:none} +.scene-graphic svg{display:block;width:100%;height:100%;overflow:visible} +.scene--signage .signage-link{fill:none;stroke:var(--scene-accent);stroke-width:1;opacity:.4} +.scene--signage .signage-display{fill:var(--scene-bg);stroke:var(--scene-accent);stroke-width:1.2} +.scene--signage .signage-pane{fill:var(--scene-accent);opacity:.14} +.scene--signage .signage-content{fill:none;stroke:var(--scene-accent);stroke-width:1.5} +.scene--gps-building .scene-graphic{top:23%;right:-5%;height:65%;width:69%} +.scene--gps-building .gps-contour{fill:none;stroke:var(--scene-accent);stroke-width:1;opacity:.17} +.scene--gps-building .gps-route{fill:none;stroke:var(--scene-accent);stroke-width:2;stroke-dasharray:5 7;stroke-linecap:round} +.scene--gps-building .gps-point{fill:var(--scene-bg);stroke:var(--scene-accent);stroke-width:2} +.scene--gps-building .gps-pin{fill:var(--scene-ink);stroke:var(--scene-bg);stroke-width:2} +.scene--gps-building .gps-pin-center{fill:#cde89b} +/* A complete, independently addressable case study for every project. */ +.portfolio-page{--case-accent:var(--blue)} +.case--givon{--case-accent:#b95a21}.case--pharmacy{--case-accent:#7950b3}.case--bems{--case-accent:#56752e}.case--bispet{--case-accent:#577c39} +.case--signage{--case-accent:#a35438}.case--gps-building{--case-accent:#327c79} +.case-progress{position:fixed;left:0;top:0;height:3px;width:100%;background:var(--case-accent);transform:scaleX(0);transform-origin:left;z-index:80} +.case-header{height:88px;position:relative;border-bottom:1px solid var(--line);z-index:40;background:var(--paper)} +.case-header>.wrap{height:100%;display:flex;align-items:center;justify-content:space-between;gap:24px} +.case-back{font-size:12px;display:flex;align-items:center;gap:15px} +.case-back>span{font-size:24px;transition:transform .3s}.case-back:hover>span{transform:translate(-3px,-3px)} +.case-brand{font-family:Arial;font-size:27px;letter-spacing:-1.6px;font-weight:800}.case-brand>span{font-size:15px;font-weight:400;vertical-align:top;display:inline-block;margin:1px 0 0 5px} +.case-header-contact{font-size:12px;display:flex;align-items:center;gap:27px}.case-header-contact>span{font-size:22px} +.case-hero{padding:61px 0 0} +.case-eyebrow{display:flex;align-items:center;justify-content:space-between;gap:20px;font-family:monospace;font-size:10px;letter-spacing:1px;color:var(--muted)} +.case-eyebrow>span:last-child{font-size:9px} +.case-title-row{display:flex;align-items:center;justify-content:space-between;gap:55px;margin-top:44px} +.case-project-brand{font-family:Arial,var(--font);font-size:13px;letter-spacing:.6px;color:var(--case-accent);margin-bottom:19px;font-weight:600} +.case-title-row h1{font-size:clamp(35px,4.45vw,65px);font-weight:600;line-height:1.26;letter-spacing:-2.6px;max-width:950px;text-wrap:balance} +.case-hero-arrow{align-self:flex-end;font-family:Arial;font-weight:300;font-size:90px;line-height:1;color:var(--case-accent)} +.case-meta{margin:44px 0 0;padding:23px 0 32px;border-top:1px solid var(--line);display:grid;grid-template-columns:1.05fr 1.55fr 1fr;gap:35px} +.case-meta dt{font-family:monospace;font-size:8px;color:var(--muted);letter-spacing:.9px;margin-bottom:13px} +.case-meta dd{margin:0;font-size:12px;line-height:1.7} +.case-cover{width:100%;padding:0 24px;overflow:hidden} +.case-cover .project-scene{height:min(670px,51vw);aspect-ratio:auto;border-radius:6px} +.case-cover .scene-kicker{left:60px;right:60px;top:39px;font-size:10px} +.case-cover .scene-wordmark{left:60px;top:108px;font-size:clamp(80px,10.5vw,160px);letter-spacing:-7px} +.case-cover .scene-phrase{left:64px;top:65%;font-size:11px;max-width:36%} +.case-cover .scene-bottom{left:60px;right:60px;bottom:34px;font-size:9px} +.case-cover .scene-annotation{right:30px;font-size:8px} +.case-cover .scene--mobility .scene-wordmark{top:88px;font-size:10.7vw} +.case-cover .scene--mobility .scene-visual{width:52%;left:44%;height:55%;top:28%;clip-path:polygon(0 0,100% 0,100% 100%,6% 100%)} +.case-cover .scene--mobility .scene-phrase{top:auto;bottom:21%} +.case-cover .scene--pharmacy .scene-wordmark{font-size:10vw;letter-spacing:-7px} +.case-cover .scene--pharmacy .scene-visual,.case-cover .scene--bispet .scene-visual{width:23%;height:79%;top:13%;right:20%;border-radius:22px} +.case-cover .scene--pharmacy .scene-phrase{top:auto;bottom:23%;max-width:35%} +.case-cover .scene--bems .scene-wordmark{font-size:13.2vw;top:16%} +.case-cover .scene--bems .scene-phrase{top:auto;bottom:31%} +.case-cover .scene--bems .scene-visual{width:54%;height:60%;left:41%;top:32%} +.case-cover .scene--givon .scene-wordmark{font-size:15.5vw;top:16%} +.case-cover .scene--givon .scene-visual{left:42%;top:51%;width:70%;height:33%} +.case-cover .scene--givon .scene-phrase{top:auto;bottom:25%;max-width:36%} +.case-cover .scene--bispet .scene-wordmark{font-size:10.5vw;top:18%;letter-spacing:-6px} +.case-cover .scene--bispet .scene-phrase{top:auto;bottom:22%} +.case-cover .scene--asset .scene-wordmark{font-size:10.8vw;top:17%} +.case-cover .scene--asset .scene-phrase{top:auto;bottom:19%} +.case-cover .scene--asset .scene-orbit{left:33%;top:2%;bottom:8%} +.case-cover .scene--signage .scene-wordmark,.case-cover .scene--gps-building .scene-wordmark{top:17%;font-size:10.8vw;letter-spacing:-.055em} +.case-cover .scene--signage .scene-phrase,.case-cover .scene--gps-building .scene-phrase{top:auto;bottom:19%} +.case-cover .scene-graphic{right:3%;top:9%;width:57%;height:80%} +.case-nav{position:sticky;top:0;background:#f5f5eff5;backdrop-filter:blur(16px);border-bottom:1px solid var(--line);z-index:30;margin-top:28px} +.case-nav>.wrap{height:69px;display:flex;align-items:center;justify-content:space-between;gap:20px} +.case-nav>.wrap>span:first-child{font-family:Arial,var(--font);font-size:12px;font-weight:600} +.case-nav>.wrap>div{display:flex;align-items:center;gap:36px} +.case-nav a{font-size:12px;position:relative;padding:26px 0;color:var(--muted)} +.case-nav a:after{content:"";position:absolute;bottom:-1px;left:0;right:0;height:2px;background:var(--case-accent);transform:scaleX(0);transform-origin:left;transition:transform .3s} +.case-nav a[aria-current="location"]{color:var(--ink)}.case-nav a[aria-current="location"]:after{transform:scaleX(1)} +.case-reading-progress{font-family:monospace;font-size:10px;color:var(--muted);min-width:35px;text-align:right} +.case-section{padding-block:100px;scroll-margin-top:68px} +.case-section-label{display:flex;justify-content:space-between;gap:25px;align-items:center;margin-bottom:49px;font-size:10px;color:var(--muted)} +.case-section-label>span:first-child{font-family:monospace;letter-spacing:1px;color:var(--case-accent);font-size:10px} +.case-overview-grid{display:grid;grid-template-columns:1.1fr 1fr;gap:85px} +.case-overview-grid h2{font-size:39px;font-weight:500;line-height:1.45;letter-spacing:-1.9px;text-wrap:balance} +.case-overview-grid>div>p{font-size:15px;line-height:2;color:#677269} + +.case-features{margin-top:65px;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:40px} +.case-feature{border-top:1px solid var(--line);padding-top:24px} +.feature-index{font-family:monospace;font-size:12px;color:var(--case-accent)} +.case-feature h3{font-size:21px;line-height:1.4;letter-spacing:-.6px;margin-top:27px;font-weight:500} +.case-feature p{font-size:13px;color:var(--muted);line-height:1.9;max-width:330px;margin-top:15px} + +.case-screens{background:#e9ede4} +.case-screen-heading{display:flex;justify-content:space-between;gap:35px;align-items:flex-end} +.case-screen-heading h2{font-size:44px;line-height:1.35;font-weight:500;letter-spacing:-2px}.case-screen-heading h2>span{color:#7f8a76} +.case-screen-heading>p{font-size:13px;line-height:1.9;color:var(--muted)} +.screen-tabs{display:flex;gap:30px;flex-wrap:wrap;margin-top:44px;border-bottom:1px solid #c4cdbb} +.screen-tabs button{padding:17px 0;background:none;display:flex;align-items:center;gap:10px;position:relative;font-size:12px;color:#7c8872} +.screen-tabs button>span{font-family:monospace;font-size:9px} +.screen-tabs button:after{content:"";position:absolute;bottom:-1px;left:0;right:0;height:2px;background:var(--case-accent);transform:scaleX(0);transition:transform .3s} +.screen-tabs button[aria-selected="true"]{color:var(--ink)}.screen-tabs button[aria-selected="true"]:after{transform:scaleX(1)} +.screen-gallery{margin-top:30px} +.case-screen{margin:0} +.screen-canvas{min-height:320px;height:clamp(350px,40vw,610px);padding:clamp(20px,4vw,56px);display:grid;place-items:center;background:#f4f5ef;overflow:hidden} +.screen-canvas>img{max-width:100%;max-height:100%;width:auto;height:auto;object-fit:contain;box-shadow:0 18px 65px #23321c12} +.screen-portrait{background:#dfe6d6}.screen-portrait>img{height:100%;max-height:100%;width:auto;max-width:90%;object-fit:contain} +.case-screen figcaption{display:flex;align-items:center;gap:25px;margin-top:21px} +.case-screen figcaption>span{font-family:monospace;font-size:10px;color:var(--muted);white-space:nowrap} +.case-screen figcaption p{font-size:12px;line-height:1.8;color:#6d7866} +.case-screen figcaption a{font-size:10px;white-space:nowrap;margin-left:auto;color:#56664c;border-bottom:1px solid #b1bca6;padding-block:5px} +.case-source-note{font-size:9px;color:#87917e;margin-top:25px;text-align:right} + +.case-next{background:#e7eadf;overflow:hidden} +.case-next-link{display:flex;align-items:center;justify-content:space-between;padding-block:70px;gap:45px} +.case-next .tiny-label{font-size:9px;color:#75826a}.case-next h2{font-family:Arial,var(--font);font-size:clamp(42px,6vw,88px);font-weight:500;letter-spacing:-3px;margin-top:21px;transition:transform .7s var(--ease),color .3s} +.case-next p{font-size:14px;line-height:1.8;color:#7b8771;margin-top:18px} +.case-next-arrow{display:grid;place-items:center;flex:none;width:110px;height:110px;border-radius:50%;border:1px solid #9ba990;font-family:Arial;font-size:60px;font-weight:300;transition:transform .6s var(--ease),background .4s,border-color .4s} +.case-next-link:hover h2{transform:translateX(10px);color:var(--blue)}.case-next-link:hover .case-next-arrow{transform:rotate(45deg);background:var(--lime);border-color:var(--lime)} +.case-connect{display:flex;justify-content:space-between;align-items:flex-end;gap:40px;padding-block:88px} +.case-connect .tiny-label{font-size:9px;letter-spacing:1px;color:var(--blue);margin-bottom:20px} +.case-connect h2{font-size:42px;letter-spacing:-1.8px;font-weight:500}.case-connect>a{display:flex;align-items:center;gap:55px;padding-bottom:13px;border-bottom:1px solid var(--ink);font-size:15px;margin-bottom:7px}.case-connect>a>span{font-family:Arial;font-size:30px;transition:transform .4s}.case-connect>a:hover>span{transform:translate(5px,-5px)} +.case-footer{display:flex;justify-content:space-between;align-items:center;border-top:1px solid var(--line);padding-block:24px;gap:20px;font-size:9px;color:var(--muted)} +.portfolio-ready .case-enter{animation:case-enter .9s both var(--ease)} +.portfolio-ready .case-project-brand{animation-delay:.06s}.portfolio-ready .case-title-row h1{animation-delay:.14s}.portfolio-ready .case-hero-arrow{animation-delay:.25s}.portfolio-ready .case-meta{animation-delay:.23s}.portfolio-ready .case-cover{animation-delay:.32s} +@keyframes case-enter{from{opacity:0;transform:translateY(25px)}to{opacity:1;transform:translateY(0)}} +.portfolio-ready .case-reveal{opacity:0;transform:translateY(24px);transition:opacity .85s var(--ease),transform .85s var(--ease);transition-delay:var(--case-delay,0ms)} +.portfolio-ready .case-reveal.is-visible{opacity:1;transform:translateY(0)} +@media(max-width:1150px){ + .work .project-card h3{font-size:22px}.work .project-grid{gap:47px 26px} + .scene-wordmark{left:25px;top:62px;letter-spacing:-2.5px}.scene-kicker{left:25px;right:25px;top:24px;font-size:7px} + .scene-phrase{left:26px;font-size:7px;top:200px}.scene-bottom{left:25px;right:25px;font-size:6px;bottom:21px}.scene-view{gap:7px}.scene-view b{width:25px;height:25px;font-size:16px} + .scene--bems .scene-wordmark,.scene--givon .scene-wordmark{top:63px;letter-spacing:-4px} + .scene--bispet .scene-wordmark,.scene--asset .scene-wordmark{top:64px;letter-spacing:-3px} + .scene--pharmacy .scene-wordmark{letter-spacing:-2px}.scene-annotation{right:15px;font-size:6px} + .case-title-row h1{font-size:48px}.case-meta{gap:24px}.case-meta dd{font-size:11px} + .case-cover .scene-wordmark,.case-cover .scene--mobility .scene-wordmark{top:85px;left:43px} + .case-cover .scene-kicker,.case-cover .scene-bottom{left:43px;right:43px} + .case-cover .scene-phrase{left:46px;font-size:9px} + .case-section{padding-block:80px}.case-overview-grid{gap:45px}.case-overview-grid h2{font-size:33px}.case-overview-grid>div>p{font-size:14px}.case-features{gap:25px}.case-feature h3{font-size:19px} + +} +@media(max-width:850px){ + .work .project-card h3{font-size:20px}.work .project-grid{gap:42px 22px}.project-scene{aspect-ratio:1.14} + .scene-kicker{left:20px;right:20px;font-size:6px;letter-spacing:.6px;top:21px}.scene-wordmark{left:20px;top:59px;font-size:11cqw;letter-spacing:-2px} + .scene--pharmacy .scene-wordmark{font-size:12cqw}.scene--pharmacy .scene-visual,.scene--bispet .scene-visual{width:35%;top:22%;right:9%;height:68%} + .scene-phrase{left:21px;font-size:6px;letter-spacing:.6px}.scene--mobility .scene-phrase{top:43%} + .scene--givon .scene-wordmark{font-size:19cqw;letter-spacing:-4px}.scene--bems .scene-wordmark{font-size:19cqw}.scene--bems .scene-phrase{top:39%}.scene--bems .scene-visual{top:47%;height:40%} + .scene--bispet .scene-wordmark{font-size:13cqw}.scene--asset .scene-wordmark{font-size:15cqw} + .scene-bottom{left:20px;right:20px;font-size:6px}.scene-bottom>span:first-child{max-width:48%;font-size:5px}.scene-view{font-size:5px;letter-spacing:.4px}.scene-view b{width:24px;height:24px}.scene-annotation{font-size:5px;right:10px} + .case-header{height:77px}.case-back,.case-header-contact{font-size:10px;gap:10px}.case-brand{font-size:24px} + .case-hero{padding-top:43px}.case-title-row{margin-top:33px;gap:25px}.case-title-row h1{font-size:39px;letter-spacing:-1.9px}.case-hero-arrow{font-size:61px} + .case-meta{margin-top:32px;grid-template-columns:1fr 1.3fr 1fr;gap:19px}.case-meta dd{font-size:10px}.case-meta dt{font-size:7px} + .case-cover{padding-inline:15px}.case-cover .project-scene{height:440px}.case-cover .scene-wordmark,.case-cover .scene--mobility .scene-wordmark{font-size:11vw;left:32px;letter-spacing:-4px;top:86px} + .case-cover .scene--givon .scene-wordmark{font-size:16vw}.case-cover .scene--bems .scene-wordmark{font-size:14vw}.case-cover .scene--pharmacy .scene-wordmark{font-size:10.5vw;letter-spacing:-4px} + .case-cover .scene-kicker,.case-cover .scene-bottom{left:32px;right:32px}.case-cover .scene-kicker{font-size:8px}.case-cover .scene-bottom{font-size:7px}.case-cover .scene-bottom>span:first-child{font-size:7px} + .case-cover .scene-phrase{left:33px;font-size:7px}.case-cover .scene--pharmacy .scene-visual,.case-cover .scene--bispet .scene-visual{width:26%;right:16%} + .case-nav>.wrap{height:60px}.case-nav>.wrap>div{gap:26px}.case-nav a{font-size:11px;padding:22px 0}.case-nav>.wrap>span:first-child{font-size:10px}.case-reading-progress{display:none} + .case-section{padding-block:68px}.case-section-label{margin-bottom:35px}.case-overview-grid{gap:35px;grid-template-columns:1fr 1fr}.case-overview-grid h2{font-size:28px;letter-spacing:-1.3px}.case-overview-grid>div>p{font-size:13px} + .case-features{gap:20px;margin-top:45px}.case-feature h3{font-size:17px;margin-top:23px}.case-feature p{font-size:12px} + .case-screen-heading h2{font-size:35px}.case-screen-heading>p{font-size:11px} + + .case-connect h2{font-size:35px}.case-connect>a{font-size:13px;gap:25px}.case-next-arrow{width:85px;height:85px;font-size:44px} +} +@media(max-width:600px){ + .work .project-grid{gap:39px;grid-template-columns:1fr;margin-top:28px}.work .project-card h3{font-size:23px;line-height:1.4;margin:12px 0 12px}.work .project-scope{display:flex;flex-wrap:wrap;margin-top:15px;padding-top:12px}.work .project-scope p{font-size:10px}.work .project-scope>span{font-size:9px;margin:0} + .project-scene{aspect-ratio:1.12}.scene-wordmark{font-size:12cqw;top:61px}.scene-kicker{font-size:6px}.scene--mobility .scene-phrase{top:44%}.scene--mobility .scene-visual{top:52%;height:36%;width:78%;left:15%} + .scene--givon .scene-wordmark{font-size:21cqw;top:65px}.scene--givon .scene-phrase{top:41%}.scene--pharmacy .scene-wordmark{font-size:13cqw}.scene--bispet .scene-wordmark{font-size:15cqw;top:65px}.scene--asset .scene-wordmark{font-size:15cqw} + .scene--bems .scene-wordmark{font-size:21cqw}.scene-bottom{font-size:6px;bottom:18px}.scene-bottom>span:first-child{font-size:6px}.scene-view{font-size:6px} + .scene--signage .scene-wordmark,.scene--gps-building .scene-wordmark{top:19%;font-size:14cqw} + .scene--signage .scene-phrase,.scene--gps-building .scene-phrase{font-size:6px;bottom:23%;max-width:42%} + .scene--signage .scene-graphic,.scene--gps-building .scene-graphic{top:34%;height:51%} + .case-header{height:72px}.case-header>.wrap{gap:12px}.case-back{font-size:10px;gap:8px}.case-back>span{font-size:18px}.case-header-contact{font-size:10px;gap:8px}.case-header-contact>span{font-size:17px}.case-brand{font-size:21px}.case-brand>span{display:none} + .case-hero{padding-top:35px}.case-eyebrow{font-size:7px;letter-spacing:.6px;gap:10px}.case-eyebrow>span:last-child{font-size:7px} + .case-title-row{margin-top:28px;gap:0}.case-project-brand{font-size:11px;margin-bottom:15px}.case-title-row h1{font-size:33px;line-height:1.35;letter-spacing:-1.7px;text-wrap:pretty}.case-hero-arrow{display:none} + .case-meta{grid-template-columns:1fr 1fr;gap:22px 20px;margin-top:28px;padding:23px 0 28px}.case-meta>div:nth-child(2){grid-column:1 / -1;grid-row:2}.case-meta dt{font-size:7px;margin-bottom:8px}.case-meta dd{font-size:11px} + .case-cover{padding-inline:10px}.case-cover .project-scene{height:440px}.case-cover .scene-kicker{top:27px;left:24px;right:24px;font-size:7px} + .case-cover .scene-wordmark,.case-cover .scene--mobility .scene-wordmark{font-size:14.5vw;left:24px;top:78px;letter-spacing:-3px} + .case-cover .scene--pharmacy .scene-wordmark{font-size:14vw;top:84px;left:24px;letter-spacing:-3px}.case-cover .scene--pharmacy .scene-visual,.case-cover .scene--bispet .scene-visual{width:36%;height:70%;right:8%;top:19%;border-radius:13px} + .case-cover .scene--pharmacy .scene-phrase{max-width:40%;bottom:30%} + .case-cover .scene--givon .scene-wordmark{font-size:25vw;top:83px}.case-cover .scene--givon .scene-visual{left:15%;top:55%;width:110%;height:31%}.case-cover .scene--givon .scene-phrase{top:42%;bottom:auto;max-width:70%} + .case-cover .scene--mobility .scene-visual{width:87%;left:10%;height:37%;top:48%;clip-path:none}.case-cover .scene--mobility .scene-phrase{top:42%;bottom:auto;max-width:80%} + .case-cover .scene--bems .scene-wordmark{font-size:24vw;top:84px}.case-cover .scene--bems .scene-visual{width:87%;left:10%;height:39%;top:48%}.case-cover .scene--bems .scene-phrase{top:41%;bottom:auto;max-width:80%} + .case-cover .scene--bispet .scene-wordmark{font-size:17vw;top:88px}.case-cover .scene--bispet .scene-phrase{bottom:30%;max-width:34%} + .case-cover .scene--asset .scene-wordmark{font-size:20vw;top:87px}.case-cover .scene--asset .scene-orbit{left:8%;top:24%;right:-15%}.case-cover .scene--asset .scene-phrase{bottom:22%;max-width:50%} + .case-cover .scene--signage .scene-wordmark,.case-cover .scene--gps-building .scene-wordmark{top:82px;font-size:17vw} + .case-cover .scene--signage .scene-phrase,.case-cover .scene--gps-building .scene-phrase{bottom:23%;max-width:43%} + .case-cover .scene-graphic{top:36%;right:-4%;width:69%;height:48%} + .case-cover .scene-phrase{left:26px;font-size:7px}.case-cover .scene-bottom{left:24px;right:24px;bottom:24px;font-size:6px}.case-cover .scene-bottom>span:first-child{font-size:6px;max-width:50%}.case-cover .scene-annotation{right:12px;font-size:6px} + .case-nav{margin-top:18px}.case-nav>.wrap{height:56px}.case-nav>.wrap>span:first-child{display:none}.case-nav>.wrap>div{width:100%;justify-content:space-between;gap:15px}.case-nav a{font-size:11px;padding:20px 0} + .case-section{padding-block:61px;scroll-margin-top:50px}.case-section-label{margin-bottom:29px;font-size:9px}.case-section-label>span:first-child{font-size:8px;letter-spacing:.7px} + .case-overview-grid{grid-template-columns:1fr;gap:26px}.case-overview-grid h2{font-size:30px;letter-spacing:-1.4px;line-height:1.4}.case-overview-grid>div>p{font-size:13px;line-height:1.95} + .case-features{grid-template-columns:1fr;gap:24px;margin-top:32px}.case-feature{display:grid;grid-template-columns:28px 1fr;gap:12px;padding-top:22px}.feature-index{grid-row:span 2;font-size:10px;padding-top:4px}.case-feature h3{margin:0;font-size:20px}.case-feature p{margin:0;font-size:12px;max-width:none} + + .case-screen-heading{display:block}.case-screen-heading h2{font-size:31px;line-height:1.4;letter-spacing:-1.4px}.case-screen-heading>p{margin-top:18px;font-size:12px}.screen-tabs{gap:22px;margin-top:25px;flex-wrap:nowrap;overflow-x:auto}.screen-tabs button{font-size:10px;white-space:nowrap;gap:7px;padding-block:15px}.screen-tabs button>span{font-size:8px} + .screen-gallery{margin-top:23px}.screen-canvas{min-height:230px;height:300px;padding:16px}.screen-portrait{height:440px;padding:24px}.case-screen figcaption{gap:10px 17px;align-items:flex-start;flex-wrap:wrap;margin-top:17px}.case-screen figcaption>span{font-size:8px;padding-top:4px}.case-screen figcaption p{font-size:11px;flex:1;min-width:200px;line-height:1.8}.case-screen figcaption a{font-size:9px;margin-left:auto}.case-source-note{font-size:8px;margin-top:18px} + + .case-next-link{padding-block:45px;gap:25px}.case-next .tiny-label{font-size:7px}.case-next h2{font-size:39px;letter-spacing:-1.7px;margin-top:16px;overflow-wrap:anywhere}.case-next p{font-size:11px;margin-top:14px}.case-next-arrow{width:52px;height:52px;font-size:30px} + .case-connect{display:block;padding-block:56px}.case-connect h2{font-size:33px;letter-spacing:-1.5px}.case-connect .tiny-label{font-size:8px;margin-bottom:17px}.case-connect>a{font-size:13px;max-width:250px;margin-top:29px;justify-content:space-between}.case-footer{font-size:8px;gap:14px;flex-wrap:wrap}.case-footer>span{order:3;width:100%}.case-footer>a:last-child{margin-left:auto} +} +@media(max-width:360px){.work .project-card h3{font-size:21px}.scene-kicker{font-size:5px}.scene-bottom>span:first-child{font-size:5px}.scene-view{font-size:5px}.scene-wordmark{letter-spacing:-2px}.case-title-row h1{font-size:29px}.case-back,.case-header-contact{font-size:9px}.case-brand{font-size:19px}.case-cover .project-scene{height:400px}.case-next h2{font-size:32px}.case-screen figcaption p{min-width:160px}} +@media(prefers-reduced-motion:reduce){ + ::view-transition-old(root),::view-transition-new(root){animation:none!important} + .portfolio-ready .case-enter{animation:none}.portfolio-ready .case-reveal{opacity:1;transform:none;transition:none} + .scene-surface,.scene-orbit,.scene-visual,.scene-orbit i,.scene-graphic{transform:none!important;transition:none!important} +} +/* Long admin screens keep their native proportions; small screens can pan for detail. */ +.screen-pan-hint{display:none} +.case--givon .screen-canvas{height:auto;min-height:0;padding:40px;display:flex;align-items:center;justify-content:center} +.case--givon .screen-canvas>img{width:100%;height:auto;max-height:none} +@media(max-width:600px){ + .screen-canvas:not(.screen-portrait){display:flex;align-items:center;justify-content:flex-start;height:auto;min-height:190px;max-height:430px;padding:16px;overflow:auto;overscroll-behavior-x:contain} + .screen-canvas:not(.screen-portrait)>img{width:640px;min-width:640px;max-width:none;height:auto;max-height:none;flex-shrink:0} + .screen-pan-hint{display:block;font-size:9px;color:#7c8b70;margin-top:11px;text-align:right} + .screen-portrait + .screen-pan-hint{display:none} +} diff --git a/portfolio.js b/portfolio.js new file mode 100644 index 0000000..14fc580 --- /dev/null +++ b/portfolio.js @@ -0,0 +1,194 @@ +/* Shared, dependency-free motion for the portfolio index and case-study pages. */ +(() => { + 'use strict'; + const $ = (selector, root = document) => root.querySelector(selector); + const $$ = (selector, root = document) => [...root.querySelectorAll(selector)]; + const reduced = window.matchMedia('(prefers-reduced-motion: reduce)'); + const finePointer = window.matchMedia('(hover: hover) and (pointer: fine)'); + const isCase = document.body.classList.contains('portfolio-page'); + document.documentElement.classList.add('portfolio-ready'); + + // A tiny tilt belongs to each artwork, without affecting its readable title. + const scenes = $$('.project-scene'); + const visibleScenes = new Set(); + const pendingPointers = new Map(); + let pointerFrame = 0; + function flushPointers() { + pendingPointers.forEach((point, scene) => { + scene.style.setProperty('--tilt-x', `${point.x}deg`); + scene.style.setProperty('--tilt-y', `${point.y}deg`); + }); + pendingPointers.clear(); + pointerFrame = 0; + } + scenes.forEach(scene => { + scene.addEventListener('pointermove', event => { + if (reduced.matches || !finePointer.matches || event.pointerType === 'touch') return; + const bounds = scene.getBoundingClientRect(); + pendingPointers.set(scene, { + x: ((event.clientY - bounds.top) / bounds.height - .5) * -3.6, + y: ((event.clientX - bounds.left) / bounds.width - .5) * 4.8 + }); + if (!pointerFrame) pointerFrame = requestAnimationFrame(flushPointers); + }); + scene.addEventListener('pointerleave', () => { + pendingPointers.delete(scene); + scene.style.setProperty('--tilt-x', '0deg'); + scene.style.setProperty('--tilt-y', '0deg'); + }); + }); + if ('IntersectionObserver' in window) { + const observer = new IntersectionObserver(entries => { + entries.forEach(entry => entry.isIntersecting ? visibleScenes.add(entry.target) : visibleScenes.delete(entry.target)); + requestUpdate(); + }, { rootMargin: '100px' }); + scenes.forEach(scene => observer.observe(scene)); + } + + // Reveal the case study in short, staggered sequences; content stays readable without JS. + $$('.case-features .case-reveal').forEach((element, index) => element.style.setProperty('--case-delay', `${index * 65}ms`)); + if ('IntersectionObserver' in window) { + const reveals = new IntersectionObserver(entries => { + entries.forEach(entry => { + if (!entry.isIntersecting) return; + entry.target.classList.add('is-visible'); + reveals.unobserve(entry.target); + }); + }, { threshold: .08, rootMargin: '0px 0px -25px 0px' }); + $$('.case-reveal').forEach(element => reveals.observe(element)); + } else { + $$('.case-reveal').forEach(element => element.classList.add('is-visible')); + } + + const progress = $('.case-progress'); + const readingProgress = $('.case-reading-progress'); + const caseLinks = $$('.case-nav a'); + const caseSections = caseLinks.map(link => $(link.getAttribute('href'))); + let updatePending = false; + function updateView() { + if (!reduced.matches) { + visibleScenes.forEach(scene => { + const bounds = scene.getBoundingClientRect(); + const relative = (bounds.top + bounds.height / 2 - window.innerHeight / 2) / window.innerHeight; + scene.style.setProperty('--scene-scroll', `${Math.max(-22, Math.min(22, relative * -22))}px`); + }); + } + if (isCase) { + const length = document.documentElement.scrollHeight - window.innerHeight; + const portion = length > 0 ? Math.min(1, Math.max(0, window.scrollY / length)) : 0; + progress.style.transform = `scaleX(${portion})`; + readingProgress.textContent = `${String(Math.round(portion * 100)).padStart(2, '0')}%`; + let active = 0; + caseSections.forEach((section, index) => { if (section.getBoundingClientRect().top <= 145) active = index; }); + caseLinks.forEach((link, index) => { + if (index === active) link.setAttribute('aria-current', 'location'); + else link.removeAttribute('aria-current'); + }); + } + updatePending = false; + } + function requestUpdate() { + if (!updatePending) { updatePending = true; requestAnimationFrame(updateView); } + } + window.addEventListener('scroll', requestUpdate, { passive: true }); + window.addEventListener('resize', requestUpdate, { passive: true }); + reduced.addEventListener('change', () => { + scenes.forEach(scene => { + scene.style.removeProperty('--scene-scroll'); + scene.style.removeProperty('--tilt-x'); + scene.style.removeProperty('--tilt-y'); + }); + requestUpdate(); + }); + requestUpdate(); + + // Preserve the selected index filter across a case-study round trip. + if (!isCase) { + const filterButtons = $$('.filter'); + filterButtons.forEach(button => button.addEventListener('click', () => { + try { sessionStorage.setItem('tront-portfolio-filter', button.dataset.filter); } catch { /* Storage is optional. */ } + })); + const returning = performance.getEntriesByType('navigation')[0]?.type === 'back_forward' || /\/projects\/[^/]+\.html$/.test(new URL(document.referrer || location.href).pathname); + if (returning) { + try { + const selected = sessionStorage.getItem('tront-portfolio-filter'); + const target = filterButtons.find(button => button.dataset.filter === selected); + target?.click(); + } catch { /* Normal navigation still works without storage. */ } + } + // A direct project anchor should always point to a visible card. + if (location.hash.startsWith('#project-')) { + const card = document.getElementById(location.hash.slice(1)); + if (card?.hidden) filterButtons.find(button => button.dataset.filter === 'all')?.click(); + } + } + + const backLink = $('[data-back-to-work]'); + backLink?.addEventListener('click', event => { + if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || event.button !== 0) return; + try { + const referrer = new URL(document.referrer); + const home = new URL('../index.html', location.href); + const isHome = referrer.origin === home.origin && (referrer.pathname === home.pathname || referrer.pathname === home.pathname.replace('index.html', '')); + if (isHome && history.length > 1) { event.preventDefault(); history.back(); } + } catch { /* A direct visit uses the ordinary index link. */ } + }); + + // Accessible galleries with real source screenshots and interruptible transitions. + const tabs = $$('.screen-tabs [role="tab"]'); + const gallery = $('.screen-gallery'); + const screens = $$('.case-screen'); + let galleryAnimation; + let screenAnimation; + function finishGallery() { + if (galleryAnimation) { + galleryAnimation.onfinish = null; + galleryAnimation.cancel(); + galleryAnimation = null; + } + screenAnimation?.cancel(); + gallery?.style.removeProperty('height'); + gallery?.style.removeProperty('overflow'); + } + function showScreen(index, focus = false) { + if (!tabs[index]) return; + if (tabs[index].getAttribute('aria-selected') === 'true') { if (focus) tabs[index].focus(); return; } + const start = gallery.getBoundingClientRect().height; + finishGallery(); + tabs.forEach((tab, i) => { + tab.setAttribute('aria-selected', String(i === index)); + tab.tabIndex = i === index ? 0 : -1; + screens[i].hidden = i !== index; + screens[i].classList.toggle('is-current', i === index); + }); + const next = screens[index]; + const img = $('img', next); + img.loading = 'eager'; + const end = next.getBoundingClientRect().height; + if (!reduced.matches && gallery.animate) { + gallery.style.height = `${start}px`; + gallery.style.overflow = 'hidden'; + galleryAnimation = gallery.animate([{ height: `${start}px` }, { height: `${end}px` }], { duration: 480, easing: 'cubic-bezier(.22,1,.36,1)' }); + screenAnimation = next.animate([{ opacity: 0, transform: 'translateY(12px)' }, { opacity: 1, transform: 'translateY(0)' }], { duration: 380, easing: 'cubic-bezier(.22,1,.36,1)' }); + galleryAnimation.onfinish = finishGallery; + } + if (focus) tabs[index].focus(); + requestUpdate(); + } + tabs.forEach((tab, index) => { + tab.addEventListener('click', () => showScreen(index)); + tab.addEventListener('pointerenter', () => { $('img', screens[index]).loading = 'eager'; }, { once: true }); + tab.addEventListener('keydown', event => { + let target; + if (event.key === 'ArrowRight') target = (index + 1) % tabs.length; + if (event.key === 'ArrowLeft') target = (index + tabs.length - 1) % tabs.length; + if (event.key === 'Home') target = 0; + if (event.key === 'End') target = tabs.length - 1; + if (target !== undefined) { event.preventDefault(); showScreen(target, true); } + }); + }); + if (gallery) { + window.addEventListener('resize', finishGallery, { passive: true }); + reduced.addEventListener('change', finishGallery); + } +})(); diff --git a/projects/asset.html b/projects/asset.html new file mode 100644 index 0000000..09a3acc --- /dev/null +++ b/projects/asset.html @@ -0,0 +1,51 @@ + + + + + + + WideAsset S/W — RF 자산관리 솔루션 구축 | TRONT + + + + + + + + + +
+
+
+
TRONT / SELECTED WORKPROJECT 06 — 08
+

WideAsset S/W

RF 자산관리 솔루션 구축

+
PROJECT PERIOD
2014.01 — 2014.12
OUR ROLE
RFID Tag Reader 인터페이스 개발 · 데이터 조회 및 업데이트 모듈 개발
PLATFORM
PDA / WideAsset S/W
+
+
+ +
+ +
+

IT 자산과 사무기기를
RF로 연결하다.

+

국내향 RF 기반 IT 자산 및 사무기기 관리 솔루션 개발

+
+
+
01

서비스 대상

국내 IT 자산과 사무기기를 관리하기 위한 RF 기반 솔루션입니다.

+
02

주요 기능

RFID Tag Reader와 PDA, WideAsset S/W를 연동해 자산 데이터를 조회하고 업데이트합니다.

+
03

적용 기술

C#과 C++를 사용하고 DB를 연동해 RF 기반 자산관리 솔루션을 구현했습니다.

+
+
+

화면으로 만나는
프로젝트의 디테일.

자산과 장비의 연결을
시스템 구성도로 살펴보세요.

프로젝트 화면 자료입니다.

+
KEEP EXPLORING / NEXT PROJECT

Signage 솔루션

빌딩(건물) 영상홍보 통합안내 시스템

+

LET'S BUILD WHAT'S NEXT.

다음 프로젝트,
함께 만들어가요.

함께 프로젝트 만들기
+
+ + + \ No newline at end of file diff --git a/projects/bems.html b/projects/bems.html new file mode 100644 index 0000000..6804a47 --- /dev/null +++ b/projects/bems.html @@ -0,0 +1,53 @@ + + + + + + + BEMS — 빌딩 에너지 통합 관리 시스템 BEMS 개발 | TRONT + + + + + + + + + +
+
+
+
TRONT / SELECTED WORKPROJECT 03 — 08
+

BEMS

빌딩 에너지 통합 관리 시스템 BEMS 개발

+
PROJECT PERIOD
2023.03.01 — 2024.02.28
OUR ROLE
사용자 웹 · 백엔드 개발 · 관리자 화면 개발
PLATFORM
웹
+
+
+ +
+ +
+

건물의 센서와 전력을
하나의 관제 화면에서.

+

빌딩 내 온/습도 센서, 공조기 가동 센서 및 사용 전력 관리를 전력 관리 관제 프로그램 개발

+
+
+
01

서비스 대상

빌딩 내 온·습도 센서, 공조기 가동 센서와 사용 전력을 관리하는 관제 프로그램입니다.

+
02

주요 기능

온·습도와 공조기 가동 정보를 확인하고, 건물의 사용 전력을 관리합니다.

+
03

적용 기술

Vue.js로 사용자 웹을 개발하고, Spring Boot로 백엔드와 관리자 화면을 구현했습니다.

+
+
+

화면으로 만나는
프로젝트의 디테일.

서비스의 기능과 흐름을
실제 개발 화면으로 살펴보세요.

프로젝트 화면 자료입니다.

+
KEEP EXPLORING / NEXT PROJECT

GivOn

블록체인 기반 기부 플랫폼 관리자 페이지 개발

+

LET'S BUILD WHAT'S NEXT.

다음 프로젝트,
함께 만들어가요.

함께 프로젝트 만들기
+
+ + + \ No newline at end of file diff --git a/projects/bispet.html b/projects/bispet.html new file mode 100644 index 0000000..b03c82b --- /dev/null +++ b/projects/bispet.html @@ -0,0 +1,52 @@ + + + + + + + BISPET — 애견인들 위한 커뮤니티 개발 | TRONT + + + + + + + + + +
+
+
+
TRONT / SELECTED WORKPROJECT 05 — 08
+

BISPET

애견인들 위한 커뮤니티 개발

+
PROJECT PERIOD
2024.07.01 — 2025.03.31
OUR ROLE
모바일 앱 · 백엔드 개발 · 관리자 화면 개발
PLATFORM
모바일 앱 / 관리자 화면
+
+
+ +
+ +
+

반려동물과의 일상을
기록하고 나누다.

+

반려 동물의 정보 관리 및 반려동물과 생활 기록(피드 작성, 일정, 산책 활동) 기록한 SNS 앱 개발

+
+
+
01

서비스 대상

반려동물과 생활하는 애견인을 위한 SNS 앱입니다.

+
02

주요 기능

반려동물 정보를 관리하고, 피드 작성과 일정, 산책 활동을 통해 함께하는 생활을 기록합니다.

+
03

적용 기술

Flutter로 모바일 앱을 개발하고, Spring Boot로 백엔드와 관리자 화면을 구현했습니다.

+
+
+

화면으로 만나는
프로젝트의 디테일.

서비스의 기능과 흐름을
실제 개발 화면으로 살펴보세요.

프로젝트 화면 자료입니다.

+
KEEP EXPLORING / NEXT PROJECT

WideAsset S/W

RF 자산관리 솔루션 구축

+

LET'S BUILD WHAT'S NEXT.

다음 프로젝트,
함께 만들어가요.

함께 프로젝트 만들기
+
+ + + \ No newline at end of file diff --git a/projects/givon.html b/projects/givon.html new file mode 100644 index 0000000..6e8de5b --- /dev/null +++ b/projects/givon.html @@ -0,0 +1,53 @@ + + + + + + + GivOn — 블록체인 기반 기부 플랫폼 관리자 페이지 개발 | TRONT + + + + + + + + + +
+
+
+
TRONT / SELECTED WORKPROJECT 04 — 08
+

GivOn

블록체인 기반 기부 플랫폼 관리자 페이지 개발

+
PROJECT PERIOD
2025.08.01 — 2025.10.31
OUR ROLE
백엔드 개발 · 관리자 화면 개발
PLATFORM
관리자 화면
+
+
+ +
+ +
+

기부와 봉사의 운영을
관리자 화면에 담다.

+

기부를 위한 단체 관리, 기부/봉사 프로젝트 생성 및 기부 현황를 관리를 위한 관리자 화면 개발

+
+
+
01

서비스 대상

블록체인 기반 기부 플랫폼 GivOn을 운영하기 위한 관리자 화면입니다.

+
02

주요 기능

기부 단체를 관리하고, 기부·봉사 프로젝트를 생성하며 기부 현황을 확인합니다.

+
03

적용 기술

Spring Boot로 백엔드를 개발하고, Vue.js로 관리자 화면을 구현했습니다.

+
+
+

화면으로 만나는
프로젝트의 디테일.

서비스의 기능과 흐름을
실제 개발 화면으로 살펴보세요.

프로젝트 화면 자료입니다.

+
KEEP EXPLORING / NEXT PROJECT

BISPET

애견인들 위한 커뮤니티 개발

+

LET'S BUILD WHAT'S NEXT.

다음 프로젝트,
함께 만들어가요.

함께 프로젝트 만들기
+
+ + + \ No newline at end of file diff --git a/projects/gps-building.html b/projects/gps-building.html new file mode 100644 index 0000000..46dbd92 --- /dev/null +++ b/projects/gps-building.html @@ -0,0 +1,58 @@ + + + + + + + GPS 솔루션 — GPS 트래킹 기반 위치확인 시스템 개발 | TRONT + + + + + + + + + +
+
+
+
TRONT / SELECTED WORKPROJECT 08 — 08
+

GPS 솔루션

GPS 트래킹 기반 위치확인 시스템 개발

+
PROJECT PERIOD
2010.03 — 2010.11
OUR ROLE
GPS Manager · Location Manager · Navigation Manager 기반 디바이스 드라이버 개발
PLATFORM
옥외 위치 추적기 / 사용자 애플리케이션
+
+
+ +
+ +
+

현재 위치에서
다음 목적지까지.

+

GPS 트래킹 방식을 활용, 옥외 위치 추적기를 개발하고 사용자의 현재 위치 확인, 목적지 설정, 이동 방향 추적등 기능을 수행할 수 있도록 시스템 구축

+
+
+
01

서비스 대상

야외 활동, 주차, 여행 등 옥외 환경에서 현재 위치와 목적지를 확인하는 사용자를 위한 위치 추적 시스템입니다.

+
02

주요 기능

현재 위치를 수신·저장하고 목적지 정보를 설정합니다. 저장한 위치를 바탕으로 이동 방향을 추적하고 표시합니다.

+
03

적용 기술

GPS 기본 칩셋과 MCU를 활용하고, GPS Manager·Location Manager·Navigation Manager 기반 디바이스 드라이버로 하드웨어와 사용자 애플리케이션을 연동했습니다.

+
+
+

화면으로 만나는
프로젝트의 디테일.

위치 수신부터 이동 방향 안내까지,
시스템의 동작 구조를 살펴보세요.

프로젝트 화면 자료입니다.

+
KEEP EXPLORING / NEXT PROJECT

현대 오토에버

국내향 및 해외향 내비게이션 HMI 개발

+

LET'S BUILD WHAT'S NEXT.

다음 프로젝트,
함께 만들어가요.

함께 프로젝트 만들기
+
+ + + \ No newline at end of file diff --git a/projects/mobility.html b/projects/mobility.html new file mode 100644 index 0000000..3976430 --- /dev/null +++ b/projects/mobility.html @@ -0,0 +1,81 @@ + + + + + + + 현대 오토에버 — 국내향 및 해외향 내비게이션 HMI 개발 | TRONT + + + + + + + + + +
+
+
+
TRONT / SELECTED WORKPROJECT 01 — 08
+

현대 오토에버

국내향 및 해외향 내비게이션 HMI 개발

+
PROJECT PERIOD
2018.05.01 — 2025.03.31
OUR ROLE
내비게이션 GUI 개발 · Interface layer 개발
PLATFORM
차량용 내비게이션
+
+
+ +
+ +
+

운전자와 차량을 잇는
내비게이션 인터페이스.

+

현대, 기아 자동차 국내향 및 해외향(북미, 유럽, 남미, 중동, 호주) HMI 외주 개발 및 Engine App 개발

+
+
+
01

서비스 대상

현대·기아 자동차의 국내향과 북미·유럽·남미·중동·호주향 차량용 내비게이션을 대상으로 한 프로젝트입니다.

+
02

주요 기능

내비게이션 GUI와 Interface layer, Engine App을 개발했습니다.

+
03

적용 기술

C++와 Qt로 내비게이션 GUI를 개발하고, C++로 Interface layer를 구현했습니다.

+
+
+
+
+ +

화면으로 만나는
프로젝트의 디테일.

서비스의 기능과 흐름을
실제 개발 화면으로 살펴보세요.

+
+ + + + +
+ +

프로젝트 화면 자료입니다.

+
+
+
KEEP EXPLORING / NEXT PROJECT

모두의약국

약사님들의 커뮤니티 개발

+

LET'S BUILD WHAT'S NEXT.

다음 프로젝트,
함께 만들어가요.

함께 프로젝트 만들기
+
+ + + \ No newline at end of file diff --git a/projects/pharmacy.html b/projects/pharmacy.html new file mode 100644 index 0000000..f533a00 --- /dev/null +++ b/projects/pharmacy.html @@ -0,0 +1,53 @@ + + + + + + + 모두의약국 — 약사님들의 커뮤니티 개발 | TRONT + + + + + + + + + +
+
+
+
TRONT / SELECTED WORKPROJECT 02 — 08
+

모두의약국

약사님들의 커뮤니티 개발

+
PROJECT PERIOD
2022.09.01 — 2025.03.31
OUR ROLE
모바일 앱 · 사용자 웹 · 백엔드 개발 · 관리자 화면 개발
PLATFORM
모바일 앱 / 웹
+
+
+ +
+ +
+

약사의 일에 필요한 정보를
하나의 커뮤니티로.

+

약사님들을 위한 구인/구직, 약국 매물, 약 정보, 커뮤니티를 제공하는 모바일 앱, 웹, 관리자 개발

+
+
+
01

서비스 대상

약사를 위한 정보와 커뮤니티를 모바일 앱과 웹으로 제공하는 서비스입니다.

+
02

주요 기능

구인·구직, 약국 매물, 약 정보와 커뮤니티 기능을 제공합니다.

+
03

적용 기술

Flutter로 모바일 앱을, Vue.js로 사용자 웹을 개발하고, Spring Boot로 백엔드와 관리자 화면을 구현했습니다.

+
+
+

화면으로 만나는
프로젝트의 디테일.

서비스의 기능과 흐름을
실제 개발 화면으로 살펴보세요.

프로젝트 화면 자료입니다.

+
KEEP EXPLORING / NEXT PROJECT

BEMS

빌딩 에너지 통합 관리 시스템 BEMS 개발

+

LET'S BUILD WHAT'S NEXT.

다음 프로젝트,
함께 만들어가요.

함께 프로젝트 만들기
+
+ + + \ No newline at end of file diff --git a/projects/signage.html b/projects/signage.html new file mode 100644 index 0000000..0dd3cd6 --- /dev/null +++ b/projects/signage.html @@ -0,0 +1,65 @@ + + + + + + + Signage 솔루션 — 빌딩(건물) 영상홍보 통합안내 시스템 | TRONT + + + + + + + + + +
+
+
+
TRONT / SELECTED WORKPROJECT 07 — 08
+

Signage 솔루션

빌딩(건물) 영상홍보 통합안내 시스템

+
PROJECT PERIOD
2012.02 — 2012.11
OUR ROLE
중앙 관리 시스템 및 서버 구축 · 스케줄 및 파일 전송 로직 구현 · Infotrans Editor 및 Multi Manager 기능 구현
PLATFORM
멀티비전 / DID TV / 중앙 관리 시스템
+
+
+ +
+ +
+

하나의 콘텐츠를,
건물 곳곳의 화면으로.

+

빌딩(건물) 내 멀티비전 및 DID(Digital Information Display) TV 스크린을 활용, 영상 홍보 및 통합 안내 콘텐츠를 중앙에서 관리/분산 송출 하는 시스템 구축

+
+
+
01

서비스 대상

빌딩 내 멀티비전과 DID TV 스크린으로 영상 홍보 및 통합 안내 콘텐츠를 제공하는 시스템입니다.

+
02

주요 기능

콘텐츠를 중앙에서 관리하고 네트워크로 분산 송출합니다. Infotrans Editor로 콘텐츠를 제작·편집하고 Multi Manager로 일일·월간 스케줄을 편성합니다.

+
03

적용 기술

C++, JSP/Servlet, ASP.NET, Oracle을 사용해 중앙 관리 시스템과 서버, 스케줄 및 파일 전송 로직, 콘텐츠 편집·관리 기능을 구현했습니다.

+
+
+

화면으로 만나는
프로젝트의 디테일.

콘텐츠 제작부터 화면 송출까지,
시스템의 연결 구조를 살펴보세요.

프로젝트 화면 자료입니다.

+
KEEP EXPLORING / NEXT PROJECT

GPS 솔루션

GPS 트래킹 기반 위치확인 시스템 개발

+

LET'S BUILD WHAT'S NEXT.

다음 프로젝트,
함께 만들어가요.

함께 프로젝트 만들기
+
+ + + \ No newline at end of file diff --git a/script.js b/script.js new file mode 100644 index 0000000..a29bbb7 --- /dev/null +++ b/script.js @@ -0,0 +1,410 @@ +/* TRONT static website. Contact details below are intentionally temporary. */ +(() => { + 'use strict'; + + const CONTACT = Object.freeze({ + email: 'hello@tront.example', // Replace with the company's actual email. + phone: '053-000-0000', // Replace with the company's actual phone. + temporary: true // Set false after replacing both details. + }); + const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)'); + const $ = (selector, root = document) => root.querySelector(selector); + const $$ = (selector, root = document) => [...root.querySelectorAll(selector)]; + + document.documentElement.classList.add('js-ready'); + $('#copyright-year').textContent = new Date().getFullYear(); + $$('[data-contact-email]').forEach(link => { + link.href = `mailto:${CONTACT.email}`; + if (link.closest('.contact-row')) link.textContent = CONTACT.email; + }); + $$('[data-contact-phone]').forEach(link => { + link.href = `tel:${CONTACT.phone.replace(/[^+\d]/g, '')}`; + link.textContent = CONTACT.phone; + }); + $('.contact-placeholder').hidden = !CONTACT.temporary; + + // Mobile navigation is available without changing desktop navigation. + const menuButton = $('.menu-toggle'); + const mobileMenu = $('#mobile-menu'); + function closeMenu(returnFocus = false) { + menuButton.setAttribute('aria-expanded', 'false'); + menuButton.setAttribute('aria-label', '메뉴 열기'); + mobileMenu.hidden = true; + if (returnFocus) menuButton.focus(); + } + menuButton.addEventListener('click', () => { + const open = menuButton.getAttribute('aria-expanded') !== 'true'; + menuButton.setAttribute('aria-expanded', String(open)); + menuButton.setAttribute('aria-label', open ? '메뉴 닫기' : '메뉴 열기'); + mobileMenu.hidden = !open; + }); + $$('a', mobileMenu).forEach(link => link.addEventListener('click', () => closeMenu())); + document.addEventListener('keydown', event => { + if (event.key === 'Escape' && !mobileMenu.hidden) closeMenu(true); + }); + document.addEventListener('click', event => { + if (!mobileMenu.hidden && !event.target.closest('.site-header')) closeMenu(); + }); + window.matchMedia('(min-width: 851px)').addEventListener('change', event => { + if (event.matches) closeMenu(); + }); + + // Scroll effects are scheduled once per animation frame. + const header = $('.site-header'); + const progress = $('.scroll-progress'); + const navLinks = $$('.desktop-nav a'); + const navSections = navLinks.map(link => $(link.getAttribute('href'))); + let scrollPending = false; + function updateScroll() { + const top = window.scrollY; + const length = document.documentElement.scrollHeight - window.innerHeight; + progress.style.transform = `scaleX(${length > 0 ? top / length : 0})`; + header.classList.toggle('is-scrolled', top > 15); + let active = -1; + navSections.forEach((section, index) => { + if (section.getBoundingClientRect().top <= 160) active = index; + }); + if ($('#contact').getBoundingClientRect().top <= 160) active = -1; + navLinks.forEach((link, index) => { + link.classList.toggle('is-active', index === active); + if (index === active) link.setAttribute('aria-current', 'location'); + else link.removeAttribute('aria-current'); + }); + scrollPending = false; + } + window.addEventListener('scroll', () => { + if (!scrollPending) { scrollPending = true; requestAnimationFrame(updateScroll); } + }, { passive: true }); + updateScroll(); + + function countUp(element) { + const target = Number(element.dataset.count); + if (target === 2008 || reduceMotion.matches) return; + const start = performance.now(); + function step(now) { + const amount = Math.min((now - start) / 1100, 1); + element.textContent = Math.round(target * (1 - Math.pow(1 - amount, 3))); + if (amount < 1) requestAnimationFrame(step); + } + requestAnimationFrame(step); + } + if ('IntersectionObserver' in window) { + const revealObserver = new IntersectionObserver(entries => { + entries.forEach(entry => { + if (!entry.isIntersecting) return; + entry.target.classList.add('is-visible'); + $$('[data-count]', entry.target).forEach(countUp); + revealObserver.unobserve(entry.target); + }); + }, { threshold: 0.08, rootMargin: '0px 0px -20px 0px' }); + $$('.reveal').forEach(element => revealObserver.observe(element)); + } else { + $$('.reveal').forEach(element => element.classList.add('is-visible')); + } + + // Keep native details semantics; coordinate height, content and artwork motion. + const services = $$('.service'); + const serviceAnimations = new Map(); + const expertiseArt = $('.expertise-art'); + const capabilityModes = [ + ['mobility', 'HMI', 'SMART MOBILITY'], + ['monitoring', 'IoT', 'INDUSTRIAL MONITORING'], + ['web3', 'WEB3', 'CONNECTED ASSETS'], + ['platform', 'APP', 'COMMUNITY & SAAS'], + ['interface', 'UX', 'DIGITAL INTERFACE'] + ]; + let artworkTextAnimation; + + function selectCapability(index) { + const mode = capabilityModes[index]; + expertiseArt.dataset.mode = mode ? mode[0] : 'idle'; + $('.capability-index').textContent = mode ? `0${index + 1} / 05` : '— / 05'; + $('.capability-name').textContent = mode ? mode[2] : 'EXPLORE OUR EXPERTISE'; + $('.sculpture-core').textContent = mode ? mode[1] : '+'; + $$('.capability-dots i').forEach((dot, i) => dot.classList.toggle('is-active', i === index)); + artworkTextAnimation?.cancel(); + if (!reduceMotion.matches) { + artworkTextAnimation = $('.sculpture-core').animate( + [{ opacity: 0, filter: 'blur(6px)', transform: 'translateY(8px)' }, { opacity: 1, filter: 'blur(0)', transform: 'translateY(0)' }], + { duration: 480, easing: 'cubic-bezier(.22,1,.36,1)' } + ); + } + } + + function settleService(service) { + const current = serviceAnimations.get(service); + if (current) { + current.height.onfinish = null; + current.height.cancel(); + current.content.forEach(animation => animation.cancel()); + serviceAnimations.delete(service); + } + service.open = service.classList.contains('is-expanded'); + service.style.removeProperty('height'); + service.style.removeProperty('overflow'); + } + + function expandService(service, expanded) { + if (service.classList.contains('is-expanded') === expanded) return; + const startHeight = service.getBoundingClientRect().height; + settleService(service); + const summary = $('summary', service); + const content = $('.service-content', service); + service.classList.toggle('is-expanded', expanded); + summary.setAttribute('aria-expanded', String(expanded)); + content.inert = !expanded; + service.open = true; + const border = parseFloat(getComputedStyle(service).borderTopWidth) + parseFloat(getComputedStyle(service).borderBottomWidth); + const endHeight = expanded ? service.getBoundingClientRect().height : summary.getBoundingClientRect().height + border; + if (reduceMotion.matches || !service.animate) { + service.open = expanded; + return; + } + service.style.height = `${startHeight}px`; + service.style.overflow = 'hidden'; + const heightAnimation = service.animate( + [{ height: `${startHeight}px` }, { height: `${endHeight}px` }], + { duration: 580, easing: 'cubic-bezier(.22,1,.36,1)' } + ); + const contentAnimations = [$('.service-content > p', service), ...$$('.tags > span', service)].map((element, index) => element.animate( + expanded + ? [{ opacity: 0, transform: 'translateY(14px)' }, { opacity: 1, transform: 'translateY(0)' }] + : [{ opacity: 1, transform: 'translateY(0)' }, { opacity: 0, transform: 'translateY(-7px)' }], + { duration: expanded ? 350 : 160, delay: expanded ? 80 + index * 45 : 0, easing: 'cubic-bezier(.22,1,.36,1)', fill: 'both' } + )); + serviceAnimations.set(service, { height: heightAnimation, content: contentAnimations }); + heightAnimation.onfinish = () => settleService(service); + } + + services.forEach((service, index) => { + const summary = $('summary', service); + const content = $('.service-content', service); + service.classList.toggle('is-expanded', service.open); + summary.setAttribute('aria-expanded', String(service.open)); + content.id = `service-content-${index + 1}`; + summary.setAttribute('aria-controls', content.id); + content.inert = !service.open; + summary.addEventListener('click', event => { + event.preventDefault(); + const expanded = !service.classList.contains('is-expanded'); + services.forEach(other => expandService(other, other === service && expanded)); + selectCapability(expanded ? index : -1); + }); + }); + window.addEventListener('resize', () => services.forEach(settleService), { passive: true }); + reduceMotion.addEventListener('change', () => { + services.forEach(settleService); + artworkTextAnimation?.cancel(); + }); + + const cards = $$('.project-card'); + const filters = $$('.filter'); + filters.forEach(button => button.addEventListener('click', () => { + filters.forEach(filter => { + const selected = filter === button; + filter.classList.toggle('is-active', selected); + filter.setAttribute('aria-pressed', String(selected)); + }); + let visible = 0; + cards.forEach(card => { + const show = button.dataset.filter === 'all' || button.dataset.filter === card.dataset.category; + card.hidden = !show; + if (show) { card.classList.add('is-visible'); visible++; } + }); + $('.project-count').textContent = `${visible} PROJECT${visible === 1 ? '' : 'S'}`; + updateScroll(); + })); + + const history = { + 2008: { label: 'THE BEGINNING', title: '기술로 여는 첫 번째 길', description: '2008년 12월 1일, 주식회사 트론트의 여정이 시작되었습니다. DDNS 서버, 그룹웨어, LMS 원격강의 지원 시스템과 차량용 미들웨어 개발로 기반을 다졌습니다.', tags: ['DDNS Server', 'Groupware', 'LMS'] }, + 2012: { label: 'NEW CONNECTIONS', title: '현장과 데이터를 연결하다', description: '디지털 사이니지와 NFC 자산 관리, 청라 U-CITY 환경 서비스 등으로 기술의 영역을 확장했습니다. 이후 RFID 기반 IT 자산 관리 솔루션으로 연결의 범위를 넓혔습니다.', tags: ['Digital Signage', 'NFC / RFID', 'U-CITY'] }, + 2018: { label: 'MOVING FORWARD', title: '세계의 도로 위로, 기술의 확장', description: '현대오토에버 국내외 내비게이션 HMI 개발을 시작했습니다. C++와 Qt 기반 GUI 및 인터페이스 기술로 다양한 시장의 차량용 사용자 경험 구현에 참여했습니다.', tags: ['Hyundai AutoEver', 'C++ / Qt', 'Navigation HMI'] }, + 2022: { label: 'EVERYDAY IMPACT', title: '기술이 일상에 가까워지는 순간', description: '약사 커뮤니티 모두의약국 개발을 시작으로 전문 플랫폼 역량을 확장했습니다. 빌딩 에너지 통합 관제 BEMS와 반려동물 커뮤니티 BISPET 등 산업과 일상을 위한 프로젝트를 이어갔습니다.', tags: ['모두의약국', 'BEMS', 'BISPET'] }, + 2025: { label: "WHAT'S NEXT", title: '더 넓은 연결, 다음의 가능성', description: '블록체인 기반 기부 플랫폼 GivOn 관리자 개발과 소규모 기관차 관제 프로그램 개발로 기술의 쓰임을 넓혀갑니다.', tags: ['Web3', '관제 시스템', '디지털 플랫폼'] } + }; + const yearTabs = $$('.timeline-tabs [role="tab"]'); + function selectYear(button, focus = false) { + const year = button.dataset.year; + const data = history[year]; + yearTabs.forEach(tab => { + tab.setAttribute('aria-selected', String(tab === button)); + tab.tabIndex = tab === button ? 0 : -1; + }); + $('#timeline-panel').setAttribute('aria-labelledby', button.id); + $('.timeline-year').textContent = year; + $('.timeline-detail .eyebrow').textContent = data.label; + $('.timeline-detail h3').textContent = data.title; + $('.timeline-description').textContent = data.description; + $('.timeline-detail .tags').replaceChildren(...data.tags.map(tag => { + const span = document.createElement('span'); span.textContent = tag; return span; + })); + if (focus) button.focus(); + } + yearTabs.forEach((button, index) => { + button.addEventListener('click', () => selectYear(button)); + button.addEventListener('keydown', event => { + let target; + if (event.key === 'ArrowRight') target = (index + 1) % yearTabs.length; + if (event.key === 'ArrowLeft') target = (index + yearTabs.length - 1) % yearTabs.length; + if (event.key === 'Home') target = 0; + if (event.key === 'End') target = yearTabs.length - 1; + if (target !== undefined) { event.preventDefault(); selectYear(yearTabs[target], true); } + }); + }); + + let toastTimer; + function notify(message) { + const toast = $('.toast'); + toast.textContent = message; + toast.classList.add('is-visible'); + clearTimeout(toastTimer); + toastTimer = setTimeout(() => toast.classList.remove('is-visible'), 3200); + } + $('.copy-email').addEventListener('click', async () => { + try { + if (!navigator.clipboard?.writeText) throw new Error('Clipboard unavailable'); + await navigator.clipboard.writeText(CONTACT.email); + notify('이메일 주소를 복사했습니다.'); + } catch { + const range = document.createRange(); + range.selectNodeContents($('.contact-row [data-contact-email]')); + const selection = window.getSelection(); + selection.removeAllRanges(); selection.addRange(range); + notify('이메일 주소를 선택했습니다. 복사 단축키를 사용해 주세요.'); + } + }); + + // Original Canvas artwork: an evolving, connected torus. No 3D library. + const canvas = $('#connection-canvas'); + const ctx = canvas.getContext('2d'); + const motionButton = $('.motion-toggle'); + let motionPaused = reduceMotion.matches; + let artVisible = true; + let frame = 0; + let previousTime = 0; + let rotation = 0; + let width = 0, height = 0; + let pointerX = 0, pointerY = 0, tiltX = 0, tiltY = 0; + let ringSteps = 100, tubeSteps = 36; + let basePoints = []; + + function updateMotionState() { + document.body.classList.toggle('motion-paused', motionPaused); + motionButton.setAttribute('aria-pressed', String(motionPaused)); + motionButton.setAttribute('aria-label', motionPaused ? '그래픽 애니메이션 재생' : '그래픽 애니메이션 일시정지'); + motionButton.firstElementChild.textContent = motionPaused ? '▷' : 'Ⅱ'; + cancelAnimationFrame(frame); + if (!motionPaused && artVisible && !document.hidden) { previousTime = 0; frame = requestAnimationFrame(animate); } + else if (ctx) draw(); + } + motionButton.addEventListener('click', () => { motionPaused = !motionPaused; updateMotionState(); }); + reduceMotion.addEventListener('change', event => { motionPaused = event.matches; updateMotionState(); }); + + function buildPoints() { + basePoints = []; + ringSteps = width < 450 ? 76 : 100; + tubeSteps = width < 450 ? 30 : 36; + for (let u = 0; u < ringSteps; u++) { + const a = u / ringSteps * Math.PI * 2; + const radius = 1.04 + .085 * Math.cos(a * 3); + for (let v = 0; v < tubeSteps; v++) { + const b = v / tubeSteps * Math.PI * 2; + const twist = b + a * 2; + const tube = .385 + .025 * Math.sin(a * 3); + basePoints.push({ + x: (radius + tube * Math.cos(twist)) * Math.cos(a), + y: (radius + tube * Math.cos(twist)) * Math.sin(a), + z: tube * Math.sin(twist) + .10 * Math.sin(a * 3), + nx: Math.cos(twist) * Math.cos(a), ny: Math.cos(twist) * Math.sin(a), nz: Math.sin(twist) + }); + } + } + } + function rotate(x, y, z, rx, ry, rz) { + const y1 = y * Math.cos(rx) - z * Math.sin(rx); + const z1 = y * Math.sin(rx) + z * Math.cos(rx); + const x2 = x * Math.cos(ry) + z1 * Math.sin(ry); + const z2 = -x * Math.sin(ry) + z1 * Math.cos(ry); + return [x2 * Math.cos(rz) - y1 * Math.sin(rz), x2 * Math.sin(rz) + y1 * Math.cos(rz), z2]; + } + function draw() { + if (!ctx || !width || !height) return; + ctx.clearRect(0, 0, width, height); + const scale = Math.min(width * .29, height * .30); + const centerX = width * .5, centerY = height * .5; + const rx = .64 + tiltY * .15, ry = -.32 + tiltX * .15, rz = -.56 + rotation * .07; + const points = basePoints.map(point => { + const [x, y, z] = rotate(point.x, point.y, point.z, rx, ry, rz); + const normal = rotate(point.nx, point.ny, point.nz, rx, ry, rz); + const perspective = 4.8 / (4.8 - z); + return { x: centerX + x * scale * perspective, y: centerY + y * scale * perspective, z, light: Math.max(0, normal[0] * -.35 + normal[1] * -.55 + normal[2] * .76) }; + }); + const faces = []; + for (let u = 0; u < ringSteps; u++) { + for (let v = 0; v < tubeSteps; v++) { + const a = points[u * tubeSteps + v]; + const b = points[((u + 1) % ringSteps) * tubeSteps + v]; + const c = points[((u + 1) % ringSteps) * tubeSteps + (v + 1) % tubeSteps]; + const d = points[u * tubeSteps + (v + 1) % tubeSteps]; + faces.push({ a, b, c, d, z: (a.z + b.z + c.z + d.z) / 4, light: (a.light + b.light + c.light + d.light) / 4, stripe: v % 3 === 0 }); + } + } + faces.sort((a, b) => a.z - b.z); + faces.forEach(face => { + const light = 27 + face.light * 39; + ctx.beginPath(); + ctx.moveTo(face.a.x, face.a.y);ctx.lineTo(face.b.x, face.b.y);ctx.lineTo(face.c.x, face.c.y);ctx.lineTo(face.d.x, face.d.y);ctx.closePath(); + ctx.fillStyle = `hsl(${225 + face.light * 4} 88% ${light}%)`; + ctx.fill(); + ctx.lineWidth = face.stripe ? .65 : .35; + ctx.strokeStyle = `hsl(228 91% ${Math.min(81, light + (face.stripe ? 20 : 6))}% / ${face.stripe ? .62 : .35})`; + ctx.stroke(); + }); + // Fine orbital path and lime satellite contrast with the blue surface. + ctx.save();ctx.translate(centerX, centerY);ctx.rotate(-.54); + ctx.beginPath();ctx.ellipse(0, 0, scale * 1.67, scale * .72, 0, 0, Math.PI * 2); + ctx.strokeStyle = 'rgba(123, 145, 107, .25)';ctx.lineWidth = .7;ctx.stroke(); + const orbit = 2.77 + rotation * .11; + const sx = Math.cos(orbit) * scale * 1.67, sy = Math.sin(orbit) * scale * .72; + const radius = Math.max(9, scale * .085); + const gradient = ctx.createRadialGradient(sx - radius * .35, sy - radius * .4, 0, sx, sy, radius); + gradient.addColorStop(0, '#edf7b6');gradient.addColorStop(.45, '#d5ed7e');gradient.addColorStop(1, '#8da742'); + ctx.beginPath();ctx.arc(sx, sy, radius, 0, Math.PI * 2);ctx.fillStyle = gradient;ctx.fill();ctx.restore(); + } + function resizeArt() { + const rect = canvas.getBoundingClientRect(); + width = rect.width; height = rect.height; + const ratio = Math.min(window.devicePixelRatio || 1, 1.75); + canvas.width = Math.round(width * ratio);canvas.height = Math.round(height * ratio); + if (!ctx) return; + ctx.setTransform(ratio, 0, 0, ratio, 0, 0); + buildPoints();draw(); + } + function animate(now) { + if (motionPaused || !artVisible || document.hidden) return; + if (now - previousTime >= 40) { + const elapsed = previousTime ? Math.min(now - previousTime, 80) : 40; + rotation += elapsed * .00025; + tiltX += (pointerX - tiltX) * .055;tiltY += (pointerY - tiltY) * .055; + draw();previousTime = now; + } + frame = requestAnimationFrame(animate); + } + if (ctx) { + resizeArt(); + if ('ResizeObserver' in window) new ResizeObserver(resizeArt).observe(canvas); + else window.addEventListener('resize', resizeArt); + $('.hero-art').addEventListener('pointermove', event => { + if (event.pointerType === 'touch' || motionPaused) return; + const rect = canvas.getBoundingClientRect(); + pointerX = (event.clientX - rect.left) / rect.width - .5; + pointerY = (event.clientY - rect.top) / rect.height - .5; + }); + $('.hero-art').addEventListener('pointerleave', () => { pointerX = 0; pointerY = 0; }); + if ('IntersectionObserver' in window) new IntersectionObserver(entries => { + artVisible = entries[0].isIntersecting;updateMotionState(); + }).observe(canvas); + document.addEventListener('visibilitychange', updateMotionState); + updateMotionState(); + } else { motionButton.hidden = true; } +})(); diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..2ccd38e --- /dev/null +++ b/styles.css @@ -0,0 +1,169 @@ +@charset "UTF-8"; + +:root{--paper:#f5f5ef;--ink:#192322;--muted:#727b73;--blue:#2556e8;--lime:#d5ee81;--line:#d8ddd2;--font:"Pretendard","Apple SD Gothic Neo","Noto Sans KR","Malgun Gothic",Arial,sans-serif;--ease:cubic-bezier(.22,1,.36,1)} +*{box-sizing:border-box}html{scroll-behavior:smooth;scroll-padding-top:104px}body{margin:0;background:var(--paper);color:var(--ink);font-family:var(--font);-webkit-font-smoothing:antialiased;word-break:keep-all}body.modal-open{overflow:hidden}button,a{-webkit-tap-highlight-color:transparent}button,input{font:inherit}button{color:inherit}button,a,summary{touch-action:manipulation}a{color:inherit;text-decoration:none}button{cursor:pointer}img{max-width:100%;display:block}p,h1,h2,h3{margin:0}button{border:0}button:focus-visible,a:focus-visible,summary:focus-visible,[tabindex="0"]:focus-visible{outline:3px solid var(--blue);outline-offset:6px}::selection{background:var(--lime);color:var(--ink)}[hidden]{display:none!important}.wrap{width:min(1320px,calc(100% - 112px));margin-inline:auto}.section{padding-block:112px}.tiny-label,.eyebrow,.section-label,.side-note{font-family:Arial,var(--font);font-size:11px;font-weight:600;letter-spacing:1.35px}.eyebrow{display:flex;align-items:center;gap:10px}.muted{color:var(--muted)}.skip-link{position:fixed;top:-70px;left:20px;padding:16px;background:var(--ink);color:white;z-index:100}.skip-link:focus{top:12px}.scroll-progress{height:3px;position:fixed;top:0;left:0;z-index:101;background:var(--blue);transform-origin:left;width:100%;transform:scaleX(0)} +.site-header{height:96px;position:sticky;top:0;z-index:30;background:rgba(245,245,239,.93);backdrop-filter:blur(18px);border-bottom:1px solid transparent;transition:border-color .25s}.site-header.is-scrolled{border-color:var(--line)}.header-inner{height:100%;display:flex;align-items:center;justify-content:space-between;gap:32px}.brand{width:138px;flex:none}.brand img{width:138px;height:auto;mix-blend-mode:multiply}.desktop-nav{display:flex;gap:36px;margin-left:70px}.desktop-nav a{font-size:14px;position:relative;padding-block:10px;font-weight:600}.desktop-nav a:after{content:"";position:absolute;height:3px;width:3px;border-radius:50%;bottom:0;left:50%;background:var(--blue);transform:scale(0);transition:transform .2s}.desktop-nav a:hover,.desktop-nav a.is-active{color:var(--blue)}.desktop-nav a.is-active:after{transform:scale(1.3)}.header-contact{font-size:13px;font-weight:700;border:1px solid var(--ink);border-radius:50px;padding:13px 19px;display:flex;gap:25px;align-items:center;transition:background .2s,color .2s}.header-contact:hover{background:var(--ink);color:var(--paper)}.header-contact span{font-size:20px;line-height:16px}.menu-toggle{display:none}.mobile-menu{display:none} +.hero{position:relative;min-height:704px;display:grid;grid-template-columns:1.12fr 1fr;align-items:center;padding-block:58px 100px;isolation:isolate}.hero-content{position:relative;z-index:2}.hero .eyebrow{font-size:10px;letter-spacing:1.25px;margin-bottom:31px}.status-dot{width:7px;height:7px;background:var(--blue);border-radius:50%;box-shadow:0 0 0 4px #2556e810}.hero h1{font-size:clamp(40px,4.35vw,68px);font-weight:800;line-height:1.25;letter-spacing:-3.3px;white-space:nowrap}.hero h1>span{color:var(--blue)}.hero-description{font-size:16px;line-height:1.85;color:#636c66;margin-top:25px}.hero-actions{display:flex;align-items:center;gap:28px;margin-top:34px}.button{padding:18px 24px;display:inline-flex;align-items:center;justify-content:space-between;gap:40px;font-weight:600;font-size:13px;border-radius:4px;transition:background .2s,transform .25s}.button-dark{color:#fff;background:var(--ink)}.button-dark:hover{background:var(--blue);transform:translateY(-3px)}.button span{font-size:21px;line-height:1}.text-link{display:inline-flex;align-items:center;gap:18px;font-size:13px;font-weight:600}.text-link>span{font-size:20px;transition:transform .25s}.text-link:hover>span{transform:translateY(3px)}.text-link small{font-size:10px;font-weight:400;color:var(--muted)}.hero-bottom{margin-top:54px;display:flex;align-items:center;gap:22px}.hero-bottom>.tiny-label{font-size:9px;line-height:1.5;width:67px;color:#6c756c;letter-spacing:1.2px}.hero-bottom strong{font-family:Arial,sans-serif;font-size:33px;font-weight:500;letter-spacing:-2px;display:flex;align-items:center;gap:13px}.small-star{color:var(--blue);font-size:29px}.hero-note{font-size:11px;line-height:1.5;padding-left:21px;border-left:1px solid var(--line);color:#7b837b}.hero-art{height:535px;width:112%;margin-left:-4%;position:relative;z-index:1;align-self:center}.art-grid{position:absolute;inset:0;background-image:linear-gradient(#cbd1c329 1px,transparent 1px),linear-gradient(90deg,#cbd1c329 1px,transparent 1px);background-size:44px 44px;mask-image:radial-gradient(ellipse,black 25%,transparent 70%)}#connection-canvas{position:absolute;inset:0;width:100%;height:100%;filter:drop-shadow(0 30px 22px #17498517)}.art-coordinate{position:absolute;font-family:monospace;font-size:9px;color:#7c877e;letter-spacing:.5px}.coordinate-top{right:24px;top:10px}.orbit-label{position:absolute;display:flex;align-items:center;gap:8px;font-size:8px;letter-spacing:1px;font-family:monospace;color:#3e554b;background:rgba(245,245,239,.83);padding:8px 10px;border:1px solid #d3dbce;border-radius:2px;box-shadow:0 5px 18px #233c1205}.orbit-label span{width:5px;height:5px;background:var(--blue);border-radius:50%}.orbit-label-one{top:26%;right:1%}.orbit-label-two{bottom:26%;left:4%}.orbit-label-two span{background:#8da938}.art-caption{position:absolute;bottom:12px;left:10%;right:5%;display:flex;align-items:center;gap:12px;font-family:monospace;font-size:8px;line-height:1.6;color:#647366;letter-spacing:.8px}.crosshair{font-size:29px;font-weight:300;color:var(--blue)}.art-index{margin-left:auto}.scroll-cue{position:absolute;bottom:31px;left:0;display:flex;gap:15px;align-items:center;font-size:9px;font-family:Arial,sans-serif;letter-spacing:1.4px;color:#6c746c}.scroll-line{width:30px;height:1px;background:#a9b1a4}.motion-toggle{position:absolute;right:0;bottom:25px;width:28px;height:28px;border:1px solid var(--line);border-radius:50%;background:transparent;font-size:10px;color:var(--muted)} +.ticker{overflow:hidden;border-block:1px solid var(--line);padding-block:24px;background:#eeefe8}.ticker-track{display:flex;width:max-content;animation:marquee 55s linear infinite}.ticker-track>span{white-space:nowrap;font-family:Arial,sans-serif;font-size:14px;letter-spacing:1.2px;font-weight:600;display:flex;align-items:center}.ticker i{font-style:normal;color:var(--blue);font-size:24px;margin-inline:37px;font-weight:400}@keyframes marquee{to{transform:translateX(-50%)}}.motion-paused .ticker-track{animation-play-state:paused} +.section-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:57px}.section-label{font-size:11px;letter-spacing:1.5px;display:flex;gap:17px;align-items:center}.section-label>span{color:var(--blue);font-family:monospace;font-size:12px}.side-note{font-family:var(--font);font-size:10px;letter-spacing:.4px;color:var(--muted);font-weight:400}h2{font-size:clamp(35px,3.8vw,54px);font-weight:700;line-height:1.3;letter-spacing:-2.3px}.about-layout{display:grid;grid-template-columns:1fr 1.15fr;gap:90px}.about-statement{padding-top:14px}.about-asterisk{font-size:88px;line-height:1;color:var(--blue);display:block;margin-bottom:32px}.about-statement>p{font-family:Arial,sans-serif;font-size:43px;line-height:1.1;font-weight:800;letter-spacing:-1.7px}.about-statement>span:last-child{display:block;font-size:12px;color:var(--muted);margin-top:18px}.about-copy h2{font-size:42px;margin-bottom:24px}.about-copy>p{font-size:14px;color:#727c72;line-height:1.95;margin-top:16px;max-width:540px}.about-copy .text-link{margin-top:27px;border-bottom:1px solid #abb3a6;padding-block:8px}.facts{border-top:1px solid var(--line);display:grid;grid-template-columns:repeat(3,1fr);gap:35px;margin-top:74px;padding-top:32px}.fact{border-right:1px solid var(--line);padding-left:16px}.fact:first-child{padding-left:0}.fact:last-child{border:0}.fact>.tiny-label{font-size:9px;font-weight:500;letter-spacing:1px;color:var(--muted)}.fact strong{font-family:Arial,sans-serif;font-size:73px;font-weight:500;letter-spacing:-4px;line-height:1.2;display:block;margin:15px 0 10px}.fact-unit{font-family:var(--font);font-size:15px;letter-spacing:0;margin-left:10px;font-weight:500}.fact p{font-size:12px;color:#697167}.fact small{font-size:9px;margin-left:8px;color:#7a837a} +.expertise{background:#172725;color:var(--paper);position:relative;overflow:hidden} +.expertise .section-label>span{color:var(--lime)} +.expertise .side-note{color:#97a79d} +.expertise-layout{display:grid;grid-template-columns:.85fr 1.3fr;gap:80px;align-items:start} +.expertise-intro h2{font-size:49px} +.expertise-intro>p{font-size:14px;line-height:1.8;color:#a0b0a4;margin-top:24px} +.expertise-art{max-width:345px;margin-top:45px;--sculpture-color:#d5ee81} +.expertise-art-top,.expertise-art-bottom{display:flex;justify-content:space-between;align-items:center;gap:10px;font-family:monospace;font-size:8px;letter-spacing:1px;color:#90a497} +.expertise-art-top{padding-top:17px;border-top:1px solid #ffffff16} +.capability-index{color:var(--sculpture-color)} +.capability-sculpture{position:relative;height:245px;isolation:isolate;background:radial-gradient(ellipse at center,#a8c66e0d,transparent 65%)} +.capability-sculpture:before,.capability-sculpture:after{content:"";position:absolute;background:#b6d39410;pointer-events:none} +.capability-sculpture:before{height:1px;inset:50% 0 auto} +.capability-sculpture:after{width:1px;inset:12px auto 12px 50%} +.sculpture-plane{position:absolute;left:50%;top:50%;width:190px;height:190px;border:1px solid #b9dc8e69;border-radius:50%;transition:transform 1s var(--ease),border-radius 1s var(--ease),border-color .6s,background .6s;will-change:transform;box-shadow:inset 0 0 35px #c5e78d04} +.plane-one{transform:translate(-50%,-50%) rotate(-28deg) scale(1.22,.62);border-color:#d5ee81aa} +.plane-two{transform:translate(-50%,-50%) rotate(28deg) scale(1.1,.62)} +.plane-three{transform:translate(-50%,-50%) rotate(90deg) scale(1.1,.62)} +.plane-four{transform:translate(-50%,-50%) scale(.76);border-style:dashed;border-color:#a4c48330} +.sculpture-core{position:absolute;inset:0;display:grid;place-items:center;font-family:Arial,sans-serif;font-size:30px;font-weight:500;letter-spacing:-1px;color:var(--sculpture-color)} +.sculpture-point{position:absolute;width:7px;height:7px;left:50%;top:50%;border-radius:50%;background:var(--sculpture-color);box-shadow:0 0 16px #c6e98a4d;transition:transform 1s var(--ease),opacity .5s} +.point-one{transform:translate(-113px,39px)}.point-two{transform:translate(88px,-58px);width:4px;height:4px} +.capability-name{color:#bacbae;font-size:8px} +.capability-dots{display:flex;align-items:center;gap:6px} +.capability-dots i{height:3px;width:5px;background:#5b6f5d;transition:width .5s var(--ease),background .5s} +.capability-dots i.is-active{width:22px;background:var(--lime)} +.expertise-art[data-mode="monitoring"] .sculpture-plane{border-radius:12px} +.expertise-art[data-mode="monitoring"] .plane-one{transform:translate(-74%,-74%) scale(.58)} +.expertise-art[data-mode="monitoring"] .plane-two{transform:translate(-26%,-74%) scale(.58)} +.expertise-art[data-mode="monitoring"] .plane-three{transform:translate(-74%,-26%) scale(.58)} +.expertise-art[data-mode="monitoring"] .plane-four{transform:translate(-26%,-26%) scale(.58);border-style:solid;border-color:#b9dc8e69} +.expertise-art[data-mode="monitoring"] .point-one{transform:translate(-92px,-94px)} +.expertise-art[data-mode="monitoring"] .point-two{transform:translate(87px,89px)} +.expertise-art[data-mode="web3"] .sculpture-plane{border-radius:15px} +.expertise-art[data-mode="web3"] .plane-one{transform:translate(-50%,-68%) rotate(-30deg) skewX(25deg) scale(.68,.57)} +.expertise-art[data-mode="web3"] .plane-two{transform:translate(-50%,-50%) rotate(-30deg) skewX(25deg) scale(.68,.57)} +.expertise-art[data-mode="web3"] .plane-three{transform:translate(-50%,-32%) rotate(-30deg) skewX(25deg) scale(.68,.57)} +.expertise-art[data-mode="web3"] .plane-four{transform:translate(-50%,-50%) rotate(45deg) scale(1.12);border-radius:0} +.expertise-art[data-mode="web3"] .point-one{transform:translate(-4px,-119px)} +.expertise-art[data-mode="web3"] .point-two{transform:translate(117px,-2px)} +.expertise-art[data-mode="platform"] .sculpture-plane{border-radius:32px} +.expertise-art[data-mode="platform"] .plane-one{transform:translate(-80%,-50%) rotate(-13deg) scale(.49,.86)} +.expertise-art[data-mode="platform"] .plane-two{transform:translate(-50%,-50%) scale(.49,.86);background:#bad9880b} +.expertise-art[data-mode="platform"] .plane-three{transform:translate(-20%,-50%) rotate(13deg) scale(.49,.86)} +.expertise-art[data-mode="platform"] .plane-four{transform:translate(-50%,-50%) scale(1.35,.46);border-radius:50%} +.expertise-art[data-mode="platform"] .point-one{transform:translate(-121px,-32px)} +.expertise-art[data-mode="platform"] .point-two{transform:translate(116px,29px)} +.expertise-art[data-mode="interface"] .plane-one{transform:translate(-50%,-50%) rotate(45deg) scale(.9);border-radius:10px} +.expertise-art[data-mode="interface"] .plane-two{transform:translate(-50%,-50%) rotate(90deg) scale(.9);border-radius:10px} +.expertise-art[data-mode="interface"] .plane-three{transform:translate(-50%,-50%) rotate(135deg) scale(.9);border-radius:50%} +.expertise-art[data-mode="interface"] .plane-four{transform:translate(-50%,-50%) scale(.54);border-radius:0} +.expertise-art[data-mode="interface"] .point-one{transform:translate(-123px,-3px)} +.expertise-art[data-mode="interface"] .point-two{transform:translate(119px,-3px)} +.expertise-art[data-mode="idle"] .sculpture-plane{transform:translate(-50%,-50%) scale(.48);opacity:.45} +.expertise-art[data-mode="idle"] .sculpture-point{opacity:0;transform:translate(0,0)} +.service-list{min-width:0} +.service{position:relative;border-top:1px solid #3a4942;transition:background .45s;overflow-anchor:none} +.service:last-child{border-bottom:1px solid #3a4942} +.service:before{content:"";position:absolute;top:-1px;left:0;right:0;height:1px;background:var(--lime);transform:scaleX(0);transform-origin:left;transition:transform .65s var(--ease);z-index:1} +.service.is-expanded:before{transform:scaleX(1)} +.service.is-expanded{background:linear-gradient(105deg,#d5ee8109,transparent 90%)} +.service summary{list-style:none;display:flex;align-items:center;gap:25px;padding:26px 18px 26px;cursor:pointer} +.service summary::-webkit-details-marker{display:none} +.service summary>div{transition:transform .55s var(--ease)} +.service.is-expanded summary>div{transform:translateX(8px)} +.service-number{font-family:monospace;font-size:12px;align-self:flex-start;margin-top:4px;color:#819787;transition:color .35s} +.service summary .tiny-label{font-size:9px;color:#90a398;letter-spacing:1px;font-weight:400;transition:color .35s} +.service h3{font-size:24px;margin-top:8px;font-weight:500;letter-spacing:-.7px;transition:color .35s} +.service-toggle{display:grid;place-items:center;flex:none;width:34px;height:34px;border:1px solid #566949;border-radius:50%;margin-left:auto;font-family:Arial;font-weight:300;font-size:21px;transition:transform .55s var(--ease),color .35s,background .35s,border-color .35s} +.service.is-expanded .service-toggle{transform:rotate(45deg);color:#172725;background:var(--lime);border-color:var(--lime)} +.service.is-expanded .service-number,.service.is-expanded h3{color:var(--lime)} +.service-content{padding:0 46px 30px 54px} +.service-content p{font-size:13px;line-height:1.9;color:#b2c2b7;max-width:510px} +.tags{display:flex;flex-wrap:wrap;gap:7px;margin-top:22px} +.tags>span{font-family:Arial,var(--font);font-size:10px;border:1px solid #c4cdbe;padding:7px 11px;border-radius:3px;font-weight:400} +.service .tags>span{color:#c1cfc1;border-color:#435449;font-size:9px} +@media(hover:hover){.service:not(.is-expanded) summary:hover h3{color:#dce9ce}.service:not(.is-expanded) summary:hover>div{transform:translateX(8px)}.service:not(.is-expanded) summary:hover .service-toggle{transform:rotate(90deg);border-color:#b2c39f}} + +.section-heading{display:flex;align-items:flex-end;justify-content:space-between;gap:40px}.section-heading>p{font-size:13px;line-height:1.9;color:var(--muted);margin-bottom:5px}.project-toolbar{margin-top:45px;border-bottom:1px solid var(--line);display:flex;align-items:center;justify-content:space-between;padding-bottom:22px;gap:20px}.project-filters{display:flex;gap:8px;flex-wrap:wrap}.filter{background:transparent;border-radius:30px;padding:11px 18px;font-size:12px;transition:background .2s,color .2s}.filter:hover{background:#e7eae0}.filter.is-active{background:var(--ink);color:white}.filter sup{font-family:Arial;font-size:8px;margin-left:3px}.project-count{font-size:9px;letter-spacing:1px;white-space:nowrap;color:var(--muted);font-family:Arial}.project-grid{display:grid;grid-template-columns:1fr 1fr;gap:54px 32px;margin-top:34px} +.project-card{min-width:0} +.project-open{background:transparent;width:100%;padding:0;text-align:left;display:block} +.project-meta{display:flex;justify-content:space-between;gap:15px;align-items:center;font-family:Arial,var(--font);font-size:11px;color:var(--muted)} +.project-meta>span:first-child{font-size:13px;font-weight:600;letter-spacing:.2px;color:#58694f} +.project-meta>span:last-child{font-size:9px;letter-spacing:.8px} +.project-card h3{font-size:26px;font-weight:600;line-height:1.4;letter-spacing:-.85px;margin:13px 0 23px;min-height:73px;max-width:570px;transition:color .25s} +.project-image{aspect-ratio:1.65;position:relative;overflow:hidden;border-radius:3px;background:#e7ebe2;isolation:isolate} +.project-image img{width:100%;height:100%;object-fit:cover;object-position:center;transform:scale(1.005);transition:transform .8s var(--ease);border-radius:0;box-shadow:none} +.project-arrow{position:absolute;right:16px;bottom:16px;z-index:2;background:var(--paper);border-radius:50%;width:39px;height:39px;display:grid;place-items:center;font-size:23px;transition:transform .35s var(--ease),background .35s,color .35s;color:var(--ink)} +.project-open:hover .project-image img{transform:scale(1.045)} +.project-open:hover .project-arrow{background:var(--blue);color:white;transform:translate(2px,-2px)} +.project-open:hover h3{color:var(--blue)} +.mobility-image{background:#1b2729} +.mobility-image img{object-fit:contain} +.pharmacy-image{background:#f3f5f6} +.pharmacy-image img{object-position:left top} +.bems-image{background:#23282e} +.bems-image img{object-fit:contain} +.givon-image{background:#f6f7f9} +.givon-image img{object-fit:cover;object-position:left top} +.bispet-image{background:#e6eddc} +.bispet-image img{width:58%;height:auto;min-height:100%;margin-inline:auto;object-fit:cover;object-position:top} +.asset-image{background:#fff} +.asset-image img{object-fit:contain} +.project-scope{margin-top:16px;padding-top:14px;border-top:1px solid var(--line);display:flex;flex-wrap:wrap;justify-content:space-between;gap:8px 15px} +.project-scope p{font-size:11px;color:#667160;line-height:1.6} +.project-scope>span{font-size:9px;color:#818a7a;line-height:1.8;font-family:Arial,sans-serif} +.work-footnote{margin-top:42px;text-align:right;font-size:10px;color:var(--muted)} + +.journey{background:#e9ede3}.journey-heading{display:flex;justify-content:space-between;align-items:flex-end;gap:35px}.journey-heading>p{font-size:13px;line-height:1.9;color:var(--muted);max-width:350px;margin-bottom:7px}.timeline-tabs{margin-top:65px;display:grid;grid-template-columns:repeat(5,1fr);border-bottom:1px solid #c2cbb8;gap:10px}.timeline-tabs button{position:relative;padding:0 0 27px;text-align:left;background:transparent;font-family:Arial;font-size:33px;letter-spacing:-1.5px;color:#7f8a76}.timeline-tabs button>span{display:block;font-size:8px;letter-spacing:.7px;margin-top:10px;color:#6e7a66}.timeline-tabs button:after{content:"";width:7px;height:7px;background:#b7c3aa;border-radius:50%;bottom:-4px;left:0;position:absolute;transition:background .2s,box-shadow .2s}.timeline-tabs button[aria-selected="true"]{color:var(--blue)}.timeline-tabs button[aria-selected="true"]:after{background:var(--blue);box-shadow:0 0 0 5px #2556e815}.timeline-tabs button[aria-selected="true"]>span{color:var(--blue)}.timeline-panel{display:grid;grid-template-columns:1fr 1.2fr;align-items:center;gap:50px;min-height:315px;padding-block:40px}.timeline-year{font-size:clamp(100px,13vw,190px);font-family:Arial;font-weight:600;letter-spacing:-10px;color:#c6d1bd;line-height:1}.timeline-detail .eyebrow{font-size:9px;color:var(--blue);margin-bottom:18px}.timeline-detail h3{font-size:26px;font-weight:600;letter-spacing:-1px}.timeline-description{font-size:13px;color:var(--muted);line-height:1.9;margin-top:16px;max-width:460px}.timeline-detail .tags{margin-top:18px}.research-strip{display:flex;align-items:center;border-top:1px solid #c9d2c0;padding-top:35px;gap:25px}.research-mark{font-size:59px;color:#708060;line-height:1}.research-strip .tiny-label{font-size:8px;color:#6f7f63}.research-strip h3{font-size:17px;font-weight:500;margin-top:9px;letter-spacing:-.5px}.research-strip p{font-size:11px;line-height:1.8;color:#78856e;margin-top:9px}.research-strip small{font-size:9px}.circle-link{display:grid;place-items:center;flex:none;width:42px;height:42px;border:1px solid #abb99d;border-radius:50%;font-size:23px;margin-left:auto;transition:background .2s}.circle-link:hover{background:var(--lime)} +.contact{background:var(--blue);color:var(--paper);position:relative;overflow:hidden}.contact:before{content:"";width:650px;height:650px;border:1px solid #fff1;border-radius:50%;position:absolute;right:-260px;top:-180px;box-shadow:0 0 0 65px #ffffff04,0 0 0 130px #ffffff03;pointer-events:none}.contact .section-label>span{color:#c2d292}.contact .side-note{color:#b6c7ff}.contact .section-top{margin-bottom:62px}.contact-layout{display:grid;grid-template-columns:1.2fr .8fr;gap:85px;position:relative}.contact-copy h2{font-size:55px;letter-spacing:-2.8px}.contact-copy h2>span{color:var(--lime)}.contact-copy>p{margin-top:25px;color:#c4d0fc;font-size:14px;line-height:1.9}.contact-cta{margin-top:34px;display:inline-flex;align-items:center;gap:55px;border-bottom:1px solid #88a3ff;padding-bottom:16px;font-size:16px;font-weight:500}.contact-cta>span{font-size:34px;line-height:1;transition:transform .2s}.contact-cta:hover>span{transform:translate(4px,-4px)}.contact-details{padding-top:6px}.contact-row{padding-bottom:22px;margin-bottom:20px;border-bottom:1px solid #ffffff30}.contact-row>.tiny-label{font-size:9px;color:#bbcafa;display:block;margin-bottom:11px;font-weight:400}.contact-row>div{display:flex;align-items:center;justify-content:space-between;gap:10px}.contact-row a{font-family:Arial,var(--font);font-size:21px;letter-spacing:-.4px}.copy-email{background:transparent;color:#c0d0ff;font-size:10px;white-space:nowrap;padding:7px}.copy-email>span{font-size:15px;margin-left:4px}.contact-row a:hover{color:var(--lime)}.contact-placeholder{font-size:10px;color:#d0dcff;margin-top:-10px;margin-bottom:25px}.address-row{border-bottom:none;margin-bottom:0;padding-bottom:0}.address-row address{font-style:normal;font-size:13px;line-height:1.8;color:#e1e8ff}.address-row .map-link{font-family:var(--font);display:inline-block;font-size:11px;margin-top:13px;color:#baccff}.map-link>span{margin-left:15px}.contact :focus-visible{outline-color:var(--lime)} +.site-footer{background:#f0f1e9;padding-block:55px 27px}.footer-top{display:flex;align-items:center;gap:40px;padding-bottom:45px}.footer-name{font-family:Arial,sans-serif;font-size:79px;font-weight:800;line-height:1;letter-spacing:-5px}.footer-name>span{font-size:14px;letter-spacing:0;vertical-align:top;display:inline-block;margin-top:10px;margin-left:5px}.footer-top>p{font-family:Arial;font-size:10px;line-height:1.7;letter-spacing:1.3px;color:#7a8475;margin-left:auto}.back-top{height:43px;width:43px;display:grid;place-items:center;border:1px solid #cad1c1;border-radius:50%;font-size:25px;margin-left:10px}.back-top:hover{background:var(--lime)}.footer-bottom{border-top:1px solid var(--line);padding-top:24px;display:flex;justify-content:space-between;gap:20px;align-items:center;font-size:9px;color:#7a8375}.footer-divider{margin-inline:10px;color:#bac2b4}.footer-bottom>a{color:#44513d}.footer-bottom>a:hover{color:var(--blue)} +.project-dialog{width:min(880px,calc(100% - 32px));max-height:90vh;max-height:90dvh;padding:0;border:none;border-radius:8px;background:var(--paper);color:var(--ink);overflow:auto}.project-dialog::backdrop{background:#101e24bd;backdrop-filter:blur(7px)}.dialog-close{position:sticky;top:15px;float:right;right:15px;margin:15px 15px -55px 0;z-index:2;width:40px;height:40px;border:1px solid var(--line);border-radius:50%;background:var(--paper);font-size:27px;line-height:1}.dialog-image{background:#e5e9df;padding:30px 50px;min-height:190px;display:flex;justify-content:center}.dialog-image img{height:270px;width:100%;object-fit:contain;border-radius:4px}.dialog-content{padding:35px 45px 45px}.dialog-content .eyebrow{font-size:9px;color:var(--blue)}.dialog-content h2{font-size:34px;margin-top:15px;letter-spacing:-1.3px}.dialog-content>p:not(.eyebrow){font-size:14px;color:var(--muted);line-height:1.9;margin-top:20px}.dialog-content dl{margin-top:25px;border-top:1px solid var(--line);font-size:12px}.dialog-content dl>div{display:grid;grid-template-columns:90px 1fr;gap:20px;padding-block:14px;border-bottom:1px solid var(--line);line-height:1.8}.dialog-content dt{font-weight:600}.dialog-content dd{margin:0;color:var(--muted)}.dialog-content .button{margin-top:30px}.toast{position:fixed;left:50%;bottom:30px;transform:translate(-50%,20px);background:var(--ink);color:white;font-size:13px;padding:15px 24px;border-radius:6px;z-index:110;opacity:0;pointer-events:none;transition:opacity .2s,transform .2s;max-width:calc(100% - 32px);box-shadow:0 5px 20px #0002;text-align:center}.toast.is-visible{opacity:1;transform:translate(-50%,0)} +.js-ready .reveal{opacity:0;transform:translateY(24px);transition:opacity .8s var(--ease),transform .8s var(--ease)}.js-ready .reveal.is-visible{opacity:1;transform:translateY(0)} +@media(min-width:1600px){.hero{min-height:760px}.hero-art{height:590px}.hero h1{font-size:73px}.hero-description{font-size:17px}} +@media(max-width:1150px){.wrap{width:calc(100% - 72px)}.desktop-nav{margin-left:0;gap:24px}.hero{min-height:640px;grid-template-columns:1.15fr 1fr}.hero h1{font-size:49px;letter-spacing:-2.4px}.hero-art{height:480px;width:118%;margin-left:-10%}.hero-description{font-size:14px}.hero-bottom{margin-top:40px}.hero-actions{gap:22px}.hero .eyebrow{font-size:8px}.section{padding-block:88px}.about-layout{gap:65px}.about-copy h2{font-size:36px}.expertise-layout{gap:50px;grid-template-columns:.8fr 1.3fr}.expertise-intro h2{font-size:41px}.service h3{font-size:23px}.contact-layout{gap:50px}.contact-copy h2{font-size:44px}.contact-row a{font-size:18px}.side-note{font-size:9px}} +@media(max-width:850px){.wrap{width:calc(100% - 48px)}.site-header{height:78px}.desktop-nav{display:none}.header-inner{gap:18px}.brand{width:117px}.brand img{width:117px}.header-contact{margin-left:auto;font-size:11px;padding:10px 15px;gap:20px}.menu-toggle{display:flex;flex-direction:column;justify-content:center;gap:6px;background:transparent;width:35px;height:35px;z-index:35;padding:6px}.menu-toggle>span{height:1.5px;width:23px;background:var(--ink);transition:transform .3s}.menu-toggle[aria-expanded="true"]>span:first-child{transform:translateY(3.75px) rotate(45deg)}.menu-toggle[aria-expanded="true"]>span:last-child{transform:translateY(-3.75px) rotate(-45deg)}.mobile-menu{display:flex;position:fixed;inset:78px 0 auto;background:var(--paper);padding:30px 30px 35px;flex-direction:column;border-bottom:1px solid var(--line);max-height:calc(100dvh - 78px);overflow:auto;box-shadow:0 30px 40px #0002}.mobile-menu a{font-size:24px;border-top:1px solid var(--line);padding:20px 0;display:flex;gap:25px;align-items:center}.mobile-menu a>span{font-family:monospace;font-size:11px;color:var(--blue)}.mobile-menu>p{font-size:9px;letter-spacing:1.2px;line-height:1.8;margin-top:30px;color:var(--muted)}.hero{padding-top:46px;padding-bottom:75px;min-height:600px}.hero h1{font-size:43px;letter-spacing:-2px}.hero-content{margin-right:-80px}.hero-art{height:440px;margin-left:-17%;width:132%;opacity:.94;align-self:center}.hero .eyebrow{font-size:8px;letter-spacing:.8px}.hero-description{max-width:330px;font-size:13px}.hero-actions .button{font-size:12px;padding:15px 18px;gap:20px}.hero-actions .text-link{font-size:12px;gap:12px}.hero-bottom{gap:15px}.hero-note{display:none}.hero-bottom strong{font-size:28px}.coordinate-top{top:0;font-size:7px}.orbit-label-one{right:0;top:19%}.orbit-label-two{bottom:22%;left:12%}.art-caption{bottom:7px;left:20%;font-size:6px;right:0}.about-layout{gap:38px;grid-template-columns:.9fr 1.3fr}.about-statement>p{font-size:33px}.about-copy h2{font-size:31px;letter-spacing:-1.5px}.about-copy>p{font-size:13px}.about-copy .text-link{font-size:12px;gap:11px}.about-copy .text-link small{font-size:8px}.facts{gap:20px}.fact{padding-left:8px}.fact strong{font-size:60px}.fact .tiny-label{font-size:7px;letter-spacing:.8px}.fact p{font-size:10px}.fact small{display:block;margin:5px 0 0}.expertise-layout{gap:25px;grid-template-columns:.75fr 1.25fr}.expertise-intro h2{font-size:34px}.expertise-intro>p{font-size:12px}.service summary{gap:17px;padding-block:22px}.service summary .tiny-label{font-size:7px}.service h3{font-size:20px}.service-content{padding:0 15px 25px 30px}.service-content p{font-size:12px}.service .tags>span{font-size:8px;padding:6px 8px}.section-heading h2,.journey-heading h2{font-size:39px}.section-heading>p,.journey-heading>p{font-size:11px}.project-grid{gap:35px 22px}.givon-image .filter{font-size:10px;padding:10px 13px}.project-count{font-size:8px}.timeline-tabs button{font-size:29px}.timeline-tabs button>span{font-size:6px}.timeline-panel{gap:30px;grid-template-columns:.8fr 1.2fr}.timeline-year{font-size:105px;letter-spacing:-6px}.timeline-detail h3{font-size:21px}.timeline-description{font-size:12px}.contact-layout{gap:35px;grid-template-columns:1.1fr 1fr}.contact-copy h2{font-size:36px;letter-spacing:-1.7px}.contact-copy>p{font-size:12px}.contact-row a{font-size:16px}.contact-cta{font-size:14px;gap:35px}.footer-name{font-size:66px}.footer-bottom{font-size:8px}.footer-divider{margin-inline:4px}} +@media(max-width:600px){html{scroll-padding-top:90px}.wrap{width:calc(100% - 40px)}.section{padding-block:70px}.site-header{height:74px}.header-inner{gap:12px}.brand,.brand img{width:110px}.header-contact{font-size:10px;padding:10px 12px;gap:13px}.header-contact>span{font-size:16px}.menu-toggle{width:29px}.mobile-menu{top:74px}.hero{display:flex;flex-direction:column;align-items:stretch;padding-top:42px;padding-bottom:59px;min-height:0}.hero-content{margin-right:0}.hero .eyebrow{font-size:7px;letter-spacing:1px;margin-bottom:25px}.status-dot{width:6px;height:6px}.hero h1{font-size:clamp(31px,7.8vw,46px);letter-spacing:-1.9px;line-height:1.27}.hero-description{margin-top:20px;font-size:13px;line-height:1.8;max-width:none}.hero-actions{margin-top:26px;gap:25px}.hero-actions .button{padding:16px 19px;font-size:11px;gap:32px}.hero-actions .text-link{font-size:11px}.hero-bottom{margin-top:32px;gap:16px}.hero-bottom>.tiny-label{font-size:7px;width:52px}.hero-bottom strong{font-size:26px}.hero-bottom .small-star{font-size:23px}.hero-note{display:inline-block;padding-left:14px;font-size:9px}.hero-art{width:100%;height:330px;max-width:430px;align-self:center;margin:20px 0 -10px}.coordinate-top{top:2px;right:5px;font-size:7px}.art-grid{background-size:30px 30px}.orbit-label{font-size:7px;padding:6px 8px}.orbit-label-one{right:0;top:25%}.orbit-label-two{left:0;bottom:24%}.art-caption{bottom:4px;left:8%;right:5%;font-size:6px}.scroll-cue{font-size:7px;bottom:22px;letter-spacing:1px}.motion-toggle{bottom:16px;width:25px;height:25px}.ticker{padding-block:17px}.ticker-track>span{font-size:10px}.ticker i{font-size:18px;margin-inline:25px}.section-top{margin-bottom:35px}.section-label{font-size:9px;gap:12px;letter-spacing:1.2px}.section-label>span{font-size:10px}.side-note{display:none}.about-layout{display:flex;flex-direction:column;gap:37px}.about-statement{padding-top:0;display:grid;grid-template-columns:54px 1fr;column-gap:16px;align-items:center}.about-asterisk{font-size:52px;margin:0;grid-row:span 2}.about-statement>p{font-size:26px;letter-spacing:-1px;line-height:1.05}.about-statement>p br{display:none}.about-statement>span:last-child{font-size:9px;margin-top:7px}.about-copy h2{font-size:33px;margin-bottom:20px;letter-spacing:-1.7px}.about-copy>p{font-size:13px;line-height:1.85;margin-top:13px}.about-copy .text-link{margin-top:22px}.facts{gap:12px;padding-top:25px;margin-top:39px}.fact{padding-left:0}.fact>.tiny-label{font-size:6px;letter-spacing:.5px;min-height:20px;display:block;line-height:1.4}.fact strong{font-size:43px;letter-spacing:-2.8px;margin:8px 0 10px;white-space:nowrap}.fact-unit{font-size:10px;margin-left:5px}.fact p{font-size:9px;line-height:1.5;max-width:90px}.fact small{font-size:7px;line-height:1.5}.expertise-layout{display:block}.expertise-intro{position:relative;margin-bottom:39px}.expertise-intro h2{font-size:35px;letter-spacing:-1.7px}.expertise-intro>p{margin-top:16px;font-size:12px}.service summary{gap:19px;padding-block:23px}.service-number{font-size:11px}.service summary .tiny-label{font-size:7px;letter-spacing:.85px}.service h3{font-size:22px;margin-top:7px}.service-toggle{font-size:22px}.service-content{padding:0 12px 26px 32px}.service-content p{font-size:12px}.service .tags>span{font-size:8px}.section-heading{display:block}.section-heading h2,.journey-heading h2{font-size:36px;letter-spacing:-1.7px}.section-heading>p{margin-top:20px;font-size:12px}.project-toolbar{margin-top:30px;display:block;padding-bottom:17px}.project-filters{gap:4px;flex-wrap:nowrap;justify-content:space-between}.filter{font-size:9px;padding:10px 11px;white-space:nowrap}.filter sup{font-size:6px}.project-count{display:none}.project-grid{grid-template-columns:1fr;gap:33px;margin-top:22px}.givon-image .work-footnote{font-size:9px;line-height:1.8;text-align:left;margin-top:30px}.journey-heading{display:block}.journey-heading>p{margin-top:19px;font-size:12px}.timeline-tabs{margin-top:37px;gap:10px}.timeline-tabs button{font-size:23px;letter-spacing:-1px;padding-bottom:23px}.timeline-tabs button>span{display:none}.timeline-panel{display:flex;flex-direction:column;align-items:flex-start;gap:25px;padding-block:38px 35px;min-height:360px}.timeline-year{font-size:94px;letter-spacing:-5px;line-height:.9}.timeline-detail .eyebrow{font-size:8px;margin-bottom:14px}.timeline-detail h3{font-size:23px}.timeline-description{font-size:12px;line-height:1.85;margin-top:14px}.timeline-detail .tags>span{font-size:8px;padding:6px 9px}.research-strip{gap:15px;align-items:flex-start;padding-top:29px}.research-mark{font-size:33px}.research-strip .tiny-label{font-size:7px}.research-strip h3{font-size:14px;line-height:1.6;margin-top:7px}.research-strip p{font-size:10px;line-height:1.8}.research-strip small{font-size:8px}.research-strip .circle-link{width:30px;height:30px;font-size:17px;margin-top:5px}.contact .section-top{margin-bottom:36px}.contact-layout{grid-template-columns:1fr;gap:43px}.contact-copy h2{font-size:39px;letter-spacing:-2px}.contact-copy>p{font-size:12px;margin-top:20px}.contact-cta{margin-top:27px;font-size:15px;gap:65px}.contact-cta>span{font-size:29px}.contact-details{padding-top:0}.contact-row>div{justify-content:flex-start;gap:20px}.contact-row a{font-size:20px}.contact-row{padding-bottom:20px;margin-bottom:20px}.contact-row>.tiny-label{font-size:8px}.address-row address{font-size:12px}.contact-placeholder{font-size:9px}.site-footer{padding-block:39px 24px}.footer-top{flex-wrap:wrap;gap:20px;padding-bottom:30px;position:relative}.footer-name{font-size:68px;letter-spacing:-4px}.footer-top>p{font-size:8px;margin-left:0;width:100%;letter-spacing:1.1px}.back-top{position:absolute;right:0;top:8px;width:37px;height:37px;font-size:23px}.footer-bottom{flex-wrap:wrap;justify-content:flex-start;gap:12px;font-size:8px;padding-top:22px}.footer-bottom>span{width:100%}.footer-bottom>a{margin-top:5px}.dialog-image{padding:40px 20px 20px}.dialog-image img{height:190px}.dialog-content{padding:25px 23px 32px}.dialog-content h2{font-size:27px}.dialog-content>p:not(.eyebrow){font-size:13px}.dialog-content dl>div{grid-template-columns:60px 1fr;gap:13px;font-size:11px}.dialog-content .button{font-size:11px;gap:25px;padding:16px 18px}.toast{font-size:11px;bottom:20px;padding:14px 20px;width:max-content}.project-dialog{max-height:92dvh}} +@media(max-width:360px){.wrap{width:calc(100% - 32px)}.hero h1{font-size:30px;letter-spacing:-1.7px}.hero .eyebrow{font-size:6px}.hero-art{height:285px}.header-contact{font-size:9px;gap:8px;padding:9px 10px}.brand,.brand img{width:96px}.header-inner{gap:9px}.filter{font-size:8px;padding-inline:8px}.fact strong{font-size:36px}.contact-copy h2{font-size:34px}.timeline-tabs button{font-size:20px}} +@media(prefers-reduced-motion:reduce){html{scroll-behavior:auto}*,*:before,*:after{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}.js-ready .reveal{opacity:1;transform:none}.ticker-track{animation:none}} + +/* Responsive refinements for capability motion and project-first work cards. */ +@media(min-width:601px) and (max-width:850px){ + .hero-art{width:116%} +} +@media(max-width:1150px){ + .expertise-art{max-width:280px}.capability-sculpture{height:235px} + .project-card h3{font-size:23px;min-height:65px}.project-grid{gap:44px 26px} + .project-scope{display:block}.project-scope>span{display:block;margin-top:5px} +} +@media(max-width:850px){ + .expertise-art{max-width:230px}.capability-sculpture{height:220px} + .sculpture-plane{width:155px;height:155px}.sculpture-point{display:none} + .expertise-art-top,.capability-name{font-size:6px;letter-spacing:.4px} + .service summary{padding-right:9px}.service-toggle{width:29px;height:29px;font-size:18px} + .service.is-expanded summary>div{transform:translateX(4px)} + .project-card h3{font-size:21px;min-height:88px;margin-bottom:19px} + .project-image{aspect-ratio:1.45}.project-meta>span:first-child{font-size:11px} + .project-meta>span:last-child{font-size:8px}.project-scope p{font-size:10px} +} +@media(max-width:600px){ + .expertise-intro{margin-bottom:35px;padding-bottom:0;min-height:185px} + .expertise-intro>p{max-width:180px} + .expertise-art{position:absolute;right:-4px;top:0;width:150px;max-width:42%;margin:0} + .expertise-art-top{border-top:0;padding-top:0;justify-content:flex-end} + .expertise-art-top>span:first-child{display:none}.capability-index{font-size:8px} + .capability-sculpture{height:154px}.sculpture-plane{width:108px;height:108px} + .sculpture-core{font-size:20px}.expertise-art-bottom{justify-content:center} + .capability-name{display:none}.capability-dots{gap:5px}.capability-dots i.is-active{width:17px} + .service summary{padding-right:2px}.service.is-expanded summary>div{transform:translateX(3px)} + .service-content{padding-left:33px}.service summary .tiny-label{font-size:6.5px} + .project-grid{grid-template-columns:1fr;gap:40px;margin-top:28px} + .project-meta>span:first-child{font-size:12px}.project-meta>span:last-child{font-size:8px} + .project-card h3{font-size:23px;min-height:0;line-height:1.45;margin:12px 0 18px;letter-spacing:-.65px} + .project-image{aspect-ratio:1.5}.project-scope{margin-top:13px;padding-top:12px} + .project-scope p{font-size:11px}.project-scope>span{font-size:9px}.project-arrow{width:34px;height:34px;font-size:21px;right:13px;bottom:13px} +} +@media(max-width:360px){ + .expertise-intro h2{font-size:31px}.expertise-intro>p{font-size:11px;max-width:155px} + .expertise-art{right:-5px;width:126px}.sculpture-plane{width:92px;height:92px} + .project-card h3{font-size:21px} +}