국내향 및 해외향 내비게이션 HMI 개발
+ +내비게이션 GUI 개발 · Interface layer 개발
프로젝트 자세히 보기diff --git a/README.md b/README.md index 081c603..1a3180c 100644 --- a/README.md +++ b/README.md @@ -12,9 +12,19 @@ python3 -m http.server 8000 --bind 127.0.0.1 이후 `http://127.0.0.1:8000`에 접속합니다. +## 문의 영역 + +- `index.html#contact`: 이메일 주소와 복사 버튼을 제공하는 문의 영역입니다. 스타일은 `contact-email.css`, 복사 및 결과 안내는 `contact-email.js`에 있습니다. +- `index-form.html#contact`: 기존 문의 폼형을 그대로 보존한 페이지입니다. 이름·회사명·이메일·연락처·문의내용·파일첨부를 받으며, 필수 입력 확인, 글자 수 표시, 첨부파일 선택·삭제·드래그와 전송 완료 안내 모달을 지원합니다. 모달은 확인 버튼·닫기 버튼·Esc·바깥 영역 클릭으로 닫습니다. +- 문의 스타일은 `contact-form.css`, 폼 동작은 `contact-form.js`에 있습니다. +- 현재는 전송 완료 화면까지만 구현되어 있으며 입력 내용과 파일을 전송하거나 저장하지 않습니다. 실제 접수에는 서버 연동이 필요합니다. 첨부는 최대 5개, 합계 20MB까지 선택할 수 있습니다. + +메인 페이지의 ‘프로젝트 문의’ 버튼은 이메일 문의 영역으로 이동합니다. + ## 파일 -- `index.html`: 회사 소개, 사업 영역, 프로젝트 8개, 연혁, 문의 정보 +- `index.html`: 회사 소개, 사업 영역, 프로젝트 8개, 연혁, 이메일 문의 +- `index-form.html`: 기존 문의 폼을 포함한 회사 홈페이지 - `styles.css`: 색상, 타이포그래피, 반응형 레이아웃 및 모션 - `company.css`: 회사 개요·대표이사·조직도를 통합한 패널과 특허 영역. 기존 사업영역의 스타일과 동작은 유지합니다. - `script.js`: Canvas 입체 그래픽, 프로젝트 필터, 연혁 탭, 메뉴, 스크롤 효과 @@ -27,17 +37,6 @@ python3 -m http.server 8000 --bind 127.0.0.1 - `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일 @@ -91,4 +90,4 @@ python3 -m http.server 8000 --bind 127.0.0.1 - Canvas는 화면 밖이나 다른 탭에서 렌더링을 중지합니다. - 본문 바로가기, 포커스 표시, 이미지 대체 텍스트, 의미 있는 HTML 구조 -외부 CDN이나 폰트 라이브러리를 사용하지 않아 다운로드한 파일만으로 홈페이지 화면을 확인할 수 있습니다. 지도 모달은 외부 지도 서비스를 불러오며 이메일/전화 링크는 기기 앱으로 연결됩니다. +외부 CDN이나 폰트 라이브러리를 사용하지 않아 다운로드한 파일만으로 홈페이지 화면을 확인할 수 있습니다. 지도 모달은 외부 지도 서비스를 불러옵니다. diff --git a/assets/icons/content_copy.svg b/assets/icons/content_copy.svg new file mode 100644 index 0000000..ffcc814 --- /dev/null +++ b/assets/icons/content_copy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/images/patent-liquid-dispenser.png b/assets/images/patent-liquid-dispenser.png index 58d6dc9..36f0b8c 100644 Binary files a/assets/images/patent-liquid-dispenser.png and b/assets/images/patent-liquid-dispenser.png differ diff --git a/assets/images/patent-virtual-image.png b/assets/images/patent-virtual-image.png index 4b0f546..d4ddb94 100644 Binary files a/assets/images/patent-virtual-image.png and b/assets/images/patent-virtual-image.png differ diff --git a/contact-email.css b/contact-email.css new file mode 100644 index 0000000..3fed95b --- /dev/null +++ b/contact-email.css @@ -0,0 +1,49 @@ +/* A direct connection: editorial typography and a detachable copy tab. */ +.contact-email-section::before { display: none; } +.contact-email-section .wrap { position: relative; } +.email-intro { display: flex; align-items: flex-end; justify-content: space-between; gap: 40px; } +.email-intro h2 { font-size: clamp(36px, 4.1vw, 62px); font-weight: 600; line-height: 1.3; letter-spacing: -.055em; } +.email-intro h2 em { color: var(--lime); font-style: normal; } +.email-intro > p { color: #d4defc; font-size: 13px; line-height: 1.95; padding-bottom: 5px; } +.email-channel { position: relative; margin-top: 72px; border-top: 1px solid #ffffff65; } +.email-channel::before { content: ''; position: absolute; top: -4px; left: 0; width: 7px; height: 7px; background: var(--lime); } +.email-channel-meta, .email-channel-footer { display: flex; justify-content: space-between; align-items: center; gap: 24px; } +.email-channel-meta { padding-top: 20px; color: #c9d6ff; font: 10px/1.5 monospace; letter-spacing: 1.1px; } +.email-address-row { display: flex; align-items: center; gap: 24px; padding: 40px 0 44px; } +.email-route { font: 44px/1 Arial, sans-serif; color: var(--lime); align-self: flex-start; margin-top: 12px; } +.email-address { font: 500 clamp(30px, 5.4vw, 82px)/1.15 Arial, sans-serif; letter-spacing: -.055em; min-width: 0; overflow-wrap: anywhere; word-break: normal; user-select: all; } +.email-copy { position: relative; flex: none; margin-left: auto; width: 116px; min-height: 116px; padding: 22px 20px 18px; background: var(--lime); color: var(--ink); display: flex; flex-direction: column; align-items: flex-start; gap: 19px; border-radius: 0; transform: rotate(-5deg); transition: transform .25s var(--ease), background .25s; } +.email-copy::before { content: ''; position: absolute; left: -7px; top: 0; bottom: 0; border-left: 1px dashed #d5ee8190; } +.email-copy img { width: 26px; height: 26px; } +.email-copy-label { font-size: 12px; font-weight: 700; } +.email-copy-index { position: absolute; top: 17px; right: 15px; font-size: 17px; } +.email-copy:hover, .email-copy:focus-visible { transform: rotate(0deg) translateY(-4px); background: #e3f7ac; } +.email-copy.is-copied { transform: rotate(0deg); background: var(--paper); } +.email-channel-footer { border-top: 1px solid #ffffff40; padding-top: 22px; } +.email-channel-note { color: #e2e9ff; font-size: 12px; } +.email-copy-status { color: #c9d6ff; font: 9px/1.7 monospace; letter-spacing: .65px; text-align: right; } +.email-copy-status.is-feedback { color: var(--lime); font-family: var(--font); letter-spacing: 0; font-size: 12px; } +.email-noscript { font-size: 12px; margin-top: 20px; } +@media (max-width: 1000px) { + .email-intro { align-items: flex-start; } + .email-intro > p { font-size: 12px; } + .email-address-row { gap: 16px; } + .email-copy { width: 96px; min-height: 104px; padding: 18px 15px; } + .email-route { font-size: 32px; margin-top: 8px; } +} +@media (max-width: 600px) { + .email-intro { flex-direction: column; gap: 22px; } + .email-intro h2 { font-size: clamp(30px, 8vw, 46px); } + .email-channel { margin-top: 42px; } + .email-channel-meta { font-size: 8px; letter-spacing: .5px; gap: 16px; } + .email-channel-meta > span:last-child { text-align: right; max-width: 115px; } + .email-address-row { flex-wrap: wrap; gap: 18px; padding: 32px 0 28px; } + .email-route { display: none; } + .email-address { flex-basis: 100%; font-size: clamp(27px, 7.5vw, 44px); } + .email-copy { margin: 8px 3px 0 auto; width: auto; min-height: 0; flex-direction: row; align-items: center; gap: 12px; padding: 15px 19px; transform: rotate(-3deg); } + .email-copy img { width: 21px; height: 21px; } + .email-copy-index { position: static; margin-left: 15px; } + .email-channel-footer { align-items: flex-start; flex-direction: column; gap: 12px; } + .email-channel-note { font-size: 11px; } + .email-copy-status { font-size: 8px; text-align: left; } +} diff --git a/contact-email.js b/contact-email.js new file mode 100644 index 0000000..2679277 --- /dev/null +++ b/contact-email.js @@ -0,0 +1,52 @@ +(() => { + 'use strict'; + const button = document.querySelector('.email-copy'); + const address = document.querySelector('.email-address'); + const status = document.querySelector('.email-copy-status'); + if (!button || !address || !status) return; + const label = button.querySelector('.email-copy-label'); + const initialStatus = status.textContent; + let resetTimer; + + function copyFallback(value) { + const field = document.createElement('textarea'); + field.value = value; + field.setAttribute('readonly', ''); + field.style.cssText = 'position:fixed;left:-9999px;top:0;opacity:0'; + document.body.append(field); + field.select(); + try { + if (!document.execCommand('copy')) throw new Error('Copy failed'); + } finally { + field.remove(); + button.focus({ preventScroll: true }); + } + } + + button.addEventListener('click', async () => { + clearTimeout(resetTimer); + const value = address.textContent.trim(); + try { + try { + if (!navigator.clipboard?.writeText) throw new Error('Clipboard unavailable'); + await navigator.clipboard.writeText(value); + } catch { + copyFallback(value); + } + label.textContent = '복사 완료'; + button.classList.add('is-copied'); + status.textContent = '이메일 주소를 복사했습니다. 메일의 받는 사람에 붙여넣어 주세요.'; + } catch { + label.textContent = '다시 복사'; + button.classList.remove('is-copied'); + status.textContent = '복사하지 못했습니다. 이메일 주소를 선택해 직접 복사해 주세요.'; + } + status.classList.add('is-feedback'); + resetTimer = setTimeout(() => { + label.textContent = '주소 복사'; + button.classList.remove('is-copied'); + status.classList.remove('is-feedback'); + status.textContent = initialStatus; + }, 6000); + }); +})(); diff --git a/contact-form.css b/contact-form.css new file mode 100644 index 0000000..115432f --- /dev/null +++ b/contact-form.css @@ -0,0 +1,111 @@ +/* Contact section, inquiry form, and completion dialog. */ +.contact-form-section .wrap { position: relative; } +.contact-form-section .section-top { align-items: center; gap: 24px; } +.contact-form-section h2 { font-size: clamp(38px, 4.4vw, 64px); font-weight: 600; line-height: 1.3; letter-spacing: -.055em; } +.contact-form-section h2 em { color: var(--lime); font-style: normal; } + +/* Introduction and inquiry form. */ +.contact-form-layout { display: grid; grid-template-columns: .85fr 1.15fr; gap: 80px; align-items: start; } +.contact-form-intro { padding-top: 15px; } +.contact-form-intro h2 { font-size: clamp(36px, 3.3vw, 49px); } +.contact-form-intro > p { color: #d9e3ff; font-size: 14px; line-height: 1.95; margin-top: 27px; } +.contact-form-topics { display: flex; flex-wrap: wrap; gap: 9px; margin-top: 29px; } +.contact-form-topics span { border: 1px solid #ffffff40; border-radius: 30px; font-size: 10px; padding: 9px 13px; color: #e1e8ff; } +.contact-form-art { position: relative; display: flex; align-items: center; justify-content: center; width: 230px; height: 215px; margin: 60px 0 0 12px; color: var(--lime); } +.contact-form-art::before, .contact-form-art::after { content: ""; position: absolute; width: 175px; height: 175px; border: 1px solid #d5ee8166; border-radius: 26px; transform: rotate(-20deg); } +.contact-form-art::after { transform: rotate(16deg); border-color: #d5ee8133; width: 200px; height: 200px; } +.contact-form-art .arrow-icon { width: 94px; height: 94px; } +.contact-form-caption { margin-top: 34px; color: #cbd7ff; font-size: 9px; font-family: monospace; letter-spacing: 1.4px; } +.inquiry-form { background: var(--paper); color: var(--ink); border-radius: 7px; padding: 36px 38px; min-width: 0; } +.inquiry-heading { display: flex; justify-content: space-between; align-items: center; gap: 20px; margin-bottom: 29px; } +.inquiry-heading h3 { font-size: 21px; font-weight: 600; letter-spacing: -.65px; } +.inquiry-heading p { font-size: 10px; color: #697269; } +.inquiry-required { color: var(--blue); } +.inquiry-fields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 23px 22px; } +.inquiry-field { min-width: 0; } +.inquiry-field--wide { grid-column: 1 / -1; } +.inquiry-field label, .inquiry-attachment-label { display: block; font-size: 12px; font-weight: 600; margin-bottom: 10px; } +.inquiry-field label > span, .inquiry-attachment-label > span { font-size: 10px; font-weight: 400; color: #7a8477; margin-left: 5px; } +.inquiry-field label .inquiry-required { color: var(--blue); } +.inquiry-field input, .inquiry-field textarea { display: block; width: 100%; border: 1px solid #d2d8cb; border-radius: 4px; background: #fff9; color: var(--ink); font: inherit; font-size: 13px; line-height: 1.5; padding: 13px 14px; transition: border-color .2s, box-shadow .2s; } +.inquiry-field input::placeholder, .inquiry-field textarea::placeholder { color: #8a9286; } +.inquiry-form :is(input, textarea):focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; } +.inquiry-field textarea { resize: vertical; min-height: 137px; } +.inquiry-counter { margin-top: 7px; text-align: right; font: 10px monospace; color: #798474; } +.inquiry-upload { position: relative; display: flex; align-items: center; justify-content: center; gap: 15px; padding: 21px 17px; border: 1px dashed #aebca1; border-radius: 4px; background: #e9eddf; transition: background .2s, border-color .2s; } +.inquiry-upload:hover, .inquiry-upload.is-dragging { background: #e0e8d2; border-color: var(--blue); } +.inquiry-upload:focus-within { outline: 2px solid var(--blue); outline-offset: 3px; } +.inquiry-upload > svg { width: 24px; height: 24px; color: #617557; flex: none; } +.inquiry-upload strong { display: block; font-size: 12px; font-weight: 500; line-height: 1.6; } +.inquiry-upload small { display: block; font-size: 10px; color: #73816a; margin-top: 3px; line-height: 1.7; } +.inquiry-upload input { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; } +.inquiry-files { margin: 0; padding: 0; list-style: none; } +.inquiry-files li { display: flex; align-items: center; gap: 12px; padding: 11px 0; border-bottom: 1px solid var(--line); font-size: 11px; } +.inquiry-file-name { flex: 1; min-width: 0; overflow-wrap: anywhere; } +.inquiry-file-size { font: 10px monospace; color: #697564; white-space: nowrap; } +.inquiry-file-remove { display: grid; place-items: center; flex: none; width: 30px; height: 30px; font-size: 19px; background: #e4e9dc; border-radius: 50%; color: #4c5e44; } +.inquiry-form button:focus-visible { outline-color: var(--blue); outline-offset: 3px; } +.inquiry-upload-status { margin-top: 8px; font-size: 11px; line-height: 1.7; color: #53664c; } +.inquiry-upload-status:empty { display: none; } +.inquiry-upload-status.is-error { color: #a03827; } +.inquiry-submit { display: flex; align-items: center; justify-content: space-between; gap: 20px; width: 100%; padding: 18px 20px; background: var(--ink); color: var(--paper); border-radius: 4px; margin-top: 27px; font-size: 13px; font-weight: 600; transition: background .2s; } +.inquiry-submit:hover { background: var(--blue); } +.inquiry-submit:disabled { opacity: .55; cursor: not-allowed; } +.inquiry-submit .arrow-icon { width: 22px; height: 22px; } +.inquiry-noscript-note { color: #77816e; font-size: 10px; line-height: 1.7; text-align: center; margin-top: 13px; } +html.inquiry-success-open { overflow: hidden; } +.inquiry-dialog { width: min(460px, calc(100% - 32px)); max-width: none; max-height: calc(100dvh - 32px); margin: auto; padding: 48px 32px 25px; border: 1px solid var(--line); border-radius: 12px; background: var(--paper); color: var(--ink); text-align: center; box-shadow: 0 28px 90px #101e2440; overflow: auto; } +.inquiry-dialog::backdrop { background: #101e2499; backdrop-filter: blur(5px); } +.inquiry-dialog-close { position: absolute; top: 10px; right: 10px; display: grid; place-items: center; width: 40px; height: 40px; border-radius: 50%; background: transparent; color: #677269; font-size: 27px; line-height: 1; } +.inquiry-dialog-close:hover { background: #e4e9dc; } +.inquiry-success-mark { display: grid; place-items: center; width: 72px; height: 72px; margin: 0 auto 26px; border-radius: 50%; background: #e2ebff; color: var(--blue); } +.inquiry-success-mark svg { width: 38px; height: 38px; } +.inquiry-success-label { color: var(--blue); font-size: 8px; font-weight: 600; letter-spacing: 1.2px; margin-bottom: 12px; } +.inquiry-dialog h2 { font-size: 27px; font-weight: 600; line-height: 1.4; letter-spacing: -1.2px; } +#inquiry-success-description { color: #65715e; font-size: 13px; line-height: 1.9; margin-top: 17px; } +.inquiry-dialog-confirm { display: block; width: 100%; min-height: 48px; margin-top: 30px; border-radius: 4px; background: var(--blue); color: white; font-size: 13px; font-weight: 600; transition: background .2s; } +.inquiry-dialog-confirm:hover { background: #1c43b7; } +@media (max-width: 400px) { + .inquiry-dialog { padding: 43px 20px 23px; } + .inquiry-dialog h2 { font-size: 23px; letter-spacing: -.8px; } + #inquiry-success-description { font-size: 12px; } +} + +@media (max-width: 1150px) { + .contact-form-layout { grid-template-columns: .8fr 1.2fr; gap: 40px; } + .inquiry-form { padding: 29px 27px; } + .contact-form-intro h2 { font-size: 36px; } + .contact-form-intro > p { font-size: 12px; } + .contact-form-intro > p br { display: none; } +} +@media (max-width: 850px) { + .contact-form-section .section-top { margin-bottom: 42px; } + .contact-form-layout { grid-template-columns: 1fr; gap: 35px; } + .contact-form-intro { padding-top: 0; position: relative; padding-right: 140px; } + .contact-form-intro > p { max-width: 420px; margin-top: 20px; } + .contact-form-art { position: absolute; right: 0; top: 10px; width: 100px; height: 130px; margin: 0 12px 0 0; } + .contact-form-art::before { width: 90px; height: 90px; border-radius: 14px; } + .contact-form-art::after { width: 110px; height: 110px; border-radius: 14px; } + .contact-form-art .arrow-icon { width: 50px; height: 50px; } + .contact-form-topics { margin-top: 20px; } + .contact-form-caption { display: none; } + .inquiry-form { padding: 32px; } +} +@media (max-width: 600px) { + .contact-form-section .section-top { align-items: flex-start; flex-direction: column; gap: 21px; margin-bottom: 37px; } + .contact-form-section h2 { font-size: clamp(31px, 8.1vw, 44px); } + .contact-form-intro { padding-right: 0; } + .contact-form-art { display: none; } + .contact-form-intro > p { font-size: 12px; max-width: 320px; } + .contact-form-topics span { font-size: 9px; padding: 8px 11px; } + .inquiry-form { padding: 26px 20px; } + .inquiry-heading { margin-bottom: 26px; gap: 10px; } + .inquiry-heading h3 { font-size: 20px; } + .inquiry-heading p { font-size: 9px; } + .inquiry-fields { grid-template-columns: 1fr; gap: 21px; } + .inquiry-field input, .inquiry-field textarea { font-size: 16px; padding: 12px; } + .inquiry-upload { padding: 20px 12px; gap: 10px; } + .inquiry-upload strong { font-size: 11px; } + .inquiry-upload small { font-size: 9px; } + .inquiry-noscript-note { font-size: 9px; } +} diff --git a/contact-form.js b/contact-form.js new file mode 100644 index 0000000..54e2d33 --- /dev/null +++ b/contact-form.js @@ -0,0 +1,129 @@ +/* Local interaction preview: no requests, uploads, or storage. */ +(() => { + 'use strict'; + const form = document.querySelector('.inquiry-form'); + if (!form) return; + + const message = form.elements.message; + const counter = form.querySelector('[data-message-count]'); + const input = form.elements.attachments; + const dropzone = form.querySelector('.inquiry-upload'); + const list = form.querySelector('.inquiry-files'); + const uploadStatus = form.querySelector('.inquiry-upload-status'); + const dialog = document.querySelector('#inquiry-success-dialog'); + const submitButton = form.querySelector('.inquiry-submit'); + submitButton.disabled = false; + const maxFiles = 5; + const maxBytes = 20 * 1024 * 1024; + let files = []; + let dragDepth = 0; + + function formatSize(size) { + return size >= 1024 * 1024 ? `${(size / 1024 / 1024).toFixed(1)} MB` : `${Math.max(1, Math.ceil(size / 1024))} KB`; + } + + function showFiles() { + list.replaceChildren(); + files.forEach((file, index) => { + const item = document.createElement('li'); + const name = document.createElement('span'); + name.className = 'inquiry-file-name'; + name.textContent = file.name; + const size = document.createElement('span'); + size.className = 'inquiry-file-size'; + size.textContent = formatSize(file.size); + const remove = document.createElement('button'); + remove.type = 'button'; + remove.className = 'inquiry-file-remove'; + remove.textContent = '×'; + remove.setAttribute('aria-label', `${file.name} 삭제`); + remove.addEventListener('click', () => { + files.splice(index, 1); + showFiles(); + const next = list.querySelectorAll('button')[Math.min(index, files.length - 1)]; + (next || input).focus(); + }); + item.append(name, size, remove); + list.append(item); + }); + uploadStatus.classList.remove('is-error'); + uploadStatus.textContent = files.length ? `${files.length}개 선택 · 총 ${formatSize(files.reduce((sum, file) => sum + file.size, 0))}` : ''; + } + + function addFiles(selection) { + const next = [...files]; + for (const file of selection) { + if (!next.some(existing => existing.name === file.name && existing.size === file.size && existing.lastModified === file.lastModified)) next.push(file); + } + let error = ''; + if (next.length > maxFiles) error = '파일은 최대 5개까지 첨부할 수 있습니다.'; + else if (next.reduce((sum, file) => sum + file.size, 0) > maxBytes) error = '첨부파일의 총 용량은 20MB 이하여야 합니다.'; + input.value = ''; + if (error) { + uploadStatus.textContent = error; + uploadStatus.classList.add('is-error'); + return; + } + files = next; + showFiles(); + } + + input.addEventListener('change', () => addFiles([...input.files])); + dropzone.addEventListener('dragenter', event => { + event.preventDefault(); + dragDepth++; + dropzone.classList.add('is-dragging'); + }); + dropzone.addEventListener('dragover', event => { + event.preventDefault(); + event.dataTransfer.dropEffect = 'copy'; + }); + dropzone.addEventListener('dragleave', () => { + if (--dragDepth <= 0) dropzone.classList.remove('is-dragging'); + }); + dropzone.addEventListener('drop', event => { + event.preventDefault(); + dragDepth = 0; + dropzone.classList.remove('is-dragging'); + addFiles([...event.dataTransfer.files]); + }); + // Avoid navigating away if a file is dropped just outside the attachment area. + for (const type of ['dragover', 'drop']) { + form.addEventListener(type, event => { + if ([...event.dataTransfer.types].includes('Files')) event.preventDefault(); + }); + } + + form.addEventListener('input', event => { + if (event.target.matches('input, textarea')) event.target.setCustomValidity(''); + counter.textContent = message.value.length.toLocaleString('ko-KR'); + }); + dialog.querySelectorAll('[data-close-inquiry]').forEach(button => { + button.addEventListener('click', () => dialog.close()); + }); + let backdropPressed = false; + function isBackdrop(event) { + const bounds = dialog.getBoundingClientRect(); + return event.target === dialog && (event.clientX < bounds.left || event.clientX > bounds.right || event.clientY < bounds.top || event.clientY > bounds.bottom); + } + dialog.addEventListener('pointerdown', event => { backdropPressed = isBackdrop(event); }); + dialog.addEventListener('click', event => { + if (backdropPressed && isBackdrop(event)) dialog.close(); + backdropPressed = false; + }); + dialog.addEventListener('close', () => { + document.documentElement.classList.remove('inquiry-success-open'); + submitButton.focus({ preventScroll: true }); + }); + form.addEventListener('submit', event => { + event.preventDefault(); + for (const field of [form.elements.name, message]) { + field.setCustomValidity(field.value.trim() ? '' : '공백을 제외한 내용을 입력해 주세요.'); + } + if (!form.reportValidity()) return; + if (!dialog.open) { + dialog.showModal(); + document.documentElement.classList.add('inquiry-success-open'); + } + }); +})(); diff --git a/index-form.html b/index-form.html new file mode 100644 index 0000000..5685abf --- /dev/null +++ b/index-form.html @@ -0,0 +1,385 @@ + + +
+ + + + + + + + + + + + +
+
+ 프로젝트 문의
+
+ CONNECTING IDEAS. CREATING IMPACT.
+모빌리티부터 디지털 플랫폼까지.
비즈니스의 다음을 함께 만드는 기술 파트너, 트론트.
01 / ABOUT TRONT
기술에 대한 깊이, 사람을 향한 방향트론트는 모바일, 웹, 애플리케이션을 넘어 산업과 일상을 연결하는 IT 솔루션 전문 기업입니다. 2008년의 첫걸음부터 다양한 산업 현장에서 쌓아온 경험으로, 고객의 아이디어를 작동하는 서비스로 만듭니다.
지능형 관제, 디지털 플랫폼, 미래 모빌리티까지.
기술의 경계를 넘나들며 비즈니스에 필요한 연결을 설계합니다.
COMPANY AT A GLANCE
+PEOPLE & ORGANIZATION
TECHNICAL SUPPORT
Web 개발
모바일 앱 개발
내비게이션 개발
02 / OUR EXPERTISE
FIVE FIELDS. ONE CONNECTED VISION.주식회사 트론트는 스마트 모빌리티, 산업 관제, 자산 관리, SaaS 플랫폼,
디지털 인터페이스 분야에서 고객의 디지털 혁신을 이끕니다.
운전자와 차량 사이, 직관적인 연결을 만듭니다. 국내외 내비게이션 HMI와 차량 통신 기술을 바탕으로 새로운 이동 경험을 구현합니다.
현장의 데이터를 한눈에, 복잡한 운영을 더 명확하게. 실시간 모니터링과 데이터 송수신, 원격 제어를 통합해 산업 현장의 효율과 안전을 지원합니다.
자산의 흐름에 정확함을, 운영에 투명함을 더합니다. 블록체인 기반 서비스와 RFID·NFC 기술을 활용해 데이터와 실물 자산을 연결합니다.
사람과 사람, 서비스와 일상을 연결합니다. 약국·교육·스포츠 등 도메인에 대한 이해를 바탕으로 모바일 앱, 웹, 관리자 시스템을 함께 구축합니다.
새로운 기술이 자연스러운 경험이 되도록. 가상현실 기반 웹 환경, 디지털 사이니지, 영상 분석과 UX 기술로 디지털 접점을 확장합니다.
03 / SELECTED WORK
아이디어가 현실이 되는 순간다양한 산업에서 증명해 온 실행력.
트론트가 함께 만든 프로젝트를 소개합니다.
내비게이션 GUI 개발 · Interface layer 개발
프로젝트 자세히 보기모바일 앱 · 사용자 웹 · 백엔드 개발 · 관리자 화면 개발
프로젝트 자세히 보기사용자 웹 · 백엔드 개발 · 관리자 화면 개발
프로젝트 자세히 보기백엔드 개발 · 관리자 화면 개발
프로젝트 자세히 보기모바일 앱 · 백엔드 개발 · 관리자 화면 개발
프로젝트 자세히 보기RFID Tag Reader 인터페이스 개발 · 데이터 조회 및 업데이트 모듈 개발
프로젝트 자세히 보기중앙 관리 시스템 및 서버 구축 · 스케줄 및 파일 전송 로직 구현 · Infotrans Editor 및 Multi Manager 기능 구현
프로젝트 자세히 보기GPS Manager · Location Manager · Navigation Manager 기반 디바이스 드라이버 개발
프로젝트 자세히 보기04 / PATENTS & R&D
IDEAS INTO INTELLECTUAL PROPERTY특정 기술에 안주하지 않는 R&D 프로세스를 핵심 과정으로 삼고 있습니다.
미래 비즈니스 환경 변화에 선제적으로 대응하기 위해 지속적인 발굴 및 지식재산권 확보에 주력하고 있습니다.
특허와 인증은 당사가 미래에도 신뢰할 수 있는 파트너임을 증명하는 잠재적 능력입니다.
+ 특허증 크게 보기
+
+ 01 VIRTUAL IMAGE
+실제 객체를 기반으로 가상 이미지를 생성하고, 현실과 통합하는 기술입니다.
+
+ 특허증 크게 보기
+
+ 02 DEVICE CONTROL
+액체 세제의 자동 판매 및 관리를 위한 기술로 효율적인 자판기 시스템 제어에 대한 특허.
+05 / OUR JOURNEY
A CONTINUOUS JOURNEY OF INNOVATION첫 서버에서 글로벌 모빌리티까지.
기술이 바뀌어도, 더 나은 답을 찾는 우리의 태도는 같습니다.
WHAT'S NEXT
블록체인 기반 기부 플랫폼 GivOn 관리자 개발과 소규모 기관차 관제 프로그램 개발로 기술의 쓰임을 넓혀갑니다.
06 / LET'S CONNECT
아직 구체적이지 않아도 괜찮습니다.
만들고 싶은 서비스와 해결하고 싶은 문제를 들려주세요.
아이디어의 다음 단계를 함께 고민하겠습니다.
소개서에 수록된 실제 프로젝트와 수행 기간을 기준으로 구성했습니다.
회사소개서에 수록된 특허증을 기준으로 소개합니다.
소개서에서 보기05 / OUR JOURNEY
A CONTINUOUS JOURNEY OF INNOVATION첫 서버에서 글로벌 모빌리티까지.
기술이 바뀌어도, 더 나은 답을 찾는 우리의 태도는 같습니다.
WHAT'S NEXT
블록체인 기반 기부 플랫폼 GivOn 관리자 개발과 소규모 기관차 관제 프로그램 개발로 기술의 쓰임을 넓혀갑니다.
06 / LET'S CONNECT
YOUR NEXT CHAPTER STARTS HERE.06 / LET'S CONNECT
YOUR IDEA. OUR NEXT CHAPTER.아직 구체적이지 않아도 괜찮습니다.
만들고 싶은 것, 해결하고 싶은 것.
당신의 생각을 이메일로 들려주세요.
hello@tront.example
+ +