Zum Inhalt
← Hero & Section-Layouts

Sektion schaltet ihren Ton per Style Query

Drei Kapitel eines Gutshofs bei Neumarkt in identischem Markup: Jede Sektion setzt nur --ton: kalk, sandstein oder nacht, und Karten, Knöpfe, Tabellen und Fokusring stellen sich per @container style() selbst um.

Seiten mit Tag-, Abend- und Nachtkapiteln oder Material- und Akzentsektionen (Eventlocation, Hotel, Weingut, Kultur).

Eine Sektion setzt nur eine Custom Property: --ton: kalk, sandstein oder nacht. Alles darin fragt diesen Ton per @container style(--ton: nacht) ab und stellt sich selbst um: Text, Linien, Knöpfe, Karten, Bildrahmen, Fokusring. Es gibt keine Klassen wie .section--dark .card__title mehr. Dieselbe Karte sieht in jeder Sektion richtig aus, und eine helle Karte in einer dunklen Sektion schaltet ihre eigenen Kinder zurück. Seit Mai 2026 laufen Style Queries für Custom Properties in allen Engines.

Für Templates und den One-Shot-Generator ist es der kürzeste Weg zu Varianten: eine HTML, der Ton steht als eine Zeile an der Sektion. Bausteine müssen nicht wissen, wo sie liegen. Das verhindert die typischen Fehler dunkler Sektionen: dunkle Schrift auf Nachtblau, unsichtbare Fokusringe, Knöpfe ohne Kante.

Wann einsetzen

Bei Seiten mit Sektionen in mehreren Tönen (Tag und Nacht, Material und Akzent), bei Baukästen, in denen dieselben Bausteine auf verschiedenen Gründen liegen, und in tokens.css als Grundlage für Sektionsvarianten.

Wann nicht

Nicht, wenn die Seite nur einen Ton hat. Nicht für Abfragen normaler Eigenschaften wie color: Das kann laut modern-css.com noch kein Browser. Nicht ohne Rückfall, solange ältere Firefox- und Safari-Versionen (vor 151 bzw. 18) eine Rolle spielen: Der Grundton muss ohne Abfrage lesbar sein.

Varianten

  • Kapitel: drei Sektionen Tag, Abend, Nacht mit identischem Markup
  • Insel: helle Karte in dunkler Sektion, die nächste Abfrage gewinnt
  • Rückfall: Umschalter zwischen Style Query und Attribut-Rückfall [data-ton], mit Browser-Erkennung

Umsetzung

  1. @property --ton { syntax: '<custom-ident>'; inherits: true; initial-value: kalk; }
  2. Jede Sektion ist ein Container: .ton { container-name: ton; } (für Style Queries reicht jedes Element, container-type ist nur für Größenabfragen nötig) und setzt --ton per style-Attribut oder Klasse.
  3. Bausteine fragen den nächsten Container ab: @container style(--ton: nacht) { .karte { --karte-grund: …; --karte-text: …; } }. Nur Custom Properties umschalten, die Bausteine lesen dann var(--karte-text).
  4. Eine Insel (helle Karte in dunkler Sektion) setzt selbst --ton: kalk und ist selbst Container; ihre Kinder fragen sie zuerst ab.
  5. Rückfall: Grundwerte ohne Abfrage für den hellen Ton; zusätzlich :where([data-ton=nacht]) mit denselben Variablen für Browser ohne Style Queries. Erkennung per Testelement in JS (getComputedStyle eines Kindes) und Klasse am <html>.
  6. Farbwechsel per transition auf den Variablen nur, wenn sie per @property registriert sind; sonst springen sie.

Robust umsetzen

Telefon. Wie am Desktop. Sektionen stehen untereinander, jede mit eigenem Ton. Der Umschalter in der Demo liegt als Knopfreihe mit Umbruch über der Sektion.

Ohne Bewegung. Tonwechsel in 200 ms Überblendung statt 725 ms, ohne Weg.

Barrierefrei. Jeder Ton hat geprüfte Textfarben (mindestens 4,5 : 1) und einen eigenen Fokusring. Im Kontrastmodus (forced-colors) übernimmt das System die Farben, Knöpfe behalten eine Kante. Der Ton ändert nie die Reihenfolge oder den Inhalt.

Code

<!DOCTYPE html>
<!--
  Sektion schaltet ihren Ton per Style Query
  Fundus · Hero & Section-Layouts · https://cecodesign.de/lab/fundus/hero-layouts/sektion-stil-abfrage/
  Eigenständige Datei. Bilder liegen hier unter bilder/ – eigene einsetzen.
