veza/apps/web/src/index.css
senke 12a78616df refactor(web): zero out @typescript-eslint/no-unused-vars (134 → 0)
Two-step cleanup of the no-unused-vars warning bucket :

1. Widened the rule's ignore patterns in eslint.config.js so the
   `_`-prefix convention works uniformly across all four contexts
   (function args, local vars, caught errors, destructured arrays).
   The argsIgnorePattern was already `^_` ; added varsIgnorePattern,
   caughtErrorsIgnorePattern, destructuredArrayIgnorePattern with
   the same `^_` regex. Knocked 17 warnings out instantly because the
   codebase had already adopted `_xxx` for unused locals and was
   waiting on this config change.

2. Fixed the remaining 117 cases across 99 files by pattern :
   * 26 catch-binding cases : `catch (e) {…}` → `catch {…}` (TS 4.0+
     optional binding, ES2019). Cleaner than `catch (_e)` for the
     dozen "swallow and toast" error handlers that don't read the
     error.
   * 58 unused imports removed (incl. one literal `electron`
     contextBridge import that crept in from a phantom port-attempt).
   * 28 destructure / assignment cases : prefixed with `_` where the
     name documents the contract (test fixtures, hook return tuples
     where one slot isn't used yet) ; deleted outright when the
     assignment had no side effect and no documentary value.
   * 3 function param cases : prefixed with `_`.
   * 2 self-recursive `requestAnimationFrame` blocks that were dead
     code (an interval-based alternative did the work) : deleted.

`tsc --noEmit` reports 0 errors after the changes. ESLint total
dropped from 1240 to 1108. Updated the baseline in
.github/workflows/ci.yml in the next commit.

Pattern decisions logged inline so future maintainers know that
`_`-prefix isn't slop — it's the documented, lint-aware way to mark
"intentionally unused" without having to remove the name.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-30 23:05:32 +02:00

1187 lines
47 KiB
CSS
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

@import 'tailwindcss';
@import 'tw-animate-css';
/* SUMI generated tokens — Single source of truth. See packages/design-system/tokens/.
This adds --color-* primitives and --sumi-* semantics (bg/text/accent/viz/feedback).
The legacy :root block below still defines additional --sumi-* vars (typography, motion,
z-index, glass…) pending migration to tokens.json (Sprint 2 follow-up). */
@import '@veza/design-system/tokens.css';
@custom-variant dark (&:is([data-theme="dark"] *));
@theme {
--font-size-xxs: 0.625rem; /* 10px */
--font-size-3xs: 0.5rem; /* 8px */
}
/* ╔══════════════════════════════════════════════════════════════════════════╗
║ SUMI DESIGN SYSTEM v3.0 — "Lavis d'encre" (墨の濃淡) ║
║ VEZA × TALAS — Single Source of Truth ║
║ Ink wash aesthetic — contrast, wabi-sabi, ma (間) ║
║ Accent: Mizu cyan (#0098B5) — the only color in monochrome ink wash ║
╚══════════════════════════════════════════════════════════════════════════╝ */
/* ═══ @font-face — Self-hosted woff2 ═══ */
@font-face {
font-family: 'Space Grotesk';
src: url('/fonts/SpaceGrotesk-Bold.woff2') format('woff2');
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Inter';
src: url('/fonts/Inter-Variable.woff2') format('woff2');
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'JetBrains Mono';
src: url('/fonts/JetBrainsMono-Regular.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
}
/* ═══════════════════════════════════════════════════════════════════════════
DARK THEME (default) — Sumi ink on void (墨の闇)
═══════════════════════════════════════════════════════════════════════════ */
:root, [data-theme="dark"] {
/* ═══ SHADCN/RADIX SEMANTIC MAPPING ═══ */
--background: var(--sumi-bg-base);
--foreground: var(--sumi-text-primary);
--card: var(--sumi-surface-card);
--card-foreground: var(--sumi-text-primary);
--popover: var(--sumi-bg-overlay);
--popover-foreground: var(--sumi-text-primary);
--primary: var(--sumi-accent);
--primary-foreground: var(--sumi-text-inverse);
--secondary: var(--sumi-bg-hover);
--secondary-foreground: var(--sumi-text-primary);
--muted: var(--sumi-bg-hover);
--muted-foreground: var(--sumi-text-secondary);
--accent: var(--sumi-bg-hover);
--accent-foreground: var(--sumi-text-primary);
--destructive: var(--sumi-error);
--destructive-foreground: var(--sumi-text-primary);
--success: var(--sumi-success);
--success-foreground: var(--sumi-text-primary);
--warning: var(--sumi-warning);
--warning-foreground: var(--sumi-text-inverse);
--info: var(--sumi-info);
--info-foreground: var(--sumi-text-inverse);
--border: var(--sumi-border-default);
--input: var(--sumi-border-default);
--ring: var(--sumi-border-focus);
--radius: var(--sumi-radius-md);
/* Charts */
--chart-1: var(--sumi-accent);
--chart-2: var(--sumi-error);
--chart-3: var(--sumi-sage);
--chart-4: var(--sumi-gold);
--chart-5: var(--sumi-viz-neutral);
/* Sidebar mapping */
--sidebar: var(--sumi-bg-raised);
--sidebar-foreground: var(--sumi-text-secondary);
--sidebar-primary: var(--sumi-accent);
--sidebar-primary-foreground: var(--sumi-text-primary);
--sidebar-accent: var(--sumi-accent-subtle);
--sidebar-accent-foreground: var(--sumi-text-primary);
--sidebar-border: var(--sumi-border-faint);
--sidebar-ring: var(--sumi-accent);
/* Sidebar active item indicator */
--sidebar-active-indicator: inset 2px 0 0 0 var(--sidebar-primary);
/* ═══ BACKWARD COMPATIBILITY ALIASES (Deprecated - avoid new usage) ═══ */
--layout-content-max-width: var(--sumi-layout-content-max-width);
--layout-main-min-height: var(--sumi-layout-main-min-height);
--layout-page-min-height: var(--sumi-layout-page-min-height);
--layout-gap: var(--sumi-layout-gap);
--layout-drawer-max-height: var(--sumi-layout-drawer-max-height);
--layout-panel-max-height: var(--sumi-layout-panel-max-height);
--layout-list-max-height: var(--sumi-layout-list-max-height);
--layout-modal-max-height: var(--sumi-layout-modal-max-height);
--layout-lyrics-height: var(--sumi-layout-lyrics-height);
--layout-chat-height: var(--sumi-layout-chat-height);
--header-height: var(--sumi-layout-shell-header-height);
--main-offset-top: var(--sumi-layout-shell-main-offset-top);
--main-offset-bottom: var(--sumi-layout-shell-main-offset-bottom);
--sidebar-width-expanded: var(--sumi-layout-sidebar-width-expanded);
--sidebar-width-collapsed: var(--sumi-layout-sidebar-width-collapsed);
--sidebar-z-index: var(--sumi-layout-sidebar-z-index);
--player-z-index: var(--sumi-layout-player-z-index);
/* Functional Legacy Aliases */
--duration-fast: var(--sumi-duration-fast);
--duration-normal: var(--sumi-duration-normal);
}
/* ═══════════════════════════════════════════════════════════════════════════
LIGHT THEME — Washi Paper (和紙) — Ink on warm paper
═══════════════════════════════════════════════════════════════════════════ */
[data-theme="light"] {
/* All --sumi-* light theme overrides tokenized in @veza/design-system/tokens.css
(see semantic/light.json — byte-for-byte preservation of former tuned values).
This block keeps only app-specific shadcn/Radix overrides for light theme. */
--primary-foreground: var(--sumi-text-inverse);
}
/* ═══════════════════════════════════════════════════════════════════════════
HIGH CONTRAST MODE — 極墨 (Extreme Ink) — WCAG AA 4.5:1+
═══════════════════════════════════════════════════════════════════════════ */
[data-theme="dark"][data-contrast="high"] {
--sumi-bg-base: #000000;
--sumi-text-primary: #FFFFFF;
--sumi-border-default: rgba(255, 255, 255, 0.4);
--border: var(--sumi-border-default);
}
[data-theme="light"][data-contrast="high"] {
--sumi-bg-base: #FFFFFF;
--sumi-text-primary: #000000;
--sumi-border-default: rgba(0, 0, 0, 0.4);
--border: var(--sumi-border-default);
}
--sumi-accent-hover: #004A58;
}
/* ═══════════════════════════════════════════════════════════════════════════
DENSITY MODES — compact reduces spacing ~25%
═══════════════════════════════════════════════════════════════════════════ */
[data-density="compact"] {
--sumi-space-0-5: 1.5px;
--sumi-space-1: 3px;
--sumi-space-1-5: 4.5px;
--sumi-space-2: 6px;
--sumi-space-2-5: 7.5px;
--sumi-space-3: 9px;
--sumi-space-4: 12px;
--sumi-space-5: 15px;
--sumi-space-6: 18px;
--sumi-space-8: 24px;
--sumi-space-10: 30px;
--sumi-space-12: 36px;
--sumi-space-16: 48px;
--sumi-space-20: 60px;
--sumi-text-base: 0.8125rem;
}
[data-density="comfortable"] {
font-size: 1rem;
}
/* ═══════════════════════════════════════════════════════════════════════════
FOCUS VISIBLE — distinct ring for keyboard navigation
═══════════════════════════════════════════════════════════════════════════ */
:focus-visible {
outline: 2px solid var(--sumi-accent);
outline-offset: 2px;
}
/* ═══════════════════════════════════════════════════════════════════════════
TAILWIND THEME MAPPING
═══════════════════════════════════════════════════════════════════════════ */
@theme inline {
/* Typography */
--font-sans: var(--sumi-font-body);
--font-heading: var(--sumi-font-heading);
--font-mono: var(--sumi-font-mono);
--font-serif: var(--sumi-font-serif);
/* Semantic color mapping to Tailwind utilities */
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);
/* Extended semantic */
--color-success: var(--success);
--color-success-foreground: var(--success-foreground);
--color-warning: var(--warning);
--color-warning-foreground: var(--warning-foreground);
--color-info: var(--info);
--color-info-foreground: var(--info-foreground);
/* SUMI direct accent access */
--color-sumi-accent: var(--sumi-accent);
--color-sumi-vermillion: var(--sumi-vermillion);
--color-sumi-sage: var(--sumi-sage);
--color-sumi-gold: var(--sumi-gold);
--color-gaming-gold: var(--sumi-gold);
--color-terminal-green: #3eaa5e;
--color-graffiti-magenta: #c840a0;
--color-sakura: #d090a8;
--color-sumi-mizu: var(--sumi-mizu);
--color-sumi-kin: var(--sumi-kin);
--color-sumi-ai: var(--sumi-ai);
/* Chart colors */
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);
/* Shadows — wired to SUMI tokens (auto dark/light) */
--shadow-xs: var(--sumi-shadow-xs);
--shadow-sm: var(--sumi-shadow-sm);
--shadow-md: var(--sumi-shadow-md);
--shadow-lg: var(--sumi-shadow-lg);
--shadow-xl: var(--sumi-shadow-xl);
--shadow-2xl: var(--sumi-shadow-2xl);
/* Card-specific shadows */
--shadow-card: var(--sumi-shadow-sm);
--shadow-card-hover: var(--sumi-shadow-md);
/* Radius */
--radius-sm: var(--sumi-radius-sm);
--radius-md: var(--sumi-radius-md);
--radius-lg: var(--sumi-radius-lg);
--radius-xl: var(--sumi-radius-xl);
--radius-2xl: var(--sumi-radius-2xl);
--radius-full: var(--sumi-radius-full);
/* Sidebar */
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);
}
/* ═══════════════════════════════════════════════════════════════════════════
BASE STYLES
═══════════════════════════════════════════════════════════════════════════ */
@layer base {
* {
@apply border-border outline-ring/50;
}
html {
scroll-behavior: smooth;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
@apply bg-background text-foreground;
font-size: var(--sumi-font-size-base);
font-feature-settings: "cv02", "cv03", "cv04", "cv11";
}
/* ═══ SUMI v3 — Washi fiber grain (和紙繊維) ═══
Anisotropic noise simulating washi paper fiber direction.
baseFrequency 0.4x0.9 = horizontal fibers. 5 octaves = fine detail.
GPU-composited via position:fixed. */
body::after {
content: '';
position: fixed;
inset: 0;
background:
url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='w'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.4 0.9' numOctaves='5' stitchTiles='stitch' seed='2'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23w)'/%3E%3C/svg%3E");
opacity: var(--sumi-grain-opacity, 0.04);
pointer-events: none;
z-index: 9998;
mix-blend-mode: soft-light;
}
/* ═══ SUMI v3 — Subtle ink wash vignette (墨暈し) ═══
Very faint radial light at top-center, like a distant lamp on canvas. */
body::before {
content: '';
position: fixed;
inset: 0;
background: radial-gradient(ellipse at 30% 20%, rgba(210,195,168, 0.04) 0%, transparent 60%),
radial-gradient(ellipse at 70% 80%, rgba(180,165,135, 0.03) 0%, transparent 50%);
pointer-events: none;
z-index: 0;
}
/* ═══ SUMI v3 — Circadian + Patina filter ═══
Applied on #root to avoid affecting the grain layer.
hue-rotate shifts warmth. brightness adjusts for time of day.
Transition is 5 min (300s) so changes are imperceptible. */
#root {
filter:
hue-rotate(calc(var(--sumi-circadian-warmth) + var(--sumi-patina-warmth, 0deg)))
brightness(var(--sumi-circadian-brightness));
transition: filter 300s linear;
}
/* Eco mode — disable all cosmetic layers */
[data-eco="true"] body::after { display: none; }
[data-eco="true"] body::before { display: none; }
[data-eco="true"] #root { filter: none; transition: none; }
/* Respect reduced motion */
@media (prefers-reduced-motion: reduce) {
body::after { display: none; }
body::before { display: none; }
#root { filter: none; transition: none; }
}
/* ═══ SUMI v3 — Patina levels (墨の歳月) ═══ */
[data-patina="1"] { --sumi-grain-opacity: 0.05; }
[data-patina="2"] { --sumi-grain-opacity: 0.06; }
[data-patina="3"] {
--sumi-grain-opacity: 0.07;
--sumi-border-default: rgba(0,152,181, 0.05);
}
[data-patina="4"] {
--sumi-grain-opacity: 0.08;
--sumi-border-default: rgba(0,152,181, 0.08);
--sumi-shadow-sm: 0 0 8px rgba(0,152,181, 0.08);
}
::selection {
background: var(--primary);
color: var(--primary-foreground);
}
/* Custom Scrollbar — thin, subtle */
::-webkit-scrollbar {
width: 6px;
height: 6px;
}
::-webkit-scrollbar-track {
background: var(--sumi-scrollbar-track);
}
::-webkit-scrollbar-thumb {
background: var(--sumi-scrollbar-thumb);
border-radius: var(--sumi-radius-full);
transition: background-color 0.2s ease;
}
::-webkit-scrollbar-thumb:hover {
background: var(--sumi-scrollbar-hover);
}
@supports (scrollbar-width: thin) {
* {
scrollbar-width: thin;
scrollbar-color: var(--sumi-scrollbar-thumb) transparent;
}
}
/* Typography — Space Grotesk Bold UPPERCASE (engraved stone) */
h1, h2, h3, h4, h5, h6 {
@apply font-heading text-foreground;
font-weight: 700;
text-transform: uppercase;
text-wrap: balance;
}
h1 { font-size: 2rem; letter-spacing: 0.15em; line-height: 1.2; }
h2 { font-size: 1.5rem; letter-spacing: 0.12em; line-height: 1.3; }
h3 { font-size: 1.25rem; letter-spacing: 0.10em; line-height: 1.4; }
h4 { font-size: 1.125rem; letter-spacing: 0.08em; line-height: 1.4; }
h5 { font-size: 1rem; letter-spacing: 0.08em; line-height: 1.4; }
h6 { font-size: 0.875rem; letter-spacing: 0.08em; line-height: 1.5; }
p {
@apply text-muted-foreground leading-relaxed;
text-wrap: pretty;
}
a:not([class]) {
@apply text-primary transition-colors;
}
a:not([class]):hover {
@apply text-primary/80;
}
code {
@apply font-mono text-sm bg-muted px-1.5 py-0.5 rounded;
}
pre {
@apply font-mono text-sm bg-muted p-4 rounded-lg overflow-x-auto;
}
}
/* ═══════════════════════════════════════════════════════════════════════════
SUMI UTILITY CLASSES
═══════════════════════════════════════════════════════════════════════════ */
@layer utilities {
/* Layout primitives — standard widths/heights for app shell and pages */
.max-w-layout-content { max-width: var(--layout-content-max-width); }
.min-h-layout-main { min-height: var(--layout-main-min-height); }
.min-h-layout-page { min-height: var(--layout-page-min-height); }
.min-h-layout-page-sm { min-height: var(--layout-page-min-height-sm); }
.min-h-layout-story { min-height: var(--layout-story-decorator-min-height); }
.max-h-layout-drawer { max-height: var(--layout-drawer-max-height); }
.max-h-layout-panel { max-height: var(--layout-panel-max-height); }
.max-h-layout-list { max-height: var(--layout-list-max-height); }
.max-h-layout-modal { max-height: var(--layout-modal-max-height); }
.max-h-layout-modal-sm { max-height: var(--layout-modal-max-height-sm); }
.max-h-layout-modal-xs { max-height: var(--layout-modal-max-height-xs); }
.max-h-layout-modal-lg { max-height: var(--layout-modal-max-height-lg); }
.h-layout-modal-sm { height: var(--layout-modal-max-height-sm); }
.h-layout-lyrics { height: var(--layout-lyrics-height); }
.h-layout-lyrics-sm { height: var(--layout-lyrics-height-sm); }
.h-layout-chat { height: var(--layout-chat-height); }
.h-layout-chat-main { height: var(--layout-chat-main-height); }
.h-layout-stream { height: var(--layout-stream-height); }
.h-layout-modal-full { height: var(--layout-modal-full-height); }
/* Sidebar layout utilities */
.w-sidebar-expanded { width: var(--sidebar-width-expanded); }
.w-sidebar-collapsed { width: var(--sidebar-width-collapsed); }
.left-sidebar { left: var(--sidebar-offset-left); }
.top-sidebar { top: var(--sidebar-offset-top); }
.bottom-sidebar { bottom: var(--sidebar-offset-bottom); }
.z-sidebar { z-index: var(--sidebar-z-index); }
.z-sidebar-overlay { z-index: var(--sidebar-overlay-z-index); }
.z-player { z-index: var(--player-z-index); }
.h-mobile-nav { height: var(--mobile-bottom-nav-height); }
/* App shell — header / main offsets */
.h-header { height: var(--header-height); }
.pt-main { padding-top: var(--main-offset-top); }
.pb-main { padding-bottom: var(--main-offset-bottom); }
.ml-main-expanded { margin-left: var(--main-margin-left-expanded); }
.ml-main-collapsed { margin-left: var(--main-margin-left-collapsed); }
/* Responsive margin-left for main */
@media (min-width: 1024px) {
.lg\:ml-main-expanded { margin-left: var(--main-margin-left-expanded); }
.lg\:ml-main-collapsed { margin-left: var(--main-margin-left-collapsed); }
.lg\:left-main-expanded { left: var(--main-margin-left-expanded); }
.lg\:left-main-collapsed { left: var(--main-margin-left-collapsed); }
.lg\:w-player-bar-expanded { width: calc(100vw - var(--main-margin-left-expanded) - 1rem); }
.lg\:w-player-bar-collapsed { width: calc(100vw - var(--main-margin-left-collapsed) - 1rem); }
}
/* Player bar width: mobile (no sidebar) */
.w-player-bar { width: calc(100vw - 2rem); }
.left-header-expanded { left: var(--header-left-expanded); }
.left-header-collapsed { left: var(--header-left-collapsed); }
/* Shell transition — sidebar width/opacity */
.transition-shell {
transition: width var(--sumi-duration-normal) var(--sumi-ease-out),
opacity var(--sumi-duration-normal) var(--sumi-ease-out),
transform var(--sumi-duration-normal) var(--sumi-ease-out);
}
/* Respect prefers-reduced-motion */
@media (prefers-reduced-motion: reduce) {
.transition-shell {
transition: none;
}
.player-bar-entrance {
animation: none !important;
}
}
/* Sidebar active nav item — left border indicator */
.sidebar-active-indicator {
box-shadow: var(--sidebar-active-indicator);
}
/* ═══ Ink Edge utilities — diffused shadows instead of borders ═══ */
.ink-edge {
border: none;
box-shadow: 0 0 8px rgba(26,26,30, 0.05);
}
.ink-edge-faint {
border: none;
box-shadow: 0 0 4px rgba(26,26,30, 0.03);
}
.ink-edge-strong {
border: none;
box-shadow: 0 0 12px rgba(26,26,30, 0.08);
}
.ink-edge-bottom {
border: none;
box-shadow: 0 4px 8px -4px rgba(26,26,30, 0.08);
}
.ink-edge-right {
border: none;
box-shadow: 4px 0 8px -4px rgba(26,26,30, 0.06);
}
/* Glass Effect — Shoji screen (障子) */
.glass, .sumi-glass {
background: var(--sumi-glass-bg);
backdrop-filter: blur(var(--sumi-glass-blur)) saturate(0.85);
-webkit-backdrop-filter: blur(var(--sumi-glass-blur)) saturate(0.85);
box-shadow: 0 0 8px rgba(26,26,30, 0.05);
}
/* Typography utilities — SUMI hierarchy */
.font-heading { font-family: var(--sumi-font-heading); }
.font-serif { font-family: var(--sumi-font-serif); }
.text-display { font-size: 2.25rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.15em; line-height: 1.2; }
.text-heading-1 { font-size: 1.875rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.12em; line-height: 1.3; }
.text-heading-2 { font-size: 1.5rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.10em; line-height: 1.4; }
.text-heading-3 { font-size: 1.25rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; line-height: 1.4; }
.text-heading-4 { font-size: 1.125rem; font-weight: 700; text-transform: uppercase; letter-spacing: 0.08em; line-height: 1.4; }
.text-body-lg { @apply text-base leading-relaxed; }
.text-body { @apply text-sm leading-relaxed; }
.text-caption { @apply text-xs text-muted-foreground; }
.text-label { @apply text-xs font-medium uppercase tracking-wider text-muted-foreground; }
/* SUMI typography classes (from design system) */
.sumi-display { font-family: var(--sumi-font-heading); font-size: 2.25rem; font-weight: 700; line-height: 1.2; letter-spacing: 0.15em; text-transform: uppercase; }
.sumi-h1 { font-family: var(--sumi-font-heading); font-size: 1.875rem; font-weight: 700; line-height: 1.3; letter-spacing: 0.12em; text-transform: uppercase; }
.sumi-h2 { font-family: var(--sumi-font-heading); font-size: 1.5rem; font-weight: 700; line-height: 1.4; letter-spacing: 0.10em; text-transform: uppercase; }
.sumi-h3 { font-family: var(--sumi-font-heading); font-size: 1.25rem; font-weight: 700; line-height: 1.4; letter-spacing: 0.08em; text-transform: uppercase; }
.sumi-h4 { font-family: var(--sumi-font-heading); font-size: 1.125rem; font-weight: 700; line-height: 1.4; letter-spacing: 0.08em; text-transform: uppercase; }
.sumi-body-lg { font-size: var(--sumi-text-md); font-weight: var(--sumi-weight-regular); line-height: var(--sumi-leading-relaxed); }
.sumi-body { font-size: var(--sumi-text-base); font-weight: var(--sumi-weight-regular); line-height: var(--sumi-leading-normal); }
.sumi-body-sm { font-size: var(--sumi-text-sm); font-weight: var(--sumi-weight-regular); line-height: var(--sumi-leading-normal); }
.sumi-caption { font-size: var(--sumi-text-xs); font-weight: var(--sumi-weight-regular); line-height: var(--sumi-leading-normal); }
.sumi-label { font-size: var(--sumi-text-xs); font-weight: var(--sumi-weight-medium); line-height: var(--sumi-leading-normal); letter-spacing: var(--sumi-tracking-wider); text-transform: uppercase; }
.sumi-mono { font-family: var(--sumi-font-mono); font-size: var(--sumi-text-sm); }
/* Animation Utilities */
.animate-fade-in { animation: sumi-fade-in var(--sumi-duration-normal) var(--sumi-ease-out); }
.animate-slide-up { animation: sumi-slide-up var(--sumi-duration-normal) var(--sumi-ease-out); }
.animate-scale-in { animation: sumi-scale-in var(--sumi-duration-normal) var(--sumi-ease-out); }
.animate-fadeIn { animation: sumi-fade-in var(--sumi-duration-normal) var(--sumi-ease-out); }
.animate-scaleIn { animation: sumi-scale-in var(--sumi-duration-normal) var(--sumi-ease-out); }
.animate-pop { animation: sumi-pop var(--sumi-duration-slower) var(--sumi-ease-bounce); }
.animate-like-bounce { animation: like-bounce var(--sumi-duration-slow) var(--sumi-ease-out); }
.animate-shake { animation: shake 0.4s ease-in-out; }
.animate-spin-slow { animation: spin-slow 10s linear infinite; }
.animate-achievement { animation: achievement-slide 0.5s var(--sumi-ease-spring); }
.animate-eq { animation: eq-bar 0.5s ease-in-out infinite; }
.animate-marquee { animation: marquee 10s linear infinite; }
.animate-auth-enter { animation: auth-enter var(--sumi-duration-slow) var(--sumi-ease-out) both; }
.animate-empty-state-in { animation: sumi-scale-in var(--sumi-duration-normal) var(--sumi-ease-out) both; }
.animate-stagger-in { animation: sumi-slide-up var(--sumi-duration-normal) var(--sumi-ease-out) both; }
.animate-glow-pulse { animation: sumi-pulse 2s ease-in-out infinite; }
.animate-content-reveal { animation: content-reveal var(--sumi-duration-slow) var(--sumi-ease-out) both; }
.animate-glow-breathe { animation: glow-breathe 3s ease-in-out infinite; }
.animate-vinyl-spin { animation: vinyl-spin 3s linear infinite; }
.animate-vinyl-spin.paused { animation-play-state: paused; }
.animate-slide-in-right { animation: slide-in-right var(--sumi-duration-normal) var(--sumi-ease-spring); }
.animate-subtle-float { animation: subtle-float 6s ease-in-out infinite; }
.animate-card-enter { animation: card-enter var(--sumi-duration-slow) var(--sumi-ease-out) both; }
/* Now-playing EQ bars container */
.eq-bars {
display: flex;
align-items: flex-end;
gap: 2px;
height: 14px;
width: 14px;
}
.eq-bars > span {
display: block;
width: 3px;
border-radius: 1px;
background: currentColor;
}
.eq-bars > span:nth-child(1) { animation: eq-bar-1 0.8s ease-in-out infinite; }
.eq-bars > span:nth-child(2) { animation: eq-bar-2 0.7s ease-in-out infinite; }
.eq-bars > span:nth-child(3) { animation: eq-bar-3 0.9s ease-in-out infinite; }
.eq-bars.paused > span { animation-play-state: paused; height: 30%; }
/* Gradient text animation */
.animate-gradient-text {
background-size: 200% auto;
animation: gradient-shift 3s ease-in-out infinite;
-webkit-background-clip: text;
background-clip: text;
-webkit-text-fill-color: transparent;
}
/* Section divider — brush stroke (筆致) */
.section-divider {
height: 2px;
background: linear-gradient(90deg,
transparent 0%,
var(--sumi-border-faint) 8%,
var(--sumi-border-strong) 25%,
var(--sumi-border-strong) 75%,
var(--sumi-border-faint) 92%,
transparent 100%
);
opacity: 0.8;
}
/* Gold leaf line — 金箔 accent divider */
.gold-line {
height: 1px;
background: linear-gradient(90deg, transparent 0%, rgba(184,134,11,0.4) 30%, rgba(184,134,11,0.15) 70%, transparent 100%);
}
/* Player bar entrance */
.player-bar-entrance {
animation: player-bar-entrance var(--sumi-duration-slower) var(--sumi-ease-spring) both;
}
/* Progress bar — ink flow */
.progress-bar-animated {
background: linear-gradient(90deg, var(--sumi-accent) 0%, var(--sumi-accent-hover) 50%, var(--sumi-accent) 100%);
background-size: 200% 100%;
animation: progress-shimmer 2s ease-in-out infinite;
}
/* Sumi-e animation utilities */
.animate-ink-drop { animation: sumi-ink-drop var(--sumi-duration-slow) var(--sumi-ease-spring); }
.animate-ink-spread { animation: sumi-ink-spread var(--sumi-duration-slower) var(--sumi-ease-out); }
.animate-brush-stroke { animation: sumi-brush-stroke var(--sumi-duration-slow) var(--sumi-ease-out); }
.animate-ink-reveal { animation: sumi-ink-reveal var(--sumi-duration-slower) var(--sumi-ease-out) both; }
/* Discord-style hover card lift */
.hover-lift {
transition: transform var(--sumi-duration-normal) var(--sumi-ease-out),
box-shadow var(--sumi-duration-normal) var(--sumi-ease-out);
}
.hover-lift:hover {
transform: translateY(-2px);
box-shadow: var(--sumi-shadow-lg);
}
.hover-lift:active {
transform: translateY(0);
box-shadow: var(--sumi-shadow-sm);
}
/* Scrollbar hide utility */
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
/* Line clamp utilities */
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.line-clamp-3 {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* Ink Wash Texture — sumi-nagashi (墨流し) */
.sumi-wash-texture { position: relative; }
.sumi-wash-texture::after {
content: '';
position: absolute;
inset: 0;
background:
radial-gradient(ellipse at 15% 60%, var(--sumi-accent-subtle) 0%, transparent 55%),
radial-gradient(ellipse at 85% 25%, rgba(255,255,255,0.015) 0%, transparent 45%),
radial-gradient(ellipse at 50% 90%, rgba(0,152,181,0.03) 0%, transparent 50%);
pointer-events: none;
}
/* Ink bleed hover — bokashi (暈し) */
.sumi-ink-bleed { position: relative; overflow: hidden; }
.sumi-ink-bleed::after {
content: '';
position: absolute;
inset: 0;
background: radial-gradient(circle at var(--mouse-x, 50%) var(--mouse-y, 50%),
rgba(0,152,181, 0.06) 0%,
transparent 55%);
opacity: 0;
transition: opacity var(--sumi-duration-normal) var(--sumi-ease-default);
pointer-events: none;
}
.sumi-ink-bleed:hover::after { opacity: 1; }
/* Hanko stamp — subtle seal impression (判子) */
.sumi-hanko {
transform: rotate(-1.5deg);
box-shadow:
1px 1px 0 rgba(0,152,181, 0.2),
-0.5px -0.5px 0 rgba(0,0,0, 0.08);
}
/* Notan — dramatic light/dark contrast container (濃淡) */
.sumi-notan {
background: linear-gradient(
180deg,
var(--sumi-bg-void) 0%,
var(--sumi-bg-base) 30%,
var(--sumi-bg-raised) 100%
);
}
/* Bokashi gradient wash — for hero backgrounds (暈し) */
.sumi-bokashi {
background: linear-gradient(
135deg,
var(--sumi-bg-void) 0%,
var(--sumi-bg-base) 40%,
var(--sumi-bg-wash) 60%,
var(--sumi-bg-void) 100%
);
}
/* Sumi-nagashi marble background — floating ink (墨流し) */
.sumi-nagashi {
background:
radial-gradient(ellipse at 20% 50%, var(--sumi-bg-wash) 0%, transparent 50%),
radial-gradient(ellipse at 80% 50%, var(--sumi-bg-raised) 0%, transparent 40%),
radial-gradient(ellipse at 50% 80%, rgba(0,152,181,0.02) 0%, transparent 45%),
var(--sumi-bg-void);
background-size: 200% 200%;
}
.sumi-nagashi.animate { animation: sumi-nagashi 25s ease-in-out infinite; }
/* Ink card — atmospheric container with wash background */
.ink-card {
position: relative;
border-radius: var(--sumi-radius-sm);
overflow: hidden;
}
.ink-card::before {
content: '';
position: absolute;
inset: -20px;
background:
radial-gradient(ellipse at 25% 25%, rgba(232,224,208, 0.035) 0%, transparent 55%),
radial-gradient(ellipse at 75% 75%, rgba(232,224,208, 0.025) 0%, transparent 50%);
filter: blur(8px);
pointer-events: none;
}
.ink-card::after {
content: '';
position: absolute;
bottom: 0; left: 5%; right: 10%;
height: 1px;
background: linear-gradient(90deg, transparent 0%, var(--sumi-border-default) 20%, var(--sumi-border-faint) 60%, transparent 100%);
pointer-events: none;
}
.ink-card > * { position: relative; z-index: 1; }
/* Ghost kanji — large decorative character */
.ghost-kanji {
position: absolute;
font-family: var(--sumi-font-heading);
font-weight: 200;
opacity: 0.06;
pointer-events: none;
user-select: none;
line-height: 1;
z-index: 0;
color: currentColor;
}
[data-theme="light"] .ghost-kanji { opacity: 0.08; }
/* Brush underline — vermillion stroke accent */
.brush-underline::after {
content: '';
display: block;
margin-top: 8px;
height: 2px;
width: min(120px, 40%);
background: linear-gradient(90deg, var(--sumi-accent) 0%, transparent 100%);
border-radius: 1px;
}
/* Hanko seal — logo mark */
.hanko-seal {
position: relative;
transform: rotate(-2deg);
box-shadow:
0 0 0 1px rgba(0,152,181, 0.25),
0 2px 8px rgba(0,152,181, 0.12);
overflow: hidden;
}
.hanko-seal::after {
content: '';
position: absolute;
inset: 0;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='50' height='50'%3E%3Cfilter id='n'%3E%3CfeTurbulence baseFrequency='0.8' numOctaves='3'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.15'/%3E%3C/svg%3E");
mix-blend-mode: overlay;
pointer-events: none;
}
/* Brush stroke divider — tapered ink line */
.brush-divider {
height: 2px;
background: linear-gradient(90deg,
var(--sumi-border-strong) 0%,
var(--sumi-border-default) 40%,
var(--sumi-border-faint) 70%,
transparent 100%);
border-radius: 1px;
}
/* Vertical brush stroke border — for sidebar/panel edges */
.sumi-stroke-border-left {
border-left: 2px solid var(--sumi-border-strong);
border-image: linear-gradient(
to bottom,
transparent 0%,
var(--sumi-border-default) 10%,
var(--sumi-border-strong) 30%,
var(--sumi-border-strong) 70%,
var(--sumi-border-default) 90%,
transparent 100%
) 1;
}
/* Noise texture utility — washi fiber (和紙) */
.noise { position: relative; }
.noise::before {
content: "";
position: absolute;
inset: 0;
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.35 0.85' numOctaves='5' stitchTiles='stitch' seed='3'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.05'/%3E%3C/svg%3E");
pointer-events: none;
opacity: 0.5;
mix-blend-mode: overlay;
border-radius: inherit;
}
@media (prefers-reduced-motion: reduce) {
.animate-stagger-in { animation: none; }
.animate-glow-pulse { animation: none; }
.animate-content-reveal { animation: none; }
.animate-glow-breathe { animation: none; }
.animate-vinyl-spin { animation: none; }
.animate-slide-in-right { animation: none; }
.animate-subtle-float { animation: none; }
.progress-bar-animated { animation: none; }
.hover-lift { transition: none; }
.hover-lift:hover { transform: none; }
.animate-ink-drop { animation: none; }
.animate-ink-spread { animation: none; }
.animate-brush-stroke { animation: none; }
.animate-ink-reveal { animation: none; }
.sumi-nagashi.animate { animation: none; }
.sumi-ink-bleed::after { display: none; }
}
}
/* ═══════════════════════════════════════════════════════════════════════════
SKELETON SHIMMER — loading effect
═══════════════════════════════════════════════════════════════════════════ */
.skeleton-shimmer {
background: linear-gradient(
90deg,
transparent 0%,
rgba(255, 255, 255, 0.06) 40%,
rgba(255, 255, 255, 0.1) 50%,
rgba(255, 255, 255, 0.06) 60%,
transparent 100%
);
background-size: 200% 100%;
animation: shimmer 1.8s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
.skeleton-shimmer {
animation: none;
background: none;
}
}
/* ═══════════════════════════════════════════════════════════════════════════
KEYFRAME ANIMATIONS — SUMI
═══════════════════════════════════════════════════════════════════════════ */
/* Core SUMI animations */
@keyframes sumi-fade-in {
from { opacity: 0; }
to { opacity: 1; }
}
/* S5.1: Branded loading progress bar */
@keyframes loading-progress {
0% { width: 0; transform: translateX(0); }
50% { width: 70%; transform: translateX(0); }
100% { width: 100%; transform: translateX(100%); }
}
@keyframes sumi-fade-out {
from { opacity: 1; }
to { opacity: 0; }
}
@keyframes sumi-slide-up {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes sumi-slide-down {
from { opacity: 0; transform: translateY(-8px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes sumi-scale-in {
from { opacity: 0; transform: scale(0.95); }
to { opacity: 1; transform: scale(1); }
}
@keyframes sumi-scale-out {
from { opacity: 1; transform: scale(1); }
to { opacity: 0; transform: scale(0.95); }
}
/* Pop animation for modals/toasts */
@keyframes sumi-pop {
0% { opacity: 0; transform: scale(0.8); }
60% { opacity: 1; transform: scale(1.05); }
100% { transform: scale(1); }
}
/* Live/status pulse */
@keyframes sumi-pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
/* Ink brush stroke reveal — page transitions */
@keyframes sumi-brush-reveal {
from { clip-path: inset(0 100% 0 0); }
to { clip-path: inset(0 0 0 0); }
}
/* Skeleton loading */
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
/* EQ bars — "now playing" indicator */
@keyframes eq-bar {
0%, 100% { transform: scaleY(0.3); }
50% { transform: scaleY(1); }
}
/* Legacy-compatible animations (used in existing codebase) */
@keyframes like-bounce {
0% { transform: scale(1); }
25% { transform: scale(1.3); }
50% { transform: scale(0.9); }
75% { transform: scale(1.1); }
100% { transform: scale(1); }
}
@keyframes shake {
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); }
20%, 40%, 60%, 80% { transform: translateX(4px); }
}
@keyframes spin-slow {
to { transform: rotate(360deg); }
}
@keyframes typing {
0%, 60%, 100% { transform: translateY(0); }
30% { transform: translateY(-4px); }
}
@keyframes marquee {
0%, 20% { transform: translateX(0); }
80%, 100% { transform: translateX(-100%); }
}
@keyframes achievement-slide {
from { transform: translateX(100%); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
@keyframes terminal-blink {
0%, 50% { opacity: 1; }
51%, 100% { opacity: 0; }
}
@keyframes auth-enter {
from { opacity: 0; transform: translateY(12px) scale(0.98); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
/* Player bar entrance — Spotify-style float up */
@keyframes player-bar-entrance {
from { opacity: 0; transform: translateY(20px) scale(0.98); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
/* Ink content reveal — emerges from darkness */
@keyframes content-reveal {
from { opacity: 0; transform: translateY(16px) scale(0.99); filter: blur(6px); }
to { opacity: 1; transform: translateY(0) scale(1); filter: blur(0); }
}
/* Sumi ink glow — subtle vermillion breathe */
@keyframes glow-breathe {
0%, 100% { box-shadow: 0 0 0 0 rgba(0,152,181, 0); }
50% { box-shadow: 0 0 20px 2px rgba(0,152,181, 0.10); }
}
/* Vinyl spin for now-playing */
@keyframes vinyl-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
/* Progress bar shimmer (Spotify-style) */
@keyframes progress-shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
/* Notification slide in from right */
@keyframes slide-in-right {
from { opacity: 0; transform: translateX(100%); }
to { opacity: 1; transform: translateX(0); }
}
/* Subtle float for cards */
@keyframes subtle-float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-4px); }
}
@keyframes bar-fill {
from { width: 0; }
}
@keyframes level-up {
0% { transform: scale(1); }
50% { transform: scale(1.2); filter: brightness(1.5); }
100% { transform: scale(1); }
}
/* Now-playing EQ bars — 3-bar audio visualizer */
@keyframes eq-bar-1 {
0%, 100% { height: 30%; }
25% { height: 80%; }
50% { height: 50%; }
75% { height: 100%; }
}
@keyframes eq-bar-2 {
0%, 100% { height: 50%; }
20% { height: 100%; }
45% { height: 30%; }
70% { height: 80%; }
}
@keyframes eq-bar-3 {
0%, 100% { height: 60%; }
30% { height: 40%; }
55% { height: 100%; }
80% { height: 45%; }
}
/* Gradient text shimmer (for headings) */
@keyframes gradient-shift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
/* Gentle card entrance — stagger-friendly */
@keyframes card-enter {
from { opacity: 0; transform: translateY(12px) scale(0.97); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
/* Ripple expand for empty states */
@keyframes ripple-expand {
0% { transform: scale(0.8); opacity: 0.4; }
50% { transform: scale(1.15); opacity: 0.15; }
100% { transform: scale(0.8); opacity: 0.4; }
}
/* ═══════════════════════════════════════════════════════════════════════════
SUMI-E ANIMATIONS — 墨絵 Ink Wash Effects
═══════════════════════════════════════════════════════════════════════════ */
/* Ink drop — element appears like ink hitting wet paper */
@keyframes sumi-ink-drop {
0% { transform: scale(0); opacity: 0; border-radius: 50%; }
35% { transform: scale(1.08); opacity: 0.85; border-radius: 48% 52% 50% 50%; }
55% { transform: scale(0.96); opacity: 1; }
75% { transform: scale(1.02); }
100% { transform: scale(1); opacity: 1; border-radius: inherit; }
}
/* Ink spread — circular reveal like ink spreading on washi */
@keyframes sumi-ink-spread {
0% { clip-path: circle(0% at 50% 50%); filter: blur(6px); opacity: 0; }
40% { filter: blur(2px); opacity: 0.8; }
100% { clip-path: circle(100% at 50% 50%); filter: blur(0); opacity: 1; }
}
/* Brush stroke reveal — horizontal wipe like a calligraphy stroke */
@keyframes sumi-brush-stroke {
0% { clip-path: inset(0 100% 0 0); opacity: 0.4; }
60% { clip-path: inset(0 8% 0 0); opacity: 0.9; }
100% { clip-path: inset(0 0 0 0); opacity: 1; }
}
/* Ink reveal — content emerges from darkness like ink on wet paper */
@keyframes sumi-ink-reveal {
0% { opacity: 0; transform: translateY(12px); filter: blur(8px) brightness(0.7); }
40% { filter: blur(3px) brightness(0.9); }
100% { opacity: 1; transform: translateY(0); filter: blur(0) brightness(1); }
}
/* Sumi-nagashi — floating ink marble drift */
@keyframes sumi-nagashi {
0% { background-position: 0% 50%; }
25% { background-position: 100% 25%; }
50% { background-position: 100% 75%; }
75% { background-position: 0% 60%; }
100% { background-position: 0% 50%; }
}
/* Ink pulse — like a droplet ripple in an ink stone (硯) */
@keyframes sumi-ink-pulse {
0% { box-shadow: 0 0 0 0 rgba(0,152,181, 0.25); }
50% { box-shadow: 0 0 0 8px rgba(0,152,181, 0); }
100% { box-shadow: 0 0 0 0 rgba(0,152,181, 0); }
}
/* ═══════════════════════════════════════════════════════════════════════════
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;
}
/* Keep opacity transitions for functional feedback */
.interactive { transition: opacity 100ms ease-out; }
}