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:
Ho Ngoc Hai
2026-01-05 18:51:49 +07:00
parent 3998bc5049
commit 5c7c6cc999
25 changed files with 1192 additions and 900 deletions

View File

@@ -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"

View File

@@ -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"

View File

@@ -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>

View 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>
);
}

View 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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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>
);
}

View File

@@ -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',
],
},

View File

@@ -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',

View File

@@ -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;

View File

@@ -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;

View File

@@ -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

View File

@@ -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);
}

View File

@@ -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!")

View File

@@ -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

View File

@@ -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', 'để', '', '', 'trong', 'với', 'này', '', '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', 'để', '', '', 'trong', 'với', 'này', '', '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()

View File

@@ -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()

View File

@@ -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!"