phase 1 styling pass

This commit is contained in:
MattLeo 2025-12-08 15:12:08 -06:00
parent 40838bd134
commit 1f1c6e0bbc
19 changed files with 1228 additions and 973 deletions

View File

@ -1,158 +1,95 @@
.admin-panel { .admin-panel {
padding: 2rem; padding: var(--spacing-2xl);
max-width: 1400px; max-width: var(--max-width-3xl);
margin: 0 auto; margin: 0 auto;
} }
.admin-header {
display: flex;
align-items: center;
gap: 2rem;
margin-bottom: 2rem;
border-bottom: 2px solid #333;
padding-bottom: 1rem;
}
.admin-header h1 {
margin: 0;
}
.admin-section {
margin-bottom: 3rem;
}
.admin-section h2 {
margin-bottom: 1rem;
color: #2c3e50;
}
.users-table { .users-table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
background: #242424; background: var(--color-background-alt);
box-shadow: 0 2px 4px rgba(0,0,0,0.1); box-shadow: var(--shadow-sm);
} }
.users-table th, .users-table th,
.users-table td { .users-table td {
padding: 12px; padding: var(--spacing-md);
text-align: left; text-align: left;
border-bottom: 1px solid #ddd; border-bottom: 1px solid var(--color-border);
} }
.users-table th { .users-table th {
background-color: #2c3e50; background-color: var(--color-dark);
color: white; color: var(--color-text-inverse);
font-weight: bold; font-weight: var(--font-weight-bold);
} }
.users-table tr:hover { .users-table tr:hover {
background-color: #7f8c8d; background-color: var(--color-dark-light);
} }
.auth-badge { .auth-badge {
padding: 4px 8px; padding: var(--spacing-xs) var(--spacing-sm);
border-radius: 4px; border-radius: var(--radius-sm);
font-size: 0.85rem; font-size: var(--font-size-sm);
font-weight: bold; font-weight: var(--font-weight-bold);
} }
.auth-badge.local { .auth-badge.local {
background-color: #95a5a6; background-color: var(--color-dark-light);
color: white; color: var(--color-text-inverse);
} }
.auth-badge.entra { .auth-badge.entra {
background-color: #0078d4; background-color: #0078d4;
color: white; color: var(--color-text-inverse);
} }
.role-badge { .role-badge {
padding: 4px 12px; padding: var(--spacing-xs) var(--spacing-md);
border-radius: 12px; border-radius: var(--radius-xl);
font-size: 0.85rem; font-size: var(--font-size-sm);
font-weight: bold; font-weight: var(--font-weight-bold);
} }
.role-badge.admin { .role-badge.admin {
background-color: #e74c3c; background-color: var(--color-danger);
color: white; color: var(--color-text-inverse);
} }
.role-badge.editor { .role-badge.editor {
background-color: #3498db; background-color: var(--color-primary);
color: white; color: var(--color-text-inverse);
} }
.role-badge.user { .role-badge.user {
background-color: #95a5a6; background-color: var(--color-dark-light);
color: white; color: var(--color-text-inverse);
} }
.role-select { .role-select {
padding: 4px 8px; padding: var(--spacing-xs) var(--spacing-sm);
border-radius: 4px; border-radius: var(--radius-sm);
border: 1px solid #ddd; border: 1px solid var(--color-border);
}
.action-buttons {
display: flex;
gap: 8px;
}
.action-buttons button {
padding: 6px 12px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.85rem;
}
.edit-btn {
background-color: #3498db;
color: white;
}
.edit-btn:hover {
background-color: #2980b9;
}
.save-btn {
background-color: #27ae60;
color: white;
}
.save-btn:hover {
background-color: #229954;
}
.cancel-btn {
background-color: #95a5a6;
color: white;
}
.cancel-btn:hover {
background-color: #7f8c8d;
}
.delete-btn {
background-color: #e74c3c;
color: white;
}
.delete-btn:hover {
background-color: #c0392b;
} }
.coming-soon { .coming-soon {
color: #7f8c8d; color: var(--color-text-muted);
font-style: italic; font-style: italic;
} }
.error-message { /* Responsive */
background-color: #ffebee; @media (max-width: 768px) {
color: #c62828; .admin-panel {
padding: 12px; padding: var(--spacing-lg);
border-radius: 4px; }
margin-bottom: 1rem;
.users-table {
font-size: var(--font-size-sm);
}
.users-table th,
.users-table td {
padding: var(--spacing-sm);
}
} }

View File

