Dienstag, 9 Dezember 2025

Diese Woche am beliebtesten

Vertiefendes Material

Adventskalender PHP

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";
}
?>
Vorheriges Tutorial
Nächstes Tutorial
Dreamcodes Redaktion
Dreamcodes Redaktion
Seit 1999 bewegt sich Dreamcodes in der Welt der digitalen Entwicklung, zwischen Codezeilen, Designentscheidungen und technischen Herausforderungen, die manchmal kleiner aussehen, als sie wirklich sind. Die Herausforderung besteht darin, komplexe Themen wie Webentwicklung, SEO, SEA, GEO, IT-Strukturen, Softwareentwicklung und moderne Technologien so aufzubereiten, dass sie verständlich, nachvollziehbar und vor allem praktisch nutzbar werden. Der Fokus liegt dabei darauf, Wissen nicht nur zu erklären, sondern es für reale Projekte anwendbar zu machen. Egal ob ein einfaches Script, ein umfangreicher Leitfaden oder ein tiefes technisches Tutorial: Das oberste Ziel dabei ist, dass Leser am Ende wirklich weiterkommen und ein Thema greifbarer wird als vorher. Über die Jahre gab es viele verschiedene Ansätze, Tools und Trends die kammen und auch wieder gingen. Genau das hilft neben Jahrelanger IT Erfahrung und mehrjähriger Tätigkeit im IT Bereich heute dabei, Inhalte mit Tiefe zu schreiben, die nicht nur Grundlagen vermitteln, sondern auch Hintergründe, Zusammenhänge und Best Practices erklären. Dreamcodes teilt diese Erfahrungen, mit dem Ziel, digitale Bildung für alle zugänglich zu machen, die neugierig sind, nach Lösungen suchen oder eigene digitale Projekte erfolgreich voranbringen möchten. Wenn dieses Wissen jemandem Zeit spart, ein Problem löst oder eine neue Idee auslöst, hat sich der Aufwand dahinter gelohnt. ;)

Vielleicht einen Blick wert