diff --git a/frontend/DESIGN_SYSTEM.md b/frontend/DESIGN_SYSTEM.md index 8cc56762..f67373e6 100644 --- a/frontend/DESIGN_SYSTEM.md +++ b/frontend/DESIGN_SYSTEM.md @@ -211,5 +211,182 @@ import { MODULE_COLORS } from '@/config/colors.config'; --- -**Last Updated**: December 30, 2025 +**Last Updated**: January 1, 2026 **Status**: Active Design System Rules + +--- + +## 🚨 MANDATORY COMPONENT & STYLING RULES + +> **FOR ALL DEVELOPERS AND AI AGENTS**: This section defines the ONLY allowed sources for components, styling, and colors. Violating these rules will result in inconsistent UI and technical debt. + +### ALLOWED COMPONENT SOURCES + +**ONLY import components from these locations:** + +| Category | Allowed Path | Components | +|----------|--------------|------------| +| **UI Components** | `@/components/ui/` or relative `../components/ui/` | Button, Card, Modal, Alert, Badge, Dropdown, Tooltip, Spinner, Tabs, Toast, Pagination, Progress, Avatar, Breadcrumb | +| **Common Components** | `@/components/common/` | PageHeader, ComponentCard, ConfirmDialog, FormModal, TablePageTemplate | +| **Icons** | `@/icons` or `@heroicons/react` | All SVG icons | +| **Templates** | `@/templates/` | TablePageTemplate, ContentViewTemplate | + +### BANNED IMPORTS (DO NOT USE) + +```tsx +// ❌ BANNED - Material UI +import { Button, Dialog, Alert, Box } from '@mui/material'; +import { SomeIcon } from '@mui/icons-material'; + +// ❌ BANNED - Chakra UI +import { Button } from '@chakra-ui/react'; + +// ❌ BANNED - Ant Design +import { Button } from 'antd'; + +// ❌ BANNED - Custom inline components +const MyButton = () => +``` + +**Correct Modal Usage:** +```tsx +import { Modal } from '../../components/ui/modal'; + + + {/* content */} + +``` + +**Correct Alert Usage:** +```tsx +import Alert from '../../components/ui/alert/Alert'; + + +``` + +**Correct Icon Usage:** +```tsx +import { CheckCircleIcon, ErrorIcon } from '../../icons'; + + +``` + +--- + +## Phase 7: Verification + +### 7.1 Final Verification Checklist + +After all fixes are applied, run: + +```bash +# Should return 0 results for each: +grep -rn "from '@mui" src/pages/ src/components/ --include="*.tsx" | wc -l +grep -rn "from 'lucide-react" src/pages/ --include="*.tsx" | wc -l +grep -rn "style={{" src/pages/ --include="*.tsx" | wc -l +grep -rn "#[0-9a-fA-F]\{6\}" src/pages/ --include="*.tsx" | wc -l +grep -rn "bg-blue-\|bg-red-\|bg-green-" src/pages/ --include="*.tsx" | wc -l +``` + +### 7.2 Visual Verification +- [ ] Check each page in browser (light mode) +- [ ] Check each page in browser (dark mode) +- [ ] Verify all buttons look consistent +- [ ] Verify all modals look consistent +- [ ] Verify all forms look consistent +- [ ] Verify all colors match brand palette + +--- + +## Notes + +- **DO NOT** replace gray-* classes - these are part of the design system +- **DO** replace blue/red/green with brand/error/success +- **DO** replace all inline styles with Tailwind classes +- **DO** replace all hardcoded hex with CSS variables or Tailwind classes +- **DO** extract inline components to separate files +- **DO** use standard ui/ components instead of custom implementations diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 691ca11d..e3c9bc6c 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -107,6 +107,9 @@ const SyncDashboard = lazy(() => import("./pages/Sites/SyncDashboard")); const DeploymentPanel = lazy(() => import("./pages/Sites/DeploymentPanel")); const PublishingQueue = lazy(() => import("./pages/Sites/PublishingQueue")); +// Setup - Lazy loaded +const SetupWizard = lazy(() => import("./pages/Setup/SetupWizard")); + // Help - Lazy loaded const Help = lazy(() => import("./pages/Help/Help")); @@ -256,6 +259,7 @@ export default function App() { } /> {/* Sites Management */} + } /> } /> } /> } /> diff --git a/frontend/src/components/BulkWordPressPublish/BulkWordPressPublish.tsx b/frontend/src/components/BulkWordPressPublish/BulkWordPressPublish.tsx deleted file mode 100644 index cb199d88..00000000 --- a/frontend/src/components/BulkWordPressPublish/BulkWordPressPublish.tsx +++ /dev/null @@ -1,284 +0,0 @@ -import React, { useState } from 'react'; -import { - Button, - Dialog, - DialogTitle, - DialogContent, - DialogActions, - Typography, - Box, - LinearProgress, - Alert, - List, - ListItem, - ListItemText, - ListItemIcon, - CircularProgress, - Chip -} from '@mui/material'; -import { - Publish as PublishIcon, - CheckCircle as SuccessIcon, - Error as ErrorIcon, - Schedule as PendingIcon -} from '@mui/icons-material'; -import { api } from '../../services/api'; - -interface BulkWordPressPublishProps { - selectedContentIds: string[]; - contentItems: Array<{ - id: string; - title: string; - status: string; - }>; - onPublishComplete: () => void; - onClose: () => void; -} - -interface BulkPublishResult { - total: number; - queued: number; - skipped: number; - errors: string[]; -} - -export const BulkWordPressPublish: React.FC = ({ - selectedContentIds, - contentItems, - onPublishComplete, - onClose -}) => { - const [open, setOpen] = useState(false); - const [publishing, setPublishing] = useState(false); - const [result, setResult] = useState(null); - const [error, setError] = useState(null); - - const selectedItems = contentItems.filter(item => - selectedContentIds.includes(item.id) - ); - - const handleBulkPublish = async () => { - setPublishing(true); - setError(null); - setResult(null); - - try { - const response = await api.post('/api/v1/content/bulk-publish-to-wordpress/', { - content_ids: selectedContentIds.map(id => parseInt(id)) - }); - - if (response.data.success) { - setResult({ - total: selectedContentIds.length, - queued: response.data.data.content_count, - skipped: 0, - errors: [] - }); - - // Start polling for individual status updates - startStatusPolling(); - } else { - setError(response.data.message || 'Failed to start bulk publishing'); - } - } catch (error: any) { - setError(error.response?.data?.message || 'Error starting bulk publish'); - } finally { - setPublishing(false); - } - }; - - const startStatusPolling = () => { - // Poll for 2 minutes to check status - const pollInterval = setInterval(async () => { - try { - // Check status of all items (this could be optimized with a dedicated endpoint) - const statusPromises = selectedContentIds.map(id => - api.get(`/api/v1/content/${id}/wordpress-status/`) - ); - - const responses = await Promise.allSettled(statusPromises); - - let completedCount = 0; - let successCount = 0; - let failedCount = 0; - - responses.forEach((response) => { - if (response.status === 'fulfilled' && response.value.data.success) { - const status = response.value.data.data.wordpress_sync_status; - if (status === 'success' || status === 'failed') { - completedCount++; - if (status === 'success') successCount++; - if (status === 'failed') failedCount++; - } - } - }); - - // If all items are complete, stop polling - if (completedCount === selectedContentIds.length) { - clearInterval(pollInterval); - setResult(prev => prev ? { - ...prev, - queued: successCount, - errors: Array(failedCount).fill('Publishing failed') - } : null); - onPublishComplete(); - } - } catch (error) { - console.error('Error polling status:', error); - } - }, 5000); - - // Stop polling after 2 minutes - setTimeout(() => { - clearInterval(pollInterval); - }, 120000); - }; - - const handleOpen = () => { - setOpen(true); - setResult(null); - setError(null); - }; - - const handleClose = () => { - setOpen(false); - onClose(); - }; - - const getResultSummary = () => { - if (!result) return null; - - const { total, queued, skipped, errors } = result; - const failed = errors.length; - - return ( - - - Bulk Publish Results - - - - } - label={`${queued} Queued`} - color="success" - size="small" - /> - {skipped > 0 && ( - } - label={`${skipped} Skipped`} - color="warning" - size="small" - /> - )} - {failed > 0 && ( - } - label={`${failed} Failed`} - color="error" - size="small" - /> - )} - - - {failed > 0 && ( - - Some items failed to publish. Check individual item status for details. - - )} - - ); - }; - - return ( - <> - - - - - Bulk Publish to Site - - - - {!publishing && !result && ( - <> - - You are about to publish {selectedContentIds.length} content items to your site: - - - - {selectedItems.map((item) => ( - - - - - - - ))} - - - - This will create new posts on your WordPress site with all content, - images, categories, and SEO metadata. Items already published will be skipped. - - - )} - - {publishing && ( - - - - Queuing content for WordPress publishing... - - - - )} - - {result && getResultSummary()} - - {error && ( - - {error} - - )} - - - - - - {!publishing && !result && ( - - )} - - - - ); -}; - -export default BulkWordPressPublish; \ No newline at end of file diff --git a/frontend/src/components/WordPressPublish/BulkWordPressPublish.tsx b/frontend/src/components/WordPressPublish/BulkWordPressPublish.tsx index 939a2778..2c642a99 100644 --- a/frontend/src/components/WordPressPublish/BulkWordPressPublish.tsx +++ b/frontend/src/components/WordPressPublish/BulkWordPressPublish.tsx @@ -1,24 +1,20 @@ +/** + * BulkWordPressPublish Component + * + * Handles bulk publishing multiple content items to connected WordPress sites. + * + * 🔒 STYLE LOCKED - Uses only standard ui/ components. See DESIGN_SYSTEM.md + */ import React, { useState } from 'react'; +import { Button } from '../ui/button/Button'; +import { Modal } from '../ui/modal'; +import Alert from '../ui/alert/Alert'; +import { Spinner } from '../ui/spinner/Spinner'; import { - Button, - Dialog, - DialogTitle, - DialogContent, - DialogActions, - Typography, - Alert, - Box, - CircularProgress, - List, - ListItem, - ListItemText, - Divider -} from '@mui/material'; -import { - Publish as PublishIcon, - CheckCircle as SuccessIcon, - Error as ErrorIcon -} from '@mui/icons-material'; + CheckCircleIcon, + ErrorIcon, + PaperPlaneIcon, +} from '../../icons'; import { api } from '../../services/api'; interface BulkWordPressPublishProps { @@ -121,140 +117,140 @@ export const BulkWordPressPublish: React.FC = ({ return ( <> - - - Bulk Publish to Site - - - +
+

+ Bulk Publish to Site +

