fix(web): restore public header login + track-tab icons/tooltips, visitor CTA to /tickets/lookup, accent-button white-text sweep
- PublicShell: track tabs get icons + title tooltips (right-aligned), login link restored at >=768px - VisitorTrackPage: wayfinding/checkin CTAs point to #guide/#transport//tickets/lookup instead of /login - accent buttons: enforce --color-on-accent (white text) across approval/docs/home/movein/schedule/work/public Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
f3b800f9be
commit
0add2cd417
@ -420,15 +420,15 @@
|
||||
}
|
||||
.kx-rev__step--cur .kx-rev__step-no {
|
||||
background: #1f29fc;
|
||||
color: #fff;
|
||||
color: var(--color-on-accent);
|
||||
}
|
||||
.kx-rev__step--ok .kx-rev__step-no {
|
||||
background: #10b981;
|
||||
color: #fff;
|
||||
color: var(--color-on-accent);
|
||||
}
|
||||
.kx-rev__step--bad .kx-rev__step-no {
|
||||
background: #ef4444;
|
||||
color: #fff;
|
||||
color: var(--color-on-accent);
|
||||
}
|
||||
.kx-rev__step--skip {
|
||||
opacity: 0.6;
|
||||
|
||||
@ -143,7 +143,7 @@
|
||||
.kx-doc__step.is-done .kx-doc__node {
|
||||
background: var(--color-primary-600);
|
||||
border-color: var(--color-primary-600);
|
||||
color: #fff;
|
||||
color: var(--color-on-accent);
|
||||
}
|
||||
.kx-doc__step.is-active .kx-doc__node {
|
||||
background: var(--color-white);
|
||||
@ -354,7 +354,7 @@
|
||||
.kx-doc__stat {
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--color-primary-700);
|
||||
color: #fff;
|
||||
color: var(--color-on-accent);
|
||||
padding: var(--space-5);
|
||||
}
|
||||
.kx-doc__stat-label {
|
||||
@ -426,7 +426,7 @@
|
||||
}
|
||||
.kx-rep__acc.is-open .kx-rep__acc-head {
|
||||
background: var(--color-primary-600);
|
||||
color: #fff;
|
||||
color: var(--color-on-accent);
|
||||
}
|
||||
.kx-rep__acc-head h3 {
|
||||
font-size: var(--fs-h3);
|
||||
@ -592,7 +592,7 @@
|
||||
transform: translateX(-50%);
|
||||
z-index: 60;
|
||||
background: var(--color-neutral-900);
|
||||
color: #fff;
|
||||
color: var(--color-white);
|
||||
padding: 10px 18px;
|
||||
border-radius: var(--radius-lg);
|
||||
font-size: var(--fs-body);
|
||||
|
||||
@ -153,7 +153,7 @@
|
||||
background: linear-gradient(120deg, var(--color-primary-600), var(--color-ai-accent));
|
||||
}
|
||||
.kx-home__banner-text h2 {
|
||||
color: #fff;
|
||||
color: var(--color-on-accent);
|
||||
font-size: var(--fs-h1);
|
||||
margin: 0 0 8px;
|
||||
}
|
||||
@ -167,7 +167,7 @@
|
||||
left: var(--space-8);
|
||||
bottom: var(--space-6);
|
||||
right: 30%;
|
||||
color: #fff;
|
||||
color: var(--color-on-accent);
|
||||
}
|
||||
.kx-home__banner-title {
|
||||
font-size: var(--fs-h1);
|
||||
@ -293,7 +293,7 @@
|
||||
background: linear-gradient(140deg, var(--color-primary-600), var(--color-ai-accent));
|
||||
}
|
||||
.kx-poster__fallback-text {
|
||||
color: #fff;
|
||||
color: var(--color-on-accent);
|
||||
font-size: var(--fs-h3);
|
||||
font-weight: var(--fw-bold);
|
||||
text-align: center;
|
||||
@ -539,7 +539,7 @@
|
||||
}
|
||||
.kx-home__cal-cell.is-today .kx-home__cal-daynum {
|
||||
background: var(--color-primary-600);
|
||||
color: #fff;
|
||||
color: var(--color-on-accent);
|
||||
border-radius: var(--radius-pill);
|
||||
padding: 1px 7px;
|
||||
font-weight: var(--fw-bold);
|
||||
@ -564,7 +564,7 @@
|
||||
border-radius: 3px;
|
||||
font-size: var(--fs-micro);
|
||||
padding: 0 6px;
|
||||
color: #fff;
|
||||
color: var(--color-on-accent);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@ -721,7 +721,7 @@
|
||||
}
|
||||
.kx-home__count-badge {
|
||||
background: var(--color-primary-600);
|
||||
color: #fff;
|
||||
color: var(--color-on-accent);
|
||||
border-radius: var(--radius-pill);
|
||||
min-width: 20px;
|
||||
height: 20px;
|
||||
@ -885,7 +885,7 @@
|
||||
}
|
||||
.kx-home__new {
|
||||
background: var(--color-error);
|
||||
color: #fff;
|
||||
color: var(--color-on-accent);
|
||||
font-size: var(--fs-nano);
|
||||
font-weight: var(--fw-bold);
|
||||
border-radius: var(--radius-sm);
|
||||
|
||||
@ -159,7 +159,7 @@
|
||||
}
|
||||
.kx-dock__block--booked {
|
||||
background: var(--color-primary-600);
|
||||
color: #fff;
|
||||
color: var(--color-on-accent);
|
||||
}
|
||||
.kx-dock__block--priority {
|
||||
background: var(--color-violation-warn-bg);
|
||||
@ -266,7 +266,7 @@
|
||||
padding: var(--space-4);
|
||||
border-radius: var(--radius-lg);
|
||||
background: var(--color-primary-700);
|
||||
color: #fff;
|
||||
color: var(--color-on-accent);
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
text-align: left;
|
||||
@ -365,7 +365,7 @@
|
||||
transform: translateX(-50%);
|
||||
z-index: 60;
|
||||
background: var(--color-neutral-900);
|
||||
color: #fff;
|
||||
color: var(--color-white);
|
||||
padding: 10px 18px;
|
||||
border-radius: var(--radius-lg);
|
||||
font-size: var(--fs-body);
|
||||
|
||||
@ -3,10 +3,24 @@
|
||||
* COEX 벤치마크(docs/analysis/coex-website.md): 상단 트랙 스위처 + 유틸을 하나의 상단 바로 통합.
|
||||
* design.md §3B/§3C-0: 3-트랙 스위처(role=tablist·로빙 탭인덱스)·GNB·다국어 토글을 단일 헤더로 흡수.
|
||||
*/
|
||||
import { useRef, useState, type KeyboardEvent, type ReactNode } from 'react';
|
||||
import {
|
||||
useRef,
|
||||
useState,
|
||||
type ComponentType,
|
||||
type KeyboardEvent,
|
||||
type ReactNode,
|
||||
type SVGProps,
|
||||
} from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link, useNavigate } from 'react-router-dom';
|
||||
import { IconMenu, IconClose, IconSearch } from './publicIcons';
|
||||
import {
|
||||
IconMenu,
|
||||
IconClose,
|
||||
IconSearch,
|
||||
IconPerson,
|
||||
IconBuyer,
|
||||
IconFactory,
|
||||
} from './publicIcons';
|
||||
import { LanguageSwitcher } from '../../components/ui/LanguageSwitcher';
|
||||
import { useAuthStore } from '../../store/authStore';
|
||||
import { usePublicMotion } from './usePublicMotion';
|
||||
@ -22,10 +36,17 @@ const GNB = [
|
||||
/** 3-트랙 진입 레이어 키 (§3C). 관문(SCR-T0)은 미선택 → undefined. */
|
||||
export type PublicTrack = 'visitor' | 'business' | 'agency';
|
||||
|
||||
const TRACK_TABS: { key: PublicTrack; to: string; i18n: string }[] = [
|
||||
{ key: 'visitor', to: '/visitor', i18n: 'track.shell.tab.visitor' },
|
||||
{ key: 'business', to: '/business', i18n: 'track.shell.tab.business' },
|
||||
{ key: 'agency', to: '/agency', i18n: 'track.shell.tab.agency' },
|
||||
type TabIcon = ComponentType<SVGProps<SVGSVGElement>>;
|
||||
|
||||
/**
|
||||
* 3-트랙 진입 탭. 텍스트 대신 선 SVG 아이콘 + 툴팁(title)·aria-label로 의미 전달
|
||||
* (아이콘만으로 의미 전달 금지 원칙 → 항상 라벨 텍스트를 접근성 속성에 부여).
|
||||
* 관람=사람 · 비즈니스=바이어(브리프케이스) · 협력사=시공(공장) 계열.
|
||||
*/
|
||||
const TRACK_TABS: { key: PublicTrack; to: string; i18n: string; icon: TabIcon }[] = [
|
||||
{ key: 'visitor', to: '/visitor', i18n: 'track.shell.tab.visitor', icon: IconPerson },
|
||||
{ key: 'business', to: '/business', i18n: 'track.shell.tab.business', icon: IconBuyer },
|
||||
{ key: 'agency', to: '/agency', i18n: 'track.shell.tab.agency', icon: IconFactory },
|
||||
];
|
||||
|
||||
/**
|
||||
@ -58,6 +79,8 @@ function TrackTabs({ track, variant }: { track?: PublicTrack; variant: 'bar' | '
|
||||
>
|
||||
{TRACK_TABS.map((it, i) => {
|
||||
const isActive = it.key === track;
|
||||
const Icon = it.icon;
|
||||
const label = t(it.i18n);
|
||||
return (
|
||||
<Link
|
||||
key={it.key}
|
||||
@ -69,10 +92,13 @@ function TrackTabs({ track, variant }: { track?: PublicTrack; variant: 'bar' | '
|
||||
className={`kxp-tracktab${isActive ? ' is-active' : ''}`}
|
||||
aria-selected={isActive}
|
||||
aria-current={isActive ? 'page' : undefined}
|
||||
aria-label={label}
|
||||
title={label}
|
||||
tabIndex={i === baseIdx ? 0 : -1}
|
||||
onKeyDown={(e) => onKeyDown(e, i)}
|
||||
>
|
||||
{t(it.i18n)}
|
||||
<Icon className="kxp-tracktab__ic" aria-hidden />
|
||||
{variant === 'menu' && <span className="kxp-tracktab__label">{label}</span>}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
@ -120,10 +146,6 @@ export function PublicShell({
|
||||
<Link className="kxp-brand" to="/" aria-label={t('shell.homeAria', { brand })}>
|
||||
{brand}
|
||||
</Link>
|
||||
<TrackTabs track={track} variant="bar" />
|
||||
</div>
|
||||
|
||||
<div className="kxp-header__right">
|
||||
<nav className="kxp-gnb" aria-label={t('shell.primaryMenu')}>
|
||||
{GNB.map((it) => (
|
||||
<a
|
||||
@ -136,6 +158,10 @@ export function PublicShell({
|
||||
</a>
|
||||
))}
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<div className="kxp-header__right">
|
||||
<TrackTabs track={track} variant="bar" />
|
||||
|
||||
{search && (
|
||||
<div className="kxp-search" role="search">
|
||||
|
||||
@ -46,7 +46,8 @@ type Ico = ComponentType<SVGProps<SVGSVGElement>>;
|
||||
const AI_TILES: { key: string; icon: Ico; to: string; soon?: boolean }[] = [
|
||||
{ key: 'assistant', icon: IconSparkles, to: '#hero' },
|
||||
{ key: 'recommend', icon: IconTarget, to: '#recommend' },
|
||||
{ key: 'wayfinding', icon: IconMap, to: '/login' },
|
||||
// 실내 길찾기는 로그인/앱 기능 → 공개 방문가이드 섹션으로 안내(로그인 유도 금지).
|
||||
{ key: 'wayfinding', icon: IconMap, to: '#guide' },
|
||||
{ key: 'schedule', icon: IconCalendar, to: '#briefing' },
|
||||
{ key: 'crowd', icon: IconCloud, to: '#briefing', soon: true },
|
||||
{ key: 'render', icon: IconRobot, to: '/business', soon: true },
|
||||
@ -56,7 +57,8 @@ const AI_TILES: { key: string; icon: Ico; to: string; soon?: boolean }[] = [
|
||||
const GUIDE_CARDS: { key: string; icon: Ico; to: string }[] = [
|
||||
{ key: 'transport', icon: IconTrain, to: '#transport' },
|
||||
{ key: 'parking', icon: IconParking, to: '#transport' },
|
||||
{ key: 'wayfinding', icon: IconMap, to: '/login' },
|
||||
// 길찾기는 공개 교통·오시는 길 안내로 대체(로그인 유도 금지).
|
||||
{ key: 'wayfinding', icon: IconMap, to: '#transport' },
|
||||
{ key: 'amenities', icon: IconInfo, to: '#guide' },
|
||||
{ key: 'accessibility', icon: IconShield, to: '#guide' },
|
||||
{ key: 'inquiry', icon: IconChat, to: '/public/exhibit-inquiry' },
|
||||
@ -65,11 +67,12 @@ const GUIDE_CARDS: { key: string; icon: Ico; to: string }[] = [
|
||||
/** 관심사 추천 칩(정적 카테고리 라벨). */
|
||||
const INTEREST_KEYS = ['robot', 'smartfactory', 'healthcare', 'eco', 'mobility', 'content'] as const;
|
||||
|
||||
/** 관람객 여정 3-스텝. */
|
||||
const JOURNEY: { key: string; icon: Ico; to: string }[] = [
|
||||
/** 관람객 여정 3-스텝. `linkKey`=기본 link 라벨 대체(로그인 유도 라벨 제거 시). */
|
||||
const JOURNEY: { key: string; icon: Ico; to: string; linkKey?: string }[] = [
|
||||
{ key: 'register', icon: IconTicket, to: '#showcase' },
|
||||
{ key: 'badge', icon: IconQr, to: '/tickets/lookup' },
|
||||
{ key: 'checkin', icon: IconCheckCircle, to: '/login' },
|
||||
// 현장 체크인은 로그인 대신 공개 입장권/QR 조회로 안내(로그인 유도 금지).
|
||||
{ key: 'checkin', icon: IconCheckCircle, to: '/tickets/lookup', linkKey: 'track.visitor.guideMore' },
|
||||
];
|
||||
|
||||
function LinkOrAnchor({
|
||||
@ -351,7 +354,7 @@ export function VisitorTrackPage() {
|
||||
</div>
|
||||
</div>
|
||||
<ol className="kxp-journey pub-reveal pub-reveal--stagger">
|
||||
{JOURNEY.map(({ key, icon: Icon, to }, i) => (
|
||||
{JOURNEY.map(({ key, icon: Icon, to, linkKey }, i) => (
|
||||
<li key={key} className="kxp-journey__step" style={{ '--i': i } as React.CSSProperties}>
|
||||
<span className="kxp-journey__num" aria-hidden>
|
||||
{i + 1}
|
||||
@ -363,7 +366,8 @@ export function VisitorTrackPage() {
|
||||
<div className="kxp-journey__title">{t(`track.visitor.journey.${key}.title`)}</div>
|
||||
<p className="kxp-journey__desc">{t(`track.visitor.journey.${key}.desc`)}</p>
|
||||
<LinkOrAnchor to={to} className="kxp-guide__link">
|
||||
{t(`track.visitor.journey.${key}.link`)} <IconChevronRight width={15} height={15} />
|
||||
{t(linkKey ?? `track.visitor.journey.${key}.link`)}{' '}
|
||||
<IconChevronRight width={15} height={15} />
|
||||
</LinkOrAnchor>
|
||||
</div>
|
||||
{i < JOURNEY.length - 1 && (
|
||||
|
||||
@ -384,14 +384,16 @@
|
||||
.kxp-header__burger {
|
||||
display: none;
|
||||
}
|
||||
/* 데스크톱(버거 숨김 시점)부터 로그인/내 홈은 항상 헤더 우측에 노출 —
|
||||
이전엔 1024px부터만 보여 768~1023 구간에서 로그인이 사라지던 결함 복구. */
|
||||
.kxp-header__login {
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
@media (min-width: 1024px) {
|
||||
.kxp-header__cta {
|
||||
display: inline-flex;
|
||||
}
|
||||
.kxp-header__login {
|
||||
display: inline-flex;
|
||||
}
|
||||
}
|
||||
|
||||
/* ═══ 메인 ═══ */
|
||||
@ -2805,9 +2807,10 @@
|
||||
* 신규 토큰 0 (기존 color·radius·kxp-maxw 변수 재사용). WCAG AA.
|
||||
* =================================================================== */
|
||||
|
||||
/* -- 3-트랙 탭 (단일 헤더 내부 흡수 · COEX 통합 top bar) --
|
||||
* 상단 프레임은 1개(.kxp-header)뿐. 별도 topbar 바 제거(이중 프레임 해소).
|
||||
* 라이트 헤더 배경 위 탭 스타일(하단 보더 활성). 모바일은 헤더에서 숨기고 버거 메뉴로. */
|
||||
/* -- 3-트랙 탭 (단일 헤더 우측 · 아이콘 + 툴팁) --
|
||||
* 상단 프레임은 1개(.kxp-header)뿐. 좌측=로고+GNB, 우측=트랙 아이콘 탭(로그인/언어 옆).
|
||||
* 텍스트 대신 선 SVG 아이콘 + title 툴팁/aria-label. 활성 트랙=배경 틴트+하단 보더.
|
||||
* 모바일은 헤더에서 숨기고 버거 메뉴에서 아이콘+텍스트로 노출. */
|
||||
.kxp-tracktabs {
|
||||
display: none;
|
||||
align-items: stretch;
|
||||
@ -2817,20 +2820,29 @@
|
||||
.kxp-tracktab {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 8px 12px;
|
||||
gap: 8px;
|
||||
padding: 8px 10px;
|
||||
font-size: var(--fs-body);
|
||||
font-weight: 600;
|
||||
color: var(--color-neutral-600);
|
||||
border-bottom: 2px solid transparent;
|
||||
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
|
||||
white-space: nowrap;
|
||||
transition: color 0.15s, border-color 0.15s;
|
||||
transition: color 0.15s, background 0.15s, border-color 0.15s;
|
||||
}
|
||||
.kxp-tracktab__ic {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.kxp-tracktab:hover {
|
||||
color: var(--color-primary-600);
|
||||
background: var(--color-primary-050);
|
||||
}
|
||||
.kxp-tracktab.is-active {
|
||||
color: var(--color-primary-700);
|
||||
font-weight: 800;
|
||||
background: var(--color-primary-050);
|
||||
border-bottom-color: var(--color-primary-600);
|
||||
}
|
||||
.kxp-tracktab:focus-visible {
|
||||
@ -3068,7 +3080,7 @@
|
||||
height: 24px;
|
||||
border-radius: 9999px;
|
||||
background: var(--color-primary-600, #0066b3);
|
||||
color: #fff;
|
||||
color: var(--color-on-accent);
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
@ -59,7 +59,7 @@
|
||||
}
|
||||
.kx-cv-switch button.is-active {
|
||||
background: var(--color-primary-600);
|
||||
color: #fff;
|
||||
color: var(--color-on-accent);
|
||||
}
|
||||
.kx-cv-muted {
|
||||
font-size: var(--fs-caption);
|
||||
|
||||
@ -309,7 +309,7 @@
|
||||
.kx-msg__rail-count {
|
||||
font-size: var(--fs-caption);
|
||||
background: var(--color-primary-600);
|
||||
color: #fff;
|
||||
color: var(--color-on-accent);
|
||||
border-radius: var(--radius-pill);
|
||||
padding: 0 7px;
|
||||
min-width: 18px;
|
||||
@ -479,7 +479,7 @@
|
||||
}
|
||||
.kx-cal__cell.is-today .kx-cal__daynum {
|
||||
background: var(--color-primary-600);
|
||||
color: #fff;
|
||||
color: var(--color-on-accent);
|
||||
border-radius: 50%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
@ -492,7 +492,7 @@
|
||||
line-height: 16px;
|
||||
padding: 1px 6px;
|
||||
border-radius: 4px;
|
||||
color: #fff;
|
||||
color: var(--color-on-accent);
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
@ -703,7 +703,7 @@
|
||||
.kx-chip.is-active {
|
||||
background: var(--color-primary-600);
|
||||
border-color: var(--color-primary-600);
|
||||
color: #fff;
|
||||
color: var(--color-on-accent);
|
||||
}
|
||||
.kx-noti__item {
|
||||
display: flex;
|
||||
@ -795,7 +795,7 @@
|
||||
height: 64px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-primary-600);
|
||||
color: #fff;
|
||||
color: var(--color-on-accent);
|
||||
font-size: 26px;
|
||||
font-weight: var(--fw-bold);
|
||||
display: flex;
|
||||
@ -817,7 +817,7 @@
|
||||
transform: translateX(-50%);
|
||||
z-index: 60;
|
||||
background: var(--color-neutral-900);
|
||||
color: #fff;
|
||||
color: var(--color-white);
|
||||
padding: 10px 18px;
|
||||
border-radius: var(--radius-lg);
|
||||
font-size: var(--fs-body);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user