mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-18 04:29:40 +02:00
Move inactive projects out of active workspace: - bauntown (community website) - maerchenzauber (AI story generation) - memoro (voice memo app) - news (news aggregation) - nutriphi (nutrition tracking) - reader (reading app) - uload (URL shortener) - wisekeep (AI wisdom extraction) Update CLAUDE.md documentation: - Add presi to active projects - Document archived projects section - Update workspace configuration Archived apps can be re-activated by moving back to apps/ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
424 lines
13 KiB
HTML
424 lines
13 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Charakter teilen | Märchenzauber</title>
|
|
|
|
<!-- Meta tags will be updated dynamically -->
|
|
<meta name="description" content="Entdecke magische Charaktere in Märchenzauber">
|
|
<meta property="og:type" content="website">
|
|
<meta property="og:title" content="Charakter teilen | Märchenzauber">
|
|
<meta property="og:description" content="Entdecke magische Charaktere in Märchenzauber">
|
|
<meta property="og:image" content="/og-image.jpg">
|
|
|
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
<link href="https://fonts.googleapis.com/css2?family=Grandstander:wght@400;700&display=swap" rel="stylesheet">
|
|
|
|
<style>
|
|
* {
|
|
margin: 0;
|
|
padding: 0;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;
|
|
background: linear-gradient(135deg, #f5f0ff 0%, #ffe5f0 50%, #e0f0ff 100%);
|
|
min-height: 100vh;
|
|
padding: 20px;
|
|
}
|
|
|
|
.container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 40px 20px;
|
|
}
|
|
|
|
.loader {
|
|
text-align: center;
|
|
padding: 60px 20px;
|
|
}
|
|
|
|
.spinner {
|
|
border: 4px solid #f3f3f3;
|
|
border-top: 4px solid #9333ea;
|
|
border-radius: 50%;
|
|
width: 50px;
|
|
height: 50px;
|
|
animation: spin 1s linear infinite;
|
|
margin: 0 auto 20px;
|
|
}
|
|
|
|
@keyframes spin {
|
|
0% { transform: rotate(0deg); }
|
|
100% { transform: rotate(360deg); }
|
|
}
|
|
|
|
.character-card {
|
|
background: white;
|
|
border-radius: 24px;
|
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
|
|
overflow: hidden;
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 40px;
|
|
padding: 40px;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.character-card {
|
|
grid-template-columns: 1fr;
|
|
gap: 20px;
|
|
padding: 20px;
|
|
}
|
|
}
|
|
|
|
h1 {
|
|
font-family: 'Grandstander', cursive;
|
|
font-size: 2.5rem;
|
|
color: #1a1a1a;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.images-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 16px;
|
|
}
|
|
|
|
.image-container {
|
|
aspect-ratio: 1;
|
|
border-radius: 16px;
|
|
overflow: hidden;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.image-container img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
}
|
|
|
|
.character-info {
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.description {
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.description h2 {
|
|
font-size: 1.25rem;
|
|
color: #333;
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.description p {
|
|
color: #666;
|
|
line-height: 1.6;
|
|
}
|
|
|
|
.info-box {
|
|
background: linear-gradient(135deg, #f3e8ff 0%, #fce7f3 100%);
|
|
border-radius: 16px;
|
|
padding: 24px;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.info-box p {
|
|
color: #4a4a4a;
|
|
line-height: 1.6;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.info-box p:last-child {
|
|
margin-bottom: 0;
|
|
font-size: 0.9rem;
|
|
color: #666;
|
|
}
|
|
|
|
.buttons {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
|
|
.btn {
|
|
padding: 16px 24px;
|
|
border-radius: 16px;
|
|
font-weight: 600;
|
|
font-size: 1rem;
|
|
text-align: center;
|
|
text-decoration: none;
|
|
cursor: pointer;
|
|
border: none;
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.btn-primary {
|
|
background: linear-gradient(135deg, #9333ea 0%, #ec4899 100%);
|
|
color: white;
|
|
box-shadow: 0 4px 12px rgba(147, 51, 234, 0.3);
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 6px 16px rgba(147, 51, 234, 0.4);
|
|
}
|
|
|
|
.btn-secondary {
|
|
background: white;
|
|
color: #9333ea;
|
|
border: 2px solid #9333ea;
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background: #f9f5ff;
|
|
}
|
|
|
|
.error {
|
|
text-align: center;
|
|
padding: 60px 20px;
|
|
}
|
|
|
|
.error-icon {
|
|
font-size: 4rem;
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.error h1 {
|
|
font-family: 'Grandstander', cursive;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.error p {
|
|
color: #666;
|
|
margin-bottom: 32px;
|
|
}
|
|
|
|
.how-it-works {
|
|
background: white;
|
|
border-radius: 24px;
|
|
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
|
|
padding: 40px;
|
|
margin-top: 40px;
|
|
}
|
|
|
|
.how-it-works h2 {
|
|
font-family: 'Grandstander', cursive;
|
|
font-size: 1.75rem;
|
|
margin-bottom: 32px;
|
|
text-align: center;
|
|
}
|
|
|
|
.steps {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
gap: 32px;
|
|
}
|
|
|
|
.step {
|
|
text-align: center;
|
|
}
|
|
|
|
.step-icon {
|
|
font-size: 3rem;
|
|
margin-bottom: 16px;
|
|
}
|
|
|
|
.step h3 {
|
|
font-size: 1.125rem;
|
|
margin-bottom: 12px;
|
|
color: #333;
|
|
}
|
|
|
|
.step p {
|
|
font-size: 0.9rem;
|
|
color: #666;
|
|
line-height: 1.5;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div id="loading" class="loader">
|
|
<div class="spinner"></div>
|
|
<p>Charakter wird geladen...</p>
|
|
</div>
|
|
|
|
<div id="content" style="display: none;"></div>
|
|
</div>
|
|
|
|
<script>
|
|
const BACKEND_URL = 'https://storyteller-backend-111768794939.europe-west3.run.app';
|
|
|
|
// Parse URL to get character ID and share code
|
|
// URL format: /character/{id}/{shareCode}/
|
|
const pathParts = window.location.pathname.split('/').filter(Boolean);
|
|
const characterId = pathParts[1]; // character is at index 0
|
|
const shareCode = pathParts[2];
|
|
|
|
console.log('Character ID:', characterId, 'Share Code:', shareCode);
|
|
|
|
async function loadCharacter() {
|
|
if (!characterId || !shareCode) {
|
|
showError('Ungültiger Link', 'Dieser Link ist ungültig oder unvollständig.');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`${BACKEND_URL}/characters/public/${characterId}/${shareCode}`);
|
|
|
|
if (!response.ok) {
|
|
throw new Error('Character not found');
|
|
}
|
|
|
|
const character = await response.json();
|
|
|
|
if (character.error) {
|
|
throw new Error(character.error);
|
|
}
|
|
|
|
// Update meta tags for social sharing
|
|
updateMetaTags(character);
|
|
|
|
// Display character
|
|
displayCharacter(character);
|
|
|
|
// Auto-redirect on mobile devices
|
|
if (isMobile()) {
|
|
setTimeout(() => {
|
|
tryOpenApp();
|
|
}, 500);
|
|
}
|
|
} catch (error) {
|
|
console.error('Error loading character:', error);
|
|
showError('Charakter nicht gefunden', 'Dieser Charakter existiert nicht oder der Freigabelink ist ungültig.');
|
|
} finally {
|
|
document.getElementById('loading').style.display = 'none';
|
|
document.getElementById('content').style.display = 'block';
|
|
}
|
|
}
|
|
|
|
function updateMetaTags(character) {
|
|
document.title = `${character.name} - Teile diesen Charakter | Märchenzauber`;
|
|
|
|
const description = `Schau dir '${character.name}' an - ein magischer Charakter aus Märchenzauber! Lade die App herunter und füge diesen Charakter zu deiner Bibliothek hinzu.`;
|
|
document.querySelector('meta[name="description"]').setAttribute('content', description);
|
|
document.querySelector('meta[property="og:title"]').setAttribute('content', `${character.name} | Märchenzauber`);
|
|
document.querySelector('meta[property="og:description"]').setAttribute('content', description);
|
|
|
|
if (character.imageUrls && character.imageUrls[0]) {
|
|
document.querySelector('meta[property="og:image"]').setAttribute('content', character.imageUrls[0]);
|
|
}
|
|
}
|
|
|
|
function displayCharacter(character) {
|
|
const content = document.getElementById('content');
|
|
|
|
const imagesHTML = character.imageUrls?.slice(0, 4).map(url => `
|
|
<div class="image-container">
|
|
<img src="${url}" alt="${character.name}" loading="lazy">
|
|
</div>
|
|
`).join('') || '';
|
|
|
|
content.innerHTML = `
|
|
<div class="character-card">
|
|
<div>
|
|
<h1>${character.name}</h1>
|
|
<div class="images-grid">
|
|
${imagesHTML}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="character-info">
|
|
<div>
|
|
${character.description ? `
|
|
<div class="description">
|
|
<h2>Beschreibung</h2>
|
|
<p>${character.description}</p>
|
|
</div>
|
|
` : ''}
|
|
|
|
<div class="info-box">
|
|
<p>Dieser magische Charakter wurde mit Märchenzauber erstellt.</p>
|
|
<p>Lade die App herunter, um diesen Charakter zu deiner Bibliothek hinzuzufügen und eigene personalisierte Geschichten zu erstellen!</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="buttons">
|
|
<button class="btn btn-primary" onclick="tryOpenApp()">
|
|
🎨 In App öffnen
|
|
</button>
|
|
<a href="/download" class="btn btn-secondary">
|
|
📱 App herunterladen
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="how-it-works">
|
|
<h2>So funktioniert's</h2>
|
|
<div class="steps">
|
|
<div class="step">
|
|
<div class="step-icon">📱</div>
|
|
<h3>1. App öffnen</h3>
|
|
<p>Tippe auf "In App öffnen" oder lade Märchenzauber herunter</p>
|
|
</div>
|
|
<div class="step">
|
|
<div class="step-icon">⭐</div>
|
|
<h3>2. Charakter hinzufügen</h3>
|
|
<p>Der Charakter wird automatisch zu deiner Bibliothek hinzugefügt</p>
|
|
</div>
|
|
<div class="step">
|
|
<div class="step-icon">📖</div>
|
|
<h3>3. Geschichten erstellen</h3>
|
|
<p>Nutze den Charakter in deinen eigenen magischen Geschichten</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function showError(title, message) {
|
|
const content = document.getElementById('content');
|
|
content.innerHTML = `
|
|
<div class="error">
|
|
<div class="error-icon">😢</div>
|
|
<h1>${title}</h1>
|
|
<p>${message}</p>
|
|
<a href="/" class="btn btn-primary">Zur Startseite</a>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
function tryOpenApp() {
|
|
// Try custom scheme (universal links will intercept this on iOS/Android)
|
|
const deepLink = `maerchenzauber://share/character/${characterId}/${shareCode}`;
|
|
|
|
// iOS/Android will automatically intercept HTTPS URLs if app is installed
|
|
// For custom scheme fallback compatibility
|
|
window.location.href = deepLink;
|
|
|
|
// Fallback to download page after 2 seconds if app didn't open
|
|
setTimeout(() => {
|
|
if (!document.hidden) {
|
|
window.location.href = '/download';
|
|
}
|
|
}, 2000);
|
|
}
|
|
|
|
function isMobile() {
|
|
return /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
|
|
}
|
|
|
|
// Load character on page load
|
|
loadCharacter();
|
|
</script>
|
|
</body>
|
|
</html>
|