+ {!publishing && results.length === 0 && ( <> - +

Ready to publish {readyToPublish.length} content items to your site: - +

- - Only content with generated images and not yet published will be included. - + - - - {readyToPublish.map((item, index) => ( -
- - - - {index < readyToPublish.length - 1 && } +
+ {readyToPublish.map((item, index) => ( +
+
+

{item.title}

+

ID: {item.id}

- ))} - - + {index < readyToPublish.length - 1 && ( +
+ )} +
+ ))} +
)} {publishing && ( - - - +
+ +

Publishing {readyToPublish.length} items to WordPress... - - +

+
)} {!publishing && results.length > 0 && ( <> - +
{successCount > 0 && ( - - ✓ Successfully published {successCount} items - + )} {failedCount > 0 && ( - - ✗ Failed to publish {failedCount} items - + )} - +
- - Results: - +

Results:

- - - {results.map((result, index) => ( -
- - - {result.status === 'success' ? ( - - ) : ( - - )} - - - - {index < results.length - 1 && } +
+ {results.map((result, index) => ( +
+
+ {result.status === 'success' ? ( + + ) : ( + + )} +
+

{result.title}

+

+ {result.message || (result.status === 'success' ? 'Published successfully' : 'Publishing failed')} +

+
- ))} - - - - )} - - - - {!publishing && results.length === 0 && ( - <> - - + {index < results.length - 1 && ( +
+ )} +
+ ))} +
)} - {publishing && ( - - )} - - {!publishing && results.length > 0 && ( - - )} - -
+
+ {!publishing && results.length === 0 && ( + <> + + + + )} + + {publishing && ( + + )} + + {!publishing && results.length > 0 && ( + + )} +
+ + ); }; \ No newline at end of file diff --git a/frontend/src/components/WordPressPublish/ContentActionsMenu.tsx b/frontend/src/components/WordPressPublish/ContentActionsMenu.tsx index d4971523..6f3ca17e 100644 --- a/frontend/src/components/WordPressPublish/ContentActionsMenu.tsx +++ b/frontend/src/components/WordPressPublish/ContentActionsMenu.tsx @@ -1,20 +1,21 @@ -import React, { useState } from 'react'; +/** + * ContentActionsMenu Component + * + * Dropdown menu for content actions (edit, generate images, export, delete, publish). + * + * 🔒 STYLE LOCKED - Uses only standard ui/ components. See DESIGN_SYSTEM.md + */ +import React, { useState, useRef } from 'react'; +import { Button } from '../ui/button/Button'; +import { Dropdown } from '../ui/dropdown/Dropdown'; import { - IconButton, - Menu, - MenuItem, - ListItemIcon, - ListItemText, - Divider -} from '@mui/material'; -import { - MoreVert as MoreVertIcon, - Publish as PublishIcon, - Edit as EditIcon, - Image as ImageIcon, - GetApp as ExportIcon, - Delete as DeleteIcon -} from '@mui/icons-material'; + MoreDotIcon, + PaperPlaneIcon, + PencilIcon, + FileIcon, + DownloadIcon, + TrashBinIcon, +} from '../../icons'; import { WordPressPublish } from './WordPressPublish'; interface ContentActionsMenuProps { @@ -40,16 +41,12 @@ export const ContentActionsMenu: React.FC = ({ onDelete, onWordPressStatusChange }) => { - const [anchorEl, setAnchorEl] = useState(null); + const [isOpen, setIsOpen] = useState(false); const [showWordPressDialog, setShowWordPressDialog] = useState(false); - const open = Boolean(anchorEl); - - const handleClick = (event: React.MouseEvent) => { - setAnchorEl(event.currentTarget); - }; + const buttonRef = useRef(null); const handleClose = () => { - setAnchorEl(null); + setIsOpen(false); }; const handlePublishClick = () => { @@ -69,85 +66,85 @@ export const ContentActionsMenu: React.FC = ({ return ( <> - setIsOpen(!isOpen)} + className="dropdown-toggle flex h-8 w-8 items-center justify-center rounded-lg text-gray-500 hover:bg-gray-100 hover:text-gray-700 dark:text-gray-400 dark:hover:bg-gray-800 dark:hover:text-white" > - - + + - } + placement="bottom-right" + className="w-48" > - {/* WordPress Publishing - Only show if images are ready */} - {canPublishToWordPress && ( - <> - - - - - Publish to Site - - - - )} - - {/* Edit Action */} - {onEdit && ( - handleMenuAction(onEdit)}> - - - - Edit - - )} - - {/* Generate Image Action */} - {onGenerateImage && ( - handleMenuAction(onGenerateImage)}> - - - - Generate Image Prompts - - )} - - {/* Export Action */} - {onExport && ( - handleMenuAction(onExport)}> - - - - Export - - )} - - {/* Delete Action */} - {onDelete && ( - <> - - handleMenuAction(onDelete)} sx={{ color: 'error.main' }}> - - - - Delete - - - )} - +
+ {/* WordPress Publishing - Only show if images are ready */} + {canPublishToWordPress && ( + <> + +
+ + )} + + {/* Edit Action */} + {onEdit && ( + + )} + + {/* Generate Image Action */} + {onGenerateImage && ( + + )} + + {/* Export Action */} + {onExport && ( + + )} + + {/* Delete Action */} + {onDelete && ( + <> +
+ + + )} +
+ {/* WordPress Publish Dialog */} {showWordPressDialog && ( diff --git a/frontend/src/components/WordPressPublish/WordPressPublish.tsx b/frontend/src/components/WordPressPublish/WordPressPublish.tsx index 91745a8e..159779b0 100644 --- a/frontend/src/components/WordPressPublish/WordPressPublish.tsx +++ b/frontend/src/components/WordPressPublish/WordPressPublish.tsx @@ -1,26 +1,23 @@ +/** + * WordPressPublish Component + * + * Handles publishing content to connected WordPress sites. + * + * 🔒 STYLE LOCKED - Uses only standard ui/ components. See DESIGN_SYSTEM.md + */ import React, { useState, useEffect } from 'react'; -import { - Button, - Alert, - Chip, - IconButton, - Tooltip, - Dialog, - DialogTitle, - DialogContent, - DialogActions, - CircularProgress, - Box, - Typography -} from '@mui/material'; -import { - Publish as PublishIcon, - Refresh as RefreshIcon, - CheckCircle as SuccessIcon, - Error as ErrorIcon, - Schedule as PendingIcon, - Sync as SyncingIcon -} from '@mui/icons-material'; +import { Button } from '../ui/button/Button'; +import { Modal } from '../ui/modal'; +import Alert from '../ui/alert/Alert'; +import { Badge } from '../ui/badge/Badge'; +import { Tooltip } from '../ui/tooltip/Tooltip'; +import { Spinner } from '../ui/spinner/Spinner'; +import { + CheckCircleIcon, + ErrorIcon, + TimeIcon, + PaperPlaneIcon, +} from '../../icons'; import { api } from '../../services/api'; export interface WordPressPublishProps { @@ -149,8 +146,9 @@ export const WordPressPublish: React.FC = ({ const getStatusInfo = () => { if (!wpStatus) { return { - color: 'default' as const, - icon: , + tone: 'neutral' as const, + badgeTone: 'neutral' as const, + icon: , label: 'Not Published', action: 'publish' }; @@ -159,36 +157,41 @@ export const WordPressPublish: React.FC = ({ switch (wpStatus.wordpress_sync_status) { case 'pending': return { - color: 'warning' as const, - icon: , + tone: 'warning' as const, + badgeTone: 'warning' as const, + icon: , label: 'Queued', action: 'wait' }; case 'syncing': return { - color: 'info' as const, - icon: , + tone: 'brand' as const, + badgeTone: 'info' as const, + icon: , label: 'Publishing...', action: 'wait' }; case 'success': return { - color: 'success' as const, - icon: , + tone: 'success' as const, + badgeTone: 'success' as const, + icon: , label: 'Published', action: 'view' }; case 'failed': return { - color: 'error' as const, - icon: , + tone: 'danger' as const, + badgeTone: 'danger' as const, + icon: , label: 'Failed', action: 'retry' }; default: return { - color: 'default' as const, - icon: , + tone: 'neutral' as const, + badgeTone: 'neutral' as const, + icon: , label: 'Not Published', action: 'publish' }; @@ -202,181 +205,178 @@ export const WordPressPublish: React.FC = ({ if (!shouldShowPublishButton) { return ( - - + +
{size !== 'small' && ( - } - label="Images Pending" - color="warning" - size="small" - variant="outlined" - /> + } + > + Images Pending + )} - +
); } - const renderButton = () => { - if (size === 'small') { - return ( - - { - if (statusInfo.action === 'publish') { - setPublishDialogOpen(true); - } else if (statusInfo.action === 'retry') { - handleRetry(); - } else if (statusInfo.action === 'view' && wpStatus?.wordpress_post_url) { - window.open(wpStatus.wordpress_post_url, '_blank'); - } - }} - disabled={loading || statusInfo.action === 'wait'} - color={statusInfo.color} - > - {loading ? : statusInfo.icon} - - - ); + const handleButtonClick = () => { + if (statusInfo.action === 'publish') { + setPublishDialogOpen(true); + } else if (statusInfo.action === 'retry') { + handleRetry(); + } else if (statusInfo.action === 'view' && wpStatus?.wordpress_post_url) { + window.open(wpStatus.wordpress_post_url, '_blank'); } + }; + + const renderButton = () => { + const buttonSize = size === 'small' ? 'sm' : size === 'large' ? 'lg' : 'md'; return ( - + + + ); }; - const renderStatusChip = () => { + const renderStatusBadge = () => { if (size === 'small') return null; return ( - { - if (wpStatus?.wordpress_post_url) { - window.open(wpStatus.wordpress_post_url, '_blank'); - } - }} - style={{ - marginLeft: 8, - cursor: wpStatus?.wordpress_post_url ? 'pointer' : 'default' - }} - /> + + { + if (wpStatus?.wordpress_post_url) { + window.open(wpStatus.wordpress_post_url, '_blank'); + } + }} + > + {statusInfo.label} + + ); }; return ( - +
{renderButton()} - {renderStatusChip()} + {renderStatusBadge()} {error && ( - - {error} - setError(null)} - sx={{ ml: 1 }} - > - × - - +
+ +
)} - {/* Publish Confirmation Dialog */} - setPublishDialogOpen(false)} - maxWidth="sm" - fullWidth + className="max-w-md p-6" > - Publish to Site - - - Are you sure you want to publish "{contentTitle}" to your site? - +
+

+ Publish to Site +

- +

+ Are you sure you want to publish "{contentTitle}" to your site? +

+ +

This will create a new post on your connected site with all content, images, categories, and SEO metadata. - +

{imageGenerationStatus === 'complete' && ( - - ✓ Images are generated and ready for publishing - + )} {imageGenerationStatus !== 'complete' && showOnlyIfImagesReady && ( - - Images are still being generated. Please wait before publishing. - + )} {wpStatus?.wordpress_sync_status === 'success' && ( - - This content is already published to your site. - You can force republish to update the existing post. - + )} - - - - {wpStatus?.wordpress_sync_status === 'success' && ( + +
- )} - - -
- + + {wpStatus?.wordpress_sync_status === 'success' && ( + + )} + + +
+
+ +
); }; diff --git a/frontend/src/components/ui/alert/Alert.tsx b/frontend/src/components/ui/alert/Alert.tsx index d367657c..d5971b56 100644 --- a/frontend/src/components/ui/alert/Alert.tsx +++ b/frontend/src/components/ui/alert/Alert.tsx @@ -71,7 +71,7 @@ const Alert: React.FC = ({ fillRule="evenodd" clipRule="evenodd" d="M20.3499 12.0004C20.3499 16.612 16.6115 20.3504 11.9999 20.3504C7.38832 20.3504 3.6499 16.612 3.6499 12.0004C3.6499 7.38881 7.38833 3.65039 11.9999 3.65039C16.6115 3.65039 20.3499 7.38881 20.3499 12.0004ZM11.9999 22.1504C17.6056 22.1504 22.1499 17.6061 22.1499 12.0004C22.1499 6.3947 17.6056 1.85039 11.9999 1.85039C6.39421 1.85039 1.8499 6.3947 1.8499 12.0004C1.8499 17.6061 6.39421 22.1504 11.9999 22.1504ZM13.0008 16.4753C13.0008 15.923 12.5531 15.4753 12.0008 15.4753L11.9998 15.4753C11.4475 15.4753 10.9998 15.923 10.9998 16.4753C10.9998 17.0276 11.4475 17.4753 11.9998 17.4753L12.0008 17.4753C12.5531 17.4753 13.0008 17.0276 13.0008 16.4753ZM11.9998 6.62898C12.414 6.62898 12.7498 6.96476 12.7498 7.37898L12.7498 13.0555C12.7498 13.4697 12.414 13.8055 11.9998 13.8055C11.5856 13.8055 11.2498 13.4697 11.2498 13.0555L11.2498 7.37898C11.2498 6.96476 11.5856 6.62898 11.9998 6.62898Z" - fill="#F04438" + fill="currentColor" /> ), diff --git a/frontend/src/config/pages/approved.config.tsx b/frontend/src/config/pages/approved.config.tsx index 422c382c..304a3534 100644 --- a/frontend/src/config/pages/approved.config.tsx +++ b/frontend/src/config/pages/approved.config.tsx @@ -82,7 +82,7 @@ export function createApprovedPageConfig(params: { target="_blank" rel="noopener noreferrer" className="text-brand-500 hover:text-brand-600 transition-colors" - title="View on WordPress" + title="View on Site" > diff --git a/frontend/src/index.css b/frontend/src/index.css deleted file mode 100644 index b24c84d1..00000000 --- a/frontend/src/index.css +++ /dev/null @@ -1,571 +0,0 @@ -@import "tailwindcss"; -@import url("https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&display=swap") layer(base); - -@import "./styles/tokens.css"; - -@keyframes slide-in-right { - from { - opacity: 0; - transform: translateX(100%); - } - to { - opacity: 1; - transform: translateX(0); - } -} - -.animate-slide-in-right { - animation: slide-in-right 0.3s ease-out; -} - -@custom-variant dark (&:is(.dark *)); - -@theme { - /* Fonts */ - --font-outfit: Outfit, sans-serif; - --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; - - /* Breakpoints */ - --breakpoint-2xsm: 375px; - --breakpoint-xsm: 425px; - --breakpoint-sm: 640px; - --breakpoint-md: 768px; - --breakpoint-lg: 1024px; - --breakpoint-xl: 1280px; - --breakpoint-2xl: 1536px; - --breakpoint-3xl: 2000px; - - /* Typography */ - --text-title-2xl: 72px; - --text-title-2xl--line-height: 90px; - --text-title-xl: 60px; - --text-title-xl--line-height: 72px; - --text-title-lg: 48px; - --text-title-lg--line-height: 60px; - --text-title-md: 36px; - --text-title-md--line-height: 44px; - --text-title-sm: 30px; - --text-title-sm--line-height: 38px; - --text-theme-xl: 20px; - --text-theme-xl--line-height: 30px; - --text-theme-sm: 14px; - --text-theme-sm--line-height: 20px; - --text-theme-xs: 12px; - --text-theme-xs--line-height: 18px; - - /* Base Colors */ - --color-current: currentColor; - --color-transparent: transparent; - --color-white: #ffffff; - --color-black: #101828; - - /* Brand Colors */ - --color-brand-25: color-mix(in srgb, var(--color-primary) 3%, white); - --color-brand-50: color-mix(in srgb, var(--color-primary) 8%, white); - --color-brand-100: color-mix(in srgb, var(--color-primary) 15%, white); - --color-brand-200: color-mix(in srgb, var(--color-primary) 25%, white); - --color-brand-300: color-mix(in srgb, var(--color-primary) 40%, white); - --color-brand-400: color-mix(in srgb, var(--color-primary) 60%, white); - --color-brand-500: var(--color-primary); - --color-brand-600: var(--color-primary-dark); - --color-brand-700: color-mix(in srgb, var(--color-primary-dark) 85%, black); - --color-brand-800: color-mix(in srgb, var(--color-primary-dark) 70%, black); - --color-brand-900: color-mix(in srgb, var(--color-primary-dark) 55%, black); - --color-brand-950: color-mix(in srgb, var(--color-primary-dark) 35%, black); - - /* Gray Scale */ - --color-gray-25: #fcfcfd; - --color-gray-50: #f9fafb; - --color-gray-100: #f2f4f7; - --color-gray-200: #e4e7ec; - --color-gray-300: #d0d5dd; - --color-gray-400: #98a2b3; - --color-gray-500: #667085; - --color-gray-600: #475467; - --color-gray-700: #344054; - --color-gray-800: #1d2939; - --color-gray-900: #101828; - --color-gray-950: #0c111d; - --color-gray-dark: #1a2231; - - /* Success */ - --color-success-25: color-mix(in srgb, var(--color-success) 2%, white); - --color-success-50: color-mix(in srgb, var(--color-success) 6%, white); - --color-success-100: color-mix(in srgb, var(--color-success) 15%, white); - --color-success-200: color-mix(in srgb, var(--color-success) 30%, white); - --color-success-300: color-mix(in srgb, var(--color-success) 50%, white); - --color-success-400: color-mix(in srgb, var(--color-success) 75%, white); - --color-success-500: var(--color-success); - --color-success-600: var(--color-success-dark); - --color-success-700: color-mix(in srgb, var(--color-success-dark) 85%, black); - --color-success-800: color-mix(in srgb, var(--color-success-dark) 65%, black); - --color-success-900: color-mix(in srgb, var(--color-success-dark) 50%, black); - --color-success-950: color-mix(in srgb, var(--color-success-dark) 35%, black); - - /* Error */ - --color-error-25: color-mix(in srgb, var(--color-danger) 2%, white); - --color-error-50: color-mix(in srgb, var(--color-danger) 5%, white); - --color-error-100: color-mix(in srgb, var(--color-danger) 10%, white); - --color-error-200: color-mix(in srgb, var(--color-danger) 20%, white); - --color-error-300: color-mix(in srgb, var(--color-danger) 40%, white); - --color-error-400: color-mix(in srgb, var(--color-danger) 65%, white); - --color-error-500: var(--color-danger); - --color-error-600: var(--color-danger-dark); - --color-error-700: color-mix(in srgb, var(--color-danger-dark) 80%, black); - --color-error-800: color-mix(in srgb, var(--color-danger-dark) 65%, black); - --color-error-900: color-mix(in srgb, var(--color-danger-dark) 50%, black); - --color-error-950: color-mix(in srgb, var(--color-danger-dark) 30%, black); - - /* Warning */ - --color-warning-25: color-mix(in srgb, var(--color-warning) 2%, white); - --color-warning-50: color-mix(in srgb, var(--color-warning) 4%, white); - --color-warning-100: color-mix(in srgb, var(--color-warning) 10%, white); - --color-warning-200: color-mix(in srgb, var(--color-warning) 20%, white); - --color-warning-300: color-mix(in srgb, var(--color-warning) 40%, white); - --color-warning-400: color-mix(in srgb, var(--color-warning) 65%, white); - --color-warning-500: var(--color-warning); - --color-warning-600: var(--color-warning-dark); - --color-warning-700: color-mix(in srgb, var(--color-warning-dark) 80%, black); - --color-warning-800: color-mix(in srgb, var(--color-warning-dark) 65%, black); - --color-warning-900: color-mix(in srgb, var(--color-warning-dark) 50%, black); - --color-warning-950: color-mix(in srgb, var(--color-warning-dark) 30%, black); - - /* Purple */ - --color-purple-25: color-mix(in srgb, var(--color-purple) 2%, white); - --color-purple-50: color-mix(in srgb, var(--color-purple) 8%, white); - --color-purple-100: color-mix(in srgb, var(--color-purple) 15%, white); - --color-purple-200: color-mix(in srgb, var(--color-purple) 25%, white); - --color-purple-300: color-mix(in srgb, var(--color-purple) 40%, white); - --color-purple-400: color-mix(in srgb, var(--color-purple) 60%, white); - --color-purple-500: var(--color-purple); - --color-purple-600: var(--color-purple-dark); - --color-purple-700: color-mix(in srgb, var(--color-purple-dark) 85%, black); - --color-purple-800: color-mix(in srgb, var(--color-purple-dark) 70%, black); - --color-purple-900: color-mix(in srgb, var(--color-purple-dark) 55%, black); - --color-purple-950: color-mix(in srgb, var(--color-purple-dark) 35%, black); - - /* Shadows */ - --shadow-theme-xs: 0px 1px 2px 0px rgba(16, 24, 40, 0.05); - --shadow-theme-sm: 0px 1px 3px 0px rgba(16, 24, 40, 0.1), 0px 1px 2px 0px rgba(16, 24, 40, 0.06); - --shadow-theme-md: 0px 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06); - --shadow-theme-lg: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); - --shadow-theme-xl: 0px 20px 24px -4px rgba(16, 24, 40, 0.08), 0px 8px 8px -4px rgba(16, 24, 40, 0.03); - --drop-shadow-4xl: 0 35px 35px rgba(0, 0, 0, 0.25), 0 45px 65px rgba(0, 0, 0, 0.15); - - /* Z-Index */ - --z-index-1: 1; - --z-index-9: 9; - --z-index-99: 99; - --z-index-999: 999; - --z-index-9999: 9999; - --z-index-99999: 99999; - --z-index-999999: 999999; -} - -@layer base { - *,::after,::before,::backdrop,::file-selector-button { - border-color: var(--color-gray-200, currentColor); - } - button:not(:disabled),[role="button"]:not(:disabled) { - cursor: pointer; - } - body { - @apply relative font-normal font-outfit z-1 bg-gray-50; - } -} - -@utility menu-item { - @apply relative flex items-center w-full gap-3.5 px-4 py-3 font-medium rounded-lg text-theme-sm; -} - -@utility menu-item-active { - @apply bg-brand-50 text-brand-500 dark:bg-brand-500/[0.12] dark:text-brand-400; -} - -@utility menu-item-inactive { - @apply text-gray-700 hover:bg-gray-100 group-hover:text-gray-700 dark:text-gray-300 dark:hover:bg-white/5 dark:hover:text-gray-300; -} - -/* Menu icon sizing - consistent across sidebar */ -@utility menu-item-icon-size { - @apply w-6 h-6 flex-shrink-0; - - /* Force SVG icons to inherit parent size */ - & svg { - width: 100%; - height: 100%; - } -} - -@utility menu-item-icon-active { - @apply text-brand-500; -} - -@utility menu-item-icon-inactive { - @apply text-gray-500 group-hover:text-gray-700; -} - -/* Dropdown menu items - increased spacing */ -@utility menu-dropdown-item { - @apply block px-3.5 py-2.5 text-theme-sm font-medium rounded-md transition-colors; -} - -@utility menu-dropdown-item-active { - @apply text-brand-600 bg-brand-50; -} - -@utility menu-dropdown-item-inactive { - @apply text-gray-600 hover:text-gray-900 hover:bg-gray-50; -} - -@utility menu-dropdown-badge { - @apply px-1.5 py-0.5 text-xs font-medium rounded; -} - -@utility menu-dropdown-badge-active { - @apply bg-brand-100 text-brand-700; -} - -@utility menu-dropdown-badge-inactive { - @apply bg-gray-100 text-gray-600; -} - -@layer utilities { - input[type="date"]::-webkit-inner-spin-button, - input[type="time"]::-webkit-inner-spin-button, - input[type="date"]::-webkit-calendar-picker-indicator, - input[type="time"]::-webkit-calendar-picker-indicator { - display: none; - -webkit-appearance: none; - } -} - -.tableCheckbox:checked ~ span span { @apply opacity-100; } -.tableCheckbox:checked ~ span { @apply border-brand-500 bg-brand-500; } - -.apexcharts-legend-text { @apply !text-gray-700 dark:!text-gray-400; } -.apexcharts-text { @apply !fill-gray-700 dark:!fill-gray-400; } -.apexcharts-tooltip.apexcharts-theme-light { @apply gap-1 !rounded-lg !border-gray-200 p-3 !shadow-theme-sm dark:!border-gray-800 dark:!bg-gray-900; } -.apexcharts-tooltip-marker { margin-right: 6px; height: 6px; width: 6px; } -.apexcharts-legend-text { @apply !pl-5 !text-gray-700 dark:!text-gray-400; } -.apexcharts-tooltip-series-group { @apply !p-0; } -.apexcharts-tooltip-y-group { @apply !p-0; } -.apexcharts-tooltip-title { @apply !mb-0 !border-b-0 !bg-transparent !p-0 !text-[10px] !leading-4 !text-gray-800 dark:!text-white/90; } -.apexcharts-tooltip-text { @apply !text-theme-xs !text-gray-700 dark:!text-white/90; } -.apexcharts-tooltip-text-y-value { @apply !font-medium; } -.apexcharts-gridline { @apply !stroke-gray-100 dark:!stroke-gray-800; } -#chartTwo .apexcharts-datalabels-group { @apply !-translate-y-24; } -#chartTwo .apexcharts-datalabels-group .apexcharts-text { @apply !fill-gray-800 !font-semibold dark:!fill-white/90; } -#chartDarkStyle .apexcharts-datalabels-group .apexcharts-text { @apply !fill-gray-800 !font-semibold dark:!fill-white/90; } -#chartSixteen .apexcharts-legend { @apply !p-0 !pl-6; } - -.jvectormap-container { @apply !bg-gray-50 dark:!bg-gray-900; } -.jvectormap-region.jvectormap-element { @apply !fill-gray-300 hover:!fill-brand-500 dark:!fill-gray-700 dark:hover:!fill-brand-500; } -.jvectormap-marker.jvectormap-element { @apply !stroke-gray-200 dark:!stroke-gray-800; } -.jvectormap-tip { @apply !bg-brand-500 !border-none !px-2 !py-1; } -.jvectormap-zoomin,.jvectormap-zoomout { @apply !bg-brand-500; } - -.task-item + .task-item { @apply !border-t-gray-200 dark:!border-t-gray-700; } -.custom-input-date input::-webkit-calendar-picker-indicator { background-position: center; background-repeat: no-repeat; background-size: 20px; cursor: pointer; } -.no-scrollbar::-webkit-scrollbar { display: none; } -.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; } -.chat-height { height: calc(100vh - 8.125rem); } -.inbox-height { height: calc(100vh - 8.125rem); } - -/* =================================================================== - IGNY8 ACTIVE UTILITY CLASSES - Migrated from igny8-colors.css - these are actively used in components - =================================================================== */ - -/* === Styled Dropdown/Select === */ -.igny8-select-styled { - background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' viewBox='0 0 12 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1L6 6L11 1' stroke='%23647085' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") !important; - background-repeat: no-repeat !important; - background-position: right 12px center !important; - padding-right: 36px !important; -} - -.dark .igny8-select-styled { - background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' viewBox='0 0 12 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1L6 6L11 1' stroke='%2398A2B3' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") !important; -} - -/* === Header Metrics Bar (compact, right-aligned) === */ -.igny8-header-metrics { - display: flex; - align-items: center; - gap: 6px; - padding: 6px 12px; - background: transparent; - border-radius: 6px; - box-shadow: 0 2px 6px 3px rgba(0, 0, 0, 0.08); -} - -.dark .igny8-header-metrics { - background: transparent; - box-shadow: 0 2px 6px 3px rgba(0, 0, 0, 0.08); -} - -.igny8-header-metric { - display: flex; - align-items: center; - gap: 8px; -} - -.igny8-header-metric-separator { - width: 1px; - height: 16px; - background: rgb(203 213 225); - opacity: 0.4; -} - -.dark .igny8-header-metric-separator { - background: rgb(148 163 184); - opacity: 0.3; -} - -.igny8-header-metric-label { - font-size: 13px; - font-weight: 500; - text-transform: capitalize; - letter-spacing: 0.3px; - color: rgb(100 116 139); -} - -.dark .igny8-header-metric-label { - color: rgb(148 163 184); -} - -.igny8-header-metric-value { - font-size: 16px; - font-weight: 700; - color: rgb(30 41 59); - margin-left: 4px; -} - -.igny8-header-metric-value-credits { - font-size: 13px; -} - -.dark .igny8-header-metric-value { - color: white; -} - -.igny8-header-metric-accent { - width: 4px; - height: 20px; - border-radius: 5px; -} - -.igny8-header-metric-accent.blue { - background: var(--color-primary); -} - -.igny8-header-metric-accent.green { - background: var(--color-success); -} - -.igny8-header-metric-accent.amber { - background: var(--color-warning); -} - -.igny8-header-metric-accent.purple { - background: var(--color-purple); -} - -/* === Table Compact Styles === */ -.igny8-table-compact th { - padding: 12px 16px !important; - font-size: 14px !important; - font-weight: 600 !important; - color: var(--color-gray-600) !important; - text-align: left !important; - background-color: var(--color-gray-50) !important; - border-bottom: 2px solid var(--color-gray-200) !important; - text-transform: capitalize; - letter-spacing: 0.3px; -} - -.dark .igny8-table-compact th { - color: var(--color-gray-200) !important; - background-color: rgba(15, 23, 42, 0.5) !important; - border-bottom-color: rgba(255, 255, 255, 0.1) !important; -} - -.igny8-table-compact td { - padding: 8px 12px !important; - font-size: 14px !important; - border-bottom: 1px solid var(--color-gray-200) !important; -} - -.dark .igny8-table-compact td { - border-bottom-color: rgba(255, 255, 255, 0.05) !important; -} - -.igny8-table-compact th.text-center { - text-align: center !important; -} - -.igny8-table-compact td.text-center { - text-align: center !important; -} - -/* === Smooth Table Height Transitions === */ -.igny8-table-container { - min-height: 500px; - transition: min-height 0.8s cubic-bezier(0.4, 0, 0.2, 1); - position: relative; - overflow: hidden; - will-change: min-height; -} - -.igny8-table-container.loading { - min-height: 500px; - overflow: hidden !important; - contain: layout style paint; -} - -.igny8-table-container.loaded { - min-height: auto; - overflow: visible; - transition: min-height 0.8s cubic-bezier(0.4, 0, 0.2, 1); - animation: fadeInContainer 0.3s ease-out; -} - -@keyframes fadeInContainer { - from { opacity: 0.95; } - to { opacity: 1; } -} - -.igny8-table-wrapper { - width: 100%; - position: relative; - overflow-x: hidden; - overflow-y: hidden; - scrollbar-width: thin; - scrollbar-color: rgba(148, 163, 184, 0.3) transparent; - transition: opacity 0.4s ease-in-out; - contain: layout; -} - -.igny8-table-container.loading .igny8-table-wrapper { - overflow-x: hidden !important; - overflow-y: hidden !important; - scrollbar-width: none; - -ms-overflow-style: none; -} - -.igny8-table-container.loading .igny8-table-wrapper::-webkit-scrollbar { - display: none !important; - width: 0 !important; - height: 0 !important; -} - -.igny8-table-container.loaded .igny8-table-wrapper { - overflow-x: auto; - overflow-y: hidden; - animation: showScrollbar 0.4s ease-out 0.3s both; -} - -@keyframes showScrollbar { - from { scrollbar-width: none; } - to { scrollbar-width: thin; } -} - -.igny8-table-smooth { - width: 100%; - table-layout: fixed; - min-width: 100%; - transition: opacity 0.5s ease-in-out; - contain: layout; -} - -.igny8-table-container.loading .igny8-table-smooth { - opacity: 0.8; - visibility: visible; -} - -.igny8-table-container.loaded .igny8-table-smooth { - opacity: 1; - table-layout: auto; - transition: opacity 0.5s ease-in-out, table-layout 0.1s ease-out; -} - -.igny8-table-body { - position: relative; - min-height: 450px; - transition: min-height 0.8s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.5s ease-in-out; - contain: layout; -} - -.igny8-table-container.loading .igny8-table-body { - min-height: 450px; - opacity: 1; - height: auto; -} - -.igny8-table-container.loaded .igny8-table-body { - min-height: 0; - opacity: 1; - transition: min-height 0.8s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.5s ease-in-out; -} - -.igny8-table-container.loading .igny8-table-body > tr:not(.igny8-skeleton-row) { - display: none !important; - visibility: hidden; -} - -.igny8-table-container.loaded .igny8-table-body > tr.igny8-skeleton-row { - display: none !important; - visibility: hidden; - opacity: 0; - pointer-events: none; -} - -.igny8-data-row { - animation: fadeInRow 0.6s ease-out forwards; - opacity: 0; - transform: translateY(8px); -} - -@keyframes fadeInRow { - from { opacity: 0; transform: translateY(8px); } - to { opacity: 1; transform: translateY(0); } -} - -.igny8-skeleton-row { - animation: none !important; - opacity: 1 !important; - transform: none !important; - display: table-row !important; -} - -.igny8-table-container.loading * { - backface-visibility: hidden; - perspective: 1000px; -} - -/* === Filter Bar === */ -.igny8-filter-bar { - display: flex; - align-items: center; - gap: 8px; -} - -/* === Difficulty Badge Special Styling === */ -.difficulty-badge { - border-radius: 3px !important; - min-width: 28px !important; - display: inline-flex !important; - justify-content: center !important; - align-items: center !important; -} - -.difficulty-badge.difficulty-very-hard { - background-color: var(--color-error-600) !important; - color: white !important; -} - -.dark .difficulty-badge.difficulty-very-hard { - background-color: var(--color-error-600) !important; - color: white !important; -} diff --git a/frontend/src/layout/AppSidebar.tsx b/frontend/src/layout/AppSidebar.tsx index a9a7bffa..7c0e1429 100644 --- a/frontend/src/layout/AppSidebar.tsx +++ b/frontend/src/layout/AppSidebar.tsx @@ -18,6 +18,7 @@ import { FileIcon, UserIcon, UserCircleIcon, + ShootingStarIcon, } from "../icons"; import { useSidebar } from "../context/SidebarContext"; import { useAuthStore } from "../store/authStore"; @@ -67,9 +68,16 @@ const AppSidebar: React.FC = () => { // New structure: Dashboard (standalone) → SETUP → WORKFLOW → SETTINGS // Module visibility is controlled by GlobalModuleSettings (Django Admin only) const menuSections: MenuSection[] = useMemo(() => { - // SETUP section items - Ordered: Sites → Add Keywords → Content Settings → Thinker + // SETUP section items - Ordered: Setup Wizard → Sites → Add Keywords → Content Settings → Thinker const setupItems: NavItem[] = []; + // Setup Wizard at top - guides users through site setup + setupItems.push({ + icon: , + name: "Setup Wizard", + path: "/setup/wizard", + }); + // Sites is always visible - it's core functionality for managing sites setupItems.push({ icon: , diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index 44e6910e..2550c2b4 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,6 +1,6 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; -import "./index.css"; +import "./styles/design-system.css"; import "swiper/swiper-bundle.css"; import "flatpickr/dist/flatpickr.css"; import App from "./App"; diff --git a/frontend/src/marketing/styles/marketing.css b/frontend/src/marketing/styles/marketing.css index a9c951fc..32760507 100644 --- a/frontend/src/marketing/styles/marketing.css +++ b/frontend/src/marketing/styles/marketing.css @@ -1,8 +1,7 @@ @import url("https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&display=swap") layer(base); -@import "../../styles/tokens.css"; -@import "tailwindcss"; +@import "../../styles/design-system.css"; @custom-variant dark (&:is(.dark *)); diff --git a/frontend/src/pages/Setup/SetupWizard.tsx b/frontend/src/pages/Setup/SetupWizard.tsx new file mode 100644 index 00000000..c847b251 --- /dev/null +++ b/frontend/src/pages/Setup/SetupWizard.tsx @@ -0,0 +1,38 @@ +/** + * Setup Wizard Page + * Wraps the OnboardingWizard component for direct access via sidebar + * Can be accessed anytime, not just for new users + */ +import { useNavigate } from 'react-router-dom'; +import OnboardingWizard from '../../components/onboarding/OnboardingWizard'; +import PageHeader from '../../components/common/PageHeader'; +import { ShootingStarIcon } from '../../icons'; + +export default function SetupWizard() { + const navigate = useNavigate(); + + const handleComplete = () => { + navigate('/dashboard'); + }; + + const handleSkip = () => { + navigate('/dashboard'); + }; + + return ( +
+ , color: 'blue' }} + description="Complete guided setup for your site" + /> + +
+ +
+
+ ); +} diff --git a/frontend/src/styles/README.md b/frontend/src/styles/README.md index 69e70f0a..aac08b8c 100644 --- a/frontend/src/styles/README.md +++ b/frontend/src/styles/README.md @@ -1,24 +1,33 @@ -# Design Tokens & Styles +# Design System Styles -This directory contains the centralized design token system for the IGNY8 application. +This directory contains the centralized design system for the IGNY8 application. ## Files -- `tokens.css` - **Single source of truth** for all design tokens (colors, gradients, shadows) -- `global.css` - Global base styles -- `cms/` - CMS-specific styles +- `design-system.css` - **SINGLE SOURCE OF TRUTH** for all styles + - Design tokens (colors, gradients, shadows, radii) + - Tailwind @theme configuration + - Dark mode overrides + - Component utilities + - All custom CSS classes -## Design Tokens (`tokens.css`) +## Quick Reference -All design tokens use plain naming (no "igny8" prefix): +### Entry Point +```tsx +// In main.tsx - THE ONLY CSS import needed +import "./styles/design-system.css"; +``` ### Color Tokens -- `--color-primary` - Primary brand blue (#2C7AA1) -- `--color-primary-dark` - Primary dark variant (#236082) -- `--color-success` - Success green (#2CA18E) -- `--color-warning` - Warning amber (#D9A12C) -- `--color-danger` - Danger red (#A12C40) -- `--color-purple` - Purple accent (#2C40A1) +```css +--color-primary /* Primary brand blue (#0077B6) */ +--color-primary-dark /* Darker variant (#005A8C) */ +--color-success /* Success green (#00B894) */ +--color-warning /* Warning amber (#F59E0B) */ +--color-danger /* Error red (#DC2626) */ +--color-purple /* Premium purple (#7C3AED) */ +``` ### Usage @@ -35,7 +44,7 @@ All design tokens use plain naming (no "igny8" prefix): // Use colors.config.ts for programmatic access import { getModuleCSSColor } from '@/config/colors.config'; -const color = getModuleCSSColor('keywords'); // Returns computed CSS value +const color = getModuleCSSColor('keywords'); ``` **❌ DON'T:** @@ -50,22 +59,25 @@ const color = getModuleCSSColor('keywords'); // Returns computed CSS value
Content
``` -## Active Utility Classes (in index.css) +## Utility Classes -The following utility classes are actively used: -- `.igny8-table-*` - Table styling utilities -- `.igny8-header-metric-*` - Header metrics bar styling -- `.igny8-select-styled` - Dropdown arrow styling +All utility classes are in `design-system.css`: -These are defined in `/src/index.css`, not here. +| Class | Purpose | +|-------|---------| +| `.menu-item` | Sidebar menu items | +| `.igny8-table-*` | Table styling utilities | +| `.igny8-header-metric-*` | Header metrics bar | +| `.igny8-select-styled` | Dropdown arrow styling | +| `.igny8-filter-bar` | Filter bar layout | ## Migration All code should use: -1. Design tokens from `tokens.css` via CSS variables +1. Design tokens from `design-system.css` via CSS variables 2. Tailwind utilities mapped to design tokens (`bg-brand-500`, `text-success-500`) 3. `colors.config.ts` for programmatic color access -3. React components (`Button`, `Badge`, `Card`) +4. Standard React components (`Button`, `Badge`, `Card`) -See `../MIGRATION_GUIDE.md` for complete migration guide. +See `../DESIGN_SYSTEM.md` for complete guidelines. diff --git a/frontend/src/styles/design-system.css b/frontend/src/styles/design-system.css new file mode 100644 index 00000000..f843deca --- /dev/null +++ b/frontend/src/styles/design-system.css @@ -0,0 +1,972 @@ +/* =================================================================== + IGNY8 DESIGN SYSTEM - SINGLE SOURCE OF TRUTH + =================================================================== + + This file consolidates ALL design tokens, color scales, utilities, + and component styles. Import this file in main.tsx/App.tsx. + + 🔒 STYLE LOCKED - All colors, spacing, and components must use + these tokens. See DESIGN_SYSTEM.md for usage guidelines. + + Last Updated: 2026-01-01 + =================================================================== */ + +@import "tailwindcss"; +@import url("https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&display=swap") layer(base); + + +/* =================================================================== + SECTION 1: DESIGN TOKENS (CSS VARIABLES) + =================================================================== */ + +:root { + /* ----------------------------------------------------------------- + 1.1 PRIMARY BRAND COLORS + ----------------------------------------------------------------- */ + + /* Primary Blue - Electric & Confident */ + --color-primary: #0077B6; + --color-primary-dark: #005A8C; + --color-primary-light: #00A8E8; + --color-primary-subtle: #E0F4FF; + + /* Success - Fresh Mint Teal */ + --color-success: #00B894; + --color-success-dark: #009676; + --color-success-light: #55EFC4; + --color-success-subtle: #E0FFF7; + + /* Warning - Bold Amber */ + --color-warning: #F59E0B; + --color-warning-dark: #D97706; + --color-warning-light: #FCD34D; + --color-warning-subtle: #FFF8E6; + + /* Danger/Error - Vivid Red */ + --color-danger: #DC2626; + --color-danger-dark: #B91C1C; + --color-danger-light: #F87171; + --color-danger-subtle: #FEE8E8; + + /* Purple - Electric Violet (Premium/Special) */ + --color-purple: #7C3AED; + --color-purple-dark: #5B21B6; + --color-purple-light: #A78BFA; + --color-purple-subtle: #F3EEFF; + + /* ----------------------------------------------------------------- + 1.2 ACCENT COLORS (Charts, Tags, Highlights) + ----------------------------------------------------------------- */ + + /* Coral */ + --color-coral: #FF6B6B; + --color-coral-dark: #EE5A5A; + --color-coral-subtle: #FFF0F0; + + /* Cyan */ + --color-cyan: #06B6D4; + --color-cyan-dark: #0891B2; + --color-cyan-subtle: #E0FCFF; + + /* Indigo */ + --color-indigo: #4F46E5; + --color-indigo-dark: #4338CA; + --color-indigo-subtle: #EEF2FF; + + /* Rose */ + --color-rose: #F43F5E; + --color-rose-dark: #E11D48; + --color-rose-subtle: #FFF1F3; + + /* Emerald */ + --color-emerald: #10B981; + --color-emerald-dark: #059669; + --color-emerald-subtle: #ECFDF5; + + /* Orange */ + --color-orange: #F97316; + --color-orange-dark: #EA580C; + --color-orange-subtle: #FFF7ED; + + /* ----------------------------------------------------------------- + 1.3 SURFACE & BACKGROUND COLORS + ----------------------------------------------------------------- */ + + /* Sidebar */ + --color-navy: #0F172A; + --color-navy-light: #1E293B; + --color-navy-lighter: #334155; + + /* Page Backgrounds */ + --color-surface: #F8FAFC; + --color-surface-alt: #F1F5F9; + + /* Cards & Panels */ + --color-panel: #FFFFFF; + --color-panel-alt: #F8FAFC; + --color-panel-hover: #F1F5F9; + + /* Elevated Surfaces */ + --color-elevated: #FFFFFF; + --color-overlay: rgba(15, 23, 42, 0.5); + + /* ----------------------------------------------------------------- + 1.4 TEXT COLORS + ----------------------------------------------------------------- */ + + --color-text: #0F172A; + --color-text-secondary: #475569; + --color-text-tertiary: #94A3B8; + --color-text-inverse: #FFFFFF; + --color-text-light: #E2E8F0; + --color-text-link: #0077B6; + --color-text-link-hover: #005A8C; + + /* ----------------------------------------------------------------- + 1.5 BORDER & STROKE COLORS + ----------------------------------------------------------------- */ + + --color-stroke: #E2E8F0; + --color-stroke-light: #F1F5F9; + --color-stroke-dark: #CBD5E1; + --color-stroke-focus: #0077B6; + + /* ----------------------------------------------------------------- + 1.6 INTERACTIVE STATES + ----------------------------------------------------------------- */ + + --color-hover: rgba(0, 119, 182, 0.08); + --color-active: rgba(0, 119, 182, 0.12); + --color-selected: rgba(0, 119, 182, 0.16); + --color-disabled: #94A3B8; + --color-disabled-bg: #F1F5F9; + + /* ----------------------------------------------------------------- + 1.7 BORDER RADIUS + ----------------------------------------------------------------- */ + + --radius-xs: 4px; + --radius-sm: 6px; + --radius-base: 8px; + --radius-md: 10px; + --radius-lg: 12px; + --radius-xl: 16px; + --radius-2xl: 20px; + --radius-full: 9999px; + + /* ----------------------------------------------------------------- + 1.8 SHADOWS + ----------------------------------------------------------------- */ + + --shadow-xs: 0 1px 2px rgba(15, 23, 42, 0.05); + --shadow-sm: 0 1px 3px rgba(15, 23, 42, 0.08), 0 1px 2px rgba(15, 23, 42, 0.04); + --shadow-md: 0 4px 6px -1px rgba(15, 23, 42, 0.08), 0 2px 4px -1px rgba(15, 23, 42, 0.04); + --shadow-lg: 0 10px 15px -3px rgba(15, 23, 42, 0.08), 0 4px 6px -2px rgba(15, 23, 42, 0.04); + --shadow-xl: 0 20px 25px -5px rgba(15, 23, 42, 0.10), 0 10px 10px -5px rgba(15, 23, 42, 0.04); + --shadow-glow-primary: 0 0 20px rgba(0, 119, 182, 0.3); + --shadow-glow-success: 0 0 20px rgba(0, 184, 148, 0.3); + --shadow-glow-purple: 0 0 20px rgba(124, 58, 237, 0.3); + + /* ----------------------------------------------------------------- + 1.9 GRADIENTS + ----------------------------------------------------------------- */ + + --gradient-primary: linear-gradient(135deg, #00A8E8 0%, #0077B6 50%, #005A8C 100%); + --gradient-primary-soft: linear-gradient(135deg, #E0F4FF 0%, #B8E4FF 100%); + --gradient-success: linear-gradient(135deg, #55EFC4 0%, #00B894 50%, #009676 100%); + --gradient-success-soft: linear-gradient(135deg, #E0FFF7 0%, #B8F5E8 100%); + --gradient-warning: linear-gradient(135deg, #FCD34D 0%, #F59E0B 50%, #D97706 100%); + --gradient-warning-soft: linear-gradient(135deg, #FFF8E6 0%, #FFEDB8 100%); + --gradient-danger: linear-gradient(135deg, #F87171 0%, #DC2626 50%, #B91C1C 100%); + --gradient-danger-soft: linear-gradient(135deg, #FEE8E8 0%, #FECACA 100%); + --gradient-purple: linear-gradient(135deg, #A78BFA 0%, #7C3AED 50%, #5B21B6 100%); + --gradient-purple-soft: linear-gradient(135deg, #F3EEFF 0%, #E4D8FF 100%); + --gradient-hero: linear-gradient(135deg, #0077B6 0%, #7C3AED 100%); + --gradient-premium: linear-gradient(135deg, #F59E0B 0%, #F97316 50%, #DC2626 100%); + --gradient-ocean: linear-gradient(135deg, #06B6D4 0%, #0077B6 50%, #4F46E5 100%); + --gradient-forest: linear-gradient(135deg, #10B981 0%, #00B894 50%, #06B6D4 100%); + --gradient-panel: linear-gradient(180deg, #FFFFFF 0%, #F8FAFC 100%); + --gradient-sidebar: linear-gradient(180deg, #1E293B 0%, #0F172A 100%); + + /* ----------------------------------------------------------------- + 1.10 CHART COLORS (Data Visualization) + ----------------------------------------------------------------- */ + + --chart-1: #0077B6; + --chart-2: #00B894; + --chart-3: #7C3AED; + --chart-4: #F59E0B; + --chart-5: #F43F5E; + --chart-6: #06B6D4; + --chart-7: #10B981; + --chart-8: #F97316; + + /* ----------------------------------------------------------------- + 1.11 BADGE PRESETS + ----------------------------------------------------------------- */ + + --badge-info-bg: var(--color-primary-subtle); + --badge-info-text: var(--color-primary-dark); + --badge-info-border: var(--color-primary-light); + + --badge-success-bg: var(--color-success-subtle); + --badge-success-text: var(--color-success-dark); + --badge-success-border: var(--color-success-light); + + --badge-warning-bg: var(--color-warning-subtle); + --badge-warning-text: var(--color-warning-dark); + --badge-warning-border: var(--color-warning-light); + + --badge-danger-bg: var(--color-danger-subtle); + --badge-danger-text: var(--color-danger-dark); + --badge-danger-border: var(--color-danger-light); + + --badge-purple-bg: var(--color-purple-subtle); + --badge-purple-text: var(--color-purple-dark); + --badge-purple-border: var(--color-purple-light); + + --badge-neutral-bg: #F1F5F9; + --badge-neutral-text: #475569; + --badge-neutral-border: #CBD5E1; +} + + +/* =================================================================== + SECTION 2: DARK MODE OVERRIDES + =================================================================== */ + +.dark { + /* Backgrounds */ + --color-surface: #0F172A; + --color-surface-alt: #1E293B; + --color-panel: #1E293B; + --color-panel-alt: #334155; + --color-panel-hover: #334155; + --color-elevated: #334155; + --color-overlay: rgba(0, 0, 0, 0.7); + + /* Text */ + --color-text: #F1F5F9; + --color-text-secondary: #CBD5E1; + --color-text-tertiary: #64748B; + --color-text-link: #00A8E8; + --color-text-link-hover: #38BDF8; + + /* Borders */ + --color-stroke: #334155; + --color-stroke-light: #1E293B; + --color-stroke-dark: #475569; + --color-stroke-focus: #00A8E8; + + /* Interactive States */ + --color-hover: rgba(0, 168, 232, 0.12); + --color-active: rgba(0, 168, 232, 0.18); + --color-selected: rgba(0, 168, 232, 0.24); + --color-disabled: #64748B; + --color-disabled-bg: #1E293B; + + /* Primary Colors (brighter for dark mode) */ + --color-primary: #00A8E8; + --color-primary-dark: #0077B6; + --color-primary-light: #38BDF8; + --color-primary-subtle: rgba(0, 168, 232, 0.15); + + --color-success: #34D399; + --color-success-dark: #10B981; + --color-success-light: #6EE7B7; + --color-success-subtle: rgba(52, 211, 153, 0.15); + + --color-warning: #FBBF24; + --color-warning-dark: #F59E0B; + --color-warning-light: #FDE68A; + --color-warning-subtle: rgba(251, 191, 36, 0.15); + + --color-danger: #F87171; + --color-danger-dark: #EF4444; + --color-danger-light: #FCA5A5; + --color-danger-subtle: rgba(248, 113, 113, 0.15); + + --color-purple: #A78BFA; + --color-purple-dark: #8B5CF6; + --color-purple-light: #C4B5FD; + --color-purple-subtle: rgba(167, 139, 250, 0.15); + + /* Accents (brighter) */ + --color-coral: #FCA5A5; + --color-coral-dark: #F87171; + --color-coral-subtle: rgba(252, 165, 165, 0.15); + + --color-cyan: #22D3EE; + --color-cyan-dark: #06B6D4; + --color-cyan-subtle: rgba(34, 211, 238, 0.15); + + --color-indigo: #818CF8; + --color-indigo-dark: #6366F1; + --color-indigo-subtle: rgba(129, 140, 248, 0.15); + + /* Gradients */ + --gradient-panel: linear-gradient(180deg, #1E293B 0%, #0F172A 100%); + --gradient-sidebar: linear-gradient(180deg, #0F172A 0%, #020617 100%); + --gradient-primary-soft: linear-gradient(135deg, rgba(0, 168, 232, 0.2) 0%, rgba(0, 119, 182, 0.1) 100%); + + /* Shadows */ + --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.3); + --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.4), 0 1px 2px rgba(0, 0, 0, 0.2); + --shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.4), 0 2px 4px -1px rgba(0, 0, 0, 0.2); + --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.4), 0 4px 6px -2px rgba(0, 0, 0, 0.2); + --shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.5), 0 10px 10px -5px rgba(0, 0, 0, 0.2); + --shadow-glow-primary: 0 0 30px rgba(0, 168, 232, 0.4); + --shadow-glow-success: 0 0 30px rgba(52, 211, 153, 0.4); + --shadow-glow-purple: 0 0 30px rgba(167, 139, 250, 0.4); + + /* Chart Colors */ + --chart-1: #38BDF8; + --chart-2: #34D399; + --chart-3: #A78BFA; + --chart-4: #FBBF24; + --chart-5: #FB7185; + --chart-6: #22D3EE; + --chart-7: #6EE7B7; + --chart-8: #FB923C; + + /* Badges */ + --badge-info-bg: rgba(0, 168, 232, 0.15); + --badge-info-text: #38BDF8; + --badge-info-border: rgba(0, 168, 232, 0.3); + + --badge-success-bg: rgba(52, 211, 153, 0.15); + --badge-success-text: #34D399; + --badge-success-border: rgba(52, 211, 153, 0.3); + + --badge-warning-bg: rgba(251, 191, 36, 0.15); + --badge-warning-text: #FBBF24; + --badge-warning-border: rgba(251, 191, 36, 0.3); + + --badge-danger-bg: rgba(248, 113, 113, 0.15); + --badge-danger-text: #F87171; + --badge-danger-border: rgba(248, 113, 113, 0.3); + + --badge-purple-bg: rgba(167, 139, 250, 0.15); + --badge-purple-text: #A78BFA; + --badge-purple-border: rgba(167, 139, 250, 0.3); + + --badge-neutral-bg: #334155; + --badge-neutral-text: #CBD5E1; + --badge-neutral-border: #475569; +} + + +/* =================================================================== + SECTION 3: TAILWIND CONFIGURATION + =================================================================== */ + +/* + 🚫 TAILWIND DEFAULT COLORS ARE DISABLED + + Only these color palettes are available: + - brand-* (primary blue) + - gray-* (neutrals) + - success-* (green) + - error-* (red) + - warning-* (amber) + - purple-* (premium) + + ❌ These will NOT work: blue-*, red-*, green-*, emerald-*, + amber-*, indigo-*, pink-*, rose-*, sky-*, teal-*, etc. +*/ + +@custom-variant dark (&:is(.dark *)); + +@theme { + /* ----------------------------------------------------------------- + DISABLE ALL TAILWIND DEFAULT COLOR PALETTES + This forces use of design system colors only + ----------------------------------------------------------------- */ + --color-red-*: initial; + --color-orange-*: initial; + --color-amber-*: initial; + --color-yellow-*: initial; + --color-lime-*: initial; + --color-green-*: initial; + --color-emerald-*: initial; + --color-teal-*: initial; + --color-cyan-*: initial; + --color-sky-*: initial; + --color-blue-*: initial; + --color-indigo-*: initial; + --color-violet-*: initial; + --color-fuchsia-*: initial; + --color-pink-*: initial; + --color-rose-*: initial; + --color-slate-*: initial; + --color-zinc-*: initial; + --color-neutral-*: initial; + --color-stone-*: initial; + + /* ----------------------------------------------------------------- + FONTS + ----------------------------------------------------------------- */ + --font-outfit: Outfit, sans-serif; + --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace; + + /* ----------------------------------------------------------------- + BREAKPOINTS + ----------------------------------------------------------------- */ + --breakpoint-2xsm: 375px; + --breakpoint-xsm: 425px; + --breakpoint-sm: 640px; + --breakpoint-md: 768px; + --breakpoint-lg: 1024px; + --breakpoint-xl: 1280px; + --breakpoint-2xl: 1536px; + --breakpoint-3xl: 2000px; + + /* Typography */ + --text-title-2xl: 72px; + --text-title-2xl--line-height: 90px; + --text-title-xl: 60px; + --text-title-xl--line-height: 72px; + --text-title-lg: 48px; + --text-title-lg--line-height: 60px; + --text-title-md: 36px; + --text-title-md--line-height: 44px; + --text-title-sm: 30px; + --text-title-sm--line-height: 38px; + --text-theme-xl: 20px; + --text-theme-xl--line-height: 30px; + --text-theme-sm: 14px; + --text-theme-sm--line-height: 20px; + --text-theme-xs: 12px; + --text-theme-xs--line-height: 18px; + + /* Base Colors */ + --color-current: currentColor; + --color-transparent: transparent; + --color-white: #ffffff; + --color-black: #101828; + + /* Brand Color Scale (Tailwind integration) */ + --color-brand-25: color-mix(in srgb, var(--color-primary) 3%, white); + --color-brand-50: color-mix(in srgb, var(--color-primary) 8%, white); + --color-brand-100: color-mix(in srgb, var(--color-primary) 15%, white); + --color-brand-200: color-mix(in srgb, var(--color-primary) 25%, white); + --color-brand-300: color-mix(in srgb, var(--color-primary) 40%, white); + --color-brand-400: color-mix(in srgb, var(--color-primary) 60%, white); + --color-brand-500: var(--color-primary); + --color-brand-600: var(--color-primary-dark); + --color-brand-700: color-mix(in srgb, var(--color-primary-dark) 85%, black); + --color-brand-800: color-mix(in srgb, var(--color-primary-dark) 70%, black); + --color-brand-900: color-mix(in srgb, var(--color-primary-dark) 55%, black); + --color-brand-950: color-mix(in srgb, var(--color-primary-dark) 35%, black); + + /* Gray Scale */ + --color-gray-25: #fcfcfd; + --color-gray-50: #f9fafb; + --color-gray-100: #f2f4f7; + --color-gray-200: #e4e7ec; + --color-gray-300: #d0d5dd; + --color-gray-400: #98a2b3; + --color-gray-500: #667085; + --color-gray-600: #475467; + --color-gray-700: #344054; + --color-gray-800: #1d2939; + --color-gray-900: #101828; + --color-gray-950: #0c111d; + --color-gray-dark: #1a2231; + + /* Success Scale */ + --color-success-25: color-mix(in srgb, var(--color-success) 2%, white); + --color-success-50: color-mix(in srgb, var(--color-success) 6%, white); + --color-success-100: color-mix(in srgb, var(--color-success) 15%, white); + --color-success-200: color-mix(in srgb, var(--color-success) 30%, white); + --color-success-300: color-mix(in srgb, var(--color-success) 50%, white); + --color-success-400: color-mix(in srgb, var(--color-success) 75%, white); + --color-success-500: var(--color-success); + --color-success-600: var(--color-success-dark); + --color-success-700: color-mix(in srgb, var(--color-success-dark) 85%, black); + --color-success-800: color-mix(in srgb, var(--color-success-dark) 65%, black); + --color-success-900: color-mix(in srgb, var(--color-success-dark) 50%, black); + --color-success-950: color-mix(in srgb, var(--color-success-dark) 35%, black); + + /* Error Scale */ + --color-error-25: color-mix(in srgb, var(--color-danger) 2%, white); + --color-error-50: color-mix(in srgb, var(--color-danger) 5%, white); + --color-error-100: color-mix(in srgb, var(--color-danger) 10%, white); + --color-error-200: color-mix(in srgb, var(--color-danger) 20%, white); + --color-error-300: color-mix(in srgb, var(--color-danger) 40%, white); + --color-error-400: color-mix(in srgb, var(--color-danger) 65%, white); + --color-error-500: var(--color-danger); + --color-error-600: var(--color-danger-dark); + --color-error-700: color-mix(in srgb, var(--color-danger-dark) 80%, black); + --color-error-800: color-mix(in srgb, var(--color-danger-dark) 65%, black); + --color-error-900: color-mix(in srgb, var(--color-danger-dark) 50%, black); + --color-error-950: color-mix(in srgb, var(--color-danger-dark) 30%, black); + + /* Warning Scale */ + --color-warning-25: color-mix(in srgb, var(--color-warning) 2%, white); + --color-warning-50: color-mix(in srgb, var(--color-warning) 4%, white); + --color-warning-100: color-mix(in srgb, var(--color-warning) 10%, white); + --color-warning-200: color-mix(in srgb, var(--color-warning) 20%, white); + --color-warning-300: color-mix(in srgb, var(--color-warning) 40%, white); + --color-warning-400: color-mix(in srgb, var(--color-warning) 65%, white); + --color-warning-500: var(--color-warning); + --color-warning-600: var(--color-warning-dark); + --color-warning-700: color-mix(in srgb, var(--color-warning-dark) 80%, black); + --color-warning-800: color-mix(in srgb, var(--color-warning-dark) 65%, black); + --color-warning-900: color-mix(in srgb, var(--color-warning-dark) 50%, black); + --color-warning-950: color-mix(in srgb, var(--color-warning-dark) 30%, black); + + /* Purple Scale */ + --color-purple-25: color-mix(in srgb, var(--color-purple) 2%, white); + --color-purple-50: color-mix(in srgb, var(--color-purple) 8%, white); + --color-purple-100: color-mix(in srgb, var(--color-purple) 15%, white); + --color-purple-200: color-mix(in srgb, var(--color-purple) 25%, white); + --color-purple-300: color-mix(in srgb, var(--color-purple) 40%, white); + --color-purple-400: color-mix(in srgb, var(--color-purple) 60%, white); + --color-purple-500: var(--color-purple); + --color-purple-600: var(--color-purple-dark); + --color-purple-700: color-mix(in srgb, var(--color-purple-dark) 85%, black); + --color-purple-800: color-mix(in srgb, var(--color-purple-dark) 70%, black); + --color-purple-900: color-mix(in srgb, var(--color-purple-dark) 55%, black); + --color-purple-950: color-mix(in srgb, var(--color-purple-dark) 35%, black); + + /* Blue-Light Scale (Info) */ + --color-blue-light-25: #f0faff; + --color-blue-light-50: #e0f4ff; + --color-blue-light-100: #bae6fd; + --color-blue-light-200: #7dd3fc; + --color-blue-light-300: #38bdf8; + --color-blue-light-400: #0ea5e9; + --color-blue-light-500: #0284c7; + --color-blue-light-600: #0369a1; + --color-blue-light-700: #075985; + + /* Shadows */ + --shadow-theme-xs: 0px 1px 2px 0px rgba(16, 24, 40, 0.05); + --shadow-theme-sm: 0px 1px 3px 0px rgba(16, 24, 40, 0.1), 0px 1px 2px 0px rgba(16, 24, 40, 0.06); + --shadow-theme-md: 0px 4px 8px -2px rgba(16, 24, 40, 0.1), 0px 2px 4px -2px rgba(16, 24, 40, 0.06); + --shadow-theme-lg: 0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03); + --shadow-theme-xl: 0px 20px 24px -4px rgba(16, 24, 40, 0.08), 0px 8px 8px -4px rgba(16, 24, 40, 0.03); + --drop-shadow-4xl: 0 35px 35px rgba(0, 0, 0, 0.25), 0 45px 65px rgba(0, 0, 0, 0.15); + + /* Z-Index */ + --z-index-1: 1; + --z-index-9: 9; + --z-index-99: 99; + --z-index-999: 999; + --z-index-9999: 9999; + --z-index-99999: 99999; + --z-index-999999: 999999; +} + + +/* =================================================================== + SECTION 4: BASE STYLES + =================================================================== */ + +@layer base { + *, + ::after, + ::before, + ::backdrop, + ::file-selector-button { + border-color: var(--color-gray-200, currentColor); + } + + button:not(:disabled), + [role="button"]:not(:disabled) { + cursor: pointer; + } + + body { + @apply relative font-normal font-outfit z-1 bg-gray-50; + } + + /* Hide date/time picker arrows */ + input[type="date"]::-webkit-inner-spin-button, + input[type="time"]::-webkit-inner-spin-button, + input[type="date"]::-webkit-calendar-picker-indicator, + input[type="time"]::-webkit-calendar-picker-indicator { + display: none; + -webkit-appearance: none; + } +} + + +/* =================================================================== + SECTION 5: COMPONENT UTILITIES + =================================================================== */ + +/* ----------------------------------------------------------------- + 5.1 MENU ITEMS (Sidebar Navigation) + ----------------------------------------------------------------- */ + +@utility menu-item { + @apply relative flex items-center w-full gap-3.5 px-4 py-3 font-medium rounded-lg text-theme-sm; +} + +@utility menu-item-active { + @apply bg-brand-50 text-brand-500 dark:bg-brand-500/[0.12] dark:text-brand-400; +} + +@utility menu-item-inactive { + @apply text-gray-700 hover:bg-gray-100 group-hover:text-gray-700 dark:text-gray-300 dark:hover:bg-white/5 dark:hover:text-gray-300; +} + +@utility menu-item-icon-size { + @apply w-6 h-6 flex-shrink-0; + & svg { width: 100%; height: 100%; } +} + +@utility menu-item-icon-active { + @apply text-brand-500; +} + +@utility menu-item-icon-inactive { + @apply text-gray-500 group-hover:text-gray-700; +} + +/* ----------------------------------------------------------------- + 5.2 DROPDOWN MENU ITEMS + ----------------------------------------------------------------- */ + +@utility menu-dropdown-item { + @apply block px-3.5 py-2.5 text-theme-sm font-medium rounded-md transition-colors; +} + +@utility menu-dropdown-item-active { + @apply text-brand-600 bg-brand-50; +} + +@utility menu-dropdown-item-inactive { + @apply text-gray-600 hover:text-gray-900 hover:bg-gray-50; +} + +@utility menu-dropdown-badge { + @apply px-1.5 py-0.5 text-xs font-medium rounded; +} + +@utility menu-dropdown-badge-active { + @apply bg-brand-100 text-brand-700; +} + +@utility menu-dropdown-badge-inactive { + @apply bg-gray-100 text-gray-600; +} + + +/* =================================================================== + SECTION 6: FORM ELEMENT STYLES + =================================================================== */ + +/* Styled Select/Dropdown with custom chevron */ +.igny8-select-styled { + background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' viewBox='0 0 12 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1L6 6L11 1' stroke='%23647085' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") !important; + background-repeat: no-repeat !important; + background-position: right 12px center !important; + padding-right: 36px !important; +} + +.dark .igny8-select-styled { + background-image: url("data:image/svg+xml,%3Csvg width='12' height='8' viewBox='0 0 12 8' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M1 1L6 6L11 1' stroke='%2398A2B3' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") !important; +} + +/* Checkbox styling */ +.tableCheckbox:checked ~ span span { @apply opacity-100; } +.tableCheckbox:checked ~ span { @apply border-brand-500 bg-brand-500; } + + +/* =================================================================== + SECTION 7: TABLE STYLES + =================================================================== */ + +/* Compact Table Headers */ +.igny8-table-compact th { + padding: 12px 16px !important; + font-size: 14px !important; + font-weight: 600 !important; + color: var(--color-gray-600) !important; + text-align: left !important; + background-color: var(--color-gray-50) !important; + border-bottom: 2px solid var(--color-gray-200) !important; + text-transform: capitalize; + letter-spacing: 0.3px; +} + +.dark .igny8-table-compact th { + color: var(--color-gray-200) !important; + background-color: rgba(15, 23, 42, 0.5) !important; + border-bottom-color: rgba(255, 255, 255, 0.1) !important; +} + +.igny8-table-compact td { + padding: 8px 12px !important; + font-size: 14px !important; + border-bottom: 1px solid var(--color-gray-200) !important; +} + +.dark .igny8-table-compact td { + border-bottom-color: rgba(255, 255, 255, 0.05) !important; +} + +.igny8-table-compact th.text-center, +.igny8-table-compact td.text-center { + text-align: center !important; +} + +/* Smooth Table Height Transitions */ +.igny8-table-container { + min-height: 500px; + transition: min-height 0.8s cubic-bezier(0.4, 0, 0.2, 1); + position: relative; + overflow: hidden; + will-change: min-height; +} + +.igny8-table-container.loading { + min-height: 500px; + overflow: hidden !important; + contain: layout style paint; +} + +.igny8-table-container.loaded { + min-height: auto; + overflow: visible; + transition: min-height 0.8s cubic-bezier(0.4, 0, 0.2, 1); + animation: fadeInContainer 0.3s ease-out; +} + +.igny8-table-wrapper { + width: 100%; + position: relative; + overflow-x: hidden; + overflow-y: hidden; + scrollbar-width: thin; + scrollbar-color: rgba(148, 163, 184, 0.3) transparent; + transition: opacity 0.4s ease-in-out; + contain: layout; +} + +.igny8-table-smooth { + width: 100%; + table-layout: fixed; + min-width: 100%; + transition: opacity 0.5s ease-in-out; + contain: layout; +} + +.igny8-table-body { + position: relative; + min-height: 450px; + transition: min-height 0.8s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.5s ease-in-out; + contain: layout; +} + +.igny8-data-row { + animation: fadeInRow 0.6s ease-out forwards; + opacity: 0; + transform: translateY(8px); +} + +.igny8-skeleton-row { + animation: none !important; + opacity: 1 !important; + transform: none !important; + display: table-row !important; +} + + +/* =================================================================== + SECTION 8: HEADER METRICS BAR + =================================================================== */ + +.igny8-header-metrics { + display: flex; + align-items: center; + gap: 6px; + padding: 6px 12px; + background: transparent; + border-radius: 6px; + box-shadow: 0 2px 6px 3px rgba(0, 0, 0, 0.08); +} + +.dark .igny8-header-metrics { + background: transparent; + box-shadow: 0 2px 6px 3px rgba(0, 0, 0, 0.08); +} + +.igny8-header-metric { + display: flex; + align-items: center; + gap: 8px; +} + +.igny8-header-metric-separator { + width: 1px; + height: 16px; + background: rgb(203 213 225); + opacity: 0.4; +} + +.dark .igny8-header-metric-separator { + background: rgb(148 163 184); + opacity: 0.3; +} + +.igny8-header-metric-label { + font-size: 13px; + font-weight: 500; + text-transform: capitalize; + letter-spacing: 0.3px; + color: rgb(100 116 139); +} + +.dark .igny8-header-metric-label { + color: rgb(148 163 184); +} + +.igny8-header-metric-value { + font-size: 16px; + font-weight: 700; + color: rgb(30 41 59); + margin-left: 4px; +} + +.igny8-header-metric-value-credits { + font-size: 13px; +} + +.dark .igny8-header-metric-value { + color: white; +} + +.igny8-header-metric-accent { + width: 4px; + height: 20px; + border-radius: 5px; +} + +.igny8-header-metric-accent.blue { background: var(--color-primary); } +.igny8-header-metric-accent.green { background: var(--color-success); } +.igny8-header-metric-accent.amber { background: var(--color-warning); } +.igny8-header-metric-accent.purple { background: var(--color-purple); } + + +/* =================================================================== + SECTION 9: BADGE SPECIAL STYLES + =================================================================== */ + +.difficulty-badge { + border-radius: 3px !important; + min-width: 28px !important; + display: inline-flex !important; + justify-content: center !important; + align-items: center !important; +} + +.difficulty-badge.difficulty-very-hard { + background-color: var(--color-error-600) !important; + color: white !important; +} + +.dark .difficulty-badge.difficulty-very-hard { + background-color: var(--color-error-600) !important; + color: white !important; +} + + +/* =================================================================== + SECTION 10: CHART LIBRARY OVERRIDES (ApexCharts) + =================================================================== */ + +.apexcharts-legend-text { @apply !text-gray-700 dark:!text-gray-400; } +.apexcharts-text { @apply !fill-gray-700 dark:!fill-gray-400; } +.apexcharts-tooltip.apexcharts-theme-light { @apply gap-1 !rounded-lg !border-gray-200 p-3 !shadow-theme-sm dark:!border-gray-800 dark:!bg-gray-900; } +.apexcharts-tooltip-marker { margin-right: 6px; height: 6px; width: 6px; } +.apexcharts-legend-text { @apply !pl-5 !text-gray-700 dark:!text-gray-400; } +.apexcharts-tooltip-series-group { @apply !p-0; } +.apexcharts-tooltip-y-group { @apply !p-0; } +.apexcharts-tooltip-title { @apply !mb-0 !border-b-0 !bg-transparent !p-0 !text-[10px] !leading-4 !text-gray-800 dark:!text-white/90; } +.apexcharts-tooltip-text { @apply !text-theme-xs !text-gray-700 dark:!text-white/90; } +.apexcharts-tooltip-text-y-value { @apply !font-medium; } +.apexcharts-gridline { @apply !stroke-gray-100 dark:!stroke-gray-800; } +#chartTwo .apexcharts-datalabels-group { @apply !-translate-y-24; } +#chartTwo .apexcharts-datalabels-group .apexcharts-text { @apply !fill-gray-800 !font-semibold dark:!fill-white/90; } +#chartDarkStyle .apexcharts-datalabels-group .apexcharts-text { @apply !fill-gray-800 !font-semibold dark:!fill-white/90; } +#chartSixteen .apexcharts-legend { @apply !p-0 !pl-6; } + + +/* =================================================================== + SECTION 11: MAP LIBRARY OVERRIDES (jVectorMap) + =================================================================== */ + +.jvectormap-container { @apply !bg-gray-50 dark:!bg-gray-900; } +.jvectormap-region.jvectormap-element { @apply !fill-gray-300 hover:!fill-brand-500 dark:!fill-gray-700 dark:hover:!fill-brand-500; } +.jvectormap-marker.jvectormap-element { @apply !stroke-gray-200 dark:!stroke-gray-800; } +.jvectormap-tip { @apply !bg-brand-500 !border-none !px-2 !py-1; } +.jvectormap-zoomin, .jvectormap-zoomout { @apply !bg-brand-500; } + + +/* =================================================================== + SECTION 12: UTILITY CLASSES + =================================================================== */ + +/* Task dividers */ +.task-item + .task-item { @apply !border-t-gray-200 dark:!border-t-gray-700; } + +/* Date picker custom styling */ +.custom-input-date input::-webkit-calendar-picker-indicator { + background-position: center; + background-repeat: no-repeat; + background-size: 20px; + cursor: pointer; +} + +/* Scrollbar hiding */ +.no-scrollbar::-webkit-scrollbar { display: none; } +.no-scrollbar { -ms-overflow-style: none; scrollbar-width: none; } + +/* Chat/Inbox heights */ +.chat-height { height: calc(100vh - 8.125rem); } +.inbox-height { height: calc(100vh - 8.125rem); } + +/* Filter bar */ +.igny8-filter-bar { + display: flex; + align-items: center; + gap: 8px; +} + + +/* =================================================================== + SECTION 13: ANIMATIONS + =================================================================== */ + +@keyframes slide-in-right { + from { opacity: 0; transform: translateX(100%); } + to { opacity: 1; transform: translateX(0); } +} + +@keyframes fadeInContainer { + from { opacity: 0.95; } + to { opacity: 1; } +} + +@keyframes fadeInRow { + from { opacity: 0; transform: translateY(8px); } + to { opacity: 1; transform: translateY(0); } +} + +@keyframes showScrollbar { + from { scrollbar-width: none; } + to { scrollbar-width: thin; } +} + +.animate-slide-in-right { + animation: slide-in-right 0.3s ease-out; +} + + +/* =================================================================== + END OF DESIGN SYSTEM + =================================================================== */ diff --git a/frontend/src/styles/global.css b/frontend/src/styles/global.css deleted file mode 100644 index 8448a16c..00000000 --- a/frontend/src/styles/global.css +++ /dev/null @@ -1,32 +0,0 @@ -@import url("https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&display=swap") - layer(base); - -@import "./tokens.css"; -@import "tailwindcss"; - -@layer base { - *, - ::before, - ::after { - box-sizing: border-box; - } - - html, - body { - margin: 0; - padding: 0; - font-family: Outfit, sans-serif; - background-color: #050913; - color: #ffffff; - } - - body { - min-height: 100vh; - } - - a { - color: inherit; - text-decoration: none; - } -} - diff --git a/frontend/src/styles/tokens.css b/frontend/src/styles/tokens.css deleted file mode 100644 index 97a1841a..00000000 --- a/frontend/src/styles/tokens.css +++ /dev/null @@ -1,77 +0,0 @@ -/* =================================================================== - DESIGN TOKENS - =================================================================== - Single source of truth for all design tokens (colors, gradients, shadows, etc.) - Used by both Tailwind @theme and legacy CSS classes. - - 🔒 DESIGN SYSTEM LOCKED - See DESIGN_SYSTEM.md for complete style guide - =================================================================== */ - -:root { - /* =================================================================== - PRIMARY BRAND COLORS - =================================================================== */ - --color-primary: #2C7AA1; /* Primary rich blue - main CTA */ - --color-primary-dark: #236082; /* Darkened for hover / active / gradient depth */ - - /* Success Green (teal-aligned) */ - --color-success: #2CA18E; /* Deep teal for success states */ - --color-success-dark: #238272; /* Deeper teal for hover / active */ - - /* Amber / Warning (warm contrast to cool palette) */ - --color-warning: #D9A12C; /* Golden amber for highlight / warning */ - --color-warning-dark: #B8872A; /* Darker amber for hover / strong warning */ - - /* Danger / Destructive */ - --color-danger: #A12C40; /* Deep red aligned with palette */ - --color-danger-dark: #832334; /* Darker red for hover / active */ - - /* Purple (accent for special emphasis) */ - --color-purple: #2C40A1; /* Navy-purple from analogous palette */ - --color-purple-dark: #232F7A; /* Darker navy-purple for hover / active */ - - /* =================================================================== - BACKGROUND COLORS - =================================================================== */ - --color-navy: #1A2B3C; /* Sidebar background (charcoal-navy) */ - --color-navy-light: #243A4D; /* Slightly lighter navy, hover/active */ - --color-surface: #F8FAFB; /* Page background (cool gray-white) */ - --color-panel: #FFFFFF; /* Cards / panel foreground */ - --color-panel-alt: #EEF4F7; /* Sub-panel / hover card background (teal-tinted) */ - - /* =================================================================== - TEXT COLORS - =================================================================== */ - --color-text: #1A2B3C; /* Main headings/body text (matches navy) */ - --color-text-dim: #5A6B7C; /* Secondary/subtext (mid-gray with blue undertone) */ - --color-text-light: #E8F0F4; /* Text on dark sidebar */ - --color-stroke: #D4E0E8; /* Table/grid borders and dividers (cool-tinted) */ - - /* =================================================================== - BORDER RADIUS - =================================================================== */ - --radius-base: 6px; - - /* =================================================================== - GRADIENTS - =================================================================== */ - --gradient-primary: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%); - --gradient-success: linear-gradient(135deg, var(--color-success) 0%, var(--color-success-dark) 100%); - --gradient-warning: linear-gradient(135deg, var(--color-warning) 0%, var(--color-warning-dark) 100%); - --gradient-danger: linear-gradient(135deg, var(--color-danger) 0%, var(--color-danger-dark) 100%); - --gradient-purple: linear-gradient(135deg, var(--color-purple) 0%, var(--color-purple-dark) 100%); - --gradient-panel: linear-gradient(180deg, var(--color-panel) 0%, var(--color-panel-alt) 100%); -} - -/* =================================================================== - DARK MODE OVERRIDES - =================================================================== */ -.dark { - --color-surface: #1A2B3C; /* Charcoal navy (matches --color-navy) */ - --color-panel: #243A4D; /* Slightly lighter navy for cards */ - --color-panel-alt: #142230; /* Deeper navy for nested panels */ - --color-text: #E8F0F4; /* Cool off-white (matches --color-text-light) */ - --color-text-dim: #8A9BAC; /* Muted blue-gray for secondary text */ - --color-stroke: #2E4A5E; /* Blue-tinted border color */ -} -