Dieser Dreamcodes Adventskalender im Apple inspiriertem Design erstellt beim ersten Aufruf automatisch drei Dateien: ein CSS für das Apple inspirierte Design, ein JavaScript für Interaktionen und eine Daten Datei mit den Inhalten der Türchen. Es liefert die HTML-Oberfläche des Adventskalenders aus und bietet eine kleine API, über die geprüft wird, ob ein Türchen geöffnet werden darf und welcher Inhalt angezeigt wird. Das JavaScript steuert Animationen, Modals und speichert geöffnete Türchen im Browser.
Ein Adventskalender mit modernem Design und persistenter Zustandsspeicherung. Willst du mehr Features, dann schau dir mal unser anderes Adventskalender PHP Script an.
<?php
declare(strict_types=1);
$baseDir = __DIR__ . DIRECTORY_SEPARATOR;
$filesToCreate = [
'style.css',
'script.js',
'data.php',
];
foreach ($filesToCreate as $fname) {
$path = $baseDir . $fname;
if (!file_exists($path)) {
$content = match ($fname) {
'style.css' => getDefaultCss(),
'script.js' => getDefaultJs(),
'data.php' => getDefaultDataPhp(),
default => '',
};
@file_put_contents($path, $content);
@chmod($path, 0644);
}
}
if (isset($_GET['action']) && $_GET['action'] === 'get') {
// API: return content for a day, enforce server date
header('Content-Type: application/json; charset=utf-8');
$day = isset($_GET['day']) ? (int)$_GET['day'] : 0;
if ($day < 1 || $day > 31) {
echo json_encode(['ok' => false, 'error' => 'invalid_day']);
exit;
}
$dataPath = $baseDir . 'data.php';
if (!file_exists($dataPath)) {
echo json_encode(['ok' => false, 'error' => 'missing_data']);
exit;
}
$ADVENT_DATA = include $dataPath;
$today = (int)date('j');
$serverMonth = (int)date('n');
$serverYear = (int)date('Y');
$allowAnyMonth = $ADVENT_DATA['__config']['allow_any_month'] ?? false;
$inDec = ($serverMonth === 12) || $allowAnyMonth;
$allowed = $inDec && ($day <= $today);
$payload = ['ok' => true, 'day' => $day, 'allowed' => $allowed, 'serverDay' => $today, 'serverMonth' => $serverMonth];
if (isset($ADVENT_DATA['doors'][$day])) {
$payload['content'] = $ADVENT_DATA['doors'][$day];
} else {
$payload['content'] = ['title' => "Tür $day", 'html' => "\n<p>Frohe Adventszeit — Inhalt für Tag $day.</p>\n", 'media' => null];
}
echo json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
exit;
}
$ADVENT = include $baseDir . 'data.php';
$doors = $ADVENT['doors'] ?? [];
$config = $ADVENT['__config'] ?? [];
$serverDate = new DateTimeImmutable('now');
$serverDay = (int)$serverDate->format('j');
$serverMonth = (int)$serverDate->format('n');
$serverYear = (int)$serverDate->format('Y');
?><!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Dreamcodes - Adventskalender</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<main class="app">
<header class="topbar">
<div class="brand">Advent · Minimal · Apple‑Look</div>
<div class="meta">Server: <?= htmlspecialchars($serverDate->format('Y‑m‑d')) ?></div>
</header>
<section class="grid" role="list">
<?php
$total = $config['days'] ?? 24;
for ($i = 1; $i <= $total; $i++):
$isPast = ($serverMonth === 12) ? ($i <= $serverDay) : ($config['allow_any_month'] ?? false);
$content = $doors[$i] ?? null;
?>
<article class="door" data-day="<?= $i ?>" role="listitem" aria-label="Tag <?= $i ?>">
<div class="door-face">
<div class="num"><?= $i ?></div>
<div class="glass"></div>
</div>
</article>
<?php endfor; ?>
</section>
<div id="modal" class="modal" aria-hidden="true">
<div class="modal-backdrop" data-close="true"></div>
<div class="modal-card" role="dialog" aria-modal="true">
<button class="close" data-close="true" aria-label="Schließen">✕</button>
<h2 id="modal-title"></h2>
<div id="modal-media"></div>
<div id="modal-body"></div>
<footer class="modal-footer">
<button id="mark-read" class="btn">Als gelesen markieren</button>
</footer>
</div>
</div>
<footer class="footer">Powered by <a href="https://www.dreamcodes.net" target="_blank" rel="noopener">Dreamcodes</a><br>Tippe/Click ein Türchen — Öffnen nur am jeweiligen Tag.</footer>
</main>
<script>
// Inject server data
window.ADVENT_CONFIG = <?= json_encode($ADVENT, JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR) ?>;
window.SERVER_DAY = <?= $serverDay ?>;
window.SERVER_MONTH = <?= $serverMonth ?>;
window.SERVER_YEAR = <?= $serverYear ?>;
</script>
<script src="script.js" defer></script>
</body>
</html>
<?php
function getDefaultCss(): string
{
// Modern Apple-like minimal style with glass/vibrancy and responsive grid
return <<<CSS
/* style.css - Apple-inspired minimal design */
:root{
--bg:#0b1220;--card:#0f1724;--glass:rgba(255,255,255,0.06);--accent:#0ea5e9;--muted:rgba(255,255,255,0.6);
--radius:14px;--gap:18px;--max-width:1200px;
}
*{box-sizing:border-box}
html,body{height:100%;margin:0;font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Text', 'Segoe UI', Roboto, 'Helvetica Neue', Arial; background: radial-gradient(1200px 600px at 10% 10%, rgba(14,165,233,0.06), transparent), var(--bg); color:#e6eef8; -webkit-font-smoothing:antialiased}
.app{max-width:var(--max-width);margin:28px auto;padding:24px}
.topbar{display:flex;justify-content:space-between;align-items:center;margin-bottom:18px}
.brand{font-weight:600;letter-spacing:0.2px}
.meta{font-size:13px;color:var(--muted)}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:var(--gap)}
.door{position:relative;height:140px;border-radius:18px;cursor:pointer;outline:none}
.door-face{position:absolute;inset:0;border-radius:inherit;padding:14px;display:flex;align-items:flex-end;justify-content:flex-end;backdrop-filter: blur(8px) saturate(120%);background:linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01));box-shadow: 0 6px 18px rgba(2,6,23,0.6), inset 0 1px 0 rgba(255,255,255,0.02);transition:transform .35s cubic-bezier(.2,.9,.2,1), box-shadow .25s}
.door:hover .door-face{transform:translateY(-6px)}
.num{font-size:26px;font-weight:700;background:linear-gradient(180deg,#fff8,#d0eaff);-webkit-background-clip:text;background-clip:text;color:transparent}
.glass{position:absolute;inset:10px;border-radius:12px;pointer-events:none;background:linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01));}
.door.open .door-face{transform:rotateX(15deg) translateY(-18px);box-shadow:0 30px 60px rgba(2,8,20,0.6)}
.door.shake{animation:shake .6s}
@keyframes shake{0%{transform:translateX(0)}20%{transform:translateX(-8px)}40%{transform:translateX(6px)}60%{transform:translateX(-4px)}80%{transform:translateX(2px)}100%{transform:translateX(0)}}
/* Modal */
.modal{position:fixed;inset:0;display:none;align-items:center;justify-content:center;z-index:60}
.modal[aria-hidden="false"]{display:flex}
.modal-backdrop{position:absolute;inset:0;background:linear-gradient(180deg, rgba(2,6,23,0.6), rgba(2,6,23,0.8));backdrop-filter: blur(8px)}
.modal-card{position:relative;z-index:70;max-width:720px;width:94%;border-radius:18px;padding:22px;background: linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01));box-shadow:0 10px 40px rgba(2,8,23,0.7)}
.close{position:absolute;right:12px;top:10px;background:transparent;border:0;color:var(--muted);font-size:18px;cursor:pointer}
#modal-media{margin:8px 0 14px}
#modal-media img, #modal-media iframe{max-width:100%;border-radius:10px}
.modal-footer{display:flex;justify-content:flex-end}
.btn{background:linear-gradient(180deg,var(--accent),#0284c7);border:none;padding:10px 14px;border-radius:10px;color:#022;cursor:pointer;font-weight:600}
.footer{margin-top:18px;color:var(--muted);font-size:13px}
/* Responsive tweaks */
@media (max-width:600px){.grid{grid-template-columns:repeat(3,1fr);gap:12px}.door{height:100px}}
@media (min-width:900px){.grid{grid-template-columns:repeat(6,1fr)}}
CSS;
}
function getDefaultJs(): string
{
// JS: interaction, localStorage state, fetch API, modal management
return <<<JS
/* script.js - interaction for Adventskalender */
'use strict';
const DAYS = window.ADVENT_CONFIG?.__config?.days ?? 24;
const allowAny = !!window.ADVENT_CONFIG?.__config?.allow_any_month;
const STORAGE_KEY = 'advent_opened_v1';
const state = {
opened: new Set(JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]')),
};
function saveState(){
localStorage.setItem(STORAGE_KEY, JSON.stringify(Array.from(state.opened)));
}
function queryDay(el){
return parseInt(el.getAttribute('data-day'))||0;
}
function showModal(data){
const modal = document.getElementById('modal');
modal.setAttribute('aria-hidden','false');
document.getElementById('modal-title').textContent = data.title || 'Inhalt';
const media = document.getElementById('modal-media'); media.innerHTML = '';
if(data.media){
if(data.media.type === 'img'){
const img = document.createElement('img'); img.src = data.media.src; media.appendChild(img);
} else if(data.media.type === 'video'){
const iframe = document.createElement('iframe'); iframe.src = data.media.src; iframe.width = '100%'; iframe.height = '320'; iframe.setAttribute('allow','accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture'); media.appendChild(iframe);
}
}
document.getElementById('modal-body').innerHTML = data.html || '';
}
function closeModal(){
const modal = document.getElementById('modal');
modal.setAttribute('aria-hidden','true');
}
function applyOpenedMarks(){
document.querySelectorAll('.door').forEach(d => {
const day = parseInt(d.dataset.day);
if(state.opened.has(day)) d.classList.add('open');
else d.classList.remove('open');
});
}
function gentleShake(el){
el.classList.remove('shake');
void el.offsetWidth;
el.classList.add('shake');
}
async function requestDay(day){
try{
const res = await fetch('?action=get&day=' + day);
if(!res.ok) throw new Error('network');
const payload = await res.json();
return payload;
}catch(e){
return {ok:false,error:'network'};
}
}
document.addEventListener('click', async (ev)=>{
const door = ev.target.closest('.door');
if(!door) return;
const day = queryDay(door);
const payload = await requestDay(day);
if(!payload.ok){
gentleShake(door);
return;
}
if(!payload.allowed){
gentleShake(door);
showModal({title:`Tür ${day}`, html:`<p>Sorry — diese Tür kann erst am ${payload.serverYear || new Date().getFullYear()}-12-${payload.serverDay} geöffnet werden.</p>`});
return;
}
door.classList.add('open');
state.opened.add(day);
saveState();
showModal(payload.content);
});
document.addEventListener('click', (ev)=>{
if(ev.target.dataset.close) closeModal();
});
document.addEventListener('keydown', (ev)=>{ if(ev.key === 'Escape') closeModal(); });
document.getElementById('mark-read').addEventListener('click', ()=>{
const title = document.getElementById('modal-title').textContent || '';
const m = title.match(/(\d{1,2})$/);
if(m){ state.opened.add(parseInt(m[1])); saveState(); applyOpenedMarks(); }
closeModal();
});
applyOpenedMarks();
document.getElementById('modal').addEventListener('keydown', (e)=>{
if(e.key === 'Tab') e.preventDefault();
});
(function prefetch(){
const today = window.SERVER_DAY || new Date().getDate();
[Math.max(1,today-1), today, Math.min(DAYS, today+1)].forEach(d=>fetch('?action=get&day='+d));
})();
JS;
}
function getDefaultDataPhp(): string
{
$now = date('Y');
$doors = [];
for ($i = 1; $i <= 24; $i++) {
$title = "Tag $i";
$html = "<p>Seliges Advents-Glück — Überraschung für Tag $i.</p>";
$media = null;
if (in_array($i, [3,7,12,18])) {
$media = ['type' => 'img', 'src' => 'https://picsum.photos/seed/advent' . $i . '/800/450'];
}
if (in_array($i, [10,20])) {
$media = ['type' => 'video', 'src' => 'https://www.youtube.com/embed/dQw4w9WgXcQ?rel=0'];
}
$doors[$i] = ['title' => $title, 'html' => $html, 'media' => $media];
}
$config = [
'days' => 24,
'allow_any_month' => false,
];
$arrayExport = var_export(['__config' => $config, 'doors' => $doors], true);
return "<?php\n// data.php - generated advent data (" . date('Y-m-d H:i:s') . ")\nreturn " . $arrayExport . ";\n";
}
?>

