From 92672f77e3e86d45561fb1e5cc5ed8658ae0211d Mon Sep 17 00:00:00 2001 From: Michael Mainguy Date: Wed, 20 Aug 2025 16:54:27 -0500 Subject: [PATCH] feat: implement absolute positioning for slide editor with viewport-aware layout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Major Improvements: ### Absolute Positioning System - Implemented fixed positioning for slide editor to break out of DOM flow - Editor uses full viewport (top: 80px; left: 0; right: 0; bottom: 0) - Fixed header at top with z-index layering for proper overlap - Background overlay to separate from underlying content ### Viewport-Optimized Layout - True 50/50 split using full available viewport width - Height calculations based on actual viewport (100vh) not container constraints - Responsive top positioning (80px desktop, 60px tablet, 50px mobile) - No scrolling issues - content always fits within viewport boundaries ### Enhanced Live Preview - Applied presentation aspect ratio classes to preview container - Dynamic aspect ratio detection (16:9, 4:3, 16:10) - Viewport-aware sizing with proper aspect ratio preservation - Added aspect ratio indicator in preview meta information ### Improved User Experience - Compact header design with reduced padding and font sizes - Internal scrolling for content fields while keeping actions visible - Better space utilization with reduced margins and gaps - Consistent behavior across all screen sizes and orientations ### Technical Enhancements - Z-index management for proper layering (header: 20, content: 10) - Flexbox structure for optimal space distribution - Overflow handling for different content types - Mobile-responsive design with appropriate scaling 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/components/presentations/SlideEditor.css | 164 +++++++++++++++---- src/components/presentations/SlideEditor.tsx | 7 +- 2 files changed, 138 insertions(+), 33 deletions(-) diff --git a/src/components/presentations/SlideEditor.css b/src/components/presentations/SlideEditor.css index 9ec0673..00e0580 100644 --- a/src/components/presentations/SlideEditor.css +++ b/src/components/presentations/SlideEditor.css @@ -9,18 +9,24 @@ .slide-editor-header { background: white; border-bottom: 1px solid #e2e8f0; - padding: 1rem 2rem; + padding: 0.75rem 2rem; display: flex; justify-content: space-between; align-items: center; - gap: 2rem; + gap: 1.5rem; flex-wrap: wrap; + position: fixed; + top: 0; + left: 0; + right: 0; + z-index: 20; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } .editor-info { display: flex; align-items: center; - gap: 1.5rem; + gap: 1rem; flex: 1; min-width: 0; } @@ -49,15 +55,15 @@ .editor-title h1 { margin: 0; - font-size: 1.5rem; + font-size: 1.25rem; font-weight: 600; color: #1e293b; } .editor-title p { - margin: 0.25rem 0 0 0; + margin: 0.125rem 0 0 0; color: #64748b; - font-size: 0.875rem; + font-size: 0.75rem; } .editor-actions { @@ -114,12 +120,12 @@ /* Step Header */ .step-header { text-align: center; - margin-bottom: 2rem; + margin-bottom: 1.5rem; } .step-header h2 { - margin: 0 0 0.5rem 0; - font-size: 1.5rem; + margin: 0 0 0.25rem 0; + font-size: 1.25rem; font-weight: 600; color: #1e293b; } @@ -127,13 +133,21 @@ .step-header p { margin: 0; color: #64748b; - font-size: 1rem; + font-size: 0.875rem; } /* Layout Selection */ .layout-selection { - max-width: 1200px; - margin: 0 auto; + position: fixed; + top: 80px; + left: 0; + right: 0; + bottom: 0; + background: #f8fafc; + z-index: 10; + overflow-y: auto; + padding: 2rem; + box-sizing: border-box; } .layouts-grid { @@ -257,28 +271,47 @@ /* Content Editing */ .content-editing { - max-width: 1400px; - margin: 0 auto; + /* Use absolute positioning to break out of all DOM constraints */ + position: fixed; + top: 80px; + left: 0; + right: 0; + bottom: 0; + background: #f8fafc; + z-index: 10; + overflow: hidden; } .editing-layout { display: grid; - grid-template-columns: 1fr 400px; + grid-template-columns: 1fr 1fr; gap: 2rem; - align-items: start; + align-items: stretch; + height: 100%; + width: 100%; + padding: 2rem; + box-sizing: border-box; + max-width: none; } .content-form { background: white; border-radius: 0.75rem; border: 1px solid #e2e8f0; - padding: 2rem; + padding: 1.5rem; + overflow-y: auto; + height: 100%; + display: flex; + flex-direction: column; } .content-fields { display: flex; flex-direction: column; - gap: 1.5rem; + gap: 1rem; + flex: 1; + overflow-y: auto; + min-height: 0; } .content-field { @@ -381,9 +414,10 @@ background: white; border-radius: 0.75rem; border: 1px solid #e2e8f0; - padding: 1.5rem; - position: sticky; - top: 2rem; + padding: 1rem; + height: 100%; + display: flex; + flex-direction: column; } .content-preview h3 { @@ -407,6 +441,9 @@ border-radius: 0.75rem; background: white; overflow: hidden; + flex: 1; + display: flex; + flex-direction: column; } .slide-preview-wrapper { @@ -415,21 +452,41 @@ display: flex; align-items: center; justify-content: center; - min-height: 200px; + flex: 1; position: relative; + /* Ensure this container doesn't exceed available space */ + max-height: 100%; + overflow: hidden; } -.slide-preview { - max-width: 100%; - transform: scale(0.6); - transform-origin: center; - box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); +/* Use the global aspect ratio classes for proper slide display */ +.slide-preview-wrapper .slide-container { + /* Use a width-based approach and let aspect-ratio handle height */ + width: min(80%, 70vw); + max-height: 60vh; + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15); border-radius: 0.5rem; background: white; border: 1px solid #e2e8f0; overflow: hidden; } +/* Override global aspect ratio classes for preview context */ +.slide-preview-wrapper .slide-container.aspect-16-9 { + aspect-ratio: 16 / 9; + width: min(80%, min(70vw, 60vh * (16/9))); +} + +.slide-preview-wrapper .slide-container.aspect-4-3 { + aspect-ratio: 4 / 3; + width: min(80%, min(70vw, 60vh * (4/3))); +} + +.slide-preview-wrapper .slide-container.aspect-16-10 { + aspect-ratio: 16 / 10; + width: min(80%, min(70vw, 60vh * (16/10))); +} + .preview-meta { display: flex; justify-content: space-between; @@ -437,6 +494,8 @@ padding: 0.75rem 1rem; background: #f8fafc; border-top: 1px solid #e2e8f0; + flex-wrap: wrap; + gap: 0.5rem; } .layout-name { @@ -445,6 +504,15 @@ color: #374151; } +.aspect-ratio-info { + font-size: 0.625rem; + color: #3b82f6; + background: #eff6ff; + padding: 0.25rem 0.5rem; + border-radius: 0.25rem; + font-weight: 500; +} + .content-count { font-size: 0.625rem; color: #6b7280; @@ -516,23 +584,57 @@ /* Responsive Design */ @media (max-width: 1024px) { + .content-editing { + top: 60px; + } + + .layout-selection { + top: 60px; + } + .editing-layout { grid-template-columns: 1fr; gap: 1.5rem; + padding: 1rem; } .content-preview { - position: relative; - top: auto; + height: 300px; + min-height: 300px; + } + + .slide-preview-wrapper { + padding: 0.5rem; + } + + /* Adjust viewport calculations for smaller screens */ + .slide-preview-wrapper .slide-container { + width: min(90%, 90vw); + max-height: 250px; + } + + .slide-preview-wrapper .slide-container.aspect-16-9, + .slide-preview-wrapper .slide-container.aspect-4-3, + .slide-preview-wrapper .slide-container.aspect-16-10 { + width: min(90%, min(90vw, 250px * var(--aspect-multiplier, 1.78))); } } @media (max-width: 768px) { - .slide-editor-header { + .content-editing { + top: 50px; + } + + .layout-selection { + top: 50px; padding: 1rem; + } + + .slide-editor-header { + padding: 0.5rem 1rem; flex-direction: column; align-items: stretch; - gap: 1rem; + gap: 0.5rem; } .editor-info { diff --git a/src/components/presentations/SlideEditor.tsx b/src/components/presentations/SlideEditor.tsx index ce334a3..31cd6a2 100644 --- a/src/components/presentations/SlideEditor.tsx +++ b/src/components/presentations/SlideEditor.tsx @@ -304,7 +304,7 @@ export const SlideEditor: React.FC = () => { )} - {currentStep === 'content' && selectedLayout && ( + {currentStep === 'content' && selectedLayout && presentation && (
@@ -389,7 +389,7 @@ export const SlideEditor: React.FC = () => {
{
{selectedLayout.name} + + {presentation.metadata.aspectRatio || '16:9'} aspect ratio + {Object.values(slideContent).filter(v => v.trim()).length} / {selectedLayout.slots.length} slots filled