Files
invyone/frontend/styles/v5-atomics.css
T
gbpark 68f85f3736 회사 관리 기능 확장 + 테넌트/비번 보안 하드닝
- 첫 로그인 비번 강제 변경 (RUN_082): FORCE_PASSWORD_CHANGE 컬럼,
  ForcePasswordChangeGuardFilter, /auth/change-password API + 페이지
- 테넌트 일관성 가드: TenantConsistencyGuardFilter 로 JWT.company_code
  ↔ 서브도메인 company_code 대조, CompanyResolver 가 (db_name, company_code)
  동시 반환
- 회사 관리 확장 (RUN_083 audit log, RUN_084 lifecycle 컬럼):
  CompanyAdmin/Members/Templates/Lifecycle/AuditLog 서비스 +
  CompanyMgmtController + SuperAdminGuard
- 회사 관리 UI: CompanyAccordionRow 탭화 + 모달 4종
  (AdminInfo/Deactivate/Delete/RecopyTemplates) + AuditLogDrawer + csvExport
- 프로비저닝 마법사: force_password_change 토글 반영
- 프론트 인증: storage 이벤트 멀티탭 동기화, 403 errorCode
  (PASSWORD_CHANGE_REQUIRED / CROSS_TENANT_REJECTED / TENANT_NOT_RESOLVED)
  전역 리다이렉트
- 기타: StartupSchemaMigrator, OS별 도커 기동 스크립트, CLAUDE.md 트래킹

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 00:36:05 +09:00

656 lines
24 KiB
CSS

