407da15e6d
- 타이포 스케일: body 12→14px, caption 9.6→12px, display 25.6→32px, 위계 강화 - 헤더 우측 3그룹화 (대시보드액션 | 테마/알림/설정 | 모드+프로필), v5-hdr-sep 구분자 추가 - 사이드바 SUPER_ADMIN 회사 카드 borderless slim 라벨로 압축 - 메뉴명 빈 텍스트 방어 + title 속성 추가 - 빈 대시보드(EmptyDashboard) 리디자인: 탭없음/위젯없음 2상태 분리, 2-CTA 카드 - 로그인 코스믹 공연 축소: 별 150→30, 파티클 20→0, 카피 한글화 (로그인 버튼/서브타이틀) - 모드 전환 burst/sweep/badge-zoom 제거, sidebar stagger morph만 유지 (handleModeSwitch 100→25줄) - View transitions duration 1800ms → 500ms Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
880 lines
31 KiB
CSS
880 lines
31 KiB
CSS
/* ===== 서명용 손글씨 폰트 (완전한 한글 지원 폰트) ===== */
|
|
@import url("https://fonts.googleapis.com/css2?family=Allura&family=Dancing+Script:wght@700&family=Great+Vibes&family=Pacifico&family=Satisfy&family=Caveat:wght@700&family=Permanent+Marker&family=Shadows+Into+Light&family=Kalam:wght@700&family=Patrick+Hand&family=Indie+Flower&family=Amatic+SC:wght@700&family=Covered+By+Your+Grace&family=Nanum+Brush+Script&family=Nanum+Pen+Script&family=Gaegu:wght@700&family=Hi+Melody&family=Gamja+Flower&family=Poor+Story&family=Do+Hyeon&family=Jua&display=swap");
|
|
|
|
/* ===== Tailwind CSS & Animations ===== */
|
|
@import "tailwindcss";
|
|
@import "tw-animate-css";
|
|
|
|
/* ===== V5 Solid + Glow Layout System ===== */
|
|
@import "../styles/v5-layout.css";
|
|
|
|
/* ===== V5 Atomic Component Library (btn / bdg / card / tbl / kpi / page-head / etc.) ===== */
|
|
@import "../styles/v5-atomics.css";
|
|
|
|
/* ===== Builder IDE Theme (ScreenDesigner 스코프 오버라이드) ===== */
|
|
@import "../styles/builder-ide.css";
|
|
|
|
/* ===== Dark Mode Variant ===== */
|
|
@custom-variant dark (&:is(.dark *));
|
|
|
|
/* ===== Tailwind Theme Extensions ===== */
|
|
@theme {
|
|
/* Color System - HSL Format (shadcn/ui Standard) */
|
|
--color-background: hsl(var(--background));
|
|
--color-foreground: hsl(var(--foreground));
|
|
--color-card: hsl(var(--card));
|
|
--color-card-foreground: hsl(var(--card-foreground));
|
|
--color-popover: hsl(var(--popover));
|
|
--color-popover-foreground: hsl(var(--popover-foreground));
|
|
--color-primary: hsl(var(--primary));
|
|
--color-primary-foreground: hsl(var(--primary-foreground));
|
|
--color-secondary: hsl(var(--secondary));
|
|
--color-secondary-foreground: hsl(var(--secondary-foreground));
|
|
--color-muted: hsl(var(--muted));
|
|
--color-muted-foreground: hsl(var(--muted-foreground));
|
|
--color-accent: hsl(var(--accent));
|
|
--color-accent-foreground: hsl(var(--accent-foreground));
|
|
--color-destructive: hsl(var(--destructive));
|
|
--color-destructive-foreground: hsl(var(--destructive-foreground));
|
|
--color-border: hsl(var(--border));
|
|
--color-input: hsl(var(--input));
|
|
--color-ring: hsl(var(--ring));
|
|
|
|
/* Success, Warning, Info Colors */
|
|
--color-success: hsl(var(--success));
|
|
--color-success-foreground: hsl(var(--success-foreground));
|
|
--color-warning: hsl(var(--warning));
|
|
--color-warning-foreground: hsl(var(--warning-foreground));
|
|
--color-info: hsl(var(--info));
|
|
--color-info-foreground: hsl(var(--info-foreground));
|
|
|
|
/* Chart Colors */
|
|
--color-chart-1: hsl(var(--chart-1));
|
|
--color-chart-2: hsl(var(--chart-2));
|
|
--color-chart-3: hsl(var(--chart-3));
|
|
--color-chart-4: hsl(var(--chart-4));
|
|
--color-chart-5: hsl(var(--chart-5));
|
|
|
|
/* Sidebar Colors */
|
|
--color-sidebar: hsl(var(--sidebar-background));
|
|
--color-sidebar-foreground: hsl(var(--sidebar-foreground));
|
|
--color-sidebar-primary: hsl(var(--sidebar-primary));
|
|
--color-sidebar-primary-foreground: hsl(var(--sidebar-primary-foreground));
|
|
--color-sidebar-accent: hsl(var(--sidebar-accent));
|
|
--color-sidebar-accent-foreground: hsl(var(--sidebar-accent-foreground));
|
|
--color-sidebar-border: hsl(var(--sidebar-border));
|
|
--color-sidebar-ring: hsl(var(--sidebar-ring));
|
|
|
|
/* Font Families */
|
|
--font-sans: var(--font-inter);
|
|
--font-mono: var(--font-jetbrains-mono);
|
|
--radius-sm: calc(var(--radius) - 4px);
|
|
--radius-md: calc(var(--radius) - 2px);
|
|
--radius-lg: var(--radius);
|
|
--radius-xl: calc(var(--radius) + 4px);
|
|
}
|
|
|
|
/* ===== CSS Variables (Cosmic Design System) ===== */
|
|
:root {
|
|
/* Light Theme: Stellar White — 맑은 은하, 미묘한 코스믹 톤 */
|
|
--background: 240 20% 99%;
|
|
--foreground: 240 10% 5%;
|
|
--card: 0 0% 100%;
|
|
--card-foreground: 240 10% 5%;
|
|
--popover: 0 0% 100%;
|
|
--popover-foreground: 240 10% 5%;
|
|
--primary: 245 75% 57%;
|
|
--primary-foreground: 0 0% 100%;
|
|
--secondary: 240 10% 95%;
|
|
--secondary-foreground: 240 20% 15%;
|
|
--muted: 240 10% 94%;
|
|
--muted-foreground: 240 5% 46%;
|
|
--accent: 240 10% 93%;
|
|
--accent-foreground: 240 20% 15%;
|
|
--destructive: 0 80% 60%;
|
|
--destructive-foreground: 0 0% 100%;
|
|
--border: 240 8% 88%;
|
|
--input: 240 8% 88%;
|
|
--ring: 245 75% 57%;
|
|
|
|
/* Success: Aurora Green */
|
|
--success: 152 72% 38%;
|
|
--success-foreground: 0 0% 100%;
|
|
|
|
/* Warning: Stellar Amber */
|
|
--warning: 38 95% 50%;
|
|
--warning-foreground: 0 0% 100%;
|
|
|
|
/* Info: Nebula Cyan */
|
|
--info: 195 90% 45%;
|
|
--info-foreground: 0 0% 100%;
|
|
|
|
/* Chart: Cosmic Palette */
|
|
--chart-1: 265 70% 62%;
|
|
--chart-2: 152 65% 42%;
|
|
--chart-3: 210 75% 55%;
|
|
--chart-4: 38 85% 58%;
|
|
--chart-5: 340 72% 58%;
|
|
|
|
/* Border Radius: Slightly rounder for modern cosmic feel */
|
|
--radius: 0.625rem;
|
|
|
|
/* Sidebar: Subtle cosmic tint */
|
|
--sidebar-background: 240 15% 97%;
|
|
--sidebar-foreground: 240 10% 5%;
|
|
--sidebar-primary: 245 75% 57%;
|
|
--sidebar-primary-foreground: 0 0% 100%;
|
|
--sidebar-accent: 240 10% 93%;
|
|
--sidebar-accent-foreground: 240 20% 15%;
|
|
--sidebar-border: 240 8% 90%;
|
|
--sidebar-ring: 245 75% 57%;
|
|
}
|
|
|
|
/* ===== Dark Theme (Deep Space) ===== */
|
|
.dark {
|
|
/* 배경: 딥 스페이스 — 보라빛 암흑 */
|
|
--background: 240 20% 3.5%;
|
|
--foreground: 240 10% 95%;
|
|
|
|
/* 카드: 성운 표면 — 배경보다 약간 밝은 우주 공간 */
|
|
--card: 240 18% 7%;
|
|
--card-foreground: 240 10% 95%;
|
|
|
|
/* 팝오버: 카드와 동일한 우주 공간 */
|
|
--popover: 240 18% 7%;
|
|
--popover-foreground: 240 10% 95%;
|
|
|
|
/* Primary: 코스믹 바이올렛-블루 — 성운 핵심 컬러 */
|
|
--primary: 245 85% 68%;
|
|
--primary-foreground: 0 0% 100%;
|
|
|
|
/* Secondary: 우주 먼지 — 깊은 슬레이트 */
|
|
--secondary: 240 15% 14%;
|
|
--secondary-foreground: 240 10% 90%;
|
|
|
|
/* Muted: 암흑 성운 — 차분한 보라빛 회색 */
|
|
--muted: 240 12% 12%;
|
|
--muted-foreground: 240 8% 58%;
|
|
|
|
/* Accent: 은하 표면 — secondary보다 약간 밝게 */
|
|
--accent: 240 15% 18%;
|
|
--accent-foreground: 240 10% 90%;
|
|
|
|
/* Destructive: 초신성 레드 */
|
|
--destructive: 0 75% 55%;
|
|
--destructive-foreground: 0 0% 100%;
|
|
|
|
/* Border: 스타더스트 라인 */
|
|
--border: 240 12% 15%;
|
|
--input: 240 12% 15%;
|
|
--ring: 245 85% 68%;
|
|
|
|
/* Success: 오로라 그린 */
|
|
--success: 152 70% 45%;
|
|
--success-foreground: 0 0% 100%;
|
|
|
|
/* Warning: 항성 앰버 */
|
|
--warning: 38 92% 58%;
|
|
--warning-foreground: 0 0% 10%;
|
|
|
|
/* Info: 성운 시안 */
|
|
--info: 195 85% 52%;
|
|
--info-foreground: 0 0% 100%;
|
|
|
|
/* Chart: Cosmic Neon Palette */
|
|
--chart-1: 265 80% 68%;
|
|
--chart-2: 152 70% 50%;
|
|
--chart-3: 210 80% 62%;
|
|
--chart-4: 38 90% 62%;
|
|
--chart-5: 340 78% 62%;
|
|
|
|
/* Sidebar: 이벤트 호라이즌 — 메인 배경보다 더 깊은 우주 */
|
|
--sidebar-background: 240 22% 5%;
|
|
--sidebar-foreground: 240 10% 90%;
|
|
--sidebar-primary: 245 85% 68%;
|
|
--sidebar-primary-foreground: 0 0% 100%;
|
|
--sidebar-accent: 240 15% 14%;
|
|
--sidebar-accent-foreground: 240 10% 90%;
|
|
--sidebar-border: 240 12% 13%;
|
|
--sidebar-ring: 245 85% 68%;
|
|
}
|
|
|
|
/* ===== Base Styles ===== */
|
|
* {
|
|
border-color: hsl(var(--border));
|
|
}
|
|
|
|
body {
|
|
color: hsl(var(--foreground));
|
|
background: hsl(var(--background));
|
|
}
|
|
|
|
/* Button 기본 커서 스타일 */
|
|
button {
|
|
cursor: pointer;
|
|
}
|
|
|
|
button:disabled {
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
/* ===== Dialog/Modal Overlay ===== */
|
|
/* Radix UI Dialog Overlay - 60% 불투명도 배경 */
|
|
[data-radix-dialog-overlay],
|
|
.fixed.inset-0.z-50.bg-black {
|
|
background-color: rgba(0, 0, 0, 0.6) !important;
|
|
backdrop-filter: none !important;
|
|
}
|
|
|
|
/* DialogPrimitive.Overlay 클래스 오버라이드 */
|
|
.fixed.inset-0.z-50 {
|
|
background-color: rgba(0, 0, 0, 0.6) !important;
|
|
backdrop-filter: none !important;
|
|
}
|
|
|
|
/* ===== Accessibility - Focus Styles ===== */
|
|
/* 모든 인터랙티브 요소에 대한 포커스 스타일 */
|
|
button:focus-visible,
|
|
a:focus-visible,
|
|
input:focus-visible,
|
|
textarea:focus-visible,
|
|
select:focus-visible {
|
|
outline: 2px solid hsl(var(--ring));
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
/* TableSearchWidget의 SelectTrigger 포커스 스타일 제거 */
|
|
[role="combobox"]:focus-visible {
|
|
outline: none !important;
|
|
box-shadow: none !important;
|
|
}
|
|
|
|
button[role="combobox"]:focus-visible {
|
|
outline: none !important;
|
|
box-shadow: none !important;
|
|
border-color: hsl(var(--input)) !important;
|
|
}
|
|
|
|
/* ===== Scrollbar Styles (Optional) ===== */
|
|
/* Webkit 기반 브라우저 (Chrome, Safari, Edge) */
|
|
::-webkit-scrollbar {
|
|
width: 10px;
|
|
height: 10px;
|
|
}
|
|
|
|
::-webkit-scrollbar-track {
|
|
background: hsl(var(--muted));
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb {
|
|
background: hsl(var(--muted-foreground) / 0.3);
|
|
border-radius: 5px;
|
|
}
|
|
|
|
::-webkit-scrollbar-thumb:hover {
|
|
background: hsl(var(--muted-foreground) / 0.5);
|
|
}
|
|
|
|
/* Firefox */
|
|
* {
|
|
scrollbar-width: thin;
|
|
scrollbar-color: hsl(var(--muted-foreground) / 0.3) hsl(var(--muted));
|
|
}
|
|
|
|
/* ===== Animation Utilities ===== */
|
|
/* Smooth transitions for interactive elements */
|
|
button,
|
|
a,
|
|
input,
|
|
textarea,
|
|
select {
|
|
transition-property:
|
|
color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, filter,
|
|
backdrop-filter;
|
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
transition-duration: 150ms;
|
|
}
|
|
|
|
/* 런타임 화면에서 컴포넌트 위치 변경 시 모든 애니메이션/트랜지션 완전 제거 */
|
|
[data-screen-runtime] [id^="component-"] {
|
|
transition: none !important;
|
|
}
|
|
[data-screen-runtime] [data-conditional-layer] {
|
|
transition: none !important;
|
|
}
|
|
|
|
/* Disable animations for users who prefer reduced motion */
|
|
@media (prefers-reduced-motion: reduce) {
|
|
*,
|
|
*::before,
|
|
*::after {
|
|
animation-duration: 0.01ms !important;
|
|
animation-iteration-count: 1 !important;
|
|
transition-duration: 0.01ms !important;
|
|
scroll-behavior: auto !important;
|
|
}
|
|
}
|
|
|
|
/* ===== Sonner Toast - B안 (하단 중앙 스낵바) ===== */
|
|
|
|
/* 기본 토스트: 다크 배경 스낵바 */
|
|
[data-sonner-toaster] [data-sonner-toast].sonner-toast-snackbar {
|
|
--normal-bg: hsl(222 30% 16%);
|
|
--normal-text: hsl(210 20% 92%);
|
|
--normal-border: hsl(222 20% 24%);
|
|
|
|
background: var(--normal-bg);
|
|
color: var(--normal-text);
|
|
border: 1px solid var(--normal-border);
|
|
border-radius: 10px;
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.25);
|
|
padding: 10px 16px;
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
gap: 10px;
|
|
}
|
|
|
|
/* 다크모드 토스트 */
|
|
.dark [data-sonner-toaster] [data-sonner-toast].sonner-toast-snackbar {
|
|
--normal-bg: hsl(220 25% 14%);
|
|
--normal-border: hsl(220 20% 22%);
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
|
|
}
|
|
|
|
/* 성공 토스트 - 좌측 초록 바 */
|
|
[data-sonner-toaster] [data-sonner-toast].sonner-toast-success {
|
|
--success-bg: hsl(222 30% 16%);
|
|
--success-text: hsl(210 20% 92%);
|
|
--success-border: hsl(222 20% 24%);
|
|
|
|
background: var(--success-bg) !important;
|
|
color: var(--success-text) !important;
|
|
border: 1px solid var(--success-border) !important;
|
|
border-left: 3px solid hsl(142 76% 42%) !important;
|
|
border-radius: 10px;
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.25);
|
|
}
|
|
|
|
.dark [data-sonner-toaster] [data-sonner-toast].sonner-toast-success {
|
|
--success-bg: hsl(220 25% 14%) !important;
|
|
--success-border: hsl(220 20% 22%) !important;
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
|
|
}
|
|
|
|
/* 에러 토스트 - 좌측 빨간 바 + 약간 붉은 배경 */
|
|
[data-sonner-toaster] [data-sonner-toast].sonner-toast-error {
|
|
--error-bg: hsl(0 30% 14%);
|
|
--error-text: hsl(0 20% 92%);
|
|
--error-border: hsl(0 20% 22%);
|
|
|
|
background: var(--error-bg) !important;
|
|
color: var(--error-text) !important;
|
|
border: 1px solid var(--error-border) !important;
|
|
border-left: 3px solid hsl(0 72% 55%) !important;
|
|
border-radius: 10px;
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.25);
|
|
}
|
|
|
|
.dark [data-sonner-toaster] [data-sonner-toast].sonner-toast-error {
|
|
--error-bg: hsl(0 25% 10%) !important;
|
|
--error-border: hsl(0 20% 18%) !important;
|
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
|
|
}
|
|
|
|
/* 경고 토스트 - 좌측 노란 바 */
|
|
[data-sonner-toaster] [data-sonner-toast].sonner-toast-warning {
|
|
background: hsl(222 30% 16%) !important;
|
|
color: hsl(210 20% 92%) !important;
|
|
border: 1px solid hsl(222 20% 24%) !important;
|
|
border-left: 3px solid hsl(38 92% 50%) !important;
|
|
border-radius: 10px;
|
|
}
|
|
|
|
/* info 토스트 - 좌측 파란 바 */
|
|
[data-sonner-toaster] [data-sonner-toast].sonner-toast-info {
|
|
background: hsl(222 30% 16%) !important;
|
|
color: hsl(210 20% 92%) !important;
|
|
border: 1px solid hsl(222 20% 24%) !important;
|
|
border-left: 3px solid hsl(217 91% 60%) !important;
|
|
border-radius: 10px;
|
|
}
|
|
|
|
/* 토스트 내부 설명 텍스트 */
|
|
[data-sonner-toaster] [data-sonner-toast] [data-description] {
|
|
color: hsl(210 15% 70%) !important;
|
|
font-size: 12px !important;
|
|
}
|
|
|
|
/* 토스트 닫기 버튼: 토스트 안쪽 우측 상단 배치 */
|
|
[data-sonner-toaster] [data-sonner-toast] [data-close-button] {
|
|
position: absolute !important;
|
|
top: 50% !important;
|
|
right: 8px !important;
|
|
left: auto !important;
|
|
transform: translateY(-50%) !important;
|
|
width: 20px !important;
|
|
height: 20px !important;
|
|
background: transparent !important;
|
|
border: none !important;
|
|
border-radius: 4px !important;
|
|
color: hsl(210 15% 55%) !important;
|
|
opacity: 0.6;
|
|
transition: opacity 0.15s, background 0.15s;
|
|
}
|
|
[data-sonner-toaster] [data-sonner-toast] [data-close-button]:hover {
|
|
background: hsl(220 20% 24%) !important;
|
|
color: hsl(210 20% 85%) !important;
|
|
opacity: 1;
|
|
}
|
|
|
|
/* 토스트 액션 버튼 */
|
|
[data-sonner-toaster] [data-sonner-toast] [data-button] {
|
|
color: hsl(217 91% 68%) !important;
|
|
font-weight: 700;
|
|
font-size: 12px;
|
|
}
|
|
|
|
/* 애니메이션 제어: 부드러운 슬라이드 업만 허용, 나머지 제거 */
|
|
[data-sonner-toaster] [data-sonner-toast][data-removed="true"] {
|
|
animation: none !important;
|
|
}
|
|
|
|
/* ===== Print Styles ===== */
|
|
@media print {
|
|
* {
|
|
background: transparent !important;
|
|
color: black !important;
|
|
box-shadow: none !important;
|
|
text-shadow: none !important;
|
|
}
|
|
|
|
a,
|
|
a:visited {
|
|
text-decoration: underline;
|
|
}
|
|
|
|
a[href]::after {
|
|
content: " (" attr(href) ")";
|
|
}
|
|
|
|
img {
|
|
max-width: 100% !important;
|
|
}
|
|
|
|
@page {
|
|
margin: 2cm;
|
|
}
|
|
|
|
p,
|
|
h2,
|
|
h3 {
|
|
orphans: 3;
|
|
widows: 3;
|
|
}
|
|
|
|
h2,
|
|
h3 {
|
|
page-break-after: avoid;
|
|
}
|
|
}
|
|
|
|
/* ===== Custom Utilities (Project-Specific) ===== */
|
|
/* 손글씨 폰트 클래스 */
|
|
.font-handwriting {
|
|
font-family: "Allura", cursive;
|
|
}
|
|
|
|
.font-dancing-script {
|
|
font-family: "Dancing Script", cursive;
|
|
}
|
|
|
|
.font-great-vibes {
|
|
font-family: "Great Vibes", cursive;
|
|
}
|
|
|
|
.font-pacifico {
|
|
font-family: "Pacifico", cursive;
|
|
}
|
|
|
|
.font-satisfy {
|
|
font-family: "Satisfy", cursive;
|
|
}
|
|
|
|
.font-caveat {
|
|
font-family: "Caveat", cursive;
|
|
}
|
|
|
|
/* 한글 손글씨 폰트 */
|
|
.font-nanum-brush {
|
|
font-family: "Nanum Brush Script", cursive;
|
|
}
|
|
|
|
.font-nanum-pen {
|
|
font-family: "Nanum Pen Script", cursive;
|
|
}
|
|
|
|
.font-gaegu {
|
|
font-family: "Gaegu", cursive;
|
|
}
|
|
|
|
/* ===== Component-Specific Overrides ===== */
|
|
/* 필요시 특정 컴포넌트에 대한 스타일 오버라이드를 여기에 추가 */
|
|
/* 예: Calendar, Table 등의 미세 조정 */
|
|
|
|
/* 테이블 레이아웃 고정 (셀 내용이 영역을 벗어나지 않도록) */
|
|
.table-mobile-fixed {
|
|
table-layout: fixed;
|
|
}
|
|
|
|
/* 그리드선 숨기기 */
|
|
.hide-grid td,
|
|
.hide-grid th {
|
|
border: none !important;
|
|
}
|
|
|
|
.hide-grid {
|
|
border-collapse: separate !important;
|
|
border-spacing: 0 !important;
|
|
}
|
|
|
|
/* ===== 카드 펄스 도트 애니메이션 ===== */
|
|
@keyframes screen-card-pulse {
|
|
0%, 100% { opacity: 0; transform: scale(1); }
|
|
50% { opacity: 0.35; transform: scale(2); }
|
|
}
|
|
.screen-card-pulse-dot::after {
|
|
content: '';
|
|
position: absolute;
|
|
inset: -3px;
|
|
border-radius: 50%;
|
|
background: hsl(var(--success));
|
|
opacity: 0;
|
|
animation: screen-card-pulse 2.5s ease-in-out infinite;
|
|
}
|
|
|
|
/* ===== 저장 테이블 막대기 애니메이션 ===== */
|
|
@keyframes saveBarDrop {
|
|
0% {
|
|
transform: scaleY(0);
|
|
transform-origin: top;
|
|
opacity: 0;
|
|
}
|
|
100% {
|
|
transform: scaleY(1);
|
|
transform-origin: top;
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
/* ===== 모달 필수 입력 검증 ===== */
|
|
@keyframes validationShake {
|
|
0%, 100% { transform: translateX(0); }
|
|
20%, 60% { transform: translateX(-4px); }
|
|
40%, 80% { transform: translateX(4px); }
|
|
}
|
|
|
|
/* 흔들림 애니메이션 (일회성) */
|
|
[data-validation-highlight] {
|
|
animation: validationShake 400ms ease-in-out;
|
|
}
|
|
|
|
/* 빨간 테두리 (값 입력 전까지 유지) */
|
|
[data-validation-error] {
|
|
border-color: hsl(var(--destructive)) !important;
|
|
}
|
|
|
|
|
|
/* 채번 세그먼트 포커스 스타일 (shadcn Input과 동일한 3단 구조) */
|
|
.numbering-segment:focus-within {
|
|
box-shadow: 0 0 0 3px hsl(var(--ring) / 0.5);
|
|
outline: 2px solid hsl(var(--ring));
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
/* 필수 입력 경고 문구 (입력 필드 아래, 레이아웃 영향 없음) */
|
|
.validation-error-msg-wrapper {
|
|
height: 0;
|
|
overflow: visible;
|
|
position: relative;
|
|
}
|
|
|
|
.validation-error-msg-wrapper > p {
|
|
position: absolute;
|
|
top: 1px;
|
|
left: 0;
|
|
font-size: 11px;
|
|
color: hsl(var(--destructive));
|
|
white-space: nowrap;
|
|
pointer-events: none;
|
|
}
|
|
|
|
/* ===== End of Global Styles ===== */
|
|
|
|
/* ===== Dark Mode Compatibility Layer ===== */
|
|
/* 하드코딩된 Tailwind 색상 → 다크 모드 자동 변환 */
|
|
/* 개별 컴포넌트 수정 없이, 이 한 곳에서 전체 프로젝트 커버 */
|
|
|
|
/* --- 1. 배경색: white/gray → 시맨틱 토큰 --- */
|
|
.dark .bg-white { background-color: hsl(var(--card)) !important; }
|
|
.dark .bg-gray-50 { background-color: hsl(var(--muted)) !important; }
|
|
.dark .bg-gray-100 { background-color: hsl(var(--muted)) !important; }
|
|
.dark .bg-gray-200 { background-color: hsl(var(--accent)) !important; }
|
|
|
|
/* --- 2. 텍스트: gray → 시맨틱 토큰 --- */
|
|
.dark .text-gray-400 { color: hsl(var(--muted-foreground)) !important; }
|
|
.dark .text-gray-500 { color: hsl(var(--muted-foreground)) !important; }
|
|
.dark .text-gray-600 { color: hsl(var(--foreground) / 0.7) !important; }
|
|
.dark .text-gray-700 { color: hsl(var(--foreground) / 0.8) !important; }
|
|
.dark .text-gray-800 { color: hsl(var(--foreground) / 0.9) !important; }
|
|
.dark .text-gray-900 { color: hsl(var(--foreground)) !important; }
|
|
|
|
/* --- 3. 보더: gray → 시맨틱 토큰 --- */
|
|
.dark .border-gray-100 { border-color: hsl(var(--border)) !important; }
|
|
.dark .border-gray-200 { border-color: hsl(var(--border)) !important; }
|
|
.dark .border-gray-300 { border-color: hsl(var(--border)) !important; }
|
|
.dark .divide-gray-200 > * + * { border-color: hsl(var(--border)) !important; }
|
|
|
|
/* --- 4. 호버: gray → 시맨틱 토큰 --- */
|
|
.dark .hover\:bg-gray-50:hover { background-color: hsl(var(--muted)) !important; }
|
|
.dark .hover\:bg-gray-100:hover { background-color: hsl(var(--accent)) !important; }
|
|
.dark .hover\:bg-gray-200:hover { background-color: hsl(var(--accent)) !important; }
|
|
|
|
/* --- 5. Emerald (성공/완료 상태) --- */
|
|
.dark .bg-emerald-50 { background-color: hsl(142 40% 12%) !important; }
|
|
.dark .bg-emerald-100 { background-color: hsl(142 40% 15%) !important; }
|
|
.dark .text-emerald-600 { color: hsl(142 70% 55%) !important; }
|
|
.dark .text-emerald-700 { color: hsl(142 70% 50%) !important; }
|
|
.dark .text-emerald-800 { color: hsl(142 70% 60%) !important; }
|
|
.dark .text-emerald-900 { color: hsl(142 70% 65%) !important; }
|
|
.dark .border-emerald-200 { border-color: hsl(142 40% 25%) !important; }
|
|
.dark .border-emerald-300 { border-color: hsl(142 40% 30%) !important; }
|
|
.dark .ring-emerald-200 { --tw-ring-color: hsl(142 40% 25%) !important; }
|
|
|
|
/* --- 6. Amber/Yellow (경고/주의 상태) --- */
|
|
.dark .bg-amber-50 { background-color: hsl(38 40% 12%) !important; }
|
|
.dark .bg-amber-100 { background-color: hsl(38 40% 15%) !important; }
|
|
.dark .bg-yellow-50 { background-color: hsl(48 40% 12%) !important; }
|
|
.dark .bg-yellow-100 { background-color: hsl(48 40% 15%) !important; }
|
|
.dark .text-amber-600 { color: hsl(38 90% 58%) !important; }
|
|
.dark .text-amber-700 { color: hsl(38 90% 55%) !important; }
|
|
.dark .text-amber-800 { color: hsl(38 90% 60%) !important; }
|
|
.dark .text-amber-900 { color: hsl(38 90% 65%) !important; }
|
|
.dark .text-yellow-600 { color: hsl(48 90% 55%) !important; }
|
|
.dark .text-yellow-700 { color: hsl(48 90% 50%) !important; }
|
|
.dark .text-yellow-800 { color: hsl(48 90% 60%) !important; }
|
|
.dark .border-amber-200 { border-color: hsl(38 40% 25%) !important; }
|
|
.dark .border-amber-300 { border-color: hsl(38 40% 30%) !important; }
|
|
.dark .border-yellow-200 { border-color: hsl(48 40% 25%) !important; }
|
|
.dark .ring-amber-200 { --tw-ring-color: hsl(38 40% 25%) !important; }
|
|
|
|
/* --- 7. Blue (정보/프라이머리 상태) --- */
|
|
.dark .bg-blue-50 { background-color: hsl(217 40% 12%) !important; }
|
|
.dark .bg-blue-100 { background-color: hsl(217 40% 15%) !important; }
|
|
.dark .text-blue-600 { color: hsl(217 90% 65%) !important; }
|
|
.dark .text-blue-700 { color: hsl(217 90% 60%) !important; }
|
|
.dark .text-blue-800 { color: hsl(217 90% 65%) !important; }
|
|
.dark .border-blue-200 { border-color: hsl(217 40% 25%) !important; }
|
|
.dark .ring-blue-200 { --tw-ring-color: hsl(217 40% 25%) !important; }
|
|
|
|
/* --- 8. Red (에러/삭제 상태) --- */
|
|
.dark .bg-red-50 { background-color: hsl(0 40% 12%) !important; }
|
|
.dark .bg-red-100 { background-color: hsl(0 40% 15%) !important; }
|
|
.dark .text-red-600 { color: hsl(0 75% 60%) !important; }
|
|
.dark .text-red-700 { color: hsl(0 75% 55%) !important; }
|
|
.dark .text-red-800 { color: hsl(0 75% 60%) !important; }
|
|
.dark .border-red-200 { border-color: hsl(0 40% 25%) !important; }
|
|
.dark .ring-red-200 { --tw-ring-color: hsl(0 40% 25%) !important; }
|
|
|
|
/* --- 9. Green (성공 - emerald 변형) --- */
|
|
.dark .bg-green-50 { background-color: hsl(142 40% 12%) !important; }
|
|
.dark .bg-green-100 { background-color: hsl(142 40% 15%) !important; }
|
|
.dark .text-green-600 { color: hsl(142 70% 55%) !important; }
|
|
.dark .text-green-700 { color: hsl(142 70% 50%) !important; }
|
|
.dark .border-green-200 { border-color: hsl(142 40% 25%) !important; }
|
|
|
|
/* --- 10. Slate (gray 변형) --- */
|
|
.dark .bg-slate-50 { background-color: hsl(var(--muted)) !important; }
|
|
.dark .bg-slate-100 { background-color: hsl(var(--muted)) !important; }
|
|
.dark .text-slate-500 { color: hsl(var(--muted-foreground)) !important; }
|
|
.dark .text-slate-600 { color: hsl(var(--foreground) / 0.7) !important; }
|
|
.dark .text-slate-700 { color: hsl(var(--foreground) / 0.8) !important; }
|
|
.dark .border-slate-200 { border-color: hsl(var(--border)) !important; }
|
|
|
|
/* --- 11. bg-white opacity 변형 --- */
|
|
.dark .bg-white\/30 { background-color: hsl(var(--card) / 0.3) !important; }
|
|
.dark .bg-white\/50 { background-color: hsl(var(--card) / 0.5) !important; }
|
|
.dark .bg-white\/80 { background-color: hsl(var(--card) / 0.8) !important; }
|
|
.dark .bg-white\/90 { background-color: hsl(var(--card) / 0.9) !important; }
|
|
.dark .hover\:bg-white:hover { background-color: hsl(var(--card)) !important; }
|
|
|
|
/* --- 12. text-black → foreground --- */
|
|
.dark .text-black { color: hsl(var(--foreground)) !important; }
|
|
|
|
/* --- 13. bg/text/border - purple (관리 UI) --- */
|
|
.dark .bg-purple-50 { background-color: hsl(270 40% 12%) !important; }
|
|
.dark .bg-purple-100 { background-color: hsl(270 40% 15%) !important; }
|
|
.dark .bg-purple-200 { background-color: hsl(270 40% 20%) !important; }
|
|
.dark .text-purple-500 { color: hsl(270 70% 60%) !important; }
|
|
.dark .text-purple-600 { color: hsl(270 70% 55%) !important; }
|
|
.dark .text-purple-700 { color: hsl(270 70% 50%) !important; }
|
|
.dark .border-purple-200 { border-color: hsl(270 40% 25%) !important; }
|
|
.dark .border-purple-300 { border-color: hsl(270 40% 30%) !important; }
|
|
|
|
/* --- 14. bg/text/border - indigo --- */
|
|
.dark .bg-indigo-50 { background-color: hsl(231 40% 12%) !important; }
|
|
.dark .bg-indigo-100 { background-color: hsl(231 40% 15%) !important; }
|
|
.dark .text-indigo-600 { color: hsl(231 70% 60%) !important; }
|
|
.dark .text-indigo-700 { color: hsl(231 70% 55%) !important; }
|
|
.dark .border-indigo-200 { border-color: hsl(231 40% 25%) !important; }
|
|
|
|
/* --- 15. bg/text - pink/rose (상태 뱃지) --- */
|
|
.dark .bg-pink-50 { background-color: hsl(330 40% 12%) !important; }
|
|
.dark .bg-pink-100 { background-color: hsl(330 40% 15%) !important; }
|
|
.dark .text-pink-600 { color: hsl(330 70% 60%) !important; }
|
|
.dark .text-pink-700 { color: hsl(330 70% 55%) !important; }
|
|
.dark .bg-rose-50 { background-color: hsl(350 40% 12%) !important; }
|
|
.dark .bg-rose-100 { background-color: hsl(350 40% 15%) !important; }
|
|
.dark .text-rose-600 { color: hsl(350 70% 60%) !important; }
|
|
.dark .text-rose-700 { color: hsl(350 70% 55%) !important; }
|
|
|
|
/* --- 16. bg/text - cyan/teal (정보/상태) --- */
|
|
.dark .bg-cyan-50 { background-color: hsl(187 40% 12%) !important; }
|
|
.dark .bg-cyan-100 { background-color: hsl(187 40% 15%) !important; }
|
|
.dark .text-cyan-600 { color: hsl(187 70% 55%) !important; }
|
|
.dark .text-cyan-700 { color: hsl(187 70% 50%) !important; }
|
|
.dark .bg-teal-50 { background-color: hsl(162 40% 12%) !important; }
|
|
.dark .bg-teal-100 { background-color: hsl(162 40% 15%) !important; }
|
|
.dark .text-teal-600 { color: hsl(162 70% 55%) !important; }
|
|
.dark .text-teal-700 { color: hsl(162 70% 50%) !important; }
|
|
|
|
/* --- 17. bg/text - orange (경고 변형) --- */
|
|
.dark .bg-orange-50 { background-color: hsl(25 40% 12%) !important; }
|
|
.dark .bg-orange-100 { background-color: hsl(25 40% 15%) !important; }
|
|
.dark .bg-orange-200 { background-color: hsl(25 40% 20%) !important; }
|
|
.dark .text-orange-600 { color: hsl(25 90% 65%) !important; }
|
|
.dark .text-orange-700 { color: hsl(25 90% 70%) !important; }
|
|
.dark .border-orange-200 { border-color: hsl(25 40% 25%) !important; }
|
|
.dark .border-orange-300 { border-color: hsl(25 40% 30%) !important; }
|
|
|
|
/* --- 18. bg/text/border - violet (필터/관계 표시) --- */
|
|
.dark .bg-violet-50 { background-color: hsl(263 40% 12%) !important; }
|
|
.dark .bg-violet-100 { background-color: hsl(263 40% 18%) !important; }
|
|
.dark .bg-violet-200 { background-color: hsl(263 40% 22%) !important; }
|
|
.dark .text-violet-500 { color: hsl(263 80% 70%) !important; }
|
|
.dark .text-violet-600 { color: hsl(263 80% 65%) !important; }
|
|
.dark .text-violet-700 { color: hsl(263 80% 72%) !important; }
|
|
.dark .border-violet-200 { border-color: hsl(263 40% 30%) !important; }
|
|
.dark .border-violet-300 { border-color: hsl(263 40% 35%) !important; }
|
|
|
|
/* --- 19. bg/text/border - amber (조인/경고) --- */
|
|
.dark .bg-amber-200 { background-color: hsl(38 40% 20%) !important; }
|
|
.dark .text-amber-500 { color: hsl(38 90% 60%) !important; }
|
|
.dark .text-amber-600 { color: hsl(38 90% 65%) !important; }
|
|
|
|
/* ===== End Dark Mode Compatibility Layer ===== */
|
|
|
|
/* ===== View Transitions API — 테마 전환 soft circular reveal =====
|
|
lib/themeTransition.ts 의 animatedThemeChange() 와 함께 동작.
|
|
|
|
동작 원리:
|
|
1) JS 가 클릭 좌표(--reveal-x, --reveal-y) 와 화면 대각선(--reveal-max) 를 root 에 세팅
|
|
2) startViewTransition() 호출 → DOM 캡처 → 새 root 의 dark/light 클래스 swap
|
|
3) ::view-transition-new(root) 가 mask-image radial-gradient 로 노출
|
|
4) @property 로 length 등록된 --reveal-radius 를 0 → --reveal-max 까지 보간 →
|
|
안쪽 50% 는 solid, 바깥은 transparent 로 페이드되는 부드러운 가장자리가 퍼짐
|
|
|
|
미지원 브라우저(Firefox 등)는 이 셀렉터가 무시되므로 즉시 swap. */
|
|
|
|
@property --reveal-radius {
|
|
syntax: "<length>";
|
|
inherits: true;
|
|
initial-value: 0px;
|
|
}
|
|
|
|
/* SVG turbulence noise tile — 220x220 fractalNoise, 알파 0.98~1.0 의 미세 변동.
|
|
이 tile 을 mask-image 두 번째 레이어로 깔고 mask-composite: intersect 로 그라데이션 알파에
|
|
곱해서 8bit 컬러 밴딩을 디더링한다.
|
|
★ 알파 범위를 0.94~1.0 → 0.98~1.0 으로 좁힘. 이전엔 평균 알파가 0.97 이라 VT 동안 새 테마가
|
|
3% 어둡거나 밝게 표현돼서, VT 끝나고 실제 DOM(100% 불투명) 이 노출되면 갑자기 색이 더
|
|
진해 보였음. 0.98~1.0 (평균 0.99) 으로 줄이면 드리프트가 1% 미만이라 인지 안 됨. */
|
|
:root {
|
|
--vt-dither-noise: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='220' height='220'><filter id='n' x='0%25' y='0%25' width='100%25' height='100%25'><feTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.02 0.98'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>");
|
|
}
|
|
|
|
/* VT pseudo-element 가 실제 DOM 위에 깔려서 2~3초 동안 클릭을 가로채는 걸 방지 —
|
|
퍼지는 동안에도 사이드바/탭/버튼 클릭이 그대로 동작해야 함.
|
|
pseudo tree: ::view-transition → group(*) → image-pair(*) → old(*)/new(*) — 전부 막아야 함 */
|
|
::view-transition,
|
|
::view-transition-group(*),
|
|
::view-transition-image-pair(*),
|
|
::view-transition-old(*),
|
|
::view-transition-new(*) {
|
|
pointer-events: none !important;
|
|
}
|
|
|
|
::view-transition-old(root),
|
|
::view-transition-new(root) {
|
|
animation: none;
|
|
mix-blend-mode: normal;
|
|
}
|
|
::view-transition-old(root) { z-index: 0; }
|
|
::view-transition-new(root) {
|
|
z-index: 1;
|
|
/* 두 레이어 마스크:
|
|
Layer 1 (top) : SVG noise tile — 알파 0.94~1.0 의 미세 변동으로 dither
|
|
Layer 2 (bottom) : 8 stop radial-gradient soft reveal
|
|
mask-composite: intersect (= alpha 곱셈) 로 두 레이어를 곱하면 그라데이션 알파에 ±3% 의 random
|
|
변동이 더해져 8bit 컬러 밴딩(띠) 이 흩어진다. -webkit- 의 source-in 은 Porter-Duff IN 으로
|
|
같은 곱셈 효과 (Safari 18 이전 호환). */
|
|
-webkit-mask-image:
|
|
var(--vt-dither-noise),
|
|
radial-gradient(
|
|
circle at var(--reveal-x, 50%) var(--reveal-y, 50%),
|
|
rgba(0, 0, 0, 1) 0%,
|
|
rgba(0, 0, 0, 1) calc(var(--reveal-radius, 0px) * 0.25),
|
|
rgba(0, 0, 0, 0.97) calc(var(--reveal-radius, 0px) * 0.4),
|
|
rgba(0, 0, 0, 0.85) calc(var(--reveal-radius, 0px) * 0.55),
|
|
rgba(0, 0, 0, 0.6) calc(var(--reveal-radius, 0px) * 0.7),
|
|
rgba(0, 0, 0, 0.3) calc(var(--reveal-radius, 0px) * 0.85),
|
|
rgba(0, 0, 0, 0.1) calc(var(--reveal-radius, 0px) * 0.95),
|
|
rgba(0, 0, 0, 0) var(--reveal-radius, 0px)
|
|
);
|
|
-webkit-mask-composite: source-in;
|
|
-webkit-mask-size: 220px 220px, 100% 100%;
|
|
-webkit-mask-repeat: repeat, no-repeat;
|
|
-webkit-mask-position: 0 0, 0 0;
|
|
mask-image:
|
|
var(--vt-dither-noise),
|
|
radial-gradient(
|
|
circle at var(--reveal-x, 50%) var(--reveal-y, 50%),
|
|
rgba(0, 0, 0, 1) 0%,
|
|
rgba(0, 0, 0, 1) calc(var(--reveal-radius, 0px) * 0.25),
|
|
rgba(0, 0, 0, 0.97) calc(var(--reveal-radius, 0px) * 0.4),
|
|
rgba(0, 0, 0, 0.85) calc(var(--reveal-radius, 0px) * 0.55),
|
|
rgba(0, 0, 0, 0.6) calc(var(--reveal-radius, 0px) * 0.7),
|
|
rgba(0, 0, 0, 0.3) calc(var(--reveal-radius, 0px) * 0.85),
|
|
rgba(0, 0, 0, 0.1) calc(var(--reveal-radius, 0px) * 0.95),
|
|
rgba(0, 0, 0, 0) var(--reveal-radius, 0px)
|
|
);
|
|
mask-composite: intersect;
|
|
mask-size: 220px 220px, 100% 100%;
|
|
mask-repeat: repeat, no-repeat;
|
|
mask-position: 0 0, 0 0;
|
|
/* ease-in-out-cubic — 대칭형 곡선으로 시간/progress 가 거의 linear 에 가깝게 진행됨.
|
|
duration 은 themeTransition.ts 가 방향에 따라 --vt-duration 변수로 지정.
|
|
검정→대비 강해서 빨라 보이는 perceived speed 비대칭을 보정하기 위해 dark 방향은 더 김. */
|
|
animation: vt-soft-reveal var(--vt-duration, 500ms) cubic-bezier(0.65, 0, 0.35, 1) forwards;
|
|
}
|
|
|
|
@keyframes vt-soft-reveal {
|
|
from {
|
|
--reveal-radius: 0px;
|
|
}
|
|
to {
|
|
--reveal-radius: var(--reveal-max, 1500px);
|
|
}
|
|
}
|
|
|
|
/* (컬러 테마 변경은 View Transitions 미사용 — colorTransition.ts 참고.
|
|
화면 cross-fade 가 깜빡임으로 느껴진다는 피드백으로 즉시 swap + 요소별 entrance 재생만 사용.) */
|