<!DOCTYPE html>
<html lang="en" data-theme="dark">
<head>
{{template "head"}}
<title>SkyDeck Editor</title>
<!-- CodeMirror for code blocks -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/theme/dracula.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/codemirror.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/javascript/javascript.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/python/python.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/go/go.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.16/mode/markdown/markdown.min.js"></script>
<!-- Sortable for drag/drop -->
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.2/Sortable.min.js"></script>
<!-- Marked for markdown preview -->
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<!-- Editor styles -->
<link rel="stylesheet" href="/public/styles/editor.css" />
<link rel="stylesheet" href="/public/styles/formatting-toolbar.css" />
</head>
<body class="min-h-screen bg-base-200 flex flex-col">
<!-- Formatting Toolbar (positioned by JS) -->
<div id="formatting-toolbar"></div>
<!-- Editor Navbar -->
<nav class="navbar bg-base-100/80 backdrop-blur-md border-b border-white/5 px-4 flex-none sticky top-0 z-50">
<div class="navbar-start gap-2">
<!-- Mobile: Slides toggle -->
<button class="btn btn-ghost btn-sm md:hidden" onclick="toggleSidebar('slides')">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/>
<rect x="3" y="14" width="7" height="7"/><rect x="14" y="14" width="7" height="7"/>
</svg>
</button>
<a href="/" class="btn btn-ghost btn-sm gap-1">
{{template "icon-logo"}}
<span class="hidden sm:inline">SkyDeck</span>
</a>
<span class="text-base-content/30 hidden sm:inline">/</span>
<input type="text" id="deck-title" class="input input-ghost text-lg font-semibold flex-1 min-w-0 sm:flex-none sm:w-56"
placeholder="Untitled Deck" value="{{with decks.CurrentDeck}}{{.Title}}{{end}}" />
<span id="save-status" class="text-xs text-base-content/50 hidden sm:inline">Saved</span>
</div>
<div class="navbar-end gap-2">
{{with decks.CurrentDeck}}
<div class="hidden lg:flex">
{{template "btn-publish" .}}
</div>
{{end}}
<!-- Mobile: Settings toggle -->
<button class="btn btn-ghost btn-sm lg:hidden" onclick="toggleSidebar('settings')">
<svg class="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M10.5 6h9.75M10.5 6a1.5 1.5 0 1 1-3 0m3 0a1.5 1.5 0 1 0-3 0M3.75 6H7.5m3 12h9.75m-9.75 0a1.5 1.5 0 0 1-3 0m3 0a1.5 1.5 0 0 0-3 0m-3.75 0H7.5m9-6h3.75m-3.75 0a1.5 1.5 0 0 1-3 0m3 0a1.5 1.5 0 0 0-3 0m-9.75 0h9.75"/>
</svg>
</button>
{{template "user-menu"}}
</div>
</nav>
<!-- Main editor -->
<div class="flex-1 flex overflow-hidden">
<!-- Slide sidebar -->
<aside id="slides-sidebar" class="hidden md:flex w-64 bg-base-200 border-r border-white/5 flex-col flex-shrink-0 absolute md:relative z-40 h-full shadow-xl md:shadow-none">
<div class="px-4 py-3 border-b border-white/5 flex items-center justify-between">
<button id="btn-add-slide" class="btn btn-ghost btn-sm flex-1">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" />
</svg>
Add Slide
</button>
<button class="btn btn-ghost btn-xs md:hidden" onclick="toggleSidebar('slides')">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<div id="slides-list" class="flex-1 overflow-y-auto px-4 py-3 space-y-4">
<!-- Slide thumbnails rendered by JS -->
</div>
</aside>
<!-- Slide canvas -->
<main class="flex-1 flex flex-col items-center justify-center p-4 md:p-8 overflow-auto bg-base-300">
<!-- Canvas wrapper for centering scaled content -->
<div id="canvas-wrapper" class="flex items-center justify-center">
<div id="slide-canvas" class="relative bg-base-100 rounded-lg shadow-2xl overflow-hidden" data-theme="{{with decks.CurrentDeckContent}}{{.Theme}}{{else}}dark{{end}}">
<div id="blocks-container" class="absolute inset-0 p-[5%] overflow-y-auto flex flex-col justify-center">
<!-- Blocks rendered by JS -->
</div>
</div>
</div>
</main>
<!-- Right panel (settings) -->
<aside id="settings-panel" class="hidden lg:block w-64 bg-base-200 border-l border-white/5 p-4 flex-shrink-0 absolute lg:relative right-0 z-40 h-full shadow-xl lg:shadow-none overflow-y-auto">
<div class="flex items-center justify-between mb-4">
<h3 class="font-semibold">Deck Settings</h3>
<button class="btn btn-ghost btn-xs lg:hidden" onclick="toggleSidebar('settings')">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<!-- Publish/Present controls (shown in sidebar on mobile) -->
{{with decks.CurrentDeck}}
<div class="lg:hidden mb-4 pb-4 border-b border-white/10">
{{template "btn-publish" .}}
</div>
{{end}}
<!-- Cover Image -->
<div class="form-control mb-4">
<label class="label">
<span class="label-text">Cover Image</span>
</label>
{{with decks.CurrentDeck}}
{{template "cover-preview" .}}
<div class="flex gap-2">
<form hx-post="/decks/{{.ID}}/cover" hx-target="#cover-preview" hx-swap="outerHTML" hx-encoding="multipart/form-data" class="flex-1">
<label class="btn btn-ghost btn-sm w-full">
<input type="file" name="image" class="hidden" accept="image/*" onchange="this.form.requestSubmit()" />
Upload
</label>
</form>
<button hx-delete="/decks/{{.ID}}/cover" hx-target="#cover-preview" hx-swap="outerHTML" class="btn btn-ghost btn-sm">Remove</button>
</div>
{{end}}
</div>
<!-- Theme selector -->
{{$theme := "dark"}}{{with decks.CurrentDeckContent}}{{$theme = .Theme}}{{end}}
<div class="form-control mb-4">
<label class="label">
<span class="label-text">Theme</span>
</label>
{{with decks.CurrentDeck}}
<select id="theme-select" name="theme" class="select select-bordered select-sm w-full"
hx-put="/decks/{{.ID}}/theme"
hx-trigger="change"
hx-swap="none"
hx-on::after-request="applyEditorTheme(this.value)">
<option value="dark" {{if eq $theme "dark"}}selected{{end}}>Dark</option>
<option value="light" {{if eq $theme "light"}}selected{{end}}>Light</option>
<option value="cyberpunk" {{if eq $theme "cyberpunk"}}selected{{end}}>Cyberpunk</option>
<option value="synthwave" {{if eq $theme "synthwave"}}selected{{end}}>Synthwave</option>
<option value="forest" {{if eq $theme "forest"}}selected{{end}}>Forest</option>
<option value="aqua" {{if eq $theme "aqua"}}selected{{end}}>Aqua</option>
<option value="dracula" {{if eq $theme "dracula"}}selected{{end}}>Dracula</option>
<option value="night" {{if eq $theme "night"}}selected{{end}}>Night</option>
<option value="coffee" {{if eq $theme "coffee"}}selected{{end}}>Coffee</option>
<option value="luxury" {{if eq $theme "luxury"}}selected{{end}}>Luxury</option>
</select>
{{end}}
</div>
<!-- Slide Background -->
<div class="form-control mb-4">
<label class="label">
<span class="label-text">Slide Background</span>
</label>
<div id="slide-background-controls" class="space-y-2">
<select id="slide-bg-type" class="select select-bordered select-sm w-full" onchange="updateSlideBackgroundType(this.value)">
<option value="">None (Theme Default)</option>
<option value="color">Solid Color</option>
<option value="image">Image</option>
</select>
<div id="slide-bg-color-controls" class="hidden space-y-2">
<div class="flex gap-2">
<input type="color" id="slide-bg-color" class="w-10 h-10 cursor-pointer rounded"
value="#000000"
onchange="updateSlideBackground({ color: this.value })" />
<input type="text" id="slide-bg-color-text" class="input input-bordered input-sm flex-1"
placeholder="#000000"
onchange="updateSlideBackground({ color: this.value })" />
</div>
<div class="form-control">
<label class="label py-1">
<span class="label-text text-xs">Opacity</span>
</label>
<input type="range" id="slide-bg-opacity" class="range range-xs range-primary"
min="0" max="1" step="0.1" value="1"
onchange="updateSlideBackground({ opacity: this.value })" />
</div>
</div>
<div id="slide-bg-image-controls" class="hidden space-y-2">
<input type="text" id="slide-bg-image-url" class="input input-bordered input-sm w-full"
placeholder="Image URL"
onchange="updateSlideBackground({ imageURL: this.value })" />
<select id="slide-bg-position" class="select select-bordered select-sm w-full"
onchange="updateSlideBackground({ position: this.value })">
<option value="cover">Cover</option>
<option value="contain">Contain</option>
<option value="center">Center</option>
</select>
</div>
</div>
</div>
<!-- Block Properties (shown when block is selected) -->
<div id="property-panel-content" class="border-t border-white/10 pt-4 mt-4">
<div class="text-center text-base-content/50 py-8">
<p class="text-sm">Select a block to edit its properties</p>
</div>
</div>
<!-- Add Block buttons -->
<div class="border-t border-white/10 pt-4 mt-4">
<h4 class="text-sm font-medium mb-3 text-base-content/70">Add Block</h4>
<div class="grid grid-cols-2 gap-2">
<button onclick="addBlock('heading')" class="btn btn-ghost btn-sm justify-start">
<span class="text-lg font-bold">H</span> Heading
</button>
<button onclick="addBlock('text')" class="btn btn-ghost btn-sm justify-start">
<span class="text-sm">Aa</span> Text
</button>
<button onclick="addBlock('code')" class="btn btn-ghost btn-sm justify-start">
<span class="font-mono text-xs"></></span> Code
</button>
<button onclick="addBlock('image')" class="btn btn-ghost btn-sm justify-start">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
<circle cx="8.5" cy="8.5" r="1.5"/>
<path d="m21 15-5-5L5 21"/>
</svg>
Image
</button>
<button onclick="addBlock('list')" class="btn btn-ghost btn-sm justify-start">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="8" y1="6" x2="21" y2="6"/>
<line x1="8" y1="12" x2="21" y2="12"/>
<line x1="8" y1="18" x2="21" y2="18"/>
<line x1="3" y1="6" x2="3.01" y2="6"/>
<line x1="3" y1="12" x2="3.01" y2="12"/>
<line x1="3" y1="18" x2="3.01" y2="18"/>
</svg>
List
</button>
<button onclick="addBlock('quote')" class="btn btn-ghost btn-sm justify-start">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="currentColor">
<path d="M6 17h3l2-4V7H5v6h3zm8 0h3l2-4V7h-6v6h3z"/>
</svg>
Quote
</button>
<button onclick="addBlock('divider')" class="btn btn-ghost btn-sm justify-start col-span-2">
<svg class="w-4 h-4" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="5" y1="12" x2="19" y2="12"/>
</svg>
Divider
</button>
</div>
</div>
</aside>
</div>
<!-- Deck list modal -->
<dialog id="decks-modal" class="modal">
<div class="modal-box">
<h3 class="font-bold text-lg mb-4">Your Decks</h3>
<div id="decks-list" class="space-y-2">
<!-- Decks loaded by JS -->
</div>
<div class="modal-action">
<button id="btn-new-deck" class="btn btn-primary">New Deck</button>
<form method="dialog">
<button class="btn">Close</button>
</form>
</div>
</div>
<form method="dialog" class="modal-backdrop">
<button>close</button>
</form>
</dialog>
<!-- Initial deck data -->
<script>
window.DECK_ID = "{{with decks.CurrentDeck}}{{.ID}}{{end}}";
// Content as string to be parsed - same pattern as preview.html
window.DECK_DATA = "{{with decks.CurrentDeck}}{{.Content}}{{end}}";
window.IS_PUBLISHED = {{with decks.CurrentDeck}}{{.IsPublished}}{{else}}false{{end}};
</script>
<script src="/public/scripts/utils/style-generator.js"></script>
<script src="/public/scripts/components/formatting-toolbar.js"></script>
<script src="/public/scripts/components/property-panel.js"></script>
<script src="/public/scripts/editor.js"></script>
</body>
</html>
Add rich text formatting and block styling to editor
Pitch decks to help promote Skyscape Apps