<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Section Layout Manager</title>
</head>
<body>
<script type="module">
const SectionLayout = () => {
// Initial data - will be loaded from server
let sections = [];
let currentStyle = 0;
const loadFromServer = async () => {
try {
const response = await fetch('sections.json?t=' + new Date().getTime());
if (response.ok) {
const data = await response.json();
sections = data.sections || [];
currentStyle = data.style || 0;
} else {
// Default data if file doesn't exist
sections = [
{"title": "First Section", "description": "This is a short description for the first section.", "color": "#6b7d8b", "image": "", "audio": ""},
{"title": "Second Section", "description": "This is a longer description for the second section that will demonstrate how the truncation works when you have more text to display.", "color": "#8b7d7a", "image": "", "audio": ""}
];
currentStyle = 0;
}
} catch (error) {
console.error('Error loading data:', error);
// Default data on error
sections = [
{"title": "First Section", "description": "This is a short description for the first section.", "color": "#8b7d6b", "image": "https://via.placeholder.com/150", "audio": ""},
{"title": "Second Section", "description": "This is a longer description for the second section that will demonstrate how the truncation works when you have more text to display.", "color": "#7a8c8e", "image": "https://via.placeholder.com/150", "audio": ""}
];
currentStyle = 0;
}
render();
};
const styles = [
{ name: 'Classic Dark', background: '#2b2d31', card: '#3d4147', text: '#d4d4d4', accent: '#6b8cae', wireframe: false, neon: false, lineBottom: false, invisible: false },
{ name: 'Vibrant Neon', background: '#050505', card: '#0b0014', text: '#fff', accent: '#ff00ff', wireframe: false, neon: true, lineBottom: false, invisible: false },
{ name: 'Neon Wireframe', background: '#050505', card: 'transparent', text: '#fff', accent: '#ff00ff', wireframe: true, neon: true, lineBottom: false, invisible: false },
{ name: 'Neon Line', background: '#050505', card: 'transparent', text: '#fff', accent: '#ff00ff', wireframe: false, neon: true, lineBottom: true, invisible: false },
{ name: 'Line Bottom', background: '#35373b', card: 'transparent', text: '#c9c9c9', accent: '#8a9ba8', wireframe: false, neon: false, lineBottom: true, invisible: false },
{ name: 'Rounded Wireframe', background: '#2e3035', card: '#2e3035', text: '#b8b8b8', accent: '#7a8c9e', wireframe: true, neon: false, lineBottom: false, invisible: false, rounded: true },
{ name: 'Wireframe', background: '#2e3035', card: '#2e3035', text: '#b8b8b8', accent: '#7a8c9e', wireframe: true, neon: false, lineBottom: false, invisible: false },
{ name: 'Invisible Block', background: '#35373b', card: 'transparent', text: '#c9c9c9', accent: '#8a9ba8', wireframe: false, neon: false, lineBottom: false, invisible: true }
];
const header = document.createElement('div');
header.className = 'header';
const title = document.createElement('h1');
title.textContent = 'Section Layout Manager';
header.appendChild(title);
const container = document.createElement('div');
container.id = 'container';
const controls = document.createElement('div');
controls.className = 'controls';
// File upload section
const uploadSection = document.createElement('div');
uploadSection.className = 'upload-section';
const imageUploadLabel = document.createElement('label');
imageUploadLabel.className = 'upload-label';
imageUploadLabel.innerHTML = 'π· Upload Image';
const imageUpload = document.createElement('input');
imageUpload.type = 'file';
imageUpload.accept = 'image/*';
imageUpload.style.display = 'none';
imageUpload.onchange = (e) => handleImageUpload(e);
imageUploadLabel.appendChild(imageUpload);
const audioUploadLabel = document.createElement('label');
audioUploadLabel.className = 'upload-label';
audioUploadLabel.innerHTML = 'π΅ Upload Audio';
const audioUpload = document.createElement('input');
audioUpload.type = 'file';
audioUpload.accept = 'audio/mp3,audio/mpeg';
audioUpload.style.display = 'none';
audioUpload.onchange = (e) => handleAudioUpload(e);
audioUploadLabel.appendChild(audioUpload);
uploadSection.append(imageUploadLabel, audioUploadLabel);
const addButton = document.createElement('button');
addButton.className = 'add-btn';
addButton.textContent = '+ Add Section';
const styleButton = document.createElement('button');
styleButton.className = 'style-btn';
styleButton.textContent = 'π¨ Style: ' + styles[currentStyle].name;
const saveButton = document.createElement('button');
saveButton.className = 'save-btn';
saveButton.textContent = 'πΎ Save';
controls.append(uploadSection, addButton, styleButton, saveButton);
const truncateText = (text, maxLength = 80) => {
if (text.length <= maxLength) return text;
return text.substring(0, maxLength) + '...';
};
const handleImageUpload = async (e) => {
const file = e.target.files[0];
if (!file) return;
const formData = new FormData();
formData.append('image', file);
try {
const response = await fetch('upload_image.php', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.success) {
alert('Image uploaded! URL: ' + result.url);
// You can now use result.url in your sections
} else {
alert('Upload failed: ' + result.error);
}
} catch (error) {
alert('Upload error: ' + error.message);
}
e.target.value = ''; // Reset input
};
const handleAudioUpload = async (e) => {
const file = e.target.files[0];
if (!file) return;
const formData = new FormData();
formData.append('audio', file);
try {
const response = await fetch('upload_audio.php', {
method: 'POST',
body: formData
});
const result = await response.json();
if (result.success) {
alert('Audio uploaded! URL: ' + result.url);
// You can now use result.url in your sections
} else {
alert('Upload failed: ' + result.error);
}
} catch (error) {
alert('Upload error: ' + error.message);
}
e.target.value = ''; // Reset input
};
const showFileBrowser = async (type, form) => {
try {
const response = await fetch(`browse_files.php?type=${type}`);
const result = await response.json();
if (!result.success) {
alert('Error: ' + result.error);
return;
}
if (result.files.length === 0) {
alert(`No ${type} files found. Upload some files first!`);
return;
}
// Create file browser overlay
const overlay = document.createElement('div');
overlay.className = 'overlay';
const modal = document.createElement('div');
modal.className = 'modal file-browser';
const closeBtn = document.createElement('button');
closeBtn.className = 'close-btn';
closeBtn.textContent = 'β';
closeBtn.onclick = () => overlay.remove();
const title = document.createElement('h2');
title.textContent = `Select ${type === 'image' ? 'an Image' : 'Audio'}`;
title.style.marginTop = '0';
const fileList = document.createElement('div');
fileList.className = 'file-list';
result.files.forEach(file => {
const fileItem = document.createElement('div');
fileItem.className = 'file-item';
if (type === 'image') {
const img = document.createElement('img');
img.src = file;
img.className = 'file-preview';
fileItem.appendChild(img);
}
const fileName = document.createElement('div');
fileName.className = 'file-name';
fileName.textContent = file.split('/').pop();
fileItem.appendChild(fileName);
fileItem.onclick = () => {
const input = form.querySelector(type === 'image' ? '.image' : '.audio');
input.value = file;
overlay.remove();
};
fileList.appendChild(fileItem);
});
modal.append(closeBtn, title, fileList);
overlay.appendChild(modal);
overlay.onclick = (e) => {
if (e.target === overlay) overlay.remove();
};
document.body.appendChild(overlay);
} catch (error) {
alert('Error loading files: ' + error.message);
}
};
// Track current audio playback
let currentAudio = null;
let isSpeaking = false;
const playAudio = (section, button) => {
// Stop any currently playing audio or speech
if (currentAudio) {
currentAudio.pause();
currentAudio.currentTime = 0;
currentAudio = null;
}
if (isSpeaking) {
window.speechSynthesis.cancel();
isSpeaking = false;
button.textContent = section.audio && section.audio.trim() !== '' ? 'π Play Audio' : 'π Read Aloud';
return;
}
if (section.audio && section.audio.trim() !== '') {
// Play MP3 if URL exists
currentAudio = new Audio(section.audio);
button.textContent = 'βΈοΈ Stop Audio';
currentAudio.onended = () => {
currentAudio = null;
button.textContent = 'π Play Audio';
};
currentAudio.play().catch(err => {
console.error('Error playing audio:', err);
alert('Could not play audio file. URL may be invalid.');
currentAudio = null;
button.textContent = 'π Play Audio';
});
} else {
// Use text-to-speech if no audio URL
const utterance = new SpeechSynthesisUtterance(section.description);
utterance.lang = 'en-US';
isSpeaking = true;
button.textContent = 'βΈοΈ Stop Reading';
utterance.onend = () => {
isSpeaking = false;
button.textContent = 'π Read Aloud';
};
window.speechSynthesis.speak(utterance);
}
};
const showOverlay = (section) => {
const overlay = document.createElement('div');
overlay.className = 'overlay';
const modal = document.createElement('div');
modal.className = 'modal';
const closeBtn = document.createElement('button');
closeBtn.className = 'close-btn';
closeBtn.textContent = 'β';
closeBtn.onclick = () => overlay.remove();
const img = document.createElement('img');
img.src = section.image;
img.alt = section.title;
img.className = 'modal-image';
const titleEl = document.createElement('h2');
titleEl.className = 'modal-title';
titleEl.textContent = section.title;
const audioBtn = document.createElement('button');
audioBtn.className = 'audio-btn';
audioBtn.textContent = section.audio && section.audio.trim() !== '' ? 'π Play Audio' : 'π Read Aloud';
audioBtn.onclick = () => playAudio(section, audioBtn);
const text = document.createElement('div');
text.className = 'modal-text';
text.textContent = section.description;
if (section.image && section.image.trim() !== '') {
modal.append(closeBtn, img, titleEl, audioBtn, text);
} else {
modal.append(closeBtn, titleEl, audioBtn, text);
}
overlay.appendChild(modal);
overlay.onclick = (e) => {
if (e.target === overlay) overlay.remove();
};
document.body.appendChild(overlay);
};
const render = () => {
const theme = styles[currentStyle];
document.body.style.backgroundColor = theme.background;
document.body.style.color = theme.text;
container.innerHTML = '';
sections.forEach((section, i) => {
const div = document.createElement('div');
div.className = `section ${theme.neon ? 'neon' : ''} ${theme.lineBottom ? 'line-bottom' : ''} ${theme.invisible ? 'invisible' : ''} ${theme.rounded ? 'rounded' : ''}`;
div.style.color = theme.text;
if (theme.wireframe) {
div.style.background = 'transparent';
div.style.border = `2px solid ${section.color || theme.accent}`;
} else if (theme.lineBottom) {
div.style.background = theme.card;
div.style.border = 'none';
div.style.borderBottom = `3px solid ${section.color || theme.accent}`;
} else if (theme.invisible) {
div.style.background = 'transparent';
div.style.border = 'none';
} else {
div.style.background = section.color || theme.card;
div.style.border = 'none';
}
const contentWrapper = document.createElement('div');
contentWrapper.className = 'content-wrapper';
contentWrapper.style.cursor = 'pointer';
contentWrapper.addEventListener("click", (e) => {
e.stopPropagation();
showOverlay(section);
});
contentWrapper.title = 'Click to view full description';
const img = document.createElement('img');
img.src = section.image;
img.alt = section.title;
const titleEl = document.createElement('h3');
titleEl.className = 'section-title';
titleEl.textContent = section.title;
const text = document.createElement('div');
text.className = 'description-text';
text.textContent = truncateText(section.description);
if (section.image && section.image.trim() !== '') {
contentWrapper.append(img, titleEl, text);
} else {
contentWrapper.append(titleEl, text);
}
const btnRow = document.createElement('div');
btnRow.className = 'button-row';
const editBtn = document.createElement('button');
editBtn.textContent = 'β Edit';
editBtn.className = 'edit-btn';
editBtn.addEventListener('click', (e) => {
e.stopPropagation();
editSection(i);
});
const deleteBtn = document.createElement('button');
deleteBtn.textContent = 'ποΈ Delete';
deleteBtn.className = 'delete-btn';
deleteBtn.addEventListener('click', (e) => {
e.stopPropagation();
deleteSection(i);
});
btnRow.append(editBtn, deleteBtn);
div.append(contentWrapper, btnRow);
container.append(div);
});
styleButton.textContent = 'π¨ Style: ' + styles[currentStyle].name;
};
const editSection = (i) => {
const s = sections[i];
const form = document.createElement('div');
form.className = 'edit-form';
form.innerHTML = `
<h3>Edit Section</h3>
<label>Title</label>
<input type="text" value="${s.title}" class="title" />
<label>Description</label>
<textarea class="desc" rows="6">${s.description}</textarea>
<label>Audio MP3 URL (optional)</label>
<div class="input-with-button">
<input type="text" value="${s.audio || ''}" class="audio" placeholder="https://example.com/audio.mp3" />
<button type="button" class="browse-btn" data-type="audio">π Browse</button>
</div>
<label>Color</label>
<input type="color" value="${s.color}" class="color" />
<label>Image URL</label>
<div class="input-with-button">
<input type="text" value="${s.image}" class="image" />
<button type="button" class="browse-btn" data-type="image">π Browse</button>
</div>
<div class="form-buttons">
<button class="save-btn">Save</button>
<button class="cancel-btn">Cancel</button>
</div>
`;
const sectionDiv = container.children[i];
sectionDiv.replaceWith(form);
// Browse button handlers
form.querySelectorAll('.browse-btn').forEach(btn => {
btn.onclick = () => showFileBrowser(btn.dataset.type, form);
});
form.querySelector('.save-btn').onclick = () => {
s.title = form.querySelector('.title').value;
s.description = form.querySelector('.desc').value;
s.audio = form.querySelector('.audio').value;
s.color = form.querySelector('.color').value;
s.image = form.querySelector('.image').value;
render();
};
form.querySelector('.cancel-btn').onclick = render;
};
const deleteSection = (i) => {
if (confirm('Delete this section?')) {
sections.splice(i, 1);
render();
}
};
const addSection = () => {
const theme = styles[currentStyle];
sections.push({
title: `New Section ${sections.length + 1}`,
description: `Description for section ${sections.length + 1}`,
color: '#7a8c7a',
image: '',
audio: ''
});
render();
};
const changeStyle = () => {
currentStyle = (currentStyle + 1) % styles.length;
render();
};
const saveToServer = async () => {
const payload = {
sections,
style: currentStyle
};
try {
const response = await fetch('save.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const result = await response.text();
alert(result);
} catch (error) {
alert('Error saving: ' + error.message);
}
};
addButton.onclick = addSection;
styleButton.onclick = changeStyle;
saveButton.onclick = saveToServer;
document.body.append(header, controls, container);
// Load data from server on startup
loadFromServer();
const style = document.createElement('style');
style.textContent = `
body {
margin: 0;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
padding: 20px;
}
.header {
width: 100%;
max-width: 900px;
text-align: center;
margin-bottom: 30px;
}
.header h1 {
margin: 0;
font-size: 2.5em;
font-weight: 700;
background: linear-gradient(90deg, #6b8cae, #7a8c9e);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
#container {
max-width: 900px;
width: 100%;
display: flex;
flex-direction: column;
gap: 20px;
padding: 20px 0;
}
.controls {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 15px;
margin-bottom: 20px;
}
.upload-section {
display: flex;
gap: 10px;
width: 100%;
justify-content: center;
margin-bottom: 10px;
}
.upload-label {
background: linear-gradient(90deg, #6b8cae, #7a8c9e);
border: none;
border-radius: 3px;
padding: 10px 20px;
color: white;
cursor: pointer;
transition: transform 0.2s;
font-size: 0.95em;
font-weight: 500;
display: inline-block;
}
.upload-label:hover {
transform: scale(1.05);
}
.section {
border-radius: 4px;
text-align: center;
padding: 40px 30px;
transition: all 0.3s;
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
}
.section:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.5);
}
.section.rounded {
border-radius: 16px !important;
}
.section.rounded img {
border-radius: 12px !important;
}
.section.line-bottom {
box-shadow: none;
}
.section.line-bottom.neon {
box-shadow: 0 3px 15px #ff00ff;
}
.section.invisible {
box-shadow: none;
}
.section.invisible:hover {
box-shadow: none;
}
.neon {
border: 2px solid #ff00ff;
box-shadow: 0 0 10px #ff00ff, 0 0 20px #ff00ff;
}
.neon .description-text {
text-shadow: 0 0 10px #ff00ff;
}
.section img {
width: 120px;
height: 120px;
border-radius: 2px;
object-fit: cover;
margin-bottom: 20px;
}
.content-wrapper {
transition: opacity 0.2s;
}
.content-wrapper:hover {
opacity: 0.8;
}
.section-title {
font-size: 1.5em;
font-weight: 600;
margin: 15px 0 10px;
}
.description-text {
font-size: 1em;
line-height: 1.6;
margin: 10px auto 20px;
max-width: 700px;
padding: 0 20px;
word-wrap: break-word;
}
.button-row {
display: flex;
gap: 10px;
justify-content: center;
margin-top: 20px;
}
button {
border: none;
border-radius: 3px;
padding: 10px 20px;
color: white;
cursor: pointer;
transition: transform 0.2s;
font-size: 0.95em;
font-weight: 500;
}
button:hover {
transform: scale(1.05);
}
.add-btn, .style-btn, .save-btn {
background: linear-gradient(90deg, #6b8cae, #7a8c9e);
}
.delete-btn {
background: #ff4d4d;
}
.edit-btn {
background: #6b8cae;
}
.cancel-btn {
background: #666;
}
.edit-form {
background: #1a1d22;
border-radius: 4px;
padding: 30px;
box-shadow: 0 2px 8px rgba(0,0,0,0.3);
}
.edit-form h3 {
margin-top: 0;
color: #eaeaea;
}
.edit-form label {
display: block;
margin-top: 15px;
margin-bottom: 5px;
font-weight: 500;
color: #eaeaea;
}
.edit-form input[type="text"],
.edit-form input[type="color"],
.edit-form textarea {
width: 100%;
padding: 10px;
border-radius: 3px;
border: 1px solid #444;
background: #2a2d32;
color: #eaeaea;
font-family: inherit;
font-size: 1em;
box-sizing: border-box;
}
.input-with-button {
display: flex;
gap: 5px;
align-items: center;
}
.input-with-button input {
flex: 1;
}
.browse-btn {
background: #6b8cae;
padding: 10px 15px;
white-space: nowrap;
flex-shrink: 0;
}
.edit-form textarea {
resize: vertical;
min-height: 120px;
line-height: 1.5;
}
.edit-form input[type="color"] {
width: 80px;
height: 40px;
cursor: pointer;
}
.form-buttons {
display: flex;
gap: 10px;
margin-top: 20px;
justify-content: center;
}
.overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.85);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
animation: fadeIn 0.2s;
padding: 20px;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.modal {
background: #1a1d22;
border-radius: 4px;
padding: 40px;
max-width: 700px;
width: 100%;
max-height: 80vh;
overflow-y: auto;
position: relative;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5);
animation: slideUp 0.3s;
}
@keyframes slideUp {
from {
transform: translateY(30px);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
.close-btn {
position: absolute;
top: 15px;
right: 15px;
background: #ff4d4d;
width: 40px;
height: 40px;
border-radius: 3px;
display: flex;
align-items: center;
justify-content: center;
font-size: 1.3em;
padding: 0;
}
.modal-image {
width: 200px;
height: 200px;
border-radius: 2px;
object-fit: cover;
margin: 0 auto 20px;
display: block;
}
.modal-title {
font-size: 2em;
font-weight: 700;
color: #eaeaea;
text-align: center;
margin: 0 0 15px;
}
.audio-btn {
background: linear-gradient(90deg, #6b8cae, #7a8c9e);
margin: 0 auto 20px;
display: block;
}
.modal-text {
font-size: 1.15em;
line-height: 1.8;
color: #eaeaea;
text-align: left;
white-space: pre-wrap;
word-wrap: break-word;
font-family: Georgia, serif;
}
.modal-text::first-letter {
font-size: 3.5em;
font-weight: bold;
float: left;
line-height: 0.9;
margin-right: 8px;
margin-top: 4px;
}
.file-browser {
max-width: 800px;
}
.file-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 15px;
max-height: 60vh;
overflow-y: auto;
padding: 10px;
}
.file-item {
border: 2px solid #444;
border-radius: 4px;
padding: 10px;
cursor: pointer;
transition: all 0.2s;
background: #2a2d32;
text-align: center;
}
.file-item:hover {
border-color: #6b8cae;
transform: scale(1.05);
}
.file-preview {
width: 100%;
height: 100px;
object-fit: cover;
border-radius: 3px;
margin-bottom: 8px;
}
.file-name {
font-size: 0.85em;
color: #d4d4d4;
word-break: break-all;
}
`;
document.head.appendChild(style);
};
document.addEventListener('DOMContentLoaded', SectionLayout);
</script>
</body>
</html>