first commit

This commit is contained in:
2026-05-30 15:02:45 +09:00
commit 005719edee
212 changed files with 38830 additions and 0 deletions
+99
View File
@@ -0,0 +1,99 @@
<script setup lang="ts">
// 공통 푸터 — 모든 페이지에서 동일하게 사용
</script>
<template>
<footer>
<div class="wrap">
<NuxtLink to="/" class="foot-logo" aria-label="이너탭 ">
<img src="/images/img-itab.png" alt="innertab" >
</NuxtLink>
<div class="foot-company">
<p class="foot-name"><span class="dot" /> 이너탭</p>
<p>사업자등록번호: 000-00-00000 &nbsp;|&nbsp; 이메일: people@innertab.co.kr</p>
<p>주소: 서울특별시 강남구 테헤란로 000 00, 0~0</p>
</div>
<div class="foot-divider" />
<div class="foot-bottom">
<NuxtLink to="/privacy" class="foot-policy">개인정보처리방침</NuxtLink>
</div>
</div>
</footer>
</template>
<style scoped>
footer {
border-top: 0.0625rem solid var(--line);
padding: clamp(3rem, 6vw, 4.75rem) 0 clamp(2.5rem, 5vw, 4rem); /* 48~76px / 40~64px */
}
.foot-logo {
display: inline-block;
}
.foot-logo img {
height: 1.875rem; /* 30px */
display: block;
}
.foot-company {
margin-top: 1.625rem; /* 26px */
}
.foot-company .foot-name {
font-size: 0.9375rem; /* 15px */
font-weight: 700;
color: var(--ink);
display: flex;
align-items: center;
gap: 0.4375rem; /* 7px */
}
.foot-company .foot-name .dot {
width: 0.5rem;
height: 0.5rem; /* 8px */
border-radius: 50%;
background: var(--brand);
}
.foot-company p {
font-size: 0.875rem; /* 14px */
color: var(--ink-soft);
line-height: 1.7;
}
.foot-company p + p {
margin-top: 0.5rem; /* 8px */
}
.foot-company .foot-name + p {
margin-top: 0.875rem; /* 14px */
}
.foot-divider {
height: 0.0625rem; /* 1px */
background: var(--line);
margin: clamp(1.75rem, 3.5vw, 2.75rem) 0 clamp(1.5rem, 3vw, 2.25rem);
}
.foot-bottom {
display: flex;
align-items: center;
justify-content: space-between;
gap: 1.25rem; /* 20px */
}
.foot-policy {
font-size: 0.875rem; /* 14px */
font-weight: 700;
color: var(--ink);
white-space: nowrap;
}
.foot-policy:hover {
color: var(--brand);
}
</style>
+81
View File
@@ -0,0 +1,81 @@
<script setup lang="ts">
// 공통 헤더 — 메인('home')은 커피챗 CTA, 서브 페이지('back')는 채용 홈 복귀 링크
interface Props {
variant?: 'home' | 'back'
}
withDefaults(defineProps<Props>(), {
variant: 'home',
})
</script>
<template>
<header>
<div class="wrap nav">
<NuxtLink to="/" class="logo" aria-label="이너탭 ">
<img src="/images/img-itab.png" alt="innertab" >
</NuxtLink>
<NuxtLink v-if="variant === 'home'" to="/coffee-chat" class="nav-cta">
커피챗 신청
</NuxtLink>
<NuxtLink v-else to="/" class="back"> 채용 홈으로</NuxtLink>
</div>
</header>
</template>
<style scoped>
header {
position: sticky;
top: 0;
z-index: 50;
background: rgba(250, 248, 244, 0.82);
backdrop-filter: blur(0.875rem); /* 14px */
border-bottom: 0.0625rem solid var(--line);
}
.nav {
height: 4.5rem; /* 72px */
display: flex;
align-items: center;
justify-content: space-between;
}
.logo {
display: flex;
align-items: center;
}
.logo img {
height: 1.75rem; /* 28px */
display: block;
}
.nav-cta {
font-size: 0.9375rem; /* 15px */
font-weight: 700;
color: #fff;
background: var(--brand);
padding: 0.625rem 1.375rem; /* 10px 22px */
border-radius: 6.25rem; /* 100px */
white-space: nowrap;
transition: background 0.2s ease, transform 0.2s ease;
}
.nav-cta:hover {
background: #161e36;
transform: translateY(-0.0625rem);
}
.back {
font-size: 0.875rem; /* 14px */
font-weight: 600;
color: var(--ink-soft);
display: inline-flex;
align-items: center;
gap: 0.4375rem; /* 7px */
}
.back:hover {
color: var(--ink);
}
</style>
+66
View File
@@ -0,0 +1,66 @@
<script setup lang="ts">
// 숫자 카운트업 — 요소가 보이면 0부터 목표값까지 증가 애니메이션
// SSR/no-JS 환경에서는 최종값을 그대로 렌더(SEO·접근성), 클라이언트에서만 애니메이션
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'
interface Props {
value: number
duration?: number // ms
locale?: boolean // 천 단위 구분기호(ko-KR)
}
const props = withDefaults(defineProps<Props>(), {
duration: 1600,
locale: true,
})
const display = ref<number>(props.value) // 초기값 = 최종값 (SSR 노출)
const elRef = ref<HTMLElement | null>(null)
let io: IntersectionObserver | null = null
let rafId = 0
const formatted = computed(() =>
props.locale ? display.value.toLocaleString('ko-KR') : String(display.value),
)
function animate() {
const start = performance.now()
const from = 0
const to = props.value
const tick = (now: number) => {
const t = Math.min(1, (now - start) / props.duration)
// easeOutCubic — 빠르게 시작해 부드럽게 감속
const eased = 1 - Math.pow(1 - t, 3)
display.value = Math.round(from + (to - from) * eased)
if (t < 1) rafId = requestAnimationFrame(tick)
}
rafId = requestAnimationFrame(tick)
}
onMounted(() => {
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches
if (reduceMotion || !elRef.value) return
display.value = 0 // 클라이언트에서 0부터 시작
io = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
animate()
io?.disconnect()
}
}
},
{ threshold: 0.4 },
)
io.observe(elRef.value)
})
onBeforeUnmount(() => {
io?.disconnect()
if (rafId) cancelAnimationFrame(rafId)
})
</script>
<template>
<span ref="elRef">{{ formatted }}</span>
</template>
+37
View File
@@ -0,0 +1,37 @@
<script setup lang="ts">
// 이미지 자리표시자 — 실제 사진이 준비되기 전 영역을 채우는 플레이스홀더
// (원본 디자인의 <image-slot> 대체. 실제 이미지가 생기면 <img> 로 교체)
interface Props {
label?: string
}
withDefaults(defineProps<Props>(), {
label: '이미지',
})
</script>
<template>
<div class="ph" role="img" :aria-label="label">
<span class="ph-cap">{{ label }}</span>
</div>
</template>
<style scoped>
.ph {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
display: grid;
place-items: center;
background: linear-gradient(135deg, #eef0f3 0%, #dfe2e7 100%);
}
.ph-cap {
font-size: 0.75rem; /* 12px */
letter-spacing: 0.1em;
color: rgba(27, 26, 23, 0.38);
font-weight: 600;
text-align: center;
padding: 0 1rem;
}
</style>