Files
Space-Banane d7712ea17d first commit
2026-01-16 22:30:37 +01:00

465 lines
22 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🎄 Christmas Wishlist</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
body {
background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #0f172a 100%);
min-height: 100vh;
}
.snowflake {
position: fixed;
top: -10px;
z-index: 9999;
user-select: none;
pointer-events: none;
animation: fall linear infinite;
}
@keyframes fall {
to {
transform: translateY(100vh);
}
}
.card-glow {
box-shadow: 0 0 20px rgba(239, 68, 68, 0.3);
}
</style>
</head>
<body class="text-gray-100">
<div id="snowflakes"></div>
<!-- Auth Screen -->
<div id="authScreen" class="min-h-screen flex items-center justify-center p-4">
<div class="bg-slate-800 rounded-2xl shadow-2xl p-8 w-full max-w-md card-glow">
<div class="text-center mb-8">
<h1 class="text-4xl font-bold text-red-500 mb-2">🎄 Christmas Wishlist</h1>
<p class="text-gray-400">Made with ❤️ by Claude Sonnet 4.5</p>
<p class="text-xs text-gray-500 mt-1">Prompted by <a href="https://space.reversed.dev/" class="text-red-400 hover:underline">Space</a><span class="font-bold">ONE SHOT</span> try</p>
</div>
<div class="space-y-4">
<input type="text" id="username" placeholder="🎅 Username"
class="w-full px-4 py-3 bg-slate-700 border border-slate-600 rounded-lg focus:outline-none focus:border-red-500 transition">
<input type="password" id="password" placeholder="🔒 Password"
class="w-full px-4 py-3 bg-slate-700 border border-slate-600 rounded-lg focus:outline-none focus:border-red-500 transition">
<div class="flex gap-3">
<button onclick="login()"
class="flex-1 bg-red-600 hover:bg-red-700 text-white font-semibold py-3 rounded-lg transition">
Login
</button>
<button onclick="register()"
class="flex-1 bg-green-600 hover:bg-green-700 text-white font-semibold py-3 rounded-lg transition">
Register
</button>
</div>
<p id="authError" class="text-red-400 text-sm text-center hidden"></p>
</div>
</div>
</div>
<!-- Wishlist Screen -->
<div id="wishlistScreen" class="hidden min-h-screen p-4 md:p-8">
<div class="max-w-6xl mx-auto">
<div class="bg-slate-800 rounded-2xl shadow-2xl p-6 md:p-8 card-glow">
<div class="flex justify-between items-center mb-6">
<h1 class="text-3xl font-bold text-red-500">🎁 My Wishlist</h1>
<div class="flex gap-2">
<button onclick="shareWishlist()"
class="bg-yellow-500 hover:bg-yellow-600 px-4 py-2 rounded-lg transition text-black font-semibold">
Share
</button>
<button onclick="logout()"
class="bg-slate-700 hover:bg-slate-600 px-4 py-2 rounded-lg transition">
Logout
</button>
</div>
</div>
<!-- Add Item Form -->
<div class="bg-slate-700 rounded-xl p-6 mb-6">
<h2 class="text-xl font-semibold mb-4 text-green-400">✨ Add New Item</h2>
<div class="grid md:grid-cols-2 gap-4">
<input type="text" id="itemName" placeholder="🎁 Item name"
class="px-4 py-2 bg-slate-600 border border-slate-500 rounded-lg focus:outline-none focus:border-green-500 transition">
<input type="text" id="itemLink" placeholder="🔗 Link (optional)"
class="px-4 py-2 bg-slate-600 border border-slate-500 rounded-lg focus:outline-none focus:border-green-500 transition">
<input type="number" id="itemPrice" placeholder="💰 Price" step="0.01"
class="px-4 py-2 bg-slate-600 border border-slate-500 rounded-lg focus:outline-none focus:border-green-500 transition">
<input type="text" id="itemImage" placeholder="🖼️ Image URL (optional)"
class="px-4 py-2 bg-slate-600 border border-slate-500 rounded-lg focus:outline-none focus:border-green-500 transition">
</div>
<button onclick="addItem()"
class="mt-4 w-full bg-green-600 hover:bg-green-700 text-white font-semibold py-3 rounded-lg transition">
Add to Wishlist
</button>
</div>
<!-- Items List -->
<div id="itemsList" class="space-y-4 mb-6"></div>
<!-- Summary -->
<div class="bg-gradient-to-r from-red-900 to-green-900 rounded-xl p-6 text-center">
<div class="flex justify-center gap-8">
<div>
<p class="text-gray-300 text-sm">Total Items</p>
<p class="text-3xl font-bold" id="itemCount">0</p>
</div>
<div>
<p class="text-gray-300 text-sm">Total Value</p>
<p class="text-3xl font-bold" id="totalPrice">€0.00</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Edit Modal -->
<div id="editModal" class="hidden fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center p-4 z-50">
<div class="bg-slate-800 rounded-2xl p-6 w-full max-w-md card-glow">
<h2 class="text-2xl font-bold text-red-500 mb-4">✏️ Edit Item</h2>
<input type="hidden" id="editItemId">
<div class="space-y-4">
<input type="text" id="editItemName" placeholder="🎁 Item name"
class="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg focus:outline-none focus:border-red-500 transition">
<input type="text" id="editItemLink" placeholder="🔗 Link (optional)"
class="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg focus:outline-none focus:border-red-500 transition">
<input type="number" id="editItemPrice" placeholder="💰 Price" step="0.01"
class="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg focus:outline-none focus:border-red-500 transition">
<input type="text" id="editItemImage" placeholder="🖼️ Image URL (optional)"
class="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg focus:outline-none focus:border-red-500 transition">
<div class="flex gap-3">
<button onclick="saveEdit()"
class="flex-1 bg-green-600 hover:bg-green-700 text-white font-semibold py-3 rounded-lg transition">
Save
</button>
<button onclick="closeEditModal()"
class="flex-1 bg-slate-700 hover:bg-slate-600 text-white font-semibold py-3 rounded-lg transition">
Cancel
</button>
</div>
</div>
</div>
</div>
<!-- Share Modal -->
<div id="shareModal" class="hidden fixed inset-0 bg-black bg-opacity-75 flex items-center justify-center p-4 z-50">
<div class="bg-slate-800 rounded-2xl p-6 w-full max-w-md card-glow text-center">
<h2 class="text-2xl font-bold text-yellow-400 mb-4">🔗 Share Your Wishlist</h2>
<input id="shareLink" type="text" readonly class="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg text-yellow-300 mb-4 text-center" />
<a id="shareUiLink" href="#" target="_blank" class="block mb-4 text-blue-400 hover:underline break-all"></a>
<button onclick="copyShareLink()" class="bg-yellow-500 hover:bg-yellow-600 text-black font-semibold py-2 px-6 rounded-lg transition mb-2">Copy Link</button>
<button onclick="closeShareModal()" class="block w-full mt-2 bg-slate-700 hover:bg-slate-600 text-white font-semibold py-2 rounded-lg transition">Close</button>
</div>
</div>
<script>
let currentUser = null;
// Create snowflakes
function createSnowflakes() {
const container = document.getElementById('snowflakes');
for (let i = 0; i < 50; i++) {
const snowflake = document.createElement('div');
snowflake.className = 'snowflake';
snowflake.innerHTML = '❄️';
snowflake.style.left = Math.random() * 100 + '%';
snowflake.style.animationDuration = Math.random() * 3 + 2 + 's';
snowflake.style.animationDelay = Math.random() * 5 + 's';
snowflake.style.opacity = Math.random() * 0.6 + 0.4;
snowflake.style.fontSize = Math.random() * 10 + 10 + 'px';
container.appendChild(snowflake);
}
}
createSnowflakes();
async function login() {
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
try {
const response = await fetch('https://shsf-api.reversed.dev/api/exec/4/57aca15b-665e-4015-b7e3-526a87b39b33/login', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({username, password})
});
const data = await response.json();
if (response.ok) {
currentUser = username;
showWishlist();
loadItems();
} else {
showError(data.error || 'Login failed');
}
} catch (error) {
showError('Connection error');
}
}
async function register() {
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
try {
const response = await fetch('https://shsf-api.reversed.dev/api/exec/4/57aca15b-665e-4015-b7e3-526a87b39b33/register', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({username, password})
});
const data = await response.json();
if (response.ok) {
currentUser = username;
showWishlist();
loadItems();
} else {
showError(data.error || 'Registration failed');
}
} catch (error) {
showError('Connection error');
}
}
function showError(message) {
const errorEl = document.getElementById('authError');
errorEl.textContent = message;
errorEl.classList.remove('hidden');
setTimeout(() => errorEl.classList.add('hidden'), 3000);
}
function showWishlist() {
document.getElementById('authScreen').classList.add('hidden');
document.getElementById('wishlistScreen').classList.remove('hidden');
}
function logout() {
currentUser = null;
document.getElementById('authScreen').classList.remove('hidden');
document.getElementById('wishlistScreen').classList.add('hidden');
document.getElementById('username').value = '';
document.getElementById('password').value = '';
}
async function loadItems() {
try {
const response = await fetch(`https://shsf-api.reversed.dev/api/exec/4/57aca15b-665e-4015-b7e3-526a87b39b33/items?username=${currentUser}`);
const data = await response.json();
if (response.ok) {
displayItems(data.items);
}
} catch (error) {
console.error('Failed to load items:', error);
}
}
function displayItems(items) {
const container = document.getElementById('itemsList');
if (items.length === 0) {
container.innerHTML = '<p class="text-center text-gray-400 py-8">🎄 No items yet. Add your first wish!</p>';
updateSummary(items);
return;
}
container.innerHTML = items.map(item => `
<div class="bg-slate-700 rounded-xl p-4 flex gap-4">
${item.image ? `<img src="${item.image}" alt="${item.name}" class="w-24 h-24 object-cover rounded-lg">` :
'<div class="w-24 h-24 bg-slate-600 rounded-lg flex items-center justify-center text-4xl">🎁</div>'}
<div class="flex-1">
<h3 class="text-xl font-semibold text-red-400">${item.name}</h3>
${item.link ? `<a href="${item.link}" target="_blank" class="text-blue-400 hover:underline text-sm">🔗 View Item</a>` : ''}
<p class="text-2xl font-bold text-green-400 mt-2">${item.price ? '€' + parseFloat(item.price).toFixed(2) : 'No price'}</p>
</div>
<div class="flex flex-col gap-2">
<button onclick="editItem('${item.id}')"
class="bg-blue-600 hover:bg-blue-700 px-4 py-2 rounded-lg transition">
✏️ Edit
</button>
<button onclick="deleteItem('${item.id}')"
class="bg-red-600 hover:bg-red-700 px-4 py-2 rounded-lg transition">
🗑️ Delete
</button>
</div>
</div>
`).join('');
updateSummary(items);
}
function updateSummary(items) {
const count = items.length;
const total = items.reduce((sum, item) => sum + (parseFloat(item.price) || 0), 0);
document.getElementById('itemCount').textContent = count;
document.getElementById('totalPrice').textContent = '€' + total.toFixed(2);
}
async function addItem() {
const name = document.getElementById('itemName').value;
const link = document.getElementById('itemLink').value;
const price = document.getElementById('itemPrice').value;
const image = document.getElementById('itemImage').value;
if (!name) {
alert('Please enter an item name');
return;
}
try {
const response = await fetch('https://shsf-api.reversed.dev/api/exec/4/57aca15b-665e-4015-b7e3-526a87b39b33/add', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({username: currentUser, name, link, price, image})
});
if (response.ok) {
document.getElementById('itemName').value = '';
document.getElementById('itemLink').value = '';
document.getElementById('itemPrice').value = '';
document.getElementById('itemImage').value = '';
loadItems();
}
} catch (error) {
console.error('Failed to add item:', error);
}
}
function editItem(id) {
fetch(`https://shsf-api.reversed.dev/api/exec/4/57aca15b-665e-4015-b7e3-526a87b39b33/items?username=${currentUser}`)
.then(r => r.json())
.then(data => {
const item = data.items.find(i => i.id === id);
if (item) {
document.getElementById('editItemId').value = id;
document.getElementById('editItemName').value = item.name;
document.getElementById('editItemLink').value = item.link || '';
document.getElementById('editItemPrice').value = item.price || '';
document.getElementById('editItemImage').value = item.image || '';
document.getElementById('editModal').classList.remove('hidden');
}
});
}
async function saveEdit() {
const id = document.getElementById('editItemId').value;
const name = document.getElementById('editItemName').value;
const link = document.getElementById('editItemLink').value;
const price = document.getElementById('editItemPrice').value;
const image = document.getElementById('editItemImage').value;
try {
const response = await fetch('https://shsf-api.reversed.dev/api/exec/4/57aca15b-665e-4015-b7e3-526a87b39b33/update', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({username: currentUser, id, name, link, price, image})
});
if (response.ok) {
closeEditModal();
loadItems();
}
} catch (error) {
console.error('Failed to update item:', error);
}
}
function closeEditModal() {
document.getElementById('editModal').classList.add('hidden');
}
async function deleteItem(id) {
if (!confirm('Are you sure you want to delete this item?')) return;
try {
const response = await fetch('https://shsf-api.reversed.dev/api/exec/4/57aca15b-665e-4015-b7e3-526a87b39b33/delete', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({username: currentUser, id})
});
if (response.ok) {
loadItems();
}
} catch (error) {
console.error('Failed to delete item:', error);
}
}
async function shareWishlist() {
if (!currentUser) return;
try {
const response = await fetch('https://shsf-api.reversed.dev/api/exec/4/57aca15b-665e-4015-b7e3-526a87b39b33/share', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({username: currentUser})
});
const data = await response.json();
if (response.ok && data.share_id) {
// UI link that triggers redirect to UI with share_id
const uiLink = `https://shsf-api.reversed.dev/api/exec/4/57aca15b-665e-4015-b7e3-526a87b39b33/?share_id=${data.share_id}`;
document.getElementById('shareLink').value = uiLink;
document.getElementById('shareUiLink').href = uiLink;
document.getElementById('shareUiLink').textContent = uiLink;
document.getElementById('shareModal').classList.remove('hidden');
} else {
alert(data.error || 'Failed to share wishlist');
}
} catch (e) {
alert('Failed to share wishlist');
}
}
function closeShareModal() {
document.getElementById('shareModal').classList.add('hidden');
}
function copyShareLink() {
const input = document.getElementById('shareLink');
input.select();
input.setSelectionRange(0, 99999);
document.execCommand('copy');
}
// Public wishlist view
async function loadPublicWishlist(shareId) {
try {
const response = await fetch(`https://shsf-api.reversed.dev/api/exec/4/57aca15b-665e-4015-b7e3-526a87b39b33/public?share_id=${shareId}`);
const data = await response.json();
if (response.ok && data.items) {
document.getElementById('authScreen').classList.add('hidden');
document.getElementById('wishlistScreen').classList.remove('hidden');
// Hide add/edit/delete controls for public view
document.querySelectorAll('[onclick^="addItem"]').forEach(el => el.style.display = 'none');
document.querySelectorAll('[onclick^="editItem"]').forEach(el => el.style.display = 'none');
document.querySelectorAll('[onclick^="deleteItem"]').forEach(el => el.style.display = 'none');
document.querySelectorAll('[onclick^="shareWishlist"]').forEach(el => el.style.display = 'none');
displayItems(data.items);
} else {
alert(data.error || 'Shared wishlist not found');
}
} catch (e) {
alert('Failed to load shared wishlist');
}
}
// On page load, check for share_id in URL
(function() {
const params = new URLSearchParams(window.location.search);
const shareId = params.get('share_id');
if (shareId) {
loadPublicWishlist(shareId);
}
})();
</script>
</body>
</html>