-->
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Sektion schaltet ihren Ton per Style Query · Fundus-Demo</title>
<script>document.documentElement.classList.add('js');if(matchMedia('(prefers-reduced-motion: reduce)').matches)document.documentElement.classList.add('reduziert')</script>
<style>
@font-face { font-family: 'Imbue'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/imbue:vf@latest/latin-opsz-normal.woff2') format('woff'); font-weight: 100 900; font-display: swap; }
@font-face { font-family: 'Archivo'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/archivo:vf@latest/latin-full-normal.woff2') format('woff2-variations'); font-weight: 100 900; font-stretch: 62% 125%; font-display: swap; }

/* Der Ton als registrierte Custom Property: nur dann ist er abfragbar und hat einen sicheren Startwert */
@property --ton { syntax: '<custom-ident>'; inherits: true; initial-value: kalk; }
/* Farben registriert, damit sie beim Tonwechsel gleiten statt zu springen */
@property --flaeche { syntax: '<color>'; inherits: true; initial-value: #F5F0E6; }
@property --tinte { syntax: '<color>'; inherits: true; initial-value: #1D1A17; }
@property --tinte-2 { syntax: '<color>'; inherits: true; initial-value: #5C574E; }
@property --linie { syntax: '<color>'; inherits: true; initial-value: #D8CFC0; }
@property --messing { syntax: '<color>'; inherits: true; initial-value: #8C6A3C; }
@property --karte { syntax: '<color>'; inherits: true; initial-value: #FBF8F2; }
@property --signal { syntax: '<color>'; inherits: true; initial-value: #C43B2B; }
@property --fokus { syntax: '<color>'; inherits: true; initial-value: #1D1A17; }

:root {
  --headline: 'Imbue', Georgia, serif; --schrift: 'Archivo', system-ui, sans-serif;
  --kurve: cubic-bezier(0.625, 0.05, 0, 1);  /* token-studio-signatur, für alles */
  --t-ton: 725ms; --t-deck: 400ms; --t-hover: 250ms;
  --rand: clamp(20px, 5vw, 72px);
}
* { box-sizing: border-box; margin: 0; }
html { background: #F5F0E6; color: #1D1A17; font-family: var(--schrift); -webkit-font-smoothing: antialiased; }
:root:has(:focus-visible) { scroll-behavior: auto; }
html { scroll-padding-bottom: 96px; } /* Fokus nicht unter dem Pult */
body { min-height: 100svh; }
.rubrik { font: 600 .8125rem/1.2 var(--schrift); font-stretch: 112%; letter-spacing: .08em; text-transform: uppercase; }

/* ——— Kopf und Einstieg (immer Kalk) ——— */
.kopf { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 12px 28px; padding: 18px var(--rand); border-bottom: 1px solid #D8CFC0; }
.wortmarke { font: 400 28px/1 var(--headline); font-variation-settings: 'opsz' 60; letter-spacing: .01em; color: inherit; text-decoration: none; }
.kopf nav { display: flex; gap: 22px; flex-wrap: wrap; font-size: .9375rem; }
.kopf nav a { color: inherit; text-decoration: none; }
.einstieg { padding: clamp(28px, 4vw, 56px) var(--rand) clamp(24px, 3vw, 40px); display: grid; grid-template-columns: minmax(0, 1fr); gap: 16px 48px; align-items: end; max-width: 1440px; margin: 0 auto; }
@media (min-width: 960px) { .einstieg { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); } }
.einstieg h1 { font: 300 clamp(2.75rem, 1.4rem + 5vw, 6rem)/.95 var(--headline); font-variation-settings: 'opsz' 100; letter-spacing: -.01em; text-wrap: balance; }
.einstieg h1 em { font-style: normal; font-weight: 500; }
.einstieg p:not(.rubrik) { font-size: 1.0625rem; line-height: 1.6; max-width: 54ch; color: #5C574E; text-wrap: pretty; }
.einstieg .rubrik { color: #5C574E; margin-bottom: 14px; }
.unterstuetzung { margin-top: 12px; font-size: .9375rem; color: #1D1A17; }
.unterstuetzung b { font-weight: 600; }
code { font: 400 .875em/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: nowrap; }
pre.code code, pre.code { white-space: pre-wrap; }

/* ——— Die Sektion: ein Container, eine Zeile Ton ——— */
.ton { container-name: ton; }
.ton-innen { background: var(--flaeche); color: var(--tinte);
  transition: --flaeche var(--t-ton) var(--kurve), --tinte var(--t-ton) var(--kurve), --tinte-2 var(--t-ton) var(--kurve), --linie var(--t-ton) var(--kurve), --messing var(--t-ton) var(--kurve), --karte var(--t-ton) var(--kurve), --signal var(--t-ton) var(--kurve), --fokus var(--t-ton) var(--kurve); }
.ton-innen :focus-visible { outline: 2px solid var(--fokus); outline-offset: 3px; }
</style>
<!-- Rückfall: gleiche Werte per Attribut, für Browser ohne Style Queries (Firefox vor 151, Safari vor 18) -->
<style id="css-rueckfall">
:where([data-ton="sandstein"]) > .ton-innen { --flaeche: #E9DCC8; --tinte: #1D1A17; --tinte-2: #554F46; --linie: #CDBDA4; --messing: #7A5C33; --karte: #F2E8D9; --signal: #B23526; --fokus: #1D1A17; }
:where([data-ton="nacht"]) > .ton-innen { --flaeche: #15171F; --tinte: #F2ECE2; --tinte-2: #B8B0A3; --linie: #343846; --messing: #C9A66B; --karte: #1E212B; --signal: #C43B2B; --fokus: #F2ECE2; }
:where([data-ton="kalk"]) > .ton-innen { --flaeche: #F5F0E6; --tinte: #1D1A17; --tinte-2: #5C574E; --linie: #D8CFC0; --messing: #8C6A3C; --karte: #FBF8F2; --signal: #C43B2B; --fokus: #1D1A17; }
</style>
<!-- Das Muster: Kinder fragen den Ton des nächsten Containers ab -->
<style id="css-abfrage">
@container ton style(--ton: kalk) { .ton-innen { --flaeche: #F5F0E6; --tinte: #1D1A17; --tinte-2: #5C574E; --linie: #D8CFC0; --messing: #8C6A3C; --karte: #FBF8F2; --signal: #C43B2B; --fokus: #1D1A17; } }
@container ton style(--ton: sandstein) { .ton-innen { --flaeche: #E9DCC8; --tinte: #1D1A17; --tinte-2: #554F46; --linie: #CDBDA4; --messing: #7A5C33; --karte: #F2E8D9; --signal: #B23526; --fokus: #1D1A17; } }
@container ton style(--ton: nacht) { .ton-innen { --flaeche: #15171F; --tinte: #F2ECE2; --tinte-2: #B8B0A3; --linie: #343846; --messing: #C9A66B; --karte: #1E212B; --signal: #C43B2B; --fokus: #F2ECE2; } }
/* Auch Formen dürfen am Ton hängen: in der Nacht dünnere Linien in Messing, Bild etwas dunkler */
@container ton style(--ton: nacht) { .bild img { filter: brightness(.86); } .raum th { color: var(--messing); } }
</style>
<style>
/* Bausteine lesen nur Variablen. Sie wissen nicht, auf welchem Grund sie liegen. */
.kapitel .ton-innen { padding: clamp(36px, 5vw, 80px) var(--rand); }
.kapitel-raster { max-width: 1440px; margin: 0 auto; display: grid; grid-template-columns: minmax(0, 1fr); gap: 28px clamp(24px, 3vw, 48px); align-items: start; }
@media (min-width: 960px) { .kapitel-raster { grid-template-columns: minmax(0, 5fr) minmax(0, 7fr); } }
.kopfzeile { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 22px; }
.kopfzeile .rubrik { color: var(--tinte-2); }
.wahl { display: inline-flex; gap: 0; border: 1px solid var(--linie); }
.wahl button { font: 600 .75rem/1 var(--schrift); font-stretch: 112%; letter-spacing: .06em; text-transform: uppercase; color: var(--tinte); background: transparent; border: 0; padding: 10px 12px; cursor: pointer; transition: background-color var(--t-hover) var(--kurve), color var(--t-hover) var(--kurve); }
.wahl button + button { border-left: 1px solid var(--linie); }
.wahl button:hover { background: color-mix(in srgb, var(--tinte) 8%, transparent); }
.wahl button[aria-pressed="true"] { background: var(--tinte); color: var(--flaeche); }
.zeile-code { display: inline-block; margin-top: 12px; padding: 6px 9px; border: 1px solid var(--linie); color: var(--tinte-2); font: 400 .8125rem/1 ui-monospace, SFMono-Regular, Menlo, monospace; }
.kapitel h2 { font: 400 clamp(2.25rem, 1.2rem + 3.6vw, 4.25rem)/.98 var(--headline); font-variation-settings: 'opsz' 90; letter-spacing: -.01em; text-wrap: balance; }
.kapitel h2 span { display: block; font-size: .5em; font-variation-settings: 'opsz' 40; color: var(--tinte-2); margin-bottom: 10px; }
.kapitel p.text { font-size: 1.0625rem; line-height: 1.6; max-width: 46ch; margin-top: 18px; color: var(--tinte); text-wrap: pretty; }
.knoepfe { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 26px; }
.knopf { font: 600 1rem/1 var(--schrift); text-decoration: none; padding: 15px 20px; border-radius: 2px; transition: background-color var(--t-hover) var(--kurve), color var(--t-hover) var(--kurve), box-shadow var(--t-hover) var(--kurve); }
.knopf.haupt { background: var(--signal); color: #fff; }
.knopf.haupt:hover { background: color-mix(in srgb, var(--signal) 82%, #000); }
.knopf.neben { color: var(--tinte); box-shadow: inset 0 0 0 1px var(--messing), 4px 4px 0 -3px var(--flaeche), 4px 4px 0 -2px var(--messing); }
.knopf.neben:hover { box-shadow: inset 0 0 0 1px var(--messing), 0 0 0 0 var(--flaeche), 0 0 0 0 var(--messing); }
.bild { margin: 0; }
.bild img { display: block; width: 100%; height: auto; aspect-ratio: 3 / 2; object-fit: cover; transition: filter var(--t-ton) var(--kurve); }
.bild figcaption { font-size: .8125rem; color: var(--tinte-2); margin-top: 8px; }
.raum { width: 100%; border-collapse: collapse; margin-top: 20px; font-stretch: 87%; font-variant-numeric: tabular-nums; background: var(--karte); }
.raum caption { text-align: left; padding: 14px 16px 6px; font: 400 1.5rem/1.1 var(--headline); font-variation-settings: 'opsz' 40; background: var(--karte); }
.raum th, .raum td { padding: 10px 16px; border-top: 1px solid var(--linie); text-align: left; font-size: .9375rem; }
.raum th { font-weight: 600; font-stretch: 112%; font-size: .75rem; letter-spacing: .08em; text-transform: uppercase; color: var(--tinte-2); }
.raum td.z { text-align: right; font-weight: 600; }

/* ——— Insel: helle Karte in dunkler Sektion, darin wieder ein dunkles Etikett ——— */
.insel-raster { max-width: 1440px; margin: 0 auto; display: grid; grid-template-columns: minmax(0, 1fr); gap: 28px clamp(24px, 3vw, 56px); align-items: center; }
@media (min-width: 960px) { .insel-raster { grid-template-columns: minmax(0, 6fr) minmax(0, 6fr); } }
.anfrage > .ton-innen { padding: clamp(20px, 2.4vw, 32px); box-shadow: 0 0 0 1px var(--linie); }
.anfrage h3 { font: 400 2rem/1 var(--headline); font-variation-settings: 'opsz' 48; }
.feld { display: grid; gap: 6px; margin-top: 16px; }
.feld label { font-weight: 600; font-size: .9375rem; }
.feld input, .feld select { font: 400 1rem/1.2 var(--schrift); color: var(--tinte); background: var(--karte); border: 1px solid var(--tinte-2); border-radius: 2px; padding: 12px; width: 100%; min-width: 0; }
.felder { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0 12px; }
.etikett { display: inline-block; margin-top: 16px; }
.etikett > .ton-innen { padding: 10px 14px; font-size: .875rem; line-height: 1.4; display: flex; gap: 10px; align-items: baseline; }
.etikett b { font-weight: 600; }
.baum { margin-top: 24px; list-style: none; padding: 0; display: grid; gap: 8px; font: 400 .875rem/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; color: var(--tinte-2); }
.baum li { padding-left: calc(var(--tiefe) * 18px); }
.baum li::before { content: "└ "; color: var(--messing); }

/* ——— Rückfall-Vergleich ——— */
.modus { display: inline-flex; flex-wrap: wrap; gap: 0; border: 1px solid #D8CFC0; margin-top: 16px; }
.modus button { font: 600 .8125rem/1 var(--schrift); color: #1D1A17; background: transparent; border: 0; padding: 12px 14px; cursor: pointer; }
.modus button + button { border-left: 1px solid #D8CFC0; }
.modus button[aria-pressed="true"] { background: #1D1A17; color: #F5F0E6; }
.modus button:focus-visible, .einstieg :focus-visible { outline: 2px solid #1D1A17; outline-offset: 3px; }
pre.code { margin-top: 16px; background: #15171F; color: #E7E0D4; padding: 16px; font: 400 12.5px/1.6 ui-monospace, SFMono-Regular, Menlo, monospace; white-space: pre-wrap; overflow-wrap: anywhere; }
pre.code i { font-style: normal; color: #9E978B; }
pre.code b { font-weight: 400; color: #E8A58E; }

[hidden] { display: none !important; }

/* Pult */
.pult { position: fixed; right: 16px; bottom: 16px; z-index: 30; display: flex; flex-wrap: wrap; gap: 0; align-items: center; background: #1D1A17; color: #F5F0E6; box-shadow: 0 10px 30px rgba(0, 0, 0, .25); }
.pult .rubrik { padding: 0 12px; color: #CFC6B8; font-size: .6875rem; }
.pult button { font: 600 .75rem/1 var(--schrift); font-stretch: 112%; letter-spacing: .06em; text-transform: uppercase; color: #F5F0E6; background: transparent; border: 0; padding: 12px 13px; cursor: pointer; transition: background-color var(--t-hover) var(--kurve); }
.pult button:hover { background: rgba(255, 255, 255, .1); }
.pult button[aria-pressed="true"] { background: #C43B2B; }
.pult button:focus-visible { outline: 2px solid #F5F0E6; outline-offset: -2px; }

.vorschau body { overflow: hidden; }
.vorschau .einstieg p, .vorschau .kopf nav, .vorschau .unterstuetzung { display: none; }
.vorschau .einstieg { padding-block: 22px 18px; }
.vorschau .einstieg h1 { font-size: 3.25rem; }

:root.reduziert * { transition-duration: .01ms !important; animation-duration: .01ms !important; }
/* Reduzierte Bewegung: Tonwechsel als kurze Überblendung statt 725 ms */
:root.reduziert .ton-innen { transition-duration: 200ms !important; transition-timing-function: linear !important; }
@media (hover: none) { .wahl button { padding: 13px 14px; } }
@media (max-width: 360px) { .pult .rubrik { display: none; } .felder { grid-template-columns: minmax(0, 1fr); } }
@media (forced-colors: active) {
  button, [role=button], [role=tab], [role=switch], input, select, textarea { border: 1px solid ButtonText !important; }
  .knopf { border: 1px solid LinkText; }
}
</style>
</head>
<body>
<header class="kopf">
  <a class="wortmarke" href="#seite">Gut Wiesenau</a>
  <nav aria-label="Hauptnavigation"><a href="#seite">Ein Tag bei uns</a><a href="#seite">Räume</a><a href="#seite">Termine</a><a href="#seite">Anfrage</a></nav>
</header>
<main id="seite">
  <section class="einstieg" aria-labelledby="h1">
    <div>
      <p class="rubrik" id="v-rubrik">Plattform · Container Style Queries</p>
      <h1 id="h1">Ein Baustein, <em>drei Lichter.</em></h1>
    </div>
    <div>
      <p id="v-text">Jede Sektion setzt nur <code>--ton</code>. Karten, Knöpfe, Tabellen und Fokusring fragen den Ton per <code>@container style()</code> ab und stellen sich selbst um. Schalten Sie den Ton jeder Sektion oben rechts.</p>
      <p class="unterstuetzung" id="unterstuetzung" role="status"></p>
      <div id="modus-box" hidden>
        <div class="modus" role="group" aria-label="Welche Regeln gelten">
          <button type="button" data-modus="beide" aria-pressed="true">Beide (empfohlen)</button>
          <button type="button" data-modus="abfrage" aria-pressed="false">Nur Style Query</button>
          <button type="button" data-modus="rueckfall" aria-pressed="false">Nur Rückfall</button>
        </div>
        <pre class="code" aria-label="CSS und Skript des Rückfalls"><i>/* 1. Rückfall, zuerst im Stylesheet */</i>
:where([data-ton="nacht"]) &gt; .ton-innen { --flaeche: #15171F; … }
<i>/* 2. Das Muster, gewinnt bei gleicher Spezifität */</i>
@container ton <b>style(--ton: nacht)</b> {
  .ton-innen { --flaeche: #15171F; … }
}
<i>/* 3. Skript: dasselbe Wort als Attribut */</i>
el.style.setProperty('--ton', ton); <b>el.dataset.ton = ton;</b></pre>
      </div>
    </div>
  </section>
  <div id="buehne"></div>
</main>

<div class="ton" id="test" style="--ton: nacht; position: absolute; visibility: hidden; pointer-events: none" aria-hidden="true"><div class="ton-innen"><i id="test-kind" style="display:block;width:1px;height:1px"></i></div></div>

<nav class="pult" aria-label="Variante wählen">
  <span class="rubrik">Variante</span>
  <button type="button" data-v="kapitel" aria-pressed="true">Kapitel</button>
  <button type="button" data-v="insel" aria-pressed="false">Insel</button>
  <button type="button" data-v="rueckfall" aria-pressed="false">Rückfall</button>
</nav>

<script>
(() => {
  const H = document.documentElement;
  const reduziert = () => H.classList.contains('reduziert');
  const $ = s => document.querySelector(s);
  const TOENE = [['kalk', 'Kalk'], ['sandstein', 'Sandstein'], ['nacht', 'Nacht']];

  /* Inhalt der erfundenen Eventlocation */
  const KAPITEL = [
    { ton: 'kalk', zeit: '13 Uhr', titel: 'Trauung im Obstgarten', text: 'Freie Trauung unter den alten Apfelbäumen, Stühle in Reihen für bis zu 120 Gäste. Bei Regen ziehen wir in die Remise um, die steht eine Stunde vorher bereit.', bild: 'bilder/fenster-licht.jpg', alt: 'Ruhiger Raum mit salbeigrüner Wand, auf die Abendsonne ein Fensterbild wirft; davor eine Eichenbank mit Vase und Büchern', unterschrift: 'Trauzimmer im Gutshaus', raum: 'Obstgarten', plaetze: [['Reihen', 120], ['Stehempfang', 160]] },
    { ton: 'sandstein', zeit: '19 Uhr', titel: 'Essen in der Remise', text: 'Lange Tafeln oder runde Tische, gekocht wird mit dem, was die Oberpfalz im Monat hergibt. Das Menü stimmen wir sechs Wochen vorher mit euch ab.', bild: 'bilder/zitronen-tisch.jpg', alt: 'Heller Teller mit Zitronen auf dunklem Schiefertisch im Gegenlicht', unterschrift: 'Vorspeise im Juni', raum: 'Remise', plaetze: [['Bankett, lange Tafel', 96], ['Runde Tische', 88]] },
    { ton: 'nacht', zeit: '23 Uhr', titel: 'Tanz in der Scheune', text: 'Ab 23 Uhr wird es laut, die Scheune steht abseits der Nachbarn. Musik bis 3 Uhr, danach Kaminzimmer und Übernachtung in den sechs Gästezimmern.', bild: 'bilder/atmosphaere-partikel-kamin.jpg', alt: 'Breites, niedriges Kaminfeuer in einer dunklen Wand, warmes Licht auf dem Boden', unterschrift: 'Kaminzimmer nach Mitternacht', raum: 'Scheune', plaetze: [['Stehend mit Tanzfläche', 140], ['Lounge', 40]] }
  ];

  const v0 = new URLSearchParams(location.search).get('v');
  let variante = ['kapitel', 'insel', 'rueckfall'].includes(v0) ? v0 : 'kapitel';

  const wahl = (id, ton) => `<div class="wahl" role="group" aria-label="Ton dieser Sektion">${TOENE.map(([t, n]) => `<button type="button" data-fuer="${id}" data-ton="${t}" aria-pressed="${t === ton}">${n}</button>`).join('')}</div>`;
  const sektion = (k, i) => `
    <section class="ton kapitel" id="k${i}" style="--ton: ${k.ton}" data-ton="${k.ton}" aria-labelledby="h-k${i}">
      <div class="ton-innen">
        <div class="kapitel-raster">
          <div>
            <div class="kopfzeile"><span class="rubrik">Kapitel ${i + 1} · Ein Tag bei uns</span>${wahl('k' + i, k.ton)}</div>
            <h2 id="h-k${i}"><span>${k.zeit}</span>${k.titel}</h2>
            <p class="text">${k.text}</p>
            <div class="knoepfe"><a class="knopf haupt" href="#seite">Termin anfragen</a><a class="knopf neben" href="#seite">Besichtigung vereinbaren</a></div>
            <code class="zeile-code" data-code>style="--ton: ${k.ton}"</code>
          </div>
          <div>
            <figure class="bild"><img src="${k.bild}" alt="${k.alt}" width="1200" height="800" loading="${i ? 'lazy' : 'eager'}"><figcaption>${k.unterschrift}</figcaption></figure>
            <table class="raum"><caption>${k.raum}</caption><thead><tr><th scope="col">Bestuhlung</th><th scope="col" style="text-align:right">Plätze</th></tr></thead><tbody>${k.plaetze.map(([a, b]) => `<tr><td>${a}</td><td class="z">${b}</td></tr>`).join('')}</tbody></table>
          </div>
        </div>
      </div>
    </section>`;

  function insel() {
    return `
    <section class="ton kapitel" id="k0" style="--ton: nacht" data-ton="nacht" aria-labelledby="h-insel">
      <div class="ton-innen">
        <div class="insel-raster">
          <div>
            <div class="kopfzeile"><span class="rubrik">Sektion · äußerer Container</span>${wahl('k0', 'nacht')}</div>
            <h2 id="h-insel"><span>Freie Termine 2027</span>Samstage im Juni sind fast weg.</h2>
            <p class="text">Die Anfrage-Karte rechts setzt selbst <code>--ton: kalk</code>. Ihre Kinder fragen den nächsten Container ab, also die Karte, nicht die dunkle Sektion. Das Etikett in der Karte schaltet wieder auf Nacht.</p>
            <ul class="baum" aria-label="Verschachtelung der Container">
              <li style="--tiefe:0">section.ton <span data-baum="k0">--ton: nacht</span></li>
              <li style="--tiefe:1">div.ton.anfrage <span data-baum="karte">--ton: kalk</span></li>
              <li style="--tiefe:2">div.ton.etikett <span data-baum="etikett">--ton: nacht</span></li>
            </ul>
          </div>
          <div class="ton anfrage" id="karte" style="--ton: kalk" data-ton="kalk">
            <form class="ton-innen" onsubmit="return false" aria-labelledby="h-anfrage">
              <div class="kopfzeile" style="margin-bottom:10px"><span class="rubrik">Insel · innerer Container</span>${wahl('karte', 'kalk')}</div>
              <h3 id="h-anfrage">Termin anfragen</h3>
              <div class="felder">
                <div class="feld"><label for="f-datum">Wunschdatum</label><input id="f-datum" type="date" value="2027-06-12"></div>
                <div class="feld"><label for="f-gaeste">Gäste</label><input id="f-gaeste" type="number" inputmode="numeric" min="20" max="160" value="90"></div>
              </div>
              <div class="feld"><label for="f-anlass">Anlass</label><select id="f-anlass"><option>Hochzeit</option><option>Geburtstag</option><option>Firmenfeier</option></select></div>
              <div class="ton etikett" id="etikett" style="--ton: nacht" data-ton="nacht"><div class="ton-innen"><b>12. Juni 2027</b><span>angefragt, Option bis 19. November</span></div></div>
              <div class="knoepfe"><button class="knopf haupt" type="submit" style="border:0;cursor:pointer">Anfrage senden</button><a class="knopf neben" href="#seite">Anrufen</a></div>
            </form>
          </div>
        </div>
      </div>
    </section>`;
  }

  function render() {
    const b = $('#buehne');
    b.innerHTML = variante === 'insel' ? insel() : KAPITEL.map(sektion).join('');
    $('#modus-box').hidden = variante !== 'rueckfall';
    const T = {
      kapitel: ['Plattform · Container Style Queries', 'Jede Sektion setzt nur <code>--ton</code>. Karten, Knöpfe, Tabellen und Fokusring fragen den Ton per <code>@container style()</code> ab und stellen sich selbst um. Schalten Sie den Ton jeder Sektion.'],
      insel: ['Plattform · Verschachtelte Container', 'Eine helle Karte in einer dunklen Sektion braucht keine Sonderklasse. Sie ist selbst ein Container mit eigenem Ton, und die nächste Abfrage gewinnt.'],
      rueckfall: ['Plattform · Rückfall', 'Style Queries sind seit Mai 2026 Baseline. Für ältere Browser setzt ein kleines Skript dasselbe Wort zusätzlich als <code>data-ton</code>. Schalten Sie die Regeln einzeln ab: Das Bild bleibt gleich, solange eine von beiden greift.']
    }[variante];
    $('#v-rubrik').textContent = T[0]; $('#v-text').innerHTML = T[1];
  }

  function setzeTon(id, ton) {
    const el = document.getElementById(id); if (!el) return;
    el.style.setProperty('--ton', ton);
    el.dataset.ton = ton; /* Rückfall: dasselbe Wort als Attribut */
    el.querySelectorAll(`:scope > .ton-innen [data-fuer="${id}"], :scope > form [data-fuer="${id}"]`).forEach(b => b.setAttribute('aria-pressed', b.dataset.ton === ton ? 'true' : 'false'));
    const c = el.querySelector(':scope > .ton-innen [data-code]'); if (c) c.textContent = `style="--ton: ${ton}"`;
    const bm = document.querySelector(`[data-baum="${id}"]`); if (bm) bm.textContent = '--ton: ' + ton;
  }
  document.addEventListener('click', e => {
    const b = e.target.closest('[data-fuer]');
    if (b) { pause(); setzeTon(b.dataset.fuer, b.dataset.ton); }
    const m = e.target.closest('[data-modus]');
    if (m) { pause(); setzeModus(m.dataset.modus); }
  });

  function setzeModus(m) {
    $('#css-abfrage').disabled = m === 'rueckfall';
    $('#css-rueckfall').disabled = m === 'abfrage';
    document.querySelectorAll('[data-modus]').forEach(b => b.setAttribute('aria-pressed', b.dataset.modus === m ? 'true' : 'false'));
    pruefe();
  }

  /* Kann dieser Browser style()? Testelement mit --ton: nacht lesen */
  function pruefe() {
    const rf = $('#css-rueckfall').disabled; $('#css-rueckfall').disabled = true;
    const ab = $('#css-abfrage').disabled; $('#css-abfrage').disabled = false;
    const farbe = getComputedStyle($('#test-kind')).getPropertyValue('--flaeche').trim().toLowerCase();
    $('#css-rueckfall').disabled = rf; $('#css-abfrage').disabled = ab;
    const kann = farbe === '#15171f' || farbe === 'rgb(21, 23, 31)';
    $('#unterstuetzung').innerHTML = `Dieser Browser kann <code>@container style()</code>: <b>${kann ? 'ja' : 'nein, der Rückfall greift'}</b>${variante === 'rueckfall' ? ` · aktiv: <b>${ab ? '' : 'Style Query'}${!ab && !rf ? ' + ' : ''}${rf ? '' : 'Rückfall'}${ab && rf ? 'nichts, Grundton Kalk' : ''}</b>` : ''}`;
  }

  function setzeVariante(v, still) {
    variante = v;
    document.querySelectorAll('.pult button').forEach(b => b.setAttribute('aria-pressed', b.dataset.v === v ? 'true' : 'false'));
    if (!still) { const q = new URLSearchParams(location.search); q.set('v', v); history.replaceState(null, '', '?' + q.toString()); }
    render(); setzeModus('beide');
    if (v === 'rueckfall') setzeTon('k0', 'nacht');
  }
  document.querySelectorAll('.pult button').forEach(b => b.addEventListener('click', () => { pause(); setzeVariante(b.dataset.v); }));

  /* Vorführung: die erste Sektion (bzw. Karte) wechselt ihren Ton */
  let lauf = null;
  function neustart() { clearTimeout(lauf); setzeVariante(variante, true); }
  function vorfuehren() {
    clearTimeout(lauf);
    const s = variante === 'insel'
      ? [() => setzeTon('k0', 'sandstein'), () => setzeTon('karte', 'nacht'), () => setzeTon('etikett', 'kalk'), () => { setzeTon('k0', 'nacht'); setzeTon('karte', 'kalk'); setzeTon('etikett', 'nacht'); }]
      : variante === 'rueckfall'
        ? [() => setzeModus('rueckfall'), () => setzeTon('k0', 'sandstein'), () => setzeModus('abfrage'), () => setzeTon('k0', 'nacht'), () => setzeModus('beide')]
        : [() => setzeTon('k0', 'sandstein'), () => setzeTon('k0', 'nacht'), () => setzeTon('k0', 'kalk')];
    let i = 0;
    const takt = () => { if (i < s.length) { s[i++](); lauf = setTimeout(takt, 1700); } else lauf = setTimeout(vorfuehren, 300); };
    lauf = setTimeout(takt, 700);
  }
  function pause() { clearTimeout(lauf); }
  function weiter() { vorfuehren(); }

  setzeVariante(variante, true);

})();
</script>
</body>
</html>