/* ===================================================================
INVYONE v5 — Atomic Component Library
Ported from INVYONE Design System (ui_kits/app + preview/*).
All classes use .v5- prefix to avoid shadcn/Tailwind collision.
Tokens defined in v5-layout.css :root / .dark.
Concept: "Solid + Glow" (no blur on main app).
=================================================================== */
/* ===================================================================
⚠️ DEPRECATION NOTICE (2026-04-22)
-------------------------------------------------------------------
NEW CODE MUST USE shadcn components from @/components/ui/*:
<Button>, <Input>, <Table>, <Dialog>, <Badge>, <Select>, ...
The .v5-* classes below are LEGACY and kept only for:
- Atomic patterns inside v5-only prototypes
- Gradual migration of existing code (see grep for .v5-btn / .v5-bdg /
.v5-tbl / .v5-modal / .v5-fi usages — currently < 20 hits)
DO NOT reference .v5-* classes in new components.
Prefer shadcn ui primitives which wrap Radix + semantic tokens.
=================================================================== */
/* =================================================================
Buttons (.v5-btn + variants)
Defaults: 30px height, 0.7rem text, primary fills, glow on hover.
================================================================= */
.v5-btn {
display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
height: 34px; padding: 0 var(--v5-sp-4);
font-family: var(--v5-font-sans);
font-size: .8125rem; font-weight: var(--v5-fw-semi);
border: 1px solid transparent; border-radius: var(--v5-radius-md);
background: transparent; color: var(--v5-text);
cursor: pointer; user-select: none; white-space: nowrap;
transition: background .2s var(--v5-ease-move),
border-color .2s var(--v5-ease-move),
color .2s var(--v5-ease-move),
box-shadow .2s var(--v5-ease-move),
transform .12s var(--v5-ease-move),
opacity .2s var(--v5-ease-move);
}
.v5-btn svg { width: 14px; height: 14px; stroke-width: 1.75; flex-shrink: 0; }
.v5-btn:disabled { opacity: .4; cursor: not-allowed; }
.v5-btn:active:not(:disabled) { transform: scale(.98); }
/* primary — filled accent */
.v5-btn.primary {
background: var(--v5-primary); color: #fff;
border-color: var(--v5-primary);
}
.v5-btn.primary:hover:not(:disabled) {
opacity: .92; box-shadow: var(--v5-glow-sm); transform: translateY(-1px);
}
/* secondary — solid surface, neutral */
.v5-btn.secondary {
background: var(--v5-surface-solid); color: var(--v5-text);
border-color: var(--v5-border);
}
.v5-btn.secondary:hover:not(:disabled) {
background: var(--v5-surface-hover); border-color: rgba(var(--v5-primary-rgb), .25);
}
/* ghost — text-only, no fill */
.v5-btn.ghost {
background: transparent; color: var(--v5-text-sec); border-color: transparent;
}
.v5-btn.ghost:hover:not(:disabled) {
background: var(--v5-surface-hover); color: var(--v5-text);
}
/* danger — destructive action */
.v5-btn.danger {
background: var(--v5-red); color: #fff; border-color: var(--v5-red);
}
.v5-btn.danger:hover:not(:disabled) {
opacity: .92; box-shadow: var(--v5-glow-danger); transform: translateY(-1px);
}
/* size: sm — compact 28px */
.v5-btn.sm {
height: 28px; padding: 0 var(--v5-sp-3);
font-size: .75rem; border-radius: var(--v5-radius-sm);
}
.v5-btn.sm svg { width: 12px; height: 12px; }
/* size: lg — prominent 40px */
.v5-btn.lg {
height: 40px; padding: 0 var(--v5-sp-5);
font-size: .875rem; border-radius: var(--v5-radius-md-2);
}
.v5-btn.lg svg { width: 16px; height: 16px; }
/* focus ring (keyboard) */
.v5-btn:focus-visible {
outline: none;
border-color: var(--v5-primary);
box-shadow: 0 0 0 3px rgba(var(--v5-primary-rgb), .15), var(--v5-glow-sm);
}
/* =================================================================
Badges (.v5-bdg + status variants)
Pill, tiny caps, status-tinted.
================================================================= */
.v5-bdg {
display: inline-flex; align-items: center; gap: .35rem;
padding: .22rem .55rem;
font-size: .72rem; font-weight: var(--v5-fw-semi);
border-radius: var(--v5-radius-pill);
background: var(--v5-bg-subtle); color: var(--v5-text-sec);
white-space: nowrap;
line-height: 1.2;
}
.v5-bdg .v5-bdg-dot {
width: 5px; height: 5px; border-radius: 50%;
background: currentColor; flex-shrink: 0;
}
.v5-bdg.in { background: rgba(var(--v5-primary-rgb), .10); color: var(--v5-primary); }
.v5-bdg.ok { background: rgba(var(--v5-green-rgb), .12); color: rgb(var(--v5-green-rgb)); }
.v5-bdg.warn { background: rgba(var(--v5-amber-rgb), .22); color: #8a5a00; }
.v5-bdg.err { background: rgba(var(--v5-red-rgb), .10); color: rgb(var(--v5-red-rgb)); }
.v5-bdg.off { background: var(--v5-bg-subtle); color: var(--v5-text-sec); }
.v5-bdg.cy { background: rgba(var(--v5-cyan-rgb), .12); color: rgb(var(--v5-cyan-rgb)); }
.dark .v5-bdg.warn { color: rgb(var(--v5-amber-rgb)); }
/* =================================================================
Cards (.v5-card + .glow / .elev)
Solid surface, primary border, optional glow.
================================================================= */
.v5-card {
background: var(--v5-surface-solid);
border: 1px solid var(--v5-border);
border-radius: var(--v5-radius-md-2);
padding: var(--v5-sp-4);
transition: border-color .2s var(--v5-ease-move), box-shadow .3s var(--v5-ease-move);
}
.v5-card.glow { box-shadow: var(--v5-glow-sm); }
.v5-card.elev { box-shadow: var(--v5-glow-lg); }
.v5-card:hover { border-color: rgba(var(--v5-primary-rgb), .22); }
.v5-card-head {
display: flex; align-items: flex-start; justify-content: space-between;
gap: .6rem; margin-bottom: .4rem;
}
.v5-card-title {
font-size: var(--v5-fs-body-sm); font-weight: 700;
color: var(--v5-text);
line-height: 1.3;
}
/* =================================================================
Page Head (.v5-page-head)
crumbs / title / sub / actions(우정렬, max 3)
================================================================= */
.v5-page-head {
display: flex; align-items: flex-start; justify-content: space-between;
gap: 1rem; margin-bottom: var(--v5-sp-5);
}
.v5-page-head-l { min-width: 0; flex: 1; }
.v5-page-head-r { display: flex; align-items: center; gap: .4rem; flex-shrink: 0; }
.v5-crumbs {
font-size: .72rem; color: var(--v5-text-muted);
display: flex; align-items: center; gap: .4rem;
margin-bottom: .3rem;
}
.v5-crumbs .sep { opacity: .5; }
.v5-page-title {
font-size: var(--v5-fs-h1);
font-weight: 800; letter-spacing: var(--v5-ls-tight);
line-height: var(--v5-lh-tight);
color: var(--v5-text);
}
.v5-page-sub {
font-size: var(--v5-fs-body-sm); color: var(--v5-text-muted);
margin-top: .25rem;
}
/* =================================================================
Tables (.v5-tbl wrapped in .v5-table-wrap)
================================================================= */
.v5-table-wrap {
border: 1px solid var(--v5-border);
border-radius: var(--v5-radius-md-2);
overflow: hidden;
background: var(--v5-surface-solid);
}
.v5-tbl {
width: 100%; border-collapse: collapse;
font-size: var(--v5-fs-body-sm);
color: var(--v5-text);
}
.v5-tbl thead th {
text-align: left;
padding: .7rem var(--v5-sp-4);
font-size: var(--v5-fs-caption-lg);
font-weight: 600;
color: var(--v5-text-sec);
background: var(--v5-bg-subtle);
border-bottom: 1px solid var(--v5-border);
white-space: nowrap;
}
.v5-tbl tbody td {
padding: .65rem var(--v5-sp-4);
border-bottom: 1px solid var(--v5-border-subtle);
vertical-align: middle;
}
.v5-tbl tbody tr:last-child td { border-bottom: none; }
.v5-tbl tbody tr:hover td { background: rgba(var(--v5-primary-rgb), .04); }
.v5-tbl .mono {
font-family: var(--v5-font-mono);
font-size: .68rem;
color: var(--v5-text-sec);
font-variant-numeric: tabular-nums;
}
.v5-tbl .num {
text-align: right;
font-variant-numeric: tabular-nums;
}
/* =================================================================
KPI (.v5-kpi-num + .v5-kpi-delta + .v5-kpi-sub)
Large number with trend pill underneath.
================================================================= */
.v5-kpi-num {
font-size: var(--v5-fs-display); font-weight: 800;
letter-spacing: -0.03em;
font-variant-numeric: tabular-nums;
line-height: 1.05;
color: var(--v5-text);
display: flex; align-items: baseline; gap: .6rem;
}
.v5-kpi-num.kpi-cyan { color: rgb(var(--v5-cyan-rgb)); }
.v5-kpi-num.kpi-green { color: rgb(var(--v5-green-rgb)); }
.v5-kpi-num.kpi-pink { color: rgb(var(--v5-pink-rgb)); }
.v5-kpi-num.kpi-amber { color: rgb(var(--v5-amber-rgb)); }
.v5-kpi-delta {
display: inline-flex; align-items: center; gap: .2rem;
font-size: .75rem; font-weight: var(--v5-fw-bold);
padding: .18rem .5rem; border-radius: var(--v5-radius-sm);
}
.v5-kpi-delta.up { background: rgba(var(--v5-green-rgb), .12); color: rgb(var(--v5-green-rgb)); }
.v5-kpi-delta.down { background: rgba(var(--v5-red-rgb), .12); color: rgb(var(--v5-red-rgb)); }
.v5-kpi-delta svg { width: 11px; height: 11px; stroke-width: 2; }
.v5-kpi-sub {
font-size: .75rem;
color: var(--v5-text-muted);
margin-top: .3rem;
}
/* =================================================================
Bar chart (.v5-bars / .v5-bar / .v5-bars-ax)
================================================================= */
.v5-bars {
display: flex; gap: 4px; height: 80px; align-items: flex-end;
padding: .3rem 0;
}
.v5-bar {
flex: 1; border-radius: 3px 3px 0 0;
background: linear-gradient(180deg, rgba(var(--v5-primary-rgb), .55), rgba(var(--v5-primary-rgb), .18));
transition: background .25s var(--v5-ease-move);
min-height: 2px;
}
.v5-bar:hover {
background: linear-gradient(180deg, var(--v5-primary), rgba(var(--v5-primary-rgb), .4));
}
.v5-bars-ax {
display: flex; gap: 4px; margin-top: .35rem;
font-size: .52rem; color: var(--v5-text-muted);
font-family: var(--v5-font-mono);
}
.v5-bars-ax > * { flex: 1; text-align: center; }
/* =================================================================
Activity Feed (.v5-feed / .v5-feed-row / .v5-feed-dot / .v5-feed-txt)
================================================================= */
.v5-feed {
display: flex; flex-direction: column; gap: .55rem;
}
.v5-feed-row {
display: flex; gap: .6rem; align-items: flex-start;
padding: .35rem .15rem; border-radius: var(--v5-radius-sm);
transition: background .2s var(--v5-ease-move);
}
.v5-feed-row:hover { background: var(--v5-surface-hover); }
.v5-feed-dot {
width: 28px; height: 28px; border-radius: var(--v5-radius-sm);
display: flex; align-items: center; justify-content: center;
background: rgba(var(--v5-primary-rgb), .12);
color: var(--v5-primary); flex-shrink: 0;
}
.v5-feed-dot.g { background: rgba(var(--v5-green-rgb), .12); color: rgb(var(--v5-green-rgb)); }
.v5-feed-dot.a { background: rgba(var(--v5-amber-rgb), .18); color: rgb(var(--v5-amber-rgb)); }
.v5-feed-dot.c { background: rgba(var(--v5-cyan-rgb), .12); color: rgb(var(--v5-cyan-rgb)); }
.v5-feed-dot.r { background: rgba(var(--v5-red-rgb), .12); color: rgb(var(--v5-red-rgb)); }
.v5-feed-dot svg { width: 14px; height: 14px; stroke-width: 1.75; }
.v5-feed-txt {
flex: 1; min-width: 0;
font-size: var(--v5-fs-body-sm);
color: var(--v5-text);
line-height: 1.4;
}
.v5-feed-txt b { color: var(--v5-text); font-weight: var(--v5-fw-semi); }
.v5-feed-txt .tm {
display: block;
font-size: .72rem; color: var(--v5-text-muted);
font-family: var(--v5-font-mono);
margin-top: .2rem;
}
/* =================================================================
Sparkline wrapper (.v5-spark)
================================================================= */
.v5-spark { display: block; width: 100%; height: 36px; }
/* =================================================================
Grid layouts (.v5-grid + .grid-2 / 3 / 4)
================================================================= */
.v5-grid { display: grid; gap: var(--v5-sp-4); }
.v5-grid.grid-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.v5-grid.grid-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.v5-grid.grid-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
@media (max-width: 1024px) {
.v5-grid.grid-4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
.v5-grid.grid-3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
.v5-grid.grid-2,
.v5-grid.grid-3,
.v5-grid.grid-4 { grid-template-columns: 1fr; }
}
/* =================================================================
Form input wrapper (.v5-fi) + bare input (.v5-input)
================================================================= */
.v5-fi {
position: relative; display: inline-flex; align-items: center;
width: 100%;
}
.v5-fi > svg, .v5-fi > .v5-fi-icon {
position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
width: 14px; height: 14px; stroke-width: 1.75;
color: var(--v5-text-muted); pointer-events: none;
transition: color .2s var(--v5-ease-move);
}
.v5-fi:focus-within > svg, .v5-fi:focus-within > .v5-fi-icon { color: var(--v5-primary); }
.v5-input,
.v5-fi > input,
.v5-fi > select {
height: 32px; width: 100%;
padding: 0 var(--v5-sp-3) 0 2rem;
font-family: var(--v5-font-sans);
font-size: var(--v5-fs-body-sm);
color: var(--v5-text);
background: var(--v5-surface-solid);
border: 1px solid var(--v5-border);
border-radius: var(--v5-radius-md);
outline: none;
transition: border-color .2s var(--v5-ease-move), box-shadow .2s var(--v5-ease-move);
}
.v5-input:focus,
.v5-fi > input:focus,
.v5-fi > select:focus {
border-color: var(--v5-primary);
box-shadow: 0 0 0 3px rgba(var(--v5-primary-rgb), .15), var(--v5-glow-sm);
}
.v5-input::placeholder,
.v5-fi > input::placeholder { color: var(--v5-text-muted); }
/* When wrapper has no leading icon, remove the left padding. */
.v5-fi.no-icon > input,
.v5-fi.no-icon > select { padding-left: var(--v5-sp-3); }
/* Checkbox / radio */
.v5-check, .v5-radio {
width: 14px; height: 14px;
accent-color: var(--v5-primary);
cursor: pointer;
}
/* Toggle switch (.v5-toggle) */
.v5-toggle {
display: inline-block; position: relative;
width: 30px; height: 16px;
background: var(--v5-border); border-radius: var(--v5-radius-pill);
cursor: pointer; transition: background .25s var(--v5-ease-move);
}
.v5-toggle::after {
content: ''; position: absolute; left: 2px; top: 2px;
width: 12px; height: 12px; background: #fff; border-radius: 50%;
transition: transform .25s var(--v5-ease-move);
box-shadow: 0 1px 2px rgba(0, 0, 0, .15);
}
.v5-toggle.on { background: var(--v5-primary); }
.v5-toggle.on::after { transform: translateX(14px); }
/* =================================================================
Modal (.v5-overlay + .v5-modal)
Solid surface, glow shadow, scale-in animation.
================================================================= */
.v5-overlay {
position: fixed; inset: 0; z-index: 100;
background: rgba(6, 5, 14, .45);
display: flex; align-items: center; justify-content: center;
padding: 1rem;
animation: v5-overlay-in .22s var(--v5-ease-enter);
}
.v5-modal {
width: 100%; max-width: 560px;
background: var(--v5-surface-solid);
border: 1px solid var(--v5-border);
border-radius: var(--v5-radius-lg-2);
box-shadow: var(--v5-glow-lg);
padding: var(--v5-sp-5);
animation: v5-modal-in .3s var(--v5-ease-enter);
}
.v5-modal.sm { max-width: 420px; }
.v5-modal.lg { max-width: 720px; }
.v5-modal.xl { max-width: 960px; }
.v5-modal-head {
display: flex; align-items: flex-start; justify-content: space-between;
gap: .6rem; margin-bottom: .7rem;
}
.v5-modal-title {
font-size: var(--v5-fs-h2);
font-weight: var(--v5-fw-bold);
letter-spacing: var(--v5-ls-tight);
color: var(--v5-text);
}
.v5-modal-body {
font-size: var(--v5-fs-body);
color: var(--v5-text-sec);
line-height: 1.55;
margin-bottom: 1.1rem;
}
.v5-modal-body b { color: var(--v5-text); font-weight: var(--v5-fw-bold); }
.v5-modal-foot {
display: flex; justify-content: flex-end; gap: .4rem;
}
@keyframes v5-overlay-in {
from { opacity: 0; }
to { opacity: 1; }
}
@keyframes v5-modal-in {
from { opacity: 0; transform: scale(.97) translateY(8px); }
to { opacity: 1; transform: scale(1) translateY(0); }
}
/* =================================================================
Generic icon helper (.v5-ic) — keeps lucide aspect-locked.
================================================================= */
.v5-ic { display: inline-flex; flex-shrink: 0; }
.v5-ic svg { width: 100%; height: 100%; display: block; stroke-width: 1.75; }
/* =================================================================
TopNav (horizontal 메뉴바) — 디자인시스템 shell-components.jsx TopNav 포팅.
헤더 로고 오른쪽에 위치. 섹션 hover → flyout → sub-flyout.
================================================================= */
.v5-topnav{
display:flex;align-items:center;height:100%;
gap:.1rem;
font-family:var(--v5-font-sans);
}
.v5-tn-section{
position:relative;height:100%;display:flex;align-items:center;
animation:v5-tn-in .4s var(--v5-ease-enter) backwards;
}
@keyframes v5-tn-in{
from{opacity:0;transform:translateY(-4px);}
to {opacity:1;transform:translateY(0);}
}
.v5-tn-item{
display:inline-flex;align-items:center;gap:.3rem;
height:30px;padding:0 var(--v5-sp-4);
border:none;background:transparent;color:var(--v5-text-sec);
font-family:inherit;font-size:.78rem;font-weight:var(--v5-fw-semi);
letter-spacing:var(--v5-ls-tight);cursor:pointer;
border-radius:var(--v5-radius-md);
transition:background .2s var(--v5-ease-move),color .2s var(--v5-ease-move);
}
.v5-tn-item svg{opacity:.55;transition:opacity .2s var(--v5-ease-move),transform .2s var(--v5-ease-move);}
.v5-tn-section:hover .v5-tn-item,
.v5-tn-section.open .v5-tn-item{color:var(--v5-primary);}
.v5-tn-section:hover .v5-tn-item svg,
.v5-tn-section.open .v5-tn-item svg{opacity:1;}
/* open 상태에선 chevron 을 살짝 내려서(2px) "펼쳤다" 감만 주기 — 180° 회전은 flyout 방향과 역방향이라 혼동 유발 */
.v5-tn-section.open .v5-tn-item svg{transform:translateY(1px);}
.v5-tn-section.on .v5-tn-item{color:var(--v5-primary);}
.v5-admin-mode .v5-tn-section.on .v5-tn-item{color:var(--v5-primary);}
/* Flyout (1단) — 섹션과 border 가 맞닿게(top:100%) + 내부 top 패딩으로 시각 여백만 유지.
→ 2px 공백 구간에서 mouseleave 가 타서 flyout 이 순간 사라지던 문제 제거. */
.v5-tn-flyout{
position:absolute;top:100%;left:0;min-width:200px;
background:var(--v5-surface-solid);
border:1px solid var(--v5-border);
border-radius:var(--v5-radius-md);
padding:.6rem .3rem .3rem .3rem;z-index:40;
animation:v5-tn-flyout-in .2s var(--v5-ease-enter) backwards;
}
/* 섹션의 마지막 1px 과 flyout 의 첫 1px 를 겹쳐 "끊김" 제로. 시각적으론 boundary 가 안 보임(밝은 border 2중). */
.v5-tn-flyout::before{
content:'';position:absolute;left:0;right:0;top:-6px;height:6px;
background:transparent;pointer-events:auto;
}
@keyframes v5-tn-flyout-in{
from{opacity:0;transform:translateY(-6px);}
to {opacity:1;transform:translateY(0);}
}
/* Row (flyout 아이템) */
.v5-tn-row{
display:flex;align-items:center;gap:.45rem;
padding:.5rem .55rem;border-radius:var(--v5-radius-sm);
font-size:.74rem;font-weight:500;
color:var(--v5-text-sec);cursor:pointer;position:relative;
animation:v5-tn-row-in .28s var(--v5-ease-enter) backwards;
transition:
background .18s var(--v5-ease-move),
color .18s var(--v5-ease-move),
transform .18s var(--v5-ease-move);
white-space:nowrap;
}
@keyframes v5-tn-row-in{
from{opacity:0;transform:translateX(-6px);}
to {opacity:1;transform:translateX(0);}
}
.v5-tn-row:hover{background:var(--v5-surface-hover);color:var(--v5-text);transform:translateX(2px);}
.v5-tn-row.on{background:rgba(var(--v5-primary-rgb),.1);color:var(--v5-primary);font-weight:600;}
.v5-admin-mode .v5-tn-row.on{background:rgba(var(--v5-primary-rgb),.1);color:var(--v5-primary);}
.v5-tn-row .v5-tn-row-label{flex:1;min-width:0;}
.v5-tn-row .v5-tn-ic{display:inline-flex;width:14px;height:14px;color:currentColor;opacity:.7;}
.v5-tn-row svg:last-of-type{opacity:.5;flex-shrink:0;}
/* Badge (e.g., pending count) */
.v5-tn-badge{
display:inline-flex;align-items:center;justify-content:center;
min-width:16px;height:14px;padding:0 .3rem;
background:rgba(var(--v5-primary-rgb),.14);
color:var(--v5-primary);
font-size:.56rem;font-weight:var(--v5-fw-bold);
border-radius:var(--v5-radius-pill);
line-height:1;
}
/* Sub-flyout (2단) */
.v5-tn-sub{
position:absolute;left:calc(100% + 6px);top:0;min-width:200px;
background:var(--v5-surface-solid);
border:1px solid var(--v5-border);
border-radius:var(--v5-radius-md-2);
padding:.3rem;z-index:45;
animation:v5-tn-flyout-in .2s var(--v5-ease-enter) backwards;
}
/* =================================================================
Tweaks floating panel (디자인시스템 Tweaks UX).
우하단 240px 고정, 슬라이드 인/아웃. SettingsModal 이 사용.
================================================================= */
.v5-tweaks-panel{
position:fixed;right:14px;bottom:14px;
width:240px;z-index:200;
background:var(--v5-surface-solid);
border:1px solid var(--v5-border);
border-radius:var(--v5-radius-lg-2);
padding:var(--v5-sp-4);
font-family:var(--v5-font-sans);
opacity:0;transform:translateY(10px) scale(.97);pointer-events:none;
transition:
opacity .25s var(--v5-ease-enter),
transform .3s var(--v5-ease-enter);
}
.v5-tweaks-panel.on{
opacity:1;transform:translateY(0) scale(1);pointer-events:auto;
}
.v5-tweaks-head{
display:flex;align-items:center;justify-content:space-between;
font-size:var(--v5-fs-body);
font-weight:var(--v5-fw-bold);
letter-spacing:var(--v5-ls-tight);
color:var(--v5-text);
margin-bottom:var(--v5-sp-3);
}
.v5-tweaks-row{
display:flex;flex-direction:column;gap:.35rem;
padding:.4rem 0;
border-top:1px solid var(--v5-border-subtle);
}
.v5-tweaks-row:first-of-type{border-top:none;padding-top:0;}
.v5-tweaks-row label{
font-size:var(--v5-fs-caption);
font-weight:var(--v5-fw-bold);
text-transform:uppercase;
letter-spacing:var(--v5-ls-wide);
color:var(--v5-text-muted);
}
.v5-tweaks-swatches{
display:grid;grid-template-columns:repeat(6,1fr);gap:5px;
}
.v5-tweaks-swatch{
position:relative;width:100%;aspect-ratio:1/1;
border-radius:var(--v5-radius-sm);
border:2px solid transparent;
cursor:pointer;
display:inline-flex;align-items:center;justify-content:center;
color:#fff;
transition:
transform .15s var(--v5-ease-move),
border-color .2s var(--v5-ease-move),
box-shadow .2s var(--v5-ease-move);
}
.v5-tweaks-swatch:hover{transform:scale(1.08);}
.v5-tweaks-swatch.on{
border-color:var(--v5-text);
box-shadow:0 0 0 2px var(--v5-surface-solid), 0 0 0 3px currentColor, var(--v5-glow-sm);
}
.v5-tweaks-seg{
display:flex;gap:4px;
}
.v5-tweaks-seg .v5-btn{flex:1;justify-content:center;}
.v5-tweaks-foot{
font-size:.52rem;
color:var(--v5-text-muted);
margin-top:.6rem;
padding-top:.5rem;
border-top:1px solid var(--v5-border-subtle);
font-family:var(--v5-font-mono);
letter-spacing:.03em;
}
/* =================================================================
Mode line (cyan/primary gradient under the header).
Already styled in v5-layout.css for shell — this is the standalone
helper for any sub-shell that wants the same accent.
================================================================= */
.v5-mode-line {
position: absolute; left: 0; right: 0; bottom: -1px; height: 1px;
background: linear-gradient(90deg,
transparent,
var(--v5-primary),
var(--v5-cyan),
var(--v5-pink),
transparent);
opacity: 0; pointer-events: none;
transition: opacity .35s var(--v5-ease-move);
}
.v5-mode-line.on { opacity: 1; }