트론트 홈페이지와 포트폴리오 초기 구성
9
.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# macOS 및 로컬 편집기 설정
|
||||
.DS_Store
|
||||
.vscode/
|
||||
|
||||
# 로컬 환경 설정 및 임시 파일
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
*.log
|
||||
94
README.md
Normal file
@@ -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이나 폰트 라이브러리를 사용하지 않아 다운로드한 파일만으로 홈페이지 화면을 확인할 수 있습니다. 지도 모달은 외부 지도 서비스를 불러오며 이메일/전화 링크는 기기 앱으로 연결됩니다.
|
||||
1
assets/icons/arrow_right_alt.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#00000"><path d="m560-240-56-58 142-142H160v-80h486L504-662l56-58 240 240-240 240Z"/></svg>
|
||||
|
After Width: | Height: | Size: 189 B |
BIN
assets/images/archive/bems_01.png
Normal file
|
After Width: | Height: | Size: 716 KiB |
BIN
assets/images/archive/bispet_01.png
Normal file
|
After Width: | Height: | Size: 466 KiB |
BIN
assets/images/archive/bispet_02.png
Normal file
|
After Width: | Height: | Size: 461 KiB |
BIN
assets/images/archive/bispet_03.png
Normal file
|
After Width: | Height: | Size: 378 KiB |
BIN
assets/images/archive/givon_01.png
Normal file
|
After Width: | Height: | Size: 187 KiB |
BIN
assets/images/asset_01.png
Normal file
|
After Width: | Height: | Size: 354 KiB |
BIN
assets/images/bems_01.png
Normal file
|
After Width: | Height: | Size: 357 KiB |
BIN
assets/images/bems_02.png
Normal file
|
After Width: | Height: | Size: 448 KiB |
BIN
assets/images/bems_03.png
Normal file
|
After Width: | Height: | Size: 630 KiB |
BIN
assets/images/bispet_01.png
Normal file
|
After Width: | Height: | Size: 580 KiB |
BIN
assets/images/bispet_02.png
Normal file
|
After Width: | Height: | Size: 396 KiB |
BIN
assets/images/givon_01.png
Normal file
|
After Width: | Height: | Size: 188 KiB |
BIN
assets/images/givon_02.png
Normal file
|
After Width: | Height: | Size: 182 KiB |
BIN
assets/images/givon_03.png
Normal file
|
After Width: | Height: | Size: 136 KiB |
BIN
assets/images/gps-building_01.png
Normal file
|
After Width: | Height: | Size: 3.5 MiB |
BIN
assets/images/mobility_01.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
assets/images/mobility_02.png
Normal file
|
After Width: | Height: | Size: 407 KiB |
BIN
assets/images/mobility_03.png
Normal file
|
After Width: | Height: | Size: 905 KiB |
BIN
assets/images/mobility_04.png
Normal file
|
After Width: | Height: | Size: 702 KiB |
BIN
assets/images/patent-liquid-dispenser.png
Normal file
|
After Width: | Height: | Size: 723 KiB |
BIN
assets/images/patent-virtual-image.png
Normal file
|
After Width: | Height: | Size: 648 KiB |
BIN
assets/images/pharmacy_01.png
Normal file
|
After Width: | Height: | Size: 535 KiB |
BIN
assets/images/pharmacy_02.png
Normal file
|
After Width: | Height: | Size: 1006 KiB |
BIN
assets/images/pharmacy_03.png
Normal file
|
After Width: | Height: | Size: 438 KiB |
BIN
assets/images/signage_01.png
Normal file
|
After Width: | Height: | Size: 2.9 MiB |
BIN
assets/images/tront-logo.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
307
company.css
Normal file
@@ -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; }
|
||||
}
|
||||
BIN
docs/(주)트론트_회사소개서_ver.1.2.0_20251202_압축.pdf
Normal file
38
icons.css
Normal file
@@ -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; }
|
||||
}
|
||||
139
image-viewer.css
Normal file
@@ -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; }
|
||||
}
|
||||
283
image-viewer.js
Normal file
@@ -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 = `
|
||||
<header class="viewer-header">
|
||||
<div class="viewer-heading"><p class="viewer-brand"></p><h2 id="image-viewer-title"></h2></div>
|
||||
<div class="viewer-controls">
|
||||
<div class="viewer-zoom" role="group" aria-label="이미지 확대 및 축소"><button type="button" data-action="out" aria-label="축소">−</button><output class="viewer-scale" aria-label="확대 비율">—</output><button type="button" data-action="in" aria-label="확대">+</button></div>
|
||||
<button type="button" class="viewer-size" data-action="actual" aria-label="원본 크기 100%로 보기">1:1</button>
|
||||
<button type="button" class="viewer-size" data-action="fit">화면 맞춤</button>
|
||||
<button type="button" class="viewer-close" data-action="close" aria-label="이미지 뷰어 닫기" autofocus><span class="viewer-close-icon" aria-hidden="true"></span><span>닫기</span></button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="viewer-body">
|
||||
<div class="viewer-stage" tabindex="0" role="region" aria-label="확대한 이미지는 드래그하거나 스크롤하여 이동할 수 있습니다">
|
||||
<div class="viewer-image-layout"><img class="viewer-image" alt="" draggable="false"></div>
|
||||
</div>
|
||||
<div class="viewer-message" role="status"><span></span><button type="button" data-action="retry" hidden>다시 불러오기</button></div>
|
||||
<button type="button" class="viewer-direction viewer-previous" data-action="previous" aria-label="이전 이미지"><span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--left" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></button>
|
||||
<button type="button" class="viewer-direction viewer-next" data-action="next" aria-label="다음 이미지"><span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></button>
|
||||
</div>
|
||||
<footer class="viewer-footer">
|
||||
<div class="viewer-caption"><span class="viewer-counter" aria-live="polite" aria-atomic="true"></span><p></p><span class="viewer-keyboard-hint" aria-hidden="true">좌우 방향키로 이미지 이동 <i>·</i> ESC 닫기</span></div>
|
||||
<div class="viewer-thumbnails" role="group" aria-label="이미지 선택"></div>
|
||||
</footer>`;
|
||||
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);
|
||||
}
|
||||
}));
|
||||
})();
|
||||
338
index.html
Normal file
@@ -0,0 +1,338 @@
|
||||
<!doctype html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="theme-color" content="#f5f5ef">
|
||||
<meta name="description" content="2008년부터 이어온 기술의 연결. 트론트는 스마트 모빌리티, 산업 관제, Web3, 모바일·웹 플랫폼으로 비즈니스의 다음을 만듭니다.">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="TRONT — 기술로 연결하고, 가능성을 확장하다">
|
||||
<meta property="og:description" content="스마트 모빌리티부터 디지털 플랫폼까지. 2008년부터 함께해 온 IT 솔루션 파트너, 주식회사 트론트.">
|
||||
<title>TRONT — 기술로 연결하고, 가능성을 확장하다</title>
|
||||
<link rel="icon" href="assets/images/tront-logo.png" type="image/png">
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
<link rel="stylesheet" href="portfolio.css">
|
||||
<link rel="stylesheet" href="company.css">
|
||||
<script src="script.js" defer></script>
|
||||
<script src="portfolio.js" defer></script>
|
||||
<link rel="stylesheet" href="map-dialog.css">
|
||||
<script src="map-dialog.js" defer></script>
|
||||
<link rel="stylesheet" href="image-viewer.css">
|
||||
<script src="image-viewer.js" defer></script>
|
||||
<link rel="stylesheet" href="icons.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Shared arrow shape from assets/icons/arrow_right_alt.svg. -->
|
||||
<svg class="icon-definitions" xmlns="http://www.w3.org/2000/svg" width="0" height="0" aria-hidden="true" focusable="false"><defs><symbol id="arrow-right-alt" viewBox="0 -960 960 960"><path d="m560-240-56-58 142-142H160v-80h486L504-662l56-58 240 240-240 240Z"/></symbol></defs></svg>
|
||||
<a class="skip-link" href="#main">본문으로 바로가기</a>
|
||||
<div class="scroll-progress" aria-hidden="true"></div>
|
||||
<header class="site-header">
|
||||
<div class="header-inner wrap">
|
||||
<a class="brand" href="#home" aria-label="트론트 홈"><img src="assets/images/tront-logo.png" width="400" height="155" alt="TRONT — The road not taken"></a>
|
||||
<nav class="desktop-nav" aria-label="주요 메뉴">
|
||||
<a href="#about">회사소개</a><a href="#expertise">사업영역</a><a href="#work">프로젝트</a><a href="#patents">특허</a><a href="#journey">걸어온 길</a>
|
||||
</nav>
|
||||
<a class="header-contact" href="#contact">함께 만들어가요 <span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a>
|
||||
<button class="menu-toggle" aria-label="메뉴 열기" aria-controls="mobile-menu" aria-expanded="false"><span></span><span></span></button>
|
||||
</div>
|
||||
<nav id="mobile-menu" class="mobile-menu" aria-label="모바일 메뉴" hidden>
|
||||
<a href="#about"><span>01</span>회사소개 <svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></a><a href="#expertise"><span>02</span>사업영역 <svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></a><a href="#work"><span>03</span>프로젝트 <svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></a><a href="#patents"><span>04</span>특허 <svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></a><a href="#journey"><span>05</span>걸어온 길 <svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></a><a href="#contact"><span>06</span>함께 만들어가요 <svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></a>
|
||||
<p>THE ROAD NOT TAKEN.<br>YOUR NEXT TECHNOLOGY PARTNER.</p>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<main id="main">
|
||||
<section id="home" class="hero wrap" aria-labelledby="hero-title">
|
||||
<div class="hero-content">
|
||||
<p class="eyebrow"><span class="status-dot"></span> CONNECTING IDEAS. CREATING IMPACT.</p>
|
||||
<h1 id="hero-title">기술로 연결하고,<br><span>가능성을 확장하다.</span></h1>
|
||||
<p class="hero-description">모빌리티부터 디지털 플랫폼까지.<br>비즈니스의 다음을 함께 만드는 기술 파트너, 트론트.</p>
|
||||
<div class="hero-actions"><a class="button button-dark" href="#work">우리가 만든 변화 <span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a><a class="text-link" href="#expertise">우리의 기술 <span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--down" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a></div>
|
||||
<div class="hero-bottom"><span class="tiny-label">INNOVATION SINCE</span><strong>2008<span class="small-star" aria-hidden="true">✳</span></strong><span class="hero-note">경험은 깊게.<br>시선은 더 멀리.</span></div>
|
||||
</div>
|
||||
<div class="hero-art" aria-label="서로 연결된 기술을 표현한 푸른 입체 궤도 그래픽" role="img">
|
||||
<div class="art-grid" aria-hidden="true"></div>
|
||||
<span class="art-coordinate coordinate-top" aria-hidden="true">[ TRONT / CONNECTED FUTURE ]</span>
|
||||
<canvas id="connection-canvas" aria-hidden="true"></canvas>
|
||||
<div class="orbit-label orbit-label-one" aria-hidden="true"><span></span>TECHNOLOGY</div>
|
||||
<div class="orbit-label orbit-label-two" aria-hidden="true"><span></span>POSSIBILITY</div>
|
||||
<div class="art-caption" aria-hidden="true"><span class="crosshair">+</span><span>INDEPENDENT IDEAS.<br>INFINITE CONNECTIONS.</span><span class="art-index">01 — 05</span></div>
|
||||
</div>
|
||||
<a class="scroll-cue" href="#about"><span class="scroll-line" aria-hidden="true"></span> SCROLL TO EXPLORE</a>
|
||||
<button class="motion-toggle" aria-pressed="false" aria-label="그래픽 애니메이션 일시정지"><span aria-hidden="true">Ⅱ</span></button>
|
||||
</section>
|
||||
|
||||
<div class="ticker" aria-hidden="true"><div class="ticker-track"><span>SMART MOBILITY <i>✳</i> DIGITAL PLATFORM <i>✳</i> CONNECTED FUTURE <i>✳</i> INDUSTRIAL INTELLIGENCE <i>✳</i> </span><span>SMART MOBILITY <i>✳</i> DIGITAL PLATFORM <i>✳</i> CONNECTED FUTURE <i>✳</i> INDUSTRIAL INTELLIGENCE <i>✳</i> </span></div></div>
|
||||
|
||||
<section id="about" class="about section wrap" aria-labelledby="about-title">
|
||||
<div class="section-top reveal"><p class="section-label"><span>01 /</span> ABOUT TRONT</p><span class="side-note">기술에 대한 깊이, 사람을 향한 방향</span></div>
|
||||
<div class="about-layout">
|
||||
<div class="about-intro reveal"><h2 id="about-title">복잡한 기술을<br><span class="muted">의미 있는 경험으로.</span></h2><div class="about-signature" aria-hidden="true"><span>✳</span><span>CONNECTING POSSIBILITIES<br>SINCE 2008</span></div></div>
|
||||
<div class="about-copy reveal"><p>트론트는 모바일, 웹, 애플리케이션을 넘어 산업과 일상을 연결하는 IT 솔루션 전문 기업입니다. 2008년의 첫걸음부터 다양한 산업 현장에서 쌓아온 경험으로, 고객의 아이디어를 작동하는 서비스로 만듭니다.</p><p>지능형 관제, 디지털 플랫폼, 미래 모빌리티까지.<br>기술의 경계를 넘나들며 비즈니스에 필요한 연결을 설계합니다.</p><a class="text-link" href="docs/(주)트론트_회사소개서_ver.1.2.0_20251202_압축.pdf" download="TRONT_회사소개서.pdf">트론트 회사소개서 <span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--down" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span><small>PDF · 9.2 MB</small></a></div>
|
||||
</div>
|
||||
<div class="company-profile reveal" aria-label="트론트 회사 개요 및 조직">
|
||||
<div class="company-identity">
|
||||
<p class="tiny-label">COMPANY AT A GLANCE</p>
|
||||
<h3>주식회사 트론트<span>TRONT Co., Ltd.</span></h3>
|
||||
<dl class="company-data">
|
||||
<div><dt>설립일</dt><dd><time datetime="2008-12-01">2008년 12월 1일</time></dd></div>
|
||||
<div><dt>사업분야</dt><dd>모바일 솔루션 · 웹 서비스 · 산업용 애플리케이션 개발</dd></div>
|
||||
<div><dt>소재지</dt><dd>대구광역시 북구 경대로17길 47<br>IT융합산업빌딩 1203호</dd></div>
|
||||
</dl>
|
||||
<a class="company-patent-link" href="#patents"><span class="company-link-mark" aria-hidden="true">✳</span>기술 연구와 특허 살펴보기<span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a>
|
||||
</div>
|
||||
<div class="company-organization">
|
||||
<div class="organization-heading">
|
||||
<div><p class="tiny-label">PEOPLE & ORGANIZATION</p><h3>함께 만드는 전문 조직</h3></div>
|
||||
<div class="organization-people"><strong><span data-count="18">18</span><span>명</span></strong><small>2025.10 기준</small></div>
|
||||
</div>
|
||||
<div class="organization-chart" aria-label="대표이사 김칠수 아래 기술지원팀, 개발1팀, 개발2팀으로 구성">
|
||||
<div class="organization-leader"><span>CEO · 대표이사</span><strong>김칠수</strong><i aria-hidden="true">✳</i></div>
|
||||
<ul class="organization-teams">
|
||||
<li><span class="team-index" aria-hidden="true">01</span><h4>기술지원팀</h4><p>TECHNICAL SUPPORT</p></li>
|
||||
<li><span class="team-index" aria-hidden="true">02</span><h4>개발1팀</h4><p>Web 개발<br>모바일 앱 개발</p></li>
|
||||
<li><span class="team-index" aria-hidden="true">03</span><h4>개발2팀</h4><p>내비게이션 개발</p></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="expertise" class="expertise section" aria-labelledby="expertise-title">
|
||||
<div class="wrap"><div class="section-top reveal"><p class="section-label"><span>02 /</span> OUR EXPERTISE</p><span class="side-note">FIVE FIELDS. ONE CONNECTED VISION.</span></div>
|
||||
<div class="expertise-layout"><div class="expertise-intro reveal"><h2 id="expertise-title">기술을 잇는<br>IT 전문가 그룹</h2><p>주식회사 트론트는 스마트 모빌리티, 산업 관제, 자산 관리, SaaS 플랫폼, <br>디지털 인터페이스 분야에서 고객의 디지털 혁신을 이끕니다.</p><div class="expertise-art" data-mode="mobility" aria-hidden="true">
|
||||
<div class="expertise-art-top"><span>CAPABILITY IN FOCUS</span><span class="capability-index">01 / 05</span></div>
|
||||
<div class="capability-sculpture"><i class="sculpture-plane plane-one"></i><i class="sculpture-plane plane-two"></i><i class="sculpture-plane plane-three"></i><i class="sculpture-plane plane-four"></i><div class="sculpture-core">HMI</div><span class="sculpture-point point-one"></span><span class="sculpture-point point-two"></span></div>
|
||||
<div class="expertise-art-bottom"><span class="capability-name">SMART MOBILITY</span><div class="capability-dots"><i class="is-active"></i><i></i><i></i><i></i><i></i></div></div>
|
||||
</div></div>
|
||||
<div class="service-list reveal">
|
||||
<details class="service" open><summary><span class="service-number">01</span><div><span class="tiny-label">SMART MOBILITY & HMI</span><h3>스마트 모빌리티 및 HMI 플랫폼</h3></div><span class="service-toggle" aria-hidden="true">+</span></summary><div class="service-content"><p>운전자와 차량 사이, 직관적인 연결을 만듭니다. 국내외 내비게이션 HMI와 차량 통신 기술을 바탕으로 새로운 이동 경험을 구현합니다.</p><div class="tags"><span>차량 통신</span><span>네비게이션 개발 연결성</span><span>관제 시스템</span></div></div></details>
|
||||
<details class="service"><summary><span class="service-number">02</span><div><span class="tiny-label">INDUSTRIAL MONITORING & CONTROL</span><h3>산업 통합 관제 및 제어 시스템</h3></div><span class="service-toggle" aria-hidden="true">+</span></summary><div class="service-content"><p>현장의 데이터를 한눈에, 복잡한 운영을 더 명확하게. 실시간 모니터링과 데이터 송수신, 원격 제어를 통합해 산업 현장의 효율과 안전을 지원합니다.</p><div class="tags"><span>실시간 모니터링</span><span>데이터 송수신 프로토콜</span><span>시스템 통합(SI)</span><span>원격 제어 및 안전 시스템</span></div></div></details>
|
||||
<details class="service"><summary><span class="service-number">03</span><div><span class="tiny-label">WEB3 & ASSET MANAGEMENT</span><h3>고도화된 Web3 & Asset 관리 솔루션</h3></div><span class="service-toggle" aria-hidden="true">+</span></summary><div class="service-content"><p>자산의 흐름에 정확함을, 운영에 투명함을 더합니다. 블록체인 기반 서비스와 RFID·NFC 기술을 활용해 데이터와 실물 자산을 연결합니다.</p><div class="tags"><span>블록체인 기반 투명성 확보</span><span>RFID / NFC 등 무선 통신 기반 자산 관리</span><span>금융 기술 융합</span></div></div></details>
|
||||
<details class="service"><summary><span class="service-number">04</span><div><span class="tiny-label">COMMUNITY & SAAS PLATFORM</span><h3>전문 커뮤니티 및 SaaS 플랫폼</h3></div><span class="service-toggle" aria-hidden="true">+</span></summary><div class="service-content"><p>사람과 사람, 서비스와 일상을 연결합니다. 약국·교육·스포츠 등 도메인에 대한 이해를 바탕으로 모바일 앱, 웹, 관리자 시스템을 함께 구축합니다.</p><div class="tags"><span>도메인 특화 Web/App 서비스</span><span>GroupWare</span><span>LMS 개발</span></div></div></details>
|
||||
<details class="service"><summary><span class="service-number">05</span><div><span class="tiny-label">FUTURE TECH & DIGITAL INTERFACE</span><h3>미래 기술 및 디지털 인터페이스</h3></div><span class="service-toggle" aria-hidden="true">+</span></summary><div class="service-content"><p>새로운 기술이 자연스러운 경험이 되도록. 가상현실 기반 웹 환경, 디지털 사이니지, 영상 분석과 UX 기술로 디지털 접점을 확장합니다.</p><div class="tags"><span>Virtual Reality기반 Web 환경</span><span>SIGNAGE</span><span>영상 분석 및 UX</span></div></div></details>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="work" class="work section wrap" aria-labelledby="work-title">
|
||||
<div class="section-top reveal"><p class="section-label"><span>03 /</span> SELECTED WORK</p><span class="side-note">아이디어가 현실이 되는 순간</span></div>
|
||||
<div class="section-heading reveal"><h2 id="work-title">기술로 만든<br><span class="muted">실제의 변화.</span></h2><p>다양한 산업에서 증명해 온 실행력.<br>트론트가 함께 만든 프로젝트를 소개합니다.</p></div>
|
||||
<div class="project-toolbar reveal"><div class="project-filters" role="group" aria-label="프로젝트 분야 필터"><button class="filter is-active" data-filter="all" aria-pressed="true">전체 <sup>08</sup></button><button class="filter" data-filter="mobility" aria-pressed="false">모빌리티</button><button class="filter" data-filter="platform" aria-pressed="false">디지털 플랫폼</button><button class="filter" data-filter="industry" aria-pressed="false">관제 & 자산 관리</button></div><span class="project-count" aria-live="polite">8 PROJECTS</span></div>
|
||||
<div class="project-grid">
|
||||
<!-- 01 -->
|
||||
<article id="project-mobility" class="project-card reveal" data-category="mobility">
|
||||
<a class="project-open" data-project="mobility" href="projects/mobility.html" aria-label="현대 오토에버 국내향 및 해외향 내비게이션 HMI 개발 포트폴리오 보기">
|
||||
<div class="project-meta"><span>현대 오토에버</span><span>2018 — 2025</span></div>
|
||||
<h3>국내향 및 해외향 내비게이션 HMI 개발</h3>
|
||||
<div class="project-scene scene--mobility" aria-hidden="true"><div class="scene-surface">
|
||||
<div class="scene-grid"></div><div class="scene-kicker"><span>NAVIGATION / HMI</span><span>01 / 08</span></div>
|
||||
<div class="scene-wordmark">Navigation<br><em>HMI.</em></div><div class="scene-phrase">NAVIGATION GUI / INTERFACE LAYER</div>
|
||||
<div class="scene-orbit"><i></i><i></i><i></i><i></i></div>
|
||||
<div class="scene-visual"><img src="assets/images/mobility_01.png" alt="" loading="lazy" width="1577" height="727"></div>
|
||||
<div class="scene-annotation">HYUNDAI / KIA</div>
|
||||
<div class="scene-bottom"><span>차량용 내비게이션</span><span class="scene-view">EXPLORE PROJECT <b class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></b></span></div>
|
||||
</div></div>
|
||||
<div class="project-scope"><p>내비게이션 GUI 개발 · Interface layer 개발</p><span>프로젝트 자세히 보기 <svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></div>
|
||||
</a>
|
||||
</article>
|
||||
<!-- 02 -->
|
||||
<article id="project-pharmacy" class="project-card reveal" data-category="platform">
|
||||
<a class="project-open" data-project="pharmacy" href="projects/pharmacy.html" aria-label="모두의약국 약사님들의 커뮤니티 개발 포트폴리오 보기">
|
||||
<div class="project-meta"><span>모두의약국</span><span>2022 — 2025</span></div>
|
||||
<h3>약사님들의 커뮤니티 개발</h3>
|
||||
<div class="project-scene scene--pharmacy" aria-hidden="true"><div class="scene-surface">
|
||||
<div class="scene-grid"></div><div class="scene-kicker"><span>COMMUNITY PLATFORM</span><span>02 / 08</span></div>
|
||||
<div class="scene-wordmark">모두의<br><em>약국.</em></div><div class="scene-phrase">A COMMUNITY FOR PHARMACISTS.</div>
|
||||
<div class="scene-orbit"><i></i><i></i><i></i><i></i></div>
|
||||
<div class="scene-visual"><img src="assets/images/pharmacy_01.png" alt="" loading="lazy" width="702" height="1440"></div>
|
||||
<div class="scene-annotation">APP / WEB / ADMIN</div>
|
||||
<div class="scene-bottom"><span>모바일 앱 / 웹</span><span class="scene-view">EXPLORE PROJECT <b class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></b></span></div>
|
||||
</div></div>
|
||||
<div class="project-scope"><p>모바일 앱 · 사용자 웹 · 백엔드 개발 · 관리자 화면 개발</p><span>프로젝트 자세히 보기 <svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></div>
|
||||
</a>
|
||||
</article>
|
||||
<!-- 03 -->
|
||||
<article id="project-bems" class="project-card reveal" data-category="industry">
|
||||
<a class="project-open" data-project="bems" href="projects/bems.html" aria-label="BEMS 빌딩 에너지 통합 관리 시스템 BEMS 개발 포트폴리오 보기">
|
||||
<div class="project-meta"><span>BEMS</span><span>2023 — 2024</span></div>
|
||||
<h3>빌딩 에너지 통합 관리 시스템 BEMS 개발</h3>
|
||||
<div class="project-scene scene--bems" aria-hidden="true"><div class="scene-surface">
|
||||
<div class="scene-grid"></div><div class="scene-kicker"><span>BUILDING ENERGY MANAGEMENT</span><span>03 / 08</span></div>
|
||||
<div class="scene-wordmark">BEMS<span class="word-period">.</span></div><div class="scene-phrase">SENSORS / ENERGY / MONITORING</div>
|
||||
<div class="scene-orbit"><i></i><i></i><i></i><i></i></div>
|
||||
<div class="scene-visual"><img src="assets/images/bems_01.png" alt="" loading="lazy" width="1199" height="534"></div>
|
||||
<div class="scene-annotation">BUILDING ENERGY MANAGEMENT</div>
|
||||
<div class="scene-bottom"><span>웹</span><span class="scene-view">EXPLORE PROJECT <b class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></b></span></div>
|
||||
</div></div>
|
||||
<div class="project-scope"><p>사용자 웹 · 백엔드 개발 · 관리자 화면 개발</p><span>프로젝트 자세히 보기 <svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></div>
|
||||
</a>
|
||||
</article>
|
||||
<!-- -->
|
||||
<article id="project-givon" class="project-card reveal" data-category="platform">
|
||||
<a class="project-open" data-project="givon" href="projects/givon.html" aria-label="GivOn 블록체인 기반 기부 플랫폼 관리자 페이지 개발 포트폴리오 보기">
|
||||
<div class="project-meta"><span>GivOn</span><span>2025</span></div>
|
||||
<h3>블록체인 기반 기부 플랫폼 관리자 페이지 개발</h3>
|
||||
<div class="project-scene scene--givon" aria-hidden="true"><div class="scene-surface">
|
||||
<div class="scene-grid"></div><div class="scene-kicker"><span>DONATION PLATFORM / ADMIN</span><span>04 / 08</span></div>
|
||||
<div class="scene-wordmark">Giv<em>On</em><span class="word-period">.</span></div><div class="scene-phrase">ORGANIZATIONS / DONATIONS / VOLUNTEERING</div>
|
||||
<div class="scene-orbit"><i></i><i></i><i></i><i></i></div>
|
||||
<div class="scene-visual"><img src="assets/images/givon_01.png" alt="" loading="lazy" width="1774" height="528"></div>
|
||||
<div class="scene-annotation">DONATION PLATFORM / ADMIN</div>
|
||||
<div class="scene-bottom"><span>관리자 화면</span><span class="scene-view">EXPLORE PROJECT <b class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></b></span></div>
|
||||
</div></div>
|
||||
<div class="project-scope"><p>백엔드 개발 · 관리자 화면 개발</p><span>프로젝트 자세히 보기 <svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></div>
|
||||
</a>
|
||||
</article>
|
||||
<!-- 05 -->
|
||||
<article id="project-bispet" class="project-card reveal" data-category="platform">
|
||||
<a class="project-open" data-project="bispet" href="projects/bispet.html" aria-label="BISPET 애견인들 위한 커뮤니티 개발 포트폴리오 보기">
|
||||
<div class="project-meta"><span>BISPET</span><span>2024 — 2025</span></div>
|
||||
<h3>애견인들 위한 커뮤니티 개발</h3>
|
||||
<div class="project-scene scene--bispet" aria-hidden="true"><div class="scene-surface">
|
||||
<div class="scene-grid"></div><div class="scene-kicker"><span>SNS / COMMUNITY</span><span>05 / 08</span></div>
|
||||
<div class="scene-wordmark">BIS<em>PET.</em></div><div class="scene-phrase">PROFILE / FEED / SCHEDULE / WALK</div>
|
||||
<div class="scene-orbit"><i></i><i></i><i></i><i></i></div>
|
||||
<div class="scene-visual"><img src="assets/images/bispet_01.png" alt="" loading="lazy" width="702" height="1440"></div>
|
||||
<div class="scene-annotation">MOBILE APP / ADMIN</div>
|
||||
<div class="scene-bottom"><span>모바일 앱 / 관리자 화면</span><span class="scene-view">EXPLORE PROJECT <b class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></b></span></div>
|
||||
</div></div>
|
||||
<div class="project-scope"><p>모바일 앱 · 백엔드 개발 · 관리자 화면 개발</p><span>프로젝트 자세히 보기 <svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></div>
|
||||
</a>
|
||||
</article>
|
||||
<!-- -->
|
||||
<article id="project-asset" class="project-card reveal" data-category="industry">
|
||||
<a class="project-open" data-project="asset" href="projects/asset.html" aria-label="WideAsset S/W RF 자산관리 솔루션 구축 포트폴리오 보기">
|
||||
<div class="project-meta"><span>WideAsset S/W</span><span>2014</span></div>
|
||||
<h3>RF 자산관리 솔루션 구축</h3>
|
||||
<div class="project-scene scene--asset" aria-hidden="true"><div class="scene-surface">
|
||||
<div class="scene-grid"></div><div class="scene-kicker"><span>RF / ASSET MANAGEMENT</span><span>06 / 08</span></div>
|
||||
<div class="scene-wordmark">Wide<em>Asset.</em></div><div class="scene-phrase">RFID TAG READER / WIDEASSET S/W</div>
|
||||
<div class="scene-orbit"><i></i><i></i><i></i><i></i></div>
|
||||
<div class="scene-visual"><img src="assets/images/asset_01.png" alt="" loading="lazy" width="1399" height="652"></div>
|
||||
<div class="scene-annotation">IT ASSETS / OFFICE EQUIPMENT</div>
|
||||
<div class="scene-bottom"><span>PDA / WideAsset S/W</span><span class="scene-view">EXPLORE PROJECT <b class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></b></span></div>
|
||||
</div></div>
|
||||
<div class="project-scope"><p>RFID Tag Reader 인터페이스 개발 · 데이터 조회 및 업데이트 모듈 개발</p><span>프로젝트 자세히 보기 <svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></div>
|
||||
</a>
|
||||
</article>
|
||||
<!-- 07 -->
|
||||
<article id="project-signage" class="project-card reveal" data-category="platform">
|
||||
<a class="project-open" data-project="signage" href="projects/signage.html" aria-label="Signage 솔루션 빌딩(건물) 영상홍보 통합안내 시스템 포트폴리오 보기">
|
||||
<div class="project-meta"><span>Signage 솔루션</span><span>2012</span></div>
|
||||
<h3>빌딩(건물) 영상홍보 통합안내 시스템</h3>
|
||||
<div class="project-scene scene--signage" aria-hidden="true"><div class="scene-surface">
|
||||
<div class="scene-grid"></div><div class="scene-kicker"><span>DIGITAL SIGNAGE / NETWORK</span><span>07 / 08</span></div>
|
||||
<div class="scene-wordmark">Signage<em>Network.</em></div><div class="scene-phrase">CONTENT / SCHEDULE / DISTRIBUTION</div>
|
||||
<div class="scene-graphic"><svg viewBox="0 0 400 330" fill="none" aria-hidden="true" focusable="false">
|
||||
<path class="signage-link" d="M100 183H170V82H222M170 183H268M170 183V270H215"/>
|
||||
<circle cx="170" cy="183" r="4" fill="currentColor"/>
|
||||
<rect class="signage-display" x="30" y="137" width="100" height="72" rx="5"/>
|
||||
<rect class="signage-pane" x="39" y="146" width="82" height="45" rx="2"/>
|
||||
<path class="signage-content" d="M66 158L85 168L66 179Z M69 218H91 M80 209V218"/>
|
||||
<rect class="signage-display" x="222" y="32" width="130" height="91" rx="5"/>
|
||||
<rect class="signage-pane" x="232" y="42" width="73" height="61" rx="2"/>
|
||||
<path class="signage-content" d="M313 49H341M313 60H341M313 71H333M240 111H334M258 62L278 73L258 85Z"/>
|
||||
<rect class="signage-display" x="268" y="151" width="65" height="108" rx="5"/>
|
||||
<rect class="signage-pane" x="276" y="159" width="49" height="62" rx="2"/>
|
||||
<path class="signage-content" d="M284 231H317M284 240H306"/>
|
||||
<rect class="signage-display" x="215" y="278" width="127" height="30" rx="4"/>
|
||||
<path class="signage-content" d="M228 293H272M281 293H330"/>
|
||||
<path class="signage-link" d="M52 243H148V295H196M192 22H369V136"/>
|
||||
</svg></div>
|
||||
<div class="scene-annotation">ONE SYSTEM / MULTIPLE SCREENS</div>
|
||||
<div class="scene-bottom"><span>MULTI-VISION / DID</span><span class="scene-view">EXPLORE PROJECT <b class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></b></span></div>
|
||||
</div></div>
|
||||
<div class="project-scope"><p>중앙 관리 시스템 및 서버 구축 · 스케줄 및 파일 전송 로직 구현 · Infotrans Editor 및 Multi Manager 기능 구현</p><span>프로젝트 자세히 보기 <svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></div>
|
||||
</a>
|
||||
</article>
|
||||
<!-- 08 -->
|
||||
<article id="project-gps-building" class="project-card reveal" data-category="mobility">
|
||||
<a class="project-open" data-project="gps-building" href="projects/gps-building.html" aria-label="GPS 솔루션 GPS 트래킹 기반 위치확인 시스템 개발 포트폴리오 보기">
|
||||
<div class="project-meta"><span>GPS 솔루션</span><span>2010</span></div>
|
||||
<h3>GPS 트래킹 기반 위치확인 시스템 개발</h3>
|
||||
<div class="project-scene scene--gps-building" aria-hidden="true"><div class="scene-surface">
|
||||
<div class="scene-grid"></div><div class="scene-kicker"><span>GPS / LOCATION TRACKING</span><span>08 / 08</span></div>
|
||||
<div class="scene-wordmark">GPS<em>Tracking.</em></div><div class="scene-phrase">LOCATE / SAVE / NAVIGATE</div>
|
||||
<div class="scene-graphic"><svg viewBox="0 0 400 360" fill="none" aria-hidden="true" focusable="false">
|
||||
<path class="gps-contour" d="M30 355C-25 291 16 235 84 235S186 278 234 226S241 128 314 111S384 37 364 0 M3 331C-14 281 33 254 79 257S188 307 252 242S259 147 326 131S412 53 386 0 M56 360C0 300 32 277 79 282S200 330 272 262S278 166 340 152S425 91 402 41"/>
|
||||
<circle class="gps-contour" cx="284" cy="109" r="49"/><circle class="gps-contour" cx="284" cy="109" r="71"/><circle class="gps-contour" cx="284" cy="109" r="93"/>
|
||||
<path class="gps-route" d="M73 281H151Q169 281 169 263V219Q169 202 189 202H263Q284 202 284 182V117"/>
|
||||
<circle class="gps-point" cx="73" cy="281" r="7"/><circle class="gps-point" cx="169" cy="227" r="5"/>
|
||||
<path class="gps-pin" d="M284 117S263 96 263 81A21 21 0 0 1 305 81C305 96 284 117 284 117Z"/>
|
||||
<circle class="gps-pin-center" cx="284" cy="81" r="7"/>
|
||||
<path class="gps-contour" d="M333 279H357M345 267V291M79 137H95M87 129V145"/>
|
||||
</svg></div>
|
||||
<div class="scene-annotation">POSITION / DESTINATION / DIRECTION</div>
|
||||
<div class="scene-bottom"><span>GPS / OUTDOOR TRACKER</span><span class="scene-view">EXPLORE PROJECT <b class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></b></span></div>
|
||||
</div></div>
|
||||
<div class="project-scope"><p>GPS Manager · Location Manager · Navigation Manager 기반 디바이스 드라이버 개발</p><span>프로젝트 자세히 보기 <svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></div>
|
||||
</a>
|
||||
</article>
|
||||
</div>
|
||||
<!-- footnote -->
|
||||
<p class="work-footnote">소개서에 수록된 실제 프로젝트와 수행 기간을 기준으로 구성했습니다.</p>
|
||||
</section>
|
||||
|
||||
<section id="patents" class="patents section" aria-labelledby="patents-title">
|
||||
<div class="wrap">
|
||||
<div class="section-top reveal"><p class="section-label"><span>04 /</span> PATENTS & R&D</p><span class="side-note">IDEAS INTO INTELLECTUAL PROPERTY</span></div>
|
||||
<div class="patents-heading reveal">
|
||||
<div><h2 id="patents-title">기술의 깊이를<br><span>보여주는 특허.</span></h2><p>특정 기술에 안주하지 않는 R&D 프로세스를 핵심 과정으로 삼고 있습니다.<br>미래 비즈니스 환경 변화에 선제적으로 대응하기 위해 지속적인 발굴 및 지식재산권 확보에 주력하고 있습니다.<br>특허와 인증은 당사가 미래에도 신뢰할 수 있는 파트너임을 증명하는 잠재적 능력입니다.</p></div>
|
||||
<div class="patent-signature" aria-hidden="true"><span class="patent-seal"><i></i><i></i><i></i><b>✳</b></span><span>RESEARCH.<br>DEVELOPMENT.<br>INNOVATION.</span></div>
|
||||
</div>
|
||||
<div class="patent-grid">
|
||||
<article class="patent-card reveal">
|
||||
<a class="patent-document" href="assets/images/patent-virtual-image.png" target="_blank" rel="noopener" aria-label="객체 가상 이미지 구현 방법 특허증 크게 보기, 새 탭">
|
||||
<span class="patent-document-label" aria-hidden="true">PATENT / 01</span>
|
||||
<img src="assets/images/patent-virtual-image.png" alt="객체 가상 이미지 구현 방법 특허증, 제10-0951362호" loading="lazy" width="960" height="1396">
|
||||
<span class="patent-document-action">특허증 크게 보기 <span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></span>
|
||||
</a>
|
||||
<div class="patent-information">
|
||||
<p class="patent-kicker"><span>01</span> VIRTUAL IMAGE</p>
|
||||
<h3>객체 가상 이미지 <span class="patent-title-line">구현 방법</span></h3>
|
||||
<p class="patent-description">실제 객체를 기반으로 가상 이미지를 생성하고, 현실과 통합하는 기술입니다.</p>
|
||||
<dl class="patent-data"><div><dt>특허번호</dt><dd>제10-0951362호</dd></div><div><dt>출원일</dt><dd><time datetime="2008-07-04">2008.07.04</time></dd></div><div><dt>등록일</dt><dd><time datetime="2010-03-30">2010.03.30</time></dd></div></dl>
|
||||
</div>
|
||||
</article>
|
||||
<article class="patent-card reveal">
|
||||
<a class="patent-document" href="assets/images/patent-liquid-dispenser.png" target="_blank" rel="noopener" aria-label="액체 세제 자동 판매 장치 특허증 크게 보기, 새 탭">
|
||||
<span class="patent-document-label" aria-hidden="true">PATENT / 02</span>
|
||||
<img src="assets/images/patent-liquid-dispenser.png" alt="액체 세제 자동 판매 장치 특허증, 제10-1099507호" loading="lazy" width="960" height="1424">
|
||||
<span class="patent-document-action">특허증 크게 보기 <span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></span>
|
||||
</a>
|
||||
<div class="patent-information">
|
||||
<p class="patent-kicker"><span>02</span> DEVICE CONTROL</p>
|
||||
<h3>액체 세제 <span class="patent-title-line">자동 판매 장치</span></h3>
|
||||
<p class="patent-description">액체 세제의 자동 판매 및 관리를 위한 기술로 효율적인 자판기 시스템 제어에 대한 특허.</p>
|
||||
<dl class="patent-data"><div><dt>특허번호</dt><dd>제10-1099507호</dd></div><div><dt>출원일</dt><dd><time datetime="2010-07-28">2010.07.28</time></dd></div><div><dt>등록일</dt><dd><time datetime="2011-12-21">2011.12.21</time></dd></div></dl>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<div class="patents-footnote reveal"><p>회사소개서에 수록된 특허증을 기준으로 소개합니다.</p><a class="text-link" href="docs/(주)트론트_회사소개서_ver.1.2.0_20251202_압축.pdf#page=4" target="_blank" rel="noopener">소개서에서 보기 <span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section id="journey" class="journey section" aria-labelledby="journey-title"><div class="wrap"><div class="section-top reveal"><p class="section-label"><span>05 /</span> OUR JOURNEY</p><span class="side-note">A CONTINUOUS JOURNEY OF INNOVATION</span></div><div class="journey-heading reveal"><h2 id="journey-title">새로운 길은,<br>계속됩니다.</h2><p>첫 서버에서 글로벌 모빌리티까지.<br>기술이 바뀌어도, 더 나은 답을 찾는 우리의 태도는 같습니다.</p></div><div class="timeline-tabs reveal" role="tablist" aria-label="트론트 주요 연혁"><button id="year-2008" role="tab" aria-selected="false" aria-controls="timeline-panel" tabindex="-1" data-year="2008">2008<span>THE BEGINNING</span></button><button id="year-2012" role="tab" aria-selected="false" aria-controls="timeline-panel" tabindex="-1" data-year="2012">2012<span>NEW CONNECTIONS</span></button><button id="year-2018" role="tab" aria-selected="false" aria-controls="timeline-panel" tabindex="-1" data-year="2018">2018<span>MOVING FORWARD</span></button><button id="year-2022" role="tab" aria-selected="false" aria-controls="timeline-panel" tabindex="-1" data-year="2022">2022<span>EVERYDAY IMPACT</span></button><button id="year-2025" role="tab" aria-selected="true" aria-controls="timeline-panel" tabindex="0" data-year="2025">2025<span>WHAT'S NEXT</span></button></div><div id="timeline-panel" class="timeline-panel reveal" role="tabpanel" aria-labelledby="year-2025" tabindex="0"><span class="timeline-year" aria-hidden="true">2025</span><div class="timeline-detail"><p class="eyebrow">WHAT'S NEXT</p><h3>더 넓은 연결, 다음의 가능성</h3><p class="timeline-description">블록체인 기반 기부 플랫폼 GivOn 관리자 개발과 소규모 기관차 관제 프로그램 개발로 기술의 쓰임을 넓혀갑니다.</p><div class="tags"><span>Web3</span><span>관제 시스템</span><span>디지털 플랫폼</span></div></div></div></div></section>
|
||||
|
||||
<section id="contact" class="contact section" aria-labelledby="contact-title"><div class="wrap"><div class="section-top reveal"><p class="section-label"><span>06 /</span> LET'S CONNECT</p><span class="side-note">YOUR NEXT CHAPTER STARTS HERE.</span></div><div class="contact-layout"><div class="contact-copy reveal"><h2 id="contact-title">다음의 가능성,<br>함께 만들까요<span>?</span></h2><p>새로운 서비스의 시작부터 복잡한 기술의 해답까지.<br>트론트와 새로운 프로젝트를 함께 만들어가요.</p><a class="contact-cta" data-contact-email href="mailto:hello@tront.example">함께 프로젝트 만들기 <span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a></div><div class="contact-details reveal"><div class="contact-row"><span class="tiny-label">EMAIL</span><div><a data-contact-email href="mailto:hello@tront.example">hello@tront.example</a><button class="copy-email" aria-label="이메일 주소 복사">복사 <span aria-hidden="true">⧉</span></button></div></div><div class="contact-row"><span class="tiny-label">PHONE</span><a data-contact-phone href="tel:0530000000">053-123-4567</a></div><p class="contact-placeholder">이메일과 연락처 정보 없음.</p><div class="contact-row address-row"><span class="tiny-label">COME FIND US</span><address>대구광역시 북구 경대로17길 47<br>IT융합산업빌딩 1203호</address><button type="button" class="map-link" aria-haspopup="dialog" aria-controls="map-dialog">지도에서 보기 <span aria-hidden="true">⌖</span></button></div></div></div></div></section>
|
||||
</main>
|
||||
|
||||
<footer class="site-footer"><div class="wrap"><div class="footer-top"><a class="footer-name" href="#home" aria-label="트론트 홈">TRONT<span class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a><p>THE ROAD NOT TAKEN.<br>THE FUTURE WE CREATE.</p><a class="back-top" href="#home" aria-label="맨 위로"><svg class="arrow-icon arrow-icon--up" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></a></div><div class="footer-bottom"><span>주식회사 트론트 <span class="footer-divider">|</span> 대표이사 김칠수</span><span>© <span id="copyright-year">2026</span> TRONT. All rights reserved.</span><a href="docs/(주)트론트_회사소개서_ver.1.2.0_20251202_압축.pdf" download="TRONT_회사소개서.pdf">회사소개서 다운로드 <svg class="arrow-icon arrow-icon--down-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></a></div></div></footer>
|
||||
|
||||
<dialog id="map-dialog" class="map-dialog" aria-labelledby="map-dialog-title" aria-describedby="map-dialog-address">
|
||||
<header class="map-dialog-header">
|
||||
<div><p class="tiny-label">COME FIND US</p><h2 id="map-dialog-title">트론트 오시는 길</h2></div>
|
||||
<button type="button" class="map-dialog-close" aria-label="지도 닫기" autofocus><span aria-hidden="true">×</span> 닫기</button>
|
||||
</header>
|
||||
<div class="map-dialog-body" aria-busy="true">
|
||||
<div class="map-dialog-canvas"></div>
|
||||
<div class="map-dialog-status" role="status"><p>지도를 불러오는 중입니다.</p><button type="button" class="map-dialog-retry" hidden>다시 불러오기</button></div>
|
||||
</div>
|
||||
<footer class="map-dialog-footer"><address id="map-dialog-address">대구광역시 북구 경대로17길 47<br><strong>IT융합산업빌딩 1203호</strong></address><span class="map-dialog-hint">바깥 영역을 누르면 닫힙니다.</span></footer>
|
||||
</dialog>
|
||||
|
||||
<div class="toast" role="status" aria-live="polite"></div>
|
||||
</body>
|
||||
</html>
|
||||
91
map-dialog.css
Normal file
@@ -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; }
|
||||
}
|
||||
113
map-dialog.js
Normal file
@@ -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();
|
||||
});
|
||||
})();
|
||||
289
portfolio.css
Normal file
@@ -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}
|
||||
}
|
||||
194
portfolio.js
Normal file
@@ -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);
|
||||
}
|
||||
})();
|
||||
51
projects/asset.html
Normal file
@@ -0,0 +1,51 @@
|
||||
<!doctype html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="theme-color" content="#f5f5ef"><meta name="description" content="국내향 RF 기반 IT 자산 및 사무기기 관리 솔루션 개발">
|
||||
<meta property="og:type" content="website"><meta property="og:title" content="WideAsset S/W — RF 자산관리 솔루션 구축 | TRONT"><meta property="og:description" content="국내향 RF 기반 IT 자산 및 사무기기 관리 솔루션 개발">
|
||||
<title>WideAsset S/W — RF 자산관리 솔루션 구축 | TRONT</title>
|
||||
<link rel="icon" href="../assets/images/tront-logo.png"><link rel="stylesheet" href="../styles.css"><link rel="stylesheet" href="../portfolio.css"><script src="../portfolio.js" defer></script>
|
||||
<link rel="stylesheet" href="../image-viewer.css">
|
||||
<script src="../image-viewer.js" defer></script>
|
||||
<link rel="stylesheet" href="../icons.css">
|
||||
</head>
|
||||
<body class="portfolio-page case--asset">
|
||||
<!-- Shared arrow shape from assets/icons/arrow_right_alt.svg. -->
|
||||
<svg class="icon-definitions" xmlns="http://www.w3.org/2000/svg" width="0" height="0" aria-hidden="true" focusable="false"><defs><symbol id="arrow-right-alt" viewBox="0 -960 960 960"><path d="m560-240-56-58 142-142H160v-80h486L504-662l56-58 240 240-240 240Z"/></symbol></defs></svg>
|
||||
<a class="skip-link" href="#case-main">본문으로 바로가기</a><div class="case-progress" aria-hidden="true"></div>
|
||||
<header class="case-header"><div class="wrap"><a class="case-back" href="../index.html#project-asset" data-back-to-work><span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-left" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span> 포트폴리오 목록</a><a class="case-brand" href="../index.html" aria-label="트론트 홈">TRONT<span class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a><a class="case-header-contact" href="../index.html#contact">함께 만들어가요 <span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a></div></header>
|
||||
<main id="case-main">
|
||||
<section class="case-hero wrap">
|
||||
<div class="case-eyebrow case-enter"><span>TRONT / SELECTED WORK</span><span>PROJECT 06 — 08</span></div>
|
||||
<div class="case-title-row"><div><p class="case-project-brand case-enter">WideAsset S/W</p><h1 class="case-enter">RF 자산관리 솔루션 구축</h1></div><span class="arrow-slot case-hero-arrow case-enter" aria-hidden="true"><svg class="arrow-icon arrow-icon--down-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></div>
|
||||
<dl class="case-meta case-enter"><div><dt>PROJECT PERIOD</dt><dd>2014.01 — 2014.12</dd></div><div><dt>OUR ROLE</dt><dd>RFID Tag Reader 인터페이스 개발 · 데이터 조회 및 업데이트 모듈 개발</dd></div><div><dt>PLATFORM</dt><dd>PDA / WideAsset S/W</dd></div></dl>
|
||||
</section>
|
||||
<div class="case-cover case-enter"><div class="project-scene scene--asset" aria-hidden="true"><div class="scene-surface">
|
||||
<div class="scene-grid"></div><div class="scene-kicker"><span>RF / ASSET MANAGEMENT</span><span>06 / 08</span></div>
|
||||
<div class="scene-wordmark">Wide<em>Asset.</em></div><div class="scene-phrase">RFID TAG READER / WIDEASSET S/W</div>
|
||||
<div class="scene-orbit"><i></i><i></i><i></i><i></i></div>
|
||||
<div class="scene-visual"><img src="../assets/images/asset_01.png" alt="" loading="eager" width="1399" height="652"></div>
|
||||
<div class="scene-annotation">IT ASSETS / OFFICE EQUIPMENT</div>
|
||||
<div class="scene-bottom"><span>PDA / WideAsset S/W</span><span class="scene-view">PROJECT SCREENS <b class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></b></span></div>
|
||||
</div></div></div>
|
||||
<nav class="case-nav" aria-label="프로젝트 상세 메뉴"><div class="wrap"><span>WideAsset S/W</span><div><a href="#overview" aria-current="location">프로젝트 개요</a><a href="#screens">시스템 구성</a></div><span class="case-reading-progress" aria-hidden="true">00%</span></div></nav>
|
||||
<section id="overview" class="case-overview case-section wrap">
|
||||
<div class="case-section-label case-reveal"><span>01 / OVERVIEW</span><span>프로젝트 소개</span></div>
|
||||
<div class="case-overview-grid">
|
||||
<h2 class="case-reveal">IT 자산과 사무기기를<br>RF로 연결하다.</h2>
|
||||
<div class="case-reveal"><p>국내향 RF 기반 IT 자산 및 사무기기 관리 솔루션 개발</p></div>
|
||||
</div>
|
||||
<div class="case-features" aria-label="서비스 대상, 주요 기능, 적용 기술">
|
||||
<article class="case-feature case-reveal"><span class="feature-index">01</span><h3>서비스 대상</h3><p>국내 IT 자산과 사무기기를 관리하기 위한 RF 기반 솔루션입니다.</p></article>
|
||||
<article class="case-feature case-reveal"><span class="feature-index">02</span><h3>주요 기능</h3><p>RFID Tag Reader와 PDA, WideAsset S/W를 연동해 자산 데이터를 조회하고 업데이트합니다.</p></article>
|
||||
<article class="case-feature case-reveal"><span class="feature-index">03</span><h3>적용 기술</h3><p>C#과 C++를 사용하고 DB를 연동해 RF 기반 자산관리 솔루션을 구현했습니다.</p></article>
|
||||
</div>
|
||||
</section>
|
||||
<section id="screens" class="case-screens case-section"><div class="wrap"><div class="case-section-label case-reveal"><span>02 / SCREENS IN FOCUS</span><span>시스템 구성</span></div><div class="case-screen-heading case-reveal"><h2>화면으로 만나는<br><span>프로젝트의 디테일.</span></h2><p>자산과 장비의 연결을<br>시스템 구성도로 살펴보세요.</p></div><div class="screen-tabs case-reveal" role="tablist" aria-label="WideAsset S/W 시스템 구성"><button id="screen-tab-0" role="tab" aria-selected="true" aria-controls="screen-0" tabindex="0" data-screen="0"><span>01</span>시스템 구성</button></div><div class="screen-gallery case-reveal"><figure class="case-screen is-current" id="screen-0" role="tabpanel" aria-labelledby="screen-tab-0" tabindex="0"><div class="screen-canvas"><img src="../assets/images/asset_01.png" alt="RFID Tag Reader, PDA, WideAsset S/W와 서버를 연결하는 시스템 구성도입니다." loading="lazy" width="1399" height="652"></div><figcaption><span>01 / 01</span><p>RFID Tag Reader, PDA, WideAsset S/W와 서버를 연결하는 시스템 구성도입니다.</p><a href="../assets/images/asset_01.png" target="_blank" rel="noopener" aria-label="시스템 구성 전체 화면으로 보기">크게 보기 ⤢</a></figcaption></figure></div><p class="case-source-note">프로젝트 화면 자료입니다.</p></div></section>
|
||||
<section class="case-next"><a href="signage.html" class="wrap case-next-link"><div><span class="tiny-label">KEEP EXPLORING / NEXT PROJECT</span><h2>Signage 솔루션</h2><p>빌딩(건물) 영상홍보 통합안내 시스템</p></div><span class="arrow-slot case-next-arrow" aria-hidden="true"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a></section>
|
||||
<section class="case-connect wrap"><div><p class="tiny-label">LET'S BUILD WHAT'S NEXT.</p><h2>다음 프로젝트,<br>함께 만들어가요.</h2></div><a href="../index.html#contact">함께 프로젝트 만들기 <span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a></section>
|
||||
</main>
|
||||
<footer class="case-footer wrap"><a href="../index.html">주식회사 트론트</a><span>© 2026 TRONT. All rights reserved.</span><a href="#case-main">맨 위로 <svg class="arrow-icon arrow-icon--up" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></a></footer>
|
||||
</body>
|
||||
</html>
|
||||
53
projects/bems.html
Normal file
@@ -0,0 +1,53 @@
|
||||
<!doctype html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="theme-color" content="#f5f5ef"><meta name="description" content="빌딩 내 온/습도 센서, 공조기 가동 센서 및 사용 전력 관리를 전력 관리 관제 프로그램 개발">
|
||||
<meta property="og:type" content="website"><meta property="og:title" content="BEMS — 빌딩 에너지 통합 관리 시스템 BEMS 개발 | TRONT"><meta property="og:description" content="빌딩 내 온/습도 센서, 공조기 가동 센서 및 사용 전력 관리를 전력 관리 관제 프로그램 개발">
|
||||
<title>BEMS — 빌딩 에너지 통합 관리 시스템 BEMS 개발 | TRONT</title>
|
||||
<link rel="icon" href="../assets/images/tront-logo.png"><link rel="stylesheet" href="../styles.css"><link rel="stylesheet" href="../portfolio.css"><script src="../portfolio.js" defer></script>
|
||||
<link rel="stylesheet" href="../image-viewer.css">
|
||||
<script src="../image-viewer.js" defer></script>
|
||||
<link rel="stylesheet" href="../icons.css">
|
||||
</head>
|
||||
<body class="portfolio-page case--bems">
|
||||
<!-- Shared arrow shape from assets/icons/arrow_right_alt.svg. -->
|
||||
<svg class="icon-definitions" xmlns="http://www.w3.org/2000/svg" width="0" height="0" aria-hidden="true" focusable="false"><defs><symbol id="arrow-right-alt" viewBox="0 -960 960 960"><path d="m560-240-56-58 142-142H160v-80h486L504-662l56-58 240 240-240 240Z"/></symbol></defs></svg>
|
||||
<a class="skip-link" href="#case-main">본문으로 바로가기</a><div class="case-progress" aria-hidden="true"></div>
|
||||
<header class="case-header"><div class="wrap"><a class="case-back" href="../index.html#project-bems" data-back-to-work><span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-left" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span> 포트폴리오 목록</a><a class="case-brand" href="../index.html" aria-label="트론트 홈">TRONT<span class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a><a class="case-header-contact" href="../index.html#contact">함께 만들어가요 <span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a></div></header>
|
||||
<main id="case-main">
|
||||
<section class="case-hero wrap">
|
||||
<div class="case-eyebrow case-enter"><span>TRONT / SELECTED WORK</span><span>PROJECT 03 — 08</span></div>
|
||||
<div class="case-title-row"><div><p class="case-project-brand case-enter">BEMS</p><h1 class="case-enter">빌딩 에너지 통합 관리 시스템 BEMS 개발</h1></div><span class="arrow-slot case-hero-arrow case-enter" aria-hidden="true"><svg class="arrow-icon arrow-icon--down-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></div>
|
||||
<dl class="case-meta case-enter"><div><dt>PROJECT PERIOD</dt><dd>2023.03.01 — 2024.02.28</dd></div><div><dt>OUR ROLE</dt><dd>사용자 웹 · 백엔드 개발 · 관리자 화면 개발</dd></div><div><dt>PLATFORM</dt><dd>웹</dd></div></dl>
|
||||
</section>
|
||||
<div class="case-cover case-enter"><div class="project-scene scene--bems" aria-hidden="true"><div class="scene-surface">
|
||||
<div class="scene-grid"></div><div class="scene-kicker"><span>BUILDING ENERGY MANAGEMENT</span><span>03 / 08</span></div>
|
||||
<div class="scene-wordmark">BEMS<span class="word-period">.</span></div><div class="scene-phrase">SENSORS / ENERGY / MONITORING</div>
|
||||
<div class="scene-orbit"><i></i><i></i><i></i><i></i></div>
|
||||
<div class="scene-visual"><img src="../assets/images/bems_01.png" alt="" loading="eager" width="1199" height="534"></div>
|
||||
<div class="scene-annotation">BUILDING ENERGY MANAGEMENT</div>
|
||||
<div class="scene-bottom"><span>웹</span><span class="scene-view">PROJECT SCREENS <b class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></b></span></div>
|
||||
</div></div></div>
|
||||
<nav class="case-nav" aria-label="프로젝트 상세 메뉴"><div class="wrap"><span>BEMS</span><div><a href="#overview" aria-current="location">프로젝트 개요</a><a href="#screens">개발 화면</a></div><span class="case-reading-progress" aria-hidden="true">00%</span></div></nav>
|
||||
<section id="overview" class="case-overview case-section wrap">
|
||||
<div class="case-section-label case-reveal"><span>01 / OVERVIEW</span><span>프로젝트 소개</span></div>
|
||||
<div class="case-overview-grid">
|
||||
<h2 class="case-reveal">건물의 센서와 전력을<br>하나의 관제 화면에서.</h2>
|
||||
<div class="case-reveal"><p>빌딩 내 온/습도 센서, 공조기 가동 센서 및 사용 전력 관리를 전력 관리 관제 프로그램 개발</p></div>
|
||||
</div>
|
||||
<div class="case-features" aria-label="서비스 대상, 주요 기능, 적용 기술">
|
||||
<article class="case-feature case-reveal"><span class="feature-index">01</span><h3>서비스 대상</h3><p>빌딩 내 온·습도 센서, 공조기 가동 센서와 사용 전력을 관리하는 관제 프로그램입니다.</p></article>
|
||||
<article class="case-feature case-reveal"><span class="feature-index">02</span><h3>주요 기능</h3><p>온·습도와 공조기 가동 정보를 확인하고, 건물의 사용 전력을 관리합니다.</p></article>
|
||||
<article class="case-feature case-reveal"><span class="feature-index">03</span><h3>적용 기술</h3><p>Vue.js로 사용자 웹을 개발하고, Spring Boot로 백엔드와 관리자 화면을 구현했습니다.</p></article>
|
||||
</div>
|
||||
</section>
|
||||
<section id="screens" class="case-screens case-section"><div class="wrap"><div class="case-section-label case-reveal"><span>02 / SCREENS IN FOCUS</span><span>실제 개발 화면</span></div><div class="case-screen-heading case-reveal"><h2>화면으로 만나는<br><span>프로젝트의 디테일.</span></h2><p>서비스의 기능과 흐름을<br>실제 개발 화면으로 살펴보세요.</p></div><div class="screen-tabs case-reveal" role="tablist" aria-label="BEMS 개발 화면"><button id="screen-tab-0" role="tab" aria-selected="true" aria-controls="screen-0" tabindex="0" data-screen="0"><span>01</span>표준 대시보드</button><button id="screen-tab-1" role="tab" aria-selected="false" aria-controls="screen-1" tabindex="-1" data-screen="1"><span>02</span>센서 정보</button><button id="screen-tab-2" role="tab" aria-selected="false" aria-controls="screen-2" tabindex="-1" data-screen="2"><span>03</span>EV 충전</button></div><div class="screen-gallery case-reveal"><figure class="case-screen is-current" id="screen-0" role="tabpanel" aria-labelledby="screen-tab-0" tabindex="0"><div class="screen-canvas"><img src="../assets/images/bems_01.png" alt="BEMS 표준 대시보드 화면입니다." loading="lazy" width="1199" height="534"></div><figcaption><span>01 / 03</span><p>BEMS 표준 대시보드 화면입니다.</p><a href="../assets/images/bems_01.png" target="_blank" rel="noopener" aria-label="표준 대시보드 전체 화면으로 보기">크게 보기 ⤢</a></figcaption></figure>
|
||||
<figure class="case-screen" id="screen-1" role="tabpanel" aria-labelledby="screen-tab-1" tabindex="0" hidden><div class="screen-canvas"><img src="../assets/images/bems_02.png" alt="BEMS 센서정보 화면입니다." loading="lazy" width="1200" height="590"></div><figcaption><span>02 / 03</span><p>BEMS 센서정보 화면입니다.</p><a href="../assets/images/bems_02.png" target="_blank" rel="noopener" aria-label="센서 정보 전체 화면으로 보기">크게 보기 ⤢</a></figcaption></figure>
|
||||
<figure class="case-screen" id="screen-2" role="tabpanel" aria-labelledby="screen-tab-2" tabindex="0" hidden><div class="screen-canvas"><img src="../assets/images/bems_03.png" alt="BEMS EV 충전 화면입니다." loading="lazy" width="1199" height="576"></div><figcaption><span>03 / 03</span><p>BEMS EV 충전 화면입니다.</p><a href="../assets/images/bems_03.png" target="_blank" rel="noopener" aria-label="EV 충전 전체 화면으로 보기">크게 보기 ⤢</a></figcaption></figure></div><p class="case-source-note">프로젝트 화면 자료입니다.</p></div></section>
|
||||
<section class="case-next"><a href="givon.html" class="wrap case-next-link"><div><span class="tiny-label">KEEP EXPLORING / NEXT PROJECT</span><h2>GivOn</h2><p>블록체인 기반 기부 플랫폼 관리자 페이지 개발</p></div><span class="arrow-slot case-next-arrow" aria-hidden="true"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a></section>
|
||||
<section class="case-connect wrap"><div><p class="tiny-label">LET'S BUILD WHAT'S NEXT.</p><h2>다음 프로젝트,<br>함께 만들어가요.</h2></div><a href="../index.html#contact">함께 프로젝트 만들기 <span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a></section>
|
||||
</main>
|
||||
<footer class="case-footer wrap"><a href="../index.html">주식회사 트론트</a><span>© 2026 TRONT. All rights reserved.</span><a href="#case-main">맨 위로 <svg class="arrow-icon arrow-icon--up" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></a></footer>
|
||||
</body>
|
||||
</html>
|
||||
52
projects/bispet.html
Normal file
@@ -0,0 +1,52 @@
|
||||
<!doctype html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="theme-color" content="#f5f5ef"><meta name="description" content="반려 동물의 정보 관리 및 반려동물과 생활 기록(피드 작성, 일정, 산책 활동) 기록한 SNS 앱 개발">
|
||||
<meta property="og:type" content="website"><meta property="og:title" content="BISPET — 애견인들 위한 커뮤니티 개발 | TRONT"><meta property="og:description" content="반려 동물의 정보 관리 및 반려동물과 생활 기록(피드 작성, 일정, 산책 활동) 기록한 SNS 앱 개발">
|
||||
<title>BISPET — 애견인들 위한 커뮤니티 개발 | TRONT</title>
|
||||
<link rel="icon" href="../assets/images/tront-logo.png"><link rel="stylesheet" href="../styles.css"><link rel="stylesheet" href="../portfolio.css"><script src="../portfolio.js" defer></script>
|
||||
<link rel="stylesheet" href="../image-viewer.css">
|
||||
<script src="../image-viewer.js" defer></script>
|
||||
<link rel="stylesheet" href="../icons.css">
|
||||
</head>
|
||||
<body class="portfolio-page case--bispet">
|
||||
<!-- Shared arrow shape from assets/icons/arrow_right_alt.svg. -->
|
||||
<svg class="icon-definitions" xmlns="http://www.w3.org/2000/svg" width="0" height="0" aria-hidden="true" focusable="false"><defs><symbol id="arrow-right-alt" viewBox="0 -960 960 960"><path d="m560-240-56-58 142-142H160v-80h486L504-662l56-58 240 240-240 240Z"/></symbol></defs></svg>
|
||||
<a class="skip-link" href="#case-main">본문으로 바로가기</a><div class="case-progress" aria-hidden="true"></div>
|
||||
<header class="case-header"><div class="wrap"><a class="case-back" href="../index.html#project-bispet" data-back-to-work><span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-left" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span> 포트폴리오 목록</a><a class="case-brand" href="../index.html" aria-label="트론트 홈">TRONT<span class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a><a class="case-header-contact" href="../index.html#contact">함께 만들어가요 <span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a></div></header>
|
||||
<main id="case-main">
|
||||
<section class="case-hero wrap">
|
||||
<div class="case-eyebrow case-enter"><span>TRONT / SELECTED WORK</span><span>PROJECT 05 — 08</span></div>
|
||||
<div class="case-title-row"><div><p class="case-project-brand case-enter">BISPET</p><h1 class="case-enter">애견인들 위한 커뮤니티 개발</h1></div><span class="arrow-slot case-hero-arrow case-enter" aria-hidden="true"><svg class="arrow-icon arrow-icon--down-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></div>
|
||||
<dl class="case-meta case-enter"><div><dt>PROJECT PERIOD</dt><dd>2024.07.01 — 2025.03.31</dd></div><div><dt>OUR ROLE</dt><dd>모바일 앱 · 백엔드 개발 · 관리자 화면 개발</dd></div><div><dt>PLATFORM</dt><dd>모바일 앱 / 관리자 화면</dd></div></dl>
|
||||
</section>
|
||||
<div class="case-cover case-enter"><div class="project-scene scene--bispet" aria-hidden="true"><div class="scene-surface">
|
||||
<div class="scene-grid"></div><div class="scene-kicker"><span>SNS / COMMUNITY</span><span>05 / 08</span></div>
|
||||
<div class="scene-wordmark">BIS<em>PET.</em></div><div class="scene-phrase">PROFILE / FEED / SCHEDULE / WALK</div>
|
||||
<div class="scene-orbit"><i></i><i></i><i></i><i></i></div>
|
||||
<div class="scene-visual"><img src="../assets/images/bispet_01.png" alt="" loading="eager" width="702" height="1440"></div>
|
||||
<div class="scene-annotation">MOBILE APP / ADMIN</div>
|
||||
<div class="scene-bottom"><span>모바일 앱 / 관리자 화면</span><span class="scene-view">PROJECT SCREENS <b class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></b></span></div>
|
||||
</div></div></div>
|
||||
<nav class="case-nav" aria-label="프로젝트 상세 메뉴"><div class="wrap"><span>BISPET</span><div><a href="#overview" aria-current="location">프로젝트 개요</a><a href="#screens">개발 화면</a></div><span class="case-reading-progress" aria-hidden="true">00%</span></div></nav>
|
||||
<section id="overview" class="case-overview case-section wrap">
|
||||
<div class="case-section-label case-reveal"><span>01 / OVERVIEW</span><span>프로젝트 소개</span></div>
|
||||
<div class="case-overview-grid">
|
||||
<h2 class="case-reveal">반려동물과의 일상을<br>기록하고 나누다.</h2>
|
||||
<div class="case-reveal"><p>반려 동물의 정보 관리 및 반려동물과 생활 기록(피드 작성, 일정, 산책 활동) 기록한 SNS 앱 개발</p></div>
|
||||
</div>
|
||||
<div class="case-features" aria-label="서비스 대상, 주요 기능, 적용 기술">
|
||||
<article class="case-feature case-reveal"><span class="feature-index">01</span><h3>서비스 대상</h3><p>반려동물과 생활하는 애견인을 위한 SNS 앱입니다.</p></article>
|
||||
<article class="case-feature case-reveal"><span class="feature-index">02</span><h3>주요 기능</h3><p>반려동물 정보를 관리하고, 피드 작성과 일정, 산책 활동을 통해 함께하는 생활을 기록합니다.</p></article>
|
||||
<article class="case-feature case-reveal"><span class="feature-index">03</span><h3>적용 기술</h3><p>Flutter로 모바일 앱을 개발하고, Spring Boot로 백엔드와 관리자 화면을 구현했습니다.</p></article>
|
||||
</div>
|
||||
</section>
|
||||
<section id="screens" class="case-screens case-section"><div class="wrap"><div class="case-section-label case-reveal"><span>02 / SCREENS IN FOCUS</span><span>실제 개발 화면</span></div><div class="case-screen-heading case-reveal"><h2>화면으로 만나는<br><span>프로젝트의 디테일.</span></h2><p>서비스의 기능과 흐름을<br>실제 개발 화면으로 살펴보세요.</p></div><div class="screen-tabs case-reveal" role="tablist" aria-label="BISPET 개발 화면"><button id="screen-tab-0" role="tab" aria-selected="true" aria-controls="screen-0" tabindex="0" data-screen="0"><span>01</span>앱 메인</button><button id="screen-tab-1" role="tab" aria-selected="false" aria-controls="screen-1" tabindex="-1" data-screen="1"><span>02</span>관리자 대시보드</button></div><div class="screen-gallery case-reveal"><figure class="case-screen is-current" id="screen-0" role="tabpanel" aria-labelledby="screen-tab-0" tabindex="0"><div class="screen-canvas screen-portrait"><img src="../assets/images/bispet_01.png" alt="BISPET 앱 메인 화면입니다." loading="lazy" width="702" height="1440"></div><figcaption><span>01 / 02</span><p>BISPET 앱 메인 화면입니다.</p><a href="../assets/images/bispet_01.png" target="_blank" rel="noopener" aria-label="앱 메인 전체 화면으로 보기">크게 보기 ⤢</a></figcaption></figure>
|
||||
<figure class="case-screen" id="screen-1" role="tabpanel" aria-labelledby="screen-tab-1" tabindex="0" hidden><div class="screen-canvas"><img src="../assets/images/bispet_02.png" alt="BISPET 관리자 대시보드 화면입니다." loading="lazy" width="1448" height="800"></div><figcaption><span>02 / 02</span><p>BISPET 관리자 대시보드 화면입니다.</p><a href="../assets/images/bispet_02.png" target="_blank" rel="noopener" aria-label="관리자 대시보드 전체 화면으로 보기">크게 보기 ⤢</a></figcaption></figure></div><p class="case-source-note">프로젝트 화면 자료입니다.</p></div></section>
|
||||
<section class="case-next"><a href="asset.html" class="wrap case-next-link"><div><span class="tiny-label">KEEP EXPLORING / NEXT PROJECT</span><h2>WideAsset S/W</h2><p>RF 자산관리 솔루션 구축</p></div><span class="arrow-slot case-next-arrow" aria-hidden="true"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a></section>
|
||||
<section class="case-connect wrap"><div><p class="tiny-label">LET'S BUILD WHAT'S NEXT.</p><h2>다음 프로젝트,<br>함께 만들어가요.</h2></div><a href="../index.html#contact">함께 프로젝트 만들기 <span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a></section>
|
||||
</main>
|
||||
<footer class="case-footer wrap"><a href="../index.html">주식회사 트론트</a><span>© 2026 TRONT. All rights reserved.</span><a href="#case-main">맨 위로 <svg class="arrow-icon arrow-icon--up" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></a></footer>
|
||||
</body>
|
||||
</html>
|
||||
53
projects/givon.html
Normal file
@@ -0,0 +1,53 @@
|
||||
<!doctype html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="theme-color" content="#f5f5ef"><meta name="description" content="기부를 위한 단체 관리, 기부/봉사 프로젝트 생성 및 기부 현황를 관리를 위한 관리자 화면 개발">
|
||||
<meta property="og:type" content="website"><meta property="og:title" content="GivOn — 블록체인 기반 기부 플랫폼 관리자 페이지 개발 | TRONT"><meta property="og:description" content="기부를 위한 단체 관리, 기부/봉사 프로젝트 생성 및 기부 현황를 관리를 위한 관리자 화면 개발">
|
||||
<title>GivOn — 블록체인 기반 기부 플랫폼 관리자 페이지 개발 | TRONT</title>
|
||||
<link rel="icon" href="../assets/images/tront-logo.png"><link rel="stylesheet" href="../styles.css"><link rel="stylesheet" href="../portfolio.css"><script src="../portfolio.js" defer></script>
|
||||
<link rel="stylesheet" href="../image-viewer.css">
|
||||
<script src="../image-viewer.js" defer></script>
|
||||
<link rel="stylesheet" href="../icons.css">
|
||||
</head>
|
||||
<body class="portfolio-page case--givon">
|
||||
<!-- Shared arrow shape from assets/icons/arrow_right_alt.svg. -->
|
||||
<svg class="icon-definitions" xmlns="http://www.w3.org/2000/svg" width="0" height="0" aria-hidden="true" focusable="false"><defs><symbol id="arrow-right-alt" viewBox="0 -960 960 960"><path d="m560-240-56-58 142-142H160v-80h486L504-662l56-58 240 240-240 240Z"/></symbol></defs></svg>
|
||||
<a class="skip-link" href="#case-main">본문으로 바로가기</a><div class="case-progress" aria-hidden="true"></div>
|
||||
<header class="case-header"><div class="wrap"><a class="case-back" href="../index.html#project-givon" data-back-to-work><span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-left" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span> 포트폴리오 목록</a><a class="case-brand" href="../index.html" aria-label="트론트 홈">TRONT<span class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a><a class="case-header-contact" href="../index.html#contact">함께 만들어가요 <span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a></div></header>
|
||||
<main id="case-main">
|
||||
<section class="case-hero wrap">
|
||||
<div class="case-eyebrow case-enter"><span>TRONT / SELECTED WORK</span><span>PROJECT 04 — 08</span></div>
|
||||
<div class="case-title-row"><div><p class="case-project-brand case-enter">GivOn</p><h1 class="case-enter">블록체인 기반 기부 플랫폼 관리자 페이지 개발</h1></div><span class="arrow-slot case-hero-arrow case-enter" aria-hidden="true"><svg class="arrow-icon arrow-icon--down-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></div>
|
||||
<dl class="case-meta case-enter"><div><dt>PROJECT PERIOD</dt><dd>2025.08.01 — 2025.10.31</dd></div><div><dt>OUR ROLE</dt><dd>백엔드 개발 · 관리자 화면 개발</dd></div><div><dt>PLATFORM</dt><dd>관리자 화면</dd></div></dl>
|
||||
</section>
|
||||
<div class="case-cover case-enter"><div class="project-scene scene--givon" aria-hidden="true"><div class="scene-surface">
|
||||
<div class="scene-grid"></div><div class="scene-kicker"><span>DONATION PLATFORM / ADMIN</span><span>04 / 08</span></div>
|
||||
<div class="scene-wordmark">Giv<em>On</em><span class="word-period">.</span></div><div class="scene-phrase">ORGANIZATIONS / DONATIONS / VOLUNTEERING</div>
|
||||
<div class="scene-orbit"><i></i><i></i><i></i><i></i></div>
|
||||
<div class="scene-visual"><img src="../assets/images/givon_01.png" alt="" loading="eager" width="1774" height="528"></div>
|
||||
<div class="scene-annotation">DONATION PLATFORM / ADMIN</div>
|
||||
<div class="scene-bottom"><span>관리자 화면</span><span class="scene-view">PROJECT SCREENS <b class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></b></span></div>
|
||||
</div></div></div>
|
||||
<nav class="case-nav" aria-label="프로젝트 상세 메뉴"><div class="wrap"><span>GivOn</span><div><a href="#overview" aria-current="location">프로젝트 개요</a><a href="#screens">개발 화면</a></div><span class="case-reading-progress" aria-hidden="true">00%</span></div></nav>
|
||||
<section id="overview" class="case-overview case-section wrap">
|
||||
<div class="case-section-label case-reveal"><span>01 / OVERVIEW</span><span>프로젝트 소개</span></div>
|
||||
<div class="case-overview-grid">
|
||||
<h2 class="case-reveal">기부와 봉사의 운영을<br>관리자 화면에 담다.</h2>
|
||||
<div class="case-reveal"><p>기부를 위한 단체 관리, 기부/봉사 프로젝트 생성 및 기부 현황를 관리를 위한 관리자 화면 개발</p></div>
|
||||
</div>
|
||||
<div class="case-features" aria-label="서비스 대상, 주요 기능, 적용 기술">
|
||||
<article class="case-feature case-reveal"><span class="feature-index">01</span><h3>서비스 대상</h3><p>블록체인 기반 기부 플랫폼 GivOn을 운영하기 위한 관리자 화면입니다.</p></article>
|
||||
<article class="case-feature case-reveal"><span class="feature-index">02</span><h3>주요 기능</h3><p>기부 단체를 관리하고, 기부·봉사 프로젝트를 생성하며 기부 현황을 확인합니다.</p></article>
|
||||
<article class="case-feature case-reveal"><span class="feature-index">03</span><h3>적용 기술</h3><p>Spring Boot로 백엔드를 개발하고, Vue.js로 관리자 화면을 구현했습니다.</p></article>
|
||||
</div>
|
||||
</section>
|
||||
<section id="screens" class="case-screens case-section"><div class="wrap"><div class="case-section-label case-reveal"><span>02 / SCREENS IN FOCUS</span><span>실제 개발 화면</span></div><div class="case-screen-heading case-reveal"><h2>화면으로 만나는<br><span>프로젝트의 디테일.</span></h2><p>서비스의 기능과 흐름을<br>실제 개발 화면으로 살펴보세요.</p></div><div class="screen-tabs case-reveal" role="tablist" aria-label="GivOn 개발 화면"><button id="screen-tab-0" role="tab" aria-selected="true" aria-controls="screen-0" tabindex="0" data-screen="0"><span>01</span>기부 프로젝트</button><button id="screen-tab-1" role="tab" aria-selected="false" aria-controls="screen-1" tabindex="-1" data-screen="1"><span>02</span>단체 관리</button><button id="screen-tab-2" role="tab" aria-selected="false" aria-controls="screen-2" tabindex="-1" data-screen="2"><span>03</span>봉사 프로젝트</button></div><div class="screen-gallery case-reveal"><figure class="case-screen is-current" id="screen-0" role="tabpanel" aria-labelledby="screen-tab-0" tabindex="0"><div class="screen-canvas"><img src="../assets/images/givon_01.png" alt="기부 프로젝트 상세 화면입니다." loading="lazy" width="1774" height="528"></div><figcaption><span>01 / 03</span><p>기부 프로젝트 상세 화면입니다.</p><a href="../assets/images/givon_01.png" target="_blank" rel="noopener" aria-label="기부 프로젝트 전체 화면으로 보기">크게 보기 ⤢</a></figcaption></figure>
|
||||
<figure class="case-screen" id="screen-1" role="tabpanel" aria-labelledby="screen-tab-1" tabindex="0" hidden><div class="screen-canvas"><img src="../assets/images/givon_02.png" alt="기부 단체 관리 화면입니다." loading="lazy" width="1774" height="528"></div><figcaption><span>02 / 03</span><p>기부 단체 관리 화면입니다.</p><a href="../assets/images/givon_02.png" target="_blank" rel="noopener" aria-label="단체 관리 전체 화면으로 보기">크게 보기 ⤢</a></figcaption></figure>
|
||||
<figure class="case-screen" id="screen-2" role="tabpanel" aria-labelledby="screen-tab-2" tabindex="0" hidden><div class="screen-canvas"><img src="../assets/images/givon_03.png" alt="봉사 프로젝트 상세 화면입니다." loading="lazy" width="1774" height="528"></div><figcaption><span>03 / 03</span><p>봉사 프로젝트 상세 화면입니다.</p><a href="../assets/images/givon_03.png" target="_blank" rel="noopener" aria-label="봉사 프로젝트 전체 화면으로 보기">크게 보기 ⤢</a></figcaption></figure></div><p class="case-source-note">프로젝트 화면 자료입니다.</p></div></section>
|
||||
<section class="case-next"><a href="bispet.html" class="wrap case-next-link"><div><span class="tiny-label">KEEP EXPLORING / NEXT PROJECT</span><h2>BISPET</h2><p>애견인들 위한 커뮤니티 개발</p></div><span class="arrow-slot case-next-arrow" aria-hidden="true"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a></section>
|
||||
<section class="case-connect wrap"><div><p class="tiny-label">LET'S BUILD WHAT'S NEXT.</p><h2>다음 프로젝트,<br>함께 만들어가요.</h2></div><a href="../index.html#contact">함께 프로젝트 만들기 <span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a></section>
|
||||
</main>
|
||||
<footer class="case-footer wrap"><a href="../index.html">주식회사 트론트</a><span>© 2026 TRONT. All rights reserved.</span><a href="#case-main">맨 위로 <svg class="arrow-icon arrow-icon--up" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></a></footer>
|
||||
</body>
|
||||
</html>
|
||||
58
projects/gps-building.html
Normal file
@@ -0,0 +1,58 @@
|
||||
<!doctype html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="theme-color" content="#f5f5ef"><meta name="description" content="GPS 트래킹 방식을 활용, 옥외 위치 추적기를 개발하고 사용자의 현재 위치 확인, 목적지 설정, 이동 방향 추적등 기능을 수행할 수 있도록 시스템 구축">
|
||||
<meta property="og:type" content="website"><meta property="og:title" content="GPS 솔루션 — GPS 트래킹 기반 위치확인 시스템 개발 | TRONT"><meta property="og:description" content="GPS 트래킹 방식을 활용, 옥외 위치 추적기를 개발하고 사용자의 현재 위치 확인, 목적지 설정, 이동 방향 추적등 기능을 수행할 수 있도록 시스템 구축">
|
||||
<title>GPS 솔루션 — GPS 트래킹 기반 위치확인 시스템 개발 | TRONT</title>
|
||||
<link rel="icon" href="../assets/images/tront-logo.png"><link rel="stylesheet" href="../styles.css"><link rel="stylesheet" href="../portfolio.css"><script src="../portfolio.js" defer></script>
|
||||
<link rel="stylesheet" href="../image-viewer.css">
|
||||
<script src="../image-viewer.js" defer></script>
|
||||
<link rel="stylesheet" href="../icons.css">
|
||||
</head>
|
||||
<body class="portfolio-page case--gps-building">
|
||||
<!-- Shared arrow shape from assets/icons/arrow_right_alt.svg. -->
|
||||
<svg class="icon-definitions" xmlns="http://www.w3.org/2000/svg" width="0" height="0" aria-hidden="true" focusable="false"><defs><symbol id="arrow-right-alt" viewBox="0 -960 960 960"><path d="m560-240-56-58 142-142H160v-80h486L504-662l56-58 240 240-240 240Z"/></symbol></defs></svg>
|
||||
<a class="skip-link" href="#case-main">본문으로 바로가기</a><div class="case-progress" aria-hidden="true"></div>
|
||||
<header class="case-header"><div class="wrap"><a class="case-back" href="../index.html#project-gps-building" data-back-to-work><span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-left" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span> 포트폴리오 목록</a><a class="case-brand" href="../index.html" aria-label="트론트 홈">TRONT<span class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a><a class="case-header-contact" href="../index.html#contact">함께 만들어가요 <span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a></div></header>
|
||||
<main id="case-main">
|
||||
<section class="case-hero wrap">
|
||||
<div class="case-eyebrow case-enter"><span>TRONT / SELECTED WORK</span><span>PROJECT 08 — 08</span></div>
|
||||
<div class="case-title-row"><div><p class="case-project-brand case-enter">GPS 솔루션</p><h1 class="case-enter">GPS 트래킹 기반 위치확인 시스템 개발</h1></div><span class="arrow-slot case-hero-arrow case-enter" aria-hidden="true"><svg class="arrow-icon arrow-icon--down-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></div>
|
||||
<dl class="case-meta case-enter"><div><dt>PROJECT PERIOD</dt><dd>2010.03 — 2010.11</dd></div><div><dt>OUR ROLE</dt><dd>GPS Manager · Location Manager · Navigation Manager 기반 디바이스 드라이버 개발</dd></div><div><dt>PLATFORM</dt><dd>옥외 위치 추적기 / 사용자 애플리케이션</dd></div></dl>
|
||||
</section>
|
||||
<div class="case-cover case-enter"><div class="project-scene scene--gps-building" aria-hidden="true"><div class="scene-surface">
|
||||
<div class="scene-grid"></div><div class="scene-kicker"><span>GPS / LOCATION TRACKING</span><span>08 / 08</span></div>
|
||||
<div class="scene-wordmark">GPS<em>Tracking.</em></div><div class="scene-phrase">LOCATE / SAVE / NAVIGATE</div>
|
||||
<div class="scene-graphic"><svg viewBox="0 0 400 360" fill="none" aria-hidden="true" focusable="false">
|
||||
<path class="gps-contour" d="M30 355C-25 291 16 235 84 235S186 278 234 226S241 128 314 111S384 37 364 0 M3 331C-14 281 33 254 79 257S188 307 252 242S259 147 326 131S412 53 386 0 M56 360C0 300 32 277 79 282S200 330 272 262S278 166 340 152S425 91 402 41"/>
|
||||
<circle class="gps-contour" cx="284" cy="109" r="49"/><circle class="gps-contour" cx="284" cy="109" r="71"/><circle class="gps-contour" cx="284" cy="109" r="93"/>
|
||||
<path class="gps-route" d="M73 281H151Q169 281 169 263V219Q169 202 189 202H263Q284 202 284 182V117"/>
|
||||
<circle class="gps-point" cx="73" cy="281" r="7"/><circle class="gps-point" cx="169" cy="227" r="5"/>
|
||||
<path class="gps-pin" d="M284 117S263 96 263 81A21 21 0 0 1 305 81C305 96 284 117 284 117Z"/>
|
||||
<circle class="gps-pin-center" cx="284" cy="81" r="7"/>
|
||||
<path class="gps-contour" d="M333 279H357M345 267V291M79 137H95M87 129V145"/>
|
||||
</svg></div>
|
||||
<div class="scene-annotation">POSITION / DESTINATION / DIRECTION</div>
|
||||
<div class="scene-bottom"><span>GPS / OUTDOOR TRACKER</span><span class="scene-view">PROJECT SCREENS <b class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></b></span></div>
|
||||
</div></div></div>
|
||||
<nav class="case-nav" aria-label="프로젝트 상세 메뉴"><div class="wrap"><span>GPS 솔루션</span><div><a href="#overview" aria-current="location">프로젝트 개요</a><a href="#screens">시스템 구성</a></div><span class="case-reading-progress" aria-hidden="true">00%</span></div></nav>
|
||||
<section id="overview" class="case-overview case-section wrap">
|
||||
<div class="case-section-label case-reveal"><span>01 / OVERVIEW</span><span>프로젝트 소개</span></div>
|
||||
<div class="case-overview-grid">
|
||||
<h2 class="case-reveal">현재 위치에서<br>다음 목적지까지.</h2>
|
||||
<div class="case-reveal"><p>GPS 트래킹 방식을 활용, 옥외 위치 추적기를 개발하고 사용자의 현재 위치 확인, 목적지 설정, 이동 방향 추적등 기능을 수행할 수 있도록 시스템 구축</p></div>
|
||||
</div>
|
||||
<div class="case-features" aria-label="서비스 대상, 주요 기능, 적용 기술">
|
||||
<article class="case-feature case-reveal"><span class="feature-index">01</span><h3>서비스 대상</h3><p>야외 활동, 주차, 여행 등 옥외 환경에서 현재 위치와 목적지를 확인하는 사용자를 위한 위치 추적 시스템입니다.</p></article>
|
||||
<article class="case-feature case-reveal"><span class="feature-index">02</span><h3>주요 기능</h3><p>현재 위치를 수신·저장하고 목적지 정보를 설정합니다. 저장한 위치를 바탕으로 이동 방향을 추적하고 표시합니다.</p></article>
|
||||
<article class="case-feature case-reveal"><span class="feature-index">03</span><h3>적용 기술</h3><p>GPS 기본 칩셋과 MCU를 활용하고, GPS Manager·Location Manager·Navigation Manager 기반 디바이스 드라이버로 하드웨어와 사용자 애플리케이션을 연동했습니다.</p></article>
|
||||
</div>
|
||||
</section>
|
||||
<section id="screens" class="case-screens case-section"><div class="wrap"><div class="case-section-label case-reveal"><span>02 / SCREENS IN FOCUS</span><span>시스템 구성</span></div><div class="case-screen-heading case-reveal"><h2>화면으로 만나는<br><span>프로젝트의 디테일.</span></h2><p>위치 수신부터 이동 방향 안내까지,<br>시스템의 동작 구조를 살펴보세요.</p></div><div class="screen-tabs case-reveal" role="tablist" aria-label="GPS 솔루션 시스템 구성"><button id="screen-tab-0" role="tab" aria-selected="true" aria-controls="screen-0" tabindex="0" data-screen="0"><span>01</span>시스템 구성</button></div><div class="screen-gallery case-reveal"><figure class="case-screen is-current" id="screen-0" role="tabpanel" aria-labelledby="screen-tab-0" tabindex="0"><div class="screen-canvas"><img src="../assets/images/gps-building_01.png" alt="GPS 인공위성과 옥외 위치 추적기를 연결해 현재 위치를 수신·저장하고, 목적지 정보 및 이동 방향을 표시하는 시스템 구성도입니다." loading="lazy" width="3499" height="1875"></div><figcaption><span>01 / 01</span><p>GPS 인공위성과 옥외 위치 추적기를 연결해 현재 위치를 수신·저장하고, 목적지 정보 및 이동 방향을 표시하는 시스템 구성도입니다.</p><a href="../assets/images/gps-building_01.png" target="_blank" rel="noopener" aria-label="시스템 구성 전체 화면으로 보기">크게 보기 ⤢</a></figcaption></figure></div><p class="case-source-note">프로젝트 화면 자료입니다.</p></div></section>
|
||||
<section class="case-next"><a href="mobility.html" class="wrap case-next-link"><div><span class="tiny-label">KEEP EXPLORING / NEXT PROJECT</span><h2>현대 오토에버</h2><p>국내향 및 해외향 내비게이션 HMI 개발</p></div><span class="arrow-slot case-next-arrow" aria-hidden="true"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a></section>
|
||||
<section class="case-connect wrap"><div><p class="tiny-label">LET'S BUILD WHAT'S NEXT.</p><h2>다음 프로젝트,<br>함께 만들어가요.</h2></div><a href="../index.html#contact">함께 프로젝트 만들기 <span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a></section>
|
||||
</main>
|
||||
<footer class="case-footer wrap"><a href="../index.html">주식회사 트론트</a><span>© 2026 TRONT. All rights reserved.</span><a href="#case-main">맨 위로 <svg class="arrow-icon arrow-icon--up" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></a></footer>
|
||||
</body>
|
||||
</html>
|
||||
81
projects/mobility.html
Normal file
@@ -0,0 +1,81 @@
|
||||
<!doctype html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="theme-color" content="#f5f5ef"><meta name="description" content="현대, 기아 자동차 국내향 및 해외향(북미, 유럽, 남미, 중동, 호주) HMI 외주 개발 및 Engine App 개발">
|
||||
<meta property="og:type" content="website"><meta property="og:title" content="현대 오토에버 — 국내향 및 해외향 내비게이션 HMI 개발 | TRONT"><meta property="og:description" content="현대, 기아 자동차 국내향 및 해외향(북미, 유럽, 남미, 중동, 호주) HMI 외주 개발 및 Engine App 개발">
|
||||
<title>현대 오토에버 — 국내향 및 해외향 내비게이션 HMI 개발 | TRONT</title>
|
||||
<link rel="icon" href="../assets/images/tront-logo.png"><link rel="stylesheet" href="../styles.css"><link rel="stylesheet" href="../portfolio.css"><script src="../portfolio.js" defer></script>
|
||||
<link rel="stylesheet" href="../image-viewer.css">
|
||||
<script src="../image-viewer.js" defer></script>
|
||||
<link rel="stylesheet" href="../icons.css">
|
||||
</head>
|
||||
<body class="portfolio-page case--mobility">
|
||||
<!-- Shared arrow shape from assets/icons/arrow_right_alt.svg. -->
|
||||
<svg class="icon-definitions" xmlns="http://www.w3.org/2000/svg" width="0" height="0" aria-hidden="true" focusable="false"><defs><symbol id="arrow-right-alt" viewBox="0 -960 960 960"><path d="m560-240-56-58 142-142H160v-80h486L504-662l56-58 240 240-240 240Z"/></symbol></defs></svg>
|
||||
<a class="skip-link" href="#case-main">본문으로 바로가기</a><div class="case-progress" aria-hidden="true"></div>
|
||||
<header class="case-header"><div class="wrap"><a class="case-back" href="../index.html#project-mobility" data-back-to-work><span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-left" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span> 포트폴리오 목록</a><a class="case-brand" href="../index.html" aria-label="트론트 홈">TRONT<span class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a><a class="case-header-contact" href="../index.html#contact">함께 만들어가요 <span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a></div></header>
|
||||
<main id="case-main">
|
||||
<section class="case-hero wrap">
|
||||
<div class="case-eyebrow case-enter"><span>TRONT / SELECTED WORK</span><span>PROJECT 01 — 08</span></div>
|
||||
<div class="case-title-row"><div><p class="case-project-brand case-enter">현대 오토에버</p><h1 class="case-enter">국내향 및 해외향 내비게이션 HMI 개발</h1></div><span class="arrow-slot case-hero-arrow case-enter" aria-hidden="true"><svg class="arrow-icon arrow-icon--down-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></div>
|
||||
<dl class="case-meta case-enter"><div><dt>PROJECT PERIOD</dt><dd>2018.05.01 — 2025.03.31</dd></div><div><dt>OUR ROLE</dt><dd>내비게이션 GUI 개발 · Interface layer 개발</dd></div><div><dt>PLATFORM</dt><dd>차량용 내비게이션</dd></div></dl>
|
||||
</section>
|
||||
<div class="case-cover case-enter"><div class="project-scene scene--mobility" aria-hidden="true"><div class="scene-surface">
|
||||
<div class="scene-grid"></div><div class="scene-kicker"><span>NAVIGATION / HMI</span><span>01 / 08</span></div>
|
||||
<div class="scene-wordmark">Navigation<br><em>HMI.</em></div><div class="scene-phrase">NAVIGATION GUI / INTERFACE LAYER</div>
|
||||
<div class="scene-orbit"><i></i><i></i><i></i><i></i></div>
|
||||
<div class="scene-visual"><img src="../assets/images/mobility_01.png" alt="" loading="eager" width="1577" height="727"></div>
|
||||
<div class="scene-annotation">HYUNDAI / KIA</div>
|
||||
<div class="scene-bottom"><span>차량용 내비게이션</span><span class="scene-view">PROJECT SCREENS <b class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></b></span></div>
|
||||
</div></div></div>
|
||||
<nav class="case-nav" aria-label="프로젝트 상세 메뉴"><div class="wrap"><span>현대 오토에버</span><div><a href="#overview" aria-current="location">프로젝트 개요</a><a href="#screens">개발 화면</a></div><span class="case-reading-progress" aria-hidden="true">00%</span></div></nav>
|
||||
<section id="overview" class="case-overview case-section wrap">
|
||||
<div class="case-section-label case-reveal"><span>01 / OVERVIEW</span><span>프로젝트 소개</span></div>
|
||||
<div class="case-overview-grid">
|
||||
<h2 class="case-reveal">운전자와 차량을 잇는<br>내비게이션 인터페이스.</h2>
|
||||
<div class="case-reveal"><p>현대, 기아 자동차 국내향 및 해외향(북미, 유럽, 남미, 중동, 호주) HMI 외주 개발 및 Engine App 개발</p></div>
|
||||
</div>
|
||||
<div class="case-features" aria-label="서비스 대상, 주요 기능, 적용 기술">
|
||||
<article class="case-feature case-reveal"><span class="feature-index">01</span><h3>서비스 대상</h3><p>현대·기아 자동차의 국내향과 북미·유럽·남미·중동·호주향 차량용 내비게이션을 대상으로 한 프로젝트입니다.</p></article>
|
||||
<article class="case-feature case-reveal"><span class="feature-index">02</span><h3>주요 기능</h3><p>내비게이션 GUI와 Interface layer, Engine App을 개발했습니다.</p></article>
|
||||
<article class="case-feature case-reveal"><span class="feature-index">03</span><h3>적용 기술</h3><p>C++와 Qt로 내비게이션 GUI를 개발하고, C++로 Interface layer를 구현했습니다.</p></article>
|
||||
</div>
|
||||
</section>
|
||||
<section id="screens" class="case-screens case-section">
|
||||
<div class="wrap">
|
||||
<div class="case-section-label case-reveal"><span>02 / SCREENS IN FOCUS</span><span>실제 개발 화면</span></div>
|
||||
<div class="case-screen-heading case-reveal"><h2>화면으로 만나는<br><span>프로젝트의 디테일.</span></h2><p>서비스의 기능과 흐름을<br>실제 개발 화면으로 살펴보세요.</p></div>
|
||||
<div class="screen-tabs case-reveal" role="tablist" aria-label="현대 오토에버 개발 화면">
|
||||
<button id="screen-tab-0" role="tab" aria-selected="true" aria-controls="screen-0" tabindex="0" data-screen="0"><span>01</span>차량 내 HMI</button>
|
||||
<button id="screen-tab-1" role="tab" aria-selected="false" aria-controls="screen-1" tabindex="-1" data-screen="1"><span>02</span>내비게이션 설정</button>
|
||||
<button id="screen-tab-2" role="tab" aria-selected="false" aria-controls="screen-2" tabindex="-1" data-screen="2"><span>03</span>지도 인터페이스</button>
|
||||
<button id="screen-tab-3" role="tab" aria-selected="false" aria-controls="screen-3" tabindex="-1" data-screen="3"><span>04</span>경치 좋은 길</button>
|
||||
</div>
|
||||
<div class="screen-gallery case-reveal">
|
||||
<figure class="case-screen is-current" id="screen-0" role="tabpanel" aria-labelledby="screen-tab-0" tabindex="0">
|
||||
<div class="screen-canvas"><img src="../assets/images/mobility_01.png" alt="차량에 적용된 내비게이션 HMI 화면입니다." loading="lazy" width="1577" height="727"></div>
|
||||
<figcaption><span>01 / 04</span><p>차량에 적용된 내비게이션 HMI 화면입니다.</p><a href="../assets/images/mobility_01.png" target="_blank" rel="noopener" aria-label="차량 내 HMI 전체 화면으로 보기">크게 보기 ⤢</a></figcaption>
|
||||
</figure>
|
||||
<figure class="case-screen" id="screen-1" role="tabpanel" aria-labelledby="screen-tab-1" tabindex="0" hidden>
|
||||
<div class="screen-canvas"><img src="../assets/images/mobility_02.png" alt="내비게이션 설정 화면입니다." loading="lazy" width="1332" height="499"></div>
|
||||
<figcaption><span>02 / 04</span><p>내비게이션 설정 화면입니다.</p><a href="../assets/images/mobility_02.png" target="_blank" rel="noopener" aria-label="내비게이션 설정 전체 화면으로 보기">크게 보기 ⤢</a></figcaption>
|
||||
</figure>
|
||||
<figure class="case-screen" id="screen-2" role="tabpanel" aria-labelledby="screen-tab-2" tabindex="0" hidden>
|
||||
<div class="screen-canvas"><img src="../assets/images/mobility_03.png" alt="차량 내비게이션의 지도 화면입니다." loading="lazy" width="1399" height="633"></div>
|
||||
<figcaption><span>03 / 04</span><p>차량 내비게이션의 지도 화면입니다.</p><a href="../assets/images/mobility_03.png" target="_blank" rel="noopener" aria-label="지도 인터페이스 전체 화면으로 보기">크게 보기 ⤢</a></figcaption>
|
||||
</figure>
|
||||
<figure class="case-screen" id="screen-3" role="tabpanel" aria-labelledby="screen-tab-3" tabindex="0" hidden>
|
||||
<div class="screen-canvas"><img src="../assets/images/mobility_04.png" alt="경치 좋은 길 탐색 화면입니다." loading="lazy" width="1332" height="499"></div>
|
||||
<figcaption><span>04 / 04</span><p>경치 좋은 길 탐색 화면입니다.</p><a href="../assets/images/mobility_04.png" target="_blank" rel="noopener" aria-label="경치 좋은 길 전체 화면으로 보기">크게 보기 ⤢</a></figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
<p class="case-source-note">프로젝트 화면 자료입니다.</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="case-next"><a href="pharmacy.html" class="wrap case-next-link"><div><span class="tiny-label">KEEP EXPLORING / NEXT PROJECT</span><h2>모두의약국</h2><p>약사님들의 커뮤니티 개발</p></div><span class="arrow-slot case-next-arrow" aria-hidden="true"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a></section>
|
||||
<section class="case-connect wrap"><div><p class="tiny-label">LET'S BUILD WHAT'S NEXT.</p><h2>다음 프로젝트,<br>함께 만들어가요.</h2></div><a href="../index.html#contact">함께 프로젝트 만들기 <span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a></section>
|
||||
</main>
|
||||
<footer class="case-footer wrap"><a href="../index.html">주식회사 트론트</a><span>© 2026 TRONT. All rights reserved.</span><a href="#case-main">맨 위로 <svg class="arrow-icon arrow-icon--up" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></a></footer>
|
||||
</body>
|
||||
</html>
|
||||
53
projects/pharmacy.html
Normal file
@@ -0,0 +1,53 @@
|
||||
<!doctype html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="theme-color" content="#f5f5ef"><meta name="description" content="약사님들을 위한 구인/구직, 약국 매물, 약 정보, 커뮤니티를 제공하는 모바일 앱, 웹, 관리자 개발">
|
||||
<meta property="og:type" content="website"><meta property="og:title" content="모두의약국 — 약사님들의 커뮤니티 개발 | TRONT"><meta property="og:description" content="약사님들을 위한 구인/구직, 약국 매물, 약 정보, 커뮤니티를 제공하는 모바일 앱, 웹, 관리자 개발">
|
||||
<title>모두의약국 — 약사님들의 커뮤니티 개발 | TRONT</title>
|
||||
<link rel="icon" href="../assets/images/tront-logo.png"><link rel="stylesheet" href="../styles.css"><link rel="stylesheet" href="../portfolio.css"><script src="../portfolio.js" defer></script>
|
||||
<link rel="stylesheet" href="../image-viewer.css">
|
||||
<script src="../image-viewer.js" defer></script>
|
||||
<link rel="stylesheet" href="../icons.css">
|
||||
</head>
|
||||
<body class="portfolio-page case--pharmacy">
|
||||
<!-- Shared arrow shape from assets/icons/arrow_right_alt.svg. -->
|
||||
<svg class="icon-definitions" xmlns="http://www.w3.org/2000/svg" width="0" height="0" aria-hidden="true" focusable="false"><defs><symbol id="arrow-right-alt" viewBox="0 -960 960 960"><path d="m560-240-56-58 142-142H160v-80h486L504-662l56-58 240 240-240 240Z"/></symbol></defs></svg>
|
||||
<a class="skip-link" href="#case-main">본문으로 바로가기</a><div class="case-progress" aria-hidden="true"></div>
|
||||
<header class="case-header"><div class="wrap"><a class="case-back" href="../index.html#project-pharmacy" data-back-to-work><span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-left" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span> 포트폴리오 목록</a><a class="case-brand" href="../index.html" aria-label="트론트 홈">TRONT<span class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a><a class="case-header-contact" href="../index.html#contact">함께 만들어가요 <span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a></div></header>
|
||||
<main id="case-main">
|
||||
<section class="case-hero wrap">
|
||||
<div class="case-eyebrow case-enter"><span>TRONT / SELECTED WORK</span><span>PROJECT 02 — 08</span></div>
|
||||
<div class="case-title-row"><div><p class="case-project-brand case-enter">모두의약국</p><h1 class="case-enter">약사님들의 커뮤니티 개발</h1></div><span class="arrow-slot case-hero-arrow case-enter" aria-hidden="true"><svg class="arrow-icon arrow-icon--down-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></div>
|
||||
<dl class="case-meta case-enter"><div><dt>PROJECT PERIOD</dt><dd>2022.09.01 — 2025.03.31</dd></div><div><dt>OUR ROLE</dt><dd>모바일 앱 · 사용자 웹 · 백엔드 개발 · 관리자 화면 개발</dd></div><div><dt>PLATFORM</dt><dd>모바일 앱 / 웹</dd></div></dl>
|
||||
</section>
|
||||
<div class="case-cover case-enter"><div class="project-scene scene--pharmacy" aria-hidden="true"><div class="scene-surface">
|
||||
<div class="scene-grid"></div><div class="scene-kicker"><span>COMMUNITY PLATFORM</span><span>02 / 08</span></div>
|
||||
<div class="scene-wordmark">모두의<br><em>약국.</em></div><div class="scene-phrase">A COMMUNITY FOR PHARMACISTS.</div>
|
||||
<div class="scene-orbit"><i></i><i></i><i></i><i></i></div>
|
||||
<div class="scene-visual"><img src="../assets/images/pharmacy_01.png" alt="" loading="eager" width="702" height="1440"></div>
|
||||
<div class="scene-annotation">APP / WEB / ADMIN</div>
|
||||
<div class="scene-bottom"><span>모바일 앱 / 웹</span><span class="scene-view">PROJECT SCREENS <b class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></b></span></div>
|
||||
</div></div></div>
|
||||
<nav class="case-nav" aria-label="프로젝트 상세 메뉴"><div class="wrap"><span>모두의약국</span><div><a href="#overview" aria-current="location">프로젝트 개요</a><a href="#screens">개발 화면</a></div><span class="case-reading-progress" aria-hidden="true">00%</span></div></nav>
|
||||
<section id="overview" class="case-overview case-section wrap">
|
||||
<div class="case-section-label case-reveal"><span>01 / OVERVIEW</span><span>프로젝트 소개</span></div>
|
||||
<div class="case-overview-grid">
|
||||
<h2 class="case-reveal">약사의 일에 필요한 정보를<br>하나의 커뮤니티로.</h2>
|
||||
<div class="case-reveal"><p>약사님들을 위한 구인/구직, 약국 매물, 약 정보, 커뮤니티를 제공하는 모바일 앱, 웹, 관리자 개발</p></div>
|
||||
</div>
|
||||
<div class="case-features" aria-label="서비스 대상, 주요 기능, 적용 기술">
|
||||
<article class="case-feature case-reveal"><span class="feature-index">01</span><h3>서비스 대상</h3><p>약사를 위한 정보와 커뮤니티를 모바일 앱과 웹으로 제공하는 서비스입니다.</p></article>
|
||||
<article class="case-feature case-reveal"><span class="feature-index">02</span><h3>주요 기능</h3><p>구인·구직, 약국 매물, 약 정보와 커뮤니티 기능을 제공합니다.</p></article>
|
||||
<article class="case-feature case-reveal"><span class="feature-index">03</span><h3>적용 기술</h3><p>Flutter로 모바일 앱을, Vue.js로 사용자 웹을 개발하고, Spring Boot로 백엔드와 관리자 화면을 구현했습니다.</p></article>
|
||||
</div>
|
||||
</section>
|
||||
<section id="screens" class="case-screens case-section"><div class="wrap"><div class="case-section-label case-reveal"><span>02 / SCREENS IN FOCUS</span><span>실제 개발 화면</span></div><div class="case-screen-heading case-reveal"><h2>화면으로 만나는<br><span>프로젝트의 디테일.</span></h2><p>서비스의 기능과 흐름을<br>실제 개발 화면으로 살펴보세요.</p></div><div class="screen-tabs case-reveal" role="tablist" aria-label="모두의약국 개발 화면"><button id="screen-tab-0" role="tab" aria-selected="true" aria-controls="screen-0" tabindex="0" data-screen="0"><span>01</span>앱 메인</button><button id="screen-tab-1" role="tab" aria-selected="false" aria-controls="screen-1" tabindex="-1" data-screen="1"><span>02</span>웹 메인</button><button id="screen-tab-2" role="tab" aria-selected="false" aria-controls="screen-2" tabindex="-1" data-screen="2"><span>03</span>관리자 대시보드</button></div><div class="screen-gallery case-reveal"><figure class="case-screen is-current" id="screen-0" role="tabpanel" aria-labelledby="screen-tab-0" tabindex="0"><div class="screen-canvas screen-portrait"><img src="../assets/images/pharmacy_01.png" alt="모두의약국 앱 메인 화면입니다." loading="lazy" width="702" height="1440"></div><figcaption><span>01 / 03</span><p>모두의약국 앱 메인 화면입니다.</p><a href="../assets/images/pharmacy_01.png" target="_blank" rel="noopener" aria-label="앱 메인 전체 화면으로 보기">크게 보기 ⤢</a></figcaption></figure>
|
||||
<figure class="case-screen" id="screen-1" role="tabpanel" aria-labelledby="screen-tab-1" tabindex="0" hidden><div class="screen-canvas"><img src="../assets/images/pharmacy_02.png" alt="모두의약국 웹 메인 화면입니다." loading="lazy" width="1200" height="538"></div><figcaption><span>02 / 03</span><p>모두의약국 웹 메인 화면입니다.</p><a href="../assets/images/pharmacy_02.png" target="_blank" rel="noopener" aria-label="웹 메인 전체 화면으로 보기">크게 보기 ⤢</a></figcaption></figure>
|
||||
<figure class="case-screen" id="screen-2" role="tabpanel" aria-labelledby="screen-tab-2" tabindex="0" hidden><div class="screen-canvas"><img src="../assets/images/pharmacy_03.png" alt="모두의약국 관리자 대시보드 화면입니다." loading="lazy" width="1399" height="602"></div><figcaption><span>03 / 03</span><p>모두의약국 관리자 대시보드 화면입니다.</p><a href="../assets/images/pharmacy_03.png" target="_blank" rel="noopener" aria-label="관리자 대시보드 전체 화면으로 보기">크게 보기 ⤢</a></figcaption></figure></div><p class="case-source-note">프로젝트 화면 자료입니다.</p></div></section>
|
||||
<section class="case-next"><a href="bems.html" class="wrap case-next-link"><div><span class="tiny-label">KEEP EXPLORING / NEXT PROJECT</span><h2>BEMS</h2><p>빌딩 에너지 통합 관리 시스템 BEMS 개발</p></div><span class="arrow-slot case-next-arrow" aria-hidden="true"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a></section>
|
||||
<section class="case-connect wrap"><div><p class="tiny-label">LET'S BUILD WHAT'S NEXT.</p><h2>다음 프로젝트,<br>함께 만들어가요.</h2></div><a href="../index.html#contact">함께 프로젝트 만들기 <span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a></section>
|
||||
</main>
|
||||
<footer class="case-footer wrap"><a href="../index.html">주식회사 트론트</a><span>© 2026 TRONT. All rights reserved.</span><a href="#case-main">맨 위로 <svg class="arrow-icon arrow-icon--up" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></a></footer>
|
||||
</body>
|
||||
</html>
|
||||
65
projects/signage.html
Normal file
@@ -0,0 +1,65 @@
|
||||
<!doctype html>
|
||||
<html lang="ko">
|
||||
<head>
|
||||
<meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta name="theme-color" content="#f5f5ef"><meta name="description" content="빌딩(건물) 내 멀티비전 및 DID(Digital Information Display) TV 스크린을 활용, 영상 홍보 및 통합 안내 콘텐츠를 중앙에서 관리/분산 송출 하는 시스템 구축">
|
||||
<meta property="og:type" content="website"><meta property="og:title" content="Signage 솔루션 — 빌딩(건물) 영상홍보 통합안내 시스템 | TRONT"><meta property="og:description" content="빌딩(건물) 내 멀티비전 및 DID(Digital Information Display) TV 스크린을 활용, 영상 홍보 및 통합 안내 콘텐츠를 중앙에서 관리/분산 송출 하는 시스템 구축">
|
||||
<title>Signage 솔루션 — 빌딩(건물) 영상홍보 통합안내 시스템 | TRONT</title>
|
||||
<link rel="icon" href="../assets/images/tront-logo.png"><link rel="stylesheet" href="../styles.css"><link rel="stylesheet" href="../portfolio.css"><script src="../portfolio.js" defer></script>
|
||||
<link rel="stylesheet" href="../image-viewer.css">
|
||||
<script src="../image-viewer.js" defer></script>
|
||||
<link rel="stylesheet" href="../icons.css">
|
||||
</head>
|
||||
<body class="portfolio-page case--signage">
|
||||
<!-- Shared arrow shape from assets/icons/arrow_right_alt.svg. -->
|
||||
<svg class="icon-definitions" xmlns="http://www.w3.org/2000/svg" width="0" height="0" aria-hidden="true" focusable="false"><defs><symbol id="arrow-right-alt" viewBox="0 -960 960 960"><path d="m560-240-56-58 142-142H160v-80h486L504-662l56-58 240 240-240 240Z"/></symbol></defs></svg>
|
||||
<a class="skip-link" href="#case-main">본문으로 바로가기</a><div class="case-progress" aria-hidden="true"></div>
|
||||
<header class="case-header"><div class="wrap"><a class="case-back" href="../index.html#project-signage" data-back-to-work><span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-left" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span> 포트폴리오 목록</a><a class="case-brand" href="../index.html" aria-label="트론트 홈">TRONT<span class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a><a class="case-header-contact" href="../index.html#contact">함께 만들어가요 <span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a></div></header>
|
||||
<main id="case-main">
|
||||
<section class="case-hero wrap">
|
||||
<div class="case-eyebrow case-enter"><span>TRONT / SELECTED WORK</span><span>PROJECT 07 — 08</span></div>
|
||||
<div class="case-title-row"><div><p class="case-project-brand case-enter">Signage 솔루션</p><h1 class="case-enter">빌딩(건물) 영상홍보 통합안내 시스템</h1></div><span class="arrow-slot case-hero-arrow case-enter" aria-hidden="true"><svg class="arrow-icon arrow-icon--down-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></div>
|
||||
<dl class="case-meta case-enter"><div><dt>PROJECT PERIOD</dt><dd>2012.02 — 2012.11</dd></div><div><dt>OUR ROLE</dt><dd>중앙 관리 시스템 및 서버 구축 · 스케줄 및 파일 전송 로직 구현 · Infotrans Editor 및 Multi Manager 기능 구현</dd></div><div><dt>PLATFORM</dt><dd>멀티비전 / DID TV / 중앙 관리 시스템</dd></div></dl>
|
||||
</section>
|
||||
<div class="case-cover case-enter"><div class="project-scene scene--signage" aria-hidden="true"><div class="scene-surface">
|
||||
<div class="scene-grid"></div><div class="scene-kicker"><span>DIGITAL SIGNAGE / NETWORK</span><span>07 / 08</span></div>
|
||||
<div class="scene-wordmark">Signage<em>Network.</em></div><div class="scene-phrase">CONTENT / SCHEDULE / DISTRIBUTION</div>
|
||||
<div class="scene-graphic"><svg viewBox="0 0 400 330" fill="none" aria-hidden="true" focusable="false">
|
||||
<path class="signage-link" d="M100 183H170V82H222M170 183H268M170 183V270H215"/>
|
||||
<circle cx="170" cy="183" r="4" fill="currentColor"/>
|
||||
<rect class="signage-display" x="30" y="137" width="100" height="72" rx="5"/>
|
||||
<rect class="signage-pane" x="39" y="146" width="82" height="45" rx="2"/>
|
||||
<path class="signage-content" d="M66 158L85 168L66 179Z M69 218H91 M80 209V218"/>
|
||||
<rect class="signage-display" x="222" y="32" width="130" height="91" rx="5"/>
|
||||
<rect class="signage-pane" x="232" y="42" width="73" height="61" rx="2"/>
|
||||
<path class="signage-content" d="M313 49H341M313 60H341M313 71H333M240 111H334M258 62L278 73L258 85Z"/>
|
||||
<rect class="signage-display" x="268" y="151" width="65" height="108" rx="5"/>
|
||||
<rect class="signage-pane" x="276" y="159" width="49" height="62" rx="2"/>
|
||||
<path class="signage-content" d="M284 231H317M284 240H306"/>
|
||||
<rect class="signage-display" x="215" y="278" width="127" height="30" rx="4"/>
|
||||
<path class="signage-content" d="M228 293H272M281 293H330"/>
|
||||
<path class="signage-link" d="M52 243H148V295H196M192 22H369V136"/>
|
||||
</svg></div>
|
||||
<div class="scene-annotation">ONE SYSTEM / MULTIPLE SCREENS</div>
|
||||
<div class="scene-bottom"><span>MULTI-VISION / DID</span><span class="scene-view">PROJECT SCREENS <b class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></b></span></div>
|
||||
</div></div></div>
|
||||
<nav class="case-nav" aria-label="프로젝트 상세 메뉴"><div class="wrap"><span>Signage 솔루션</span><div><a href="#overview" aria-current="location">프로젝트 개요</a><a href="#screens">시스템 구성</a></div><span class="case-reading-progress" aria-hidden="true">00%</span></div></nav>
|
||||
<section id="overview" class="case-overview case-section wrap">
|
||||
<div class="case-section-label case-reveal"><span>01 / OVERVIEW</span><span>프로젝트 소개</span></div>
|
||||
<div class="case-overview-grid">
|
||||
<h2 class="case-reveal">하나의 콘텐츠를,<br>건물 곳곳의 화면으로.</h2>
|
||||
<div class="case-reveal"><p>빌딩(건물) 내 멀티비전 및 DID(Digital Information Display) TV 스크린을 활용, 영상 홍보 및 통합 안내 콘텐츠를 중앙에서 관리/분산 송출 하는 시스템 구축</p></div>
|
||||
</div>
|
||||
<div class="case-features" aria-label="서비스 대상, 주요 기능, 적용 기술">
|
||||
<article class="case-feature case-reveal"><span class="feature-index">01</span><h3>서비스 대상</h3><p>빌딩 내 멀티비전과 DID TV 스크린으로 영상 홍보 및 통합 안내 콘텐츠를 제공하는 시스템입니다.</p></article>
|
||||
<article class="case-feature case-reveal"><span class="feature-index">02</span><h3>주요 기능</h3><p>콘텐츠를 중앙에서 관리하고 네트워크로 분산 송출합니다. Infotrans Editor로 콘텐츠를 제작·편집하고 Multi Manager로 일일·월간 스케줄을 편성합니다.</p></article>
|
||||
<article class="case-feature case-reveal"><span class="feature-index">03</span><h3>적용 기술</h3><p>C++, JSP/Servlet, ASP.NET, Oracle을 사용해 중앙 관리 시스템과 서버, 스케줄 및 파일 전송 로직, 콘텐츠 편집·관리 기능을 구현했습니다.</p></article>
|
||||
</div>
|
||||
</section>
|
||||
<section id="screens" class="case-screens case-section"><div class="wrap"><div class="case-section-label case-reveal"><span>02 / SCREENS IN FOCUS</span><span>시스템 구성</span></div><div class="case-screen-heading case-reveal"><h2>화면으로 만나는<br><span>프로젝트의 디테일.</span></h2><p>콘텐츠 제작부터 화면 송출까지,<br>시스템의 연결 구조를 살펴보세요.</p></div><div class="screen-tabs case-reveal" role="tablist" aria-label="Signage 솔루션 시스템 구성"><button id="screen-tab-0" role="tab" aria-selected="true" aria-controls="screen-0" tabindex="0" data-screen="0"><span>01</span>시스템 구성</button></div><div class="screen-gallery case-reveal"><figure class="case-screen is-current" id="screen-0" role="tabpanel" aria-labelledby="screen-tab-0" tabindex="0"><div class="screen-canvas"><img src="../assets/images/signage_01.png" alt="중앙·개별 관리자가 콘텐츠와 스케줄을 관리하고, 네트워크를 통해 건물의 멀티비전 및 DID TV로 송출하는 시스템 구성도입니다." loading="lazy" width="3859" height="1947"></div><figcaption><span>01 / 01</span><p>중앙·개별 관리자가 콘텐츠와 스케줄을 관리하고, 네트워크를 통해 건물의 멀티비전 및 DID TV로 송출하는 시스템 구성도입니다.</p><a href="../assets/images/signage_01.png" target="_blank" rel="noopener" aria-label="시스템 구성 전체 화면으로 보기">크게 보기 ⤢</a></figcaption></figure></div><p class="case-source-note">프로젝트 화면 자료입니다.</p></div></section>
|
||||
<section class="case-next"><a href="gps-building.html" class="wrap case-next-link"><div><span class="tiny-label">KEEP EXPLORING / NEXT PROJECT</span><h2>GPS 솔루션</h2><p>GPS 트래킹 기반 위치확인 시스템 개발</p></div><span class="arrow-slot case-next-arrow" aria-hidden="true"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a></section>
|
||||
<section class="case-connect wrap"><div><p class="tiny-label">LET'S BUILD WHAT'S NEXT.</p><h2>다음 프로젝트,<br>함께 만들어가요.</h2></div><a href="../index.html#contact">함께 프로젝트 만들기 <span aria-hidden="true" class="arrow-slot"><svg class="arrow-icon arrow-icon--up-right" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></span></a></section>
|
||||
</main>
|
||||
<footer class="case-footer wrap"><a href="../index.html">주식회사 트론트</a><span>© 2026 TRONT. All rights reserved.</span><a href="#case-main">맨 위로 <svg class="arrow-icon arrow-icon--up" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><use href="#arrow-right-alt"></use></svg></a></footer>
|
||||
</body>
|
||||
</html>
|
||||
410
script.js
Normal file
@@ -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; }
|
||||
})();
|
||||