Compare commits
3 commits
299b603e53
...
b5faaaae61
| Author | SHA1 | Date | |
|---|---|---|---|
| b5faaaae61 | |||
| 0775ee6161 | |||
| 0e1eb25649 |
14 changed files with 1409 additions and 79 deletions
146
CLAUDE.md
146
CLAUDE.md
|
|
@ -12,12 +12,18 @@ Browser-based interactief workshop spel waarbij deelnemers fysiek kiezen tussen
|
|||
Een presentatie tool voor workshops waar deelnemers tussen twee stellingen moeten kiezen. De facilitator toont stellingen op een beamer, een timer loopt visueel rond het scherm, en daarna zie je de stellingen grayed out terwijl mensen fysiek naar links/rechts bewegen.
|
||||
|
||||
### Features
|
||||
- Startscherm met controle over startmoment
|
||||
- Fullscreen modus voor presentaties zonder browser UI
|
||||
- Timer animatie die synchroon rond het scherm loopt
|
||||
- Geluidssignaal bij einde timer
|
||||
- Overlay met stellingen na timer
|
||||
- Keyboard shortcuts (spatiebalk voor volgende)
|
||||
- Stelling counter rechtsonder (toont voortgang)
|
||||
- Keyboard shortcuts (spatiebalk voor volgende, ESC voor fullscreen uit)
|
||||
- Direct eindscherm bij laatste stelling
|
||||
- Web-based stellingen editor met Flask backend
|
||||
- Workshop sets systeem (meerdere configuraties)
|
||||
- Stellingkast met bibliotheek voor snelle import
|
||||
- Drag & drop om stellingen te herordenen
|
||||
- Real-time CRUD operaties op stellingen
|
||||
- Kleurenpicker voor achtergronden
|
||||
- Volledig offline werkend
|
||||
|
|
@ -25,7 +31,7 @@ Een presentatie tool voor workshops waar deelnemers tussen twee stellingen moete
|
|||
### Tech Stack
|
||||
- Frontend: Pure HTML/CSS/JavaScript
|
||||
- Backend: Python Flask + flask-cors
|
||||
- Config driven via JSON
|
||||
- Config driven via JSON (sets.json voor meerdere configuraties)
|
||||
- Geen externe dependencies voor presentatie modus
|
||||
|
||||
## Bestandsstructuur
|
||||
|
|
@ -35,11 +41,13 @@ Een presentatie tool voor workshops waar deelnemers tussen twee stellingen moete
|
|||
├── index.html # Presentatie modus (hoofdspel)
|
||||
├── app.js # Game logic en timer animatie
|
||||
├── style.css # Presentatie styling
|
||||
├── config.json # Stellingen database (JSON)
|
||||
├── editor.html # Web-based config editor
|
||||
├── editor.js # Editor functionaliteit
|
||||
├── config.json # Actieve configuratie (gebruikt door presentatie)
|
||||
├── sets.json # Database met alle workshop sets
|
||||
├── stellingkast.json # Bibliotheek met stellingen voor import
|
||||
├── editor.html # Web-based config editor met sets beheer
|
||||
├── editor.js # Editor functionaliteit en sets management
|
||||
├── editor.css # Editor styling
|
||||
├── server.py # Flask backend voor editor
|
||||
├── server.py # Flask backend voor editor en sets API
|
||||
├── requirements.txt # Python dependencies
|
||||
├── .gitignore # Exclude venv, .DS_Store, etc
|
||||
└── README.md # Uitgebreide documentatie
|
||||
|
|
@ -49,12 +57,19 @@ Een presentatie tool voor workshops waar deelnemers tussen twee stellingen moete
|
|||
|
||||
### Presentatie Modus
|
||||
```bash
|
||||
# Start Flask server (nodig voor config.json laden)
|
||||
source venv/bin/activate
|
||||
python server.py
|
||||
|
||||
# Open in browser
|
||||
open index.html
|
||||
open http://localhost:8000/index.html
|
||||
```
|
||||
|
||||
**Controls:**
|
||||
- Spatiebalk: Volgende stelling
|
||||
- Groene "Start IJsbreker" knop om te beginnen
|
||||
- Fullscreen knop rechtsonder voor volledig scherm
|
||||
- Spatiebalk: Start spel of ga naar volgende stelling
|
||||
- ESC: Verlaat fullscreen modus
|
||||
- Automatische timer animatie
|
||||
- Geluidssignaal bij einde
|
||||
|
||||
|
|
@ -67,15 +82,17 @@ pip install -r requirements.txt
|
|||
python server.py
|
||||
|
||||
# Open editor
|
||||
open http://localhost:5000/editor.html
|
||||
open http://localhost:8000/editor.html
|
||||
```
|
||||
|
||||
**Editor features:**
|
||||
- Toevoegen/verwijderen stellingen
|
||||
- Timer aanpassen per stelling
|
||||
- Kleuren aanpassen
|
||||
- Preview kleuren
|
||||
- Auto-save naar config.json
|
||||
- **Sets beheer:** Meerdere workshop configuraties opslaan en laden
|
||||
- **Sets dropdown:** Selecteer actieve set of maak nieuwe set aan
|
||||
- Toevoegen/verwijderen stellingen via drag & drop
|
||||
- Stellingkast met bibliotheek voor snelle import
|
||||
- Timer aanpassen per set
|
||||
- Kleuren aanpassen per set
|
||||
- Auto-save naar sets.json en config.json
|
||||
|
||||
## Session History
|
||||
|
||||
|
|
@ -152,3 +169,104 @@ Tijdens gebruik bleek de knop onderaan moeilijk leesbaar door de overlay, en de
|
|||
- Alle wijzigingen getest en werkend
|
||||
- Gecommit en gepusht naar remote (commit e1f1659)
|
||||
- UX verbeterd voor workshop gebruik
|
||||
|
||||
### 2025-12-12 - Stellingkast en Drag & Drop
|
||||
**Nieuwe features:**
|
||||
- **Stellingkast:** Nieuw inschuif-paneel in de editor om direct stellingen uit een bibliotheek (`stellingkast.json`) te importeren.
|
||||
- **Drag & Drop:** Stellingen kunnen nu in de editor versleept worden om de volgorde aan te passen.
|
||||
- **Cache Busting:** Toegevoegd aan fetch calls om laadproblemen met verouderde JSON bestanden te voorkomen.
|
||||
- **Validatie:** `stellingkast.json` is opgeschoond en bevat nu valide JSON.
|
||||
|
||||
**Gewijzigde bestanden:**
|
||||
- `editor.html` - HTML structuur voor Stellingkast panel en nieuwe header knop
|
||||
- `editor.css` - Styling voor slide-in panel, sleep-handvatten en responsive design
|
||||
- `editor.js` - Logica voor drag & drop, stellingkast import, verbeterde foutafhandeling en loading states
|
||||
- `stellingkast.json` - Opgeschoond (regelnummers verwijderd)
|
||||
|
||||
**Status:**
|
||||
- Features volledig werkend en getest in browser
|
||||
- Editor robuuster gemaakt tegen laadfouten
|
||||
|
||||
### 2025-12-16 - Workshop Sets Management
|
||||
**Nieuwe features:**
|
||||
- **Sets systeem:** Meerdere complete workshop configuraties kunnen opslaan en laden
|
||||
- **Sets dropdown:** Bovenaan editor voor eenvoudig switchen tussen sets
|
||||
- **"Opslaan als Nieuwe Set"** knop voor nieuwe configuraties aanmaken
|
||||
- **Automatische migratie:** Huidige config.json wordt bij eerste gebruik omgezet naar default set
|
||||
- **Backwards compatibility:** Systeem blijft werken zonder sets.json (legacy fallback)
|
||||
|
||||
**Gewijzigde bestanden:**
|
||||
- `server.py` - Nieuwe endpoints: GET/POST `/api/sets`, helper functies voor sets management
|
||||
- `editor.html` - Sets sectie met dropdown en knoppen toegevoegd
|
||||
- `editor.css` - Styling voor sets UI (blauwe border-left accent)
|
||||
- `editor.js` - Complete sets management logica: laden, switchen, opslaan, nieuwe sets aanmaken
|
||||
- `config.json` - Blijft actieve configuratie voor presentatie modus
|
||||
|
||||
**Nieuwe bestanden:**
|
||||
- `sets.json` - Database met alle workshop sets (wordt automatisch aangemaakt)
|
||||
|
||||
**Hoe het werkt:**
|
||||
1. Editor openen → Zie dropdown met alle sets
|
||||
2. Set selecteren → Laadt stellingen en instellingen van die set
|
||||
3. Bewerken en opslaan → Overschrijft huidige set
|
||||
4. "Opslaan als Nieuwe Set" → Maakt nieuwe set aan met custom naam
|
||||
5. Presentatie starten → Gebruikt laatst opgeslagen set uit config.json
|
||||
|
||||
**Data structuur sets.json:**
|
||||
```json
|
||||
{
|
||||
"sets": [
|
||||
{
|
||||
"id": "default",
|
||||
"name": "Standaard Set",
|
||||
"config": { /* complete config */ }
|
||||
},
|
||||
{
|
||||
"id": "set-1234567890",
|
||||
"name": "PKM Workshop 2025",
|
||||
"config": { /* complete config */ }
|
||||
}
|
||||
],
|
||||
"activeSetId": "default"
|
||||
}
|
||||
```
|
||||
|
||||
**Waarom:**
|
||||
Facilitators kunnen nu meerdere workshop configuraties voorbereiden (bijv. verschillende doelgroepen, thema's) en eenvoudig tussen sets wisselen zonder telkens stellingen handmatig te moeten aanpassen.
|
||||
|
||||
**Status:**
|
||||
- Backend endpoints volledig werkend (GET/POST /api/sets)
|
||||
- Frontend UI en logic compleet geïmplementeerd
|
||||
- Automatische migratie van bestaande config.json getest
|
||||
- Sets kunnen worden aangemaakt, geladen, gewijzigd en opgeslagen
|
||||
- Backwards compatible met oude systeem
|
||||
- Klaar voor productie gebruik
|
||||
|
||||
### 2025-12-17 - Fullscreen Modus
|
||||
**Nieuwe feature:**
|
||||
- **Fullscreen knop:** Semi-transparante knop rechtsonder (naast counter) om presentatie in volledig scherm te tonen
|
||||
- **Toggle functionaliteit:** Klik op knop om fullscreen te activeren/deactiveren
|
||||
- **Visuele feedback:** Icoon draait 180° wanneer in fullscreen modus
|
||||
- **ESC toets support:** Browser standaard ESC werkt om fullscreen te verlaten
|
||||
- **Glasmorphism styling:** Moderne semi-transparante knop met blur effect
|
||||
|
||||
**Gewijzigde bestanden:**
|
||||
- `index.html` - Fullscreen knop met SVG icoon toegevoegd
|
||||
- `style.css` - Styling voor fullscreen button met hover states en is-fullscreen class
|
||||
- `app.js` - toggleFullscreen() functie, event listeners voor knop en fullscreenchange
|
||||
- `README.md` - Documentatie bijgewerkt met fullscreen instructies
|
||||
- `CLAUDE.md` - Features lijst en Session History bijgewerkt
|
||||
|
||||
**Waarom:**
|
||||
Voor workshop presentaties is het belangrijk dat de browser UI (adresbalk, tabs, bookmarks) niet zichtbaar is op de beamer. De fullscreen knop maakt het eenvoudig om de presentatie volledig scherm te tonen zonder browser elementen.
|
||||
|
||||
**Hoe het werkt:**
|
||||
- Gebruikt Fullscreen API (`document.documentElement.requestFullscreen()`)
|
||||
- Knop blijft zichtbaar in fullscreen modus voor gemakkelijke toggle
|
||||
- Event listener voor `fullscreenchange` zorgt voor correcte state sync bij ESC toets
|
||||
- Icoon rotatie geeft visuele feedback over huidige fullscreen state
|
||||
|
||||
**Status:**
|
||||
- Feature volledig werkend en getest
|
||||
- Documentatie bijgewerkt (README.md en CLAUDE.md)
|
||||
- Klaar voor gebruik bij workshop presentaties
|
||||
26
README.md
26
README.md
|
|
@ -42,11 +42,13 @@ Hier kun je:
|
|||
Open in je browser: `http://localhost:8000`
|
||||
|
||||
Sluit aan op beamer/groot scherm en:
|
||||
1. Het spel toont automatisch de eerste stelling
|
||||
2. Deelnemers lopen naar links of rechts
|
||||
3. Na de timer: beep + overlay met knop
|
||||
4. Druk op knop of **spatiebalk** voor volgende stelling
|
||||
5. Laatste stelling toont direct eindscherm
|
||||
1. Klik op groene "Start IJsbreker" knop om te beginnen
|
||||
2. Klik op **fullscreen knop** rechtsonder voor volledig scherm (zonder browser UI)
|
||||
3. Deelnemers lezen stellingen en lopen naar links of rechts
|
||||
4. Na de timer: beep + overlay met knop
|
||||
5. Druk op knop of **spatiebalk** voor volgende stelling
|
||||
6. Laatste stelling toont direct eindscherm
|
||||
7. Druk nogmaals op fullscreen knop of **ESC** om fullscreen te verlaten
|
||||
|
||||
## Configuratie
|
||||
|
||||
|
|
@ -83,23 +85,31 @@ Alle instellingen staan in `config.json`:
|
|||
|
||||
## Interactie
|
||||
|
||||
**Presentatie modus:**
|
||||
- **Klik op knop**: Ga naar volgende stelling
|
||||
- **Spatiebalk**: Ga naar volgende stelling (sneller tijdens presentatie)
|
||||
- **Spatiebalk**: Start spel (op startscherm) of ga naar volgende stelling
|
||||
- **Fullscreen knop**: Toggle volledig scherm (rechtsonder, naast counter)
|
||||
- **ESC toets**: Verlaat fullscreen modus
|
||||
|
||||
## Features
|
||||
|
||||
**Presentatie:**
|
||||
- ✅ Startscherm met controle over startmoment
|
||||
- ✅ Fullscreen modus voor presentaties zonder browser UI
|
||||
- ✅ Visuele timer rondom scherm (loopt synchroon rond)
|
||||
- ✅ Geluidssignaal bij einde timer
|
||||
- ✅ Overlay met stellingen na timer (grayed out)
|
||||
- ✅ Keyboard shortcut (spatiebalk)
|
||||
- ✅ Stelling counter rechtsonder (toont bijv. "1/8", "2/8")
|
||||
- ✅ Keyboard shortcuts (spatiebalk voor volgende, ESC voor fullscreen uit)
|
||||
- ✅ Direct eindscherm bij laatste stelling
|
||||
|
||||
**Editor:**
|
||||
- ✅ Web-based stellingen beheer
|
||||
- ✅ Workshop sets systeem (meerdere configuraties opslaan en laden)
|
||||
- ✅ Stellingkast met bibliotheek voor snelle import
|
||||
- ✅ Drag & drop om stellingen te herordenen
|
||||
- ✅ Real-time preview van aantal stellingen
|
||||
- ✅ Kleurenpicker voor achtergronden
|
||||
- ✅ Drag-free toevoegen/verwijderen
|
||||
- ✅ Keyboard shortcut (Ctrl/Cmd+S)
|
||||
- ✅ Validatie van invoer
|
||||
|
||||
|
|
|
|||
20
agents.md
20
agents.md
|
|
@ -1,7 +1,6 @@
|
|||
# agents.md
|
||||
> Deze file wordt automatisch gesynchroniseerd met CLAUDE.md
|
||||
> Laatste sync: 2025-11-14 21:45
|
||||
|
||||
> Laatste sync: 2025-12-12 08:35
|
||||
# IJsbreker Workshop Spel
|
||||
|
||||
Browser-based interactief workshop spel waarbij deelnemers fysiek kiezen tussen twee stellingen die op een beamer worden getoond.
|
||||
|
|
@ -156,3 +155,20 @@ Tijdens gebruik bleek de knop onderaan moeilijk leesbaar door de overlay, en de
|
|||
- Alle wijzigingen getest en werkend
|
||||
- Gecommit en gepusht naar remote (commit e1f1659)
|
||||
- UX verbeterd voor workshop gebruik
|
||||
|
||||
### 2025-12-12 - Stellingkast en Drag & Drop
|
||||
**Nieuwe features:**
|
||||
- **Stellingkast:** Nieuw inschuif-paneel in de editor om direct stellingen uit een bibliotheek (`stellingkast.json`) te importeren.
|
||||
- **Drag & Drop:** Stellingen kunnen nu in de editor versleept worden om de volgorde aan te passen.
|
||||
- **Cache Busting:** Toegevoegd aan fetch calls om laadproblemen met verouderde JSON bestanden te voorkomen.
|
||||
- **Validatie:** `stellingkast.json` is opgeschoond en bevat nu valide JSON.
|
||||
|
||||
**Gewijzigde bestanden:**
|
||||
- `editor.html` - HTML structuur voor Stellingkast panel en nieuwe header knop
|
||||
- `editor.css` - Styling voor slide-in panel, sleep-handvatten en responsive design
|
||||
- `editor.js` - Logica voor drag & drop, stellingkast import, verbeterde foutafhandeling en loading states
|
||||
- `stellingkast.json` - Opgeschoond (regelnummers verwijderd)
|
||||
|
||||
**Status:**
|
||||
- Features volledig werkend en getest in browser
|
||||
- Editor robuuster gemaakt tegen laadfouten
|
||||
33
app.js
33
app.js
|
|
@ -17,6 +17,7 @@ const startButton = document.getElementById('startButton');
|
|||
const nextButton = document.getElementById('nextButton');
|
||||
const finishText = document.getElementById('finishText');
|
||||
const counterDisplay = document.getElementById('counterDisplay');
|
||||
const fullscreenButton = document.getElementById('fullscreenButton');
|
||||
|
||||
// Laad configuratie bij start
|
||||
async function loadConfig() {
|
||||
|
|
@ -169,6 +170,25 @@ function playBeep() {
|
|||
}
|
||||
}
|
||||
|
||||
// Toggle fullscreen modus
|
||||
function toggleFullscreen() {
|
||||
if (!document.fullscreenElement) {
|
||||
// Ga naar fullscreen
|
||||
document.documentElement.requestFullscreen().then(() => {
|
||||
fullscreenButton.classList.add('is-fullscreen');
|
||||
}).catch((error) => {
|
||||
console.error('Fout bij activeren fullscreen:', error);
|
||||
});
|
||||
} else {
|
||||
// Verlaat fullscreen
|
||||
document.exitFullscreen().then(() => {
|
||||
fullscreenButton.classList.remove('is-fullscreen');
|
||||
}).catch((error) => {
|
||||
console.error('Fout bij verlaten fullscreen:', error);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
startButton.addEventListener('click', () => {
|
||||
startGame();
|
||||
|
|
@ -182,6 +202,19 @@ nextButton.addEventListener('click', () => {
|
|||
}
|
||||
});
|
||||
|
||||
fullscreenButton.addEventListener('click', () => {
|
||||
toggleFullscreen();
|
||||
});
|
||||
|
||||
// Luister naar fullscreen veranderingen (bijv. via ESC toets)
|
||||
document.addEventListener('fullscreenchange', () => {
|
||||
if (document.fullscreenElement) {
|
||||
fullscreenButton.classList.add('is-fullscreen');
|
||||
} else {
|
||||
fullscreenButton.classList.remove('is-fullscreen');
|
||||
}
|
||||
});
|
||||
|
||||
// Spatiebalk voor volgende stelling EN startscherm
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.code === 'Space') {
|
||||
|
|
|
|||
28
config.json
28
config.json
|
|
@ -12,32 +12,28 @@
|
|||
"rechts": "Thee"
|
||||
},
|
||||
{
|
||||
"links": "Friet",
|
||||
"rechts": "Patat"
|
||||
"links": "Structurele planner",
|
||||
"rechts": "Creatieve chaoot"
|
||||
},
|
||||
{
|
||||
"links": "Ik gebruik meer sneltoetsen",
|
||||
"rechts": "Ik ben van team muisgebruik"
|
||||
"links": "❤️ Mappenstructuur",
|
||||
"rechts": "❤️ Zoekfunctie"
|
||||
},
|
||||
{
|
||||
"links": "Ik vind eenvoudig mijn eigen informatie terug",
|
||||
"rechts": "Ik ben constant alles kwijt"
|
||||
"links": "Samenwerken in één document",
|
||||
"rechts": "Concept_versie_3_def_final.docx mailen"
|
||||
},
|
||||
{
|
||||
"links": "We hebben duidelijke afspraken over naamgeving van bestanden",
|
||||
"rechts": "Mijn naamgeving van bestanden is veel logischer"
|
||||
"links": "Ik gebruik tags en labels",
|
||||
"rechts": "Ik stop alles in mapjes"
|
||||
},
|
||||
{
|
||||
"links": "Ik maak eigen notities op één plek",
|
||||
"rechts": "Ik maak overal notities en zoek me suf"
|
||||
"links": "Kennis zit in mijn hoofd",
|
||||
"rechts": "Kennis staat in het systeem"
|
||||
},
|
||||
{
|
||||
"links": "❤️ ❤️ ❤️ Notitie en memo templates ",
|
||||
"rechts": "☠️☠️☠️ Wie bedenkt die templates?"
|
||||
},
|
||||
{
|
||||
"links": "Zin in de dag!",
|
||||
"rechts": "Wat gaan we doen?"
|
||||
"links": "Ik kom vandaag vooral halen",
|
||||
"rechts": "Ik kom vandaag ook brengen"
|
||||
}
|
||||
],
|
||||
"timer": 2
|
||||
|
|
|
|||
209
editor.css
209
editor.css
|
|
@ -62,6 +62,12 @@ section h2 {
|
|||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.header-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.count-badge {
|
||||
background: #E5E7EB;
|
||||
color: #4B5563;
|
||||
|
|
@ -71,6 +77,66 @@ section h2 {
|
|||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Sets Section Styling */
|
||||
.sets-section {
|
||||
border-left: 4px solid #3b82f6;
|
||||
}
|
||||
|
||||
.sets-controls {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.set-selector-group {
|
||||
flex: 1;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.set-selector-group label {
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.set-select {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 2px solid #e5e7eb;
|
||||
border-radius: 6px;
|
||||
font-size: 1rem;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.set-select:hover {
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
|
||||
.set-select:focus {
|
||||
outline: none;
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.set-actions {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #ef4444;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #dc2626;
|
||||
}
|
||||
|
||||
/* Settings grid */
|
||||
.settings-grid {
|
||||
display: grid;
|
||||
|
|
@ -121,20 +187,38 @@ section h2 {
|
|||
|
||||
.statement-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr auto;
|
||||
grid-template-columns: auto 1fr 1fr auto;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
padding: 1rem;
|
||||
background: #F9FAFB;
|
||||
border-radius: 8px;
|
||||
border: 2px solid #E5E7EB;
|
||||
transition: border-color 0.2s;
|
||||
transition: border-color 0.2s, transform 0.2s, box-shadow 0.2s;
|
||||
user-select: none; /* Prevent text selection while dragging */
|
||||
}
|
||||
|
||||
.statement-row:hover {
|
||||
border-color: #D1D5DB;
|
||||
}
|
||||
|
||||
.statement-row.dragging {
|
||||
opacity: 0.5;
|
||||
background: #E5E7EB;
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
cursor: grab;
|
||||
color: #9CA3AF;
|
||||
font-size: 1.2rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.drag-handle:hover {
|
||||
color: #4B5563;
|
||||
}
|
||||
|
||||
.statement-input {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -251,6 +335,109 @@ section h2 {
|
|||
color: #991B1B;
|
||||
}
|
||||
|
||||
/* Stellingkast Panel */
|
||||
.stellingkast-panel {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: -400px; /* Start hidden */
|
||||
width: 400px;
|
||||
height: 100vh;
|
||||
background: white;
|
||||
box-shadow: -4px 0 15px rgba(0, 0, 0, 0.1);
|
||||
z-index: 1000;
|
||||
transition: right 0.3s ease-in-out;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.stellingkast-panel.open {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
padding: 1.5rem;
|
||||
background: #F3F4F6;
|
||||
border-bottom: 1px solid #E5E7EB;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.panel-header h2 {
|
||||
margin: 0;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.btn-close {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 2rem;
|
||||
color: #6B7280;
|
||||
cursor: pointer;
|
||||
padding: 0 0.5rem;
|
||||
}
|
||||
|
||||
.panel-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
padding: 0.75rem;
|
||||
border: 2px solid #E5E7EB;
|
||||
border-radius: 8px;
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.stellingkast-item {
|
||||
background: #F9FAFB;
|
||||
border: 1px solid #E5E7EB;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.stellingkast-item:hover {
|
||||
border-color: #3B82F6;
|
||||
background: #F0F9FF;
|
||||
}
|
||||
|
||||
.stelling-text {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.8rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.stelling-label {
|
||||
font-weight: 600;
|
||||
color: #6B7280;
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
display: block;
|
||||
margin-bottom: 0.2rem;
|
||||
}
|
||||
|
||||
.btn-import {
|
||||
width: 100%;
|
||||
background: #3B82F6;
|
||||
color: white;
|
||||
padding: 0.5rem;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-import:hover {
|
||||
background: #2563EB;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
|
|
@ -264,10 +451,24 @@ section h2 {
|
|||
}
|
||||
|
||||
.statement-row {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-columns: auto 1fr; /* Adjusted for handle */
|
||||
}
|
||||
|
||||
.statement-row .statement-input {
|
||||
grid-column: 2; /* Move inputs to second column */
|
||||
}
|
||||
|
||||
.statement-row .btn-remove {
|
||||
grid-column: 2;
|
||||
justify-self: end;
|
||||
}
|
||||
|
||||
.stellingkast-panel {
|
||||
width: 100%; /* Full width on mobile */
|
||||
right: -100%;
|
||||
}
|
||||
|
||||
.settings-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
}
|
||||
36
editor.html
36
editor.html
|
|
@ -16,6 +16,23 @@
|
|||
</header>
|
||||
|
||||
<main>
|
||||
<!-- Workshop Sets -->
|
||||
<section class="sets-section">
|
||||
<h2>📂 Workshop Sets</h2>
|
||||
<div class="sets-controls">
|
||||
<div class="set-selector-group">
|
||||
<label for="setSelector">Actieve Set:</label>
|
||||
<select id="setSelector" class="set-select">
|
||||
<option value="">Laden...</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="set-actions">
|
||||
<button id="saveAsNewSet" class="btn btn-secondary">💾 Opslaan als Nieuwe Set</button>
|
||||
<button id="deleteSet" class="btn btn-danger" style="display:none;">🗑️ Verwijder Set</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Basis instellingen -->
|
||||
<section class="settings-section">
|
||||
<h2>⚙️ Instellingen</h2>
|
||||
|
|
@ -53,7 +70,10 @@
|
|||
<section class="statements-section">
|
||||
<div class="section-header">
|
||||
<h2>📝 Stellingen</h2>
|
||||
<span id="statementCount" class="count-badge">0 stellingen</span>
|
||||
<div class="header-controls">
|
||||
<button id="openStellingkast" class="btn btn-secondary">📚 Stellingkast</button>
|
||||
<span id="statementCount" class="count-badge">0 stellingen</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="statementsList" class="statements-list">
|
||||
|
|
@ -65,6 +85,20 @@
|
|||
</button>
|
||||
</section>
|
||||
|
||||
<!-- Stellingkast Panel -->
|
||||
<div id="stellingkastPanel" class="stellingkast-panel">
|
||||
<div class="panel-header">
|
||||
<h2>📚 Stellingkast</h2>
|
||||
<button id="closeStellingkast" class="btn-close">×</button>
|
||||
</div>
|
||||
<div class="panel-content">
|
||||
<input type="text" id="searchStellingkast" placeholder="Zoek stelling..." class="search-input">
|
||||
<div id="stellingkastList" class="stellingkast-list">
|
||||
<!-- Stellingen uit kast worden hier geladen -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Opslaan -->
|
||||
<section class="actions-section">
|
||||
<button id="saveConfig" class="btn btn-primary">
|
||||
|
|
|
|||
482
editor.js
482
editor.js
|
|
@ -1,5 +1,9 @@
|
|||
// Globale state
|
||||
let config = null;
|
||||
let stellingkastItems = [];
|
||||
let dragSrcEl = null;
|
||||
let allSets = null;
|
||||
let currentSetId = null;
|
||||
|
||||
// DOM elementen
|
||||
const timerInput = document.getElementById('timer');
|
||||
|
|
@ -16,30 +20,262 @@ const saveConfigBtn = document.getElementById('saveConfig');
|
|||
const statusMessage = document.getElementById('statusMessage');
|
||||
const statementCount = document.getElementById('statementCount');
|
||||
|
||||
// Laad config bij start
|
||||
// Stellingkast elementen
|
||||
const openStellingkastBtn = document.getElementById('openStellingkast');
|
||||
const closeStellingkastBtn = document.getElementById('closeStellingkast');
|
||||
const stellingkastPanel = document.getElementById('stellingkastPanel');
|
||||
const stellingkastList = document.getElementById('stellingkastList');
|
||||
const searchStellingkastInput = document.getElementById('searchStellingkast');
|
||||
|
||||
// Sets elementen
|
||||
const setSelector = document.getElementById('setSelector');
|
||||
const saveAsNewSetBtn = document.getElementById('saveAsNewSet');
|
||||
const deleteSetBtn = document.getElementById('deleteSet');
|
||||
|
||||
// Initialize
|
||||
syncColorInputs();
|
||||
// Start alle laad-acties onafhankelijk van elkaar
|
||||
loadSets();
|
||||
loadStellingkast();
|
||||
|
||||
// --- SETS MANAGEMENT ---
|
||||
|
||||
// Laad alle sets bij start
|
||||
async function loadSets() {
|
||||
try {
|
||||
console.log('Fetching sets...');
|
||||
const response = await fetch('/api/sets?t=' + Date.now());
|
||||
if (!response.ok) throw new Error('Kon sets niet laden (' + response.status + ')');
|
||||
|
||||
allSets = await response.json();
|
||||
console.log('Sets loaded:', allSets);
|
||||
|
||||
// Ensure structure
|
||||
if (!allSets.sets) allSets.sets = [];
|
||||
|
||||
populateSetSelector();
|
||||
|
||||
// Laad actieve set of eerste set
|
||||
if (allSets.activeSetId) {
|
||||
currentSetId = allSets.activeSetId;
|
||||
} else if (allSets.sets.length > 0) {
|
||||
currentSetId = allSets.sets[0].id;
|
||||
}
|
||||
|
||||
if (currentSetId) {
|
||||
loadSetById(currentSetId);
|
||||
}
|
||||
|
||||
showStatus('Sets geladen!', 'success');
|
||||
} catch (error) {
|
||||
console.error('Fout bij laden sets:', error);
|
||||
showStatus('Fout bij laden sets: ' + error.message, 'error');
|
||||
// Fallback naar oude config.json manier
|
||||
loadConfig();
|
||||
}
|
||||
}
|
||||
|
||||
// Vul set selector dropdown
|
||||
function populateSetSelector() {
|
||||
if (!setSelector || !allSets) return;
|
||||
|
||||
setSelector.innerHTML = '<option value="">-- Nieuwe set --</option>';
|
||||
|
||||
allSets.sets.forEach(set => {
|
||||
const option = document.createElement('option');
|
||||
option.value = set.id;
|
||||
option.textContent = set.name;
|
||||
if (set.id === currentSetId) {
|
||||
option.selected = true;
|
||||
}
|
||||
setSelector.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
// Laad een specifieke set in de editor
|
||||
function loadSetById(setId) {
|
||||
const set = allSets.sets.find(s => s.id === setId);
|
||||
if (!set || !set.config) {
|
||||
console.error('Set niet gevonden:', setId);
|
||||
return;
|
||||
}
|
||||
|
||||
config = set.config;
|
||||
currentSetId = setId;
|
||||
|
||||
// Ensure minimal config structure
|
||||
if (!config.stellingen) config.stellingen = [];
|
||||
if (!config.colors) config.colors = { left: '#3B82F6', right: '#EF4444' };
|
||||
|
||||
populateForm();
|
||||
showStatus(`Set geladen: ${set.name}`, 'success');
|
||||
}
|
||||
|
||||
// Event handler voor set selectie
|
||||
function onSetSelected(event) {
|
||||
const setId = event.target.value;
|
||||
|
||||
if (!setId) {
|
||||
// "Nieuwe set" optie gekozen
|
||||
currentSetId = null;
|
||||
config = {
|
||||
timer: 30,
|
||||
fontSize: '3rem',
|
||||
buttonText: 'Volgende Stelling',
|
||||
finishText: 'Einde!',
|
||||
colors: { left: '#3B82F6', right: '#EF4444' },
|
||||
stellingen: []
|
||||
};
|
||||
populateForm();
|
||||
showStatus('Nieuwe set - vul gegevens in en gebruik "Opslaan als Nieuwe Set"', 'info');
|
||||
} else {
|
||||
loadSetById(setId);
|
||||
}
|
||||
}
|
||||
|
||||
// Sla huidige config op naar actieve set
|
||||
async function saveCurrentSet() {
|
||||
if (!currentSetId) {
|
||||
showStatus('Geen actieve set - gebruik "Opslaan als Nieuwe Set"', 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// Update set in allSets
|
||||
const setIndex = allSets.sets.findIndex(s => s.id === currentSetId);
|
||||
if (setIndex === -1) {
|
||||
showStatus('Set niet gevonden', 'error');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Update config van deze set
|
||||
allSets.sets[setIndex].config = { ...config };
|
||||
allSets.activeSetId = currentSetId;
|
||||
|
||||
// Sla op naar server
|
||||
const response = await fetch('/api/sets', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(allSets)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
showStatus('✅ Set opgeslagen!', 'success');
|
||||
return true;
|
||||
} else {
|
||||
showStatus(`❌ Fout: ${result.error}`, 'error');
|
||||
return false;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fout bij opslaan set:', error);
|
||||
showStatus('❌ Fout bij opslaan set', 'error');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Sla op als nieuwe set (met naam prompt)
|
||||
async function saveAsNewSet() {
|
||||
const setName = prompt('Geef een naam voor deze set:', 'Nieuwe Workshop Set');
|
||||
|
||||
if (!setName || setName.trim() === '') {
|
||||
showStatus('Opslaan geannuleerd', 'info');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Genereer unieke ID
|
||||
const newId = 'set-' + Date.now();
|
||||
|
||||
// Maak nieuwe set
|
||||
const newSet = {
|
||||
id: newId,
|
||||
name: setName.trim(),
|
||||
config: { ...config }
|
||||
};
|
||||
|
||||
// Voeg toe aan sets
|
||||
allSets.sets.push(newSet);
|
||||
allSets.activeSetId = newId;
|
||||
currentSetId = newId;
|
||||
|
||||
// Sla op naar server
|
||||
const response = await fetch('/api/sets', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(allSets)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
populateSetSelector();
|
||||
setSelector.value = newId;
|
||||
showStatus(`✅ Nieuwe set "${setName}" aangemaakt!`, 'success');
|
||||
} else {
|
||||
showStatus(`❌ Fout: ${result.error}`, 'error');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fout bij aanmaken set:', error);
|
||||
showStatus('❌ Fout bij aanmaken set', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Toon status bericht
|
||||
function showStatus(message, type = 'info') {
|
||||
if (!statusMessage) return;
|
||||
|
||||
statusMessage.textContent = message;
|
||||
statusMessage.className = 'status-message ' + type;
|
||||
statusMessage.style.display = 'block';
|
||||
|
||||
// Auto-hide na 5 seconden
|
||||
setTimeout(() => {
|
||||
statusMessage.style.display = 'none';
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
// Fallback: Laad config op oude manier (voor backwards compatibility)
|
||||
async function loadConfig() {
|
||||
try {
|
||||
const response = await fetch('/api/config');
|
||||
console.log('Fetching config...');
|
||||
const response = await fetch('/api/config?t=' + Date.now());
|
||||
if (!response.ok) throw new Error('Kon config niet laden (' + response.status + ')');
|
||||
|
||||
config = await response.json();
|
||||
console.log('Config loaded:', config);
|
||||
|
||||
// Ensure minimal config structure
|
||||
if (!config.stellingen) config.stellingen = [];
|
||||
if (!config.colors) config.colors = { left: '#3B82F6', right: '#EF4444' };
|
||||
|
||||
populateForm();
|
||||
showStatus('Config geladen!', 'success');
|
||||
} catch (error) {
|
||||
console.error('Fout bij laden config:', error);
|
||||
showStatus('Fout bij laden config', 'error');
|
||||
showStatus('Fout bij laden config: ' + error.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Vul formulier met config data
|
||||
function populateForm() {
|
||||
// Basis instellingen
|
||||
timerInput.value = config.timer;
|
||||
fontSizeInput.value = config.fontSize;
|
||||
buttonTextInput.value = config.buttonText;
|
||||
finishTextInput.value = config.finishText;
|
||||
colorLeftInput.value = config.colors.left;
|
||||
colorLeftTextInput.value = config.colors.left;
|
||||
colorRightInput.value = config.colors.right;
|
||||
colorRightTextInput.value = config.colors.right;
|
||||
if (!config) return;
|
||||
|
||||
// Basis instellingen (met fallback values)
|
||||
timerInput.value = config.timer || 30;
|
||||
fontSizeInput.value = config.fontSize || '3rem';
|
||||
buttonTextInput.value = config.buttonText || 'Volgende Stelling';
|
||||
finishTextInput.value = config.finishText || 'Einde!';
|
||||
|
||||
// Safety check voor colors object
|
||||
const leftColor = (config.colors && config.colors.left) ? config.colors.left : '#3B82F6';
|
||||
const rightColor = (config.colors && config.colors.right) ? config.colors.right : '#EF4444';
|
||||
|
||||
colorLeftInput.value = leftColor;
|
||||
colorLeftTextInput.value = leftColor;
|
||||
colorRightInput.value = rightColor;
|
||||
colorRightTextInput.value = rightColor;
|
||||
|
||||
// Render stellingen
|
||||
renderStatements();
|
||||
|
|
@ -49,9 +285,11 @@ function populateForm() {
|
|||
function renderStatements() {
|
||||
statementsList.innerHTML = '';
|
||||
|
||||
config.stellingen.forEach((stelling, index) => {
|
||||
addStatementRow(stelling, index);
|
||||
});
|
||||
if (config.stellingen) {
|
||||
config.stellingen.forEach((stelling, index) => {
|
||||
addStatementRow(stelling, index);
|
||||
});
|
||||
}
|
||||
|
||||
updateCount();
|
||||
}
|
||||
|
|
@ -61,8 +299,10 @@ function addStatementRow(stelling = { links: '', rechts: '' }, index) {
|
|||
const row = document.createElement('div');
|
||||
row.className = 'statement-row';
|
||||
row.dataset.index = index;
|
||||
row.setAttribute('draggable', 'true');
|
||||
|
||||
row.innerHTML = `
|
||||
<div class="drag-handle" title="Sleep om te verplaatsen">☰</div>
|
||||
<div class="statement-input">
|
||||
<label>Stelling Links</label>
|
||||
<input
|
||||
|
|
@ -108,6 +348,60 @@ function addStatementRow(stelling = { links: '', rechts: '' }, index) {
|
|||
removeStatement(index);
|
||||
}
|
||||
});
|
||||
|
||||
// Drag and Drop listeners
|
||||
addDragListeners(row);
|
||||
}
|
||||
|
||||
// Drag and Drop functionaliteit
|
||||
function addDragListeners(row) {
|
||||
row.addEventListener('dragstart', handleDragStart);
|
||||
row.addEventListener('dragover', handleDragOver);
|
||||
row.addEventListener('drop', handleDrop);
|
||||
row.addEventListener('dragend', handleDragEnd);
|
||||
}
|
||||
|
||||
function handleDragStart(e) {
|
||||
dragSrcEl = this;
|
||||
e.dataTransfer.effectAllowed = 'move';
|
||||
e.dataTransfer.setData('text/plain', this.dataset.index);
|
||||
this.classList.add('dragging');
|
||||
}
|
||||
|
||||
function handleDragOver(e) {
|
||||
if (e.preventDefault) {
|
||||
e.preventDefault();
|
||||
}
|
||||
e.dataTransfer.dropEffect = 'move';
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleDrop(e) {
|
||||
if (e.stopPropagation) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
const sourceIndex = parseInt(e.dataTransfer.getData('text/plain'));
|
||||
const targetIndex = parseInt(this.dataset.index);
|
||||
|
||||
if (dragSrcEl !== this && !isNaN(sourceIndex) && !isNaN(targetIndex)) {
|
||||
// Swap in array
|
||||
const itemToMove = config.stellingen[sourceIndex];
|
||||
config.stellingen.splice(sourceIndex, 1);
|
||||
config.stellingen.splice(targetIndex, 0, itemToMove);
|
||||
|
||||
// Re-render
|
||||
renderStatements();
|
||||
showStatus('Volgorde aangepast', 'success');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleDragEnd(e) {
|
||||
this.classList.remove('dragging');
|
||||
const rows = document.querySelectorAll('.statement-row');
|
||||
rows.forEach(row => row.classList.remove('dragging'));
|
||||
}
|
||||
|
||||
// Verwijder stelling
|
||||
|
|
@ -126,12 +420,16 @@ function addNewStatement() {
|
|||
const newRow = statementsList.lastElementChild;
|
||||
const firstInput = newRow.querySelector('.statement-left');
|
||||
firstInput.focus();
|
||||
// Scroll naar beneden
|
||||
newRow.scrollIntoView({ behavior: 'smooth' });
|
||||
|
||||
showStatus('Nieuwe stelling toegevoegd', 'success');
|
||||
}
|
||||
|
||||
// Update stelling count
|
||||
function updateCount() {
|
||||
if (!config || !config.stellingen) return;
|
||||
|
||||
const count = config.stellingen.filter(
|
||||
s => s.links.trim() || s.rechts.trim()
|
||||
).length;
|
||||
|
|
@ -183,7 +481,16 @@ async function saveConfig() {
|
|||
return;
|
||||
}
|
||||
|
||||
// POST naar server
|
||||
// Als we sets gebruiken, sla via sets op
|
||||
if (allSets && currentSetId) {
|
||||
const saved = await saveCurrentSet();
|
||||
if (saved) {
|
||||
setTimeout(() => loadSetById(currentSetId), 500);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback naar oude methode (legacy)
|
||||
const response = await fetch('/api/save-config', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
|
@ -196,7 +503,6 @@ async function saveConfig() {
|
|||
|
||||
if (result.success) {
|
||||
showStatus('✅ Config succesvol opgeslagen!', 'success');
|
||||
// Reload om lege stellingen te verwijderen
|
||||
setTimeout(() => loadConfig(), 500);
|
||||
} else {
|
||||
showStatus(`❌ Fout: ${result.error}`, 'error');
|
||||
|
|
@ -207,21 +513,133 @@ async function saveConfig() {
|
|||
}
|
||||
}
|
||||
|
||||
// Toon status bericht
|
||||
function showStatus(message, type = 'success') {
|
||||
statusMessage.textContent = message;
|
||||
statusMessage.className = `status-message ${type}`;
|
||||
// --- Stellingkast Functionaliteit ---
|
||||
|
||||
// Verberg na 3 seconden
|
||||
setTimeout(() => {
|
||||
statusMessage.textContent = '';
|
||||
statusMessage.className = 'status-message';
|
||||
}, 3000);
|
||||
async function loadStellingkast() {
|
||||
const listContainer = document.getElementById('stellingkastList');
|
||||
if (!listContainer) return; // Safety check
|
||||
|
||||
listContainer.innerHTML = '<div style="padding:1rem; text-align:center; color:#666;">Stellingen laden...</div>';
|
||||
|
||||
try {
|
||||
console.log('Fetching stellingkast...');
|
||||
const response = await fetch('/stellingkast.json?t=' + Date.now());
|
||||
if (response.ok) {
|
||||
stellingkastItems = await response.json();
|
||||
console.log('Stellingkast loaded:', stellingkastItems.length, 'items');
|
||||
renderStellingkastList(stellingkastItems);
|
||||
} else {
|
||||
console.error('Kon stellingkast.json niet laden', response.status);
|
||||
listContainer.innerHTML = '<div style="padding:1rem; text-align:center; color:red;">Kon stellingkast niet laden (' + response.status + ').</div>';
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fout bij laden stellingkast:', error);
|
||||
listContainer.innerHTML = '<div style="padding:1rem; text-align:center; color:red;">Fout: ' + error.message + '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
function renderStellingkastList(items) {
|
||||
if (!stellingkastList) return;
|
||||
|
||||
stellingkastList.innerHTML = '';
|
||||
|
||||
if (!items || items.length === 0) {
|
||||
stellingkastList.innerHTML = '<div style="padding:1rem; text-align:center; color:#666;">Geen stellingen gevonden.</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
items.forEach((item, index) => {
|
||||
const el = document.createElement('div');
|
||||
el.className = 'stellingkast-item';
|
||||
el.innerHTML = `
|
||||
<div class="stelling-text">
|
||||
<div>
|
||||
<span class="stelling-label">Links</span>
|
||||
${item.links}
|
||||
</div>
|
||||
<div style="text-align: right;">
|
||||
<span class="stelling-label">Rechts</span>
|
||||
${item.rechts}
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn-import" data-index="${index}">+ Toevoegen</button>
|
||||
`;
|
||||
|
||||
// Add event listener
|
||||
const btn = el.querySelector('.btn-import');
|
||||
if (btn) {
|
||||
btn.addEventListener('click', () => {
|
||||
importStelling(item);
|
||||
});
|
||||
}
|
||||
|
||||
stellingkastList.appendChild(el);
|
||||
});
|
||||
}
|
||||
|
||||
function importStelling(item) {
|
||||
if (!config) config = { stellingen: [] };
|
||||
if (!config.stellingen) config.stellingen = [];
|
||||
|
||||
// Voeg toe aan config
|
||||
config.stellingen.push({ ...item }); // Kopie om referentie issues te voorkomen
|
||||
|
||||
// Render opnieuw
|
||||
renderStatements();
|
||||
|
||||
// Scroll naar beneden
|
||||
const newRow = statementsList.lastElementChild;
|
||||
if (newRow) {
|
||||
newRow.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
|
||||
// Feedback
|
||||
showStatus('Stelling geïmporteerd!', 'success');
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
addStatementBtn.addEventListener('click', addNewStatement);
|
||||
saveConfigBtn.addEventListener('click', saveConfig);
|
||||
if (addStatementBtn) addStatementBtn.addEventListener('click', addNewStatement);
|
||||
if (saveConfigBtn) saveConfigBtn.addEventListener('click', saveConfig);
|
||||
|
||||
// Sets events
|
||||
if (setSelector) setSelector.addEventListener('change', onSetSelected);
|
||||
if (saveAsNewSetBtn) saveAsNewSetBtn.addEventListener('click', saveAsNewSet);
|
||||
|
||||
// Stellingkast events
|
||||
if (openStellingkastBtn) {
|
||||
openStellingkastBtn.addEventListener('click', () => {
|
||||
stellingkastPanel.classList.add('open');
|
||||
});
|
||||
}
|
||||
|
||||
if (closeStellingkastBtn) {
|
||||
closeStellingkastBtn.addEventListener('click', () => {
|
||||
stellingkastPanel.classList.remove('open');
|
||||
});
|
||||
}
|
||||
|
||||
// Zoek functionaliteit
|
||||
if (searchStellingkastInput) {
|
||||
searchStellingkastInput.addEventListener('input', (e) => {
|
||||
const term = e.target.value.toLowerCase();
|
||||
if (stellingkastItems) {
|
||||
const filtered = stellingkastItems.filter(item =>
|
||||
(item.links && item.links.toLowerCase().includes(term)) ||
|
||||
(item.rechts && item.rechts.toLowerCase().includes(term))
|
||||
);
|
||||
renderStellingkastList(filtered);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Klik buiten panel om te sluiten
|
||||
document.addEventListener('click', (e) => {
|
||||
if (stellingkastPanel && stellingkastPanel.classList.contains('open') &&
|
||||
!stellingkastPanel.contains(e.target) &&
|
||||
e.target !== openStellingkastBtn) {
|
||||
stellingkastPanel.classList.remove('open');
|
||||
}
|
||||
});
|
||||
|
||||
// Keyboard shortcuts
|
||||
document.addEventListener('keydown', (e) => {
|
||||
|
|
@ -230,8 +648,8 @@ document.addEventListener('keydown', (e) => {
|
|||
e.preventDefault();
|
||||
saveConfig();
|
||||
}
|
||||
// Escape sluit panel
|
||||
if (e.key === 'Escape' && stellingkastPanel && stellingkastPanel.classList.contains('open')) {
|
||||
stellingkastPanel.classList.remove('open');
|
||||
}
|
||||
});
|
||||
|
||||
// Initialize
|
||||
syncColorInputs();
|
||||
loadConfig();
|
||||
|
|
|
|||
20
gemini.md
20
gemini.md
|
|
@ -1,7 +1,6 @@
|
|||
# gemini.md
|
||||
> Deze file wordt automatisch gesynchroniseerd met CLAUDE.md
|
||||
> Laatste sync: 2025-11-14 21:45
|
||||
|
||||
> Laatste sync: 2025-12-12 08:35
|
||||
# IJsbreker Workshop Spel
|
||||
|
||||
Browser-based interactief workshop spel waarbij deelnemers fysiek kiezen tussen twee stellingen die op een beamer worden getoond.
|
||||
|
|
@ -156,3 +155,20 @@ Tijdens gebruik bleek de knop onderaan moeilijk leesbaar door de overlay, en de
|
|||
- Alle wijzigingen getest en werkend
|
||||
- Gecommit en gepusht naar remote (commit e1f1659)
|
||||
- UX verbeterd voor workshop gebruik
|
||||
|
||||
### 2025-12-12 - Stellingkast en Drag & Drop
|
||||
**Nieuwe features:**
|
||||
- **Stellingkast:** Nieuw inschuif-paneel in de editor om direct stellingen uit een bibliotheek (`stellingkast.json`) te importeren.
|
||||
- **Drag & Drop:** Stellingen kunnen nu in de editor versleept worden om de volgorde aan te passen.
|
||||
- **Cache Busting:** Toegevoegd aan fetch calls om laadproblemen met verouderde JSON bestanden te voorkomen.
|
||||
- **Validatie:** `stellingkast.json` is opgeschoond en bevat nu valide JSON.
|
||||
|
||||
**Gewijzigde bestanden:**
|
||||
- `editor.html` - HTML structuur voor Stellingkast panel en nieuwe header knop
|
||||
- `editor.css` - Styling voor slide-in panel, sleep-handvatten en responsive design
|
||||
- `editor.js` - Logica voor drag & drop, stellingkast import, verbeterde foutafhandeling en loading states
|
||||
- `stellingkast.json` - Opgeschoond (regelnummers verwijderd)
|
||||
|
||||
**Status:**
|
||||
- Features volledig werkend en getest in browser
|
||||
- Editor robuuster gemaakt tegen laadfouten
|
||||
|
|
@ -18,6 +18,13 @@
|
|||
<!-- Stelling counter rechtsonder -->
|
||||
<div id="counterDisplay" class="counter-display hidden"></div>
|
||||
|
||||
<!-- Fullscreen knop rechtsonder -->
|
||||
<button id="fullscreenButton" class="fullscreen-button" title="Volledig scherm">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
||||
<path d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<!-- Hoofdcontainer -->
|
||||
<div id="app" class="container">
|
||||
<!-- Start scherm -->
|
||||
|
|
|
|||
71
server.py
71
server.py
|
|
@ -13,6 +13,7 @@ app = Flask(__name__)
|
|||
CORS(app) # Enable CORS voor alle routes
|
||||
|
||||
CONFIG_FILE = 'config.json'
|
||||
SETS_FILE = 'sets.json'
|
||||
|
||||
# Serve static files
|
||||
@app.route('/')
|
||||
|
|
@ -59,6 +60,76 @@ def save_config():
|
|||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
# API endpoint om alle sets te laden
|
||||
@app.route('/api/sets', methods=['GET'])
|
||||
def get_sets():
|
||||
try:
|
||||
# Als sets.json niet bestaat, maak initieel bestand aan
|
||||
if not os.path.exists(SETS_FILE):
|
||||
initialize_sets_file()
|
||||
|
||||
with open(SETS_FILE, 'r', encoding='utf-8') as f:
|
||||
sets_data = json.load(f)
|
||||
return jsonify(sets_data)
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
# API endpoint om sets op te slaan
|
||||
@app.route('/api/sets', methods=['POST'])
|
||||
def save_sets():
|
||||
try:
|
||||
data = request.json
|
||||
|
||||
# Validatie: check of sets array bestaat
|
||||
if 'sets' not in data:
|
||||
return jsonify({'error': 'Geen sets gevonden'}), 400
|
||||
|
||||
# Schrijf naar sets.json met mooie formatting
|
||||
with open(SETS_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(data, f, indent=2, ensure_ascii=False)
|
||||
|
||||
# Update config.json met actieve set als die gezet is
|
||||
if 'activeSetId' in data and data['activeSetId']:
|
||||
active_set = next((s for s in data['sets'] if s['id'] == data['activeSetId']), None)
|
||||
if active_set and 'config' in active_set:
|
||||
update_active_config(active_set['config'])
|
||||
|
||||
return jsonify({'success': True, 'message': 'Sets opgeslagen!'})
|
||||
except Exception as e:
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
# Helper: Update config.json met set configuratie
|
||||
def update_active_config(set_config):
|
||||
with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(set_config, f, indent=2, ensure_ascii=False)
|
||||
|
||||
# Helper: Initialiseer sets.json met huidige config als default set
|
||||
def initialize_sets_file():
|
||||
try:
|
||||
# Lees huidige config.json
|
||||
with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
|
||||
current_config = json.load(f)
|
||||
|
||||
# Maak initiële sets structuur
|
||||
initial_sets = {
|
||||
'sets': [
|
||||
{
|
||||
'id': 'default',
|
||||
'name': 'Standaard Set',
|
||||
'config': current_config
|
||||
}
|
||||
],
|
||||
'activeSetId': 'default'
|
||||
}
|
||||
|
||||
# Schrijf naar sets.json
|
||||
with open(SETS_FILE, 'w', encoding='utf-8') as f:
|
||||
json.dump(initial_sets, f, indent=2, ensure_ascii=False)
|
||||
|
||||
print("✅ sets.json aangemaakt met default set")
|
||||
except Exception as e:
|
||||
print(f"⚠️ Fout bij initialiseren sets.json: {e}")
|
||||
|
||||
if __name__ == '__main__':
|
||||
print("🎯 IJsbreker Server gestart!")
|
||||
print("📝 Editor: http://localhost:8000/editor.html")
|
||||
|
|
|
|||
173
sets.json
Normal file
173
sets.json
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
{
|
||||
"activeSetId": "set-1765963899829",
|
||||
"sets": [
|
||||
{
|
||||
"config": {
|
||||
"buttonText": "Volgende stelling",
|
||||
"colors": {
|
||||
"left": "#3b82f6",
|
||||
"right": "#ef4444"
|
||||
},
|
||||
"finishText": "Dat was het! Having fun yet?",
|
||||
"fontSize": "3rem",
|
||||
"stellingen": [
|
||||
{
|
||||
"links": "Koffie",
|
||||
"rechts": "Thee"
|
||||
},
|
||||
{
|
||||
"links": "Structurele planner",
|
||||
"rechts": "Creatieve chaoot"
|
||||
},
|
||||
{
|
||||
"links": "❤️ Mappenstructuur",
|
||||
"rechts": "❤️ Zoekfunctie"
|
||||
},
|
||||
{
|
||||
"links": "Samenwerken in één document",
|
||||
"rechts": "Concept_versie_3_def_final.docx mailen"
|
||||
},
|
||||
{
|
||||
"links": "Ik gebruik tags en labels",
|
||||
"rechts": "Ik stop alles in mapjes"
|
||||
},
|
||||
{
|
||||
"links": "Kennis zit in mijn hoofd",
|
||||
"rechts": "Kennis staat in het systeem"
|
||||
},
|
||||
{
|
||||
"links": "Ik kom vandaag vooral halen",
|
||||
"rechts": "Ik kom vandaag ook brengen"
|
||||
}
|
||||
],
|
||||
"timer": 10
|
||||
},
|
||||
"id": "default",
|
||||
"name": "Standaard Set"
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"buttonText": "Volgende",
|
||||
"colors": {
|
||||
"left": "#10b981",
|
||||
"right": "#f59e0b"
|
||||
},
|
||||
"finishText": "Bedankt!",
|
||||
"fontSize": "2.5rem",
|
||||
"stellingen": [
|
||||
{
|
||||
"links": "Inbox Zero held",
|
||||
"rechts": "1000+ ongelezen mails"
|
||||
},
|
||||
{
|
||||
"links": "Alles digitaal",
|
||||
"rechts": "Mijn papieren notitieboek is heilig"
|
||||
},
|
||||
{
|
||||
"links": "Samenwerken in één document",
|
||||
"rechts": "Concept_versie_3_def_final.docx mailen"
|
||||
},
|
||||
{
|
||||
"links": "Camera aan tijdens Teams",
|
||||
"rechts": "Lekker onzichtbaar luisteren"
|
||||
},
|
||||
{
|
||||
"links": "Agenda blokken voor focus",
|
||||
"rechts": "Mijn deur staat altijd open (digitaal)"
|
||||
},
|
||||
{
|
||||
"links": "Ik weet wat de AVG van me vraagt",
|
||||
"rechts": "Privacy is voor de juristen"
|
||||
}
|
||||
],
|
||||
"timer": 3
|
||||
},
|
||||
"id": "pkm-workshop",
|
||||
"name": "PKM Workshop 2025"
|
||||
},
|
||||
{
|
||||
"config": {
|
||||
"buttonText": "Volgende",
|
||||
"colors": {
|
||||
"left": "#10b981",
|
||||
"right": "#f59e0b"
|
||||
},
|
||||
"finishText": "Bedankt!",
|
||||
"fontSize": "2.5rem",
|
||||
"stellingen": [
|
||||
{
|
||||
"links": "Inbox Zero held",
|
||||
"rechts": "1000+ ongelezen mails"
|
||||
},
|
||||
{
|
||||
"links": "Alles digitaal",
|
||||
"rechts": "Mijn papieren notitieboek is heilig"
|
||||
},
|
||||
{
|
||||
"links": "Samenwerken in één document",
|
||||
"rechts": "Concept_versie_3_def_final.docx mailen"
|
||||
},
|
||||
{
|
||||
"links": "Camera aan tijdens Teams",
|
||||
"rechts": "Lekker onzichtbaar luisteren"
|
||||
},
|
||||
{
|
||||
"links": "Agenda blokken voor focus",
|
||||
"rechts": "Mijn deur staat altijd open (digitaal)"
|
||||
},
|
||||
{
|
||||
"links": "Ik weet wat de AVG van me vraagt",
|
||||
"rechts": "Privacy is voor de juristen"
|
||||
}
|
||||
],
|
||||
"timer": 3
|
||||
},
|
||||
"id": "set-1765866890733",
|
||||
"name": "Kerstboom"
|
||||
},
|
||||
{
|
||||
"id": "set-1765963899829",
|
||||
"name": "Korte Pilot sessie",
|
||||
"config": {
|
||||
"buttonText": "Volgende stelling",
|
||||
"colors": {
|
||||
"left": "#3b82f6",
|
||||
"right": "#ef4444"
|
||||
},
|
||||
"finishText": "Dat was het! Having fun yet?",
|
||||
"fontSize": "3rem",
|
||||
"stellingen": [
|
||||
{
|
||||
"links": "Koffie",
|
||||
"rechts": "Thee"
|
||||
},
|
||||
{
|
||||
"links": "Structurele planner",
|
||||
"rechts": "Creatieve chaoot"
|
||||
},
|
||||
{
|
||||
"links": "❤️ Mappenstructuur",
|
||||
"rechts": "❤️ Zoekfunctie"
|
||||
},
|
||||
{
|
||||
"links": "Samenwerken in één document",
|
||||
"rechts": "Concept_versie_3_def_final.docx mailen"
|
||||
},
|
||||
{
|
||||
"links": "Ik gebruik tags en labels",
|
||||
"rechts": "Ik stop alles in mapjes"
|
||||
},
|
||||
{
|
||||
"links": "Kennis zit in mijn hoofd",
|
||||
"rechts": "Kennis staat in het systeem"
|
||||
},
|
||||
{
|
||||
"links": "Ik kom vandaag vooral halen",
|
||||
"rechts": "Ik kom vandaag ook brengen"
|
||||
}
|
||||
],
|
||||
"timer": 2
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
202
stellingkast.json
Normal file
202
stellingkast.json
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
[
|
||||
{
|
||||
"links": "Inbox Zero held",
|
||||
"rechts": "1000+ ongelezen mails"
|
||||
},
|
||||
{
|
||||
"links": "Mappenstructuur",
|
||||
"rechts": "Zoekfunctie"
|
||||
},
|
||||
{
|
||||
"links": "Alles digitaal",
|
||||
"rechts": "Mijn papieren notitieboek is heilig"
|
||||
},
|
||||
{
|
||||
"links": "Samenwerken in één document",
|
||||
"rechts": "Concept_versie_3_def_final.docx mailen"
|
||||
},
|
||||
{
|
||||
"links": "Camera aan tijdens Teams",
|
||||
"rechts": "Lekker onzichtbaar luisteren"
|
||||
},
|
||||
{
|
||||
"links": "Direct archiveren",
|
||||
"rechts": "Dat zoek ik later wel uit"
|
||||
},
|
||||
{
|
||||
"links": "Notificaties staan altijd uit",
|
||||
"rechts": "Ik reageer direct op elke 'pling'"
|
||||
},
|
||||
{
|
||||
"links": "Ik deel ook onaf werk (WIP)",
|
||||
"rechts": "Ik deel pas als het perfect is"
|
||||
},
|
||||
{
|
||||
"links": "Eén groot beeldscherm",
|
||||
"rechts": "Twee (of meer) schermen"
|
||||
},
|
||||
{
|
||||
"links": "Microsoft OneNote / Teams",
|
||||
"rechts": "Obsidian / Notion / Roam"
|
||||
},
|
||||
{
|
||||
"links": "Alles in de cloud",
|
||||
"rechts": "Lokaal op mijn bureaublad"
|
||||
},
|
||||
{
|
||||
"links": "Ik lees uitgebreide stukken",
|
||||
"rechts": "Geef mij maar een samenvatting"
|
||||
},
|
||||
{
|
||||
"links": "Agenda blokken voor focus",
|
||||
"rechts": "Mijn deur staat altijd open (digitaal)"
|
||||
},
|
||||
{
|
||||
"links": "Ik weet wat de AVG van me vraagt",
|
||||
"rechts": "Privacy is voor de juristen"
|
||||
},
|
||||
{
|
||||
"links": "Ik gebruik tags en labels",
|
||||
"rechts": "Ik stop alles in mapjes"
|
||||
},
|
||||
{
|
||||
"links": "AI helpt me dagelijks (ChatGPT e.d.)",
|
||||
"rechts": "Mag dat wel van de IT-afdeling?"
|
||||
},
|
||||
{
|
||||
"links": "Browser met 50+ open tabbladen",
|
||||
"rechts": "Opgeruimde browser"
|
||||
},
|
||||
{
|
||||
"links": "Vergaderen is werken",
|
||||
"rechts": "Vergaderen houdt me van mijn werk"
|
||||
},
|
||||
{
|
||||
"links": "Kennis zit in mijn hoofd",
|
||||
"rechts": "Kennis staat in het systeem"
|
||||
},
|
||||
{
|
||||
"links": "Ik noteer bronnen meteen",
|
||||
"rechts": "Waar had ik dat ook alweer gelezen?"
|
||||
},
|
||||
{
|
||||
"links": "Linkjes delen",
|
||||
"rechts": "Bijlagen sturen"
|
||||
},
|
||||
{
|
||||
"links": "Structurele planner",
|
||||
"rechts": "Creatieve chaoot"
|
||||
},
|
||||
{
|
||||
"links": "Ik leer door te lezen",
|
||||
"rechts": "Ik leer door te doen"
|
||||
},
|
||||
{
|
||||
"links": "Mijn 'Te Lezen' lijst wordt korter",
|
||||
"rechts": "Mijn 'Te Lezen' lijst is een kerkhof"
|
||||
},
|
||||
{
|
||||
"links": "Ik kom vandaag vooral halen",
|
||||
"rechts": "Ik kom vandaag ook brengen"
|
||||
},
|
||||
{
|
||||
"links": "Digitale agenda",
|
||||
"rechts": "Papieren agenda"
|
||||
},
|
||||
{
|
||||
"links": "Inbox zero is het doel",
|
||||
"rechts": "Inbox zero is een mythe"
|
||||
},
|
||||
{
|
||||
"links": "Ik hou van een opgeruimd bureaublad",
|
||||
"rechts": "Chaos is mijn systeem"
|
||||
},
|
||||
{
|
||||
"links": "Ik heb één centrale werkplek",
|
||||
"rechts": "Ik werk waar het nodig is"
|
||||
},
|
||||
{
|
||||
"links": "Structuur eerst, dan content",
|
||||
"rechts": "Content eerst, structuur later"
|
||||
},
|
||||
{
|
||||
"links": "Vergadernotulen zijn heilig",
|
||||
"rechts": "Wie leest die notulen nou?"
|
||||
},
|
||||
{
|
||||
"links": "Mijn mappenstructuur is doordacht",
|
||||
"rechts": "Ik gebruik vooral de zoekfunctie"
|
||||
},
|
||||
{
|
||||
"links": "Ik lees vergaderstukken vooraf",
|
||||
"rechts": "Tijdens de vergadering is vroeg genoeg"
|
||||
},
|
||||
{
|
||||
"links": "Kennisdeling is prioriteit",
|
||||
"rechts": "Kennisdeling kost te veel tijd"
|
||||
},
|
||||
{
|
||||
"links": "Taggen van documenten helpt echt",
|
||||
"rechts": "Taggen is extra werk zonder meerwaarde"
|
||||
},
|
||||
{
|
||||
"links": "Samenwerken in gedeelde documenten",
|
||||
"rechts": "Liever versies heen en weer mailen"
|
||||
},
|
||||
{
|
||||
"links": "Ik neem notities tijdens vergaderingen",
|
||||
"rechts": "Ik vertrouw op mijn geheugen"
|
||||
},
|
||||
{
|
||||
"links": "Ons intranet is nuttig",
|
||||
"rechts": "Ons intranet is een zwart gat"
|
||||
},
|
||||
{
|
||||
"links": "Afspraken vastleggen in het systeem",
|
||||
"rechts": "Afspraken blijven in mijn hoofd"
|
||||
},
|
||||
{
|
||||
"links": "Ik archiveer regelmatig",
|
||||
"rechts": "Alles staat er nog, voor de zekerheid"
|
||||
},
|
||||
{
|
||||
"links": "Liever te veel documenteren",
|
||||
"rechts": "Liever te weinig documenteren"
|
||||
},
|
||||
{
|
||||
"links": "Workflows zijn nuttig",
|
||||
"rechts": "Workflows remmen creativiteit"
|
||||
},
|
||||
{
|
||||
"links": "Digitaal ondertekenen is handig",
|
||||
"rechts": "Geef mij maar papier en pen"
|
||||
},
|
||||
{
|
||||
"links": "We delen kennis goed binnen het team",
|
||||
"rechts": "Kennis zit vooral in hoofden"
|
||||
},
|
||||
{
|
||||
"links": "Mijn TO-DO lijst is actueel",
|
||||
"rechts": "Mijn TO-DO lijst is fictie"
|
||||
},
|
||||
{
|
||||
"links": "Ik weet wat collega's aan het doen zijn",
|
||||
"rechts": "Ik heb geen idee waar anderen mee bezig zijn"
|
||||
},
|
||||
{
|
||||
"links": "Minder vergaderen, meer documenteren",
|
||||
"rechts": "Beter overleggen dan documenteren"
|
||||
},
|
||||
{
|
||||
"links": "Ik vertrouw op automatische backups",
|
||||
"rechts": "Ik maak liever zelf kopieën"
|
||||
},
|
||||
{
|
||||
"links": "Cloud-opslag voor alles",
|
||||
"rechts": "Lokaal opslaan blijft veiliger"
|
||||
},
|
||||
{
|
||||
"links": "Onze afspraken over informatie werken",
|
||||
"rechts": "Iedereen doet toch maar wat"
|
||||
}
|
||||
]
|
||||
35
style.css
35
style.css
|
|
@ -219,6 +219,41 @@ body {
|
|||
z-index: 1500;
|
||||
}
|
||||
|
||||
/* Fullscreen knop rechtsonder */
|
||||
.fullscreen-button {
|
||||
position: fixed;
|
||||
bottom: 2rem;
|
||||
right: 6rem;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: 2px solid rgba(255, 255, 255, 0.4);
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
z-index: 1500;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.fullscreen-button:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
border-color: rgba(255, 255, 255, 0.6);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.fullscreen-button:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
/* Fullscreen knop icoon wisselen wanneer in fullscreen */
|
||||
.fullscreen-button.is-fullscreen svg {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
/* Helper classes */
|
||||
.hidden {
|
||||
display: none !important;
|
||||
|
|
|
|||
Loading…
Reference in a new issue