@ -96,19 +96,27 @@ function AdminPanel({ token, onBack }) {
}; };
if (loading) { if (loading) {
return <div className='admin-panel'>Loading...</div> return (
<div className='admin-panel'>
<div className='loading-container'>
<div className='loading-spinner'></div>
<div className='loading-text'>Loading users...</div>
</div>
</div>
);
} }
return ( return (
<div className='admin-panel'> <div className='admin-panel'>
<div className='admin-header'> <div className='page-header'>
<button className='back-button' onClick={onBack}> Back to Articles</button> <h1 className='page-title'>Admin Panel</h1>
<button className='btn btn-ghost' onClick={onBack}> Back to Articles</button>
</div> </div>
{error && <div className='error-message'>{error}</div>} {error && <div className='error-message'>{error}</div>}
<div className='admin-section'> <div className='section'>
<h2>User Management</h2> <h2 className='section-title'>User Management</h2>
<table className='users-table'> <table className='users-table'>
<thead> <thead>
<tr> <tr>
@ -123,7 +131,7 @@ function AdminPanel({ token, onBack }) {
</thead> </thead>
<tbody> <tbody>
{users.map(user => ( {users.map(user => (
<tr key ={user.id}> <tr key={user.id}>
<td>{user.username}</td> <td>{user.username}</td>
<td>{user.email}</td> <td>{user.email}</td>
<td>{user.display_name}</td> <td>{user.display_name}</td>
@ -152,24 +160,24 @@ function AdminPanel({ token, onBack }) {
<td>{new Date(user.created_at).toLocaleDateString()}</td> <td>{new Date(user.created_at).toLocaleDateString()}</td>
<td> <td>
{editingUserId === user.id ? ( {editingUserId === user.id ? (
<div className='action-buttons'> <div className='btn-group'>
<button <button
className='save-btn' className='btn btn-success btn-sm'
onClick={() => handleSaveRole(user.id)} onClick={() => handleSaveRole(user.id)}
>Save</button> >Save</button>
<button <button
className='cancel-btn' className='btn btn-secondary btn-sm'
onClick={() => handleCancelEdit()} onClick={() => handleCancelEdit()}
>Cancel</button> >Cancel</button>
</div> </div>
) : ( ) : (
<div className='action-buttons'> <div className='btn-group'>
<button <button
className='edit-btn' className='btn btn-primary btn-sm'
onClick={() => handleRoleChange(user.id, user.role)} onClick={() => handleRoleChange(user.id, user.role)}
>Edit Role</button> >Edit Role</button>
<button <button
className='delete-btn' className='btn btn-danger btn-sm'
onClick={() => handleDeleteUser(user.id)} onClick={() => handleDeleteUser(user.id)}
>Delete</button> >Delete</button>
</div> </div>
@ -180,8 +188,9 @@ function AdminPanel({ token, onBack }) {
</tbody> </tbody>
</table> </table>
</div> </div>
<div className='admin-section'>
<h2>Audit Logs</h2> <div className='section'>
<h2 className='section-title'>Audit Logs</h2>
<p className='coming-soon'>Coming Soon...</p> <p className='coming-soon'>Coming Soon...</p>
</div> </div>
</div> </div>

View File

@ -1,105 +1,43 @@
.article-detail { .article-detail {
padding: 2rem; padding: var(--spacing-2xl);
max-width: 800px; max-width: var(--max-width-lg);
margin: 0 auto; margin: 0 auto;
} }
.back-button {
background: none;
border: 1px solid #333;
padding: 0.5rem 1rem;
cursor: pointer;
margin-bottom: 1rem;
}
.back-button:hover {
background-color: #f0f0f0;
}
.edit-button {
background: none;
border: 1px solid #333;
padding: 0.5rem 1rem;
cursor: pointer;
margin-bottom: 1rem;
}
.edit-button:hover {
background-color: #f0f0f0;
}
.delete-button {
background: none;
border: 1px solid #333;
padding: 0.5rem 1rem;
cursor: pointer;
margin-bottom: 1rem;
}
.delete-button:hover {
background-color: #f0f0f0;
}
.ka-number {
background-color: #3498db;
color: white;
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.9rem;
}
.article-meta { .article-meta {
color: #666; color: var(--color-text-secondary);
font-size: 0.9rem; font-size: var(--font-size-md);
} }
.article-content { .article-content {
margin-top: 2rem; margin-top: var(--spacing-2xl);
line-height: 1.6; line-height: var(--line-height-relaxed);
} }
.article-metadata { .article-metadata {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 1rem; gap: var(--spacing-lg);
margin-top: 1rem; margin-top: var(--spacing-lg);
flex-wrap: wrap; flex-wrap: wrap;
} }
.metadata-label { .metadata-label {
font-weight: bold; font-weight: var(--font-weight-bold);
color: #2c3e50; color: var(--color-text-primary);
font-size: 0.95rem; font-size: var(--font-size-md);
}
.metadata-badges {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.badge {
display: inline-flex;
align-items: center;
padding: 0.4rem 0.75rem;
border-radius: 16px;
font-size: 0.85rem;
font-weight: 500;
}
.category-badge {
background-color: #3498db;
color: white;
}
.tag-badge {
background-color: #27ae60;
color: white;
} }
.article-content video { .article-content video {
max-width: 100%; max-width: 100%;
height: auto; height: auto;
display: block; display: block;
margin: 1rem 0; margin: var(--spacing-lg) 0;
}
/* Responsive */
@media (max-width: 768px) {
.article-detail {
padding: var(--spacing-lg);
}
} }

View File

@ -9,16 +9,16 @@ function ArticleDetail({ article, onBack, onEdit, onDelete, currentUser }) {
return ( return (
<div className='article-detail'> <div className='article-detail'>
<div className='button-group'> <div className='btn-group' style={{ marginBottom: 'var(--spacing-xl)' }}>
<button className='back-button' onClick={onBack}> <button className='btn btn-ghost' onClick={onBack}>
Back Back
</button> </button>
{canEdit && ( {canEdit && (
<> <>
<button className='edit-button' onClick={onEdit}> <button className='btn btn-primary' onClick={onEdit}>
Edit Edit
</button> </button>
<button className='delete-button' onClick={onDelete}> <button className='btn btn-danger' onClick={onDelete}>
Delete Delete
</button> </button>
</> </>
@ -26,7 +26,7 @@ function ArticleDetail({ article, onBack, onEdit, onDelete, currentUser }) {
</div> </div>
<div className='article-header'> <div className='article-header'>
<span className='ka-number'>{article.ka_number}</span> <span className='ka-number-badge'>{article.ka_number}</span>
<h1>{article.title}</h1> <h1>{article.title}</h1>
<p className='article-meta'> <p className='article-meta'>
Created: {new Date(article.created_at).toLocaleDateString()} Created: {new Date(article.created_at).toLocaleDateString()}
@ -36,7 +36,7 @@ function ArticleDetail({ article, onBack, onEdit, onDelete, currentUser }) {
{article.categories && article.categories.length > 0 && ( {article.categories && article.categories.length > 0 && (
<div className='article-metadata'> <div className='article-metadata'>
<div className='metadata-label'>Categories:</div> <div className='metadata-label'>Categories:</div>
<div className='metadata-badges'> <div className='badge-group'>
{article.categories.map(category => ( {article.categories.map(category => (
<span key={category} className='badge category-badge'> <span key={category} className='badge category-badge'>
{category} {category}
@ -49,7 +49,7 @@ function ArticleDetail({ article, onBack, onEdit, onDelete, currentUser }) {
{article.tags && article.tags.length > 0 && ( {article.tags && article.tags.length > 0 && (
<div className='article-metadata'> <div className='article-metadata'>
<div className='metadata-label'>Tags:</div> <div className='metadata-label'>Tags:</div>
<div className='metadata-badges'> <div className='badge-group'>
{article.tags.map(tag => ( {article.tags.map(tag => (
<span key={tag} className='badge tag-badge'> <span key={tag} className='badge tag-badge'>
#{tag} #{tag}

View File

@ -1,13 +1,17 @@
.article-editor { .article-editor {
padding: 2rem; padding: var(--spacing-2xl);
max-width: 900px; max-width: var(--max-width-lg);
margin: 0 auto; margin: 0 auto;
transition: padding-bottom var(--transition-slow);
} }
.editor-header { .editor-header {
margin-bottom: 2rem; display: flex;
border-bottom: 2px solid #333; justify-content: space-between;
padding-bottom: 1rem; align-items: center;
margin-bottom: var(--spacing-2xl);
padding-bottom: var(--spacing-lg);
border-bottom: 2px solid var(--color-border-dark);
} }
.editor-header h2 { .editor-header h2 {
@ -17,166 +21,25 @@
.editor-form { .editor-form {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1.5rem; gap: var(--spacing-xl);
}
.editor-form label {
font-weight: bold;
margin-bottom: 0.5rem;
display: block;
}
.editor-form input[type="text"] {
width: 100%;
padding: 0.75rem;
font-size: 1rem;
border: 1px solid #ddd;
border-radius: 4px;
}
.editor-form input[type="text"]:focus {
outline: none;
border-color: #3498db;
} }
/* React Quill editor styling */ /* React Quill editor styling */
.quill { .quill {
background-color: white; background-color: var(--color-background);
} }
.ql-container { .ql-container {
min-height: 300px; min-height: 300px;
font-size: 1rem; font-size: var(--font-size-base);
} }
.ql-editor { .ql-editor {
min-height: 300px; min-height: 300px;
} }
.editor-buttons { /* Category/Tag selector specific styles */
display: flex; .category-selector,
gap: 1rem;
margin-top: 1rem;
}
.editor-buttons button {
padding: 0.75rem 2rem;
font-size: 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
}
.editor-buttons button:first-child {
background-color: #27ae60;
color: white;
}
.editor-buttons button:first-child:hover {
background-color: #229954;
}
.editor-buttons button:last-child {
background-color: #95a5a6;
color: white;
}
.editor-buttons button:last-child:hover {
background-color: #7f8c8d;
}
/* Form field styling */
.form-field {
margin-bottom: 1.5rem;
}
.form-field label {
display: block;
font-weight: bold;
margin-bottom: 0.5rem;
color: #2c3e50;
}
.form-field input[type="text"] {
width: 100%;
padding: 0.75rem;
font-size: 1rem;
border: 1px solid #ddd;
border-radius: 4px;
}
.form-field input[type="text"]:focus {
outline: none;
border-color: #3498db;
}
/* Category selector */
.category-selector {
position: relative;
}
.category-dropdown-btn {
width: 100%;
padding: 0.75rem;
background-color: white;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
text-align: left;
font-size: 1rem;
}
.category-dropdown-btn:hover {
border-color: #3498db;
}
.dropdown-arrow {
color: #666;
font-size: 0.8rem;
}
.category-dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border: 1px solid #ddd;
border-radius: 4px;
max-height: 200px;
overflow-y: auto;
z-index: 100;
margin-top: 4px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.category-option {
display: flex;
align-items: center;
padding: 0.75rem;
cursor: pointer;
transition: background-color 0.2s;
}
.category-option:hover {
background-color: #f5f5f5;
}
.category-option input[type="checkbox"] {
margin-right: 0.75rem;
cursor: pointer;
}
.no-options {
padding: 1rem;
text-align: center;
color: #999;
}
/* Tag selector */
.tag-selector { .tag-selector {
position: relative; position: relative;
} }
@ -185,142 +48,29 @@
position: relative; position: relative;
} }
.tag-input {
width: 100%;
padding: 0.75rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
.tag-input:focus {
outline: none;
border-color: #3498db;
}
.tag-suggestions {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border: 1px solid #ddd;
border-radius: 4px;
margin-top: 4px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
z-index: 100;
}
.tag-suggestion {
padding: 0.75rem;
cursor: pointer;
transition: background-color 0.2s;
}
.tag-suggestion:hover {
background-color: #f5f5f5;
}
/* Badges */
.selected-badges {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
margin-top: 0.75rem;
}
.badge {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.75rem;
border-radius: 16px;
font-size: 0.9rem;
font-weight: 500;
}
.category-badge {
background-color: #3498db;
color: white;
}
.tag-badge {
background-color: #27ae60;
color: white;
}
.badge-remove {
background: none;
border: none;
color: white;
font-size: 1.2rem;
cursor: pointer;
padding: 0;
line-height: 1;
opacity: 0.8;
}
.badge-remove:hover {
opacity: 1;
}
.create-new-section { .create-new-section {
padding: 0.75rem; padding: var(--spacing-md);
border-bottom: 1px solid #ddd; border-bottom: 1px solid var(--color-border);
background-color: #f9f9f9; background-color: var(--color-gray-100);
} }
.create-new-section input { .create-new-section input {
width: 100%; width: 100%;
padding: 0.5rem; padding: var(--spacing-sm);
border: 1px solid #ddd; border: 1px solid var(--color-border);
border-radius: 4px; border-radius: var(--radius-sm);
margin-bottom: 0.5rem; margin-bottom: var(--spacing-sm);
} }
.create-new-buttons { /* Responsive */
display: flex; @media (max-width: 768px) {
gap: 0.5rem; .article-editor {
} padding: var(--spacing-lg);
}
.create-new-buttons button {
padding: 0.4rem 0.8rem; .editor-header {
border: none; flex-direction: column;
border-radius: 4px; align-items: flex-start;
cursor: pointer; gap: var(--spacing-md);
font-size: 0.85rem; }
}
.create-new-buttons .create-btn {
background-color: #27ae60;
color: white;
}
.create-new-buttons .create-btn:hover {
background-color: #229954;
}
.create-new-buttons .cancel-btn {
background-color: #95a5a6;
color: white;
}
.create-new-buttons .cancel-btn:hover {
background-color: #7f8c8d;
}
.create-new-button-dropdown {
width: 100%;
padding: 0.75rem;
background-color: #27ae60;
color: white;
border: none;
border-bottom: 1px solid #ddd;
text-align: left;
cursor: pointer;
font-weight: bold;
}
.create-new-button-dropdown:hover {
background-color: #229954;
} }

View File

@ -84,6 +84,34 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
} }
}, [article]); }, [article]);
// Add padding and scroll when gallery opens
useEffect(() => {
const editorElement = document.querySelector('.article-editor');
if (isGalleryOpen) {
// Add padding equal to drawer height (40vh + some extra space)
editorElement.style.paddingBottom = 'calc(40vh + 2rem)';
// Scroll down smoothly so editor is visible above drawer
setTimeout(() => {
window.scrollTo({
top: document.body.scrollHeight,
behavior: 'smooth'
});
}, 100); // Small delay to let padding apply first
} else {
// Remove padding when closed
editorElement.style.paddingBottom = '';
}
// Cleanup on unmount
return () => {
if (editorElement) {
editorElement.style.paddingBottom = '';
}
};
}, [isGalleryOpen]);
const fetchCategories = async () => { const fetchCategories = async () => {
try { try {
const response = await fetch('http://localhost:9000/api/categories', { const response = await fetch('http://localhost:9000/api/categories', {
@ -209,7 +237,7 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
setShowCreateCategory(false); setShowCreateCategory(false);
} else { } else {
const data = await response.json(); const data = await response.json();
alert(data.error || 'Failed to create cateogry'); alert(data.error || 'Failed to create category');
} }
} catch (err) { } catch (err) {
console.error('Failed to create new category:', err); console.error('Failed to create new category:', err);
@ -295,7 +323,7 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
{article.ka_number} {article.ka_number}
</h2> </h2>
{article.status === 'draft' && ( {article.status === 'draft' && (
<span className='draft-indicator'>DRAFT</span> <span className='badge draft-badge'>DRAFT</span>
)} )}
</div> </div>
@ -304,6 +332,7 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
<label>Title:</label> <label>Title:</label>
<input <input
type='text' type='text'
className='form-input'
value={title} value={title}
onChange={(e) => setTitle(e.target.value)} onChange={(e) => setTitle(e.target.value)}
/> />
@ -314,17 +343,19 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
<div className='category-selector'> <div className='category-selector'>
<button <button
type='button' type='button'
className='category-dropdown-btn' className='dropdown-button'
onClick={() => setShowCategoryDropdown(!showCategoryDropdown)} onClick={() => setShowCategoryDropdown(!showCategoryDropdown)}
> >
{selectedCategories.length > 0 <span>
? `${selectedCategories.length} selected` {selectedCategories.length > 0
: 'Select categories'} ? `${selectedCategories.length} selected`
: 'Select categories'}
</span>
<span className='dropdown-arrow'>{showCategoryDropdown ? '▲' : '▼'}</span> <span className='dropdown-arrow'>{showCategoryDropdown ? '▲' : '▼'}</span>
</button> </button>
{showCategoryDropdown && ( {showCategoryDropdown && (
<div className='category-dropdown'> <div className='dropdown-menu'>
{showCreateCategory ? ( {showCreateCategory ? (
<div className='create-new-section'> <div className='create-new-section'>
<input <input
@ -340,17 +371,17 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
}} }}
autoFocus autoFocus
/> />
<div className='create-new-buttons'> <div className='btn-group gap-sm'>
<button <button
type='button' type='button'
className='create-btn' className='btn btn-success btn-sm'
onClick={handleCreateNewCategory} onClick={handleCreateNewCategory}
> >
Create Create
</button> </button>
<button <button
type='button' type='button'
className='cancel-btn' className='btn btn-secondary btn-sm'
onClick={() => { onClick={() => {
setShowCreateCategory(false); setShowCreateCategory(false);
setNewCategoryInput(''); setNewCategoryInput('');
@ -363,7 +394,8 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
) : ( ) : (
<button <button
type='button' type='button'
className='create-new-button-dropdown' className='btn btn-success'
style={{ width: '100%', borderRadius: '0', borderBottom: '1px solid var(--color-border)' }}
onClick={() => setShowCreateCategory(true)} onClick={() => setShowCreateCategory(true)}
> >
+ Create New Category + Create New Category
@ -371,10 +403,10 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
)} )}
{availableCategories.length === 0 ? ( {availableCategories.length === 0 ? (
<div className='no-options'>No categories available</div> <div className='dropdown-empty'>No categories available</div>
) : ( ) : (
availableCategories.map(category => ( availableCategories.map(category => (
<label key={category} className='category-option'> <label key={category} className='dropdown-item'>
<input <input
type='checkbox' type='checkbox'
checked={selectedCategories.includes(category)} checked={selectedCategories.includes(category)}
@ -387,9 +419,9 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
</div> </div>
)} )}
<div className='selected-badges'> <div className='badge-group mt-md'>
{selectedCategories.map(category => ( {selectedCategories.map(category => (
<span key={category} className='badge category-badge'> <span key={category} className='badge category-badge badge-removable'>
{category} {category}
<button <button
type='button' type='button'
@ -408,7 +440,7 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
<div className='tag-input-wrapper'> <div className='tag-input-wrapper'>
<input <input
type='text' type='text'
className='tag-input' className='form-input'
placeholder='Type to add tags...' placeholder='Type to add tags...'
value={tagInput} value={tagInput}
onChange={(e) => { onChange={(e) => {
@ -420,11 +452,11 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
/> />
{showTagSuggestions && filteredTagSuggestions.length > 0 && ( {showTagSuggestions && filteredTagSuggestions.length > 0 && (
<div className='tag-suggestions'> <div className='dropdown-menu'>
{filteredTagSuggestions.slice(0, 5).map(tag => ( {filteredTagSuggestions.slice(0, 5).map(tag => (
<div <div
key={tag} key={tag}
className='tag-suggestion' className='dropdown-item'
onClick={() => addTag(tag)} onClick={() => addTag(tag)}
> >
{tag} {tag}
@ -434,9 +466,9 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
)} )}
</div> </div>
<div className='selected-badges'> <div className='badge-group mt-md'>
{selectedTags.map(tag => ( {selectedTags.map(tag => (
<span key={tag} className='badge tag-badge'> <span key={tag} className='badge tag-badge badge-removable'>
#{tag} #{tag}
<button <button
type='button' type='button'
@ -460,14 +492,14 @@ function ArticleEditor({ article, onSave, onCancel, token }) {
/> />
</div> </div>
<div className='editor-buttons'> <div className='btn-group'>
<button className='save-draft-btn' onClick={handleSaveDraft}> <button className='btn btn-secondary' onClick={handleSaveDraft}>
Save Draft Save Draft
</button> </button>
<button className='publish-btn' onClick={handlePublish}> <button className='btn btn-success' onClick={handlePublish}>
Publish Publish
</button> </button>
<button className='cancel-btn' onClick={onCancel}> <button className='btn btn-ghost' onClick={onCancel}>
Cancel Cancel
</button> </button>
</div> </div>

View File

@ -1,56 +1,37 @@
.article-item { .article-item {
border: 1px solid #ddd; border: 1px solid var(--color-border);
padding: 1rem; padding: var(--spacing-lg);
margin-bottom: 1rem; margin-bottom: var(--spacing-lg);
border-radius: 4px; border-radius: var(--radius-sm);
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all var(--transition-base);
} }
.article-item:hover { .article-item:hover {
background-color: #f5f5f5; background-color: var(--color-gray-100);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); box-shadow: var(--shadow-md);
} }
.article-item-header h3 { .article-item-header h3 {
margin: 0 0 0.5rem 0; margin: 0 0 var(--spacing-sm) 0;
} }
.article-date { .article-date {
color: #666; color: var(--color-text-secondary);
font-size: 0.9rem; font-size: var(--font-size-md);
margin: 0; margin: 0;
} }
.article-item-metadata { .article-item-metadata {
display: flex; display: flex;
gap: 0.5rem; gap: var(--spacing-sm);
margin-top: 0.75rem; margin-top: var(--spacing-md);
flex-wrap: wrap; flex-wrap: wrap;
} }
.metadata-badges { /* Responsive */
display: flex; @media (max-width: 768px) {
flex-wrap: wrap; .article-item {
gap: 0.5rem; padding: var(--spacing-md);
} }
/* Badges */
.badge {
display: inline-flex;
align-items: center;
padding: 0.3rem 0.6rem;
border-radius: 12px;
font-size: 0.8rem;
font-weight: 500;
}
.category-badge {
background-color: #3498db;
color: white;
}
.tag-badge {
background-color: #27ae60;
color: white;
} }

View File

@ -1,51 +1,27 @@
.drafts-list { .drafts-list {
padding: 2rem; padding: var(--spacing-2xl);
max-width: 1200px; max-width: var(--max-width-2xl);
margin: 0 auto; margin: 0 auto;
} }
.drafts-header {
display: flex;
align-items: center;
gap: 2rem;
margin-bottom: 2rem;
border-bottom: 2px solid #333;
padding-bottom: 1rem;
}
.drafts-header h1 {
margin: 0;
}
.no-drafts {
text-align: center;
padding: 4rem 2rem;
color: #666;
}
.no-drafts p {
margin: 0.5rem 0;
font-size: 1.1rem;
}
.drafts-grid { .drafts-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1.5rem; gap: var(--spacing-xl);
} }
.draft-card { .draft-card {
background: white; background: var(--color-background);
border: 2px solid #e0e0e0; border: 2px solid var(--color-border-light);
border-radius: 8px; border-radius: var(--radius-lg);
padding: 1.5rem; padding: var(--spacing-xl);
cursor: pointer; cursor: pointer;
transition: all 0.2s; transition: all var(--transition-base);
} }
.draft-card:hover { .draft-card:hover {
border-color: #3498db; border-color: var(--color-primary);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); box-shadow: var(--shadow-lg);
transform: translateY(-2px); transform: translateY(-2px);
} }
@ -53,43 +29,26 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 1rem; margin-bottom: var(--spacing-lg);
}
.draft-badge {
background-color: #f39c12;
color: white;
padding: 0.25rem 0.75rem;
border-radius: 4px;
font-size: 0.75rem;
font-weight: bold;
}
.ka-number {
background-color: #3498db;
color: white;
padding: 0.25rem 0.5rem;
border-radius: 4px;
font-size: 0.85rem;
} }
.draft-title { .draft-title {
margin: 0 0 1rem 0; margin: 0 0 var(--spacing-lg) 0;
font-size: 1.25rem; font-size: var(--font-size-xl);
color: #2c3e50; color: var(--color-text-primary);
} }
.draft-title:empty::before { .draft-title:empty::before {
content: '(Untitled)'; content: '(Untitled)';
color: #999; color: var(--color-text-muted);
font-style: italic; font-style: italic;
} }
.draft-preview { .draft-preview {
color: #666; color: var(--color-text-secondary);
font-size: 0.9rem; font-size: var(--font-size-md);
line-height: 1.4; line-height: var(--line-height-snug);
margin-bottom: 1rem; margin-bottom: var(--spacing-lg);
min-height: 3rem; min-height: 3rem;
} }
@ -97,33 +56,22 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
padding-top: 1rem; padding-top: var(--spacing-lg);
border-top: 1px solid #eee; border-top: 1px solid var(--color-border-light);
} }
.draft-date { .draft-date {
font-size: 0.85rem; font-size: var(--font-size-sm);
color: #999; color: var(--color-text-muted);
} }
.delete-draft-btn { /* Responsive */
background-color: #e74c3c; @media (max-width: 768px) {
color: white; .drafts-list {
border: none; padding: var(--spacing-lg);
padding: 0.5rem 1rem; }
border-radius: 4px;
cursor: pointer; .drafts-grid {
font-size: 0.85rem; grid-template-columns: 1fr;
} }
.delete-draft-btn:hover {
background-color: #c0392b;
}
.error-message {
background-color: #ffebee;
color: #c62828;
padding: 1rem;
border-radius: 4px;
margin-bottom: 1rem;
} }

View File

@ -57,32 +57,44 @@ function DraftsList({ token, onBack, onSelectDraft }) {
}; };
if (loading) { if (loading) {
return <div className='drafts-list'>Loading...</div>; return (
<div className='drafts-list'>
<div className='loading-container'>
<div className='loading-spinner'></div>
<div className='loading-text'>Loading drafts...</div>
</div>
</div>
);
} }
return ( return (
<div className='drafts-list'> <div className='drafts-list'>
<div className='drafts-header'> <div className='page-header'>
<h1>My Drafts</h1> <h1 className='page-title'>My Drafts</h1>
<button className='btn btn-ghost' onClick={onBack}>
Back to Articles
</button>
</div> </div>
{error && (<div className='error-message'>{error}</div>)} {error && (<div className='error-message'>{error}</div>)}
{drafts.length === 0 ? ( {drafts.length === 0 ? (
<div className='no-drafts'> <div className='empty-state'>
<p>You do not have any drafts yet.</p> <div className='empty-state-icon'>📝</div>
<h2 className='empty-state-title'>No Drafts Yet</h2>
<p className='empty-state-description'>You don't have any drafts. Click "Create New Article" to start writing.</p>
</div> </div>
) : ( ) : (
<div className='drafts-grid'> <div className='drafts-grid'>
{drafts.map(draft => ( {drafts.map(draft => (
<div <div
key={draft.id} key={draft.id}
className='draft-card' className='draft-card card-hover'
onClick={() => onSelectDraft(draft)} onClick={() => onSelectDraft(draft)}
> >
<div className='draft-header'> <div className='draft-header'>
<span className='draft-badge'>DRAFT</span> <span className='badge draft-badge'>DRAFT</span>
<span className='ka-number'>{draft.ka_number}</span> <span className='ka-number-badge'>{draft.ka_number}</span>
</div> </div>
<h3 className='draft-title'>{draft.title || '(Untitled)'}</h3> <h3 className='draft-title'>{draft.title || '(Untitled)'}</h3>
<div className='draft-preview'> <div className='draft-preview'>
@ -94,15 +106,14 @@ function DraftsList({ token, onBack, onSelectDraft }) {
<div className='draft-footer'> <div className='draft-footer'>
<span className='draft-date'>Created: {new Date(draft.created_at).toLocaleDateString()}</span> <span className='draft-date'>Created: {new Date(draft.created_at).toLocaleDateString()}</span>
<button <button
className='delete-draft-btn' className='btn btn-danger btn-sm'
onClick={(e) => handleDeleteDraft(draft.ka_number, e)} onClick={(e) => handleDeleteDraft(draft.ka_number, e)}
>Delete</button> >Delete</button>
</div> </div>
</div> </div>
))} ))}
</div> </div>
) )}
}
</div> </div>
); );
} }

View File

@ -1,12 +1,12 @@
.filter-bar { .filter-bar {
padding: 1rem 2rem; padding: var(--spacing-lg) var(--spacing-2xl);
background-color: #f9f9f9; background-color: var(--color-background);
border-bottom: 1px solid #ddd; border-bottom: 1px solid var(--color-border);
} }
.filter-controls { .filter-controls {
display: flex; display: flex;
gap: 1rem; gap: var(--spacing-lg);
align-items: flex-end; align-items: flex-end;
flex-wrap: wrap; flex-wrap: wrap;
} }
@ -14,142 +14,48 @@
.filter-group { .filter-group {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 0.5rem; gap: var(--spacing-sm);
} }
.filter-group label { .filter-group label {
font-weight: bold; font-weight: var(--font-weight-bold);
font-size: 0.9rem; font-size: var(--font-size-md);
color: #2c3e50; color: var(--color-text-primary);
} }
.filter-dropdown-container { .filter-dropdown-container {
position: relative; position: relative;
}
.filter-dropdown-btn {
min-width: 180px; min-width: 180px;
padding: 0.5rem 1rem;
background-color: white;
border: 1px solid #ddd;
border-radius: 4px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 0.9rem;
}
.filter-dropdown-btn:hover {
border-color: #3498db;
}
.dropdown-arrow {
color: #666;
font-size: 0.7rem;
margin-left: 0.5rem;
}
.filter-dropdown {
position: absolute;
top: 100%;
left: 0;
right: 0;
background: white;
border: 1px solid #ddd;
border-radius: 4px;
max-height: 250px;
overflow-y: auto;
z-index: 100;
margin-top: 4px;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.filter-option {
display: flex;
align-items: center;
padding: 0.75rem;
cursor: pointer;
transition: background-color 0.2s;
}
.filter-option:hover {
background-color: #f5f5f5;
}
.filter-option input[type="checkbox"] {
margin-right: 0.75rem;
cursor: pointer;
}
.no-options {
padding: 1rem;
text-align: center;
color: #999;
}
.clear-filters-btn {
padding: 0.5rem 1rem;
background-color: #e74c3c;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
align-self: flex-end;
}
.clear-filters-btn:hover {
background-color: #c0392b;
} }
.active-filters { .active-filters {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 0.5rem; gap: var(--spacing-sm);
align-items: center; align-items: center;
margin-top: 1rem; margin-top: var(--spacing-lg);
padding-top: 1rem; padding-top: var(--spacing-lg);
border-top: 1px solid #ddd; border-top: 1px solid var(--color-border);
} }
.active-filters-label { .active-filters-label {
font-weight: bold; font-weight: var(--font-weight-bold);
color: #2c3e50; color: var(--color-text-primary);
font-size: 0.9rem; font-size: var(--font-size-md);
} }
.filter-badge { /* Responsive */
display: inline-flex; @media (max-width: 768px) {
align-items: center; .filter-bar {
gap: 0.5rem; padding: var(--spacing-md) var(--spacing-lg);
padding: 0.4rem 0.75rem; }
border-radius: 16px;
font-size: 0.85rem; .filter-controls {
font-weight: 500; flex-direction: column;
} align-items: stretch;
}
.filter-badge.category-badge {
background-color: #3498db; .filter-dropdown-container {
color: white; min-width: auto;
} }
.filter-badge.tag-badge {
background-color: #27ae60;
color: white;
}
.filter-badge button {
background: none;
border: none;
color: white;
font-size: 1.2rem;
cursor: pointer;
padding: 0;
line-height: 1;
opacity: 0.8;
}
.filter-badge button:hover {
opacity: 1;
} }

View File

@ -1,7 +1,7 @@
import { useState, useEffect } from 'react'; import { useState, useEffect } from 'react';
import './FilterBar.css'; import './FilterBar.css';
function FilterBar ({ token, onFilterChange }) { function FilterBar({ token, onFilterChange }) {
const [availableCategories, setAvailableCategories] = useState([]); const [availableCategories, setAvailableCategories] = useState([]);
const [availableTags, setAvailableTags] = useState([]); const [availableTags, setAvailableTags] = useState([]);
const [selectedCategories, setSelectedCategories] = useState([]); const [selectedCategories, setSelectedCategories] = useState([]);
@ -34,7 +34,7 @@ function FilterBar ({ token, onFilterChange }) {
setAvailableCategories(data.map(category => category.name)); setAvailableCategories(data.map(category => category.name));
} }
} catch(err) { } catch(err) {
console.error('Error fetchign categories:', err); console.error('Error fetching categories:', err);
} }
}; };
@ -85,22 +85,24 @@ function FilterBar ({ token, onFilterChange }) {
<label>Filter by Category:</label> <label>Filter by Category:</label>
<div className="filter-dropdown-container"> <div className="filter-dropdown-container">
<button <button
className="filter-dropdown-btn" className="dropdown-button"
onClick={() => setShowCategoryDropdown(!showCategoryDropdown)} onClick={() => setShowCategoryDropdown(!showCategoryDropdown)}
> >
{selectedCategories.length > 0 <span>
? `${selectedCategories.length} selected` {selectedCategories.length > 0
: 'All Categories'} ? `${selectedCategories.length} selected`
: 'All Categories'}
</span>
<span className="dropdown-arrow">{showCategoryDropdown ? '▲' : '▼'}</span> <span className="dropdown-arrow">{showCategoryDropdown ? '▲' : '▼'}</span>
</button> </button>
{showCategoryDropdown && ( {showCategoryDropdown && (
<div className="filter-dropdown"> <div className="dropdown-menu">
{availableCategories.length === 0 ? ( {availableCategories.length === 0 ? (
<div className="no-options">No categories available</div> <div className="dropdown-empty">No categories available</div>
) : ( ) : (
availableCategories.map(category => ( availableCategories.map(category => (
<label key={category} className="filter-option"> <label key={category} className="dropdown-item">
<input <input
type="checkbox" type="checkbox"
checked={selectedCategories.includes(category)} checked={selectedCategories.includes(category)}
@ -119,22 +121,24 @@ function FilterBar ({ token, onFilterChange }) {
<label>Filter by Tag:</label> <label>Filter by Tag:</label>
<div className="filter-dropdown-container"> <div className="filter-dropdown-container">
<button <button
className="filter-dropdown-btn" className="dropdown-button"
onClick={() => setShowTagDropdown(!showTagDropdown)} onClick={() => setShowTagDropdown(!showTagDropdown)}
> >
{selectedTags.length > 0 <span>
? `${selectedTags.length} selected` {selectedTags.length > 0
: 'All Tags'} ? `${selectedTags.length} selected`
: 'All Tags'}
</span>
<span className="dropdown-arrow">{showTagDropdown ? '▲' : '▼'}</span> <span className="dropdown-arrow">{showTagDropdown ? '▲' : '▼'}</span>
</button> </button>
{showTagDropdown && ( {showTagDropdown && (
<div className="filter-dropdown"> <div className="dropdown-menu">
{availableTags.length === 0 ? ( {availableTags.length === 0 ? (
<div className="no-options">No tags available</div> <div className="dropdown-empty">No tags available</div>
) : ( ) : (
availableTags.map(tag => ( availableTags.map(tag => (
<label key={tag} className="filter-option"> <label key={tag} className="dropdown-item">
<input <input
type="checkbox" type="checkbox"
checked={selectedTags.includes(tag)} checked={selectedTags.includes(tag)}
@ -150,7 +154,7 @@ function FilterBar ({ token, onFilterChange }) {
</div> </div>
{hasActiveFilters && ( {hasActiveFilters && (
<button className="clear-filters-btn" onClick={clearFilters}> <button className="btn btn-danger btn-sm" onClick={clearFilters}>
Clear Filters Clear Filters
</button> </button>
)} )}
@ -160,22 +164,21 @@ function FilterBar ({ token, onFilterChange }) {
<div className="active-filters"> <div className="active-filters">
<span className="active-filters-label">Active Filters:</span> <span className="active-filters-label">Active Filters:</span>
{selectedCategories.map(category => ( {selectedCategories.map(category => (
<span key={category} className="filter-badge category-badge"> <span key={category} className="badge category-badge badge-removable">
{category} {category}
<button onClick={() => toggleCategory(category)}>×</button> <button className="badge-remove" onClick={() => toggleCategory(category)}>×</button>
</span> </span>
))} ))}
{selectedTags.map(tag => ( {selectedTags.map(tag => (
<span key={tag} className="filter-badge tag-badge"> <span key={tag} className="badge tag-badge badge-removable">
#{tag} #{tag}
<button onClick={() => toggleTag(tag)}>×</button> <button className="badge-remove" onClick={() => toggleTag(tag)}>×</button>
</span> </span>
))} ))}
</div> </div>
)} )}
</div> </div>
); );
} }
export default FilterBar; export default FilterBar;

View File

@ -3,45 +3,45 @@
bottom: 0; bottom: 0;
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
background-color: #3498db; background-color: var(--color-primary);
color: white; color: var(--color-text-inverse);
border: none; border: none;
border-radius: 8px 8px 0 0; border-radius: var(--radius-lg) var(--radius-lg) 0 0;
padding: 0.75rem 2rem; padding: var(--spacing-md) var(--spacing-2xl);
cursor: pointer; cursor: pointer;
z-index: 1001; z-index: var(--z-drawer-toggle);
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.5rem; gap: var(--spacing-sm);
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.2); box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.2);
transition: background-color 0.2s; transition: background-color var(--transition-base);
} }
.drawer-toggle:hover { .drawer-toggle:hover {
background-color: #2980b9; background-color: var(--color-primary-hover);
} }
.drawer-toggle.open { .drawer-toggle.open {
background-color: #e74c3c; background-color: var(--color-danger);
} }
.drawer-toggle.open:hover { .drawer-toggle.open:hover {
background-color: #c0392b; background-color: var(--color-danger-hover);
} }
.toggle-icon { .toggle-icon {
font-size: 1.2rem; font-size: var(--font-size-xl);
} }
.toggle-text { .toggle-text {
font-weight: bold; font-weight: var(--font-weight-bold);
} }
.media-count { .media-count {
background-color: rgba(255, 255, 255, 0.3); background-color: rgba(255, 255, 255, 0.3);
padding: 0.2rem 0.5rem; padding: var(--spacing-xs) var(--spacing-sm);
border-radius: 12px; border-radius: var(--radius-xl);
font-size: 0.85rem; font-size: var(--font-size-sm);
} }
.media-drawer { .media-drawer {
@ -50,12 +50,12 @@
left: 0; left: 0;
right: 0; right: 0;
height: 40vh; height: 40vh;
background-color: white; background-color: var(--color-background);
border-radius: 12px 12px 0 0; border-radius: var(--radius-xl) var(--radius-xl) 0 0;
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.3); box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.3);
z-index: 1000; z-index: var(--z-drawer);
transform: translateY(100%); transform: translateY(100%);
transition: transform 0.3s ease-in-out; transition: transform var(--transition-slow);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
@ -65,94 +65,64 @@
} }
.drawer-handle { .drawer-handle {
padding: 0.75rem; padding: var(--spacing-md);
cursor: pointer; cursor: pointer;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
background-color: #f5f5f5; background-color: var(--color-gray-200);
border-radius: 12px 12px 0 0; border-radius: var(--radius-xl) var(--radius-xl) 0 0;
} }
.drawer-handle:hover { .drawer-handle:hover {
background-color: #e0e0e0; background-color: var(--color-gray-300);
} }
.handle-bar { .handle-bar {
width: 50px; width: 50px;
height: 4px; height: 4px;
background-color: #bbb; background-color: var(--color-gray-400);
border-radius: 2px; border-radius: var(--radius-sm);
} }
.drawer-content { .drawer-content {
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
padding: 1rem 2rem; padding: var(--spacing-lg) var(--spacing-2xl);
} }
.gallery-header { .gallery-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 1rem; margin-bottom: var(--spacing-lg);
padding-bottom: 1rem; padding-bottom: var(--spacing-lg);
border-bottom: 2px solid #e0e0e0; border-bottom: 2px solid var(--color-border-light);
} }
.gallery-header h3 { .gallery-header h3 {
margin: 0; margin: 0;
color: #2c3e50; color: var(--color-text-primary);
}
.upload-button {
background-color: #27ae60;
color: white;
padding: 0.75rem 1.5rem;
border-radius: 6px;
cursor: pointer;
transition: background-color 0.2s;
font-weight: bold;
}
.upload-button:hover {
background-color: #229954;
}
.loading {
text-align: center;
padding: 2rem;
color: #666;
}
.no-media {
text-align: center;
padding: 2rem;
color: #666;
}
.no-media p {
margin: 0.5rem 0;
} }
.media-grid { .media-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 1rem; gap: var(--spacing-lg);
padding: 1rem 0; padding: var(--spacing-lg) 0;
} }
.media-item { .media-item {
border: 2px solid #e0e0e0; border: 2px solid var(--color-border-light);
border-radius: 8px; border-radius: var(--radius-md);
padding: 0.75rem; padding: var(--spacing-md);
background: white; background: var(--color-background);
transition: all 0.2s; transition: all var(--transition-base);
} }
.media-item:hover { .media-item:hover {
border-color: #3498db; border-color: var(--color-primary);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); box-shadow: var(--shadow-lg);
transform: translateY(-2px); transform: translateY(-2px);
} }
@ -160,22 +130,22 @@
width: 100%; width: 100%;
height: 120px; height: 120px;
object-fit: cover; object-fit: cover;
border-radius: 6px; border-radius: var(--radius-md);
margin-bottom: 0.75rem; margin-bottom: var(--spacing-md);
cursor: pointer; cursor: pointer;
} }
.video-thumbnail { .video-thumbnail {
width: 100%; width: 100%;
height: 120px; height: 120px;
background-color: #34495e; background-color: var(--color-dark-hover);
border-radius: 6px; border-radius: var(--radius-md);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
margin-bottom: 0.75rem; margin-bottom: var(--spacing-md);
color: white; color: var(--color-text-inverse);
cursor: pointer; cursor: pointer;
} }
@ -184,59 +154,33 @@
} }
.video-label { .video-label {
margin-top: 0.5rem; margin-top: var(--spacing-sm);
font-size: 0.85rem; font-size: var(--font-size-sm);
font-weight: bold; font-weight: var(--font-weight-bold);
}
.media-actions {
display: flex;
gap: 0.5rem;
margin-bottom: 0.5rem;
}
.media-actions button {
flex: 1;
padding: 0.5rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.85rem;
font-weight: bold;
transition: all 0.2s;
}
.insert-btn {
background-color: #3498db;
color: white;
}
.insert-btn:hover {
background-color: #2980b9;
}
.delete-btn {
background-color: #e74c3c;
color: white;
}
.delete-btn:hover {
background-color: #c0392b;
} }
.media-filename { .media-filename {
font-size: 0.75rem; font-size: var(--font-size-xs);
color: #666; color: var(--color-text-secondary);
text-align: center; text-align: center;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
margin-top: var(--spacing-sm);
} }
.error-message { /* Responsive */
background-color: #ffebee; @media (max-width: 768px) {
color: #c62828; .media-drawer {
padding: 0.75rem; height: 50vh;
border-radius: 4px; }
margin-bottom: 1rem;
.drawer-content {
padding: var(--spacing-md) var(--spacing-lg);
}
.media-grid {
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
gap: var(--spacing-md);
}
} }

View File

@ -60,7 +60,7 @@ function MediaGallery({ kaNumber, token, onInsertMedia, isOpen, onToggle }) {
setError(data.error || 'Upload failed'); setError(data.error || 'Upload failed');
} }
} catch(err) { } catch(err) {
console.error('Failed to uplaod file:', err); console.error('Failed to upload file:', err);
setError('Failed to upload file'); setError('Failed to upload file');
} finally { } finally {
setUploading(false); setUploading(false);
@ -106,7 +106,7 @@ function MediaGallery({ kaNumber, token, onInsertMedia, isOpen, onToggle }) {
<div className='drawer-content'> <div className='drawer-content'>
<div className='gallery-header'> <div className='gallery-header'>
<h3>Media Gallery - {kaNumber}</h3> <h3>Media Gallery - {kaNumber}</h3>
<label className='upload-button'> <label className='btn btn-success'>
<input <input
type='file' type='file'
onChange={handleFileUpload} onChange={handleFileUpload}
@ -121,11 +121,17 @@ function MediaGallery({ kaNumber, token, onInsertMedia, isOpen, onToggle }) {
{error && <div className='error-message'>{error}</div>} {error && <div className='error-message'>{error}</div>}
{loading ? ( {loading ? (
<div className="loading">Loading media...</div> <div className='loading-container'>
<div className='loading-spinner'></div>
<div className='loading-text'>Loading media...</div>
</div>
) : media.length === 0 ? ( ) : media.length === 0 ? (
<div className="no-media"> <div className='empty-state'>
<p>No media uploaded yet</p> <div className='empty-state-icon'>📷</div>
<p>Click "Upload Media" to add images or videos</p> <h3 className='empty-state-title'>No Media Yet</h3>
<p className='empty-state-description'>
Click "Upload Media" to add images or videos to this article.
</p>
</div> </div>
) : ( ) : (
<div className='media-grid'> <div className='media-grid'>
@ -144,13 +150,13 @@ function MediaGallery({ kaNumber, token, onInsertMedia, isOpen, onToggle }) {
</div> </div>
)} )}
<div className='media-actions'> <div className='btn-group gap-sm'>
<button <button
className='insert-btn' className='btn btn-primary btn-sm'
onClick={() => onInsertMedia(item)} onClick={() => onInsertMedia(item)}
>Insert</button> >Insert</button>
<button <button
className='delete-btn' className='btn btn-danger btn-sm'
onClick={() => handleDelete(item.filename)} onClick={() => handleDelete(item.filename)}
>Delete</button> >Delete</button>
</div> </div>

View File

@ -0,0 +1,70 @@
/* SearchBar Component Styles */
.search-bar {
padding: var(--spacing-xl) var(--spacing-2xl);
background-color: var(--color-background);
border-bottom: 1px solid var(--color-border);
}
.search-bar form {
display: flex;
gap: var(--spacing-md);
max-width: 800px;
margin: 0 auto;
}
.search-bar input[type="text"] {
flex: 1;
padding: var(--spacing-md) var(--spacing-lg);
font-size: var(--font-size-base);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
background-color: var(--color-background);
transition: border-color var(--transition-base);
}
.search-bar input[type="text"]:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 3px var(--color-primary-light);
}
.search-bar input[type="text"]::placeholder {
color: var(--color-text-muted);
}
.search-bar button[type="submit"] {
padding: var(--spacing-md) var(--spacing-2xl);
background-color: var(--color-primary);
color: var(--color-text-inverse);
border: none;
border-radius: var(--radius-sm);
font-size: var(--font-size-base);
font-weight: var(--font-weight-medium);
cursor: pointer;
transition: background-color var(--transition-base);
white-space: nowrap;
}
.search-bar button[type="submit"]:hover {
background-color: var(--color-primary-hover);
}
.search-bar button[type="submit"]:active {
transform: translateY(1px);
}
/* Responsive */
@media (max-width: 640px) {
.search-bar {
padding: var(--spacing-lg);
}
.search-bar form {
flex-direction: column;
}
.search-bar button[type="submit"] {
width: 100%;
}
}

View File

@ -1,5 +1,6 @@
//import 'SearchBar.css' //import 'SearchBar.css'
import { useState } from 'react'; import { useState } from 'react';
import './SearchBar.css';
function SearchBar({ onSearch }) { function SearchBar({ onSearch }) {
const [query, setQuery] = useState(''); const [query, setQuery] = useState('');
@ -16,15 +17,10 @@ function SearchBar({ onSearch }) {
type='text' type='text'
placeholder='Search articles by title, content, or KA number...' placeholder='Search articles by title, content, or KA number...'
value={query} value={query}
onChange={(e) => setQuery(e.target.value)}
/* USer this for instant search
onChange={(e) => { onChange={(e) => {
setQuery(e.target.value); setQuery(e.target.value);
onSearch(e.target.value); onSearch(e.target.value);
}} }}
*/
/> />
<button type='submit'>Search</button> <button type='submit'>Search</button>
</form> </form>

View File

@ -1,68 +1,60 @@
/* Base Reset & Typography */
* {
box-sizing: border-box;
}
:root { :root {
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif; font-family: system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
line-height: 1.5; line-height: 1.5;
font-weight: 400; font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none; font-synthesis: none;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body { body {
margin: 0; margin: 0;
display: flex;
place-items: center;
min-width: 320px; min-width: 320px;
min-height: 100vh; min-height: 100vh;
background-color: var(--color-background);
color: var(--color-text-primary);
}
h1, h2, h3, h4, h5, h6 {
line-height: 1.2;
margin-top: 0;
} }
h1 { h1 {
font-size: 3.2em; font-size: 2rem;
line-height: 1.1; line-height: 1.1;
} }
button { a {
border-radius: 8px; color: var(--color-primary);
border: 1px solid transparent; text-decoration: none;
padding: 0.6em 1.2em; transition: color var(--transition-base);
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
} }
@media (prefers-color-scheme: light) { a:hover {
:root { color: var(--color-primary-hover);
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
} }
/* Remove default button styles - our .btn class will handle it */
button {
font-family: inherit;
}
/* App container */
#root {
min-height: 100vh;
}
/* Override Vite's centered layout */
.app {
min-height: 100vh;
display: flex;
flex-direction: column;
}

View File

@ -0,0 +1,599 @@
/* Cram-A-Lot Knowledge Base - Shared Component Styles */
/* Import this in your main.jsx or index.jsx after variables.css */
/* ===== BUTTONS ===== */
/* Base button styles */
.btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: var(--spacing-sm);
padding: var(--spacing-md) var(--spacing-xl);
font-size: var(--font-size-base);
font-weight: var(--font-weight-medium);
line-height: 1;
border: none;
border-radius: var(--radius-sm);
cursor: pointer;
transition: all var(--transition-base);
text-decoration: none;
white-space: nowrap;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
/* Button sizes */
.btn-sm {
padding: var(--spacing-sm) var(--spacing-lg);
font-size: var(--font-size-sm);
}
.btn-lg {
padding: var(--spacing-lg) var(--spacing-2xl);
font-size: var(--font-size-lg);
}
/* Button variants */
.btn-primary {
background-color: var(--color-primary);
color: var(--color-text-inverse);
}
.btn-primary:hover:not(:disabled) {
background-color: var(--color-primary-hover);
}
.btn-success {
background-color: var(--color-success);
color: var(--color-text-inverse);
}
.btn-success:hover:not(:disabled) {
background-color: var(--color-success-hover);
}
.btn-danger {
background-color: var(--color-danger);
color: var(--color-text-inverse);
}
.btn-danger:hover:not(:disabled) {
background-color: var(--color-danger-hover);
}
.btn-secondary {
background-color: var(--color-dark-light);
color: var(--color-text-inverse);
}
.btn-secondary:hover:not(:disabled) {
background-color: var(--color-dark-hover);
}
.btn-ghost {
background-color: transparent;
color: var(--color-text-primary);
border: 1px solid var(--color-border);
}
.btn-ghost:hover:not(:disabled) {
background-color: var(--color-gray-200);
border-color: var(--color-gray-400);
}
.btn-link {
background: none;
border: none;
color: var(--color-primary);
text-decoration: underline;
padding: 0;
}
.btn-link:hover:not(:disabled) {
color: var(--color-primary-hover);
}
/* Button groups */
.btn-group {
display: flex;
gap: var(--spacing-lg);
align-items: center;
}
/* ===== BADGES ===== */
.badge {
display: inline-flex;
align-items: center;
gap: var(--spacing-sm);
padding: var(--spacing-sm) var(--spacing-md);
border-radius: var(--radius-2xl);
font-size: var(--font-size-sm);
font-weight: var(--font-weight-medium);
line-height: 1;
white-space: nowrap;
}
.badge-sm {
padding: var(--spacing-xs) var(--spacing-sm);
font-size: var(--font-size-xs);
}
.badge-lg {
padding: var(--spacing-md) var(--spacing-lg);
font-size: var(--font-size-base);
}
/* Badge variants */
.badge-primary {
background-color: var(--color-primary);
color: var(--color-text-inverse);
}
.badge-success {
background-color: var(--color-success);
color: var(--color-text-inverse);
}
.badge-danger {
background-color: var(--color-danger);
color: var(--color-text-inverse);
}
.badge-warning {
background-color: var(--color-warning);
color: var(--color-text-inverse);
}
.badge-secondary {
background-color: var(--color-dark-light);
color: var(--color-text-inverse);
}
.badge-gray {
background-color: var(--color-gray-400);
color: var(--color-text-inverse);
}
/* Special badge types for app */
.category-badge {
background-color: var(--color-primary);
color: var(--color-text-inverse);
}
.tag-badge {
background-color: var(--color-success);
color: var(--color-text-inverse);
}
.draft-badge {
background-color: var(--color-warning);
color: var(--color-text-inverse);
}
.ka-number-badge {
background-color: var(--color-primary);
color: var(--color-text-inverse);
padding: var(--spacing-xs) var(--spacing-sm);
border-radius: var(--radius-sm);
font-size: var(--font-size-sm);
}
/* Badge with remove button */
.badge-removable {
padding-right: var(--spacing-xs);
}
.badge-remove {
background: none;
border: none;
color: inherit;
font-size: 1.2rem;
cursor: pointer;
padding: 0 var(--spacing-xs);
line-height: 1;
opacity: 0.8;
transition: opacity var(--transition-fast);
}
.badge-remove:hover {
opacity: 1;
}
/* Badge container */
.badge-group {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-sm);
align-items: center;
}
/* ===== FORM ELEMENTS ===== */
.form-field {
margin-bottom: var(--spacing-xl);
}
.form-field label {
display: block;
font-weight: var(--font-weight-bold);
margin-bottom: var(--spacing-sm);
color: var(--color-text-primary);
font-size: var(--font-size-md);
}
.form-input,
.form-textarea,
.form-select {
width: 100%;
padding: var(--spacing-md);
font-size: var(--font-size-base);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
background-color: var(--color-background);
transition: border-color var(--transition-base);
box-sizing: border-box;
}
.form-input:focus,
.form-textarea:focus,
.form-select:focus {
outline: none;
border-color: var(--color-primary);
}
.form-input:disabled,
.form-textarea:disabled,
.form-select:disabled {
background-color: var(--color-gray-200);
cursor: not-allowed;
opacity: 0.6;
}
.form-textarea {
min-height: 120px;
resize: vertical;
}
/* Form helper text */
.form-helper {
display: block;
margin-top: var(--spacing-xs);
font-size: var(--font-size-sm);
color: var(--color-text-muted);
}
.form-error {
color: var(--color-danger);
}
/* ===== MESSAGES & ALERTS ===== */
.message {
padding: var(--spacing-lg);
border-radius: var(--radius-sm);
margin-bottom: var(--spacing-lg);
display: flex;
align-items: flex-start;
gap: var(--spacing-md);
}
.error-message {
background-color: var(--color-danger-light);
color: var(--color-danger-hover);
border: 1px solid var(--color-danger);
}
.success-message {
background-color: var(--color-success-light);
color: var(--color-success-hover);
border: 1px solid var(--color-success);
}
.warning-message {
background-color: var(--color-warning-light);
color: var(--color-warning-hover);
border: 1px solid var(--color-warning);
}
.info-message {
background-color: var(--color-primary-light);
color: var(--color-primary-hover);
border: 1px solid var(--color-primary);
}
/* ===== LOADING STATES ===== */
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: var(--spacing-3xl) var(--spacing-2xl);
color: var(--color-text-muted);
}
.loading-spinner {
width: 40px;
height: 40px;
border: 4px solid var(--color-gray-300);
border-top-color: var(--color-primary);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
.loading-spinner-sm {
width: 24px;
height: 24px;
border-width: 3px;
}
.loading-spinner-lg {
width: 56px;
height: 56px;
border-width: 5px;
}
.loading-text {
margin-top: var(--spacing-lg);
font-size: var(--font-size-base);
color: var(--color-text-secondary);
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
/* ===== EMPTY STATES ===== */
.empty-state {
text-align: center;
padding: var(--spacing-3xl) var(--spacing-2xl);
color: var(--color-text-muted);
}
.empty-state-icon {
font-size: var(--font-size-3xl);
margin-bottom: var(--spacing-lg);
opacity: 0.5;
}
.empty-state-title {
font-size: var(--font-size-xl);
font-weight: var(--font-weight-semibold);
color: var(--color-text-secondary);
margin-bottom: var(--spacing-sm);
}
.empty-state-description {
font-size: var(--font-size-base);
color: var(--color-text-muted);
margin-bottom: var(--spacing-xl);
}
/* ===== DROPDOWNS ===== */
.dropdown-container {
position: relative;
}
.dropdown-button {
width: 100%;
padding: var(--spacing-md);
background-color: var(--color-background);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
text-align: left;
font-size: var(--font-size-base);
transition: border-color var(--transition-base);
}
.dropdown-button:hover {
border-color: var(--color-primary);
}
.dropdown-arrow {
color: var(--color-text-muted);
font-size: var(--font-size-sm);
transition: transform var(--transition-base);
}
.dropdown-arrow.open {
transform: rotate(180deg);
}
.dropdown-menu {
position: absolute;
top: calc(100% + var(--spacing-xs));
left: 0;
right: 0;
background: var(--color-background);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
max-height: 250px;
overflow-y: auto;
z-index: var(--z-dropdown);
box-shadow: var(--shadow-lg);
}
.dropdown-item {
padding: var(--spacing-md);
cursor: pointer;
transition: background-color var(--transition-base);
display: flex;
align-items: center;
gap: var(--spacing-md);
}
.dropdown-item:hover {
background-color: var(--color-gray-100);
}
.dropdown-item input[type="checkbox"] {
cursor: pointer;
}
.dropdown-divider {
height: 1px;
background-color: var(--color-border);
margin: var(--spacing-xs) 0;
}
.dropdown-header {
padding: var(--spacing-md);
font-weight: var(--font-weight-bold);
color: var(--color-text-secondary);
font-size: var(--font-size-sm);
background-color: var(--color-gray-100);
}
.dropdown-empty {
padding: var(--spacing-lg);
text-align: center;
color: var(--color-text-muted);
font-size: var(--font-size-sm);
}
/* ===== CARDS ===== */
.card {
background: var(--color-background);
border: 1px solid var(--color-border-light);
border-radius: var(--radius-lg);
padding: var(--spacing-xl);
transition: all var(--transition-base);
}
.card-hover:hover {
border-color: var(--color-primary);
box-shadow: var(--shadow-lg);
transform: translateY(-2px);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: var(--spacing-lg);
}
.card-title {
font-size: var(--font-size-xl);
font-weight: var(--font-weight-bold);
color: var(--color-text-primary);
margin: 0;
}
.card-body {
color: var(--color-text-secondary);
line-height: var(--line-height-relaxed);
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: var(--spacing-lg);
padding-top: var(--spacing-lg);
border-top: 1px solid var(--color-border-light);
}
/* ===== HEADERS & SECTIONS ===== */
.page-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: var(--spacing-2xl);
padding-bottom: var(--spacing-lg);
border-bottom: 2px solid var(--color-border-dark);
}
.page-title {
font-size: var(--font-size-2xl);
font-weight: var(--font-weight-bold);
color: var(--color-text-primary);
margin: 0;
}
.section {
margin-bottom: var(--spacing-3xl);
}
.section-title {
font-size: var(--font-size-xl);
font-weight: var(--font-weight-bold);
color: var(--color-text-primary);
margin-bottom: var(--spacing-lg);
}
/* ===== UTILITIES ===== */
.text-center {
text-align: center;
}
.text-muted {
color: var(--color-text-muted);
}
.text-secondary {
color: var(--color-text-secondary);
}
.text-sm {
font-size: var(--font-size-sm);
}
.text-lg {
font-size: var(--font-size-lg);
}
.font-bold {
font-weight: var(--font-weight-bold);
}
.mt-0 { margin-top: 0; }
.mt-sm { margin-top: var(--spacing-sm); }
.mt-md { margin-top: var(--spacing-md); }
.mt-lg { margin-top: var(--spacing-lg); }
.mt-xl { margin-top: var(--spacing-xl); }
.mt-2xl { margin-top: var(--spacing-2xl); }
.mb-0 { margin-bottom: 0; }
.mb-sm { margin-bottom: var(--spacing-sm); }
.mb-md { margin-bottom: var(--spacing-md); }
.mb-lg { margin-bottom: var(--spacing-lg); }
.mb-xl { margin-bottom: var(--spacing-xl); }
.mb-2xl { margin-bottom: var(--spacing-2xl); }
.flex {
display: flex;
}
.flex-col {
flex-direction: column;
}
.items-center {
align-items: center;
}
.justify-between {
justify-content: space-between;
}
.gap-sm { gap: var(--spacing-sm); }
.gap-md { gap: var(--spacing-md); }
.gap-lg { gap: var(--spacing-lg); }
.gap-xl { gap: var(--spacing-xl); }

View File

@ -0,0 +1,133 @@
/* Cram-A-Lot Knowledge Base - Design System Variables */
:root {
/* ===== COLORS ===== */
/* Primary Blue */
--color-primary: #3498db;
--color-primary-hover: #2980b9;
--color-primary-light: #ebf5fb;
/* Success Green */
--color-success: #27ae60;
--color-success-hover: #229954;
--color-success-light: #e8f6f0;
/* Danger Red */
--color-danger: #e74c3c;
--color-danger-hover: #c0392b;
--color-danger-light: #ffebee;
/* Warning Orange */
--color-warning: #f39c12;
--color-warning-hover: #e67e22;
--color-warning-light: #fef5e7;
/* Neutral Grays */
--color-gray-50: #fafafa;
--color-gray-100: #f9f9f9;
--color-gray-200: #f5f5f5;
--color-gray-300: #e0e0e0;
--color-gray-400: #bbb;
--color-gray-500: #999;
--color-gray-600: #666;
--color-gray-700: #333;
--color-gray-800: #242424;
/* Dark Colors */
--color-dark: #2c3e50;
--color-dark-hover: #34495e;
--color-dark-light: #7f8c8d;
/* Semantic Colors */
--color-text-primary: #2c3e50;
--color-text-secondary: #666;
--color-text-muted: #999;
--color-text-inverse: #ffffff;
--color-border: #ddd;
--color-border-light: #e0e0e0;
--color-border-dark: #333;
--color-background: #ffffff;
--color-background-alt: #f9f9f9;
--color-background-dark: #242424;
/* ===== SPACING SCALE ===== */
--spacing-xs: 0.25rem; /* 4px */
--spacing-sm: 0.5rem; /* 8px */
--spacing-md: 0.75rem; /* 12px */
--spacing-lg: 1rem; /* 16px */
--spacing-xl: 1.5rem; /* 24px */
--spacing-2xl: 2rem; /* 32px */
--spacing-3xl: 3rem; /* 48px */
--spacing-4xl: 4rem; /* 64px */
/* ===== TYPOGRAPHY ===== */
--font-size-xs: 0.75rem; /* 12px */
--font-size-sm: 0.85rem; /* 13.6px */
--font-size-base: 1rem; /* 16px */
--font-size-md: 0.95rem; /* 15.2px */
--font-size-lg: 1.1rem; /* 17.6px */
--font-size-xl: 1.25rem; /* 20px */
--font-size-2xl: 1.5rem; /* 24px */
--font-size-3xl: 2rem; /* 32px */
--font-weight-normal: 400;
--font-weight-medium: 500;
--font-weight-semibold: 600;
--font-weight-bold: 700;
--line-height-tight: 1.2;
--line-height-snug: 1.4;
--line-height-normal: 1.5;
--line-height-relaxed: 1.6;
/* ===== BORDER RADIUS ===== */
--radius-sm: 4px;
--radius-md: 6px;
--radius-lg: 8px;
--radius-xl: 12px;
--radius-2xl: 16px;
--radius-full: 9999px;
/* ===== SHADOWS ===== */
--shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.1);
--shadow-md: 0 4px 8px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.15);
--shadow-xl: 0 8px 24px rgba(0, 0, 0, 0.2);
/* ===== TRANSITIONS ===== */
--transition-fast: 0.15s ease;
--transition-base: 0.2s ease;
--transition-slow: 0.3s ease;
--transition-slower: 0.4s ease;
/* ===== Z-INDEX SCALE ===== */
--z-base: 1;
--z-dropdown: 100;
--z-sticky: 500;
--z-fixed: 1000;
--z-drawer: 1000;
--z-drawer-toggle: 1001;
--z-modal-backdrop: 1040;
--z-modal: 1050;
--z-popover: 1060;
--z-tooltip: 1070;
/* ===== LAYOUT ===== */
--max-width-xs: 480px;
--max-width-sm: 640px;
--max-width-md: 768px;
--max-width-lg: 900px;
--max-width-xl: 1024px;
--max-width-2xl: 1280px;
--max-width-3xl: 1400px;
/* ===== BREAKPOINTS (for reference, use in media queries) ===== */
--breakpoint-sm: 640px;
--breakpoint-md: 768px;
--breakpoint-lg: 1024px;
--breakpoint-xl: 1280px;
}