Compare commits
No commits in common. "ed0a57f8026ecc904ad3ea59c9c257258dbea7ca" and "e7f8650ff4292f294b028cb2ea704324d2aef9f5" have entirely different histories.
ed0a57f802
...
e7f8650ff4
@ -12,5 +12,5 @@
|
|||||||
"hasMasterSlide": true
|
"hasMasterSlide": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"generated": "2025-08-21T21:35:36.361Z"
|
"generated": "2025-08-21T18:19:19.993Z"
|
||||||
}
|
}
|
@ -1,360 +0,0 @@
|
|||||||
# Theme Creation Guidelines
|
|
||||||
|
|
||||||
## Theme Structure
|
|
||||||
|
|
||||||
Each theme must follow this directory structure:
|
|
||||||
```
|
|
||||||
themes/
|
|
||||||
theme-name/
|
|
||||||
style.css # Theme styles and metadata
|
|
||||||
master-slide.html # Master slide template (optional)
|
|
||||||
layouts/ # Layout templates directory
|
|
||||||
layout1.html
|
|
||||||
layout2.html
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
## CSS Theme Metadata
|
|
||||||
|
|
||||||
Theme metadata MUST be included as comments at the top of `style.css`:
|
|
||||||
|
|
||||||
```css
|
|
||||||
/*
|
|
||||||
* Theme: [theme-id]
|
|
||||||
* Name: [Display Name]
|
|
||||||
* Description: [Theme description]
|
|
||||||
* Author: [Author Name] ([email])
|
|
||||||
* Version: [version]
|
|
||||||
*/
|
|
||||||
```
|
|
||||||
|
|
||||||
## CSS Variables System
|
|
||||||
|
|
||||||
### Required Theme Variables
|
|
||||||
Define these CSS custom properties in `:root` for consistent theming:
|
|
||||||
|
|
||||||
```css
|
|
||||||
:root {
|
|
||||||
/* Colors */
|
|
||||||
--theme-primary: #color; /* Primary brand color */
|
|
||||||
--theme-secondary: #color; /* Secondary accent color */
|
|
||||||
--theme-accent: #color; /* Interactive accent color */
|
|
||||||
--theme-background: #color; /* Slide background */
|
|
||||||
--theme-text: #color; /* Primary text color */
|
|
||||||
--theme-text-secondary: #color; /* Secondary text color */
|
|
||||||
|
|
||||||
/* Typography */
|
|
||||||
--theme-font-heading: 'Font', fallback;
|
|
||||||
--theme-font-body: 'Font', fallback;
|
|
||||||
--theme-font-code: 'Font', fallback;
|
|
||||||
|
|
||||||
/* Layout */
|
|
||||||
--slide-padding: 5%; /* Slide edge padding */
|
|
||||||
--content-max-width: 90%; /* Max content width */
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Required Base Slide Styling
|
|
||||||
Always include this base styling that works with the global `.slide-container` classes:
|
|
||||||
|
|
||||||
```css
|
|
||||||
.slide-container .slide-content,
|
|
||||||
.slide {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: var(--theme-background);
|
|
||||||
color: var(--theme-text);
|
|
||||||
font-family: var(--theme-font-body);
|
|
||||||
padding: var(--slide-padding);
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Layout HTML Templates
|
|
||||||
|
|
||||||
### Required Layout Structure
|
|
||||||
Each layout HTML file must:
|
|
||||||
|
|
||||||
1. **Use semantic class naming**: `.layout-[layout-name]`
|
|
||||||
2. **Include slot elements** for editable content
|
|
||||||
3. **Use Handlebars syntax** for template variables
|
|
||||||
|
|
||||||
### Slot System
|
|
||||||
Slots are editable areas defined with specific data attributes:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div class="slot [slot-type]"
|
|
||||||
data-slot="[slot-id]"
|
|
||||||
data-placeholder="[placeholder-text]"
|
|
||||||
data-required
|
|
||||||
data-multiline="true"
|
|
||||||
data-accept="image/*"
|
|
||||||
data-hidden="true">
|
|
||||||
{{slot-id}}
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Slot Data Attributes:
|
|
||||||
- `data-slot="[id]"`: Unique identifier for the slot
|
|
||||||
- `data-placeholder="[text]"`: Placeholder text when empty
|
|
||||||
- `data-required`: Mark slot as required (optional)
|
|
||||||
- `data-multiline="true"`: Allow multiline text input (optional)
|
|
||||||
- `data-accept="image/*"`: For image slots (optional)
|
|
||||||
- `data-hidden="true"`: Hide from direct editing (optional)
|
|
||||||
|
|
||||||
#### Common Slot Types:
|
|
||||||
- **Title slots**: `data-slot="title"`
|
|
||||||
- **Text content**: `data-slot="content"`
|
|
||||||
- **Images**: `data-slot="image"` with `data-accept="image/*"`
|
|
||||||
- **Subtitles**: `data-slot="subtitle"`
|
|
||||||
|
|
||||||
### Layout CSS Naming Convention
|
|
||||||
Style layouts using the pattern: `.layout-[layout-name]`
|
|
||||||
|
|
||||||
```css
|
|
||||||
.layout-my-layout,
|
|
||||||
.slide-container .layout-my-layout {
|
|
||||||
/* Layout-specific styles */
|
|
||||||
}
|
|
||||||
|
|
||||||
.layout-my-layout .slot[data-slot="title"] {
|
|
||||||
/* Slot-specific styles */
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Creating New Layouts
|
|
||||||
|
|
||||||
### 1. Create Layout HTML Template
|
|
||||||
Create `themes/[theme]/layouts/my-layout.html`:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div class="slide layout-my-layout">
|
|
||||||
<h1 class="slot title-slot"
|
|
||||||
data-slot="title"
|
|
||||||
data-placeholder="Slide Title"
|
|
||||||
data-required>
|
|
||||||
{{title}}
|
|
||||||
</h1>
|
|
||||||
|
|
||||||
<div class="slot content-area"
|
|
||||||
data-slot="content"
|
|
||||||
data-placeholder="Your content here..."
|
|
||||||
data-multiline="true">
|
|
||||||
{{content}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Add Layout Styles to theme CSS
|
|
||||||
Add to the theme's `style.css`:
|
|
||||||
|
|
||||||
```css
|
|
||||||
/* My Layout */
|
|
||||||
.layout-my-layout,
|
|
||||||
.slide-container .layout-my-layout {
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
.layout-my-layout .slot[data-slot="title"] {
|
|
||||||
font-size: clamp(1.5rem, 6vw, 2.5rem);
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.layout-my-layout .slot[data-slot="content"] {
|
|
||||||
flex: 1;
|
|
||||||
font-size: clamp(1rem, 2.5vw, 1.25rem);
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Standard Slot Styles
|
|
||||||
|
|
||||||
### Required Slot Base Styles
|
|
||||||
```css
|
|
||||||
.slot {
|
|
||||||
position: relative;
|
|
||||||
border: 2px dashed transparent;
|
|
||||||
min-height: 2rem;
|
|
||||||
transition: border-color 0.2s ease;
|
|
||||||
width: 100%;
|
|
||||||
max-width: var(--content-max-width);
|
|
||||||
margin: 0 auto;
|
|
||||||
text-align: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slot:hover,
|
|
||||||
.slot.editing {
|
|
||||||
border-color: var(--theme-accent);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slot.empty {
|
|
||||||
border-color: var(--theme-secondary);
|
|
||||||
opacity: 0.5;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slot.empty::before {
|
|
||||||
content: attr(data-placeholder);
|
|
||||||
color: var(--theme-text-secondary);
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Slot Type Styles
|
|
||||||
```css
|
|
||||||
/* Text slots */
|
|
||||||
.slot[data-type="title"] {
|
|
||||||
font-family: var(--theme-font-heading);
|
|
||||||
font-weight: bold;
|
|
||||||
line-height: 1.2;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slot[data-type="subtitle"] {
|
|
||||||
font-family: var(--theme-font-heading);
|
|
||||||
line-height: 1.4;
|
|
||||||
opacity: 0.8;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slot[data-type="text"] {
|
|
||||||
line-height: 1.6;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Image slots */
|
|
||||||
.slot[data-type="image"] {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: #f8fafc;
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slot[data-type="image"] img {
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 100%;
|
|
||||||
object-fit: contain;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Master Slide Templates
|
|
||||||
|
|
||||||
Master slides provide unchangeable content that appears on all slides. Create `master-slide.html`:
|
|
||||||
|
|
||||||
```html
|
|
||||||
<div class="master-slide footer">
|
|
||||||
{{footerText}}
|
|
||||||
</div>
|
|
||||||
```
|
|
||||||
|
|
||||||
Style master slide elements:
|
|
||||||
```css
|
|
||||||
.master-slide {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.master-slide.footer {
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
padding: 1rem;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Responsive Design
|
|
||||||
|
|
||||||
### Aspect Ratio Support
|
|
||||||
Include aspect ratio specific adjustments:
|
|
||||||
|
|
||||||
```css
|
|
||||||
.slide-container.aspect-16-9 .slide-content,
|
|
||||||
.slide-container.aspect-16-9 .slide {
|
|
||||||
--slide-padding: 4%;
|
|
||||||
--content-max-width: 85%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slide-container.aspect-4-3 .slide-content,
|
|
||||||
.slide-container.aspect-4-3 .slide {
|
|
||||||
--slide-padding: 6%;
|
|
||||||
--content-max-width: 80%;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mobile Responsive
|
|
||||||
Add mobile breakpoints:
|
|
||||||
|
|
||||||
```css
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
:root {
|
|
||||||
--slide-padding: 3%;
|
|
||||||
--content-max-width: 95%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.layout-my-layout .slot[data-slot="title"] {
|
|
||||||
font-size: clamp(1.2rem, 5vw, 2rem);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Print Support
|
|
||||||
Include print styles for presentation export:
|
|
||||||
|
|
||||||
```css
|
|
||||||
@media print {
|
|
||||||
.slide {
|
|
||||||
page-break-after: always;
|
|
||||||
width: 100%;
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slot {
|
|
||||||
border: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slot.empty::before {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
1. **Use clamp() for responsive typography**: `font-size: clamp(min, preferred, max)`
|
|
||||||
2. **Leverage CSS custom properties**: Makes themes easily customizable
|
|
||||||
3. **Follow existing naming conventions**: `.layout-[name]`, `.slot[data-slot="name"]`
|
|
||||||
4. **Test across aspect ratios**: Ensure layouts work in 16:9, 4:3, and 16:10
|
|
||||||
5. **Consider accessibility**: Use sufficient color contrast and readable fonts
|
|
||||||
6. **Use semantic HTML**: Proper heading hierarchy and meaningful class names
|
|
||||||
7. **Keep layouts flexible**: Use flexbox/grid for responsive behavior
|
|
||||||
|
|
||||||
## Template Variables (Handlebars)
|
|
||||||
|
|
||||||
Use Handlebars syntax for dynamic content:
|
|
||||||
- `{{variableName}}` - Simple variable
|
|
||||||
- `{{#if condition}}...{{/if}}` - Conditional rendering
|
|
||||||
- `{{#image}}...{{/image}}` - Check if image exists
|
|
||||||
|
|
||||||
Common variables:
|
|
||||||
- `{{title}}` - Slide title
|
|
||||||
- `{{content}}` - Main content
|
|
||||||
- `{{image}}` - Image source
|
|
||||||
- `{{imageAlt}}` - Image alt text
|
|
||||||
- `{{footerText}}` - Footer text
|
|
@ -5,4 +5,5 @@
|
|||||||
<div class="slot diagram-slot" data-slot="diagram" data-placeholder="Diagram or Image" data-required>
|
<div class="slot diagram-slot" data-slot="diagram" data-placeholder="Diagram or Image" data-required>
|
||||||
{{diagram}}
|
{{diagram}}
|
||||||
</div>
|
</div>
|
||||||
|
<h3>Static Content</h3>
|
||||||
</div>
|
</div>
|
164
src/App.css
164
src/App.css
@ -1,7 +1,165 @@
|
|||||||
/* Import aspect ratio system for theme engine */
|
/* Import aspect ratio system for theme engine */
|
||||||
@import './styles/aspectRatios.css';
|
@import './styles/aspectRatios.css';
|
||||||
|
|
||||||
/* Import application-wide styles */
|
/* App Layout */
|
||||||
@import './styles/application.css';
|
.app-root {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
text-align: left;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
/* App.tsx specific styles can go here if needed */
|
.app-header {
|
||||||
|
border-bottom: 1px solid var(--border-primary);
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-nav {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
border-bottom: 1px solid var(--border-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-logo {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-logo:hover {
|
||||||
|
color: var(--text-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-button {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
text-decoration: none;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-button.primary {
|
||||||
|
background: var(--btn-primary-bg);
|
||||||
|
color: var(--btn-primary-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-button.primary:hover {
|
||||||
|
background: var(--btn-primary-bg-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
transition: color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link:hover {
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title-section {
|
||||||
|
padding: 2rem;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-header h1 {
|
||||||
|
margin: 0;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-header p {
|
||||||
|
margin: 0.5rem 0 0 0;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-main {
|
||||||
|
padding: 2rem;
|
||||||
|
max-width: 1400px;
|
||||||
|
margin: 0 auto;
|
||||||
|
width: 100%;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-theme-section {
|
||||||
|
margin-top: 2rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border: 1px solid var(--border-secondary);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
background: var(--bg-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-theme-title {
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-theme-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-theme-item {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-theme-item strong {
|
||||||
|
color: var(--text-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Design */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.app-main {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-theme-grid {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-nav {
|
||||||
|
padding: 1rem;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-actions {
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-title-section {
|
||||||
|
padding: 1.5rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-header h1 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -4,6 +4,7 @@ import { ThemeDetailPage } from './components/themes/ThemeDetailPage.tsx';
|
|||||||
import { LayoutDetailPage } from './components/themes/LayoutDetailPage.tsx';
|
import { LayoutDetailPage } from './components/themes/LayoutDetailPage.tsx';
|
||||||
import { LayoutPreviewPage } from './components/themes/LayoutPreviewPage.tsx';
|
import { LayoutPreviewPage } from './components/themes/LayoutPreviewPage.tsx';
|
||||||
import { NewPresentationPage } from './components/presentations/NewPresentationPage.tsx';
|
import { NewPresentationPage } from './components/presentations/NewPresentationPage.tsx';
|
||||||
|
import { PresentationViewer } from './components/presentations/PresentationViewer.tsx';
|
||||||
import { PresentationMode } from './components/presentations/PresentationMode.tsx';
|
import { PresentationMode } from './components/presentations/PresentationMode.tsx';
|
||||||
import { PresentationEditor } from './components/presentations/PresentationEditor.tsx';
|
import { PresentationEditor } from './components/presentations/PresentationEditor.tsx';
|
||||||
import { SlideEditor } from './components/slide-editor/SlideEditor.tsx';
|
import { SlideEditor } from './components/slide-editor/SlideEditor.tsx';
|
||||||
@ -24,6 +25,7 @@ function App() {
|
|||||||
<Route path="/presentations" element={<PresentationsList />} />
|
<Route path="/presentations" element={<PresentationsList />} />
|
||||||
<Route path="/presentations/new" element={<NewPresentationPage />} />
|
<Route path="/presentations/new" element={<NewPresentationPage />} />
|
||||||
<Route path="/presentations/:presentationId/edit/slides/:slideNumber" element={<PresentationEditor />} />
|
<Route path="/presentations/:presentationId/edit/slides/:slideNumber" element={<PresentationEditor />} />
|
||||||
|
<Route path="/presentations/:presentationId/view/slides/:slideNumber" element={<PresentationViewer />} />
|
||||||
<Route path="/presentations/:presentationId/present/:slideNumber" element={<PresentationMode />} />
|
<Route path="/presentations/:presentationId/present/:slideNumber" element={<PresentationMode />} />
|
||||||
<Route path="/presentations/:presentationId/slide/:slideId/edit" element={<SlideEditor />} />
|
<Route path="/presentations/:presentationId/slide/:slideId/edit" element={<SlideEditor />} />
|
||||||
<Route path="/themes" element={<ThemeBrowser />} />
|
<Route path="/themes" element={<ThemeBrowser />} />
|
||||||
|
@ -1,18 +1,77 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link, useLocation, useParams } from 'react-router-dom';
|
||||||
|
|
||||||
export const AppHeader: React.FC = () => {
|
export const AppHeader: React.FC = () => {
|
||||||
|
const location = useLocation();
|
||||||
|
const params = useParams();
|
||||||
|
|
||||||
|
const getPageTitle = () => {
|
||||||
|
const { pathname } = location;
|
||||||
|
|
||||||
|
// Route patterns with titles (cleaner than manual parsing)
|
||||||
|
const routes = [
|
||||||
|
{ pattern: /^\/$/, title: 'Welcome to Slideshare' },
|
||||||
|
{ pattern: /^\/presentations$/, title: 'My Presentations' },
|
||||||
|
{ pattern: /^\/presentations\/new$/, title: 'Create New Presentation' },
|
||||||
|
{ pattern: /^\/presentations\/[^/]+\/edit\/slides\/(\d+)$/, title: () => `Editing Slide ${params.slideNumber}` },
|
||||||
|
{ pattern: /^\/presentations\/[^/]+\/view\/slides\/(\d+)$/, title: () => `Viewing Slide ${params.slideNumber}` },
|
||||||
|
{ pattern: /^\/presentations\/[^/]+\/present\/(\d+)$/, title: () => `Presenting Slide ${params.slideNumber}` },
|
||||||
|
{ pattern: /^\/presentations\/[^/]+\/slide\/new\/edit$/, title: 'Add New Slide' },
|
||||||
|
{ pattern: /^\/presentations\/[^/]+\/slide\/[^/]+\/edit$/, title: 'Edit Slide' },
|
||||||
|
{ pattern: /^\/themes$/, title: 'Theme Browser' },
|
||||||
|
{ pattern: /^\/themes\/([^/]+)$/, title: () => `Theme: ${params.themeId}` },
|
||||||
|
{ pattern: /^\/themes\/[^/]+\/layouts\/([^/]+)$/, title: () => `Layout: ${params.layoutId}` },
|
||||||
|
{ pattern: /^\/themes\/[^/]+\/layouts\/[^/]+\/preview$/, title: () => `Preview Layout` },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const route of routes) {
|
||||||
|
if (route.pattern.test(pathname)) {
|
||||||
|
return typeof route.title === 'function' ? route.title() : route.title;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Slideshare';
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPageDescription = () => {
|
||||||
|
const { pathname } = location;
|
||||||
|
|
||||||
|
// Route patterns with descriptions
|
||||||
|
const routes = [
|
||||||
|
{ pattern: /^\/$/, description: 'Create beautiful presentations with customizable themes' },
|
||||||
|
{ pattern: /^\/presentations$/, description: 'View and manage all your presentations' },
|
||||||
|
{ pattern: /^\/presentations\/new$/, description: 'Enter details for your new presentation' },
|
||||||
|
{ pattern: /^\/presentations\/[^/]+\/edit\/slides\/(\d+)$/, description: 'Edit slide content, add notes, and manage your presentation' },
|
||||||
|
{ pattern: /^\/presentations\/[^/]+\/view\/slides\/(\d+)$/, description: 'View your presentation slides in read-only mode' },
|
||||||
|
{ pattern: /^\/presentations\/[^/]+\/present\/(\d+)$/, description: 'Present your slides in full screen mode' },
|
||||||
|
{ pattern: /^\/presentations\/[^/]+\/slide\/new\/edit$/, description: 'Choose a layout and add content for your new slide' },
|
||||||
|
{ pattern: /^\/presentations\/[^/]+\/slide\/[^/]+\/edit$/, description: 'Edit slide content and layout' },
|
||||||
|
{ pattern: /^\/themes$/, description: 'Browse and select themes for your presentations' },
|
||||||
|
{ pattern: /^\/themes\/([^/]+)$/, description: 'View theme details, layouts, and color palette' },
|
||||||
|
{ pattern: /^\/themes\/[^/]+\/layouts\/([^/]+)$/, description: 'View layout template, slots, and configuration' },
|
||||||
|
{ pattern: /^\/themes\/[^/]+\/layouts\/[^/]+\/preview$/, description: 'Preview layout with sample content' },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const route of routes) {
|
||||||
|
if (route.pattern.test(pathname)) {
|
||||||
|
return route.description;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Slide authoring and presentation tool';
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="app-header">
|
<header className="app-header">
|
||||||
<nav className="flex justify-between items-center px-8 py-4 border-b">
|
<nav className="app-nav">
|
||||||
<Link to="/" className="text-xl font-bold text-primary hover:text-accent transition">
|
<Link to="/" className="app-logo">
|
||||||
Slideshare
|
Slideshare
|
||||||
</Link>
|
</Link>
|
||||||
<div className="flex items-center gap-4">
|
<div className="nav-actions">
|
||||||
<Link to="/presentations" className="nav-link">
|
<Link to="/presentations" className="nav-link">
|
||||||
My Presentations
|
My Presentations
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/presentations/new" className="btn btn-primary btn-sm">
|
<Link to="/presentations/new" className="nav-button primary">
|
||||||
Create Presentation
|
Create Presentation
|
||||||
</Link>
|
</Link>
|
||||||
{/* Theme browsing - Coming soon */}
|
{/* Theme browsing - Coming soon */}
|
||||||
@ -23,6 +82,10 @@ export const AppHeader: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
|
<div className="page-title-section">
|
||||||
|
<h1 className="page-title">{getPageTitle()}</h1>
|
||||||
|
<p className="page-description">{getPageDescription()}</p>
|
||||||
|
</div>
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
};
|
};
|
@ -1,42 +1,88 @@
|
|||||||
/* Uses utility classes in HTML: */
|
.aspect-ratio-selection h2 {
|
||||||
/* aspect-ratio-selection h2: .text-xl .font-semibold .text-primary .mb-2 */
|
font-size: 1.25rem;
|
||||||
/* section-description: .text-secondary .mb-6 .text-sm */
|
font-weight: 600;
|
||||||
/* aspect-ratio-grid: .grid .grid-auto-fill-200 .gap-4 .mb-8 */
|
color: #1f2937;
|
||||||
/* aspect-ratio-card: .card-compact .cursor-pointer with 2px border override */
|
margin-bottom: 0.5rem;
|
||||||
/* aspect-ratio-preview: .flex .justify-center .items-center .mb-4 .rounded-lg */
|
}
|
||||||
|
|
||||||
|
.section-description {
|
||||||
|
color: #6b7280;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aspect-ratio-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* Component-specific overrides only */
|
|
||||||
.aspect-ratio-card {
|
.aspect-ratio-card {
|
||||||
border: 2px solid var(--border-primary);
|
border: 2px solid #e5e7eb;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
padding: 1rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
background: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aspect-ratio-card:hover {
|
.aspect-ratio-card:hover {
|
||||||
border-color: var(--border-accent);
|
border-color: #3b82f6;
|
||||||
box-shadow: 0 4px 12px var(--bg-accent);
|
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aspect-ratio-card.selected {
|
.aspect-ratio-card.selected {
|
||||||
border-color: var(--border-accent);
|
border-color: #3b82f6;
|
||||||
background: var(--bg-accent);
|
background: #eff6ff;
|
||||||
box-shadow: 0 4px 12px var(--bg-accent);
|
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aspect-ratio-preview {
|
.aspect-ratio-preview {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
height: 60px;
|
height: 60px;
|
||||||
background: var(--bg-muted);
|
margin-bottom: 1rem;
|
||||||
|
background: #f9fafb;
|
||||||
|
border-radius: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-box {
|
.preview-box {
|
||||||
background: var(--text-accent);
|
background: #3b82f6;
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Preview box dimensions - component specific */
|
.preview-box.aspect-16-9 {
|
||||||
.preview-box.aspect-16-9 { width: 48px; height: 27px; }
|
width: 48px;
|
||||||
.preview-box.aspect-4-3 { width: 40px; height: 30px; }
|
height: 27px;
|
||||||
.preview-box.aspect-1-1 { width: 32px; height: 32px; }
|
}
|
||||||
|
|
||||||
/* Uses utility classes in HTML: */
|
.preview-box.aspect-4-3 {
|
||||||
/* aspect-ratio-info h3: .text-base .font-semibold .text-primary .mb-1 */
|
width: 40px;
|
||||||
/* ratio-description: .text-secondary .text-xs .mb-2 */
|
height: 30px;
|
||||||
/* ratio-dimensions: .text-xs .text-tertiary .font-medium */
|
}
|
||||||
|
|
||||||
|
.preview-box.aspect-1-1 {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aspect-ratio-info h3 {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2937;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ratio-description {
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ratio-dimensions {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #9ca3af;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
@ -1,99 +1,96 @@
|
|||||||
/* Creation Actions Layout */
|
|
||||||
.creation-actions {
|
.creation-actions {
|
||||||
background: var(--bg-secondary);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
padding: 2rem;
|
|
||||||
border: 1px solid var(--border-primary);
|
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected-theme-info {
|
.selected-theme-info {
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 2rem;
|
||||||
}
|
|
||||||
|
|
||||||
/* Selected Theme Display */
|
|
||||||
.selected-theme-section {
|
|
||||||
margin-top: 2rem;
|
|
||||||
padding: 1.5rem;
|
|
||||||
border: 1px solid var(--border-secondary);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
background: var(--bg-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected-theme-title {
|
|
||||||
margin: 0 0 1rem 0;
|
|
||||||
color: var(--text-primary);
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected-theme-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 1rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected-theme-item {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selected-theme-item strong {
|
|
||||||
color: var(--text-primary);
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.selected-theme-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 0.75rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-preview-info h3 {
|
.theme-preview-info h3 {
|
||||||
font-size: 1rem;
|
font-size: 1.125rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--text-primary);
|
color: #1f2937;
|
||||||
margin: 0 0 0.5rem 0;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-preview-info p {
|
.theme-preview-info p {
|
||||||
color: var(--text-secondary);
|
color: #6b7280;
|
||||||
margin: 0 0 0.75rem 0;
|
margin-bottom: 1rem;
|
||||||
font-size: 0.875rem;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-stats {
|
.theme-stats {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
font-size: 0.75rem;
|
font-size: 0.875rem;
|
||||||
|
color: #9ca3af;
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-stats span {
|
.theme-stats span {
|
||||||
background: var(--bg-muted);
|
background: #f3f4f6;
|
||||||
padding: 0.25rem 0.75rem;
|
padding: 0.25rem 0.5rem;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
color: var(--text-tertiary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Action Buttons - Clear visual hierarchy */
|
.creation-error {
|
||||||
|
background: #fee2e2;
|
||||||
|
border: 1px solid #fca5a5;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 1rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.creation-error p {
|
||||||
|
color: #dc2626;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
.action-buttons {
|
.action-buttons {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
align-items: center;
|
gap: 0.75rem;
|
||||||
gap: 1rem;
|
padding-top: 2rem;
|
||||||
padding-top: 1.5rem;
|
border-top: 1px solid #e5e7eb;
|
||||||
border-top: 1px solid var(--border-primary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Make primary action stand out */
|
.button {
|
||||||
.action-buttons .button.primary {
|
padding: 0.75rem 1.5rem;
|
||||||
min-width: 180px;
|
border-radius: 0.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
min-width: 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Ghost button has no min-width for minimal appearance */
|
.button:disabled {
|
||||||
.action-buttons .button.ghost {
|
opacity: 0.6;
|
||||||
min-width: auto;
|
cursor: not-allowed;
|
||||||
padding: 0.5rem 0.75rem;
|
}
|
||||||
|
|
||||||
|
.button.secondary {
|
||||||
|
background: #f9fafb;
|
||||||
|
color: #374151;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.secondary:hover:not(:disabled) {
|
||||||
|
background: #f3f4f6;
|
||||||
|
border-color: #9ca3af;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.primary {
|
||||||
|
background: #3b82f6;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.primary:hover:not(:disabled) {
|
||||||
|
background: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.primary:focus {
|
||||||
|
outline: 2px solid #3b82f6;
|
||||||
|
outline-offset: 2px;
|
||||||
}
|
}
|
@ -43,7 +43,7 @@ export const CreationActions: React.FC<CreationActionsProps> = ({
|
|||||||
<div className="action-buttons">
|
<div className="action-buttons">
|
||||||
<button
|
<button
|
||||||
onClick={onCancel}
|
onClick={onCancel}
|
||||||
className="button ghost"
|
className="button secondary"
|
||||||
type="button"
|
type="button"
|
||||||
disabled={creating}
|
disabled={creating}
|
||||||
>
|
>
|
||||||
|
@ -1,27 +1,129 @@
|
|||||||
/* Uses utility classes in HTML: */
|
|
||||||
/* empty-presentation: .empty-content (from application.css) with min-h-32 override */
|
|
||||||
/* empty-content h2: .text-3xl .font-bold .text-primary .mb-4 */
|
|
||||||
/* empty-content > p: .text-lg .text-secondary .mb-8 */
|
|
||||||
/* theme-preview: .card .my-8 .text-left */
|
|
||||||
/* theme-preview h3: .text-xl .font-semibold .text-primary .mb-2 */
|
|
||||||
/* theme-description: .text-secondary .mb-6 .leading-normal */
|
|
||||||
/* available-layouts h4: .text-base .font-semibold .text-primary .mb-4 */
|
|
||||||
/* layouts-grid: .grid .gap-4 */
|
|
||||||
|
|
||||||
/* Component-specific overrides */
|
|
||||||
.empty-presentation {
|
.empty-presentation {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
min-height: 400px;
|
min-height: 400px;
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-content {
|
||||||
|
text-align: center;
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-content h2 {
|
||||||
|
font-size: 1.875rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1f2937;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-content > p {
|
||||||
|
font-size: 1.125rem;
|
||||||
|
color: #6b7280;
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-preview {
|
||||||
|
background: #f9fafb;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
margin: 2rem 0;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-preview h3 {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2937;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-description {
|
||||||
|
color: #6b7280;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.available-layouts h4 {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #374151;
|
||||||
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.layouts-grid {
|
.layouts-grid {
|
||||||
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Uses utility classes in HTML: */
|
.layout-preview-card {
|
||||||
/* layout-preview-card: .card-compact .text-center */
|
background: #ffffff;
|
||||||
/* layout-name: .font-semibold .text-primary .text-sm .mb-2 .block */
|
border: 1px solid #d1d5db;
|
||||||
/* layout-description: .text-secondary .text-xs .mb-2 .block */
|
border-radius: 0.5rem;
|
||||||
/* slot-count: .text-tertiary .text-xs .font-medium */
|
padding: 1rem;
|
||||||
/* more-layouts: .flex .items-center .justify-center .bg-muted .border .border-dashed .rounded-lg .text-secondary .text-sm .font-medium */
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
/* Uses .btn .btn-primary .btn-large classes from application.css */
|
.layout-preview-card .layout-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1f2937;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-preview-card .layout-description {
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layout-preview-card .slot-count {
|
||||||
|
color: #9ca3af;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.more-layouts {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: #f3f4f6;
|
||||||
|
border: 1px dashed #9ca3af;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button {
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.primary {
|
||||||
|
background: #3b82f6;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.primary:hover {
|
||||||
|
background: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.large {
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
@ -1,71 +1,382 @@
|
|||||||
/* Component uses utility classes: .page-container, .page-header, .page-content */
|
.new-presentation-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
background: white;
|
||||||
|
border-bottom: 1px solid #e2e8f0;
|
||||||
|
padding: 1.5rem 2rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* Back Button - Subtle navigation link */
|
|
||||||
.back-button {
|
.back-button {
|
||||||
background: transparent;
|
background: none;
|
||||||
border: none;
|
border: none;
|
||||||
color: var(--text-tertiary);
|
color: #64748b;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
margin-right: 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-button:hover {
|
.back-button:hover {
|
||||||
background: var(--bg-hover);
|
background: #f1f5f9;
|
||||||
color: var(--text-primary);
|
color: #334155;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Header Content */
|
.header-content h1 {
|
||||||
.header-content {
|
margin: 0;
|
||||||
flex: 1;
|
font-size: 1.875rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #1e293b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-content p {
|
||||||
|
margin: 0.25rem 0 0 0;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-content {
|
||||||
|
padding: 2rem;
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Creation Form - Better spacing and visual hierarchy */
|
|
||||||
.creation-form {
|
.creation-form {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 2.5rem;
|
gap: 3rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Uses .flex .gap-4 .text-xs utility classes for theme-stats */
|
/* Presentation Details Section */
|
||||||
/* Uses .badge-secondary class for theme-stats span */
|
.presentation-details {
|
||||||
/* Uses .alert-error class for creation-error */
|
background: white;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
padding: 2rem;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-details h2 {
|
||||||
|
margin: 0 0 1.5rem 0;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1e293b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #374151;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input,
|
||||||
|
.form-textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
background: white;
|
||||||
|
color: #374151;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input:focus,
|
||||||
|
.form-textarea:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #3b82f6;
|
||||||
|
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-textarea {
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 80px;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Theme Selection Section */
|
||||||
|
.theme-selection {
|
||||||
|
background: white;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
padding: 2rem;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-selection h2 {
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1e293b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-description {
|
||||||
|
margin: 0 0 2rem 0;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-themes {
|
||||||
|
text-align: center;
|
||||||
|
padding: 3rem;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Creation Actions Section */
|
||||||
|
.creation-actions {
|
||||||
|
background: white;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
padding: 2rem;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selected-theme-info {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-preview-info h3 {
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1e293b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-preview-info p {
|
||||||
|
margin: 0 0 0.75rem 0;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-stats {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-stats span {
|
||||||
|
background: #f1f5f9;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.creation-error {
|
||||||
|
margin-top: 1rem;
|
||||||
|
padding: 0.75rem;
|
||||||
|
background: #fef2f2;
|
||||||
|
border: 1px solid #fecaca;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
color: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.creation-error p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
/* Aspect Ratio Selection */
|
/* Aspect Ratio Selection */
|
||||||
/* aspect-ratio-selection: .mb-8 */
|
.aspect-ratio-selection {
|
||||||
/* aspect-ratio-grid: .grid-auto-fill-250 .gap-4 */
|
margin-bottom: 2rem;
|
||||||
/* aspect-ratio-card: .card .border-2 .cursor-pointer */
|
}
|
||||||
/* aspect-ratio-preview: .flex .justify-center .items-center .border-dashed */
|
|
||||||
|
.aspect-ratio-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* Component-specific overrides only */
|
|
||||||
.aspect-ratio-card {
|
.aspect-ratio-card {
|
||||||
border-width: 2px;
|
border: 2px solid #e2e8f0;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
padding: 1.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aspect-ratio-card:hover {
|
||||||
|
border-color: #cbd5e1;
|
||||||
|
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.aspect-ratio-card.selected {
|
.aspect-ratio-card.selected {
|
||||||
border-color: var(--border-accent);
|
border-color: #3b82f6;
|
||||||
background: var(--bg-accent);
|
box-shadow: 0 4px 6px -1px rgba(59, 130, 246, 0.2);
|
||||||
|
background: #eff6ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.aspect-ratio-preview {
|
.aspect-ratio-preview {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
height: 80px;
|
height: 80px;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
background: #f8fafc;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border: 1px dashed #cbd5e1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-box {
|
.preview-box {
|
||||||
background: var(--text-accent);
|
background: #3b82f6;
|
||||||
|
border-radius: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-box.aspect-16-9 { width: 64px; height: 36px; }
|
.preview-box.aspect-16-9 {
|
||||||
.preview-box.aspect-4-3 { width: 60px; height: 45px; }
|
width: 64px;
|
||||||
.preview-box.aspect-16-10 { width: 64px; height: 40px; }
|
height: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Uses .badge-secondary for ratio-dimensions */
|
.preview-box.aspect-4-3 {
|
||||||
/* Uses .flex .gap-4 .flex-shrink-0 for action-buttons */
|
width: 60px;
|
||||||
|
height: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
/* Uses .btn, .btn-primary, .btn-secondary classes from application.css */
|
.preview-box.aspect-16-10 {
|
||||||
/* Uses .loading-content, .error-content, .loading-spinner classes from application.css */
|
width: 64px;
|
||||||
/* Responsive design handled by utility classes in application.css */
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.aspect-ratio-info h3 {
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1e293b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ratio-description {
|
||||||
|
margin: 0 0 0.75rem 0;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #64748b;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ratio-dimensions {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: #6b7280;
|
||||||
|
background: #f1f5f9;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
border: none;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.primary {
|
||||||
|
background: #3b82f6;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.primary:hover:not(:disabled) {
|
||||||
|
background: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.primary:disabled {
|
||||||
|
background: #9ca3af;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.secondary {
|
||||||
|
background: #f8fafc;
|
||||||
|
color: #64748b;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button.secondary:hover {
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: #475569;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading and Error States */
|
||||||
|
.loading-content,
|
||||||
|
.error-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 50vh;
|
||||||
|
text-align: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-content h2 {
|
||||||
|
color: #dc2626;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-content p {
|
||||||
|
color: #64748b;
|
||||||
|
margin: 0.5rem 0 1.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Design */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.page-header {
|
||||||
|
padding: 1rem;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-content {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.creation-actions {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
justify-content: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
@ -127,7 +127,7 @@ export const NewPresentationPage: React.FC = () => {
|
|||||||
</button>
|
</button>
|
||||||
<div className="header-content">
|
<div className="header-content">
|
||||||
<h1>Create New Presentation</h1>
|
<h1>Create New Presentation</h1>
|
||||||
<p>Enter details for your new presentation</p>
|
<p>Choose a theme and enter details for your new presentation</p>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
@ -15,10 +15,10 @@ export const PresentationDetailsForm: React.FC<PresentationDetailsFormProps> = (
|
|||||||
onDescriptionChange
|
onDescriptionChange
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<section className="form-section">
|
<section className="presentation-details">
|
||||||
<h2>Presentation Details</h2>
|
<h2>Presentation Details</h2>
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label htmlFor="title" className="form-label">Title *</label>
|
<label htmlFor="title">Title *</label>
|
||||||
<input
|
<input
|
||||||
id="title"
|
id="title"
|
||||||
type="text"
|
type="text"
|
||||||
@ -31,13 +31,13 @@ export const PresentationDetailsForm: React.FC<PresentationDetailsFormProps> = (
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="form-group">
|
<div className="form-group">
|
||||||
<label htmlFor="description" className="form-label">Description</label>
|
<label htmlFor="description">Description</label>
|
||||||
<textarea
|
<textarea
|
||||||
id="description"
|
id="description"
|
||||||
value={description}
|
value={description}
|
||||||
onChange={(e) => onDescriptionChange(e.target.value)}
|
onChange={(e) => onDescriptionChange(e.target.value)}
|
||||||
placeholder="Optional description of your presentation"
|
placeholder="Optional description of your presentation"
|
||||||
className="form-textarea form-input"
|
className="form-textarea"
|
||||||
rows={3}
|
rows={3}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
390
src/components/presentations/PresentationViewer.css
Normal file
390
src/components/presentations/PresentationViewer.css
Normal file
@ -0,0 +1,390 @@
|
|||||||
|
.presentation-viewer {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #f8fafc;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Header */
|
||||||
|
.viewer-header {
|
||||||
|
background: white;
|
||||||
|
border-bottom: 1px solid #e2e8f0;
|
||||||
|
padding: 1rem 2rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 2rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-info {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1.5rem;
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-link {
|
||||||
|
color: #64748b;
|
||||||
|
text-decoration: none;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-link:hover {
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-title {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-title h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1e293b;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-description {
|
||||||
|
margin: 0.25rem 0 0 0;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-meta {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.theme-badge {
|
||||||
|
background: #dbeafe;
|
||||||
|
color: #1e40af;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-counter {
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.75rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.primary {
|
||||||
|
background: #3b82f6;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.primary:hover {
|
||||||
|
background: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.secondary {
|
||||||
|
background: #f8fafc;
|
||||||
|
color: #64748b;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.secondary:hover {
|
||||||
|
background: #f1f5f9;
|
||||||
|
color: #475569;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button.large {
|
||||||
|
padding: 0.75rem 1.5rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main Content */
|
||||||
|
.viewer-content {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 2rem;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Empty State */
|
||||||
|
.empty-presentation {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-content {
|
||||||
|
text-align: center;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-content h2 {
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
color: #374151;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-content p {
|
||||||
|
margin: 0 0 2rem 0;
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Slide Area */
|
||||||
|
.slide-area {
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-container {
|
||||||
|
background: white;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
padding: 2rem;
|
||||||
|
max-width: 800px;
|
||||||
|
width: 100%;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-content h3 {
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
color: #1e293b;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-content p {
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-preview {
|
||||||
|
background: #f8fafc;
|
||||||
|
border: 2px dashed #cbd5e1;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 3rem;
|
||||||
|
text-align: center;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-placeholder {
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-placeholder p {
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-notes {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
padding: 1rem;
|
||||||
|
background: #f8fafc;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-notes h4 {
|
||||||
|
margin: 0 0 0.5rem 0;
|
||||||
|
color: #374151;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-notes p {
|
||||||
|
margin: 0;
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-error {
|
||||||
|
text-align: center;
|
||||||
|
padding: 2rem;
|
||||||
|
color: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Slide Navigation */
|
||||||
|
.slide-navigation {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
background: white;
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-button {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
background: white;
|
||||||
|
color: #374151;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-button:hover:not(:disabled) {
|
||||||
|
background: #f8fafc;
|
||||||
|
border-color: #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-button:disabled {
|
||||||
|
opacity: 0.5;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-thumbnails {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
max-width: 300px;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail {
|
||||||
|
min-width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border: 2px solid #e2e8f0;
|
||||||
|
background: white;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #6b7280;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail:hover {
|
||||||
|
border-color: #cbd5e1;
|
||||||
|
background: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail.active {
|
||||||
|
border-color: #3b82f6;
|
||||||
|
background: #dbeafe;
|
||||||
|
color: #1e40af;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Loading and Error States */
|
||||||
|
.loading-content,
|
||||||
|
.error-content,
|
||||||
|
.not-found-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 50vh;
|
||||||
|
text-align: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-content h2,
|
||||||
|
.not-found-content h2 {
|
||||||
|
color: #dc2626;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-content p,
|
||||||
|
.not-found-content p {
|
||||||
|
color: #64748b;
|
||||||
|
margin: 0.5rem 0 1.5rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Responsive Design */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.viewer-header {
|
||||||
|
padding: 1rem;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-info {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.presentation-meta {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-actions {
|
||||||
|
justify-content: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-button {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.viewer-content {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-container {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-navigation {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.slide-thumbnails {
|
||||||
|
justify-content: center;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
}
|
249
src/components/presentations/PresentationViewer.tsx
Normal file
249
src/components/presentations/PresentationViewer.tsx
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { useParams, useNavigate, Link } from 'react-router-dom';
|
||||||
|
import type { Presentation } from '../../types/presentation.ts';
|
||||||
|
import type { Theme } from '../../types/theme.ts';
|
||||||
|
import { getPresentationById } from '../../utils/presentationStorage.ts';
|
||||||
|
import { loadTheme } from '../../utils/themeLoader.ts';
|
||||||
|
import './PresentationViewer.css';
|
||||||
|
|
||||||
|
export const PresentationViewer: React.FC = () => {
|
||||||
|
const { presentationId, slideNumber } = useParams<{
|
||||||
|
presentationId: string;
|
||||||
|
slideNumber: string;
|
||||||
|
}>();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [presentation, setPresentation] = useState<Presentation | null>(null);
|
||||||
|
const [theme, setTheme] = useState<Theme | null>(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const currentSlideIndex = slideNumber ? parseInt(slideNumber, 10) - 1 : 0;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadPresentationAndTheme = async () => {
|
||||||
|
if (!presentationId) {
|
||||||
|
setError('No presentation ID provided');
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
// Load presentation
|
||||||
|
const presentationData = await getPresentationById(presentationId);
|
||||||
|
if (!presentationData) {
|
||||||
|
setError(`Presentation not found: ${presentationId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setPresentation(presentationData);
|
||||||
|
|
||||||
|
// Load theme
|
||||||
|
const themeData = await loadTheme(presentationData.metadata.theme, false);
|
||||||
|
if (!themeData) {
|
||||||
|
setError(`Theme not found: ${presentationData.metadata.theme}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setTheme(themeData);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err instanceof Error ? err.message : 'Failed to load presentation');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
loadPresentationAndTheme();
|
||||||
|
}, [presentationId]);
|
||||||
|
|
||||||
|
const goToSlide = (slideIndex: number) => {
|
||||||
|
if (!presentation) return;
|
||||||
|
|
||||||
|
const slideNum = slideIndex + 1;
|
||||||
|
navigate(`/presentations/${presentationId}/view/slides/${slideNum}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToPreviousSlide = () => {
|
||||||
|
if (currentSlideIndex > 0) {
|
||||||
|
goToSlide(currentSlideIndex - 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const goToNextSlide = () => {
|
||||||
|
if (presentation && currentSlideIndex < presentation.slides.length - 1) {
|
||||||
|
goToSlide(currentSlideIndex + 1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const editPresentation = () => {
|
||||||
|
if (!presentation) return;
|
||||||
|
|
||||||
|
const slideNum = Math.max(1, currentSlideIndex + 1);
|
||||||
|
navigate(`/presentations/${presentationId}/edit/slides/${slideNum}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
const enterPresentationMode = () => {
|
||||||
|
if (!presentation) return;
|
||||||
|
navigate(`/presentations/${presentationId}/present/${currentSlideIndex + 1}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="presentation-viewer">
|
||||||
|
<div className="loading-content">
|
||||||
|
<div className="loading-spinner">Loading presentation...</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className="presentation-viewer">
|
||||||
|
<div className="error-content">
|
||||||
|
<h2>Error Loading Presentation</h2>
|
||||||
|
<p>{error}</p>
|
||||||
|
<Link to="/themes" className="back-link">← Back to Themes</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!presentation || !theme) {
|
||||||
|
return (
|
||||||
|
<div className="presentation-viewer">
|
||||||
|
<div className="not-found-content">
|
||||||
|
<h2>Presentation Not Found</h2>
|
||||||
|
<p>The requested presentation could not be found.</p>
|
||||||
|
<Link to="/themes" className="back-link">← Back to Themes</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentSlide = presentation.slides[currentSlideIndex];
|
||||||
|
const totalSlides = presentation.slides.length;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="presentation-viewer">
|
||||||
|
<header className="viewer-header">
|
||||||
|
<div className="presentation-info">
|
||||||
|
<Link to="/themes" className="back-link">← Back</Link>
|
||||||
|
<div className="presentation-title">
|
||||||
|
<h1>{presentation.metadata.name}</h1>
|
||||||
|
{presentation.metadata.description && (
|
||||||
|
<p className="presentation-description">{presentation.metadata.description}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="presentation-meta">
|
||||||
|
<span className="theme-badge">Theme: {theme.name}</span>
|
||||||
|
<span className="slide-counter">
|
||||||
|
{totalSlides === 0 ? 'No slides' : `Slide ${currentSlideIndex + 1} of ${totalSlides}`}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="viewer-actions">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="action-button secondary"
|
||||||
|
onClick={editPresentation}
|
||||||
|
>
|
||||||
|
Edit
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="action-button primary"
|
||||||
|
onClick={enterPresentationMode}
|
||||||
|
>
|
||||||
|
Present
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main className="viewer-content">
|
||||||
|
{totalSlides === 0 ? (
|
||||||
|
<div className="empty-presentation">
|
||||||
|
<div className="empty-content">
|
||||||
|
<h2>This presentation is empty</h2>
|
||||||
|
<p>Switch to edit mode to add slides</p>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="action-button primary large"
|
||||||
|
onClick={editPresentation}
|
||||||
|
>
|
||||||
|
Edit Presentation
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="slide-area">
|
||||||
|
<div className="slide-container">
|
||||||
|
{currentSlide ? (
|
||||||
|
<div className="slide-content">
|
||||||
|
<h3>Slide {currentSlideIndex + 1}</h3>
|
||||||
|
<p>Layout: {currentSlide.layoutId}</p>
|
||||||
|
<div className="slide-preview">
|
||||||
|
{/* TODO: Render actual slide content based on layout */}
|
||||||
|
<div className="slide-placeholder">
|
||||||
|
<p>Slide content will be rendered here</p>
|
||||||
|
<p>Layout: {currentSlide.layoutId}</p>
|
||||||
|
<p>Content slots: {Object.keys(currentSlide.content).length}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{currentSlide.notes && (
|
||||||
|
<div className="slide-notes">
|
||||||
|
<h4>Notes:</h4>
|
||||||
|
<p>{currentSlide.notes}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="slide-error">
|
||||||
|
<p>Invalid slide number</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="slide-navigation">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="nav-button"
|
||||||
|
onClick={goToPreviousSlide}
|
||||||
|
disabled={currentSlideIndex === 0}
|
||||||
|
>
|
||||||
|
← Previous
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div className="slide-thumbnails">
|
||||||
|
{presentation.slides.map((slide, index) => (
|
||||||
|
<button
|
||||||
|
key={slide.id}
|
||||||
|
type="button"
|
||||||
|
className={`thumbnail ${index === currentSlideIndex ? 'active' : ''}`}
|
||||||
|
onClick={() => goToSlide(index)}
|
||||||
|
>
|
||||||
|
{index + 1}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="nav-button"
|
||||||
|
onClick={goToNextSlide}
|
||||||
|
disabled={currentSlideIndex === totalSlides - 1}
|
||||||
|
>
|
||||||
|
Next →
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@ -61,6 +61,11 @@ export const PresentationsList: React.FC = () => {
|
|||||||
setDeleting(presentationId);
|
setDeleting(presentationId);
|
||||||
await deletePresentation(presentationId);
|
await deletePresentation(presentationId);
|
||||||
setPresentations(prev => prev.filter(p => p.metadata.id !== presentationId));
|
setPresentations(prev => prev.filter(p => p.metadata.id !== presentationId));
|
||||||
|
setAlertDialog({
|
||||||
|
isOpen: true,
|
||||||
|
message: 'Presentation deleted successfully',
|
||||||
|
type: 'success'
|
||||||
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
loggers.presentation.error('Failed to delete presentation', err instanceof Error ? err : new Error(String(err)));
|
loggers.presentation.error('Failed to delete presentation', err instanceof Error ? err : new Error(String(err)));
|
||||||
setAlertDialog({
|
setAlertDialog({
|
||||||
@ -78,6 +83,10 @@ export const PresentationsList: React.FC = () => {
|
|||||||
navigate(`/presentations/${id}/edit/slides/${slideNumber}`);
|
navigate(`/presentations/${id}/edit/slides/${slideNumber}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleViewPresentation = (id: string, slideCount: number) => {
|
||||||
|
const slideNumber = slideCount > 0 ? 1 : 1; // Always go to slide 1, or empty state
|
||||||
|
navigate(`/presentations/${id}/view/slides/${slideNumber}`);
|
||||||
|
};
|
||||||
|
|
||||||
const formatDate = (dateString: string) => {
|
const formatDate = (dateString: string) => {
|
||||||
return new Date(dateString).toLocaleDateString('en-US', {
|
return new Date(dateString).toLocaleDateString('en-US', {
|
||||||
@ -162,6 +171,14 @@ export const PresentationsList: React.FC = () => {
|
|||||||
>
|
>
|
||||||
✏️
|
✏️
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="action-icon"
|
||||||
|
onClick={() => handleViewPresentation(presentation.metadata.id, presentation.slides.length)}
|
||||||
|
title="View presentation"
|
||||||
|
>
|
||||||
|
👁️
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="action-icon delete"
|
className="action-icon delete"
|
||||||
@ -210,6 +227,13 @@ export const PresentationsList: React.FC = () => {
|
|||||||
>
|
>
|
||||||
Edit
|
Edit
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="card-action secondary"
|
||||||
|
onClick={() => handleViewPresentation(presentation.metadata.id, presentation.slides.length)}
|
||||||
|
>
|
||||||
|
View
|
||||||
|
</button>
|
||||||
{presentation.slides.length > 0 && (
|
{presentation.slides.length > 0 && (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -24,6 +24,3 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
}
|
}
|
||||||
.theme-preview-info {
|
|
||||||
display: none;
|
|
||||||
}
|
|
@ -638,7 +638,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.preview-info span {
|
.preview-info span {
|
||||||
background: var(--bg-overlay);
|
background: rgba(0, 0, 0, 0.5);
|
||||||
padding: var(--space-sm) var(--space-lg);
|
padding: var(--space-sm) var(--space-lg);
|
||||||
border-radius: var(--radius-sm);
|
border-radius: var(--radius-sm);
|
||||||
backdrop-filter: blur(8px);
|
backdrop-filter: blur(8px);
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background-color: var(--bg-overlay);
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
/* Modal Content */
|
/* Modal Content */
|
||||||
.modal-content {
|
.modal-content {
|
||||||
background: var(--bg-primary);
|
background: white;
|
||||||
border-radius: 0.75rem;
|
border-radius: 0.75rem;
|
||||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
@ -25,7 +25,6 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
animation: modalSlideIn 0.2s ease-out;
|
animation: modalSlideIn 0.2s ease-out;
|
||||||
border: 1px solid var(--border-primary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes modalSlideIn {
|
@keyframes modalSlideIn {
|
||||||
@ -39,22 +38,19 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Modal Sizes - Applied to modal-content with size modifier */
|
/* Modal Sizes */
|
||||||
.modal-content.modal-small {
|
.modal-small {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 320px;
|
max-width: 400px;
|
||||||
max-width: 420px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content.modal-medium {
|
.modal-medium {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 400px;
|
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-content.modal-large {
|
.modal-large {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 500px;
|
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,14 +109,6 @@
|
|||||||
max-height: 95vh;
|
max-height: 95vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Override min-widths on mobile for better fit */
|
|
||||||
.modal-content.modal-small,
|
|
||||||
.modal-content.modal-medium,
|
|
||||||
.modal-content.modal-large {
|
|
||||||
min-width: auto;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-header {
|
.modal-header {
|
||||||
padding: 1rem 1rem 0 1rem;
|
padding: 1rem 1rem 0 1rem;
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
/* Import global color system */
|
/* Import global color system */
|
||||||
@import './styles/colors.css';
|
@import './styles/colors.css';
|
||||||
/* Import consolidated application styles */
|
|
||||||
@import './styles/application.css';
|
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||||
|
@ -1,781 +0,0 @@
|
|||||||
/* Application CSS - Consolidated Styles */
|
|
||||||
/* Import color system first */
|
|
||||||
@import './colors.css';
|
|
||||||
|
|
||||||
/* =================================================================
|
|
||||||
BASE APP LAYOUT
|
|
||||||
================================================================= */
|
|
||||||
|
|
||||||
/* Root application container */
|
|
||||||
.app-root {
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
text-align: left;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Main content area */
|
|
||||||
.app-main {
|
|
||||||
padding: 2rem;
|
|
||||||
max-width: 1400px;
|
|
||||||
margin: 0 auto;
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* App header */
|
|
||||||
.app-header {
|
|
||||||
border-bottom: 1px solid var(--border-primary);
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =================================================================
|
|
||||||
UTILITY CLASSES - Layout
|
|
||||||
================================================================= */
|
|
||||||
|
|
||||||
/* Display */
|
|
||||||
.flex { display: flex; }
|
|
||||||
.grid { display: grid; }
|
|
||||||
.block { display: block; }
|
|
||||||
.inline-block { display: inline-block; }
|
|
||||||
.hidden { display: none; }
|
|
||||||
|
|
||||||
/* Flex Direction */
|
|
||||||
.flex-col { flex-direction: column; }
|
|
||||||
.flex-row { flex-direction: row; }
|
|
||||||
|
|
||||||
/* Flex Alignment */
|
|
||||||
.items-start { align-items: flex-start; }
|
|
||||||
.items-center { align-items: center; }
|
|
||||||
.items-end { align-items: flex-end; }
|
|
||||||
.items-stretch { align-items: stretch; }
|
|
||||||
|
|
||||||
.justify-start { justify-content: flex-start; }
|
|
||||||
.justify-center { justify-content: center; }
|
|
||||||
.justify-end { justify-content: flex-end; }
|
|
||||||
.justify-between { justify-content: space-between; }
|
|
||||||
|
|
||||||
/* Flex Properties */
|
|
||||||
.flex-1 { flex: 1; }
|
|
||||||
.flex-shrink-0 { flex-shrink: 0; }
|
|
||||||
.flex-wrap { flex-wrap: wrap; }
|
|
||||||
|
|
||||||
/* Grid Templates */
|
|
||||||
.grid-auto-fill-200 { grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); }
|
|
||||||
.grid-auto-fill-250 { grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); }
|
|
||||||
.grid-auto-fill-300 { grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); }
|
|
||||||
.grid-cols-2 { grid-template-columns: repeat(2, 1fr); }
|
|
||||||
.grid-cols-3 { grid-template-columns: repeat(3, 1fr); }
|
|
||||||
|
|
||||||
/* Gap */
|
|
||||||
.gap-1 { gap: 0.25rem; }
|
|
||||||
.gap-2 { gap: 0.5rem; }
|
|
||||||
.gap-3 { gap: 0.75rem; }
|
|
||||||
.gap-4 { gap: 1rem; }
|
|
||||||
.gap-6 { gap: 1.5rem; }
|
|
||||||
.gap-8 { gap: 2rem; }
|
|
||||||
|
|
||||||
/* Position */
|
|
||||||
.relative { position: relative; }
|
|
||||||
.absolute { position: absolute; }
|
|
||||||
.fixed { position: fixed; }
|
|
||||||
.sticky { position: sticky; }
|
|
||||||
|
|
||||||
/* =================================================================
|
|
||||||
UTILITY CLASSES - Spacing
|
|
||||||
================================================================= */
|
|
||||||
|
|
||||||
/* Margin */
|
|
||||||
.m-0 { margin: 0; }
|
|
||||||
.m-auto { margin: auto; }
|
|
||||||
.mx-auto { margin-left: auto; margin-right: auto; }
|
|
||||||
|
|
||||||
.mt-1 { margin-top: 0.25rem; }
|
|
||||||
.mt-2 { margin-top: 0.5rem; }
|
|
||||||
.mt-4 { margin-top: 1rem; }
|
|
||||||
.mt-6 { margin-top: 1.5rem; }
|
|
||||||
.mt-8 { margin-top: 2rem; }
|
|
||||||
|
|
||||||
.mb-1 { margin-bottom: 0.25rem; }
|
|
||||||
.mb-2 { margin-bottom: 0.5rem; }
|
|
||||||
.mb-4 { margin-bottom: 1rem; }
|
|
||||||
.mb-6 { margin-bottom: 1.5rem; }
|
|
||||||
.mb-8 { margin-bottom: 2rem; }
|
|
||||||
|
|
||||||
.ml-2 { margin-left: 0.5rem; }
|
|
||||||
.ml-4 { margin-left: 1rem; }
|
|
||||||
.mr-2 { margin-right: 0.5rem; }
|
|
||||||
.mr-4 { margin-right: 1rem; }
|
|
||||||
|
|
||||||
/* Padding */
|
|
||||||
.p-1 { padding: 0.25rem; }
|
|
||||||
.p-2 { padding: 0.5rem; }
|
|
||||||
.p-3 { padding: 0.75rem; }
|
|
||||||
.p-4 { padding: 1rem; }
|
|
||||||
.p-6 { padding: 1.5rem; }
|
|
||||||
.p-8 { padding: 2rem; }
|
|
||||||
|
|
||||||
.px-2 { padding-left: 0.5rem; padding-right: 0.5rem; }
|
|
||||||
.px-3 { padding-left: 0.75rem; padding-right: 0.75rem; }
|
|
||||||
.px-4 { padding-left: 1rem; padding-right: 1rem; }
|
|
||||||
.px-6 { padding-left: 1.5rem; padding-right: 1.5rem; }
|
|
||||||
.px-8 { padding-left: 2rem; padding-right: 2rem; }
|
|
||||||
|
|
||||||
.py-1 { padding-top: 0.25rem; padding-bottom: 0.25rem; }
|
|
||||||
.py-2 { padding-top: 0.5rem; padding-bottom: 0.5rem; }
|
|
||||||
.py-3 { padding-top: 0.75rem; padding-bottom: 0.75rem; }
|
|
||||||
.py-4 { padding-top: 1rem; padding-bottom: 1rem; }
|
|
||||||
.py-6 { padding-top: 1.5rem; padding-bottom: 1.5rem; }
|
|
||||||
.py-8 { padding-top: 2rem; padding-bottom: 2rem; }
|
|
||||||
|
|
||||||
/* =================================================================
|
|
||||||
UTILITY CLASSES - Sizing
|
|
||||||
================================================================= */
|
|
||||||
|
|
||||||
/* Width */
|
|
||||||
.w-full { width: 100%; }
|
|
||||||
.w-auto { width: auto; }
|
|
||||||
.w-8 { width: 2rem; }
|
|
||||||
.w-12 { width: 3rem; }
|
|
||||||
.w-16 { width: 4rem; }
|
|
||||||
.w-32 { width: 8rem; }
|
|
||||||
|
|
||||||
/* Height */
|
|
||||||
.h-full { height: 100%; }
|
|
||||||
.h-auto { height: auto; }
|
|
||||||
.h-8 { height: 2rem; }
|
|
||||||
.h-12 { height: 3rem; }
|
|
||||||
.h-16 { height: 4rem; }
|
|
||||||
.h-20 { height: 5rem; }
|
|
||||||
.h-32 { height: 8rem; }
|
|
||||||
|
|
||||||
/* Min/Max Width */
|
|
||||||
.max-w-sm { max-width: 24rem; }
|
|
||||||
.max-w-md { max-width: 28rem; }
|
|
||||||
.max-w-lg { max-width: 32rem; }
|
|
||||||
.max-w-xl { max-width: 36rem; }
|
|
||||||
.max-w-2xl { max-width: 42rem; }
|
|
||||||
.max-w-4xl { max-width: 56rem; }
|
|
||||||
.max-w-6xl { max-width: 72rem; }
|
|
||||||
.max-w-7xl { max-width: 80rem; }
|
|
||||||
.max-w-full { max-width: 100%; }
|
|
||||||
|
|
||||||
/* Min Height */
|
|
||||||
.min-h-0 { min-height: 0; }
|
|
||||||
.min-h-screen { min-height: 100vh; }
|
|
||||||
.min-h-20 { min-height: 5rem; }
|
|
||||||
.min-h-32 { min-height: 8rem; }
|
|
||||||
|
|
||||||
/* =================================================================
|
|
||||||
UTILITY CLASSES - Typography
|
|
||||||
================================================================= */
|
|
||||||
|
|
||||||
/* Font Size */
|
|
||||||
.text-xs { font-size: 0.75rem; line-height: 1rem; }
|
|
||||||
.text-sm { font-size: 0.875rem; line-height: 1.25rem; }
|
|
||||||
.text-base { font-size: 1rem; line-height: 1.5rem; }
|
|
||||||
.text-lg { font-size: 1.125rem; line-height: 1.75rem; }
|
|
||||||
.text-xl { font-size: 1.25rem; line-height: 1.75rem; }
|
|
||||||
.text-2xl { font-size: 1.5rem; line-height: 2rem; }
|
|
||||||
.text-3xl { font-size: 1.875rem; line-height: 2.25rem; }
|
|
||||||
|
|
||||||
/* Font Weight */
|
|
||||||
.font-normal { font-weight: 400; }
|
|
||||||
.font-medium { font-weight: 500; }
|
|
||||||
.font-semibold { font-weight: 600; }
|
|
||||||
.font-bold { font-weight: 700; }
|
|
||||||
|
|
||||||
/* Text Alignment */
|
|
||||||
.text-left { text-align: left; }
|
|
||||||
.text-center { text-align: center; }
|
|
||||||
.text-right { text-align: right; }
|
|
||||||
|
|
||||||
/* Line Height */
|
|
||||||
.leading-none { line-height: 1; }
|
|
||||||
.leading-tight { line-height: 1.25; }
|
|
||||||
.leading-normal { line-height: 1.5; }
|
|
||||||
.leading-relaxed { line-height: 1.625; }
|
|
||||||
|
|
||||||
/* =================================================================
|
|
||||||
UTILITY CLASSES - Borders & Appearance
|
|
||||||
================================================================= */
|
|
||||||
|
|
||||||
/* Border Radius */
|
|
||||||
.rounded { border-radius: 0.25rem; }
|
|
||||||
.rounded-md { border-radius: 0.375rem; }
|
|
||||||
.rounded-lg { border-radius: 0.5rem; }
|
|
||||||
.rounded-xl { border-radius: 0.75rem; }
|
|
||||||
.rounded-2xl { border-radius: 1rem; }
|
|
||||||
.rounded-full { border-radius: 50%; }
|
|
||||||
|
|
||||||
/* Borders */
|
|
||||||
.border { border: 1px solid var(--border-primary); }
|
|
||||||
.border-2 { border: 2px solid var(--border-primary); }
|
|
||||||
.border-t { border-top: 1px solid var(--border-primary); }
|
|
||||||
.border-b { border-bottom: 1px solid var(--border-primary); }
|
|
||||||
.border-l { border-left: 1px solid var(--border-primary); }
|
|
||||||
.border-r { border-right: 1px solid var(--border-primary); }
|
|
||||||
|
|
||||||
.border-dashed { border-style: dashed; }
|
|
||||||
|
|
||||||
/* Box Shadow */
|
|
||||||
.shadow-none { box-shadow: none; }
|
|
||||||
.shadow-sm { box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05); }
|
|
||||||
.shadow { box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); }
|
|
||||||
.shadow-md { box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); }
|
|
||||||
.shadow-lg { box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); }
|
|
||||||
|
|
||||||
/* =================================================================
|
|
||||||
UTILITY CLASSES - Aspect Ratios
|
|
||||||
================================================================= */
|
|
||||||
|
|
||||||
.aspect-16-9 { aspect-ratio: 16 / 9; }
|
|
||||||
.aspect-4-3 { aspect-ratio: 4 / 3; }
|
|
||||||
.aspect-16-10 { aspect-ratio: 16 / 10; }
|
|
||||||
.aspect-square { aspect-ratio: 1 / 1; }
|
|
||||||
|
|
||||||
/* =================================================================
|
|
||||||
UTILITY CLASSES - Transitions & Animations
|
|
||||||
================================================================= */
|
|
||||||
|
|
||||||
.transition { transition: all 0.2s ease; }
|
|
||||||
.transition-colors {
|
|
||||||
transition: background-color 0.2s ease, border-color 0.2s ease, color 0.2s ease;
|
|
||||||
}
|
|
||||||
.transition-opacity { transition: opacity 0.2s ease; }
|
|
||||||
.transition-transform { transition: transform 0.2s ease; }
|
|
||||||
|
|
||||||
/* Common Animation States */
|
|
||||||
.hover\:shadow-md:hover { box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); }
|
|
||||||
.hover\:bg-hover:hover { background-color: var(--bg-hover); }
|
|
||||||
|
|
||||||
/* =================================================================
|
|
||||||
UTILITY CLASSES - Interactions
|
|
||||||
================================================================= */
|
|
||||||
|
|
||||||
.cursor-pointer { cursor: pointer; }
|
|
||||||
.cursor-not-allowed { cursor: not-allowed; }
|
|
||||||
.select-none { user-select: none; }
|
|
||||||
|
|
||||||
/* Opacity */
|
|
||||||
.opacity-50 { opacity: 0.5; }
|
|
||||||
.opacity-75 { opacity: 0.75; }
|
|
||||||
|
|
||||||
/* Z-Index */
|
|
||||||
.z-10 { z-index: 10; }
|
|
||||||
.z-20 { z-index: 20; }
|
|
||||||
.z-50 { z-index: 50; }
|
|
||||||
.z-1000 { z-index: 1000; }
|
|
||||||
|
|
||||||
/* =================================================================
|
|
||||||
COMPONENT BASE CLASSES
|
|
||||||
================================================================= */
|
|
||||||
|
|
||||||
/* Page Layout */
|
|
||||||
.page-container {
|
|
||||||
min-height: 100vh;
|
|
||||||
background: var(--bg-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
border-bottom: 1px solid var(--border-primary);
|
|
||||||
padding: 2rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1.5rem;
|
|
||||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header h1 {
|
|
||||||
font-size: 2rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--text-primary);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header p {
|
|
||||||
font-size: 1rem;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
margin: 0.25rem 0 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-content {
|
|
||||||
padding: 3rem 2rem;
|
|
||||||
max-width: 800px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Cards */
|
|
||||||
.card {
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
border: 1px solid var(--border-primary);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
padding: 1.5rem;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card:hover {
|
|
||||||
border-color: var(--border-hover);
|
|
||||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Form Section Card - Better visual separation */
|
|
||||||
.form-section {
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
border: 1px solid var(--border-primary);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
padding: 2rem;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-section h2 {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--text-primary);
|
|
||||||
margin: 0 0 1.5rem 0;
|
|
||||||
padding-bottom: 0.75rem;
|
|
||||||
border-bottom: 1px solid var(--border-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-compact {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header {
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header h1,
|
|
||||||
.card-header h2,
|
|
||||||
.card-header h3 {
|
|
||||||
margin: 0 0 0.5rem 0;
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-header p {
|
|
||||||
margin: 0;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Grid Cards */
|
|
||||||
.card-grid {
|
|
||||||
display: grid;
|
|
||||||
gap: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-grid-auto-300 {
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-grid-auto-250 {
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Buttons */
|
|
||||||
.btn, .button {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 0.75rem 1.5rem;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
text-decoration: none;
|
|
||||||
border: none;
|
|
||||||
box-sizing: border-box;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:disabled, .button:disabled {
|
|
||||||
cursor: not-allowed;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Primary Button - More prominent with shadow and stronger colors */
|
|
||||||
.btn-primary, .button.primary {
|
|
||||||
background: var(--text-accent);
|
|
||||||
color: white;
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 0.875rem 2rem;
|
|
||||||
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:hover:not(:disabled), .button.primary:hover:not(:disabled) {
|
|
||||||
background: var(--text-link-hover);
|
|
||||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.15), 0 4px 6px -2px rgba(0, 0, 0, 0.08);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:active:not(:disabled), .button.primary:active:not(:disabled) {
|
|
||||||
transform: translateY(0);
|
|
||||||
box-shadow: 0 2px 4px -1px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Secondary Button - Less prominent, ghost style */
|
|
||||||
.btn-secondary, .button.secondary {
|
|
||||||
background: transparent;
|
|
||||||
color: var(--text-tertiary);
|
|
||||||
border: 1px solid var(--border-secondary);
|
|
||||||
font-weight: 400;
|
|
||||||
padding: 0.625rem 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-secondary:hover:not(:disabled), .button.secondary:hover:not(:disabled) {
|
|
||||||
background: var(--bg-muted);
|
|
||||||
color: var(--text-secondary);
|
|
||||||
border-color: var(--border-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-danger {
|
|
||||||
background: var(--text-error);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-danger:hover:not(:disabled) {
|
|
||||||
background: var(--text-error);
|
|
||||||
opacity: 0.9;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ghost Link - Minimal style, looks like a text link */
|
|
||||||
.btn-ghost, .button.ghost {
|
|
||||||
background: transparent;
|
|
||||||
color: var(--text-tertiary);
|
|
||||||
border: none;
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
font-weight: 400;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: all 0.15s ease;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-ghost:hover:not(:disabled), .button.ghost:hover:not(:disabled) {
|
|
||||||
color: var(--text-primary);
|
|
||||||
background: transparent;
|
|
||||||
text-decoration: underline;
|
|
||||||
transform: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-ghost:active:not(:disabled), .button.ghost:active:not(:disabled) {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Button Sizes */
|
|
||||||
.btn-sm {
|
|
||||||
padding: 0.5rem 1rem;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-lg {
|
|
||||||
padding: 1rem 2rem;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Icon Buttons */
|
|
||||||
.btn-icon {
|
|
||||||
padding: 0.5rem;
|
|
||||||
width: 2.5rem;
|
|
||||||
height: 2.5rem;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-icon-sm {
|
|
||||||
padding: 0.25rem;
|
|
||||||
width: 2rem;
|
|
||||||
height: 2rem;
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Form Elements */
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 1.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.625rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--text-primary);
|
|
||||||
font-size: 0.875rem;
|
|
||||||
letter-spacing: 0.025em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-input, .form-textarea {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.875rem 1rem;
|
|
||||||
border: 1.5px solid var(--border-secondary);
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
font-size: 0.9375rem;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
color: var(--text-primary);
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-input:hover, .form-textarea:hover {
|
|
||||||
border-color: var(--border-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-input:focus, .form-textarea:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: var(--border-accent);
|
|
||||||
box-shadow: 0 0 0 3px var(--bg-accent);
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Prevent browser autofill from using white background */
|
|
||||||
.form-input:-webkit-autofill,
|
|
||||||
.form-input:-webkit-autofill:hover,
|
|
||||||
.form-input:-webkit-autofill:focus,
|
|
||||||
.form-textarea:-webkit-autofill,
|
|
||||||
.form-textarea:-webkit-autofill:hover,
|
|
||||||
.form-textarea:-webkit-autofill:focus {
|
|
||||||
-webkit-text-fill-color: var(--text-primary);
|
|
||||||
-webkit-box-shadow: 0 0 0px 1000px var(--bg-secondary) inset;
|
|
||||||
transition: background-color 5000s ease-in-out 0s;
|
|
||||||
background: var(--bg-secondary) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Placeholder text styling */
|
|
||||||
.form-input::placeholder,
|
|
||||||
.form-textarea::placeholder {
|
|
||||||
color: var(--text-muted);
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-textarea {
|
|
||||||
resize: vertical;
|
|
||||||
min-height: 100px;
|
|
||||||
font-family: inherit;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Error States */
|
|
||||||
.form-input.error {
|
|
||||||
border-color: var(--border-error);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-error {
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
color: var(--text-error);
|
|
||||||
font-size: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Loading States */
|
|
||||||
.loading-content,
|
|
||||||
.error-content,
|
|
||||||
.empty-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
min-height: 50vh;
|
|
||||||
text-align: center;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading-spinner {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 1.125rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-content h2 {
|
|
||||||
color: var(--text-error);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-content p {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
margin: 0.5rem 0 1.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Alert/Notification Styles */
|
|
||||||
.alert {
|
|
||||||
padding: 0.75rem;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
border: 1px solid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-error {
|
|
||||||
background: var(--bg-error);
|
|
||||||
border-color: var(--border-error);
|
|
||||||
color: var(--text-error);
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-success {
|
|
||||||
background: var(--bg-success);
|
|
||||||
border-color: var(--border-success);
|
|
||||||
color: var(--text-success);
|
|
||||||
}
|
|
||||||
|
|
||||||
.alert-warning {
|
|
||||||
background: var(--bg-warning);
|
|
||||||
border-color: var(--border-warning);
|
|
||||||
color: var(--text-warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Modal Base */
|
|
||||||
.modal-overlay {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
background-color: var(--bg-overlay);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 1000;
|
|
||||||
padding: 1rem;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-content {
|
|
||||||
background: var(--bg-primary);
|
|
||||||
border-radius: 0.75rem;
|
|
||||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 90vh;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
border: 1px solid var(--border-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Navigation */
|
|
||||||
.nav-link {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
padding: 0.5rem 0.75rem;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-link:hover {
|
|
||||||
color: var(--text-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Stats/Badge Components */
|
|
||||||
.badge {
|
|
||||||
padding: 0.25rem 0.75rem;
|
|
||||||
border-radius: 0.375rem;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge-primary {
|
|
||||||
background: var(--bg-accent);
|
|
||||||
color: var(--text-accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.badge-secondary {
|
|
||||||
background: var(--bg-muted);
|
|
||||||
color: var(--text-tertiary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =================================================================
|
|
||||||
RESPONSIVE UTILITIES
|
|
||||||
================================================================= */
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.page-header {
|
|
||||||
padding: 1rem;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-content {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-grid-auto-300 {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-grid-auto-250 {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-col-mobile {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-center-mobile {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =================================================================
|
|
||||||
ANIMATION KEYFRAMES
|
|
||||||
================================================================= */
|
|
||||||
|
|
||||||
@keyframes slideIn {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: scale(0.95) translateY(-10px);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: scale(1) translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeIn {
|
|
||||||
from { opacity: 0; }
|
|
||||||
to { opacity: 1; }
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
from { transform: rotate(0deg); }
|
|
||||||
to { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Animation utilities */
|
|
||||||
.animate-slide-in {
|
|
||||||
animation: slideIn 0.2s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.animate-fade-in {
|
|
||||||
animation: fadeIn 0.2s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
.animate-spin {
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* =================================================================
|
|
||||||
RESPONSIVE DESIGN
|
|
||||||
================================================================= */
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.app-main {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.app-header h1 {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-title-section {
|
|
||||||
padding: 1.5rem 1rem;
|
|
||||||
}
|
|
||||||
}
|
|
@ -154,7 +154,6 @@
|
|||||||
--bg-muted: var(--color-gray-50);
|
--bg-muted: var(--color-gray-50);
|
||||||
--bg-hover: var(--color-slate-100);
|
--bg-hover: var(--color-slate-100);
|
||||||
--bg-active: var(--color-slate-200);
|
--bg-active: var(--color-slate-200);
|
||||||
--bg-overlay: rgba(0, 0, 0, 0.5);
|
|
||||||
|
|
||||||
/* Borders */
|
/* Borders */
|
||||||
--border-primary: var(--color-slate-200);
|
--border-primary: var(--color-slate-200);
|
||||||
@ -239,7 +238,6 @@
|
|||||||
--bg-muted: var(--color-gray-800);
|
--bg-muted: var(--color-gray-800);
|
||||||
--bg-hover: var(--color-gray-700);
|
--bg-hover: var(--color-gray-700);
|
||||||
--bg-active: var(--color-gray-600);
|
--bg-active: var(--color-gray-600);
|
||||||
--bg-overlay: rgba(0, 0, 0, 0.7);
|
|
||||||
|
|
||||||
--border-primary: var(--color-gray-600);
|
--border-primary: var(--color-gray-600);
|
||||||
--border-secondary: var(--color-gray-500);
|
--border-secondary: var(--color-gray-500);
|
||||||
|
Loading…
Reference in New Issue
Block a user