first commit
This commit is contained in:
@@ -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 | 이메일: 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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user