remove translations
Build and Deploy / build-and-deploy (push) Failing after 1m5s

This commit is contained in:
2026-05-18 12:57:33 +02:00
parent 0d02c1f1c9
commit 10c7ab699f
10 changed files with 59 additions and 699 deletions
-238
View File
@@ -1,238 +0,0 @@
# Content Editor Guide for Theater Ziefen Website
This guide is for non-technical team members who will be managing the website content.
## 📋 Quick Start
### What You Need
- The development server running (ask your developer to set this up)
- A web browser
- Basic knowledge of writing and formatting text
### Accessing the Editor
1. Open your web browser
2. Go to: `http://localhost:4321/admin`
3. You'll see the TinaCMS admin panel
## ✍️ Creating and Editing Blog Posts
### Creating a New Blog Post
1. **Navigate to Blog Posts**
- In the TinaCMS admin panel, click on "Blog Posts" in the left sidebar
2. **Create New Post**
- Click the "Create New" button
- You'll see a form with several fields
3. **Fill in the Post Details**
- **Title**: Enter your blog post title (e.g., "Rehearsal Updates for March")
- **Publication Date**: Click to select the date (usually today's date)
- **Excerpt**: Write a short summary (2-3 sentences) - this appears on the blog listing page
- **Cover Image**: (Optional) Click "Upload" to add a featured image
4. **Write Your Content**
- Use the rich text editor to write your blog post
- Formatting options available:
- **Bold**: Select text and click B
- **Italic**: Select text and click I
- **Headings**: Use ## for main headings, ### for subheadings
- **Lists**: Click the bullet or numbered list icons
- **Links**: Select text, click the link icon, enter URL
5. **Save Your Post**
- Click "Save" in the top right
- Your post will appear on the website immediately!
### Editing an Existing Blog Post
1. Go to "Blog Posts" in the sidebar
2. Click on the post you want to edit
3. Make your changes
4. Click "Save"
### Deleting a Blog Post
1. Go to "Blog Posts" in the sidebar
2. Click on the post you want to delete
3. Click the "Delete" button
4. Confirm the deletion
## 📄 Editing Static Pages
### Editing the About Page
1. In the TinaCMS admin panel, click on "Pages" in the left sidebar
2. Select "About Theater Ziefen"
3. Edit the content using the rich text editor
4. Click "Save"
### Content Tips for Pages
- **Keep it concise**: Visitors typically scan pages, so use short paragraphs
- **Use headings**: Break up content with headings (## Main Heading, ### Subheading)
- **Add lists**: Bullet points make information easy to scan
- **Include calls to action**: Encourage visitors to check the blog, contact you, etc.
## 🖼️ Working with Images
### Adding Images to Blog Posts
1. In the blog post editor, find the "Cover Image" field
2. Click "Upload"
3. Select your image file (JPG or PNG recommended)
4. The image will be uploaded and displayed
### Image Best Practices
- **Size**: Keep images under 2MB for fast loading
- **Dimensions**: 1200x630 pixels works well for blog post covers
- **Format**: Use JPG for photos, PNG for graphics with text
- **File names**: Use descriptive names (e.g., `rehearsal-march-2026.jpg` instead of `IMG_1234.jpg`)
## 📝 Formatting Tips
### Markdown Basics
The editor uses Markdown, a simple formatting language:
```
# Heading 1 (largest)
## Heading 2
### Heading 3
**Bold text**
*Italic text*
- Bullet point 1
- Bullet point 2
1. Numbered item 1
2. Numbered item 2
[Link text](https://example.com)
```
### Writing Style Guidelines
**For Blog Posts:**
- Use a conversational, friendly tone
- Include dates for time-specific information
- Add personal touches and behind-the-scenes details
- End with a call to action or question
**For Static Pages:**
- Be clear and informative
- Use headings to organize information
- Keep paragraphs short (3-4 lines max)
- Update regularly with current information
## 🔄 Publishing Workflow
### How Changes Go Live
1. **You save your changes** in the TinaCMS editor
2. **Changes are saved to Git** automatically
3. **GitHub receives the changes**
4. **Netlify deploys the updated site** (takes 2-3 minutes)
5. **Your changes are live!**
### Checking Your Published Changes
1. Wait 2-3 minutes after saving
2. Visit the live website
3. Refresh your browser to see the changes
## ⚠️ Common Issues and Solutions
### Problem: Can't access `/admin`
**Solution:** Make sure the development server is running. Ask your developer to start it with `npm run dev`.
### Problem: Changes not appearing
**Solution:**
1. Make sure you clicked "Save"
2. Wait a few minutes for deployment
3. Hard refresh your browser (Ctrl+F5 or Cmd+Shift+R)
4. Check if you're viewing the correct URL (development vs. production)
### Problem: Image won't upload
**Solution:**
1. Check file size (should be under 5MB)
2. Make sure it's a supported format (JPG, PNG, GIF)
3. Try renaming the file to remove special characters
### Problem: Formatting looks weird
**Solution:**
1. Try removing all formatting and reapplying it
2. Make sure you're using valid Markdown syntax
3. Preview your changes before saving
## 📅 Content Planning Tips
### Blog Post Ideas
- Rehearsal updates and progress reports
- Cast and crew spotlights
- Behind-the-scenes photos and stories
- Production timeline updates
- Ticket sales announcements (when ready)
- Event announcements
- Community involvement opportunities
- Historical context about the production
### Posting Frequency
- **Minimum**: 1-2 posts per month to keep the site active
- **Ideal**: 1 post per week during active production
- **Special occasions**: Extra posts for major announcements
### Content Calendar
Create a simple calendar with:
- Upcoming post topics
- Important dates to announce
- Seasonal content (holidays, seasons)
- Milestone celebrations
## 🎯 SEO Tips (Optional but Helpful)
### Make Your Content Searchable
1. **Use descriptive titles**: Instead of "Update", use "March 2026 Rehearsal Schedule"
2. **Write good excerpts**: Include key information in the first 2-3 sentences
3. **Use relevant keywords**: Include terms people might search for
4. **Add alt text to images**: Describe what's in the image
## 🆘 Getting Help
### If You Need Assistance
1. **Check this guide first**
2. **Ask your developer** for technical issues
3. **Consult the README.md** for more detailed information
4. **Visit TinaCMS documentation**: https://tina.io/docs
### Quick Reference
- Admin Panel: `http://localhost:4321/admin`
- Live Website: (your website URL)
- Save Button: Top right corner of editor
- Preview: View your page before saving
## 📞 Support Contacts
- **Technical Issues**: [Developer contact]
- **Content Questions**: [Content manager contact]
- **General Questions**: info@theater-ziefen.ch
---
**Remember**: Don't be afraid to experiment! You can always undo changes or ask for help. The system is designed to be user-friendly, so feel free to explore and learn as you go.
Happy editing! 🎭
-257
View File
@@ -1,257 +0,0 @@
/**
* One-shot migration script: TinaCMS MDX files → Sanity documents
*
* Prerequisites:
* npm install --save-dev gray-matter @portabletext/markdown
*
* Usage:
* npm run migrate
*
* Revoke SANITY_API_TOKEN after migration is complete.
*/
import { createClient } from '@sanity/client'
import { createReadStream, readdirSync, readFileSync, existsSync } from 'fs'
import { join, basename } from 'path'
import matter from 'gray-matter'
import { markdownToPortableText } from '@portabletext/markdown'
const PROJECT_ID = process.env.PUBLIC_SANITY_PROJECT_ID
const TOKEN = process.env.SANITY_API_TOKEN
const DATASET = process.env.PUBLIC_SANITY_DATASET ?? 'production'
if (!PROJECT_ID || !TOKEN) {
console.error('Missing PUBLIC_SANITY_PROJECT_ID or SANITY_API_TOKEN environment variables.')
process.exit(1)
}
const client = createClient({
projectId: PROJECT_ID,
dataset: DATASET,
apiVersion: '2024-01-01',
token: TOKEN,
useCdn: false,
})
function randomKey() {
return Math.random().toString(36).slice(2, 10)
}
function emptyPortableText() {
return []
}
function stringToPortableText(text) {
if (!text || text === '') return emptyPortableText()
return [
{
_type: 'block',
_key: randomKey(),
style: 'normal',
markDefs: [],
children: [{ _type: 'span', _key: randomKey(), text, marks: [] }],
},
]
}
// Upload a local image file to Sanity and return a Sanity image reference
async function uploadImageFile(localPath, label) {
if (!existsSync(localPath)) {
console.warn(` Skipping missing image: ${localPath}`)
return null
}
console.log(` Uploading image: ${label}`)
const asset = await client.assets.upload('image', createReadStream(localPath), {
filename: basename(localPath),
})
return { _type: 'image', asset: { _type: 'reference', _ref: asset._id } }
}
// Map /images/filename.ext → public/images/filename.ext (relative to project root)
function localPathForImageUrl(imageUrl) {
if (!imageUrl || !imageUrl.startsWith('/images/')) return null
return join('public', imageUrl)
}
// ---------------------------------------------------------------------------
// Migrate theaterHomepage singleton
// ---------------------------------------------------------------------------
async function migrateTheaterHomepage() {
console.log('\n--- Migrating theaterHomepage ---')
const raw = readFileSync('content/theater/homepage.mdx', 'utf-8')
const { data: fm } = matter(raw)
// Pre-upload all organization logos
const orgs = (fm.team?.organizations ?? [])
const uploadedLogos = {}
for (const org of orgs) {
if (org.logo && !uploadedLogos[org.logo]) {
const localPath = localPathForImageUrl(org.logo)
if (localPath) {
uploadedLogos[org.logo] = await uploadImageFile(localPath, org.logo)
}
}
}
const doc = {
_type: 'theaterHomepage',
_id: 'theaterHomepage',
title: fm.title,
hero: fm.hero ?? {},
projekt: {
title_de: fm.projekt?.title_de ?? '',
title_en: fm.projekt?.title_en ?? '',
content_de: stringToPortableText(fm.projekt?.content_de),
content_en: stringToPortableText(fm.projekt?.content_en),
},
team: {
title_de: fm.team?.title_de ?? '',
title_en: fm.team?.title_en ?? '',
content_de: stringToPortableText(fm.team?.content_de),
content_en: stringToPortableText(fm.team?.content_en),
organizations: orgs.map((org) => ({
_key: randomKey(),
name: org.name,
role_de: org.role_de,
role_en: org.role_en,
url: org.url,
...(org.logo && uploadedLogos[org.logo] ? { logo: uploadedLogos[org.logo] } : {}),
})),
},
stueck: {
title_de: fm.stueck?.title_de ?? '',
title_en: fm.stueck?.title_en ?? '',
content_de: stringToPortableText(fm.stueck?.content_de),
content_en: stringToPortableText(fm.stueck?.content_en),
},
mitwirkung: {
title_de: fm.mitwirkung?.title_de ?? '',
title_en: fm.mitwirkung?.title_en ?? '',
content_de: stringToPortableText(fm.mitwirkung?.content_de),
content_en: stringToPortableText(fm.mitwirkung?.content_en),
director_name: fm.mitwirkung?.director_name,
director_email: fm.mitwirkung?.director_email,
director_phone: fm.mitwirkung?.director_phone,
},
termine: {
title_de: fm.termine?.title_de ?? '',
title_en: fm.termine?.title_en ?? '',
events: (fm.termine?.events ?? []).map((e) => ({ ...e, _key: randomKey() })),
},
presse: {
title_de: fm.presse?.title_de ?? '',
title_en: fm.presse?.title_en ?? '',
content_de: stringToPortableText(fm.presse?.content_de),
content_en: stringToPortableText(fm.presse?.content_en),
},
gallery: {
title_de: fm.gallery?.title_de ?? '',
title_en: fm.gallery?.title_en ?? '',
images: [],
},
tickets: {
title_de: fm.tickets?.title_de ?? '',
title_en: fm.tickets?.title_en ?? '',
content_de: stringToPortableText(fm.tickets?.content_de),
content_en: stringToPortableText(fm.tickets?.content_en),
...(fm.tickets?.ticket_url ? { ticket_url: fm.tickets.ticket_url } : {}),
},
}
await client.createOrReplace(doc)
console.log(' ✓ theaterHomepage created/replaced')
}
// ---------------------------------------------------------------------------
// Migrate blog posts
// ---------------------------------------------------------------------------
async function migratePosts() {
console.log('\n--- Migrating blog posts ---')
const blogDir = 'content/blog'
if (!existsSync(blogDir)) {
console.log(' No blog directory found, skipping.')
return
}
const files = readdirSync(blogDir).filter((f) => f.endsWith('.mdx'))
for (const file of files) {
const slug = file.replace('.mdx', '')
const raw = readFileSync(join(blogDir, file), 'utf-8')
const { data: fm, content: body } = matter(raw)
const portableTextBody = body.trim()
? markdownToPortableText(body)
: []
// Upload cover image if present and exists locally
let coverImage
if (fm.coverImage) {
const localPath = localPathForImageUrl(fm.coverImage)
if (localPath) {
coverImage = await uploadImageFile(localPath, fm.coverImage)
}
}
const doc = {
_type: 'post',
_id: `post-${slug}`,
title: fm.title,
slug: { _type: 'slug', current: slug },
date: fm.date,
excerpt: fm.excerpt,
body: portableTextBody,
...(coverImage ? { coverImage } : {}),
}
await client.createOrReplace(doc)
console.log(` ✓ Post migrated: ${slug}`)
}
}
// ---------------------------------------------------------------------------
// Migrate static pages
// ---------------------------------------------------------------------------
async function migratePages() {
console.log('\n--- Migrating pages ---')
const pagesDir = 'content/pages'
if (!existsSync(pagesDir)) {
console.log(' No pages directory found, skipping.')
return
}
const files = readdirSync(pagesDir).filter((f) => f.endsWith('.mdx'))
for (const file of files) {
const slug = file.replace('.mdx', '')
const raw = readFileSync(join(pagesDir, file), 'utf-8')
const { data: fm, content: body } = matter(raw)
const portableTextBody = body.trim()
? markdownToPortableText(body)
: []
const doc = {
_type: 'page',
_id: `page-${slug}`,
title: fm.title,
slug: { _type: 'slug', current: slug },
description: fm.description,
body: portableTextBody,
}
await client.createOrReplace(doc)
console.log(` ✓ Page migrated: ${slug}`)
}
}
// ---------------------------------------------------------------------------
// Run
// ---------------------------------------------------------------------------
try {
await migrateTheaterHomepage()
await migratePosts()
await migratePages()
console.log('\n✓ Migration complete.')
} catch (err) {
console.error('\n✗ Migration failed:', err)
process.exit(1)
}
+10 -12
View File
@@ -1,13 +1,13 @@
---
const navItems = [
{ id: 'projekt', label_de: 'Das Projekt', label_en: 'The Project' },
{ id: 'team', label_de: 'Wer steckt dahinter', label_en: 'Who\'s Behind It' },
{ id: 'stueck', label_de: 'Das Stück', label_en: 'The Play' },
{ id: 'mitwirkung', label_de: 'Mitwirkung', label_en: 'Participation' },
{ id: 'termine', label_de: 'Termine', label_en: 'Schedule' },
{ id: 'presse', label_de: 'Presse', label_en: 'Press' },
{ id: 'gallery', label_de: 'Galerie', label_en: 'Gallery' },
{ id: 'tickets', label_de: 'Tickets', label_en: 'Tickets' },
{ id: 'projekt', label: 'Das Projekt' },
{ id: 'team', label: 'Wer steckt dahinter' },
{ id: 'stueck', label: 'Das Stück' },
{ id: 'mitwirkung', label: 'Mitwirkung' },
{ id: 'termine', label: 'Termine' },
{ id: 'presse', label: 'Presse' },
{ id: 'gallery', label: 'Galerie' },
{ id: 'tickets', label: 'Tickets' },
];
---
@@ -23,8 +23,7 @@ const navItems = [
class="nav-link block text-white no-underline py-1.5 px-3 rounded transition-all duration-300 text-[0.85rem] font-medium whitespace-nowrap hover:bg-white/20 hover:-translate-y-0.5 [&.active]:bg-white/30 [&.active]:font-bold"
data-section={item.id}
>
<span class="nav-label-de" data-lang="de">{item.label_de}</span>
<span class="nav-label-en" data-lang="en">{item.label_en}</span>
{item.label}
</a>
</li>
))}
@@ -69,8 +68,7 @@ const navItems = [
class="mobile-nav-link nav-link flex items-center text-white no-underline py-3 px-3 rounded transition-all duration-200 text-base font-medium hover:bg-white/20 [&.active]:bg-white/30 [&.active]:font-bold"
data-section={item.id}
>
<span class="nav-label-de" data-lang="de">{item.label_de}</span>
<span class="nav-label-en" data-lang="en">{item.label_en}</span>
{item.label}
</a>
</li>
))}
-55
View File
@@ -1,55 +0,0 @@
---
interface Props {
currentLang: 'de' | 'en';
}
const { currentLang } = Astro.props;
---
<div class="flex items-center gap-2">
<button
class="lang-btn bg-transparent border-none text-white text-base md:text-sm font-medium cursor-pointer p-2 transition-all duration-300 opacity-60 hover:opacity-100 hover:scale-110 data-[active=true]:opacity-100 data-[active=true]:font-bold data-[active=true]:underline"
data-lang="de"
data-active={currentLang === 'de'}
>
DE
</button>
<span class="text-white opacity-40">|</span>
<button
class="lang-btn bg-transparent border-none text-white text-base md:text-sm font-medium cursor-pointer p-2 transition-all duration-300 opacity-60 hover:opacity-100 hover:scale-110 data-[active=true]:opacity-100 data-[active=true]:font-bold data-[active=true]:underline"
data-lang="en"
data-active={currentLang === 'en'}
>
EN
</button>
</div>
<script>
// Initialize language from localStorage or default to 'de'
let currentLanguage = localStorage.getItem('language') || 'de';
// Apply language on page load
document.documentElement.setAttribute('data-language', currentLanguage);
updateActiveButton();
// Add click handlers to language buttons
document.querySelectorAll('.lang-btn').forEach(button => {
button.addEventListener('click', (e) => {
const target = e.target as HTMLElement;
const lang = target.getAttribute('data-lang');
if (lang) {
currentLanguage = lang;
localStorage.setItem('language', lang);
document.documentElement.setAttribute('data-language', lang);
updateActiveButton();
}
});
});
function updateActiveButton() {
document.querySelectorAll('.lang-btn').forEach(btn => {
const btnLang = btn.getAttribute('data-lang');
btn.setAttribute('data-active', (btnLang === currentLanguage).toString());
});
}
</script>
+1 -2
View File
@@ -18,7 +18,6 @@ const { images } = Astro.props;
</div>
) : (
<div class="text-center p-[--spacing-xl] bg-white/10 rounded-lg italic">
<p class="placeholder-de m-0 text-lg" data-lang="de">Fotos werden bald hinzugefügt. Besuchen Sie uns später wieder!</p>
<p class="placeholder-en m-0 text-lg" data-lang="en">Photos will be added soon. Check back later!</p>
<p class="m-0 text-lg">Fotos werden bald hinzugefügt. Besuchen Sie uns später wieder!</p>
</div>
)}
+3 -5
View File
@@ -1,19 +1,17 @@
---
interface Props {
id: string;
titleDe: string;
titleEn: string;
title: string;
variant?: 'light' | 'dark';
}
const { id, titleDe, titleEn, variant = 'light' } = Astro.props;
const { id, title, variant = 'light' } = Astro.props;
---
<section class={`theater-section py-12 px-4 md:py-20 md:px-16 scroll-mt-[100px] ${variant === 'light' ? 'bg-[#3d3d6f]' : 'bg-primary'} text-white`} id={id}>
<div class="max-w-7xl mx-auto">
<h2 class="font-heading text-4xl md:text-6xl mb-8 text-center font-normal text-white [text-shadow:2px_2px_4px_rgba(0,0,0,0.3)]">
<span class="title-de" data-lang="de">{titleDe}</span>
<span class="title-en" data-lang="en">{titleEn}</span>
{title}
</h2>
<div class="section-content text-lg md:text-base leading-relaxed">
<slot />
+1 -8
View File
@@ -10,7 +10,7 @@ const { title, description = "Theater Ziefen - Coming 2029" } = Astro.props;
---
<!doctype html>
<html lang="de" data-language="de">
<html lang="de">
<head>
<meta charset="UTF-8" />
<meta name="description" content={description} />
@@ -21,13 +21,6 @@ const { title, description = "Theater Ziefen - Coming 2029" } = Astro.props;
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Great+Vibes&display=swap" rel="stylesheet">
<title>{title} | Theater Ziefen</title>
<script is:inline>
// Set language immediately before page renders
(function() {
const savedLang = localStorage.getItem('language') || 'de';
document.documentElement.setAttribute('data-language', savedLang);
})();
</script>
</head>
<body>
<main class="min-h-[calc(100vh-200px)] flex flex-col items-center *:w-full">
+23 -52
View File
@@ -23,13 +23,11 @@ const content = await sanityClient.fetch(theaterHomepageQuery) ?? {};
<div class="max-w-[--max-width-container] mx-auto px-[--spacing-md] md:px-[--spacing-sm]">
<div class="relative flex flex-col items-center md:items-start gap-2 z-8 text-center md:text-left md:pl-12 md:pl-16 min-h-96 md:min-h-80 ">
<h1 class="text-6xl md:text-8xl md:pt-24 md:h-auto font-normalmd:mb-4 leading-[1.3] [text-shadow:2px_2px_4px_rgba(0,0,0,0.3)]">
<span class="title-de" data-lang="de">{content.hero?.title_de || 'Freilichttheater in Ziefen'}</span>
<span class="title-en" data-lang="en">{content.hero?.title_en || 'Open-Air Theater in Ziefen'}</span>
{content.hero?.title || 'Freilichttheater in Ziefen'}
</h1>
<img src="/logo.svg" alt="Theater Ziefen Logo" class="max-h-[30rem] md:absolute md:right-2 md:right-10 opacity-95 z-[5] md:opacity-70" />
<p class="text-xl md:text-lg leading-relaxed mb-8 md:mb-6 opacity-95 max-w-2xl ">
<span class="subtitle-de" data-lang="de">{content.hero?.subtitle_de || ''}</span>
<span class="subtitle-en" data-lang="en">{content.hero?.subtitle_en || ''}</span>
{content.hero?.subtitle || ''}
</p>
<div class="font-heading text-8xl md:text-6xl font-normal text-accent [text-shadow:3px_3px_6px_rgba(0,0,0,0.4)] mt-4">2028</div>
</div>
@@ -39,23 +37,19 @@ const content = await sanityClient.fetch(theaterHomepageQuery) ?? {};
<!-- Section 1: Das Projekt -->
<TheaterSection
id="projekt"
titleDe={content.projekt?.title_de || 'Das Projekt'}
titleEn={content.projekt?.title_en || 'The Project'}
title={content.projekt?.title || 'Das Projekt'}
variant="light"
>
<div class="content-de" data-lang="de" set:html={renderPortableText(content.projekt?.content_de)} />
<div class="content-en" data-lang="en" set:html={renderPortableText(content.projekt?.content_en)} />
<div set:html={renderPortableText(content.projekt?.content)} />
</TheaterSection>
<!-- Section 2: Wer steckt dahinter -->
<TheaterSection
id="team"
titleDe={content.team?.title_de || 'Wer steckt dahinter'}
titleEn={content.team?.title_en || "Who's Behind It"}
title={content.team?.title || 'Wer steckt dahinter'}
variant="dark"
>
<div class="content-de" data-lang="de" set:html={renderPortableText(content.team?.content_de)} />
<div class="content-en" data-lang="en" set:html={renderPortableText(content.team?.content_en)} />
<div set:html={renderPortableText(content.team?.content)} />
{content.team?.organizations && content.team.organizations.length > 0 && (
<div class="grid grid-cols-1 md:grid-cols-[repeat(auto-fit,minmax(250px,1fr))] gap-12 mt-16">
@@ -68,8 +62,7 @@ const content = await sanityClient.fetch(theaterHomepageQuery) ?? {};
</div>
)}
<h3 class="text-3xl mb-2 text-white">{org.name}</h3>
<p class="org-role-de m-0 opacity-90" data-lang="de">{org.role_de}</p>
<p class="org-role-en m-0 opacity-90" data-lang="en">{org.role_en}</p>
<p class="org-role m-0 opacity-90">{org.role}</p>
</Fragment>
);
@@ -95,44 +88,33 @@ const content = await sanityClient.fetch(theaterHomepageQuery) ?? {};
<!-- Section 3: Das Stück -->
<TheaterSection
id="stueck"
titleDe={content.stueck?.title_de || 'Das Stück'}
titleEn={content.stueck?.title_en || 'The Play'}
title={content.stueck?.title || 'Das Stück'}
variant="light"
>
<div class="content-de" data-lang="de" set:html={renderPortableText(content.stueck?.content_de)} />
<div class="content-en" data-lang="en" set:html={renderPortableText(content.stueck?.content_en)} />
<div set:html={renderPortableText(content.stueck?.content)} />
</TheaterSection>
<!-- Section 4: Mitwirkung -->
<TheaterSection
id="mitwirkung"
titleDe={content.mitwirkung?.title_de || 'Mitwirkung im Schauspiel'}
titleEn={content.mitwirkung?.title_en || 'Participation in the Play'}
title={content.mitwirkung?.title || 'Mitwirkung im Schauspiel'}
variant="dark"
>
<div class="content-de" data-lang="de" set:html={renderPortableText(content.mitwirkung?.content_de)} />
<div class="content-en" data-lang="en" set:html={renderPortableText(content.mitwirkung?.content_en)} />
<div set:html={renderPortableText(content.mitwirkung?.content)} />
{content.mitwirkung?.director_name && (
<div class="mt-16 p-12 bg-white/10 rounded-lg text-center">
<h3 class="text-3xl text-white mb-4">
<span data-lang="de">Kontakt Regisseur:</span>
<span data-lang="en">Contact Director:</span>
</h3>
<h3 class="text-3xl text-white mb-4">Kontakt Regisseur:</h3>
<p><strong>{content.mitwirkung.director_name}</strong></p>
{content.mitwirkung.director_email && (
<p>
<span data-lang="de">E-Mail:</span>
<span data-lang="en">Email:</span>
{' '}
<span>E-Mail:
<a href={`mailto:${content.mitwirkung.director_email}`} class="text-accent no-underline hover:underline">{content.mitwirkung.director_email}</a>
</p>
)}
{content.mitwirkung.director_phone && (
<p>
<span data-lang="de">Telefon:</span>
<span data-lang="en">Phone:</span>
{' '}
<span>Telefon:
<a href={`tel:${content.mitwirkung.director_phone}`} class="text-accent no-underline hover:underline">{content.mitwirkung.director_phone}</a>
</p>
)}
@@ -143,8 +125,7 @@ const content = await sanityClient.fetch(theaterHomepageQuery) ?? {};
<!-- Section 5: Termine -->
<TheaterSection
id="termine"
titleDe={content.termine?.title_de || 'Termine'}
titleEn={content.termine?.title_en || 'Schedule'}
title={content.termine?.title || 'Termine'}
variant="light"
>
{content.termine?.events && content.termine.events.length > 0 && (
@@ -167,12 +148,8 @@ const content = await sanityClient.fetch(theaterHomepageQuery) ?? {};
</div>
<div class="flex-1 bg-white/10 p-12 rounded-xl backdrop-blur-[10px] border-2 border-white/20 transition-all duration-300 hover:-translate-y-1 hover:bg-white/15 hover:shadow-[0_8px_20px_rgba(0,0,0,0.3)] max-w-full md:max-w-2xl">
<div class="md:hidden text-lg font-bold text-accent text-center whitespace-nowrap md:text-base">{event.date}</div>
<h3 class="text-3xl mb-2 text-white md:text-2xl">
<span class="event-title-de" data-lang="de">{event.title_de}</span>
<span class="event-title-en" data-lang="en">{event.title_en}</span>
</h3>
<p class="event-desc-de m-0 text-white/90 leading-relaxed" data-lang="de">{event.description_de}</p>
<p class="event-desc-en m-0 text-white/90 leading-relaxed" data-lang="en">{event.description_en}</p>
<h3 class="text-3xl mb-2 text-white md:text-2xl">{event.title}</h3>
<p class="event-desc m-0 text-white/90 leading-relaxed">{event.description}</p>
</div>
</div>
))}
@@ -183,19 +160,16 @@ const content = await sanityClient.fetch(theaterHomepageQuery) ?? {};
<!-- Section 6: Presse -->
<TheaterSection
id="presse"
titleDe={content.presse?.title_de || 'Presse'}
titleEn={content.presse?.title_en || 'Press'}
title={content.presse?.title || 'Presse'}
variant="dark"
>
<div class="content-de" data-lang="de" set:html={renderPortableText(content.presse?.content_de)} />
<div class="content-en" data-lang="en" set:html={renderPortableText(content.presse?.content_en)} />
<div set:html={renderPortableText(content.presse?.content)} />
</TheaterSection>
<!-- Section 7: Foto Galerie -->
<TheaterSection
id="gallery"
titleDe={content.gallery?.title_de || 'Foto Galerie'}
titleEn={content.gallery?.title_en || 'Photo Gallery'}
title={content.gallery?.title || 'Foto Galerie'}
variant="light"
>
<PhotoGallery images={content.gallery?.images || []} />
@@ -204,18 +178,15 @@ const content = await sanityClient.fetch(theaterHomepageQuery) ?? {};
<!-- Section 8: Tickets -->
<TheaterSection
id="tickets"
titleDe={content.tickets?.title_de || 'Tickets'}
titleEn={content.tickets?.title_en || 'Tickets'}
title={content.tickets?.title || 'Tickets'}
variant="dark"
>
<div class="content-de" data-lang="de" set:html={renderPortableText(content.tickets?.content_de)} />
<div class="content-en" data-lang="en" set:html={renderPortableText(content.tickets?.content_en)} />
<div set:html={renderPortableText(content.tickets?.content)} />
{content.tickets?.ticket_url && (
<div class="mt-[--spacing-lg] text-center">
<a href={content.tickets.ticket_url} class="inline-block py-4 px-10 bg-accent text-secondary no-underline font-bold text-xl rounded-lg transition-all duration-300 shadow-[0_4px_10px_rgba(0,0,0,0.2)] hover:-translate-y-1 hover:shadow-[0_6px_15px_rgba(0,0,0,0.3)] hover:bg-[#fbfa9a]" target="_blank" rel="noopener noreferrer">
<span data-lang="de">Tickets kaufen</span>
<span data-lang="en">Buy Tickets</span>
Tickets kaufen
</a>
</div>
)}
+19 -38
View File
@@ -18,10 +18,8 @@ export const theaterHomepageSchema = defineType({
title: 'Hero Section',
type: 'object',
fields: [
defineField({ name: 'title_de', title: 'Title (German)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'title_en', title: 'Title (English)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'subtitle_de', title: 'Subtitle (German)', type: 'text' }),
defineField({ name: 'subtitle_en', title: 'Subtitle (English)', type: 'text' }),
defineField({ name: 'title', title: 'Title', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'subtitle', title: 'Subtitle', type: 'text' }),
],
}),
@@ -31,10 +29,8 @@ export const theaterHomepageSchema = defineType({
title: 'Section 1: Das Projekt / The Project',
type: 'object',
fields: [
defineField({ name: 'title_de', title: 'Title (German)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'title_en', title: 'Title (English)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'content_de', title: 'Content (German)', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
defineField({ name: 'content_en', title: 'Content (English)', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
defineField({ name: 'title', title: 'Title', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'content', title: 'Content', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
],
}),
@@ -44,10 +40,8 @@ export const theaterHomepageSchema = defineType({
title: "Section 2: Wer steckt dahinter / Who's Behind It",
type: 'object',
fields: [
defineField({ name: 'title_de', title: 'Title (German)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'title_en', title: 'Title (English)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'content_de', title: 'Content (German)', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
defineField({ name: 'content_en', title: 'Content (English)', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
defineField({ name: 'title', title: 'Title', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'content', title: 'Content', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
defineField({
name: 'organizations',
title: 'Organizations',
@@ -57,8 +51,7 @@ export const theaterHomepageSchema = defineType({
type: 'object',
fields: [
defineField({ name: 'name', title: 'Name', type: 'string' }),
defineField({ name: 'role_de', title: 'Role (German)', type: 'string' }),
defineField({ name: 'role_en', title: 'Role (English)', type: 'string' }),
defineField({ name: 'role', title: 'Role', type: 'string' }),
defineField({ name: 'logo', title: 'Logo', type: 'image', options: { hotspot: false } }),
defineField({ name: 'url', title: 'Website URL', type: 'url' }),
],
@@ -74,10 +67,8 @@ export const theaterHomepageSchema = defineType({
title: 'Section 3: Das Stück / The Play',
type: 'object',
fields: [
defineField({ name: 'title_de', title: 'Title (German)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'title_en', title: 'Title (English)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'content_de', title: 'Content (German)', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
defineField({ name: 'content_en', title: 'Content (English)', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
defineField({ name: 'title', title: 'Title', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'content', title: 'Content', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
],
}),
@@ -87,10 +78,8 @@ export const theaterHomepageSchema = defineType({
title: 'Section 4: Mitwirkung / Participation',
type: 'object',
fields: [
defineField({ name: 'title_de', title: 'Title (German)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'title_en', title: 'Title (English)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'content_de', title: 'Content (German)', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
defineField({ name: 'content_en', title: 'Content (English)', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
defineField({ name: 'title', title: 'Title', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'content', title: 'Content', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
defineField({ name: 'director_name', title: 'Director Name', type: 'string' }),
defineField({ name: 'director_email', title: 'Director Email', type: 'string' }),
defineField({ name: 'director_phone', title: 'Director Phone', type: 'string' }),
@@ -103,8 +92,7 @@ export const theaterHomepageSchema = defineType({
title: 'Section 5: Termine / Schedule',
type: 'object',
fields: [
defineField({ name: 'title_de', title: 'Title (German)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'title_en', title: 'Title (English)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'title', title: 'Title', type: 'string', validation: (r) => r.required() }),
defineField({
name: 'events',
title: 'Events',
@@ -113,11 +101,9 @@ export const theaterHomepageSchema = defineType({
defineArrayMember({
type: 'object',
fields: [
defineField({ name: 'title_de', title: 'Event Title (German)', type: 'string' }),
defineField({ name: 'title_en', title: 'Event Title (English)', type: 'string' }),
defineField({ name: 'title', title: 'Event Title', type: 'string' }),
defineField({ name: 'date', title: 'Date / Period', type: 'string' }),
defineField({ name: 'description_de', title: 'Description (German)', type: 'text' }),
defineField({ name: 'description_en', title: 'Description (English)', type: 'text' }),
defineField({ name: 'description', title: 'Description', type: 'text' }),
],
}),
],
@@ -131,10 +117,8 @@ export const theaterHomepageSchema = defineType({
title: 'Section 6: Presse / Press',
type: 'object',
fields: [
defineField({ name: 'title_de', title: 'Title (German)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'title_en', title: 'Title (English)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'content_de', title: 'Content (German)', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
defineField({ name: 'content_en', title: 'Content (English)', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
defineField({ name: 'title', title: 'Title', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'content', title: 'Content', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
],
}),
@@ -144,8 +128,7 @@ export const theaterHomepageSchema = defineType({
title: 'Section 7: Foto Galerie / Photo Gallery',
type: 'object',
fields: [
defineField({ name: 'title_de', title: 'Title (German)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'title_en', title: 'Title (English)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'title', title: 'Title', type: 'string', validation: (r) => r.required() }),
defineField({
name: 'images',
title: 'Gallery Images',
@@ -161,10 +144,8 @@ export const theaterHomepageSchema = defineType({
title: 'Section 8: Tickets',
type: 'object',
fields: [
defineField({ name: 'title_de', title: 'Title (German)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'title_en', title: 'Title (English)', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'content_de', title: 'Content (German)', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
defineField({ name: 'content_en', title: 'Content (English)', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
defineField({ name: 'title', title: 'Title', type: 'string', validation: (r) => r.required() }),
defineField({ name: 'content', title: 'Content', type: 'array', of: [{ type: 'block' }, defineArrayMember({ type: 'image', options: { hotspot: true } })] }),
defineField({ name: 'ticket_url', title: 'Ticket Purchase URL', type: 'url' }),
],
}),
-30
View File
@@ -40,36 +40,6 @@
}
/* Language-specific content visibility */
[data-language="de"] .title-en,
[data-language="de"] .subtitle-en,
[data-language="de"] .content-en,
[data-language="de"] .org-role-en,
[data-language="de"] .event-title-en,
[data-language="de"] .event-desc-en,
[data-language="de"] .nav-label-en,
[data-language="de"] .placeholder-en,
[data-language="de"] [data-lang="en"] {
display: none !important;
}
[data-language="en"] .title-de,
[data-language="en"] .subtitle-de,
[data-language="en"] .content-de,
[data-language="en"] .org-role-de,
[data-language="en"] .event-title-de,
[data-language="en"] .event-desc-de,
[data-language="en"] .nav-label-de,
[data-language="en"] .placeholder-de,
[data-language="en"] [data-lang="de"] {
display: none !important;
}
[data-language="de"] .nav-label-de,
[data-language="en"] .nav-label-en,
[data-language="de"] .placeholder-de,
[data-language="en"] .placeholder-en {
display: inline;
}
/* Section content styling */
.section-content p {