feat: Thêm trang Mood Board mới và cập nhật giao diện người dùng, đồng thời loại bỏ các script cũ.
This commit is contained in:
@@ -117,7 +117,7 @@ export default function ForgotPasswordPage() {
|
||||
|
||||
<AuthControls />
|
||||
|
||||
<div className="w-full max-w-md space-y-8 relative z-10 glass-appear">
|
||||
<div className="w-full max-w-md md:max-w-[400px] space-y-8 relative z-10 glass-appear">
|
||||
<div className="text-center space-y-4">
|
||||
<div className="flex justify-center">
|
||||
<div className="p-3 rounded-2xl bg-accent-primary/5 border border-accent-primary/10 shadow-glass-sm">
|
||||
@@ -239,6 +239,7 @@ export default function ForgotPasswordPage() {
|
||||
|
||||
<Input
|
||||
type="email"
|
||||
variant="solid"
|
||||
label={t('auth.forgotPassword.email')}
|
||||
placeholder="you@example.com"
|
||||
isInvalid={!!errors.email}
|
||||
@@ -268,10 +269,10 @@ export default function ForgotPasswordPage() {
|
||||
: t('auth.forgotPassword.sendResetLink')}
|
||||
</Button>
|
||||
|
||||
<div className="text-center pt-4 border-t border-glass-subtle">
|
||||
<div className="text-center pt-4 border-t border-border-primary/50">
|
||||
<Link
|
||||
href="/login"
|
||||
className="text-sm font-medium text-accent-primary hover:text-accent-primary-hover transition-colors inline-flex items-center gap-2 group"
|
||||
className="inline-flex items-center justify-center gap-2 px-6 py-2.5 rounded-xl border border-border-primary bg-bg-secondary text-sm font-semibold text-text-primary hover:bg-bg-tertiary hover:border-border-secondary transition-all group"
|
||||
>
|
||||
<svg
|
||||
className="h-4 w-4 transform group-hover:-translate-x-1 transition-transform"
|
||||
|
||||
@@ -156,6 +156,7 @@ export default function LoginPage() {
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
type="email"
|
||||
variant="solid"
|
||||
label={t('auth.login.email')}
|
||||
placeholder="you@example.com"
|
||||
isInvalid={!!errors.email}
|
||||
@@ -177,6 +178,7 @@ export default function LoginPage() {
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
type="password"
|
||||
variant="solid"
|
||||
label={t('auth.login.password')}
|
||||
placeholder={t('auth.login.password')}
|
||||
isInvalid={!!errors.password}
|
||||
@@ -201,7 +203,7 @@ export default function LoginPage() {
|
||||
checked={field.value}
|
||||
onChange={field.onChange}
|
||||
onBlur={field.onBlur}
|
||||
className="w-4 h-4 rounded border-glass bg-glass-subtle text-accent-primary focus:ring-2 focus:ring-accent-primary focus:ring-offset-2 focus:ring-offset-bg-primary cursor-pointer transition-all"
|
||||
className="w-4 h-4 rounded border-border-primary bg-bg-secondary text-accent-primary focus:ring-2 focus:ring-accent-primary focus:ring-offset-2 focus:ring-offset-bg-primary cursor-pointer transition-all"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
@@ -220,7 +222,6 @@ export default function LoginPage() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* EN: Submit button with loading state / VI: Nút submit với trạng thái loading */}
|
||||
<Button
|
||||
type="submit"
|
||||
variant="brand"
|
||||
|
||||
@@ -272,6 +272,7 @@ export default function RegisterPage() {
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
type="text"
|
||||
variant="solid"
|
||||
label={t('auth.register.fullName')}
|
||||
placeholder="John Doe"
|
||||
isInvalid={!!errors.fullName}
|
||||
@@ -292,6 +293,7 @@ export default function RegisterPage() {
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
type="email"
|
||||
variant="solid"
|
||||
label={t('auth.register.email')}
|
||||
placeholder="you@example.com"
|
||||
isInvalid={!!errors.email}
|
||||
@@ -313,6 +315,7 @@ export default function RegisterPage() {
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
type="password"
|
||||
variant="solid"
|
||||
label={t('auth.register.password')}
|
||||
placeholder={t('auth.register.createStrongPassword')}
|
||||
isInvalid={!!errors.password}
|
||||
@@ -337,7 +340,7 @@ export default function RegisterPage() {
|
||||
'h-1 flex-1 rounded-full transition-all duration-500',
|
||||
passwordStrength.percentage >= step * 25
|
||||
? getStrengthColor(passwordStrength.strength)
|
||||
: 'bg-glass-subtle'
|
||||
: 'bg-bg-tertiary'
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
@@ -365,6 +368,7 @@ export default function RegisterPage() {
|
||||
render={({ field }) => (
|
||||
<Input
|
||||
type="password"
|
||||
variant="solid"
|
||||
label={t('auth.register.confirmPassword')}
|
||||
placeholder={t('auth.register.reEnterPassword')}
|
||||
isInvalid={!!errors.confirmPassword}
|
||||
@@ -390,7 +394,7 @@ export default function RegisterPage() {
|
||||
checked={field.value}
|
||||
onChange={field.onChange}
|
||||
onBlur={field.onBlur}
|
||||
className="mt-1 w-4 h-4 rounded border-glass bg-glass-subtle text-accent-primary focus:ring-2 focus:ring-accent-primary focus:ring-offset-2 focus:ring-offset-bg-primary cursor-pointer flex-shrink-0 transition-all"
|
||||
className="mt-1 w-4 h-4 rounded border-border-primary bg-bg-secondary text-accent-primary focus:ring-2 focus:ring-accent-primary focus:ring-offset-2 focus:ring-offset-bg-primary cursor-pointer flex-shrink-0 transition-all"
|
||||
aria-required="true"
|
||||
aria-invalid={errors.terms ? 'true' : 'false'}
|
||||
/>
|
||||
@@ -424,13 +428,14 @@ export default function RegisterPage() {
|
||||
type="submit"
|
||||
variant="brand"
|
||||
size="lg"
|
||||
className="w-full mt-4"
|
||||
fullWidth
|
||||
className="mt-2"
|
||||
isLoading={isLoading || isSubmitting}
|
||||
isDisabled={isLoading || isSubmitting}
|
||||
>
|
||||
{isLoading || isSubmitting
|
||||
? t('auth.register.creatingAccount')
|
||||
: t('auth.register.createAccount')}
|
||||
? t('auth.register.signingUp')
|
||||
: t('auth.register.title')}
|
||||
</Button>
|
||||
</form>
|
||||
</AuthCard>
|
||||
|
||||
56
apps/web-client/src/app/mood-board/page.tsx
Normal file
56
apps/web-client/src/app/mood-board/page.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* EN: Mood Board - Design System Showcase
|
||||
* VI: Mood Board - Showcase Design System
|
||||
*
|
||||
* A comprehensive showcase of all UI components and design tokens
|
||||
* Một trang showcase toàn diện cho tất cả UI components và design tokens
|
||||
*/
|
||||
import { Metadata } from 'next';
|
||||
import { ThemeToggle } from '@/features/theme/components/theme-toggle-enhanced';
|
||||
import TypographySection from './sections/TypographySection';
|
||||
import ColorPaletteSection from './sections/ColorPaletteSection';
|
||||
import SpacingSection from './sections/SpacingSection';
|
||||
import GlassEffectsSection from './sections/GlassEffectsSection';
|
||||
import ButtonsSection from './sections/ButtonsSection';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Mood Board | Design System Showcase',
|
||||
description: 'Comprehensive showcase of all UI components and design tokens',
|
||||
};
|
||||
|
||||
export default function MoodBoardPage() {
|
||||
return (
|
||||
<div className="min-h-screen bg-bg-primary">
|
||||
{/* EN: Header with sticky navigation / VI: Header với navigation sticky */}
|
||||
<header className="glass-nav sticky top-0 z-50 p-6">
|
||||
<div className="container-responsive flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-4xl font-extrabold tracking-tight text-text-primary">
|
||||
Mood Board
|
||||
</h1>
|
||||
<p className="text-text-secondary mt-2">
|
||||
Design System Showcase - All Components & Tokens
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* EN: Theme Toggle / VI: Theme Toggle */}
|
||||
<ThemeToggle />
|
||||
</div>
|
||||
</header>
|
||||
|
||||
{/* EN: Main content with all sections / VI: Nội dung chính với tất cả sections */}
|
||||
<main className="container-responsive py-12 space-y-16">
|
||||
<TypographySection />
|
||||
<ColorPaletteSection />
|
||||
<SpacingSection />
|
||||
<GlassEffectsSection />
|
||||
<ButtonsSection />
|
||||
</main>
|
||||
|
||||
{/* EN: Footer / VI: Footer */}
|
||||
<footer className="border-t border-border-primary p-6 text-center text-text-tertiary">
|
||||
<p>Design System v1.0.0 - X.ai Minimalist Theme</p>
|
||||
</footer>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
113
apps/web-client/src/app/mood-board/sections/ButtonsSection.tsx
Normal file
113
apps/web-client/src/app/mood-board/sections/ButtonsSection.tsx
Normal file
@@ -0,0 +1,113 @@
|
||||
/**
|
||||
* EN: Buttons Section - All button variants
|
||||
* VI: Buttons Section - Tất cả button variants
|
||||
*/
|
||||
import SectionWrapper from '@/features/mood-board/components/SectionWrapper';
|
||||
import ComponentShowcase from '@/features/mood-board/components/ComponentShowcase';
|
||||
|
||||
export default function ButtonsSection() {
|
||||
return (
|
||||
<SectionWrapper
|
||||
id="buttons"
|
||||
title="Buttons"
|
||||
description="All button styles and states"
|
||||
>
|
||||
{/* EN: Glass Buttons / VI: Glass Buttons */}
|
||||
<ComponentShowcase
|
||||
title="Glass Buttons"
|
||||
description="Glassmorphism button styles"
|
||||
code={`<button className="glass-button">Default Glass</button>
|
||||
<button className="glass-button" disabled>Disabled</button>`}
|
||||
darkBg
|
||||
>
|
||||
<div className="flex gap-4 flex-wrap">
|
||||
<button className="glass-button">Default Glass</button>
|
||||
<button className="glass-button hover:bg-glass-bg-hover">
|
||||
Hover Me
|
||||
</button>
|
||||
<button className="glass-button" disabled>
|
||||
Disabled
|
||||
</button>
|
||||
</div>
|
||||
</ComponentShowcase>
|
||||
|
||||
{/* EN: Primary Buttons / VI: Primary Buttons */}
|
||||
<ComponentShowcase
|
||||
title="Primary Buttons (Accent)"
|
||||
description="Brand primary button with X.ai blue"
|
||||
code={`<button className="bg-accent-primary text-white px-6 py-3 rounded-md font-medium hover:bg-accent-primary-hover transition-all">
|
||||
Primary Action
|
||||
</button>`}
|
||||
>
|
||||
<div className="flex gap-4 flex-wrap">
|
||||
<button className="bg-accent-primary text-white px-6 py-3 rounded-md font-medium hover:bg-accent-primary-hover transition-all btn-press">
|
||||
Primary Action
|
||||
</button>
|
||||
<button className="bg-accent-primary text-white px-6 py-3 rounded-md font-medium opacity-50 cursor-not-allowed">
|
||||
Disabled Primary
|
||||
</button>
|
||||
</div>
|
||||
</ComponentShowcase>
|
||||
|
||||
{/* EN: Secondary Buttons / VI: Secondary Buttons */}
|
||||
<ComponentShowcase
|
||||
title="Secondary Buttons"
|
||||
description="Secondary button style with border"
|
||||
code={`<button className="border border-border-primary text-text-primary px-6 py-3 rounded-md font-medium hover:border-border-secondary transition-all">
|
||||
Secondary Action
|
||||
</button>`}
|
||||
>
|
||||
<div className="flex gap-4 flex-wrap">
|
||||
<button className="border border-border-primary text-text-primary px-6 py-3 rounded-md font-medium hover:border-border-secondary hover:bg-bg-secondary transition-all btn-press">
|
||||
Secondary Action
|
||||
</button>
|
||||
<button className="border border-border-primary text-text-primary px-6 py-3 rounded-md font-medium opacity-50 cursor-not-allowed">
|
||||
Disabled Secondary
|
||||
</button>
|
||||
</div>
|
||||
</ComponentShowcase>
|
||||
|
||||
{/* EN: Button Sizes / VI: Button Sizes */}
|
||||
<ComponentShowcase
|
||||
title="Button Sizes"
|
||||
description="Different button size variants"
|
||||
code={`<button className="glass-button text-xs px-3 py-1.5">Small</button>
|
||||
<button className="glass-button text-sm px-4 py-2">Medium</button>
|
||||
<button className="glass-button text-base px-6 py-3">Large</button>`}
|
||||
darkBg
|
||||
>
|
||||
<div className="flex gap-4 items-center flex-wrap">
|
||||
<button className="glass-button text-xs px-3 py-1.5">Small</button>
|
||||
<button className="glass-button text-sm px-4 py-2">Medium</button>
|
||||
<button className="glass-button text-base px-6 py-3">Large</button>
|
||||
<button className="glass-button text-lg px-8 py-4">Extra Large</button>
|
||||
</div>
|
||||
</ComponentShowcase>
|
||||
|
||||
{/* EN: Icon Buttons / VI: Icon Buttons */}
|
||||
<ComponentShowcase
|
||||
title="Icon Buttons"
|
||||
description="Square icon buttons for actions"
|
||||
code={`<button className="glass-button w-10 h-10 flex items-center justify-center">
|
||||
×
|
||||
</button>`}
|
||||
darkBg
|
||||
>
|
||||
<div className="flex gap-4 flex-wrap">
|
||||
<button className="glass-button w-10 h-10 flex items-center justify-center">
|
||||
×
|
||||
</button>
|
||||
<button className="glass-button w-10 h-10 flex items-center justify-center">
|
||||
✓
|
||||
</button>
|
||||
<button className="glass-button w-10 h-10 flex items-center justify-center">
|
||||
⋮
|
||||
</button>
|
||||
<button className="glass-button w-12 h-12 flex items-center justify-center rounded-full">
|
||||
✎
|
||||
</button>
|
||||
</div>
|
||||
</ComponentShowcase>
|
||||
</SectionWrapper>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* EN: Color Palette Section - All design token colors
|
||||
* VI: Color Palette Section - Tất cả color tokens từ design system
|
||||
*/
|
||||
import SectionWrapper from '@/features/mood-board/components/SectionWrapper';
|
||||
import ColorSwatch from '@/features/mood-board/components/ColorSwatch';
|
||||
|
||||
export default function ColorPaletteSection() {
|
||||
return (
|
||||
<SectionWrapper
|
||||
id="colors"
|
||||
title="Color Palette"
|
||||
description="All color tokens - backgrounds, text, borders, accents (Dark/Light mode)"
|
||||
>
|
||||
{/* EN: Background Colors / VI: Màu nền */}
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold mb-4 text-text-primary">
|
||||
Background Colors
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<ColorSwatch name="--bg-primary" var="var(--bg-primary)" />
|
||||
<ColorSwatch name="--bg-secondary" var="var(--bg-secondary)" />
|
||||
<ColorSwatch name="--bg-tertiary" var="var(--bg-tertiary)" />
|
||||
<ColorSwatch name="--bg-elevated" var="var(--bg-elevated)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* EN: Text Colors / VI: Màu chữ */}
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold mb-4 text-text-primary">
|
||||
Text Colors (WCAG Compliant)
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
<ColorSwatch name="--text-primary" var="var(--text-primary)" />
|
||||
<ColorSwatch name="--text-secondary" var="var(--text-secondary)" />
|
||||
<ColorSwatch name="--text-tertiary" var="var(--text-tertiary)" />
|
||||
<ColorSwatch name="--text-muted" var="var(--text-muted)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* EN: Accent Colors / VI: Màu accent */}
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold mb-4 text-text-primary">
|
||||
Accent Colors (X.ai Brand)
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-5 gap-4">
|
||||
<ColorSwatch name="--accent-primary" var="var(--accent-primary)" />
|
||||
<ColorSwatch name="--accent-success" var="var(--accent-success)" />
|
||||
<ColorSwatch name="--accent-warning" var="var(--accent-warning)" />
|
||||
<ColorSwatch name="--accent-error" var="var(--accent-error)" />
|
||||
<ColorSwatch name="--accent-info" var="var(--accent-info)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* EN: Border Colors / VI: Màu viền */}
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold mb-4 text-text-primary">
|
||||
Border Colors
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||
<ColorSwatch name="--border-primary" var="var(--border-primary)" />
|
||||
<ColorSwatch
|
||||
name="--border-secondary"
|
||||
var="var(--border-secondary)"
|
||||
/>
|
||||
<ColorSwatch name="--border-focus" var="var(--border-focus)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* EN: Chat Colors / VI: Màu chat */}
|
||||
<div>
|
||||
<h3 className="text-xl font-semibold mb-4 text-text-primary">
|
||||
Chat Specific Colors
|
||||
</h3>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||
<ColorSwatch name="--chat-user-bubble" var="var(--chat-user-bubble)" />
|
||||
<ColorSwatch name="--chat-user-text" var="var(--chat-user-text)" />
|
||||
<ColorSwatch name="--chat-ai-text" var="var(--chat-ai-text)" />
|
||||
</div>
|
||||
</div>
|
||||
</SectionWrapper>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* EN: Glass Effects Section - All glassmorphism utilities
|
||||
* VI: Glass Effects Section - Tất cả glassmorphism utilities
|
||||
*/
|
||||
import SectionWrapper from '@/features/mood-board/components/SectionWrapper';
|
||||
import ComponentShowcase from '@/features/mood-board/components/ComponentShowcase';
|
||||
|
||||
export default function GlassEffectsSection() {
|
||||
return (
|
||||
<SectionWrapper
|
||||
id="glass-effects"
|
||||
title="Glassmorphism Effects"
|
||||
description="All glass utilities from glass.css (X.ai Minimal Style)"
|
||||
>
|
||||
{/* EN: Glass Card / VI: Glass Card */}
|
||||
<ComponentShowcase
|
||||
title="Glass Card"
|
||||
description="Default glass card with subtle blur and border"
|
||||
code={`<div className="glass-card p-6">
|
||||
<h3 className="text-xl font-semibold">Glass Card</h3>
|
||||
<p>Ultra-minimal glassmorphism effect</p>
|
||||
</div>`}
|
||||
darkBg
|
||||
>
|
||||
<div className="glass-card p-6 max-w-md">
|
||||
<h3 className="text-xl font-semibold text-text-primary mb-2">
|
||||
Glass Card
|
||||
</h3>
|
||||
<p className="text-text-secondary">
|
||||
Ultra-minimal glassmorphism effect with 4% opacity background and 8px
|
||||
blur.
|
||||
</p>
|
||||
</div>
|
||||
</ComponentShowcase>
|
||||
|
||||
{/* EN: Glass Variants / VI: Glass Variants */}
|
||||
<ComponentShowcase
|
||||
title="Glass Variants"
|
||||
description="Different glass effect intensities"
|
||||
code={`<div className="glass-subtle">Subtle (1% bg, 4px blur)</div>
|
||||
<div className="glass-card">Default (4% bg, 8px blur)</div>
|
||||
<div className="glass-strong">Strong (5% bg, 12px blur)</div>`}
|
||||
darkBg
|
||||
>
|
||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||
<div className="glass-subtle p-6 rounded-lg border border-glass-border-subtle">
|
||||
<h4 className="font-semibold text-text-primary mb-2">
|
||||
Glass Subtle
|
||||
</h4>
|
||||
<p className="text-sm text-text-secondary">
|
||||
1% bg, 4px blur
|
||||
</p>
|
||||
</div>
|
||||
<div className="glass-card p-6">
|
||||
<h4 className="font-semibold text-text-primary mb-2">
|
||||
Glass Card (Default)
|
||||
</h4>
|
||||
<p className="text-sm text-text-secondary">
|
||||
4% bg, 8px blur
|
||||
</p>
|
||||
</div>
|
||||
<div className="glass-strong p-6 rounded-lg">
|
||||
<h4 className="font-semibold text-text-primary mb-2">
|
||||
Glass Strong
|
||||
</h4>
|
||||
<p className="text-sm text-text-secondary">
|
||||
5% bg, 12px blur
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</ComponentShowcase>
|
||||
|
||||
{/* EN: Glass Button / VI: Glass Button */}
|
||||
<ComponentShowcase
|
||||
title="Glass Button"
|
||||
description="Interactive glass button with hover/active states"
|
||||
code={`<button className="glass-button">
|
||||
Click Me
|
||||
</button>`}
|
||||
darkBg
|
||||
>
|
||||
<div className="flex gap-4 flex-wrap">
|
||||
<button className="glass-button">Default Button</button>
|
||||
<button className="glass-button" disabled>
|
||||
Disabled Button
|
||||
</button>
|
||||
<button className="glass-button px-6 py-3">Larger Button</button>
|
||||
</div>
|
||||
</ComponentShowcase>
|
||||
|
||||
{/* EN: Glass Input / VI: Glass Input */}
|
||||
<ComponentShowcase
|
||||
title="Glass Input"
|
||||
description="Glass input with focus states"
|
||||
code={`<input
|
||||
type="text"
|
||||
placeholder="Enter text..."
|
||||
className="glass-input w-full rounded-md px-4 py-2"
|
||||
/>`}
|
||||
darkBg
|
||||
>
|
||||
<div className="space-y-4 max-w-md">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="Glass input..."
|
||||
className="glass-input w-full rounded-md px-4 py-2 text-text-primary"
|
||||
/>
|
||||
<input
|
||||
type="email"
|
||||
placeholder="Email address..."
|
||||
className="glass-input w-full rounded-md px-4 py-2 text-text-primary"
|
||||
/>
|
||||
</div>
|
||||
</ComponentShowcase>
|
||||
|
||||
{/* EN: Glass Badge / VI: Glass Badge */}
|
||||
<ComponentShowcase
|
||||
title="Glass Badge"
|
||||
description="Small glass badges for tags and labels"
|
||||
code={`<span className="glass-badge">Design</span>
|
||||
<span className="glass-badge">Development</span>`}
|
||||
darkBg
|
||||
>
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
<span className="glass-badge">Design</span>
|
||||
<span className="glass-badge">Development</span>
|
||||
<span className="glass-badge">X.ai Minimal</span>
|
||||
<span className="glass-badge">Glassmorphism</span>
|
||||
</div>
|
||||
</ComponentShowcase>
|
||||
</SectionWrapper>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* EN: Spacing Section - Spacing scale and layout tokens
|
||||
* VI: Spacing Section - Spacing scale và layout tokens
|
||||
*/
|
||||
import SectionWrapper from '@/features/mood-board/components/SectionWrapper';
|
||||
import ComponentShowcase from '@/features/mood-board/components/ComponentShowcase';
|
||||
|
||||
export default function SpacingSection() {
|
||||
return (
|
||||
<SectionWrapper
|
||||
id="spacing"
|
||||
title="Spacing & Layout"
|
||||
description="Spacing scale (4px base unit) and layout tokens"
|
||||
>
|
||||
{/* EN: Spacing Scale / VI: Spacing Scale */}
|
||||
<ComponentShowcase
|
||||
title="Spacing Scale"
|
||||
description="8-point grid system from --space-0 to --space-20"
|
||||
code={`<div className="space-y-1">0.25rem (4px)</div>
|
||||
<div className="space-y-2">0.5rem (8px)</div>
|
||||
<div className="space-y-4">1rem (16px)</div>
|
||||
<div className="space-y-8">2rem (32px)</div>`}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
{[
|
||||
{ name: '--space-0', value: '0', px: '0px' },
|
||||
{ name: '--space-1', value: '0.25rem', px: '4px' },
|
||||
{ name: '--space-2', value: '0.5rem', px: '8px' },
|
||||
{ name: '--space-3', value: '0.75rem', px: '12px' },
|
||||
{ name: '--space-4', value: '1rem', px: '16px' },
|
||||
{ name: '--space-5', value: '1.25rem', px: '20px' },
|
||||
{ name: '--space-6', value: '1.5rem', px: '24px' },
|
||||
{ name: '--space-8', value: '2rem', px: '32px' },
|
||||
{ name: '--space-10', value: '2.5rem', px: '40px' },
|
||||
{ name: '--space-12', value: '3rem', px: '48px' },
|
||||
{ name: '--space-16', value: '4rem', px: '64px' },
|
||||
{ name: '--space-20', value: '5rem', px: '80px' },
|
||||
].map((token) => (
|
||||
<div key={token.name} className="flex items-center gap-4">
|
||||
<div
|
||||
className="bg-accent-primary rounded"
|
||||
style={{
|
||||
width: token.value,
|
||||
height: '24px',
|
||||
minWidth: '4px',
|
||||
}}
|
||||
/>
|
||||
<div>
|
||||
<code className="text-sm font-mono text-text-primary">
|
||||
{token.name}
|
||||
</code>
|
||||
<span className="text-text-tertiary text-sm ml-2">
|
||||
{token.value} ({token.px})
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ComponentShowcase>
|
||||
|
||||
{/* EN: Border Radius / VI: Border Radius */}
|
||||
<ComponentShowcase
|
||||
title="Border Radius"
|
||||
description="Minimal roundness for X.ai aesthetic"
|
||||
code={`<div className="rounded-sm">2px - Sharp</div>
|
||||
<div className="rounded-md">4px - Buttons</div>
|
||||
<div className="rounded-lg">8px - Cards</div>
|
||||
<div className="rounded-full">9999px - Avatars</div>`}
|
||||
>
|
||||
<div className="grid grid-cols-2 md:grid-cols-3 gap-4">
|
||||
{[
|
||||
{ name: '--radius-sm', value: '2px', class: 'rounded-sm' },
|
||||
{ name: '--radius-md', value: '4px', class: 'rounded-md' },
|
||||
{ name: '--radius-lg', value: '8px', class: 'rounded-lg' },
|
||||
{ name: '--radius-xl', value: '12px', class: 'rounded-xl' },
|
||||
{ name: '--radius-2xl', value: '16px', class: 'rounded-2xl' },
|
||||
{ name: '--radius-full', value: '9999px', class: 'rounded-full' },
|
||||
].map((radius) => (
|
||||
<div key={radius.name} className="glass-card p-4">
|
||||
<div
|
||||
className={`bg-accent-primary w-full h-20 ${radius.class} mb-2`}
|
||||
/>
|
||||
<p className="text-sm font-medium text-text-primary">
|
||||
{radius.name}
|
||||
</p>
|
||||
<code className="text-xs text-text-tertiary font-mono">
|
||||
{radius.value}
|
||||
</code>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</ComponentShowcase>
|
||||
</SectionWrapper>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* EN: Typography Section - Font scales, weights, letter spacing showcase
|
||||
* VI: Typography Section - Showcase font scales, weights, letter spacing
|
||||
*/
|
||||
import SectionWrapper from '@/features/mood-board/components/SectionWrapper';
|
||||
import ComponentShowcase from '@/features/mood-board/components/ComponentShowcase';
|
||||
|
||||
export default function TypographySection() {
|
||||
return (
|
||||
<SectionWrapper
|
||||
id="typography"
|
||||
title="Typography"
|
||||
description="Font scales, weights, line heights, and letter spacing from Design System"
|
||||
>
|
||||
{/* EN: Font Sizes / VI: Font Sizes */}
|
||||
<ComponentShowcase
|
||||
title="Type Scale"
|
||||
description="All font sizes from --text-xs (12px) to --text-6xl (56px)"
|
||||
code={`<h1 className="text-6xl font-extrabold">Hero Title (56px)</h1>
|
||||
<h2 className="text-5xl font-bold">Page Title (44px)</h2>
|
||||
<h3 className="text-4xl font-semibold">Section Header (36px)</h3>
|
||||
<p className="text-base">Body Text (16px)</p>
|
||||
<span className="text-xs">Caption (12px)</span>`}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<p className="text-6xl font-extrabold text-text-primary">
|
||||
Hero Title (--text-6xl)
|
||||
</p>
|
||||
<p className="text-5xl font-bold text-text-primary">
|
||||
Page Title (--text-5xl)
|
||||
</p>
|
||||
<p className="text-4xl font-semibold text-text-primary">
|
||||
Section Header (--text-4xl)
|
||||
</p>
|
||||
<p className="text-3xl font-semibold text-text-primary">
|
||||
Card Header (--text-3xl)
|
||||
</p>
|
||||
<p className="text-2xl text-text-primary">Large Body (--text-2xl)</p>
|
||||
<p className="text-xl text-text-primary">Emphasized (--text-xl)</p>
|
||||
<p className="text-lg text-text-primary">Large Body (--text-lg)</p>
|
||||
<p className="text-base text-text-primary">
|
||||
Default Body (--text-base)
|
||||
</p>
|
||||
<p className="text-sm text-text-secondary">Small Text (--text-sm)</p>
|
||||
<p className="text-xs text-text-tertiary">Caption (--text-xs)</p>
|
||||
</div>
|
||||
</ComponentShowcase>
|
||||
|
||||
{/* EN: Font Weights / VI: Font Weights */}
|
||||
<ComponentShowcase
|
||||
title="Font Weights"
|
||||
description="All weight variants for typography hierarchy (100-900)"
|
||||
code={`<p className="font-thin">Thin (100)</p>
|
||||
<p className="font-light">Light (300)</p>
|
||||
<p className="font-normal">Normal (400)</p>
|
||||
<p className="font-medium">Medium (500)</p>
|
||||
<p className="font-semibold">Semibold (600)</p>
|
||||
<p className="font-bold">Bold (700)</p>
|
||||
<p className="font-extrabold">Extra Bold (800)</p>
|
||||
<p className="font-black">Black (900)</p>`}
|
||||
>
|
||||
<div className="space-y-2 text-2xl text-text-primary">
|
||||
<p className="font-thin">Thin (--font-thin: 100)</p>
|
||||
<p className="font-extralight">
|
||||
Extra Light (--font-extralight: 200)
|
||||
</p>
|
||||
<p className="font-light">Light (--font-light: 300)</p>
|
||||
<p className="font-normal">Normal (--font-normal: 400)</p>
|
||||
<p className="font-medium">Medium (--font-medium: 500)</p>
|
||||
<p className="font-semibold">Semibold (--font-semibold: 600)</p>
|
||||
<p className="font-bold">Bold (--font-bold: 700)</p>
|
||||
<p className="font-extrabold">Extra Bold (--font-extrabold: 800)</p>
|
||||
<p className="font-black">Black (--font-black: 900)</p>
|
||||
</div>
|
||||
</ComponentShowcase>
|
||||
|
||||
{/* EN: Letter Spacing / VI: Letter Spacing */}
|
||||
<ComponentShowcase
|
||||
title="Letter Spacing (Tracking)"
|
||||
description="Tracking variants for clean, minimal aesthetics"
|
||||
code={`<p className="tracking-tighter">Very Tight (-0.04em)</p>
|
||||
<p className="tracking-tight">Tight (-0.02em)</p>
|
||||
<p className="tracking-normal">Normal (0)</p>
|
||||
<p className="tracking-wide">Wide (0.02em)</p>
|
||||
<p className="tracking-wider">Wider (0.04em)</p>`}
|
||||
>
|
||||
<div className="space-y-2 text-2xl text-text-primary">
|
||||
<p className="tracking-tighter">
|
||||
Very Tight (--tracking-tighter: -0.04em)
|
||||
</p>
|
||||
<p className="tracking-tight">
|
||||
Tight (--tracking-tight: -0.02em)
|
||||
</p>
|
||||
<p className="tracking-normal">Normal (--tracking-normal: 0)</p>
|
||||
<p className="tracking-wide">Wide (--tracking-wide: 0.02em)</p>
|
||||
<p className="tracking-wider">Wider (--tracking-wider: 0.04em)</p>
|
||||
</div>
|
||||
</ComponentShowcase>
|
||||
</SectionWrapper>
|
||||
);
|
||||
}
|
||||
@@ -25,7 +25,7 @@ export function AuthCard({
|
||||
}: AuthCardProps) {
|
||||
return (
|
||||
<div className="flex min-h-[calc(100vh-3.5rem)] flex-col items-center justify-center py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div className="w-full max-w-md space-y-8 flex flex-col items-center">
|
||||
<div className="w-full max-w-md md:max-w-[400px] space-y-8 flex flex-col items-center">
|
||||
{/* EN: Logo & Header / VI: Logo & Tiêu đề */}
|
||||
<div className="flex flex-col items-center">
|
||||
<BrandLogo variant="icon" size="lg" className="mb-6" />
|
||||
@@ -48,16 +48,9 @@ export function AuthCard({
|
||||
{children}
|
||||
|
||||
{footer && (
|
||||
<>
|
||||
<div className="relative my-8">
|
||||
<div className="absolute inset-0 flex items-center" aria-hidden="true">
|
||||
<div className="w-full border-t border-border-primary"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-center text-sm text-text-secondary">
|
||||
{footer}
|
||||
</div>
|
||||
</>
|
||||
<div className="text-center text-sm text-text-secondary mt-8">
|
||||
{footer}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* EN: Code block with syntax highlighting and copy-to-clipboard
|
||||
* VI: Code block với syntax highlighting và copy-to-clipboard
|
||||
*
|
||||
* Displays formatted code with copy functionality
|
||||
* Hiển thị code đã format với tính năng copy
|
||||
*/
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
|
||||
interface CodeBlockProps {
|
||||
code: string;
|
||||
language: string;
|
||||
}
|
||||
|
||||
export default function CodeBlock({ code, language }: CodeBlockProps) {
|
||||
const [copied, setCopied] = useState(false);
|
||||
|
||||
const handleCopy = async () => {
|
||||
await navigator.clipboard.writeText(code);
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
{/* EN: Copy Button / VI: Nút copy */}
|
||||
<button
|
||||
onClick={handleCopy}
|
||||
className="absolute top-2 right-2 glass-button text-xs px-3 py-1.5"
|
||||
>
|
||||
{copied ? '✓ Copied!' : 'Copy'}
|
||||
</button>
|
||||
|
||||
{/* EN: Code Display / VI: Hiển thị code */}
|
||||
<pre className="bg-bg-tertiary rounded-lg p-4 overflow-x-auto">
|
||||
<code className="text-sm text-text-primary font-mono">{code}</code>
|
||||
</pre>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* EN: Color swatch component to display CSS variable colors
|
||||
* VI: Component color swatch để hiển thị màu từ CSS variable
|
||||
*
|
||||
* Shows color preview with variable name and value
|
||||
* Hiển thị preview màu với tên biến và giá trị
|
||||
*/
|
||||
'use client';
|
||||
|
||||
interface ColorSwatchProps {
|
||||
name: string;
|
||||
var: string;
|
||||
}
|
||||
|
||||
export default function ColorSwatch({ name, var: cssVar }: ColorSwatchProps) {
|
||||
return (
|
||||
<div className="glass-card p-4">
|
||||
{/* EN: Color Preview Box / VI: Hộp preview màu */}
|
||||
<div
|
||||
className="w-full h-24 rounded-lg mb-3 border border-border-primary"
|
||||
style={{ backgroundColor: cssVar }}
|
||||
/>
|
||||
|
||||
{/* EN: Color Variable Name / VI: Tên biến màu */}
|
||||
<p className="text-sm font-medium text-text-primary">{name}</p>
|
||||
|
||||
{/* EN: CSS Variable / VI: CSS Variable */}
|
||||
<code className="text-xs text-text-tertiary font-mono">{cssVar}</code>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* EN: Component showcase wrapper with code preview
|
||||
* VI: Wrapper showcase component kèm code preview
|
||||
*
|
||||
* Displays component demo with optional code snippet
|
||||
* Hiển thị demo component với tùy chọn xem code snippet
|
||||
*/
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import CodeBlock from './CodeBlock';
|
||||
|
||||
interface ComponentShowcaseProps {
|
||||
title: string;
|
||||
description?: string;
|
||||
code: string;
|
||||
children: React.ReactNode;
|
||||
darkBg?: boolean; // EN: For light components on dark background / VI: Cho light components trên dark background
|
||||
}
|
||||
|
||||
export default function ComponentShowcase({
|
||||
title,
|
||||
description,
|
||||
code,
|
||||
children,
|
||||
darkBg = false,
|
||||
}: ComponentShowcaseProps) {
|
||||
const [showCode, setShowCode] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="glass-card p-6">
|
||||
{/* EN: Title and description / VI: Tiêu đề và mô tả */}
|
||||
<div className="mb-4">
|
||||
<h3 className="text-xl font-semibold text-text-primary">{title}</h3>
|
||||
{description && (
|
||||
<p className="text-text-tertiary text-sm mt-1">{description}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* EN: Component Preview Area / VI: Khu vực preview component */}
|
||||
<div
|
||||
className={`rounded-lg border border-border-primary p-8 mb-4 ${darkBg ? 'bg-bg-primary' : 'bg-bg-secondary'
|
||||
}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
{/* EN: Toggle Code Button / VI: Nút toggle code */}
|
||||
<button
|
||||
onClick={() => setShowCode(!showCode)}
|
||||
className="glass-button text-sm"
|
||||
>
|
||||
{showCode ? 'Hide Code' : 'Show Code'}
|
||||
</button>
|
||||
|
||||
{/* EN: Code Block / VI: Code block */}
|
||||
{showCode && (
|
||||
<div className="mt-4">
|
||||
<CodeBlock code={code} language="tsx" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* EN: Section wrapper component for Mood Board sections
|
||||
* VI: Component wrapper cho các sections của Mood Board
|
||||
*
|
||||
* Provides consistent layout and styling for each section
|
||||
* Cung cấp layout và styling nhất quán cho mỗi section
|
||||
*/
|
||||
import React from 'react';
|
||||
|
||||
interface SectionWrapperProps {
|
||||
id: string;
|
||||
title: string;
|
||||
description?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export default function SectionWrapper({
|
||||
id,
|
||||
title,
|
||||
description,
|
||||
children,
|
||||
}: SectionWrapperProps) {
|
||||
return (
|
||||
<section id={id} className="scroll-mt-24">
|
||||
{/* EN: Section Header / VI: Header của section */}
|
||||
<div className="mb-8">
|
||||
<h2 className="text-3xl font-bold tracking-tight text-text-primary">
|
||||
{title}
|
||||
</h2>
|
||||
{description && (
|
||||
<p className="text-text-secondary mt-2">{description}</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* EN: Section Content / VI: Nội dung section */}
|
||||
<div className="space-y-6">{children}</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -79,13 +79,15 @@ const buttonVariants = cva(
|
||||
'shadow-glass-md hover:shadow-glass-lg',
|
||||
'hover:bg-glass-hover',
|
||||
],
|
||||
// Brand (X.ai blue - solid color, no gradient)
|
||||
// Brand (Subtle dark glass - X.ai minimal)
|
||||
brand: [
|
||||
'bg-accent-primary text-white',
|
||||
'shadow-md hover:shadow-lg',
|
||||
'hover:bg-accent-primary-hover',
|
||||
'transition-colors duration-quick',
|
||||
'focus-visible:ring-2 focus-visible:ring-accent-primary/30',
|
||||
'bg-[#1f2f3d]/95 text-white',
|
||||
'backdrop-blur-glass border border-white/10',
|
||||
'shadow-[0_2px_8px_rgba(0,0,0,0.4),inset_0_1px_1px_rgba(255,255,255,0.1)]',
|
||||
'hover:bg-[#243442] hover:border-white/15',
|
||||
'hover:shadow-[0_4px_12px_rgba(0,0,0,0.5),inset_0_1px_1px_rgba(255,255,255,0.15)]',
|
||||
'transition-all duration-quick',
|
||||
'focus-visible:ring-2 focus-visible:ring-white/20',
|
||||
'focus-visible:ring-offset-2 focus-visible:ring-offset-bg-primary',
|
||||
],
|
||||
},
|
||||
|
||||
@@ -53,6 +53,12 @@ export interface InputProps extends Omit<AriaTextFieldProps, 'children'> {
|
||||
*/
|
||||
type?: 'text' | 'email' | 'password' | 'search' | 'tel' | 'url' | 'number';
|
||||
|
||||
/**
|
||||
* Input variant
|
||||
* @default 'glass'
|
||||
*/
|
||||
variant?: 'glass' | 'solid';
|
||||
|
||||
/**
|
||||
* Custom className for the container
|
||||
*/
|
||||
@@ -121,6 +127,7 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
errorMessage,
|
||||
placeholder,
|
||||
type = 'text',
|
||||
variant = 'glass',
|
||||
className,
|
||||
inputClassName,
|
||||
leftElement,
|
||||
@@ -194,8 +201,8 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
type={inputType}
|
||||
placeholder={placeholder}
|
||||
className={cn(
|
||||
// Base glass input styles
|
||||
'glass-input',
|
||||
// Base input styles
|
||||
variant === 'glass' ? 'glass-input' : 'solid-input',
|
||||
'w-full rounded-xl px-3 py-2',
|
||||
'text-base text-text-primary placeholder:text-text-tertiary',
|
||||
'transition-all duration-quick ease-smooth',
|
||||
@@ -203,7 +210,8 @@ export const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
// Focus state with X.ai blue ring (X.ai Minimal Design)
|
||||
'focus:outline-none focus:ring-2 focus:ring-offset-2',
|
||||
'focus:ring-accent-primary/30 focus:ring-offset-bg-primary',
|
||||
'focus:bg-glass focus:border-accent-primary',
|
||||
variant === 'glass' && 'focus:bg-glass focus:border-accent-primary',
|
||||
variant === 'solid' && 'focus:bg-bg-primary focus:border-accent-primary',
|
||||
|
||||
// Invalid state
|
||||
'data-[invalid]:border-accent-error',
|
||||
|
||||
@@ -55,7 +55,7 @@ export function LanguageSwitcher() {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-text-primary border border-border-primary hover:bg-glass-hover transition-all gap-2"
|
||||
className="text-text-primary border border-border-primary hover:bg-bg-secondary transition-all gap-2"
|
||||
aria-label="Change language"
|
||||
>
|
||||
<span className="font-medium">{currentLanguage.code.toUpperCase()}</span>
|
||||
@@ -64,7 +64,7 @@ export function LanguageSwitcher() {
|
||||
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
className="bg-glass-bg backdrop-blur-glass border border-glass-border min-w-[150px]"
|
||||
className="bg-bg-primary border border-border-primary shadow-xl min-w-[150px]"
|
||||
>
|
||||
{languages.map((language) => {
|
||||
const isActive = currentLocale === language.code;
|
||||
|
||||
@@ -57,7 +57,7 @@ export function ThemeToggle() {
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-text-primary border border-border-primary hover:bg-glass-hover transition-all"
|
||||
className="text-text-primary border border-border-primary hover:bg-bg-secondary transition-all"
|
||||
aria-label="Toggle theme"
|
||||
>
|
||||
<CurrentIcon className="h-5 w-5" />
|
||||
@@ -66,7 +66,7 @@ export function ThemeToggle() {
|
||||
|
||||
<DropdownMenuContent
|
||||
align="end"
|
||||
className="bg-glass-bg backdrop-blur-glass border border-glass-border min-w-[150px]"
|
||||
className="bg-bg-primary border border-border-primary shadow-xl min-w-[150px]"
|
||||
>
|
||||
{themeOptions.map((option) => {
|
||||
const Icon = option.icon;
|
||||
|
||||
@@ -136,6 +136,31 @@
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
EN: Solid Input (X.ai Style)
|
||||
VI: Solid Input (Phong cách X.ai)
|
||||
============================================ */
|
||||
|
||||
/**
|
||||
* Solid input field for a more grounded feel
|
||||
*/
|
||||
.solid-input {
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border-primary);
|
||||
transition: all var(--duration-fast) var(--ease-snap);
|
||||
}
|
||||
|
||||
.solid-input:hover {
|
||||
border-color: var(--border-secondary);
|
||||
}
|
||||
|
||||
.solid-input:focus {
|
||||
background: var(--bg-primary);
|
||||
border-color: var(--accent-primary);
|
||||
box-shadow: 0 0 0 3px rgba(29, 155, 240, 0.1);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
EN: Glass Modal/Dialog
|
||||
VI: Glass Modal/Dialog
|
||||
|
||||
@@ -14,400 +14,406 @@
|
||||
*/
|
||||
|
||||
:root {
|
||||
/* ============================================
|
||||
/* ============================================
|
||||
EN: Color Palette - Dark Mode (Primary Theme)
|
||||
VI: Bảng màu - Dark Mode (Theme chính)
|
||||
============================================ */
|
||||
|
||||
/* Background Colors - X.ai Minimal / Màu nền - X.ai Minimal */
|
||||
--bg-primary: #15202b;
|
||||
/* Warm dark gray - Main background (X.ai style) */
|
||||
--bg-secondary: #1a2734;
|
||||
/* Warm dark gray lighter - Card/Panel background */
|
||||
--bg-tertiary: #1f2f3d;
|
||||
/* Warm dark gray medium - Hover states */
|
||||
--bg-elevated: #243442;
|
||||
/* Warm dark gray elevated - Modals, dropdowns */
|
||||
/* Background Colors - X.ai Minimal / Màu nền - X.ai Minimal */
|
||||
--bg-primary: #15202b;
|
||||
/* Warm dark gray - Main background (X.ai style) */
|
||||
--bg-secondary: #1a2734;
|
||||
/* Warm dark gray lighter - Card/Panel background */
|
||||
--bg-tertiary: #1f2f3d;
|
||||
/* Warm dark gray medium - Hover states */
|
||||
--bg-elevated: #243442;
|
||||
/* Warm dark gray elevated - Modals, dropdowns */
|
||||
|
||||
/* Text Colors (WCAG Compliant) - X.ai Minimal / Màu chữ (tuân thủ WCAG) - X.ai Minimal */
|
||||
--text-primary: #FFFFFF;
|
||||
/* Pure white - Primary text */
|
||||
--text-secondary: #8899A6;
|
||||
/* X.ai gray - Secondary text (labels, descriptions) */
|
||||
--text-tertiary: #657786;
|
||||
/* X.ai dark gray - Tertiary text (captions, footnotes) */
|
||||
--text-muted: #505050;
|
||||
/* Dark grey - Muted elements */
|
||||
--text-inverse: #000000;
|
||||
/* Black - Text on light/white backgrounds */
|
||||
/* Text Colors (WCAG Compliant) - X.ai Minimal / Màu chữ (tuân thủ WCAG) - X.ai Minimal */
|
||||
--text-primary: #FFFFFF;
|
||||
/* Pure white - Primary text */
|
||||
--text-secondary: #8899A6;
|
||||
/* X.ai gray - Secondary text (labels, descriptions) */
|
||||
--text-tertiary: #657786;
|
||||
/* X.ai dark gray - Tertiary text (captions, footnotes) */
|
||||
--text-muted: #505050;
|
||||
/* Dark grey - Muted elements */
|
||||
--text-inverse: #000000;
|
||||
/* Black - Text on light/white backgrounds */
|
||||
|
||||
/* Brand/Accent Colors - X.ai Blue / Màu thương hiệu/Accent - X.ai Blue */
|
||||
--accent-primary: #1D9BF0;
|
||||
/* X.ai blue - Primary actions, links, focus */
|
||||
--accent-primary-hover: #1a8cd8;
|
||||
/* X.ai blue darker - Hover states */
|
||||
--accent-primary-light: #8ecdf7;
|
||||
/* X.ai blue lighter - Light accents */
|
||||
--accent-secondary: #333333;
|
||||
/* Dark grey - Secondary actions */
|
||||
--accent-success: #10B981;
|
||||
/* Green - Success states */
|
||||
--accent-warning: #F59E0B;
|
||||
/* Amber - Warnings */
|
||||
--accent-error: #EF4444;
|
||||
/* Red - Errors */
|
||||
--accent-info: #06B6D4;
|
||||
/* Cyan - Info */
|
||||
/* Brand/Accent Colors - X.ai Blue / Màu thương hiệu/Accent - X.ai Blue */
|
||||
--accent-primary: #1D9BF0;
|
||||
/* X.ai blue - Primary actions, links, focus */
|
||||
--accent-primary-hover: #1a8cd8;
|
||||
/* X.ai blue darker - Hover states */
|
||||
--accent-primary-light: #8ecdf7;
|
||||
/* X.ai blue lighter - Light accents */
|
||||
--accent-secondary: #333333;
|
||||
/* Dark grey - Secondary actions */
|
||||
--accent-success: #10B981;
|
||||
/* Green - Success states */
|
||||
--accent-warning: #F59E0B;
|
||||
/* Amber - Warnings */
|
||||
--accent-error: #EF4444;
|
||||
/* Red - Errors */
|
||||
--accent-info: #06B6D4;
|
||||
/* Cyan - Info */
|
||||
|
||||
/* Chat Specific Colors / Màu riêng cho Chat */
|
||||
--chat-user-bubble: #1A1A1A;
|
||||
/* Dark grey - User message */
|
||||
--chat-ai-bubble: transparent;
|
||||
/* Transparent - AI message (Minimal) */
|
||||
--chat-user-text: #FFFFFF;
|
||||
/* White text */
|
||||
--chat-ai-text: #E5E5E5;
|
||||
/* Off-white text */
|
||||
--chat-timestamp: #555555;
|
||||
/* Dark grey timestamp */
|
||||
--chat-divider: #222222;
|
||||
/* Divider between messages */
|
||||
/* Chat Specific Colors / Màu riêng cho Chat */
|
||||
--chat-user-bubble: #1A1A1A;
|
||||
/* Dark grey - User message */
|
||||
--chat-ai-bubble: transparent;
|
||||
/* Transparent - AI message (Minimal) */
|
||||
--chat-user-text: #FFFFFF;
|
||||
/* White text */
|
||||
--chat-ai-text: #E5E5E5;
|
||||
/* Off-white text */
|
||||
--chat-timestamp: #555555;
|
||||
/* Dark grey timestamp */
|
||||
--chat-divider: #222222;
|
||||
/* Divider between messages */
|
||||
|
||||
/* Border Colors - X.ai Minimal / Màu viền - X.ai Minimal */
|
||||
--border-primary: #222222;
|
||||
/* Subtle borders */
|
||||
--border-secondary: #333333;
|
||||
/* Hover borders */
|
||||
--border-focus: #1D9BF0;
|
||||
/* Focus state - X.ai blue */
|
||||
/* Border Colors - X.ai Minimal / Màu viền - X.ai Minimal */
|
||||
--border-primary: #222222;
|
||||
/* Subtle borders */
|
||||
--border-secondary: #333333;
|
||||
/* Hover borders */
|
||||
--border-focus: #1D9BF0;
|
||||
/* Focus state - X.ai blue */
|
||||
|
||||
/* ============================================
|
||||
/* ============================================
|
||||
EN: X.ai Brand Colors (2026 Minimal)
|
||||
VI: Màu thương hiệu X.ai (2026 Minimal)
|
||||
============================================ */
|
||||
|
||||
/* Primary Brand Color - X.ai Blue */
|
||||
--brand-primary: #1D9BF0;
|
||||
/* X.ai blue - Main brand color */
|
||||
--brand-primary-light: #8ecdf7;
|
||||
/* X.ai blue light */
|
||||
--brand-primary-dark: #1a8cd8;
|
||||
/* X.ai blue dark */
|
||||
--brand-primary-contrast: #FFFFFF;
|
||||
/* White text on X.ai blue */
|
||||
/* Primary Brand Color - X.ai Blue */
|
||||
--brand-primary: #1D9BF0;
|
||||
/* X.ai blue - Main brand color */
|
||||
--brand-primary-light: #8ecdf7;
|
||||
/* X.ai blue light */
|
||||
--brand-primary-dark: #1a8cd8;
|
||||
/* X.ai blue dark */
|
||||
--brand-primary-contrast: #FFFFFF;
|
||||
/* White text on X.ai blue */
|
||||
|
||||
/* Secondary Brand Color - Warm dark gray */
|
||||
--brand-secondary: #8899A6;
|
||||
/* X.ai gray */
|
||||
--brand-secondary-light: #AAAAAA;
|
||||
--brand-secondary-dark: #657786;
|
||||
/* X.ai dark gray */
|
||||
/* Secondary Brand Color - Warm dark gray */
|
||||
--brand-secondary: #8899A6;
|
||||
/* X.ai gray */
|
||||
--brand-secondary-light: #AAAAAA;
|
||||
--brand-secondary-dark: #657786;
|
||||
/* X.ai dark gray */
|
||||
|
||||
/* No gradients - X.ai Minimalism uses solid colors only */
|
||||
/* No gradients - X.ai Minimalism uses solid colors only */
|
||||
|
||||
/* ============================================
|
||||
/* ============================================
|
||||
EN: Minimal Effects (x.ai Clean Style)
|
||||
VI: Hiệu ứng tối giản (Phong cách x.ai sạch sẽ)
|
||||
============================================ */
|
||||
|
||||
/* Glass Backgrounds - X.ai Ultra Minimal (2-5% opacity) */
|
||||
--glass-bg-subtle: rgba(255, 255, 255, 0.01);
|
||||
/* Ultra subtle - For minimal effect */
|
||||
--glass-bg-default: rgba(255, 255, 255, 0.04);
|
||||
/* Default X.ai minimal background (4%) */
|
||||
--glass-bg-medium: rgba(255, 255, 255, 0.05);
|
||||
/* Medium minimal effect */
|
||||
--glass-bg-hover: rgba(255, 255, 255, 0.06);
|
||||
/* Hover state */
|
||||
--glass-bg-active: rgba(255, 255, 255, 0.08);
|
||||
/* Active/pressed state */
|
||||
/* Glass Backgrounds - X.ai Ultra Minimal (2-5% opacity) */
|
||||
--glass-bg-subtle: rgba(255, 255, 255, 0.01);
|
||||
/* Ultra subtle - For minimal effect */
|
||||
--glass-bg-default: rgba(255, 255, 255, 0.04);
|
||||
/* Default X.ai minimal background (4%) */
|
||||
--glass-bg-medium: rgba(255, 255, 255, 0.05);
|
||||
/* Medium minimal effect */
|
||||
--glass-bg-hover: rgba(255, 255, 255, 0.06);
|
||||
/* Hover state */
|
||||
--glass-bg-active: rgba(255, 255, 255, 0.08);
|
||||
/* Active/pressed state */
|
||||
|
||||
/* Glass Borders - X.ai Subtle */
|
||||
--glass-border-subtle: rgba(255, 255, 255, 0.05);
|
||||
/* Very subtle border */
|
||||
--glass-border-default: rgba(255, 255, 255, 0.08);
|
||||
/* Default X.ai border (8%) */
|
||||
--glass-border-hover: rgba(255, 255, 255, 0.12);
|
||||
/* Hover border */
|
||||
--glass-border-focus: rgba(29, 155, 240, 0.5);
|
||||
/* Focus state - X.ai blue (50%) */
|
||||
/* Glass Borders - X.ai Subtle */
|
||||
--glass-border-subtle: rgba(255, 255, 255, 0.05);
|
||||
/* Very subtle border */
|
||||
--glass-border-default: rgba(255, 255, 255, 0.08);
|
||||
/* Default X.ai border (8%) */
|
||||
--glass-border-hover: rgba(255, 255, 255, 0.12);
|
||||
/* Hover border */
|
||||
--glass-border-focus: rgba(29, 155, 240, 0.5);
|
||||
/* Focus state - X.ai blue (50%) */
|
||||
|
||||
/* Backdrop Blur Levels - X.ai Minimal (Reduced) */
|
||||
--glass-blur-sm: 4px;
|
||||
/* Small blur - for inputs */
|
||||
--glass-blur-md: 8px;
|
||||
/* Medium blur - X.ai default (reduced from 16px) */
|
||||
--glass-blur-lg: 12px;
|
||||
/* Large blur - cards only */
|
||||
--glass-blur-xl: 12px;
|
||||
/* Max blur - X.ai keeps it subtle (reduced from 16px) */
|
||||
/* Backdrop Blur Levels - X.ai Minimal (Reduced) */
|
||||
--glass-blur-sm: 4px;
|
||||
/* Small blur - for inputs */
|
||||
--glass-blur-md: 8px;
|
||||
/* Medium blur - X.ai default (reduced from 16px) */
|
||||
--glass-blur-lg: 12px;
|
||||
/* Large blur - cards only */
|
||||
--glass-blur-xl: 12px;
|
||||
/* Max blur - X.ai keeps it subtle (reduced from 16px) */
|
||||
|
||||
/* Glass Shadows - Dark mode optimized */
|
||||
--glass-shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.4);
|
||||
--glass-shadow-md: 0 4px 16px rgba(0, 0, 0, 0.5);
|
||||
--glass-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.6);
|
||||
--glass-shadow-inset: inset 0 1px 1px rgba(255, 255, 255, 0.1);
|
||||
/* Inner highlight */
|
||||
/* Glass Shadows - Dark mode optimized */
|
||||
--glass-shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.4);
|
||||
--glass-shadow-md: 0 4px 16px rgba(0, 0, 0, 0.5);
|
||||
--glass-shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.6);
|
||||
--glass-shadow-inset: inset 0 1px 1px rgba(255, 255, 255, 0.1);
|
||||
/* Inner highlight */
|
||||
|
||||
/* Interactive Glass States */
|
||||
--interactive-glass-rest: rgba(255, 255, 255, 0.04);
|
||||
--interactive-glass-hover: rgba(255, 255, 255, 0.08);
|
||||
--interactive-glass-active: rgba(255, 255, 255, 0.12);
|
||||
--interactive-glass-disabled: rgba(255, 255, 255, 0.02);
|
||||
/* Interactive Glass States */
|
||||
--interactive-glass-rest: rgba(255, 255, 255, 0.04);
|
||||
--interactive-glass-hover: rgba(255, 255, 255, 0.08);
|
||||
--interactive-glass-active: rgba(255, 255, 255, 0.12);
|
||||
--interactive-glass-disabled: rgba(255, 255, 255, 0.02);
|
||||
|
||||
/* Legacy support - keeping old variables for backward compatibility */
|
||||
--glass-bg: var(--glass-bg-default);
|
||||
--glass-border: var(--glass-border-default);
|
||||
--glass-blur: var(--glass-blur-md);
|
||||
/* Legacy support - keeping old variables for backward compatibility */
|
||||
--glass-bg: var(--glass-bg-default);
|
||||
--glass-border: var(--glass-border-default);
|
||||
--glass-blur: var(--glass-blur-md);
|
||||
|
||||
/* ============================================
|
||||
/* ============================================
|
||||
EN: Removed - Extended Shadows (X.ai Minimalist)
|
||||
VI: Đã xóa - Extended Shadows (X.ai Minimalist)
|
||||
============================================ */
|
||||
/* Removed brand shadows for minimalist approach */
|
||||
/* Use --shadow or --shadow-lg instead */
|
||||
/* Removed brand shadows for minimalist approach */
|
||||
/* Use --shadow or --shadow-lg instead */
|
||||
|
||||
/* ============================================
|
||||
/* ============================================
|
||||
EN: Light Mode Colors (Secondary Theme)
|
||||
VI: Màu sắc cho Light Mode (Theme phụ)
|
||||
============================================ */
|
||||
--bg-primary-light: #FFFFFF;
|
||||
--bg-secondary-light: #FBFBFD;
|
||||
/* Apple Gray */
|
||||
--bg-tertiary-light: #F5F5F7;
|
||||
--text-primary-light: #1D1D1F;
|
||||
/* Apple Black */
|
||||
--text-secondary-light: #86868B;
|
||||
/* Apple Gray Text */
|
||||
--border-primary-light: #D2D2D7;
|
||||
--bg-primary-light: #FFFFFF;
|
||||
--bg-secondary-light: #FBFBFD;
|
||||
/* Apple Gray */
|
||||
--bg-tertiary-light: #F5F5F7;
|
||||
--text-primary-light: #1D1D1F;
|
||||
/* Apple Black */
|
||||
--text-secondary-light: #86868B;
|
||||
/* Apple Gray Text */
|
||||
--border-primary-light: #D2D2D7;
|
||||
|
||||
/* ============================================
|
||||
/* ============================================
|
||||
EN: Typography
|
||||
VI: Kiểu chữ
|
||||
============================================ */
|
||||
|
||||
/* Font Stack / Bộ font */
|
||||
--font-sans: var(--font-inter), -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
--font-mono: "JetBrains Mono", "SF Mono", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
/* Font Stack / Bộ font */
|
||||
--font-sans: var(--font-inter), -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
--font-mono: "JetBrains Mono", "SF Mono", Consolas, "Liberation Mono", Menlo, monospace;
|
||||
|
||||
/* Display Font - For hero titles (48px+) */
|
||||
--font-display: var(--font-inter), -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
/* Display Font - For hero titles (48px+) */
|
||||
--font-display: var(--font-inter), -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
|
||||
/* Heading Font - For section headings (24-36px) */
|
||||
--font-heading: var(--font-inter), -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
/* Heading Font - For section headings (24-36px) */
|
||||
--font-heading: var(--font-inter), -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
|
||||
/* Type Scale - Clean minimal approach */
|
||||
--text-6xl: 3.5rem;
|
||||
/* 56px - Hero titles */
|
||||
--text-5xl: 2.75rem;
|
||||
/* 44px - Page titles */
|
||||
--text-4xl: 2.25rem;
|
||||
/* 36px - Section headers */
|
||||
--text-3xl: 1.75rem;
|
||||
/* 28px - Card headers */
|
||||
--text-2xl: 1.5rem;
|
||||
/* 24px - Large body */
|
||||
--text-xl: 1.25rem;
|
||||
/* 20px - Emphasized text */
|
||||
--text-lg: 1.125rem;
|
||||
/* 18px - Large body */
|
||||
--text-base: 1rem;
|
||||
/* 16px - Default body */
|
||||
--text-sm: 0.875rem;
|
||||
/* 14px - Small text */
|
||||
--text-xs: 0.75rem;
|
||||
/* 12px - Captions */
|
||||
/* Type Scale - Clean minimal approach */
|
||||
--text-6xl: 3.5rem;
|
||||
/* 56px - Hero titles */
|
||||
--text-5xl: 2.75rem;
|
||||
/* 44px - Page titles */
|
||||
--text-4xl: 2.25rem;
|
||||
/* 36px - Section headers */
|
||||
--text-3xl: 1.75rem;
|
||||
/* 28px - Card headers */
|
||||
--text-2xl: 1.5rem;
|
||||
/* 24px - Large body */
|
||||
--text-xl: 1.25rem;
|
||||
/* 20px - Emphasized text */
|
||||
--text-lg: 1.125rem;
|
||||
/* 18px - Large body */
|
||||
--text-base: 1rem;
|
||||
/* 16px - Default body */
|
||||
--text-sm: 0.875rem;
|
||||
/* 14px - Small text */
|
||||
--text-xs: 0.75rem;
|
||||
/* 12px - Captions */
|
||||
|
||||
/* Line Heights / Chiều cao dòng */
|
||||
--leading-none: 1;
|
||||
--leading-tight: 1.1;
|
||||
--leading-snug: 1.2;
|
||||
--leading-normal: 1.5;
|
||||
--leading-relaxed: 1.625;
|
||||
--leading-loose: 2;
|
||||
/* Line Heights / Chiều cao dòng */
|
||||
--leading-none: 1;
|
||||
--leading-tight: 1.1;
|
||||
--leading-snug: 1.2;
|
||||
--leading-normal: 1.5;
|
||||
--leading-relaxed: 1.625;
|
||||
--leading-loose: 2;
|
||||
|
||||
/* Font Weights - X.ai Minimalist (Bolder Impact) */
|
||||
--font-thin: 100;
|
||||
--font-extralight: 200;
|
||||
--font-light: 300;
|
||||
/* Light text */
|
||||
--font-normal: 400;
|
||||
/* Body text */
|
||||
--font-medium: 500;
|
||||
/* Emphasized */
|
||||
--font-semibold: 600;
|
||||
/* Headings */
|
||||
--font-bold: 700;
|
||||
/* Bold - stronger impact */
|
||||
--font-extrabold: 800;
|
||||
/* Extra bold - hero text */
|
||||
--font-black: 900;
|
||||
/* Black - maximum impact for titles */
|
||||
/* Font Weights - X.ai Minimalist (Bolder Impact) */
|
||||
--font-thin: 100;
|
||||
--font-extralight: 200;
|
||||
--font-light: 300;
|
||||
/* Light text */
|
||||
--font-normal: 400;
|
||||
/* Body text */
|
||||
--font-medium: 500;
|
||||
/* Emphasized */
|
||||
--font-semibold: 600;
|
||||
/* Headings */
|
||||
--font-bold: 700;
|
||||
/* Bold - stronger impact */
|
||||
--font-extrabold: 800;
|
||||
/* Extra bold - hero text */
|
||||
--font-black: 900;
|
||||
/* Black - maximum impact for titles */
|
||||
|
||||
/* Letter Spacing - Clean Minimalist Look */
|
||||
--tracking-tighter: -0.04em;
|
||||
/* Very tight - for large display text */
|
||||
--tracking-tight: -0.02em;
|
||||
/* Tight - for headings */
|
||||
--tracking-normal: 0;
|
||||
/* Normal */
|
||||
--tracking-wide: 0.02em;
|
||||
/* Wide - for small caps */
|
||||
--tracking-wider: 0.04em;
|
||||
/* Wider - for emphasis */
|
||||
/* Letter Spacing - Clean Minimalist Look */
|
||||
--tracking-tighter: -0.04em;
|
||||
/* Very tight - for large display text */
|
||||
--tracking-tight: -0.02em;
|
||||
/* Tight - for headings */
|
||||
--tracking-normal: 0;
|
||||
/* Normal */
|
||||
--tracking-wide: 0.02em;
|
||||
/* Wide - for small caps */
|
||||
--tracking-wider: 0.04em;
|
||||
/* Wider - for emphasis */
|
||||
|
||||
/* ============================================
|
||||
/* ============================================
|
||||
EN: Spacing & Layout
|
||||
VI: Khoảng cách & Bố cục
|
||||
============================================ */
|
||||
|
||||
/* Base Unit: 4px (0.25rem) / Đơn vị cơ sở: 4px (0.25rem) */
|
||||
--space-0: 0;
|
||||
--space-1: 0.25rem;
|
||||
/* 4px */
|
||||
--space-2: 0.5rem;
|
||||
/* 8px */
|
||||
--space-3: 0.75rem;
|
||||
/* 12px */
|
||||
--space-4: 1rem;
|
||||
/* 16px */
|
||||
--space-5: 1.25rem;
|
||||
/* 20px */
|
||||
--space-6: 1.5rem;
|
||||
/* 24px */
|
||||
--space-8: 2rem;
|
||||
/* 32px */
|
||||
--space-10: 2.5rem;
|
||||
/* 40px */
|
||||
--space-12: 3rem;
|
||||
/* 48px */
|
||||
--space-16: 4rem;
|
||||
/* 64px */
|
||||
--space-20: 5rem;
|
||||
/* 80px */
|
||||
/* Base Unit: 4px (0.25rem) / Đơn vị cơ sở: 4px (0.25rem) */
|
||||
--space-0: 0;
|
||||
--space-1: 0.25rem;
|
||||
/* 4px */
|
||||
--space-2: 0.5rem;
|
||||
/* 8px */
|
||||
--space-3: 0.75rem;
|
||||
/* 12px */
|
||||
--space-4: 1rem;
|
||||
/* 16px */
|
||||
--space-5: 1.25rem;
|
||||
/* 20px */
|
||||
--space-6: 1.5rem;
|
||||
/* 24px */
|
||||
--space-8: 2rem;
|
||||
/* 32px */
|
||||
--space-10: 2.5rem;
|
||||
/* 40px */
|
||||
--space-12: 3rem;
|
||||
/* 48px */
|
||||
--space-16: 4rem;
|
||||
/* 64px */
|
||||
--space-20: 5rem;
|
||||
/* 80px */
|
||||
|
||||
/* Container Widths / Chiều rộng container */
|
||||
--container-sm: 640px;
|
||||
/* Small devices */
|
||||
--container-md: 768px;
|
||||
/* Medium devices */
|
||||
--container-lg: 1024px;
|
||||
/* Large devices */
|
||||
--container-xl: 1280px;
|
||||
/* Extra large */
|
||||
--container-2xl: 1536px;
|
||||
/* 2X large */
|
||||
--chat-max-width: 800px;
|
||||
/* Max width for chat messages */
|
||||
--sidebar-width: 280px;
|
||||
/* Conversation history sidebar */
|
||||
/* Container Widths / Chiều rộng container */
|
||||
--container-sm: 640px;
|
||||
/* Small devices */
|
||||
--container-md: 768px;
|
||||
/* Medium devices */
|
||||
--container-lg: 1024px;
|
||||
/* Large devices */
|
||||
--container-xl: 1280px;
|
||||
/* Extra large */
|
||||
--container-2xl: 1536px;
|
||||
/* 2X large */
|
||||
--chat-max-width: 800px;
|
||||
/* Max width for chat messages */
|
||||
--sidebar-width: 280px;
|
||||
/* Conversation history sidebar */
|
||||
|
||||
/* Mobile Layout / Layout Mobile */
|
||||
--mobile-header-height: 56px;
|
||||
/* Standard mobile header height */
|
||||
--mobile-bottom-nav-height: 64px;
|
||||
/* iOS/Android bottom nav height */
|
||||
--mobile-safe-area-top: env(safe-area-inset-top);
|
||||
/* iOS notch safe area */
|
||||
--mobile-safe-area-bottom: env(safe-area-inset-bottom);
|
||||
/* iOS home indicator safe area */
|
||||
/* Mobile Layout / Layout Mobile */
|
||||
--mobile-header-height: 56px;
|
||||
/* Standard mobile header height */
|
||||
--mobile-bottom-nav-height: 64px;
|
||||
/* iOS/Android bottom nav height */
|
||||
--mobile-safe-area-top: env(safe-area-inset-top);
|
||||
/* iOS notch safe area */
|
||||
--mobile-safe-area-bottom: env(safe-area-inset-bottom);
|
||||
/* iOS home indicator safe area */
|
||||
|
||||
/* Border Radius / Bo góc */
|
||||
--radius-sm: 2px;
|
||||
/* Small elements - sharp */
|
||||
--radius-md: 4px;
|
||||
/* Buttons, inputs - sharp */
|
||||
--radius-lg: 8px;
|
||||
/* Cards - minimal roundness */
|
||||
--radius-xl: 12px;
|
||||
/* Large cards */
|
||||
--radius-2xl: 16px;
|
||||
/* Modals */
|
||||
--radius-full: 9999px;
|
||||
/* Full round - Avatars, pills */
|
||||
/* Border Radius / Bo góc */
|
||||
--radius-sm: 2px;
|
||||
/* Small elements - sharp */
|
||||
--radius-md: 4px;
|
||||
/* Buttons, inputs - sharp */
|
||||
--radius-lg: 8px;
|
||||
/* Cards - minimal roundness */
|
||||
--radius-xl: 12px;
|
||||
/* Large cards */
|
||||
--radius-2xl: 16px;
|
||||
/* Modals */
|
||||
--radius-full: 9999px;
|
||||
/* Full round - Avatars, pills */
|
||||
|
||||
/* ============================================
|
||||
/* ============================================
|
||||
EN: Shadows - X.ai Minimalist (Ultra Subtle)
|
||||
VI: Đổ bóng - X.ai Minimalist (Cực kỳ tinh tế)
|
||||
============================================ */
|
||||
--shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
|
||||
/* Default shadow - subtle */
|
||||
--shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.6);
|
||||
/* Large shadow - for modals only */
|
||||
--shadow: 0 1px 3px rgba(0, 0, 0, 0.5);
|
||||
/* Default shadow - subtle */
|
||||
--shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.6);
|
||||
/* Large shadow - for modals only */
|
||||
|
||||
/* Legacy support - mapping to new shadows */
|
||||
--shadow-sm: var(--shadow);
|
||||
--shadow-md: var(--shadow);
|
||||
--shadow-xl: var(--shadow-lg);
|
||||
--shadow-glow: 0 0 8px rgba(255, 255, 255, 0.05);
|
||||
/* Minimal glow for focus */
|
||||
/* Legacy support - mapping to new shadows */
|
||||
--shadow-sm: var(--shadow);
|
||||
--shadow-md: var(--shadow);
|
||||
--shadow-xl: var(--shadow-lg);
|
||||
--shadow-glow: 0 0 8px rgba(255, 255, 255, 0.05);
|
||||
/* Minimal glow for focus */
|
||||
|
||||
/* ============================================
|
||||
/* Brand shadows for brand buttons */
|
||||
--shadow-brand: 0 4px 16px rgba(29, 155, 240, 0.3);
|
||||
/* Brand shadow with X.ai blue glow */
|
||||
--shadow-brand-lg: 0 8px 24px rgba(29, 155, 240, 0.4);
|
||||
/* Large brand shadow */
|
||||
|
||||
/* ============================================
|
||||
EN: Grid System & Breakpoints
|
||||
VI: Hệ thống lưới & Điểm ngắt
|
||||
============================================ */
|
||||
--screen-sm: 640px;
|
||||
/* Mobile landscape */
|
||||
--screen-md: 768px;
|
||||
/* Tablet */
|
||||
--screen-lg: 1024px;
|
||||
/* Desktop */
|
||||
--screen-xl: 1280px;
|
||||
/* Large desktop */
|
||||
--screen-2xl: 1536px;
|
||||
/* Extra large desktop */
|
||||
--screen-sm: 640px;
|
||||
/* Mobile landscape */
|
||||
--screen-md: 768px;
|
||||
/* Tablet */
|
||||
--screen-lg: 1024px;
|
||||
/* Desktop */
|
||||
--screen-xl: 1280px;
|
||||
/* Large desktop */
|
||||
--screen-2xl: 1536px;
|
||||
/* Extra large desktop */
|
||||
|
||||
/* ============================================
|
||||
/* ============================================
|
||||
EN: Animation & Transitions
|
||||
VI: Animation & Chuyển tiếp
|
||||
============================================ */
|
||||
|
||||
/* Timing Functions - X.ai Minimalist (Simplified) */
|
||||
--ease-smooth: cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
/* Smooth and natural - primary easing */
|
||||
--ease-snap: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
/* Snappy - for quick interactions */
|
||||
/* Timing Functions - X.ai Minimalist (Simplified) */
|
||||
--ease-smooth: cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||||
/* Smooth and natural - primary easing */
|
||||
--ease-snap: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
/* Snappy - for quick interactions */
|
||||
|
||||
/* Legacy support - mapping to new easing */
|
||||
--ease-in: var(--ease-snap);
|
||||
--ease-out: var(--ease-snap);
|
||||
--ease-in-out: var(--ease-smooth);
|
||||
--motion-ease-smooth: var(--ease-smooth);
|
||||
--motion-ease-glide: var(--ease-snap);
|
||||
/* Legacy support - mapping to new easing */
|
||||
--ease-in: var(--ease-snap);
|
||||
--ease-out: var(--ease-snap);
|
||||
--ease-in-out: var(--ease-smooth);
|
||||
--motion-ease-smooth: var(--ease-smooth);
|
||||
--motion-ease-glide: var(--ease-snap);
|
||||
|
||||
/* Duration - X.ai Minimalist (Faster, Snappier) */
|
||||
--duration-instant: 100ms;
|
||||
/* Instant feedback */
|
||||
--duration-fast: 150ms;
|
||||
/* Hover effects */
|
||||
--duration-normal: 200ms;
|
||||
/* Default transitions - FASTER */
|
||||
--duration-slow: 300ms;
|
||||
/* Complex animations - FASTER */
|
||||
/* Duration - X.ai Minimalist (Faster, Snappier) */
|
||||
--duration-instant: 100ms;
|
||||
/* Instant feedback */
|
||||
--duration-fast: 150ms;
|
||||
/* Hover effects */
|
||||
--duration-normal: 200ms;
|
||||
/* Default transitions - FASTER */
|
||||
--duration-slow: 300ms;
|
||||
/* Complex animations - FASTER */
|
||||
|
||||
/* Legacy support - mapping to new durations */
|
||||
--duration-slower: var(--duration-slow);
|
||||
--motion-duration-instant: var(--duration-instant);
|
||||
--motion-duration-quick: var(--duration-fast);
|
||||
--motion-duration-normal: var(--duration-normal);
|
||||
--motion-duration-smooth: var(--duration-slow);
|
||||
/* Legacy support - mapping to new durations */
|
||||
--duration-slower: var(--duration-slow);
|
||||
--motion-duration-instant: var(--duration-instant);
|
||||
--motion-duration-quick: var(--duration-fast);
|
||||
--motion-duration-normal: var(--duration-normal);
|
||||
--motion-duration-smooth: var(--duration-slow);
|
||||
|
||||
/* ============================================
|
||||
/* ============================================
|
||||
EN: Interactive States - X.ai Minimalist
|
||||
VI: Trạng thái tương tác - X.ai Minimalist
|
||||
============================================ */
|
||||
|
||||
/* Removed bounce/elastic - too playful for minimalism */
|
||||
/* Removed bounce/elastic - too playful for minimalism */
|
||||
|
||||
/* Hover Scale - Minimal movement */
|
||||
--hover-scale-sm: 1.01;
|
||||
/* Barely noticeable */
|
||||
--hover-scale-md: 1.02;
|
||||
/* Subtle */
|
||||
/* Hover Scale - Minimal movement */
|
||||
--hover-scale-sm: 1.01;
|
||||
/* Barely noticeable */
|
||||
--hover-scale-md: 1.02;
|
||||
/* Subtle */
|
||||
|
||||
/* Active Scale - For pressed states */
|
||||
--active-scale: 0.99;
|
||||
/* Minimal press feedback */
|
||||
/* Active Scale - For pressed states */
|
||||
--active-scale: 0.99;
|
||||
/* Minimal press feedback */
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
@@ -415,14 +421,14 @@
|
||||
VI: Ghi đè theme cho Light Mode
|
||||
============================================ */
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
--bg-primary: var(--bg-primary-light);
|
||||
--bg-secondary: var(--bg-secondary-light);
|
||||
--bg-tertiary: var(--bg-tertiary-light);
|
||||
--text-primary: var(--text-primary-light);
|
||||
--text-secondary: var(--text-secondary-light);
|
||||
--border-primary: var(--border-primary-light);
|
||||
}
|
||||
:root {
|
||||
--bg-primary: var(--bg-primary-light);
|
||||
--bg-secondary: var(--bg-secondary-light);
|
||||
--bg-tertiary: var(--bg-tertiary-light);
|
||||
--text-primary: var(--text-primary-light);
|
||||
--text-secondary: var(--text-secondary-light);
|
||||
--border-primary: var(--border-primary-light);
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
@@ -431,25 +437,25 @@
|
||||
============================================ */
|
||||
[data-theme="dark"],
|
||||
.dark {
|
||||
/* X.ai Minimal Dark Theme */
|
||||
--bg-primary: #15202b;
|
||||
/* Warm dark gray */
|
||||
--bg-secondary: #1a2734;
|
||||
--bg-tertiary: #1f2f3d;
|
||||
--bg-elevated: #243442;
|
||||
--text-primary: #FFFFFF;
|
||||
--text-secondary: #8899A6;
|
||||
/* X.ai gray */
|
||||
--text-tertiary: #657786;
|
||||
/* X.ai dark gray */
|
||||
--text-muted: #505050;
|
||||
--border-primary: #222222;
|
||||
--border-secondary: #333333;
|
||||
--border-focus: #1D9BF0;
|
||||
/* X.ai blue */
|
||||
--accent-primary: #1D9BF0;
|
||||
/* X.ai blue */
|
||||
--accent-primary-hover: #1a8cd8;
|
||||
/* X.ai Minimal Dark Theme */
|
||||
--bg-primary: #15202b;
|
||||
/* Warm dark gray */
|
||||
--bg-secondary: #1a2734;
|
||||
--bg-tertiary: #1f2f3d;
|
||||
--bg-elevated: #243442;
|
||||
--text-primary: #FFFFFF;
|
||||
--text-secondary: #8899A6;
|
||||
/* X.ai gray */
|
||||
--text-tertiary: #657786;
|
||||
/* X.ai dark gray */
|
||||
--text-muted: #505050;
|
||||
--border-primary: #222222;
|
||||
--border-secondary: #333333;
|
||||
--border-focus: #1D9BF0;
|
||||
/* X.ai blue */
|
||||
--accent-primary: #1D9BF0;
|
||||
/* X.ai blue */
|
||||
--accent-primary-hover: #1a8cd8;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
@@ -458,19 +464,19 @@
|
||||
============================================ */
|
||||
[data-theme="light"],
|
||||
.light {
|
||||
/* X.ai Minimal Light Theme */
|
||||
--bg-primary: var(--bg-primary-light);
|
||||
--bg-secondary: var(--bg-secondary-light);
|
||||
--bg-tertiary: var(--bg-tertiary-light);
|
||||
--text-primary: var(--text-primary-light);
|
||||
--text-secondary: var(--text-secondary-light);
|
||||
--border-primary: var(--border-primary-light);
|
||||
--border-focus: #1D9BF0;
|
||||
/* X.ai blue stays in light mode */
|
||||
--accent-primary: #1D9BF0;
|
||||
/* X.ai blue */
|
||||
--accent-primary-hover: #1a8cd8;
|
||||
/* Glass effects adapted for light */
|
||||
--glass-bg-default: rgba(0, 0, 0, 0.04);
|
||||
--glass-border-default: rgba(0, 0, 0, 0.1);
|
||||
/* X.ai Minimal Light Theme */
|
||||
--bg-primary: var(--bg-primary-light);
|
||||
--bg-secondary: var(--bg-secondary-light);
|
||||
--bg-tertiary: var(--bg-tertiary-light);
|
||||
--text-primary: var(--text-primary-light);
|
||||
--text-secondary: var(--text-secondary-light);
|
||||
--border-primary: var(--border-primary-light);
|
||||
--border-focus: #1D9BF0;
|
||||
/* X.ai blue stays in light mode */
|
||||
--accent-primary: #1D9BF0;
|
||||
/* X.ai blue */
|
||||
--accent-primary-hover: #1a8cd8;
|
||||
/* Glass effects adapted for light */
|
||||
--glass-bg-default: rgba(0, 0, 0, 0.04);
|
||||
--glass-border-default: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import re
|
||||
from pathlib import Path
|
||||
|
||||
# Base directory
|
||||
base_dir = Path("/Users/velikho/Desktop/WORKING/Base/services/iam-service/src")
|
||||
|
||||
# Find all files that use getErrorMessage
|
||||
for root, dirs, files in os.walk(base_dir):
|
||||
# Skip test directories
|
||||
if '__tests__' in root or 'node_modules' in root:
|
||||
continue
|
||||
|
||||
for file in files:
|
||||
if not (file.endswith('.controller.ts') or file.endswith('.middleware.ts') or file.endswith('.service.ts')):
|
||||
continue
|
||||
if file.endswith('.test.ts') or file.endswith('.e2e.ts'):
|
||||
continue
|
||||
|
||||
filepath = Path(root) / file
|
||||
|
||||
# Read file content
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Check if file uses getErrorMessage
|
||||
if 'getErrorMessage(' not in content:
|
||||
continue
|
||||
|
||||
# Check if import already exists
|
||||
if re.search(r'import.*getErrorMessage.*from.*error-utils', content):
|
||||
continue
|
||||
|
||||
# Calculate relative path
|
||||
rel_path = filepath.relative_to(base_dir)
|
||||
depth = len(rel_path.parts) - 1 # -1 for the file itself
|
||||
|
||||
# Build relative import path
|
||||
if depth == 1: # src/middlewares/
|
||||
import_path = '../utils/error-utils'
|
||||
elif depth == 2: # src/modules/xxx/
|
||||
import_path = '../../utils/error-utils'
|
||||
elif depth == 3: # src/modules/xxx/yyy/
|
||||
import_path = '../../../utils/error-utils'
|
||||
else:
|
||||
import_path = '../../utils/error-utils' # default
|
||||
|
||||
# Find the last import line
|
||||
lines = content.split('\n')
|
||||
last_import_idx = -1
|
||||
for i, line in enumerate(lines):
|
||||
if line.strip().startswith('import '):
|
||||
last_import_idx = i
|
||||
|
||||
if last_import_idx >= 0:
|
||||
# Insert new import after last import
|
||||
import_statement = f"import {{ getErrorMessage }} from '{import_path}';"
|
||||
lines.insert(last_import_idx + 1, import_statement)
|
||||
|
||||
# Write back
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write('\n'.join(lines))
|
||||
|
||||
print(f"Added import to {filepath}")
|
||||
|
||||
print("Done!")
|
||||
@@ -1,42 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Script to add getErrorMessage import to files that use it
|
||||
|
||||
cd /Users/velikho/Desktop/WORKING/Base/services/iam-service/src
|
||||
|
||||
# Find all files that use getErrorMessage but don't have the import
|
||||
for file in $(find . \( -name "*.controller.ts" -o -name "*.middleware.ts" -o -name "*.service.ts" \) -not -path "*/__tests__/*" -not -name "*.test.ts" -exec grep -l "getErrorMessage" {} \;); do
|
||||
# Check if import already exists
|
||||
if ! grep -q "getErrorMessage.*from.*error-utils" "$file"; then
|
||||
# Calculate relative path to error-utils
|
||||
depth=$(echo "$file" | grep -o "/" | wc -l)
|
||||
if [ $depth -eq 2 ]; then
|
||||
# File is in src/modules/xxx/
|
||||
relative_path="../utils/error-utils"
|
||||
elif [ $depth -eq 3 ]; then
|
||||
# File is in src/modules/xxx/yyy/
|
||||
relative_path="../../utils/error-utils"
|
||||
elif [ $depth -eq 4 ]; then
|
||||
# File is in src/modules/xxx/yyy/zzz/
|
||||
relative_path="../../../utils/error-utils"
|
||||
else
|
||||
# Default to ../../utils/error-utils
|
||||
relative_path="../../utils/error-utils"
|
||||
fi
|
||||
|
||||
# Add import after the last import statement
|
||||
awk -v path="$relative_path" '
|
||||
/^import/ { last_import=NR }
|
||||
{ lines[NR]=$0 }
|
||||
END {
|
||||
for(i=1; i<=NR; i++) {
|
||||
print lines[i]
|
||||
if(i==last_import) {
|
||||
print "import { getErrorMessage } from \"" path "\";"
|
||||
}
|
||||
}
|
||||
}
|
||||
' "$file" > "$file.tmp" && mv "$file.tmp" "$file"
|
||||
|
||||
echo "Added import to $file"
|
||||
fi
|
||||
done
|
||||
@@ -1,105 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Final cleanup script to ensure complete separation of bilingual documentation.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import glob
|
||||
|
||||
def clean_english_file(filepath):
|
||||
"""Remove all Vietnamese content from English files."""
|
||||
print(f"Cleaning English file: {filepath}")
|
||||
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
cleaned_lines = []
|
||||
skip_mode = False
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
# Skip Vietnamese description lines
|
||||
if line.strip().startswith('>') and any(viet_word in line.lower() for viet_word in ['thực hành', 'mẫu', 'nền tảng', 'sử dụng', 'triển khai', 'bảo vệ', 'xác thực', 'giới hạn', 'quản lý', 'kiểm tra']):
|
||||
continue
|
||||
|
||||
# Skip lines that are clearly Vietnamese content
|
||||
stripped = line.strip()
|
||||
if stripped and not stripped.startswith('#') and not stripped.startswith('>') and not stripped.startswith('```') and not stripped.startswith('|') and not stripped.startswith('-') and not stripped.startswith('*'):
|
||||
# Check if line contains Vietnamese characters or Vietnamese words
|
||||
vietnamese_indicators = [
|
||||
'các', 'cho', 'để', 'và', 'là', 'trong', 'với', 'này', 'có', 'không',
|
||||
'từ', 'được', 'sẽ', 'nên', 'cần', 'khi', 'thì', 'lại', 'rất', 'đã',
|
||||
'thực', 'hiện', 'dụng', 'bảo', 'mật', 'xác', 'thức', 'phân', 'quyền',
|
||||
'dữ liệu', 'nhạy cảm', 'giới hạn', 'tốc độ', 'quản lý', 'bí mật', 'kiểm tra'
|
||||
]
|
||||
if any(indicator in stripped.lower() for indicator in vietnamese_indicators):
|
||||
continue
|
||||
|
||||
# Skip empty lines that follow Vietnamese content
|
||||
if not stripped and skip_mode:
|
||||
skip_mode = False
|
||||
continue
|
||||
|
||||
cleaned_lines.append(line)
|
||||
|
||||
# Write back the cleaned content
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.writelines(cleaned_lines)
|
||||
|
||||
def clean_vietnamese_file(filepath):
|
||||
"""Remove all English content from Vietnamese files."""
|
||||
print(f"Cleaning Vietnamese file: {filepath}")
|
||||
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
lines = f.readlines()
|
||||
|
||||
cleaned_lines = []
|
||||
skip_mode = False
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
# Skip English description lines
|
||||
if line.strip().startswith('>') and any(en_word in line.lower() for en_word in ['security', 'best practices', 'patterns', 'platform', 'use when', 'implementing', 'authentication']):
|
||||
continue
|
||||
|
||||
# Skip lines that are clearly English content
|
||||
stripped = line.strip()
|
||||
if stripped and not stripped.startswith('#') and not stripped.startswith('>') and not stripped.startswith('```') and not stripped.startswith('|') and not stripped.startswith('-') and not stripped.startswith('*'):
|
||||
# Skip if line doesn't contain Vietnamese indicators
|
||||
vietnamese_indicators = [
|
||||
'các', 'cho', 'để', 'và', 'là', 'trong', 'với', 'này', 'có', 'không',
|
||||
'từ', 'được', 'sẽ', 'nên', 'cần', 'khi', 'thì', 'lại', 'rất', 'đã',
|
||||
'thực', 'hiện', 'dụng', 'bảo', 'mật', 'xác', 'thức', 'phân', 'quyền',
|
||||
'dữ liệu', 'nhạy cảm', 'giới hạn', 'tốc độ', 'quản lý', 'bí mật', 'kiểm tra',
|
||||
'tổng quan', 'khi nào sử dụng', 'khái niệm chính', 'patterns', 'cache'
|
||||
]
|
||||
has_vietnamese = any(indicator in stripped.lower() for indicator in vietnamese_indicators)
|
||||
if not has_vietnamese and stripped and not stripped.startswith(('The', 'Use', 'This', 'For', 'In', 'When', 'How', 'What', 'Why')):
|
||||
continue
|
||||
|
||||
cleaned_lines.append(line)
|
||||
|
||||
# Write back the cleaned content
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.writelines(cleaned_lines)
|
||||
|
||||
def main():
|
||||
print("Starting final cleanup of bilingual documentation...")
|
||||
|
||||
# Clean English files
|
||||
print("Cleaning English documentation files...")
|
||||
en_files = glob.glob('docs/en/skills/*.md')
|
||||
for filepath in en_files:
|
||||
if os.path.isfile(filepath):
|
||||
clean_english_file(filepath)
|
||||
|
||||
# Clean Vietnamese files
|
||||
print("Cleaning Vietnamese documentation files...")
|
||||
vi_files = glob.glob('docs/vi/skills/*.md')
|
||||
for filepath in vi_files:
|
||||
if os.path.isfile(filepath):
|
||||
clean_vietnamese_file(filepath)
|
||||
|
||||
print("Final cleanup completed!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,154 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Script to separate bilingual documentation into language-specific files.
|
||||
Removes Vietnamese content from English docs and English content from Vietnamese docs.
|
||||
"""
|
||||
|
||||
import os
|
||||
import re
|
||||
import glob
|
||||
|
||||
def process_english_file(filepath):
|
||||
"""Process an English file to keep only English content."""
|
||||
print(f"Processing English file: {filepath}")
|
||||
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# First, handle the description line - keep only English
|
||||
content = re.sub(r'^> \*\*EN\*\*: (.+)\n> \*\*VI\*\*: .+', r'> \1', content, flags=re.MULTILINE)
|
||||
|
||||
# Remove standalone Vietnamese description lines
|
||||
content = re.sub(r'^> \*\*VI\*\*: .+\n', '', content, flags=re.MULTILINE)
|
||||
|
||||
# Convert bilingual titles to English only
|
||||
content = re.sub(r'^(#{1,6}) .+ / (.+)$', r'\1 \2', content, flags=re.MULTILINE)
|
||||
|
||||
# For sections with both EN and VI blocks, keep only EN content
|
||||
# Pattern: **EN**: content\n**VI**: content
|
||||
content = re.sub(r'\*\*EN\*\*: ([^\n]+)\n\*\*VI\*\*: [^\n]+', r'\1', content)
|
||||
|
||||
# Remove remaining **VI** blocks and their content
|
||||
content = re.sub(r'\*\*VI\*\*:\n((?:(?!\*\*EN\*\*:|\*\*VI\*\*:|^#{1,6}).*\n)*)', '', content)
|
||||
|
||||
# Remove standalone **VI**: lines
|
||||
content = re.sub(r'^\*\*VI\*\*: .+\n', '', content, flags=re.MULTILINE)
|
||||
|
||||
# Remove **EN**: markers
|
||||
content = re.sub(r'\*\*EN\*\*: ', '', content)
|
||||
|
||||
# Convert bilingual labels to English
|
||||
replacements = [
|
||||
('**Reference / Tham Khảo**:', '**Reference**:'),
|
||||
('**Patterns / Các Patterns**:', '**Patterns**:'),
|
||||
('**Key Generators / Bộ Tạo Key**:', '**Key Generators**:'),
|
||||
('**Speed / Tốc Độ**:', '**Speed**:'),
|
||||
('**Capacity / Dung Lượng**:', '**Capacity**:'),
|
||||
('**Scope / Phạm Vi**:', '**Scope**:'),
|
||||
('**Use Case / Trường Hợp Sử Dụng**:', '**Use Case**:'),
|
||||
('**Storage**:', '**Storage**:'),
|
||||
('**L1 Cache (Memory) / Cache Bộ Nhớ**:', '**L1 Cache (Memory)**:'),
|
||||
('**L2 Cache (Redis) / Cache Redis**:', '**L2 Cache (Redis)**:'),
|
||||
]
|
||||
|
||||
for old, new in replacements:
|
||||
content = content.replace(old, new)
|
||||
|
||||
# Convert bilingual comments in code to English
|
||||
lines = content.split('\n')
|
||||
processed_lines = []
|
||||
for line in lines:
|
||||
if line.strip().startswith('//') and ' / ' in line:
|
||||
parts = line.split(' / ', 1)
|
||||
processed_lines.append(parts[0])
|
||||
else:
|
||||
processed_lines.append(line)
|
||||
|
||||
content = '\n'.join(processed_lines)
|
||||
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
def process_vietnamese_file(filepath):
|
||||
"""Process a Vietnamese file to keep only Vietnamese content."""
|
||||
print(f"Processing Vietnamese file: {filepath}")
|
||||
|
||||
with open(filepath, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# First, handle the description line - keep only Vietnamese
|
||||
content = re.sub(r'^> \*\*EN\*\*: .+\n> \*\*VI\*\*: (.+)', r'> \1', content, flags=re.MULTILINE)
|
||||
|
||||
# Remove standalone English description lines
|
||||
content = re.sub(r'^> \*\*EN\*\*: .+\n', '', content, flags=re.MULTILINE)
|
||||
|
||||
# Convert bilingual titles to Vietnamese only
|
||||
content = re.sub(r'^(#{1,6}) .+ / (.+)$', r'\1 \2', content, flags=re.MULTILINE)
|
||||
|
||||
# For sections with both EN and VI blocks, keep only VI content
|
||||
# Pattern: **EN**: content\n**VI**: content
|
||||
content = re.sub(r'\*\*EN\*\*: [^\n]+\n\*\*VI\*\*: ([^\n]+)', r'\1', content)
|
||||
|
||||
# Remove remaining **EN** blocks and their content
|
||||
content = re.sub(r'\*\*EN\*\*:\n((?:(?!\*\*EN\*\*:|\*\*VI\*\*:|^#{1,6}).*\n)*)', '', content)
|
||||
|
||||
# Remove standalone **EN**: lines
|
||||
content = re.sub(r'^\*\*EN\*\*: .+\n', '', content, flags=re.MULTILINE)
|
||||
|
||||
# Remove **VI**: markers
|
||||
content = re.sub(r'\*\*VI\*\*: ', '', content)
|
||||
|
||||
# Convert bilingual labels to Vietnamese
|
||||
replacements = [
|
||||
('**Reference / Tham Khảo**:', '**Tham Khảo**:'),
|
||||
('**Patterns / Các Patterns**:', '**Các Patterns**:'),
|
||||
('**Key Generators / Bộ Tạo Key**:', '**Bộ Tạo Key**:'),
|
||||
('**Speed / Tốc Độ**:', '**Tốc Độ**:'),
|
||||
('**Capacity / Dung Lượng**:', '**Dung Lượng**:'),
|
||||
('**Scope / Phạm Vi**:', '**Phạm Vi**:'),
|
||||
('**Use Case / Trường Hợp Sử Dụng**:', '**Trường Hợp Sử Dụng**:'),
|
||||
('**Storage**:', '**Storage**:'),
|
||||
('**L1 Cache (Memory) / Cache Bộ Nhớ**:', '**Cache Bộ Nhớ**:'),
|
||||
('**L2 Cache (Redis) / Cache Redis**:', '**Cache Redis**:'),
|
||||
]
|
||||
|
||||
for old, new in replacements:
|
||||
content = content.replace(old, new)
|
||||
|
||||
# Convert bilingual comments in code to Vietnamese
|
||||
lines = content.split('\n')
|
||||
processed_lines = []
|
||||
for line in lines:
|
||||
if line.strip().startswith('//') and ' / ' in line:
|
||||
parts = line.split(' / ', 1)
|
||||
if len(parts) == 2:
|
||||
processed_lines.append(f"// {parts[1]}")
|
||||
else:
|
||||
processed_lines.append(line)
|
||||
|
||||
content = '\n'.join(processed_lines)
|
||||
|
||||
with open(filepath, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
def main():
|
||||
print("Starting bilingual documentation separation...")
|
||||
|
||||
# Process all English files
|
||||
print("Processing English documentation files...")
|
||||
en_files = glob.glob('docs/en/skills/*.md')
|
||||
for filepath in en_files:
|
||||
if os.path.isfile(filepath):
|
||||
process_english_file(filepath)
|
||||
|
||||
# Process all Vietnamese files
|
||||
print("Processing Vietnamese documentation files...")
|
||||
vi_files = glob.glob('docs/vi/skills/*.md')
|
||||
for filepath in vi_files:
|
||||
if os.path.isfile(filepath):
|
||||
process_vietnamese_file(filepath)
|
||||
|
||||
print("Bilingual documentation separation completed!")
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,138 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Script to separate bilingual documentation into language-specific files
|
||||
# Removes Vietnamese content from English docs and English content from Vietnamese docs
|
||||
|
||||
set -e
|
||||
|
||||
echo "Starting bilingual documentation separation..."
|
||||
|
||||
# Function to process English files (remove Vietnamese content)
|
||||
process_en_file() {
|
||||
local file="$1"
|
||||
echo "Processing English file: $file"
|
||||
|
||||
# Use sed and awk for more comprehensive processing
|
||||
# First, remove all **VI** blocks and bilingual markers
|
||||
sed -i.bak \
|
||||
-e '/^> \*\*VI\*\*:/d' \
|
||||
-e '/^\*\*VI\*\*:/d' \
|
||||
-e 's/\*\*VI\*\*: //' \
|
||||
"$file"
|
||||
|
||||
# Convert bilingual titles/headers to English only
|
||||
sed -i \
|
||||
-e 's/# .\+ \/ .\+$/# \1/' \
|
||||
-e 's/## .\+ \/ .\+$ /## \1 /' \
|
||||
-e 's/### .\+ \/ .\+$ /### \1 /' \
|
||||
"$file"
|
||||
|
||||
# Remove bilingual patterns in content
|
||||
sed -i \
|
||||
-e 's/\*\*Reference \/ Tham Khảo\*\*/**Reference**/g' \
|
||||
-e 's/\*\*Patterns \/ Các Patterns\*\*/**Patterns**/g' \
|
||||
-e 's/\*\*Key Generators \/ Bộ Tạo Key\*\*/**Key Generators**/g' \
|
||||
-e 's/\*\*Speed \/ Tốc Độ\*\*/**Speed**/g' \
|
||||
-e 's/\*\*Capacity \/ Dung Lượng\*\*/**Capacity**/g' \
|
||||
-e 's/\*\*TTL\*\*/**TTL**/g' \
|
||||
-e 's/\*\*Scope \/ Phạm Vi\*\*/**Scope**/g' \
|
||||
-e 's/\*\*Use Case \/ Trường Hợp Sử Dụng\*\*/**Use Case**/g' \
|
||||
-e 's/\*\*Storage\*\*/**Storage**/g' \
|
||||
"$file"
|
||||
|
||||
# Remove **EN**: markers
|
||||
sed -i 's/\*\*EN\*\*: //' "$file"
|
||||
|
||||
# Remove bilingual comments in code blocks
|
||||
sed -i 's|// .\+ / .\+$|// \1|' "$file"
|
||||
|
||||
# Clean up any remaining bilingual markers in headers
|
||||
sed -i 's| (.\+ / .\+):| (\1):|g' "$file"
|
||||
|
||||
# Remove backup file
|
||||
rm -f "${file}.bak"
|
||||
}
|
||||
|
||||
# Function to process Vietnamese files (remove English content)
|
||||
process_vi_file() {
|
||||
local file="$1"
|
||||
echo "Processing Vietnamese file: $file"
|
||||
|
||||
# Use sed and awk for more comprehensive processing
|
||||
# First, remove all **EN** blocks and bilingual markers
|
||||
sed -i.bak \
|
||||
-e '/^> \*\*EN\*\*:/d' \
|
||||
-e '/^\*\*EN\*\*:/d' \
|
||||
-e 's/\*\*EN\*\*: //' \
|
||||
"$file"
|
||||
|
||||
# Convert bilingual titles/headers to Vietnamese only
|
||||
awk '
|
||||
/^#/ {
|
||||
# Handle headers with bilingual format
|
||||
if (match($0, /^#+ .+ \/ (.+)$/, arr)) {
|
||||
# Extract Vietnamese part after "/"
|
||||
level = substr($0, 1, index($0, " ") - 1)
|
||||
viet_part = arr[1]
|
||||
print level " " viet_part
|
||||
} else {
|
||||
print $0
|
||||
}
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
|
||||
|
||||
# Remove bilingual patterns in content - Vietnamese versions
|
||||
sed -i \
|
||||
-e 's/\*\*Reference \/ Tham Khảo\*\*/**Tham Khảo**/g' \
|
||||
-e 's/\*\*Patterns \/ Các Patterns\*\*/**Các Patterns**/g' \
|
||||
-e 's/\*\*Key Generators \/ Bộ Tạo Key\*\*/**Bộ Tạo Key**/g' \
|
||||
-e 's/\*\*Speed \/ Tốc Độ\*\*/**Tốc Độ**/g' \
|
||||
-e 's/\*\*Capacity \/ Dung Lượng\*\*/**Dung Lượng**/g' \
|
||||
-e 's/\*\*TTL\*\*/**TTL**/g' \
|
||||
-e 's/\*\*Scope \/ Phạm Vi\*\*/**Phạm Vi**/g' \
|
||||
-e 's/\*\*Use Case \/ Trường Hợp Sử Dụng\*\*/**Trường Hợp Sử Dụng**/g' \
|
||||
-e 's/\*\*Storage\*\*/**Storage**/g' \
|
||||
"$file"
|
||||
|
||||
# Remove **VI**: markers
|
||||
sed -i 's/\*\*VI\*\*: //' "$file"
|
||||
|
||||
# Remove bilingual comments in code blocks - Vietnamese version
|
||||
awk '
|
||||
/^\/\// {
|
||||
if (match($0, /^\/\/ .+ \/ (.+)$/, arr)) {
|
||||
print "// " arr[1]
|
||||
} else {
|
||||
print $0
|
||||
}
|
||||
next
|
||||
}
|
||||
{ print }
|
||||
' "$file" > "${file}.tmp" && mv "${file}.tmp" "$file"
|
||||
|
||||
# Clean up any remaining bilingual markers in headers
|
||||
sed -i 's| (.\+ / .\+):| (\1):|g' "$file"
|
||||
|
||||
# Remove backup file
|
||||
rm -f "${file}.bak"
|
||||
}
|
||||
|
||||
# Process all English files
|
||||
echo "Processing English documentation files..."
|
||||
for file in docs/en/skills/*.md; do
|
||||
if [[ -f "$file" ]]; then
|
||||
process_en_file "$file"
|
||||
fi
|
||||
done
|
||||
|
||||
# Process all Vietnamese files
|
||||
echo "Processing Vietnamese documentation files..."
|
||||
for file in docs/vi/skills/*.md; do
|
||||
if [[ -f "$file" ]]; then
|
||||
process_vi_file "$file"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Bilingual documentation separation completed!"
|
||||
Reference in New Issue
Block a user