BareGit

Add simple slides

Author: MetroWind <chris.corsair@gmail.com>
Date: Sat Jan 24 20:39:14 2026 -0800
Commit: 530a08af87be5bc153aee4141c112db3aebb279e

Changes

diff --git a/simple-slides/README.md b/simple-slides/README.md
new file mode 100644
index 0000000..c965a6d
--- /dev/null
+++ b/simple-slides/README.md
@@ -0,0 +1,55 @@
+# Random Slides
+
+A minimalist, browser-based slideshow application that allows you to create, edit, and play Markdown-based slides. It runs entirely in your browser with no backend required and persists your data locally.
+
+## Features
+
+-   **Markdown Support**: Write your slides using standard Markdown syntax.
+-   **No Setup**: Runs directly in the browser using modern web standards—no build steps or installation required.
+-   **Local Persistence**: Your slides are automatically saved to your browser's local storage.
+-   **Drag-and-Drop Reordering**: Easily rearrange your slides by dragging the slide headers.
+-   **Customizable Font Size**: Set a base font size for the presentation mode (defaults to 256px for high visibility).
+-   **Shuffle Mode**: Present your slides in a randomized order for study or brainstorming.
+-   **Keyboard Navigation**: Simple controls for a focused presentation experience.
+
+## Quick Start
+
+### Running Locally
+
+Since the app uses ES Modules, it needs to be served via a web server (rather than opened as a local file).
+
+1.  Navigate to the project directory in your terminal.
+2.  Start a simple local server:
+    ```bash
+    # If you have Python installed:
+    python3 -m http.server
+    
+    # Or if you have Node.js/npx:
+    npx serve .
+    ```
+3.  Open your browser to `http://localhost:8000` (or the port provided by your server).
+
+## How to Use
+
+### Edit Mode
+-   **Adding Slides**: Click the **"+ Add Slide"** button at the bottom.
+-   **Editing Content**: Type directly into the text boxes using Markdown.
+-   **Reordering**: Click and hold the header of a slide (where it says "Slide X") and drag it to a new position.
+-   **Deleting**: Click the **"Delete"** button on any slide card.
+-   **Settings**: Adjust the **"Font Size"** in the top header to change how large the text appears in Play Mode.
+
+### Play Mode
+-   **Start Playing**: Click **"Play"** for the standard order or **"Shuffle Play"** for a randomized order.
+-   **Navigation**: 
+    -   Click the mouse or press **any key** (except ESC) to advance to the next slide.
+    -   Press **ESC** to exit Play Mode and return to the editor.
+-   **Layout**: Text is automatically centered both horizontally and vertically.
+
+## Technologies Used
+-   [Preact](https://preactjs.com/) (Lightweight React alternative)
+-   [htm](https://github.com/developit/htm) (JSX-like syntax for standard JavaScript)
+-   [Marked](https://marked.js.org/) (Markdown parsing)
+-   Standard Browser APIs (LocalStorage, Drag and Drop API, ES Modules)
+
+## License
+MIT
diff --git a/simple-slides/design.md b/simple-slides/design.md
new file mode 100644
index 0000000..3f3a7fd
--- /dev/null
+++ b/simple-slides/design.md
@@ -0,0 +1,151 @@
+# Technical Design Document: Random Slides
+
+## 1. Overview
+"Random Slides" is a lightweight, browser-based slideshow application that allows users to create, edit, and play text-based slides using Markdown. The application operates entirely in the browser (Single Page Application) with no backend and requires no build steps. Data is persisted using the browser's `localStorage`.
+
+## 2. Technical Constraints & Requirements
+- **No Build Step**: Must use native ES Modules (ESM) and libraries available via CDN (UMD/ESM).
+- **Pure Frontend**: No server-side logic.
+- **Persistence**: `localStorage`.
+- **Framework**: Preact (via `htm` for JSX-like syntax without transpilation).
+- **Format**: Markdown for slide content.
+
+## 3. Architecture
+
+### 3.1 Tech Stack
+- **Core Library**: `Preact` (imported via `esm.sh` or `unpkg`).
+- **Templating**: `htm` (Hyperscript Tagged Markup) to write component layouts similar to JSX.
+- **Markdown Renderer**: `marked` (via CDN).
+- **Styling**: Standard CSS3.
+- **State Management**: Preact standard `useState`, `useEffect`.
+
+### 3.2 File Structure
+Since there is no bundler, we will use standard ES modules.
+```
+/
+├── index.html          # Entry point, loads styles and scripts
+├── style.css           # Global styles
+└── src/
+    ├── main.js         # App entry and mount
+    ├── App.js          # Main container and state manager
+    ├── components/
+    │   ├── Editor.js   # Edit mode view
+    │   └── Player.js   # Play mode view
+    └── utils/
+        ├── storage.js  # LocalStorage wrapper
+        └── array.js    # Shuffle logic
+```
+
+## 4. Data Model
+
+### 4.1 Slide Object
+```javascript
+{
+  id: string,       // Unique UUID/ID for React keys and drag-drop tracking
+  content: string   // Markdown text content
+}
+```
+
+### 4.2 Application State
+The `App` component will hold the central state:
+- `slides`: `Array<SlideObject>` - The source of truth for all slides.
+- `fontSize`: `number` - Base font size in pixels (default: 256).
+- `mode`: `'edit' | 'play'` - Current application mode.
+- `playDeck`: `Array<SlideObject>` - The subset/ordered list of slides currently being played (handling normal vs. shuffled).
+- `currentIndex`: `number` - Current slide index in the `playDeck`.
+
+## 5. Component Design
+
+### 5.1 `App.js` (Container)
+- **Responsibilities**:
+    - Initialize state from `localStorage` (slides and settings).
+    - Persist changes to `slides` and `settings` to `localStorage`.
+    - Handle transitions between 'edit' and 'play' modes.
+    - Handle "Play" vs "Shuffle Play" logic (generating the `playDeck`).
+    - Manage `fontSize` state.
+
+### 5.2 `Editor.js`
+- **Props**: 
+    - `slides`: Array of slides.
+    - `fontSize`: Current font size setting.
+    - `onUpdate`: Function to update the slides array.
+    - `onUpdateFontSize`: Function to update the font size.
+    - `onPlay`: Function to start normal playback.
+    - `onShufflePlay`: Function to start shuffled playback.
+- **Features**:
+    - Renders a list of textareas.
+    - **Settings Control**: Input for Base Font Size (px).
+    - **Drag and Drop**: Implemented using HTML5 Drag and Drop API.
+    - **Actions**: Add Slide, Delete Slide.
+
+### 5.3 `Player.js`
+- **Props**:
+    - `deck`: Array of slides to play.
+    - `fontSize`: Base font size to apply.
+    - `onExit`: Function to return to edit mode.
+- **State**:
+    - `index`: Internal tracking of current slide (initialized to 0).
+- **Features**:
+    - Renders current slide using `marked`.
+    - **Style**: 
+        - Apply `fontSize` (px).
+        - Content centered horizontally (text-align) and vertically (flexbox).
+    - **Event Listeners**:
+        - `click` / `keydown` (any key except ESC): Advance slide.
+        - `keydown` (ESC): Trigger `onExit`.
+    - **Rendering**:
+        - Centered, large text.
+        - HTML rendered from Markdown (unsanitized as per requirements).
+
+## 6. Key Workflows
+
+### 6.1 Initialization
+1. App loads.
+2. `storage.js` reads key `random_slides_data`.
+3. If null, return default `[{ id: uuid(), content: '' }]`.
+4. App mounts in `mode: 'edit'`.
+
+### 6.2 Editing
+1. User types in textarea.
+2. `onChange` triggers update to specific slide in `slides` array.
+3. `useEffect` in `App` observes `slides` and saves to `localStorage`.
+
+### 6.3 Drag and Drop Reordering
+1. User drags a slide handle.
+2. `dragstart`: Capture index.
+3. `drop`: Calculate new index, splice array, update state.
+
+### 6.4 Play Mode
+1. User clicks "Play".
+    - `playDeck` = `slides`.
+    - `mode` = `'play'`.
+2. User clicks "Shuffle".
+    - `playDeck` = `shuffle(slides)`.
+    - `mode` = `'play'`.
+
+### 6.5 Navigation (Play Mode)
+1. Render `playDeck[currentIndex]`.
+2. Input event (Click/Key):
+    - `currentIndex++`.
+    - If `currentIndex >= playDeck.length`, show "End of Slideshow" or loop? (Default: Loop or Stop. Prd implies "advance", will implement loop or stop at last slide. Design decision: **Loop** for continuous flow, or **Stop** at end. Let's **Stop** at the end to avoid confusion).
+3. ESC Key:
+    - Call `onExit()`.
+    - Reset `mode` to `'edit'`.
+
+## 7. UI/UX Plan
+- **Edit Mode**:
+    - Clean vertical list of cards.
+    - Each card has a handle (for dragging), textarea, and delete button.
+    - Floating or sticky header/footer for "Add Slide", "Play", "Shuffle".
+- **Play Mode**:
+    - Full screen overlay or full viewport.
+    - Minimalist.
+    - Content vertically and horizontally centered.
+    - High contrast text.
+
+## 8. Third-Party Libraries (CDN)
+- **Preact**: `https://esm.sh/preact`
+- **Preact Hooks**: `https://esm.sh/preact/hooks`
+- **htm**: `https://esm.sh/htm`
+- **marked**: `https://esm.sh/marked`
+- **uuid**: `https://esm.sh/uuid` (for reliable ID generation).
diff --git a/simple-slides/index.html b/simple-slides/index.html
new file mode 100644
index 0000000..677113a
--- /dev/null
+++ b/simple-slides/index.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Random Slides</title>
+    <link rel="stylesheet" href="style.css">
+    <script type="importmap">
+    {
+        "imports": {
+            "preact": "https://esm.sh/preact@10.19.3",
+            "preact/hooks": "https://esm.sh/preact@10.19.3/hooks",
+            "htm": "https://esm.sh/htm@3.1.1",
+            "marked": "https://esm.sh/marked@11.1.1",
+            "uuid": "https://esm.sh/uuid@9.0.1"
+        }
+    }
+    </script>
+</head>
+<body>
+    <div id="app"></div>
+    <script type="module" src="./src/main.js"></script>
+</body>
+</html>
diff --git a/simple-slides/prd.md b/simple-slides/prd.md
new file mode 100644
index 0000000..af98523
--- /dev/null
+++ b/simple-slides/prd.md
@@ -0,0 +1,30 @@
+# A Simple Slideshow Web App
+
+* A pure frontend app runs in the browser that display a slideshow.
+  There’s not backend. Data are stored in browser storage.
+* The app should be able to run directly in browser, without any build
+  steps.
+* The slideshow is just a bunch of slides
+* The data for each slide is just a markdown string.
+* The app will have two modes: edit and play
+* In edit mode, the app shows a list of text boxes, where the user can
+  input the markdown string in each. Each box represents a slide.
+* In edit mode the user can add new slides and delete slides.
+* In edit mode, the user can adjust the order of the slide by draggin
+  the text boxes.
+* Edit mode has a button to play the slideshow, and the app switches
+  to play mode.
+* The user will also have the choice to play the slideshow shuffled.
+* In play mode, the markdown strings are rendered to big texts and
+  displayed. Don’t worry about sanitizing the markdown.
+* In play mode, the user can advance to the next slide by clicking the
+  mouse or press any key, except the ESC key. Pressing the ESC key
+  will switch the app to edit mode. There’s no way to go to the
+  previous slide. Don’t worry about mobile use cases.
+* When the user opens the app for the first time, they are in edit
+  mode with one empty slide.
+* Preact can be used if needed.
+* In the edit mode, there should be a control to set the base font
+  size. It should default to 256 (px).
+* In play mode, the text should be center both horizontally and
+  vertically.
diff --git a/simple-slides/src/App.js b/simple-slides/src/App.js
new file mode 100644
index 0000000..b039fd6
--- /dev/null
+++ b/simple-slides/src/App.js
@@ -0,0 +1,75 @@
+import { h } from 'preact';
+import { useState, useEffect } from 'preact/hooks';
+import { v4 as uuid } from 'uuid';
+import htm from 'htm';
+import { loadSlides, saveSlides, loadSettings, saveSettings } from './utils/storage.js';
+import { shuffle } from './utils/array.js';
+import { Editor } from './components/Editor.js';
+import { Player } from './components/Player.js';
+
+const html = htm.bind(h);
+
+export function App() {
+    // Initialize slides from storage or default
+    const [slides, setSlides] = useState(() => {
+        const saved = loadSlides();
+        if (saved && saved.length > 0) return saved;
+        return [{ id: uuid(), content: '' }];
+    });
+
+    // Initialize settings
+    const [fontSize, setFontSize] = useState(() => {
+        const settings = loadSettings();
+        return settings.fontSize || 256;
+    });
+
+    const [mode, setMode] = useState('edit'); // 'edit' | 'play'
+    const [playDeck, setPlayDeck] = useState([]);
+
+    // Auto-save whenever slides change
+    useEffect(() => {
+        saveSlides(slides);
+    }, [slides]);
+
+    // Auto-save settings
+    useEffect(() => {
+        saveSettings({ fontSize });
+    }, [fontSize]);
+
+    const handleUpdateSlides = (newSlides) => {
+        setSlides(newSlides);
+    };
+
+    const handlePlay = () => {
+        setPlayDeck(slides);
+        setMode('play');
+    };
+
+    const handlePlayShuffled = () => {
+        setPlayDeck(shuffle(slides));
+        setMode('play');
+    };
+
+    const handleExitPlay = () => {
+        setMode('edit');
+        setPlayDeck([]);
+    };
+
+    return html`
+        ${mode === 'edit' 
+            ? html`<${Editor} 
+                    slides=${slides} 
+                    fontSize=${fontSize}
+                    onUpdate=${handleUpdateSlides}
+                    onUpdateFontSize=${setFontSize}
+                    onPlay=${handlePlay}
+                    onPlayShuffled=${handlePlayShuffled} 
+                  />` 
+            : html`<${Player} 
+                    deck=${playDeck} 
+                    fontSize=${fontSize}
+                    onExit=${handleExitPlay} 
+                  />`
+        }
+    `;
+}
\ No newline at end of file
diff --git a/simple-slides/src/components/Editor.js b/simple-slides/src/components/Editor.js
new file mode 100644
index 0000000..32c1d68
--- /dev/null
+++ b/simple-slides/src/components/Editor.js
@@ -0,0 +1,115 @@
+import { h } from 'preact';
+import { useState } from 'preact/hooks';
+import { v4 as uuid } from 'uuid';
+import htm from 'htm';
+
+const html = htm.bind(h);
+
+export function Editor({ slides, fontSize, onUpdate, onUpdateFontSize, onPlay, onPlayShuffled }) {
+    const [draggedIndex, setDraggedIndex] = useState(null);
+    const [dropTargetIndex, setDropTargetIndex] = useState(null);
+
+    const updateSlide = (index, newContent) => {
+        const newSlides = [...slides];
+        newSlides[index] = { ...newSlides[index], content: newContent };
+        onUpdate(newSlides);
+    };
+
+    const addSlide = () => {
+        onUpdate([...slides, { id: uuid(), content: '' }]);
+    };
+
+    const deleteSlide = (index) => {
+        const newSlides = slides.filter((_, i) => i !== index);
+        onUpdate(newSlides);
+    };
+
+    // Drag and Drop handlers
+    const handleDragStart = (e, index) => {
+        if (e.target.tagName === 'TEXTAREA' || e.target.tagName === 'BUTTON') {
+            e.preventDefault(); // Prevent dragging the card when interacting with inputs
+            return; 
+        }
+        
+        setDraggedIndex(index);
+        e.dataTransfer.effectAllowed = "move";
+        e.dataTransfer.setData("text/plain", String(index));
+    };
+
+    const handleDragOver = (e, index) => {
+        e.preventDefault(); 
+        e.dataTransfer.dropEffect = "move";
+        if (draggedIndex !== index) {
+             setDropTargetIndex(index);
+        }
+    };
+    
+    const handleDragLeave = (e) => {
+        // Optional: clear if leaving the list entirely, but often tricky with child elements
+    };
+
+    const handleDrop = (e, targetIndex) => {
+        e.preventDefault();
+        
+        const sourceIndexStr = e.dataTransfer.getData("text/plain");
+        if (!sourceIndexStr && sourceIndexStr !== '0') return;
+
+        const sourceIndex = parseInt(sourceIndexStr, 10);
+        if (isNaN(sourceIndex) || sourceIndex === targetIndex) {
+            setDropTargetIndex(null);
+            return;
+        }
+
+        const newSlides = [...slides];
+        const [movedItem] = newSlides.splice(sourceIndex, 1);
+        newSlides.splice(targetIndex, 0, movedItem);
+        
+        onUpdate(newSlides);
+        setDraggedIndex(null);
+        setDropTargetIndex(null);
+    };
+
+    const handleDragEnd = () => {
+        setDraggedIndex(null);
+        setDropTargetIndex(null);
+    };
+
+    return html`
+        <div class="editor-container">
+            <div class="editor-header">
+                <h1>Random Slides</h1>
+                <div class="controls">
+                    <div style="display: flex; align-items: center; margin-right: 15px;">
+                        <label for="font-size" style="margin-right: 5px; font-size: 0.9rem;">Font Size:</label>
+                        <input id="font-size" type="number" value=${fontSize} onInput=${(e) => onUpdateFontSize(Number(e.target.value))} style="width: 60px; padding: 5px;" />
+                    </div>
+                    <button class="btn btn-primary" onClick=${onPlay} disabled=${slides.length === 0}>Play</button>
+                    <button class="btn btn-secondary" onClick=${onPlayShuffled} disabled=${slides.length === 0}>Shuffle Play</button>
+                </div>
+            </div>
+            
+            <div class="slide-list">
+                ${slides.map((slide, index) => html`
+                    <div class="slide-card ${draggedIndex === index ? 'dragging' : ''} ${dropTargetIndex === index ? 'drop-target' : ''}"
+                         draggable="true"
+                         onDragStart=${(e) => handleDragStart(e, index)}
+                         onDragOver=${(e) => handleDragOver(e, index)}
+                         onDrop=${(e) => handleDrop(e, index)}
+                         onDragEnd=${handleDragEnd}
+                         key=${slide.id}>
+                        <div class="card-header">
+                            <span>Slide ${index + 1}</span>
+                            <button class="btn btn-danger" onClick=${() => deleteSlide(index)}>Delete</button>
+                        </div>
+                        <textarea class="slide-input"
+                                  value=${slide.content}
+                                  onInput=${(e) => updateSlide(index, e.target.value)}
+                                  placeholder="# Markdown content here..."></textarea>
+                    </div>
+                `)}
+            </div>
+
+            <button class="add-slide-btn" onClick=${addSlide}>+ Add Slide</button>
+        </div>
+    `;
+}
\ No newline at end of file
diff --git a/simple-slides/src/components/Player.js b/simple-slides/src/components/Player.js
new file mode 100644
index 0000000..79a287d
--- /dev/null
+++ b/simple-slides/src/components/Player.js
@@ -0,0 +1,61 @@
+import { h } from 'preact';
+import { useState, useEffect, useRef } from 'preact/hooks';
+import { marked } from 'marked';
+import htm from 'htm';
+
+const html = htm.bind(h);
+
+export function Player({ deck, fontSize, onExit }) {
+    const [index, setIndex] = useState(0);
+    const containerRef = useRef(null);
+
+    useEffect(() => {
+        const handleKeyDown = (e) => {
+            if (e.key === 'Escape') {
+                e.preventDefault();
+                onExit();
+            } else {
+                setIndex(prev => {
+                    if (prev < deck.length - 1) {
+                        return prev + 1;
+                    }
+                    return prev;
+                });
+            }
+        };
+
+        window.addEventListener('keydown', handleKeyDown);
+        
+        if (containerRef.current) {
+            containerRef.current.focus();
+        }
+
+        return () => window.removeEventListener('keydown', handleKeyDown);
+    }, [deck.length, onExit]);
+
+    const handleClick = (e) => {
+        setIndex(prev => {
+            if (prev < deck.length - 1) {
+                return prev + 1;
+            }
+            return prev;
+        });
+    };
+
+    const currentSlide = deck[index];
+    const rawHtml = marked.parse(currentSlide.content || '# (Empty Slide)');
+
+    return html`
+        <div class="player-container" 
+             ref=${containerRef}
+             onClick=${handleClick}
+             title="Click to advance">
+            <div class="slide-content" 
+                 style="font-size: ${fontSize}px;"
+                 dangerouslySetInnerHTML=${{ __html: rawHtml }}></div>
+            <div class="hint-overlay">
+                ${index + 1} / ${deck.length} • Press ESC to exit
+            </div>
+        </div>
+    `;
+}
\ No newline at end of file
diff --git a/simple-slides/src/main.js b/simple-slides/src/main.js
new file mode 100644
index 0000000..e521f23
--- /dev/null
+++ b/simple-slides/src/main.js
@@ -0,0 +1,7 @@
+import { h, render } from 'preact';
+import htm from 'htm';
+import { App } from './App.js';
+
+const html = htm.bind(h);
+
+render(html`<${App} />`, document.getElementById('app'));
diff --git a/simple-slides/src/utils/array.js b/simple-slides/src/utils/array.js
new file mode 100644
index 0000000..08cf6ca
--- /dev/null
+++ b/simple-slides/src/utils/array.js
@@ -0,0 +1,8 @@
+export const shuffle = (array) => {
+    const newArray = [...array];
+    for (let i = newArray.length - 1; i > 0; i--) {
+        const j = Math.floor(Math.random() * (i + 1));
+        [newArray[i], newArray[j]] = [newArray[j], newArray[i]];
+    }
+    return newArray;
+};
diff --git a/simple-slides/src/utils/storage.js b/simple-slides/src/utils/storage.js
new file mode 100644
index 0000000..58f4624
--- /dev/null
+++ b/simple-slides/src/utils/storage.js
@@ -0,0 +1,38 @@
+const KEY = 'random_slides_data';
+const SETTINGS_KEY = 'random_slides_settings';
+
+export const loadSlides = () => {
+    try {
+        const data = localStorage.getItem(KEY);
+        return data ? JSON.parse(data) : null;
+    } catch (e) {
+        console.error("Failed to load slides", e);
+        return null;
+    }
+};
+
+export const saveSlides = (slides) => {
+    try {
+        localStorage.setItem(KEY, JSON.stringify(slides));
+    } catch (e) {
+        console.error("Failed to save slides", e);
+    }
+};
+
+export const loadSettings = () => {
+    try {
+        const data = localStorage.getItem(SETTINGS_KEY);
+        return data ? JSON.parse(data) : { fontSize: 256 };
+    } catch (e) {
+        console.error("Failed to load settings", e);
+        return { fontSize: 256 };
+    }
+};
+
+export const saveSettings = (settings) => {
+    try {
+        localStorage.setItem(SETTINGS_KEY, JSON.stringify(settings));
+    } catch (e) {
+        console.error("Failed to save settings", e);
+    }
+};
\ No newline at end of file
diff --git a/simple-slides/style.css b/simple-slides/style.css
new file mode 100644
index 0000000..492cd1a
--- /dev/null
+++ b/simple-slides/style.css
@@ -0,0 +1,238 @@
+:root {
+    --bg-color: #f4f4f9;
+    --card-bg: #ffffff;
+    --text-color: #333;
+    --primary-color: #007bff;
+    --danger-color: #dc3545;
+    --border-color: #ddd;
+    --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
+}
+
+* {
+    box-sizing: border-box;
+}
+
+body {
+    margin: 0;
+    padding: 0;
+    font-family: var(--font-family);
+    background-color: var(--bg-color);
+    color: var(--text-color);
+}
+
+#app {
+    display: flex;
+    flex-direction: column;
+    height: 100vh;
+}
+
+/* Editor Styles */
+.editor-container {
+    max-width: 800px;
+    margin: 0 auto;
+    padding: 20px;
+    width: 100%;
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+}
+
+.editor-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 20px;
+    position: sticky;
+    top: 0;
+    background-color: var(--bg-color);
+    z-index: 10;
+    padding: 10px 0;
+    border-bottom: 1px solid transparent;
+}
+
+.editor-header h1 {
+    margin: 0;
+    font-size: 1.5rem;
+}
+
+.controls {
+    display: flex;
+    gap: 10px;
+}
+
+.slide-list {
+    display: flex;
+    flex-direction: column;
+    gap: 15px;
+    padding-bottom: 80px; /* Space for FAB or just scrolling */
+}
+
+.slide-card {
+    background: var(--card-bg);
+    border: 1px solid var(--border-color);
+    border-radius: 8px;
+    padding: 15px;
+    display: flex;
+    flex-direction: column;
+    gap: 10px;
+    box-shadow: 0 2px 4px rgba(0,0,0,0.05);
+    transition: transform 0.2s, box-shadow 0.2s;
+    position: relative;
+}
+
+.slide-card.dragging {
+    opacity: 0.5;
+    border: 2px dashed var(--primary-color);
+}
+
+.slide-card.drop-target {
+    border: 2px solid var(--primary-color);
+    box-shadow: 0 0 10px rgba(0,123,255,0.3);
+    transform: scale(1.02);
+}
+
+.card-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    color: #888;
+    font-size: 0.9rem;
+    cursor: grab;
+    user-select: none;
+    padding-bottom: 5px;
+    border-bottom: 1px solid #eee;
+    margin-bottom: 5px;
+}
+
+.card-header:active {
+    cursor: grabbing;
+}
+
+.slide-input {
+    width: 100%;
+    min-height: 100px;
+    padding: 10px;
+    border: 1px solid var(--border-color);
+    border-radius: 4px;
+    font-family: monospace;
+    font-size: 1rem;
+    resize: vertical;
+}
+
+.btn {
+    padding: 8px 16px;
+    border: none;
+    border-radius: 4px;
+    cursor: pointer;
+    font-weight: 600;
+    font-size: 0.9rem;
+    transition: background-color 0.2s;
+}
+
+.btn-primary {
+    background-color: var(--primary-color);
+    color: white;
+}
+
+.btn-primary:hover {
+    background-color: #0056b3;
+}
+
+.btn-danger {
+    background-color: transparent;
+    color: var(--danger-color);
+    border: 1px solid var(--danger-color);
+    padding: 4px 8px;
+    font-size: 0.8rem;
+}
+
+.btn-danger:hover {
+    background-color: var(--danger-color);
+    color: white;
+}
+
+.btn-secondary {
+    background-color: #6c757d;
+    color: white;
+}
+
+.btn-secondary:hover {
+    background-color: #545b62;
+}
+
+.add-slide-btn {
+    margin-top: 20px;
+    width: 100%;
+    padding: 15px;
+    background-color: white;
+    border: 2px dashed var(--border-color);
+    color: #888;
+    font-size: 1rem;
+    cursor: pointer;
+}
+
+.add-slide-btn:hover {
+    border-color: var(--primary-color);
+    color: var(--primary-color);
+}
+
+/* Player Styles */
+.player-container {
+    position: fixed;
+    top: 0;
+    left: 0;
+    width: 100vw;
+    height: 100vh;
+    background-color: white;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    z-index: 1000;
+    padding: 40px;
+    overflow: hidden;
+}
+
+.slide-content {
+    max-width: 90vw;
+    max-height: 90vh;
+    font-size: 2rem; /* Fallback base size */
+    line-height: 1.5;
+    overflow-y: auto;
+    width: 100%;
+    text-align: center;
+}
+
+/* Markdown Styles in Player */
+.slide-content h1 { font-size: 2.5em; margin-bottom: 0.5em; }
+.slide-content h2 { font-size: 2em; margin-bottom: 0.5em; }
+.slide-content h3 { font-size: 1.75em; margin-bottom: 0.5em; }
+.slide-content p { margin-bottom: 1em; }
+.slide-content ul, .slide-content ol { margin-bottom: 1em; padding-left: 1.5em; }
+.slide-content pre {
+    background: #f4f4f4;
+    padding: 1em;
+    border-radius: 5px;
+    overflow-x: auto;
+    font-size: 0.6em; /* Code blocks smaller */
+}
+.slide-content img {
+    max-width: 100%;
+    height: auto;
+    display: block;
+    margin: 0 auto;
+}
+.slide-content blockquote {
+    border-left: 5px solid #ccc;
+    padding-left: 1em;
+    color: #666;
+    font-style: italic;
+}
+
+.hint-overlay {
+    position: absolute;
+    bottom: 20px;
+    right: 20px;
+    color: #ccc;
+    font-size: 0.8rem;
+    pointer-events: none;
+}
\ No newline at end of file