diff --git a/backend/api/migrations/0008_alter_user_theme.py b/backend/api/migrations/0008_alter_user_theme.py new file mode 100644 index 0000000..65155ec --- /dev/null +++ b/backend/api/migrations/0008_alter_user_theme.py @@ -0,0 +1,18 @@ +# Generated by Django 6.0.2 on 2026-02-16 13:11 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0007_flavor_image_flavor_is_legacy_alter_flavor_image_url'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='theme', + field=models.CharField(choices=[('latte', 'Latte'), ('frappe', 'Frappé'), ('macchiato', 'Macchiato'), ('mocha', 'Mocha'), ('pink', 'Pastel Pink'), ('atom', 'Atom One Dark'), ('dracula', 'Dracula'), ('gruvbox', 'Gruvbox Dark'), ('nord', 'Nord'), ('cyberpunk', 'Cyberpunk'), ('forest', 'Forest Dark')], default='mocha', max_length=20), + ), + ] diff --git a/backend/api/migrations/0009_alter_user_theme.py b/backend/api/migrations/0009_alter_user_theme.py new file mode 100644 index 0000000..41ab65b --- /dev/null +++ b/backend/api/migrations/0009_alter_user_theme.py @@ -0,0 +1,18 @@ +# Generated by Django 6.0.2 on 2026-02-16 13:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0008_alter_user_theme'), + ] + + operations = [ + migrations.AlterField( + model_name='user', + name='theme', + field=models.CharField(choices=[('latte', 'Latte'), ('pink', 'Pastel Pink'), ('solarized_light', 'Solarized Light'), ('one_light', 'One Light'), ('paper', 'Paper White'), ('frappe', 'Frappé'), ('macchiato', 'Macchiato'), ('mocha', 'Mocha'), ('atom', 'Atom One Dark'), ('dracula', 'Dracula'), ('gruvbox', 'Gruvbox Dark'), ('nord', 'Nord'), ('cyberpunk', 'Cyberpunk'), ('forest', 'Forest Dark')], default='mocha', max_length=20), + ), + ] diff --git a/backend/api/models.py b/backend/api/models.py index 34b9b82..2a9a096 100644 --- a/backend/api/models.py +++ b/backend/api/models.py @@ -5,10 +5,19 @@ from django.core.validators import MinValueValidator, MaxValueValidator class User(AbstractUser): THEME_CHOICES = [ ('latte', 'Latte'), + ('pink', 'Pastel Pink'), + ('solarized_light', 'Solarized Light'), + ('one_light', 'One Light'), + ('paper', 'Paper White'), ('frappe', 'Frappé'), ('macchiato', 'Macchiato'), ('mocha', 'Mocha'), - ('pink', 'Pastel Pink'), + ('atom', 'Atom One Dark'), + ('dracula', 'Dracula'), + ('gruvbox', 'Gruvbox Dark'), + ('nord', 'Nord'), + ('cyberpunk', 'Cyberpunk'), + ('forest', 'Forest Dark'), ] theme = models.CharField(max_length=20, choices=THEME_CHOICES, default='mocha') pending_email = models.EmailField(max_length=254, blank=True, null=True) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 0bfa83f..27af35e 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -21,7 +21,8 @@ import { useMediaQuery, TextField, InputAdornment, - Autocomplete + Autocomplete, + ListSubheader } from '@mui/material'; import PaletteIcon from '@mui/icons-material/Palette'; import MenuIcon from '@mui/icons-material/Menu'; @@ -259,12 +260,27 @@ const App: React.FC = () => { anchorEl={anchorEl} open={Boolean(anchorEl)} onClose={() => setAnchorEl(null)} + sx={{ maxHeight: 500 }} > + Light Themes handleThemeChange('latte')}>Latte + handleThemeChange('pink')}>Pastel Pink + handleThemeChange('solarized_light')}>Solarized Light + handleThemeChange('one_light')}>One Light + handleThemeChange('paper')}>Paper White + + + + Dark Themes + handleThemeChange('mocha')}>Mocha (Default) handleThemeChange('frappe')}>Frappé handleThemeChange('macchiato')}>Macchiato - handleThemeChange('mocha')}>Mocha - handleThemeChange('pink')}>Pastel Pink + handleThemeChange('atom')}>Atom One Dark + handleThemeChange('dracula')}>Dracula + handleThemeChange('gruvbox')}>Gruvbox Dark + handleThemeChange('nord')}>Nord + handleThemeChange('cyberpunk')}>Cyberpunk + handleThemeChange('forest')}>Forest Dark diff --git a/frontend/src/theme.ts b/frontend/src/theme.ts index 7673709..55765ab 100644 --- a/frontend/src/theme.ts +++ b/frontend/src/theme.ts @@ -1,27 +1,122 @@ import { createTheme, type ThemeOptions } from '@mui/material/styles'; import { flavors } from '@catppuccin/palette'; -export type CatppuccinTheme = 'latte' | 'frappe' | 'macchiato' | 'mocha' | 'pink'; +export type CatppuccinTheme = + | 'latte' | 'pink' | 'solarized_light' | 'one_light' | 'paper' + | 'frappe' | 'macchiato' | 'mocha' | 'atom' | 'dracula' | 'gruvbox' | 'nord' | 'cyberpunk' | 'forest'; + +export const isLightTheme = (mode: CatppuccinTheme): boolean => { + return ['latte', 'pink', 'solarized_light', 'one_light', 'paper'].includes(mode); +}; export const getTheme = (mode: CatppuccinTheme) => { - let palette; + let palette: any; if (mode === 'pink') { palette = { - base: { hex: '#fdf6f7' }, // Soft cream pink - surface0: { hex: '#f8e1e5' }, // Pastel pink - crust: { hex: '#f2ccd5' }, // Deeper pink for headers - text: { hex: '#4a3135' }, // Deep maroon-brown for high readability + base: { hex: '#fdf6f7' }, + surface0: { hex: '#f8e1e5' }, + crust: { hex: '#f2ccd5' }, + text: { hex: '#4a3135' }, subtext0: { hex: '#7d5c62' }, - mauve: { hex: '#e05a8d' }, // Rich pink (Primary) - pink: { hex: '#4ecdc4' }, // Fresh Turquoise (Secondary/Accent) + mauve: { hex: '#e05a8d' }, + pink: { hex: '#4ecdc4' }, + }; + } else if (mode === 'solarized_light') { + palette = { + base: { hex: '#fdf6e3' }, + surface0: { hex: '#eee8d5' }, + crust: { hex: '#e3dbbc' }, + text: { hex: '#586e75' }, + subtext0: { hex: '#839496' }, + mauve: { hex: '#6c71c4' }, + pink: { hex: '#d33682' }, + }; + } else if (mode === 'one_light') { + palette = { + base: { hex: '#fafafa' }, + surface0: { hex: '#f0f0f0' }, + crust: { hex: '#e5e5e6' }, + text: { hex: '#383a42' }, + subtext0: { hex: '#a0a1a7' }, + mauve: { hex: '#a626a4' }, + pink: { hex: '#e45649' }, + }; + } else if (mode === 'paper') { + palette = { + base: { hex: '#ffffff' }, + surface0: { hex: '#f5f5f5' }, + crust: { hex: '#eeeeee' }, + text: { hex: '#222222' }, + subtext0: { hex: '#666666' }, + mauve: { hex: '#007aff' }, + pink: { hex: '#ff2d55' }, + }; + } else if (mode === 'atom') { + palette = { + base: { hex: '#282c34' }, + surface0: { hex: '#353b45' }, + crust: { hex: '#21252b' }, + text: { hex: '#abb2bf' }, + subtext0: { hex: '#5c6370' }, + mauve: { hex: '#c678dd' }, + pink: { hex: '#e06c75' }, + }; + } else if (mode === 'dracula') { + palette = { + base: { hex: '#282a36' }, + surface0: { hex: '#44475a' }, + crust: { hex: '#191a21' }, + text: { hex: '#f8f8f2' }, + subtext0: { hex: '#6272a4' }, + mauve: { hex: '#bd93f9' }, + pink: { hex: '#ff79c6' }, + }; + } else if (mode === 'gruvbox') { + palette = { + base: { hex: '#282828' }, + surface0: { hex: '#3c3836' }, + crust: { hex: '#1d2021' }, + text: { hex: '#ebdbb2' }, + subtext0: { hex: '#928374' }, + mauve: { hex: '#d3869b' }, + pink: { hex: '#fb4934' }, + }; + } else if (mode === 'nord') { + palette = { + base: { hex: '#2e3440' }, + surface0: { hex: '#3b4252' }, + crust: { hex: '#242933' }, + text: { hex: '#eceff4' }, + subtext0: { hex: '#d8dee9' }, + mauve: { hex: '#b48ead' }, + pink: { hex: '#88c0d0' }, + }; + } else if (mode === 'cyberpunk') { + palette = { + base: { hex: '#1a1a2e' }, + surface0: { hex: '#16213e' }, + crust: { hex: '#0f3460' }, + text: { hex: '#e94560' }, + subtext0: { hex: '#0f3460' }, + mauve: { hex: '#e94560' }, + pink: { hex: '#00d2ff' }, + }; + } else if (mode === 'forest') { + palette = { + base: { hex: '#1b262c' }, + surface0: { hex: '#213e3b' }, + crust: { hex: '#0f161c' }, + text: { hex: '#e8e8e8' }, + subtext0: { hex: '#aebb83' }, + mauve: { hex: '#4e8d7c' }, + pink: { hex: '#aebb83' }, }; } else { - const flavor = flavors[mode]; + const flavor = (flavors as any)[mode]; if (flavor) { palette = flavor.colors; } else { - // Fallback to mocha if theme name is invalid palette = flavors['mocha'].colors; mode = 'mocha'; } @@ -29,7 +124,7 @@ export const getTheme = (mode: CatppuccinTheme) => { const themeOptions: ThemeOptions = { palette: { - mode: (mode === 'latte' || mode === 'pink') ? 'light' : 'dark', + mode: isLightTheme(mode) ? 'light' : 'dark', primary: { main: palette.mauve.hex, },