diff --git a/src/App.tsx b/src/App.tsx index a65ece0..432a350 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import { useRef, useEffect, useCallback } from 'react'; +import { useRef, useEffect, useCallback, useState } from 'react'; import type Konva from 'konva'; import Canvas from './components/Canvas'; import TopBar from './components/TopBar'; @@ -6,10 +6,12 @@ import Toolbar from './components/Toolbar'; import Inspector from './components/Inspector'; import LayersPanel from './components/LayersPanel'; import FontLoader from './components/FontLoader'; +import MainScreen from './components/MainScreen'; import { useCanvasStore } from './store/canvasStore'; import { useRecentSnapsStore } from './store/recentSnapsStore'; function App() { + const [showMainScreen, setShowMainScreen] = useState(true); const stageRef = useRef(null); const { snap, @@ -79,6 +81,14 @@ function App() { } }, [snap, addRecentSnap, newSnap]); + // Handle going back to main screen + const handleGoToMainScreen = useCallback(() => { + if (snap.elements.length > 0) { + addRecentSnap(snap); + } + setShowMainScreen(true); + }, [snap, addRecentSnap]); + // Keyboard shortcuts useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { @@ -178,10 +188,20 @@ function App() { return () => window.removeEventListener('keydown', handleKeyDown); }, [selectedElementId, deleteElement, duplicateElement, undo, redo, zoom, setZoom, showGrid, setShowGrid, setTool, selectElement, handleNewSnap, handleImportFile, handleExportFile]); + // Show main screen + if (showMainScreen) { + return ( + <> + + setShowMainScreen(false)} /> + + ); + } + return (
- +
diff --git a/src/components/MainScreen.tsx b/src/components/MainScreen.tsx new file mode 100644 index 0000000..15d834d --- /dev/null +++ b/src/components/MainScreen.tsx @@ -0,0 +1,277 @@ +import { useState } from 'react'; +import { Plus, FileText, Layout, Clock, Trash2, ChevronRight, Sparkles } from 'lucide-react'; +import { useRecentSnapsStore, formatRelativeTime } from '../store/recentSnapsStore'; +import { useCanvasStore } from '../store/canvasStore'; +import { templates, type Template } from '../data/templates'; +import SnapPreview from './SnapPreview'; +import type { Snap } from '../types'; + +interface MainScreenProps { + onOpenEditor: () => void; +} + +export default function MainScreen({ onOpenEditor }: MainScreenProps) { + const [activeTab, setActiveTab] = useState<'recent' | 'templates'>('recent'); + const { recentSnaps, removeRecentSnap, clearRecentSnaps } = useRecentSnapsStore(); + const { setSnap, newSnap } = useCanvasStore(); + + const handleNewSnap = () => { + newSnap({ title: 'Untitled', aspect: '16:9', width: 1920, height: 1080 }); + onOpenEditor(); + }; + + const handleOpenRecent = (snap: Snap) => { + setSnap(snap); + onOpenEditor(); + }; + + const handleUseTemplate = (template: Template) => { + setSnap(template.snap); + onOpenEditor(); + }; + + const handleImportFile = () => { + const input = document.createElement('input'); + input.type = 'file'; + input.accept = '.json'; + input.onchange = (e) => { + const file = (e.target as HTMLInputElement).files?.[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (evt) => { + try { + const json = evt.target?.result as string; + const snap = JSON.parse(json) as Snap; + setSnap(snap); + onOpenEditor(); + } catch { + alert('Invalid file format'); + } + }; + reader.readAsText(file); + } + }; + input.click(); + }; + + return ( +
+ {/* Header */} +
+
+
+
+ + + +
+
+

YvCode

+

Create beautiful code screenshots

+
+
+
+
+ + {/* Main Content */} +
+ {/* Create New Section */} +
+

Create New

+
+ {/* New Blank */} + + + {/* Import File */} + + + {/* Quick Template */} + +
+
+ + {/* Tabs */} +
+ + +
+ + {/* Tab Content */} + {activeTab === 'recent' && ( +
+ {recentSnaps.length === 0 ? ( +
+
+ +
+

No recent work

+

Your recent projects will appear here

+ +
+ ) : ( + <> +
+

{recentSnaps.length} recent project{recentSnaps.length !== 1 ? 's' : ''}

+ +
+
+ {recentSnaps.map((entry) => ( +
+ {/* Preview */} + + + {/* Info */} +
+
+
+

{entry.title}

+

{formatRelativeTime(entry.savedAt)}

+
+ +
+
+ {entry.snap.meta.width}×{entry.snap.meta.height} + + {entry.snap.elements.length} element{entry.snap.elements.length !== 1 ? 's' : ''} +
+
+
+ ))} +
+ + )} +
+ )} + + {activeTab === 'templates' && ( +
+

{templates.length} templates available

+
+ {templates.map((template) => ( +
+ {/* Preview */} + + + {/* Info */} +
+

{template.name}

+

{template.description}

+
+ {template.snap.meta.width}×{template.snap.meta.height} + + {template.snap.elements.length} element{template.snap.elements.length !== 1 ? 's' : ''} +
+
+
+ ))} +
+
+ )} +
+ + {/* Footer */} +
+
+

+ YvCode — Create beautiful code screenshots for social media +

+
+
+
+ ); +} diff --git a/src/components/SnapPreview.tsx b/src/components/SnapPreview.tsx new file mode 100644 index 0000000..a71d659 --- /dev/null +++ b/src/components/SnapPreview.tsx @@ -0,0 +1,222 @@ +import type { Snap, CanvasElement, CodeElement, TextElement, ArrowElement } from '../types'; + +interface SnapPreviewProps { + snap: Snap; +} + +// Helper to get code theme background color +function getCodeThemeBg(theme: string): string { + const themes: Record = { + 'andromeeda': '#23262e', + 'aurora-x': '#07090f', + 'dark-plus': '#1e1e1e', + 'dracula': '#282a36', + 'dracula-soft': '#282a36', + 'github-dark': '#0d1117', + 'github-dark-dimmed': '#22272e', + 'github-light': '#ffffff', + 'light-plus': '#ffffff', + 'material-theme-darker': '#212121', + 'material-theme-ocean': '#0f111a', + 'material-theme-palenight': '#292d3e', + 'min-dark': '#1f1f1f', + 'min-light': '#ffffff', + 'monokai': '#272822', + 'night-owl': '#011627', + 'nord': '#2e3440', + 'one-dark-pro': '#282c34', + 'poimandres': '#1b1e28', + 'rose-pine': '#191724', + 'rose-pine-moon': '#232136', + 'slack-dark': '#222222', + 'solarized-dark': '#002b36', + 'solarized-light': '#fdf6e3', + 'tokyo-night': '#1a1b26', + 'vesper': '#101010', + 'vitesse-dark': '#121212', + 'vitesse-light': '#ffffff', + }; + return themes[theme] || '#1e1e1e'; +} + +// Helper to get highlight color for code lines +function getHighlightColor(lineNum: number, highlights: { from: number; to: number; style: string }[]): string | null { + for (const h of highlights) { + if (lineNum >= h.from && lineNum <= h.to) { + switch (h.style) { + case 'focus': return 'rgba(251, 191, 36, 0.4)'; + case 'added': return 'rgba(34, 197, 94, 0.4)'; + case 'removed': return 'rgba(239, 68, 68, 0.4)'; + } + } + } + return null; +} + +export default function SnapPreview({ snap }: SnapPreviewProps) { + const { meta, background, elements } = snap; + const scaleX = 1 / (meta.width / 400); // Scale to fit ~400px width + const scaleY = 1 / (meta.height / 225); // Scale to fit ~225px height (16:9 aspect) + const scale = Math.min(scaleX, scaleY); + + const getBackground = () => { + if (background.type === 'gradient') { + return `linear-gradient(${background.gradient.angle}deg, ${background.gradient.from}, ${background.gradient.to})`; + } + return background.solid.color; + }; + + const renderElement = (element: CanvasElement) => { + if (!element.visible) return null; + + switch (element.type) { + case 'code': { + const codeEl = element as CodeElement; + return ( +
0 + ? `0 ${codeEl.props.shadow.blur * scale * 0.5}px ${codeEl.props.shadow.blur * scale}px ${codeEl.props.shadow.color}` + : undefined, + }} + > + {/* Code lines preview */} +
+ {codeEl.props.code.split('\n').slice(0, 8).map((line, i) => ( +
+ {codeEl.props.lineNumbers && ( +
+ )} +
+
+ ))} +
+
+ ); + } + + case 'text': { + const textEl = element as TextElement; + const fontSize = Math.max(6, textEl.props.fontSize * scale); + return ( +
+ {textEl.props.text.length > 50 ? textEl.props.text.slice(0, 50) + '...' : textEl.props.text} +
+ ); + } + + case 'arrow': { + const arrowEl = element as ArrowElement; + if (arrowEl.points.length < 2) return null; + const start = arrowEl.points[0]; + const end = arrowEl.points[arrowEl.points.length - 1]; + + // Calculate SVG bounds + const minX = Math.min(start.x, end.x) - 10; + const minY = Math.min(start.y, end.y) - 10; + const maxX = Math.max(start.x, end.x) + 10; + const maxY = Math.max(start.y, end.y) + 10; + + return ( + + + + + + + + + ); + } + + default: + return null; + } + }; + + return ( +
+ {elements.map(renderElement)} +
+ ); +} diff --git a/src/components/TopBar.tsx b/src/components/TopBar.tsx index 3bb161f..bc4ffa9 100644 --- a/src/components/TopBar.tsx +++ b/src/components/TopBar.tsx @@ -7,9 +7,10 @@ import RecentSnapsDropdown from './RecentSnapsDropdown'; interface TopBarProps { stageRef: React.RefObject; + onGoHome?: () => void; } -const TopBar: React.FC = ({ stageRef }) => { +const TopBar: React.FC = ({ stageRef, onGoHome }) => { const [showRecentSnaps, setShowRecentSnaps] = useState(false); const [showExportMenu, setShowExportMenu] = useState(false); const recentButtonRef = useRef(null); @@ -138,11 +139,15 @@ const TopBar: React.FC = ({ stageRef }) => { {/* Left section: Logo & Actions */}
-
+
+ YvCode diff --git a/src/data/templates.ts b/src/data/templates.ts new file mode 100644 index 0000000..56271a0 --- /dev/null +++ b/src/data/templates.ts @@ -0,0 +1,1168 @@ +import type { Snap } from '../types'; + +export interface Template { + id: string; + name: string; + description: string; + preview: string; // gradient colors (kept for fallback) + snap: Snap; +} + +export const templates: Template[] = [ + { + id: 'code-snippet', + name: 'Code Snippet', + description: 'Clean code presentation with gradient background', + preview: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', + snap: { + version: '1.0.0', + meta: { title: 'Code Snippet', aspect: '16:9', width: 1920, height: 1080 }, + background: { + type: 'gradient', + solid: { color: '#667eea' }, + gradient: { from: '#667eea', to: '#764ba2', angle: 135 }, + brandStrip: { enabled: false, position: 'bottom', height: 60, color: '#000000', text: '', textColor: '#ffffff', fontSize: 16, fontFamily: 'Inter' }, + branding: { enabled: false, position: 'bottom-right', name: '', website: '', social: {}, showName: true, showWebsite: true, showSocial: true, showAvatar: false, fontSize: 14, fontFamily: 'Inter', color: '#ffffff', opacity: 0.8, padding: 24, socialIconSize: 20, socialLayout: 'horizontal', avatarSize: 56 }, + }, + elements: [ + { + id: 'code-1', + type: 'code', + x: 460, + y: 240, + rotation: 0, + locked: false, + visible: true, + width: 1000, + height: 600, + props: { + code: `function greet(name: string): string { + return \`Hello, \${name}! 👋\`; +} + +const message = greet("World"); +console.log(message);`, + language: 'typescript', + theme: 'one-dark-pro', + fontFamily: 'JetBrains Mono', + fontSize: 24, + lineHeight: 1.6, + lineNumbers: true, + highlights: [], + padding: 40, + cornerRadius: 16, + shadow: { blur: 40, spread: 0, color: 'rgba(0,0,0,0.4)' }, + }, + }, + ], + }, + }, + { + id: 'code-with-title', + name: 'Code with Title', + description: 'Code block with a descriptive title header', + preview: 'linear-gradient(135deg, #0f0f0f 0%, #1a1a2e 100%)', + snap: { + version: '1.0.0', + meta: { title: 'Code with Title', aspect: '16:9', width: 1920, height: 1080 }, + background: { + type: 'gradient', + solid: { color: '#0f0f0f' }, + gradient: { from: '#0f0f0f', to: '#1a1a2e', angle: 135 }, + brandStrip: { enabled: false, position: 'bottom', height: 60, color: '#000000', text: '', textColor: '#ffffff', fontSize: 16, fontFamily: 'Inter' }, + branding: { enabled: false, position: 'bottom-right', name: '', website: '', social: {}, showName: true, showWebsite: true, showSocial: true, showAvatar: false, fontSize: 14, fontFamily: 'Inter', color: '#ffffff', opacity: 0.8, padding: 24, socialIconSize: 20, socialLayout: 'horizontal', avatarSize: 56 }, + }, + elements: [ + { + id: 'title-1', + type: 'text', + x: 460, + y: 120, + rotation: 0, + locked: false, + visible: true, + props: { + text: '🚀 Quick Tip: Array Methods', + fontFamily: 'Inter', + fontSize: 48, + color: '#ffffff', + bold: true, + italic: false, + underline: false, + align: 'left', + background: null, + padding: 0, + cornerRadius: 0, + }, + }, + { + id: 'code-1', + type: 'code', + x: 460, + y: 220, + rotation: 0, + locked: false, + visible: true, + width: 1000, + height: 500, + props: { + code: `const numbers = [1, 2, 3, 4, 5]; + +// Map: Transform each element +const doubled = numbers.map(n => n * 2); +// [2, 4, 6, 8, 10] + +// Filter: Keep matching elements +const evens = numbers.filter(n => n % 2 === 0); +// [2, 4] + +// Reduce: Combine into single value +const sum = numbers.reduce((a, b) => a + b, 0); +// 15`, + language: 'javascript', + theme: 'tokyo-night', + fontFamily: 'JetBrains Mono', + fontSize: 20, + lineHeight: 1.5, + lineNumbers: true, + highlights: [{ from: 3, to: 4, style: 'focus' }, { from: 7, to: 8, style: 'focus' }, { from: 11, to: 12, style: 'focus' }], + padding: 32, + cornerRadius: 16, + shadow: { blur: 30, spread: 0, color: 'rgba(0,0,0,0.5)' }, + }, + }, + { + id: 'subtitle-1', + type: 'text', + x: 460, + y: 780, + rotation: 0, + locked: false, + visible: true, + props: { + text: '@yourhandle', + fontFamily: 'Inter', + fontSize: 24, + color: '#6b7280', + bold: false, + italic: false, + underline: false, + align: 'left', + background: null, + padding: 0, + cornerRadius: 0, + }, + }, + ], + }, + }, + { + id: 'before-after', + name: 'Before & After', + description: 'Compare two code approaches side by side', + preview: 'linear-gradient(135deg, #1e3a5f 0%, #0d1b2a 100%)', + snap: { + version: '1.0.0', + meta: { title: 'Before After', aspect: '16:9', width: 1920, height: 1080 }, + background: { + type: 'gradient', + solid: { color: '#1e3a5f' }, + gradient: { from: '#1e3a5f', to: '#0d1b2a', angle: 135 }, + brandStrip: { enabled: false, position: 'bottom', height: 60, color: '#000000', text: '', textColor: '#ffffff', fontSize: 16, fontFamily: 'Inter' }, + branding: { enabled: false, position: 'bottom-right', name: '', website: '', social: {}, showName: true, showWebsite: true, showSocial: true, showAvatar: false, fontSize: 14, fontFamily: 'Inter', color: '#ffffff', opacity: 0.8, padding: 24, socialIconSize: 20, socialLayout: 'horizontal', avatarSize: 56 }, + }, + elements: [ + { + id: 'before-label', + type: 'text', + x: 160, + y: 80, + rotation: 0, + locked: false, + visible: true, + props: { + text: '❌ Before', + fontFamily: 'Inter', + fontSize: 32, + color: '#ef4444', + bold: true, + italic: false, + underline: false, + align: 'left', + background: null, + padding: 0, + cornerRadius: 0, + }, + }, + { + id: 'code-before', + type: 'code', + x: 100, + y: 150, + rotation: 0, + locked: false, + visible: true, + width: 800, + height: 400, + props: { + code: `// Callback hell 😱 +getData(function(a) { + getMoreData(a, function(b) { + getEvenMore(b, function(c) { + getFinal(c, function(d) { + console.log(d); + }); + }); + }); +});`, + language: 'javascript', + theme: 'material-theme-darker', + fontFamily: 'JetBrains Mono', + fontSize: 18, + lineHeight: 1.5, + lineNumbers: false, + highlights: [], + padding: 28, + cornerRadius: 12, + shadow: { blur: 20, spread: 0, color: 'rgba(0,0,0,0.3)' }, + }, + }, + { + id: 'after-label', + type: 'text', + x: 1040, + y: 80, + rotation: 0, + locked: false, + visible: true, + props: { + text: '✅ After', + fontFamily: 'Inter', + fontSize: 32, + color: '#22c55e', + bold: true, + italic: false, + underline: false, + align: 'left', + background: null, + padding: 0, + cornerRadius: 0, + }, + }, + { + id: 'code-after', + type: 'code', + x: 980, + y: 150, + rotation: 0, + locked: false, + visible: true, + width: 800, + height: 400, + props: { + code: `// Clean async/await 🎉 +async function fetchAll() { + const a = await getData(); + const b = await getMoreData(a); + const c = await getEvenMore(b); + const d = await getFinal(c); + + console.log(d); +}`, + language: 'javascript', + theme: 'material-theme-darker', + fontFamily: 'JetBrains Mono', + fontSize: 18, + lineHeight: 1.5, + lineNumbers: false, + highlights: [], + padding: 28, + cornerRadius: 12, + shadow: { blur: 20, spread: 0, color: 'rgba(0,0,0,0.3)' }, + }, + }, + { + id: 'arrow-1', + type: 'arrow', + x: 0, + y: 0, + rotation: 0, + locked: false, + visible: true, + points: [{ x: 920, y: 350 }, { x: 970, y: 350 }], + props: { + style: 'straight', + color: '#fbbf24', + thickness: 4, + head: 'filled', + }, + }, + { + id: 'tip-text', + type: 'text', + x: 100, + y: 620, + rotation: 0, + locked: false, + visible: true, + props: { + text: '💡 Pro tip: async/await makes asynchronous code much more readable!', + fontFamily: 'Inter', + fontSize: 24, + color: '#94a3b8', + bold: false, + italic: true, + underline: false, + align: 'left', + background: null, + padding: 0, + cornerRadius: 0, + }, + }, + ], + }, + }, + { + id: 'code-explanation', + name: 'Code Explanation', + description: 'Code with annotations and arrows pointing to key parts', + preview: 'linear-gradient(135deg, #2d1b4e 0%, #1a0a2e 100%)', + snap: { + version: '1.0.0', + meta: { title: 'Code Explanation', aspect: '16:9', width: 1920, height: 1080 }, + background: { + type: 'gradient', + solid: { color: '#2d1b4e' }, + gradient: { from: '#2d1b4e', to: '#1a0a2e', angle: 135 }, + brandStrip: { enabled: false, position: 'bottom', height: 60, color: '#000000', text: '', textColor: '#ffffff', fontSize: 16, fontFamily: 'Inter' }, + branding: { enabled: false, position: 'bottom-right', name: '', website: '', social: {}, showName: true, showWebsite: true, showSocial: true, showAvatar: false, fontSize: 14, fontFamily: 'Inter', color: '#ffffff', opacity: 0.8, padding: 24, socialIconSize: 20, socialLayout: 'horizontal', avatarSize: 56 }, + }, + elements: [ + { + id: 'main-title', + type: 'text', + x: 100, + y: 60, + rotation: 0, + locked: false, + visible: true, + props: { + text: 'React useEffect Explained', + fontFamily: 'Inter', + fontSize: 42, + color: '#ffffff', + bold: true, + italic: false, + underline: false, + align: 'left', + background: null, + padding: 0, + cornerRadius: 0, + }, + }, + { + id: 'code-main', + type: 'code', + x: 100, + y: 140, + rotation: 0, + locked: false, + visible: true, + width: 900, + height: 500, + props: { + code: `useEffect(() => { + // Effect code runs here + document.title = \`Count: \${count}\`; + + return () => { + // Cleanup function + console.log('Cleaning up...'); + }; +}, [count]); // Dependencies`, + language: 'javascript', + theme: 'dracula', + fontFamily: 'JetBrains Mono', + fontSize: 22, + lineHeight: 1.6, + lineNumbers: true, + highlights: [{ from: 1, to: 1, style: 'focus' }, { from: 5, to: 7, style: 'added' }, { from: 8, to: 8, style: 'focus' }], + padding: 32, + cornerRadius: 16, + shadow: { blur: 30, spread: 0, color: 'rgba(0,0,0,0.4)' }, + }, + }, + { + id: 'annotation-1', + type: 'text', + x: 1100, + y: 180, + rotation: 0, + locked: false, + visible: true, + props: { + text: '1️⃣ Hook that runs\nside effects', + fontFamily: 'Inter', + fontSize: 20, + color: '#a78bfa', + bold: false, + italic: false, + underline: false, + align: 'left', + background: { color: 'rgba(139, 92, 246, 0.15)' }, + padding: 16, + cornerRadius: 8, + }, + }, + { + id: 'arrow-1', + type: 'arrow', + x: 0, + y: 0, + rotation: 0, + locked: false, + visible: true, + points: [{ x: 1090, y: 200 }, { x: 1020, y: 180 }], + props: { + style: 'straight', + color: '#a78bfa', + thickness: 2, + head: 'filled', + }, + }, + { + id: 'annotation-2', + type: 'text', + x: 1100, + y: 340, + rotation: 0, + locked: false, + visible: true, + props: { + text: '2️⃣ Cleanup runs\nbefore next effect', + fontFamily: 'Inter', + fontSize: 20, + color: '#4ade80', + bold: false, + italic: false, + underline: false, + align: 'left', + background: { color: 'rgba(74, 222, 128, 0.15)' }, + padding: 16, + cornerRadius: 8, + }, + }, + { + id: 'arrow-2', + type: 'arrow', + x: 0, + y: 0, + rotation: 0, + locked: false, + visible: true, + points: [{ x: 1090, y: 370 }, { x: 1020, y: 380 }], + props: { + style: 'straight', + color: '#4ade80', + thickness: 2, + head: 'filled', + }, + }, + { + id: 'annotation-3', + type: 'text', + x: 1100, + y: 500, + rotation: 0, + locked: false, + visible: true, + props: { + text: '3️⃣ Re-run when\nthese change', + fontFamily: 'Inter', + fontSize: 20, + color: '#fbbf24', + bold: false, + italic: false, + underline: false, + align: 'left', + background: { color: 'rgba(251, 191, 36, 0.15)' }, + padding: 16, + cornerRadius: 8, + }, + }, + { + id: 'arrow-3', + type: 'arrow', + x: 0, + y: 0, + rotation: 0, + locked: false, + visible: true, + points: [{ x: 1090, y: 530 }, { x: 1020, y: 530 }], + props: { + style: 'straight', + color: '#fbbf24', + thickness: 2, + head: 'filled', + }, + }, + ], + }, + }, + { + id: 'social-tip', + name: 'Social Tip Card', + description: 'Eye-catching tip card perfect for Instagram', + preview: 'linear-gradient(135deg, #8e2de2 0%, #4a00e0 100%)', + snap: { + version: '1.0.0', + meta: { title: 'Social Tip', aspect: '1:1', width: 1080, height: 1080 }, + background: { + type: 'gradient', + solid: { color: '#8e2de2' }, + gradient: { from: '#8e2de2', to: '#4a00e0', angle: 135 }, + brandStrip: { enabled: false, position: 'bottom', height: 60, color: '#000000', text: '', textColor: '#ffffff', fontSize: 16, fontFamily: 'Inter' }, + branding: { enabled: false, position: 'bottom-right', name: '', website: '', social: {}, showName: true, showWebsite: true, showSocial: true, showAvatar: false, fontSize: 14, fontFamily: 'Inter', color: '#ffffff', opacity: 0.8, padding: 24, socialIconSize: 20, socialLayout: 'horizontal', avatarSize: 56 }, + }, + elements: [ + { + id: 'emoji-badge', + type: 'text', + x: 440, + y: 80, + rotation: 0, + locked: false, + visible: true, + props: { + text: '💡', + fontFamily: 'Inter', + fontSize: 80, + color: '#ffffff', + bold: false, + italic: false, + underline: false, + align: 'center', + background: null, + padding: 0, + cornerRadius: 0, + }, + }, + { + id: 'tip-title', + type: 'text', + x: 100, + y: 200, + rotation: 0, + locked: false, + visible: true, + props: { + text: 'Did you know?', + fontFamily: 'Inter', + fontSize: 48, + color: '#ffffff', + bold: true, + italic: false, + underline: false, + align: 'center', + background: null, + padding: 0, + cornerRadius: 0, + }, + }, + { + id: 'code-tip', + type: 'code', + x: 90, + y: 300, + rotation: 0, + locked: false, + visible: true, + width: 900, + height: 320, + props: { + code: `// Nullish coalescing (??) vs OR (||) + +const value = 0; + +value || 'default' // 'default' ❌ +value ?? 'default' // 0 ✅ + +// ?? only checks null/undefined`, + language: 'javascript', + theme: 'night-owl', + fontFamily: 'JetBrains Mono', + fontSize: 20, + lineHeight: 1.5, + lineNumbers: false, + highlights: [{ from: 5, to: 5, style: 'removed' }, { from: 6, to: 6, style: 'added' }], + padding: 28, + cornerRadius: 16, + shadow: { blur: 30, spread: 0, color: 'rgba(0,0,0,0.3)' }, + }, + }, + { + id: 'tip-explanation', + type: 'text', + x: 90, + y: 680, + rotation: 0, + locked: false, + visible: true, + props: { + text: '?? treats 0 and "" as valid values,\nwhile || treats them as falsy!', + fontFamily: 'Inter', + fontSize: 28, + color: '#e0e0e0', + bold: false, + italic: false, + underline: false, + align: 'center', + background: null, + padding: 0, + cornerRadius: 0, + }, + }, + { + id: 'save-cta', + type: 'text', + x: 340, + y: 820, + rotation: 0, + locked: false, + visible: true, + props: { + text: '💾 Save for later!', + fontFamily: 'Inter', + fontSize: 24, + color: '#ffffff', + bold: true, + italic: false, + underline: false, + align: 'center', + background: { color: 'rgba(255,255,255,0.2)' }, + padding: 16, + cornerRadius: 24, + }, + }, + ], + }, + }, + { + id: 'code-flow', + name: 'Code Flow', + description: 'Show code execution flow with arrows', + preview: 'linear-gradient(135deg, #134e4a 0%, #0f172a 100%)', + snap: { + version: '1.0.0', + meta: { title: 'Code Flow', aspect: '16:9', width: 1920, height: 1080 }, + background: { + type: 'gradient', + solid: { color: '#134e4a' }, + gradient: { from: '#134e4a', to: '#0f172a', angle: 135 }, + brandStrip: { enabled: false, position: 'bottom', height: 60, color: '#000000', text: '', textColor: '#ffffff', fontSize: 16, fontFamily: 'Inter' }, + branding: { enabled: false, position: 'bottom-right', name: '', website: '', social: {}, showName: true, showWebsite: true, showSocial: true, showAvatar: false, fontSize: 14, fontFamily: 'Inter', color: '#ffffff', opacity: 0.8, padding: 24, socialIconSize: 20, socialLayout: 'horizontal', avatarSize: 56 }, + }, + elements: [ + { + id: 'title', + type: 'text', + x: 100, + y: 50, + rotation: 0, + locked: false, + visible: true, + props: { + text: 'API Request Flow 🔄', + fontFamily: 'Inter', + fontSize: 40, + color: '#ffffff', + bold: true, + italic: false, + underline: false, + align: 'left', + background: null, + padding: 0, + cornerRadius: 0, + }, + }, + { + id: 'step-1-label', + type: 'text', + x: 100, + y: 140, + rotation: 0, + locked: false, + visible: true, + props: { + text: 'Step 1: Make Request', + fontFamily: 'Inter', + fontSize: 20, + color: '#5eead4', + bold: true, + italic: false, + underline: false, + align: 'left', + background: null, + padding: 0, + cornerRadius: 0, + }, + }, + { + id: 'code-1', + type: 'code', + x: 100, + y: 180, + rotation: 0, + locked: false, + visible: true, + width: 500, + height: 200, + props: { + code: `const response = await fetch( + '/api/users' +);`, + language: 'javascript', + theme: 'material-theme-ocean', + fontFamily: 'JetBrains Mono', + fontSize: 18, + lineHeight: 1.5, + lineNumbers: false, + highlights: [], + padding: 24, + cornerRadius: 12, + shadow: { blur: 20, spread: 0, color: 'rgba(0,0,0,0.3)' }, + }, + }, + { + id: 'arrow-1-2', + type: 'arrow', + x: 0, + y: 0, + rotation: 0, + locked: false, + visible: true, + points: [{ x: 350, y: 400 }, { x: 350, y: 480 }], + props: { + style: 'straight', + color: '#5eead4', + thickness: 3, + head: 'filled', + }, + }, + { + id: 'step-2-label', + type: 'text', + x: 100, + y: 500, + rotation: 0, + locked: false, + visible: true, + props: { + text: 'Step 2: Check Response', + fontFamily: 'Inter', + fontSize: 20, + color: '#fbbf24', + bold: true, + italic: false, + underline: false, + align: 'left', + background: null, + padding: 0, + cornerRadius: 0, + }, + }, + { + id: 'code-2', + type: 'code', + x: 100, + y: 540, + rotation: 0, + locked: false, + visible: true, + width: 500, + height: 200, + props: { + code: `if (!response.ok) { + throw new Error('Failed'); +}`, + language: 'javascript', + theme: 'material-theme-ocean', + fontFamily: 'JetBrains Mono', + fontSize: 18, + lineHeight: 1.5, + lineNumbers: false, + highlights: [], + padding: 24, + cornerRadius: 12, + shadow: { blur: 20, spread: 0, color: 'rgba(0,0,0,0.3)' }, + }, + }, + { + id: 'arrow-2-3', + type: 'arrow', + x: 0, + y: 0, + rotation: 0, + locked: false, + visible: true, + points: [{ x: 620, y: 640 }, { x: 700, y: 640 }], + props: { + style: 'straight', + color: '#5eead4', + thickness: 3, + head: 'filled', + }, + }, + { + id: 'step-3-label', + type: 'text', + x: 720, + y: 140, + rotation: 0, + locked: false, + visible: true, + props: { + text: 'Step 3: Parse Data', + fontFamily: 'Inter', + fontSize: 20, + color: '#a78bfa', + bold: true, + italic: false, + underline: false, + align: 'left', + background: null, + padding: 0, + cornerRadius: 0, + }, + }, + { + id: 'code-3', + type: 'code', + x: 720, + y: 180, + rotation: 0, + locked: false, + visible: true, + width: 500, + height: 200, + props: { + code: `const data = await + response.json();`, + language: 'javascript', + theme: 'material-theme-ocean', + fontFamily: 'JetBrains Mono', + fontSize: 18, + lineHeight: 1.5, + lineNumbers: false, + highlights: [], + padding: 24, + cornerRadius: 12, + shadow: { blur: 20, spread: 0, color: 'rgba(0,0,0,0.3)' }, + }, + }, + { + id: 'arrow-3-4', + type: 'arrow', + x: 0, + y: 0, + rotation: 0, + locked: false, + visible: true, + points: [{ x: 970, y: 400 }, { x: 970, y: 480 }], + props: { + style: 'straight', + color: '#5eead4', + thickness: 3, + head: 'filled', + }, + }, + { + id: 'step-4-label', + type: 'text', + x: 720, + y: 500, + rotation: 0, + locked: false, + visible: true, + props: { + text: 'Step 4: Return Result', + fontFamily: 'Inter', + fontSize: 20, + color: '#f472b6', + bold: true, + italic: false, + underline: false, + align: 'left', + background: null, + padding: 0, + cornerRadius: 0, + }, + }, + { + id: 'code-4', + type: 'code', + x: 720, + y: 540, + rotation: 0, + locked: false, + visible: true, + width: 500, + height: 200, + props: { + code: `return { + users: data, + count: data.length +};`, + language: 'javascript', + theme: 'material-theme-ocean', + fontFamily: 'JetBrains Mono', + fontSize: 18, + lineHeight: 1.5, + lineNumbers: false, + highlights: [], + padding: 24, + cornerRadius: 12, + shadow: { blur: 20, spread: 0, color: 'rgba(0,0,0,0.3)' }, + }, + }, + { + id: 'complete-badge', + type: 'text', + x: 1340, + y: 140, + rotation: 0, + locked: false, + visible: true, + props: { + text: '✅ Complete!', + fontFamily: 'Inter', + fontSize: 24, + color: '#22c55e', + bold: true, + italic: false, + underline: false, + align: 'center', + background: { color: 'rgba(34, 197, 94, 0.15)' }, + padding: 16, + cornerRadius: 12, + }, + }, + ], + }, + }, + { + id: 'linkedin-carousel', + name: 'LinkedIn Carousel', + description: 'Perfect dimensions for LinkedIn carousel posts', + preview: 'linear-gradient(135deg, #0077b5 0%, #00a0dc 100%)', + snap: { + version: '1.0.0', + meta: { title: 'LinkedIn Carousel', aspect: 'linkedin', width: 1200, height: 627 }, + background: { + type: 'gradient', + solid: { color: '#0077b5' }, + gradient: { from: '#0a192f', to: '#112240', angle: 135 }, + brandStrip: { enabled: false, position: 'bottom', height: 60, color: '#000000', text: '', textColor: '#ffffff', fontSize: 16, fontFamily: 'Inter' }, + branding: { enabled: false, position: 'bottom-right', name: '', website: '', social: {}, showName: true, showWebsite: true, showSocial: true, showAvatar: false, fontSize: 14, fontFamily: 'Inter', color: '#ffffff', opacity: 0.8, padding: 24, socialIconSize: 20, socialLayout: 'horizontal', avatarSize: 56 }, + }, + elements: [ + { + id: 'slide-number', + type: 'text', + x: 60, + y: 40, + rotation: 0, + locked: false, + visible: true, + props: { + text: '01', + fontFamily: 'Inter', + fontSize: 72, + color: '#64ffda', + bold: true, + italic: false, + underline: false, + align: 'left', + background: null, + padding: 0, + cornerRadius: 0, + }, + }, + { + id: 'slide-title', + type: 'text', + x: 60, + y: 130, + rotation: 0, + locked: false, + visible: true, + props: { + text: 'Clean Code Principle', + fontFamily: 'Inter', + fontSize: 36, + color: '#ffffff', + bold: true, + italic: false, + underline: false, + align: 'left', + background: null, + padding: 0, + cornerRadius: 0, + }, + }, + { + id: 'slide-subtitle', + type: 'text', + x: 60, + y: 185, + rotation: 0, + locked: false, + visible: true, + props: { + text: 'Use meaningful variable names', + fontFamily: 'Inter', + fontSize: 24, + color: '#8892b0', + bold: false, + italic: false, + underline: false, + align: 'left', + background: null, + padding: 0, + cornerRadius: 0, + }, + }, + { + id: 'code-example', + type: 'code', + x: 60, + y: 250, + rotation: 0, + locked: false, + visible: true, + width: 1080, + height: 280, + props: { + code: `// ❌ Bad +const d = new Date(); +const x = d.getTime(); + +// ✅ Good +const currentDate = new Date(); +const timestamp = currentDate.getTime();`, + language: 'javascript', + theme: 'one-dark-pro', + fontFamily: 'JetBrains Mono', + fontSize: 18, + lineHeight: 1.5, + lineNumbers: false, + highlights: [{ from: 1, to: 3, style: 'removed' }, { from: 5, to: 7, style: 'added' }], + padding: 24, + cornerRadius: 12, + shadow: { blur: 20, spread: 0, color: 'rgba(0,0,0,0.3)' }, + }, + }, + { + id: 'swipe-cta', + type: 'text', + x: 1000, + y: 560, + rotation: 0, + locked: false, + visible: true, + props: { + text: 'Swipe →', + fontFamily: 'Inter', + fontSize: 18, + color: '#64ffda', + bold: true, + italic: false, + underline: false, + align: 'right', + background: null, + padding: 0, + cornerRadius: 0, + }, + }, + ], + }, + }, + { + id: 'terminal-style', + name: 'Terminal Style', + description: 'Classic terminal look with command output', + preview: 'linear-gradient(135deg, #000000 0%, #1a1a1a 100%)', + snap: { + version: '1.0.0', + meta: { title: 'Terminal Style', aspect: '16:9', width: 1920, height: 1080 }, + background: { + type: 'solid', + solid: { color: '#0a0a0a' }, + gradient: { from: '#0a0a0a', to: '#1a1a1a', angle: 135 }, + brandStrip: { enabled: false, position: 'bottom', height: 60, color: '#000000', text: '', textColor: '#ffffff', fontSize: 16, fontFamily: 'Inter' }, + branding: { enabled: false, position: 'bottom-right', name: '', website: '', social: {}, showName: true, showWebsite: true, showSocial: true, showAvatar: false, fontSize: 14, fontFamily: 'Inter', color: '#ffffff', opacity: 0.8, padding: 24, socialIconSize: 20, socialLayout: 'horizontal', avatarSize: 56 }, + }, + elements: [ + { + id: 'terminal-title', + type: 'text', + x: 360, + y: 80, + rotation: 0, + locked: false, + visible: true, + props: { + text: '⚡ Getting Started with npm', + fontFamily: 'Inter', + fontSize: 42, + color: '#22c55e', + bold: true, + italic: false, + underline: false, + align: 'left', + background: null, + padding: 0, + cornerRadius: 0, + }, + }, + { + id: 'terminal-code', + type: 'code', + x: 360, + y: 180, + rotation: 0, + locked: false, + visible: true, + width: 1200, + height: 700, + props: { + code: `# Initialize a new project +$ npm init -y + +# Install dependencies +$ npm install express + +# Install dev dependencies +$ npm install -D typescript @types/node + +# Run scripts +$ npm run dev + +# Output: +✓ Server running on http://localhost:3000`, + language: 'bash', + theme: 'vitesse-dark', + fontFamily: 'JetBrains Mono', + fontSize: 22, + lineHeight: 1.6, + lineNumbers: false, + highlights: [{ from: 2, to: 2, style: 'focus' }, { from: 5, to: 5, style: 'focus' }, { from: 8, to: 8, style: 'focus' }, { from: 11, to: 11, style: 'focus' }], + padding: 40, + cornerRadius: 16, + shadow: { blur: 40, spread: 0, color: 'rgba(34, 197, 94, 0.2)' }, + }, + }, + ], + }, + }, +]; diff --git a/src/index.css b/src/index.css index cf1295e..14a2b57 100644 --- a/src/index.css +++ b/src/index.css @@ -6,7 +6,8 @@ html, body, #root { height: 100%; width: 100%; - overflow: hidden; + overflow-x: hidden; + overflow-y: auto; } body { @@ -37,6 +38,7 @@ body { /* Color picker styling */ input[type="color"] { -webkit-appearance: none; + appearance: none; border: none; padding: 0; }