Prompt-Hero mit rotierenden Beispiel-Prompts
Ein großes Eingabefeld als Haupt-CTA, in dem sich Beispiel-Prompts selbst tippen und löschen; Absenden macht aus dem Hero sofort die Anwendung.
Im Hero steht statt Button und Screenshot ein großes Eingabefeld: eine abgerundete Textarea mit zwei bis vier Zeilen, Senden-Button und oft Anhang oder Mikrofon. Im leeren Feld tippt sich ein Beispiel-Prompt Zeichen für Zeichen ein, bleibt kurz stehen, löscht sich und macht dem nächsten Platz. Darunter liegen Vorschlags-Chips, die das Feld mit einem Klick füllen. Sobald jemand selbst tippt, verschwindet die Animation. Absenden startet direkt das Produkt, eine Anmeldung kommt erst danach.
Die Seite verspricht nicht, sie lässt sofort handeln: Der erste Klick ist schon Nutzung. Die rotierenden Beispiele nehmen die Scheu vor dem leeren Feld und zeigen nebenbei, was das Produkt alles kann. Der Hero wird zur Schnittstelle statt zur Werbefläche.
Wann einsetzen
KI-Tools und Generatoren, Website-Builder, Such- und Konfigurations-Einstiege („Beschreib deine Website“, „Welchen Kurs suchst du?“), Support-Startseiten. Auch als Template-Hero für Kunden mit Such- oder Buchungsfunktion.
Wann nicht
Wenn hinter dem Feld keine echte Funktion steht: Ein Eingabefeld, das nur zur Anmeldung führt, frustriert. Bei Portfolios und Marken ohne interaktives Produkt. Wenn das Ergebnis lange dauert und niemand sieht, dass etwas passiert.
Varianten
- Schleife: Tippen, Stehen, Löschen über einem Overlay (Base44, Anything)
- Fester Präfix: Der Satzanfang bleibt stehen, nur der Rest wechselt (Base44)
- Statischer Platzhalter plus Vorschlags-Chips, ohne Tipp-Animation (Figma Make, Krea, Replit, v0)
- contenteditable-Feld mit data-placeholder statt Textarea (Krea, v0)
- Modus-Umschalter im Feld (z. B. Agent oder Chat), Anhang-Plus, Mikrofon mit Aufnahme-Puls
Umsetzung
Markup: <form><label class="visually-hidden" for="prompt">Beschreib dein Projekt</label><textarea id="prompt" rows="2" placeholder="Zum Beispiel: …"></textarea><div class="ghost" aria-hidden="true"></div><button type="submit" aria-label="Senden">…</button></form>. Das Ghost-Overlay liegt absolut über dem Textfeld (pointer-events: none) und ist nur sichtbar, solange das Feld leer ist, etwa über form:has(textarea:placeholder-shown). Der native Platzhalter bleibt fest und ist transparent, solange das Overlay läuft. Schleife mit setTimeout: alle 45–75 ms ein Zeichen anhängen, 2–4 s stehen lassen, alle 25–50 ms ein Zeichen löschen, 400 ms leer, dann der nächste Text. Anhalten bei focus und input, über einen Pause-Knopf und wenn das Feld den Viewport verlässt (IntersectionObserver). Höhe: field-sizing: content mit min-height 84px und max-height 144px, sonst per JS über scrollHeight. Chips sind <button type="button"> und setzen den Text ins Feld. Senden per Enter (Shift+Enter bricht um) oder wie bei Anything per Cmd/Ctrl+Enter. Fallstricke: Den nativen placeholder nicht im Takt umschreiben, Screenreader lesen sonst jede Änderung vor. Overlay und Textarea brauchen exakt dieselbe Schrift, Zeilenhöhe und dasselbe Padding. Auf dem Telefon kein Autofocus. KI-Hinweis als erste Zeile im Feld (über dem Textfeld, Kürzel „KI“ in der Markenfarbe, ein fetter Satz, ein Halbsatz zur Grenze), Textfeld mit aria-describedby darauf. Das Ergebnis trägt den Hinweis weiter („KI-Entwurf 01“).
Robust umsetzen
Telefon. Feld auf volle Breite, zwei Zeilen. Chips in einer waagerecht wischbaren Reihe. Kein Autofocus, auch nicht nach dem Antippen eines Chips. Kürzere Beispieltexte oder ein statischer Platzhalter.
Ohne Bewegung. Keine Tipp-Schleife: ein fester Beispiel-Platzhalter, die Chips bleiben. Zustandswechsel nur kurz einblenden.
Barrierefrei. Sichtbares oder visuell verstecktes <label> oder aria-label. Das animierte Overlay bekommt aria-hidden="true", damit Screenreader nicht jede Buchstabenänderung ansagen; den festen Platzhalter lesen sie trotzdem. Senden-Button mit Namen und aria-disabled, solange das Feld leer ist. Die Schleife läuft länger als fünf Sekunden, braucht also einen Pause-Knopf und stoppt bei Fokus (WCAG 2.2.2). Chips sind echte Buttons. KI-Kennzeichnung (Art. 50 KI-VO, seit 02.08.2026): Antwortet hinter dem Feld eine KI, steht das direkt beim Start der Unterhaltung sichtbar im Feld, nicht erst in Impressum oder Datenschutz, etwa „Du schreibst mit einem KI-Assistenten.“ (Formulierung nach IHK Halle). Der Satz ist per aria-describedby mit dem Textfeld verbunden, Screenreader hören ihn beim Fokus. Wortlaut vom Kunden prüfen lassen.
Code
<!DOCTYPE html>
<!--
Prompt-Hero mit rotierenden Beispiel-Prompts
Fundus · Hero & Section-Layouts · https://cecodesign.de/lab/fundus/hero-layouts/prompt-hero-eingabefeld/
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>Prompt-Hero mit rotierenden Beispielen · 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: 'Instrument Sans'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/instrument-sans:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 400 700; font-display: swap; }
@font-face { font-family: 'Geist Mono'; src: url('https://cdn.jsdelivr.net/fontsource/fonts/geist-mono:vf@latest/latin-wght-normal.woff2') format('woff2-variations'); font-weight: 100 900; font-display: swap; }
@font-face { font-family: 'Instrument Serif'; font-style: italic; src: url('https://cdn.jsdelivr.net/fontsource/fonts/instrument-serif@latest/latin-400-italic.woff2') format('woff2'); font-weight: 400; font-display: swap; }
:root {
--papier: #f3f0ea; --tinte: #16161a; --leise: rgba(22, 22, 26, .66); --linie: rgba(22, 22, 26, .12);
--emaille: #1f43a6; /* Hausnummern-Blau */
--kurve: cubic-bezier(0.16, 1, 0.3, 1); /* expo.out: Ergebnis klappt auf, 900 ms */
--linear: cubic-bezier(0, 0, 1, 1); /* Tippen ist zeichenweise, ohne Kurve */
}
* { box-sizing: border-box; margin: 0; }
html { background: var(--papier); color: var(--tinte); font-family: 'Instrument Sans', system-ui, sans-serif; -webkit-font-smoothing: antialiased; }
body {
min-height: 100svh; overflow-x: hidden;
background:
radial-gradient(56% 44% at 50% 62%, rgba(31, 67, 166, .11), transparent 70%),
radial-gradient(30% 30% at 82% 18%, rgba(226, 120, 64, .07), transparent 70%),
var(--papier);
}
button, input, textarea { font: inherit; color: inherit; }
.mono { font: 500 11px/1.3 'Geist Mono', ui-monospace, monospace; letter-spacing: .14em; text-transform: uppercase; color: var(--leise); }
.unsichtbar { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
/* Seite: Kopfzeile, Bühne, Demo-Leiste */
.seite { min-height: 100svh; display: grid; grid-template-rows: auto 1fr auto; padding: 0 clamp(16px, 4vw, 48px); }
.nav { display: flex; align-items: center; justify-content: space-between; gap: 16px; height: 72px; }
.marke { display: flex; align-items: center; gap: 10px; font-weight: 600; font-size: 17px; letter-spacing: -.02em; color: inherit; text-decoration: none; }
.marke svg { width: 26px; height: 26px; flex: none; }
.nav ul { display: flex; gap: 28px; list-style: none; padding: 0; font-size: 14px; }
.nav ul a { color: var(--leise); text-decoration: none; transition: color .15s; }
.nav ul a:hover { color: var(--tinte); }
.anmelden { font-size: 14px; text-decoration: none; color: var(--tinte); border: 1px solid var(--linie); border-radius: 999px; padding: 8px 14px; background: rgba(255, 255, 255, .5); }
.buehne { align-self: center; width: min(100%, 780px); justify-self: center; padding: clamp(24px, 5vh, 56px) 0; display: grid; }
/* Kopftext klappt nach dem Absenden weg: grid-template-rows 1fr → 0fr */
.klapp { display: grid; grid-template-rows: 1fr; transition: grid-template-rows .9s var(--kurve), opacity .5s var(--kurve); }
.klapp > div { overflow: hidden; min-height: 0; }
.kopftext { text-align: center; padding-bottom: clamp(24px, 4.4vh, 40px); }
.neu { display: inline-flex; align-items: center; gap: 8px; font-size: 13px; color: var(--leise); border: 1px solid var(--linie); border-radius: 999px; padding: 6px 12px 6px 8px; background: rgba(255, 255, 255, .55); margin-bottom: 22px; }
.neu b { font: 500 10px/1 'Geist Mono', monospace; letter-spacing: .12em; text-transform: uppercase; color: #fff; background: var(--emaille); border-radius: 999px; padding: 4px 7px; }
h1 { font-size: clamp(40px, 6.4vw, 78px); font-weight: 500; line-height: .98; letter-spacing: -.045em; text-wrap: balance; }
h1 em { font-family: 'Instrument Serif', Georgia, serif; font-weight: 400; font-style: italic; letter-spacing: -.02em; color: var(--emaille); padding-right: .04em; }
.unterzeile { font-size: clamp(16px, 1.5vw, 18px); line-height: 1.5; color: var(--leise); max-width: 46ch; margin: 18px auto 0; text-wrap: pretty; }
/* Das Feld */
.feld {
position: relative; background: #fff; border: 1px solid var(--linie); border-radius: 22px;
box-shadow: 0 1px 0 rgba(22, 22, 26, .04), 0 28px 64px -28px rgba(31, 67, 166, .32), 0 8px 18px -12px rgba(22, 22, 26, .12);
transition: border-color .15s, box-shadow .15s;
}
.feld:focus-within { border-color: rgba(31, 67, 166, .45); box-shadow: 0 0 0 4px rgba(31, 67, 166, .12), 0 28px 64px -28px rgba(31, 67, 166, .32); }
.schreibflaeche { position: relative; }
/* KI-Hinweis (Art. 50 KI-VO): steht im Feld selbst, also direkt beim Start der Unterhaltung, nicht erst im Impressum */
.ki-zeile { display: flex; align-items: center; gap: 9px; padding: 14px 22px 0; font-size: 13px; line-height: 1.4; color: var(--leise); }
.ki-zeile b { flex: none; font: 600 10px/1 'Geist Mono', ui-monospace, monospace; letter-spacing: .12em; color: #fff; background: var(--emaille); border-radius: 6px; padding: 4px 6px 3px; }
.ki-zeile strong { color: var(--tinte); font-weight: 600; }
.feld textarea, .geist {
display: block; width: 100%; padding: 20px 22px 6px; font-size: 19px; line-height: 1.5; letter-spacing: -.005em;
white-space: pre-wrap; overflow-wrap: break-word;
}
.feld textarea {
border: 0; outline: 0; background: transparent; resize: none;
field-sizing: content; min-height: 84px; max-height: 144px; /* Base44: 84–144 px */
}
.feld textarea::placeholder { color: var(--leise); opacity: 1; }
.mit-geist textarea::placeholder { color: transparent; }
/* Geist-Overlay: liegt exakt über dem Textfeld, nur sichtbar, solange das Feld leer ist */
.geist { position: absolute; inset: 0; pointer-events: none; color: var(--leise); display: none; overflow: hidden; }
.mit-geist:has(textarea:placeholder-shown) .geist { display: block; }
.geist .praefix { color: var(--tinte); }
.geist .strich { display: inline-block; width: 2px; height: 1.15em; margin-left: 1px; vertical-align: -.2em; background: var(--emaille); border-radius: 1px; animation: blinken 1s steps(1) infinite; }
.geist.tippt .strich { animation: none; } /* beim Tippen ruhig, in Pausen blinkend */
.geist.steht .strich { display: none; }
@keyframes blinken { 50% { opacity: 0; } }
.leiste { display: flex; align-items: center; gap: 8px; padding: 8px 10px 10px 12px; }
.rund { width: 36px; height: 36px; border-radius: 50%; border: 1px solid var(--linie); background: none; display: grid; place-items: center; cursor: pointer; color: var(--leise); transition: color .15s, border-color .15s, background .15s; }
.rund:hover { color: var(--tinte); border-color: rgba(22, 22, 26, .3); }
.rund svg { width: 16px; height: 16px; }
.datei { font-size: 13px; color: var(--leise); display: none; align-items: center; gap: 6px; max-width: 40%; }
.datei.an { display: inline-flex; }
.datei span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tasten { margin-left: auto; display: flex; align-items: center; gap: 12px; }
.tasten .mono { font-size: 10px; letter-spacing: .12em; }
.senden {
width: 40px; height: 40px; border-radius: 50%; border: 0; background: var(--tinte); color: #fff; display: grid; place-items: center; cursor: pointer;
transition: opacity .15s, background .15s, transform .16s var(--kurve);
}
.senden svg { width: 18px; height: 18px; }
.senden[aria-disabled='true'] { opacity: .6; cursor: default; } /* Figma Make: Deckkraft 0.6, solange leer */
.senden:not([aria-disabled='true']) { background: var(--emaille); }
.senden:active, .senden.drueckt { transform: scale(.97); } /* active:scale(0.97), 160 ms */
.senden .kreis { display: none; }
.laedt .senden .pfeil { display: none; }
.laedt .senden .kreis { display: block; animation: drehen .8s linear infinite; }
@keyframes drehen { to { rotate: 360deg; } }
/* Fortschritt beim Anlegen */
.fortschritt { position: absolute; left: 22px; right: 22px; bottom: -1px; height: 2px; border-radius: 2px; overflow: hidden; opacity: 0; }
.fortschritt i { display: block; height: 100%; background: var(--emaille); transform-origin: left; scale: 0 1; }
.laedt .fortschritt { opacity: 1; }
.laedt .fortschritt i { scale: 1 1; transition: scale var(--anlegen, 1400ms) linear; }
/* Chips */
.unten { display: grid; grid-template-rows: 1fr; transition: grid-template-rows .9s var(--kurve), opacity .4s var(--kurve); }
.unten > div { overflow: hidden; min-height: 0; }
.chips { display: flex; flex-wrap: wrap; justify-content: center; gap: 8px; padding: 18px 0 4px; list-style: none; }
@media (hover: hover) and (min-width: 900px) { .unten > div { margin-inline: -40px; } }
.chip {
display: inline-flex; align-items: center; gap: 8px; height: 36px; padding: 0 14px 0 5px; border-radius: 999px;
border: 1px solid var(--linie); background: rgba(255, 255, 255, .62); font-size: 14px; cursor: pointer; white-space: nowrap;
transition: background .15s, border-color .15s, transform .16s var(--kurve);
}
.symbol { width: 26px; height: 26px; border-radius: 50%; background: rgba(31, 67, 166, .08); color: var(--emaille); display: grid; place-items: center; flex: none; transition: background .15s, color .15s; }
.symbol svg { width: 15px; height: 15px; fill: none; stroke: currentColor; stroke-width: 1.3; stroke-linecap: round; stroke-linejoin: round; }
.chip:hover .symbol, .chip.drueckt .symbol { background: var(--emaille); color: #fff; }
.chip:hover, .chip.drueckt { background: #fff; border-color: rgba(22, 22, 26, .28); }
.chip:active, .chip.drueckt { transform: scale(.97); }
.chip:focus-visible, .rund:focus-visible, .senden:focus-visible, .halt:focus-visible { outline: 2px solid var(--emaille); outline-offset: 2px; }
.fuss { display: flex; justify-content: center; align-items: center; gap: 18px; padding-top: 14px; flex-wrap: wrap; }
.fuss p { font-size: 13px; color: var(--leise); }
.halt { display: inline-flex; align-items: center; gap: 7px; background: none; border: 0; padding: 4px 0; cursor: pointer; }
.halt svg { width: 10px; height: 10px; }
.halt:hover { color: var(--tinte); }
.halt[hidden] { display: none; }
.halt .spielen, .halt[aria-pressed='true'] .anhalten { display: none; }
.halt[aria-pressed='true'] .spielen { display: block; }
/* Ergebnis: der Hero wird zur Anwendung */
.entwurf { display: grid; grid-template-rows: 0fr; opacity: 0; transition: grid-template-rows .9s var(--kurve), opacity .6s var(--kurve); }
.entwurf > div { overflow: hidden; min-height: 0; }
.fertig .klapp, .fertig .unten { grid-template-rows: 0fr; opacity: 0; }
.fertig .entwurf { grid-template-rows: 1fr; opacity: 1; }
.karte { margin-top: 18px; background: #fff; border: 1px solid var(--linie); border-radius: 18px; overflow: hidden; box-shadow: 0 24px 50px -30px rgba(22, 22, 26, .3); }
.fensterleiste { display: flex; align-items: center; gap: 6px; height: 36px; padding: 0 14px; border-bottom: 1px solid var(--linie); }
.fensterleiste i { width: 8px; height: 8px; border-radius: 50%; background: rgba(22, 22, 26, .14); }
.fensterleiste .mono { margin: 0 auto; text-transform: none; letter-spacing: .02em; font-size: 11px; background: rgba(22, 22, 26, .05); padding: 4px 10px; border-radius: 6px; }
.karte-innen { display: grid; grid-template-columns: 1.15fr 1fr; }
.karte-bild { aspect-ratio: 3 / 2; width: 100%; height: 100%; object-fit: cover; display: block; scale: 1.06; transition: scale 1.4s var(--kurve); }
.fertig .karte-bild { scale: 1; }
.karte-text { padding: 20px 22px; display: grid; align-content: start; gap: 12px; }
.karte-text h2 { font-size: 26px; font-weight: 500; letter-spacing: -.03em; line-height: 1.05; }
.seiten { list-style: none; padding: 0; display: grid; gap: 7px; font-size: 14px; }
.seiten li { display: flex; align-items: center; gap: 8px; opacity: 0; translate: 0 8px; transition: opacity .6s var(--kurve), translate .6s var(--kurve); transition-delay: calc(var(--i) * 90ms + 250ms); }
.fertig .seiten li { opacity: 1; translate: 0 0; }
.seiten svg { width: 14px; height: 14px; color: var(--emaille); flex: none; }
.knoepfe { display: flex; gap: 8px; flex-wrap: wrap; padding-top: 4px; }
.knopf { height: 36px; padding: 0 14px; border-radius: 999px; border: 1px solid var(--linie); background: none; font-size: 14px; cursor: pointer; transition: background .15s, border-color .15s; }
.knopf.voll { background: var(--tinte); color: #fff; border-color: var(--tinte); }
.knopf:hover { border-color: rgba(22, 22, 26, .35); }
/* Demo-Leiste: Varianten und Werte */
.demo { display: flex; justify-content: space-between; align-items: center; gap: 12px 24px; flex-wrap: wrap; padding: 14px 0 20px; border-top: 1px solid var(--linie); }
.modi { display: flex; gap: 4px; flex-wrap: wrap; }
.modi button { font: inherit; letter-spacing: inherit; text-transform: inherit; color: var(--leise); background: none; border: 1px solid var(--linie); border-radius: 999px; height: 28px; padding: 0 11px; cursor: pointer; transition: color .15s, background .15s, border-color .15s; }
.modi button:hover { color: var(--tinte); border-color: rgba(22, 22, 26, .3); }
.modi button[aria-pressed='true'] { background: var(--tinte); color: var(--papier); border-color: var(--tinte); }
.werte { display: flex; flex-wrap: wrap; gap: 4px 16px; }
.werte b { color: var(--tinte); font-weight: 500; }
/* Vorführung: gezeichneter Zeiger */
.zeiger { position: fixed; left: 0; top: 0; z-index: 9; width: 20px; height: 20px; pointer-events: none; display: none; }
.vorfuehrung .zeiger { display: block; }
/* Galerie-Vorschau: nur die Bühne */
.vorschau body { overflow: hidden; }
.vorschau .demo { display: none; }
/* Touch: Chips waagerecht wischen, kein Tastenhinweis */
@media (hover: none) {
.tasten .mono { display: none; }
.unten > div { margin-inline: calc(-1 * clamp(16px, 4vw, 48px)); }
.chips { flex-wrap: nowrap; justify-content: flex-start; overflow-x: auto; scrollbar-width: none; padding-inline: clamp(16px, 4vw, 48px); scroll-padding-inline: clamp(16px, 4vw, 48px); }
.chips::-webkit-scrollbar { display: none; }
.chip { flex: none; }
}
@media (max-width: 720px) {
.nav ul { display: none; }
.feld textarea, .geist { font-size: 17px; padding: 16px 16px 4px; }
.ki-zeile { padding: 12px 16px 0; font-size: 12.5px; align-items: flex-start; }
.ki-zeile b { margin-top: 1px; }
.karte-innen { grid-template-columns: 1fr; }
.karte-bild { aspect-ratio: 16 / 9; }
.demo { flex-direction: column; align-items: flex-start; }
}
/* Reduzierte Bewegung: keine Tipp-Schleife (statischer Platzhalter), Zustände wechseln ohne Weg */
.reduziert .klapp, .reduziert .unten, .reduziert .entwurf, .reduziert .karte-bild, .reduziert .seiten li { transition: opacity .2s linear; transition-delay: 0s; translate: none; scale: 1; }
.reduziert .laedt .senden .kreis { animation: none; }
@media (forced-colors: active) { button, [role=button], [role=tab], [role=switch] { border: 1px solid ButtonText !important; } }
</style>
</head>
<body>
<div class="seite">
<header class="nav">
<a class="marke" href="#" aria-label="Hausnummer, Startseite">
<svg viewBox="0 0 26 26" aria-hidden="true"><rect x="1" y="1" width="24" height="24" rx="6" fill="#1f43a6"/><rect x="3.5" y="3.5" width="19" height="19" rx="4" fill="none" stroke="#fff" stroke-width="1.2"/><text x="13" y="17.6" text-anchor="middle" font-family="Instrument Sans, sans-serif" font-weight="600" font-size="11.5" fill="#fff">12</text></svg>
Hausnummer
</a>
<ul>
<li><a href="#">Beispiele</a></li>
<li><a href="#">Preise</a></li>
<li><a href="#">Für Agenturen</a></li>
</ul>
<a class="anmelden" href="#">Anmelden</a>
</header>
<main class="buehne" data-buehne>
<div class="klapp"><div>
<div class="kopftext">
<p class="neu"><b>Neu</b> Speisekarten direkt aus PDF übernehmen</p>
<h1>Beschreib deinen Laden. <em>Wir bauen die Seite.</em></h1>
<p class="unterzeile">Ein Satz genügt. Hausnummer legt Seiten, Texte und Öffnungszeiten an. Du änderst nur noch, was nicht passt.</p>
</div>
</div></div>
<form class="feld mit-geist" data-form novalidate>
<label class="unsichtbar" for="prompt">Beschreib deine Website</label>
<p class="ki-zeile" id="ki-hinweis"><b aria-hidden="true">KI</b><span><strong>Du schreibst mit einem KI-Assistenten.</strong> Den Entwurf prüfst du, bevor etwas online geht.</span></p>
<div class="schreibflaeche">
<textarea id="prompt" name="prompt" rows="2" aria-describedby="ki-hinweis" placeholder="Zum Beispiel: Eine Website für unsere Kaffeerösterei, mit Bohnen-Shop" data-feld></textarea>
<div class="geist" aria-hidden="true" data-geist><span class="praefix" data-praefix></span><span data-text></span><span class="strich"></span></div>
</div>
<div class="leiste">
<label class="rund" title="Logo oder Speisekarte anhängen">
<input class="unsichtbar" type="file" accept="image/*,application/pdf" data-datei>
<span class="unsichtbar">Logo oder Speisekarte anhängen</span>
<svg viewBox="0 0 16 16" aria-hidden="true"><path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
</label>
<span class="datei" data-dateiname><svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><path d="M10.5 5.5 6 10a1.4 1.4 0 0 0 2 2l5-5a2.8 2.8 0 0 0-4-4L4 8a4.2 4.2 0 0 0 6 6l3.5-3.5" fill="none" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/></svg><span></span></span>
<div class="tasten">
<span class="mono">↵ Senden</span>
<button class="senden" type="submit" aria-label="Entwurf anlegen" aria-disabled="true" data-senden>
<svg class="pfeil" viewBox="0 0 18 18" aria-hidden="true"><path d="M9 14V4M4.5 8.5 9 4l4.5 4.5" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"/></svg>
<svg class="kreis" viewBox="0 0 18 18" aria-hidden="true"><path d="M9 2.5a6.5 6.5 0 1 1-6.5 6.5" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round"/></svg>
</button>
</div>
</div>
<div class="fortschritt" aria-hidden="true"><i></i></div>
</form>
<div class="unten"><div>
<ul class="chips" aria-label="Beispiele zum Übernehmen">
<li><button class="chip" type="button" data-prompt="Eine Website für unsere Kaffeerösterei, mit Bohnen-Shop und Ausschank am Samstag"><span class="symbol" aria-hidden="true"><svg viewBox="0 0 16 16"><path d="M3.5 6.5h8v3.2a3 3 0 0 1-3 3h-2a3 3 0 0 1-3-3Z M11.5 7.5h1a1.6 1.6 0 0 1 0 3.2h-1.2 M6 2.2c-.6.8.6 1.3 0 2.3 M8.6 2.2c-.6.8.6 1.3 0 2.3"/></svg></span>Rösterei mit Shop</button></li>
<li><button class="chip" type="button" data-prompt="Eine Seite für meine Backstube, mit Brot des Tages und Vorbestellung bis 18 Uhr"><span class="symbol" aria-hidden="true"><svg viewBox="0 0 16 16"><path d="M2.5 11.5V10c0-3 2.5-5 5.5-5s5.5 2 5.5 5v1.5Z M6 6.8l.7 2.4 M8.4 6.4l.4 2.6 M10.8 7l-.1 2.4"/></svg></span>Backstube</button></li>
<li><button class="chip" type="button" data-prompt="Ein dunkler, ruhiger Auftritt für unsere Buchhandlung mit Lesecafé, mit Lesungen und Bestellung"><span class="symbol" aria-hidden="true"><svg viewBox="0 0 16 16"><path d="M8 4.2C6.6 3.2 4.6 2.8 2.5 3v9c2.1-.2 4.1.2 5.5 1.2 1.4-1 3.4-1.4 5.5-1.2V3c-2.1-.2-4.1.2-5.5 1.2Z M8 4.2v9"/></svg></span>Buchhandlung</button></li>
<li><button class="chip" type="button" data-prompt="Eine Seite für meine Töpferei, mit Kursen zum Buchen und Werkstattverkauf"><span class="symbol" aria-hidden="true"><svg viewBox="0 0 16 16"><path d="M6 2.5h4 M6.6 2.5v2.2C4.6 5.6 3.5 7.4 3.5 9.4c0 2.2 2 3.9 4.5 3.9s4.5-1.7 4.5-3.9c0-2-1.1-3.8-3.1-4.7V2.5"/></svg></span>Töpferei</button></li>
<li><button class="chip" type="button" data-prompt="Eine klare Seite für meine Duftmanufaktur, mit Shop und Probenset"><span class="symbol" aria-hidden="true"><svg viewBox="0 0 16 16"><path d="M6.5 2h3v2.5h-3Z M4 4.5h8V13H4Z M6 7.5h4v3H6Z"/></svg></span>Duftmanufaktur</button></li>
</ul>
<div class="fuss">
<p>Kostenlos ausprobieren. Anmelden erst beim Veröffentlichen.</p>
<button class="halt mono" type="button" aria-pressed="false" data-halt>
<svg class="anhalten" viewBox="0 0 10 10" aria-hidden="true"><path d="M2 1h2v8H2zM6 1h2v8H6z" fill="currentColor"/></svg>
<svg class="spielen" viewBox="0 0 10 10" aria-hidden="true"><path d="M2 1l7 4-7 4z" fill="currentColor"/></svg>
<span data-halt-text>Beispiele anhalten</span>
</button>
</div>
</div></div>
<section class="entwurf" aria-live="polite" data-entwurf><div>
<div class="karte">
<div class="fensterleiste"><i></i><i></i><i></i><span class="mono" data-adresse>entwurf.hausnummer.site</span></div>
<div class="karte-innen">
<img class="karte-bild" src="bilder/marke-roestkontor.jpg" alt="" data-bild>
<div class="karte-text">
<p class="mono" data-status>KI-Entwurf 01 · in 1,4 s angelegt</p>
<h2 data-name>Röstkontor</h2>
<ul class="seiten" data-seiten></ul>
<div class="knoepfe">
<button class="knopf voll" type="button">Entwurf bearbeiten</button>
<button class="knopf" type="button" data-neu>Neu beschreiben</button>
</div>
</div>
</div>
</div>
</div></section>
</main>
<footer class="demo">
<div class="modi mono" role="group" aria-label="Variante">
<button type="button" data-variante="schleife" aria-pressed="true">Schleife</button>
<button type="button" data-variante="praefix" aria-pressed="false">Fester Präfix</button>
<button type="button" data-variante="statisch" aria-pressed="false">Statisch + Chips</button>
</div>
<p class="werte mono" data-werte></p>
</footer>
</div>
<svg class="zeiger" data-zeiger viewBox="0 0 18 18" aria-hidden="true"><path d="M2 1.5 15.5 9.2l-6 1.3-2.9 5.6Z" fill="#16161a" stroke="#fff" stroke-width="1.2" stroke-linejoin="round"/></svg>
<script>
(() => {
const html = document.documentElement;
const reduziert = () => html.classList.contains('reduziert');
const touch = matchMedia('(hover: none)');
const $ = s => document.querySelector(s);
const buehne = $('[data-buehne]'), form = $('[data-form]'), feld = $('[data-feld]');
/* Zugang: was gerade weggeklappt ist (opacity 0), ist inert, damit der Fokus nicht ins Unsichtbare springt */
const weg = () => { const f = buehne.classList.contains('fertig'); $('[data-entwurf]').inert = !f; buehne.querySelectorAll('.klapp, .unten').forEach(el => { el.inert = f; }); };
new MutationObserver(weg).observe(buehne, { attributes: true, attributeFilter: ['class'] });
weg();
const geist = $('[data-geist]'), geistPraefix = $('[data-praefix]'), geistText = $('[data-text]');
const senden = $('[data-senden]'), halt = $('[data-halt]'), haltText = $('[data-halt-text]');
const chips = [...document.querySelectorAll('.chip')];
const werte = $('[data-werte]'), zeiger = $('[data-zeiger]');
/* Takt aus Base44 (useTypingAnimation): 45 ms tippen, 25 ms löschen, 2000 ms stehen, 400 ms leer */
const TAKT = { tippen: 45, loeschen: 25, stehen: 2000, leer: 400 };
const ANLEGEN = 1400; // Fortschritt bis zum Entwurf, linear
const BEISPIELE = {
schleife: [
'Eine Website für unsere Kaffeerösterei, mit Bohnen-Shop und Ausschank am Samstag',
'Eine Seite für meine Backstube, mit Vorbestellung bis 18 Uhr',
'Ein dunkler, ruhiger Auftritt für unsere Buchhandlung, mit Lesungen',
'Eine Seite für meine Töpferei, mit Kursen zum Buchen'
],
kurz: [ // Telefon: kürzere Sätze, damit die Schleife nicht ewig dauert
'Rösterei mit Bohnen-Shop',
'Backstube mit Vorbestellung',
'Buchhandlung, dunkel, mit Lesungen',
'Töpferei mit Kursen'
],
praefix: 'Eine Website für ',
rest: ['unsere Kaffeerösterei', 'meine Backstube', 'unsere Buchhandlung', 'meine Töpferei', 'meine Duftmanufaktur']
};
let variante = 'schleife';
let angehalten = false, fokus = false, sichtbar = true, fertig = false, extern = false;
/* ---------- Tipp-Schleife ---------- */
let nr = 0, pos = 0, phase = 'tippen', uhr = null, laeuft = false;
const liste = () => variante === 'praefix' ? BEISPIELE.rest : (touch.matches ? BEISPIELE.kurz : BEISPIELE.schleife);
function malen() {
geistPraefix.textContent = variante === 'praefix' ? BEISPIELE.praefix : '';
geistText.textContent = liste()[nr % liste().length].slice(0, pos);
}
function takt() {
const ziel = liste()[nr % liste().length];
geist.classList.add('tippt');
if (phase === 'tippen') {
pos++; malen();
if (pos >= ziel.length) { phase = 'loeschen'; geist.classList.remove('tippt'); return plane(TAKT.stehen); }
return plane(TAKT.tippen);
}
pos--; malen();
if (pos <= 0) { phase = 'tippen'; nr = (nr + 1) % liste().length; geist.classList.remove('tippt'); return plane(TAKT.leer); }
plane(TAKT.loeschen);
}
function plane(ms) { uhr = setTimeout(takt, ms); }
function starte() { if (laeuft) return; laeuft = true; geist.classList.remove('steht'); plane(pos === 0 ? TAKT.leer : TAKT.tippen); }
function stoppe(ganz) {
clearTimeout(uhr); laeuft = false; geist.classList.remove('tippt');
if (ganz) { // angehalten oder Fokus: aktuelles Beispiel vollständig und still zeigen
pos = liste()[nr % liste().length].length; phase = 'loeschen'; malen(); geist.classList.add('steht');
}
}
const schleifeErlaubt = () => variante !== 'statisch' && !reduziert();
function pruefen() {
form.classList.toggle('mit-geist', schleifeErlaubt());
halt.hidden = !schleifeErlaubt();
const an = schleifeErlaubt() && !angehalten && !fokus && sichtbar && !fertig && !extern && feld.value === '';
if (an) starte(); else if (laeuft) stoppe(angehalten || fokus);
senden.setAttribute('aria-disabled', feld.value.trim() ? 'false' : 'true');
}
/* ---------- Feld ---------- */
const hoehe = () => { if (CSS.supports('field-sizing', 'content')) return; feld.style.height = 'auto'; feld.style.height = Math.min(144, Math.max(84, feld.scrollHeight)) + 'px'; };
feld.addEventListener('focus', () => { fokus = true; pruefen(); });
feld.addEventListener('blur', () => { fokus = false; setTimeout(pruefen, 1200); });
feld.addEventListener('input', () => { hoehe(); pruefen(); });
feld.addEventListener('keydown', e => {
if (e.key === 'Enter' && !e.shiftKey && !e.isComposing) { e.preventDefault(); form.requestSubmit(); }
});
new IntersectionObserver(([e]) => { sichtbar = e.isIntersecting; pruefen(); }).observe(form);
document.addEventListener('visibilitychange', () => { sichtbar = !document.hidden; pruefen(); });
halt.addEventListener('click', () => {
angehalten = !angehalten;
halt.setAttribute('aria-pressed', angehalten);
haltText.textContent = angehalten ? 'Beispiele abspielen' : 'Beispiele anhalten';
if (!angehalten) { pos = 0; phase = 'tippen'; nr = (nr + 1) % liste().length; malen(); }
pruefen();
});
function chipSetzen(chip, mitFokus) {
feld.value = chip.dataset.prompt; hoehe(); pruefen();
if (mitFokus) { feld.focus(); feld.setSelectionRange(feld.value.length, feld.value.length); }
}
// Am Telefon nicht fokussieren: sonst springt die Tastatur auf
chips.forEach(c => c.addEventListener('click', () => chipSetzen(c, !touch.matches)));
$('[data-datei]').addEventListener('change', e => {
const f = e.target.files[0], n = $('[data-dateiname]');
n.classList.toggle('an', !!f); n.querySelector('span').textContent = f ? f.name : '';
});
/* ---------- Absenden: Entwurf anlegen ---------- */
const PROFILE = [
{ such: /buch|lesung|lesecaf|roman/i, name: 'Eselsohr', bild: 'marke-eselsohr', adresse: 'eselsohr-buchhandlung', seiten: ['Start', 'Lesungen und Termine', 'Bücher bestellen', 'Lesecafé und Öffnungszeiten'] },
{ such: /back|bäcker|brot|sauerteig|brötchen/i, name: 'Krume', bild: 'marke-krume', adresse: 'krume-backstube', seiten: ['Start', 'Brot des Tages', 'Vorbestellen', 'Filialen und Öffnungszeiten'] },
{ such: /töpfer|keramik|\bton\b|kurs/i, name: 'Werkstatt Ufer', bild: 'keramik-stillleben', adresse: 'werkstatt-ufer', seiten: ['Start', 'Kurse buchen', 'Werkstattverkauf', 'Kontakt'] },
{ such: /duft|parfum|parfüm|flakon/i, name: 'Sillage', bild: 'flakon', adresse: 'sillage-duefte', seiten: ['Start', 'Düfte', 'Probenset bestellen', 'Manufaktur'] },
{ such: /caf[eé]|kaffee|röst|bohne|espresso/i, name: 'Röstkontor', bild: 'marke-roestkontor', adresse: 'roestkontor', seiten: ['Start', 'Bohnen-Shop', 'Ausschank am Samstag', 'Anfahrt und Öffnungszeiten'] }
];
const STANDARD = { name: 'Dein neuer Auftritt', bild: 'fenster-licht', adresse: 'entwurf', seiten: ['Start', 'Über uns', 'Angebot', 'Kontakt'] };
const haken = '<svg viewBox="0 0 14 14" aria-hidden="true"><path d="M3 7.4 5.8 10 11 4" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>';
let anlegUhr = null;
form.addEventListener('submit', e => {
e.preventDefault();
const text = feld.value.trim();
if (!text) { feld.focus(); return; }
anlegen(text);
});
function anlegen(text) {
const p = PROFILE.find(x => x.such.test(text)) || STANDARD;
const ms = reduziert() ? 300 : ANLEGEN;
$('[data-name]').textContent = p.name;
$('[data-bild]').src = `bilder/${p.bild}.jpg`;
$('[data-adresse]').textContent = `${p.adresse}.hausnummer.site`;
$('[data-status]').textContent = `KI-Entwurf 01 · ${p.seiten.length} Seiten angelegt`;
$('[data-seiten]').innerHTML = p.seiten.map((s, i) => `<li style="--i:${i}">${haken}${s}</li>`).join('');
form.style.setProperty('--anlegen', ms + 'ms');
form.classList.add('laedt'); pruefen();
clearTimeout(anlegUhr);
anlegUhr = setTimeout(() => { form.classList.remove('laedt'); buehne.classList.add('fertig'); fertig = true; pruefen(); }, ms);
}
function zuruecksetzen() {
clearTimeout(anlegUhr);
form.classList.remove('laedt'); buehne.classList.remove('fertig'); fertig = false;
feld.value = ''; hoehe(); stoppe(false);
nr = 0; pos = 0; phase = 'tippen'; malen();
pruefen();
}
$('[data-neu]').addEventListener('click', () => { zuruecksetzen(); if (!touch.matches) feld.focus(); });
/* ---------- Varianten und Werte ---------- */
function zeigeWerte() {
const w = [];
if (reduziert()) w.push('<span>Reduzierte Bewegung: <b>fester Platzhalter, keine Schleife</b></span>');
else if (variante === 'statisch') w.push('<span>Platzhalter <b>statisch</b></span>', '<span>Senden leer <b>Deckkraft 0.6</b></span>');
else {
w.push(`<span>Tippen <b>${TAKT.tippen} ms</b></span>`, `<span>Löschen <b>${TAKT.loeschen} ms</b></span>`, `<span>Pause <b>${TAKT.stehen} / ${TAKT.leer} ms</b></span>`);
if (variante === 'praefix') w.push(`<span>Präfix <b>„${BEISPIELE.praefix.trim()}“</b></span>`);
}
w.push('<span>Feld <b>84–144 px</b></span>', '<span>Entwurf <b>expo.out 900 ms</b></span>');
werte.innerHTML = w.join('');
}
document.querySelectorAll('[data-variante]').forEach(b => b.addEventListener('click', () => {
variante = b.dataset.variante;
document.querySelectorAll('[data-variante]').forEach(x => x.setAttribute('aria-pressed', x === b));
stoppe(false); nr = 0; pos = 0; phase = 'tippen'; malen();
zeigeWerte(); pruefen();
}));
/* ---------- Vorführung: zwei Beispiele laufen, dann tippt ein gedachter Zeiger einen Chip an und sendet ---------- */
let lauf = 0, zx = innerWidth * 0.82, zy = innerHeight * 0.95;
const warte = ms => new Promise(r => setTimeout(r, ms));
function zeigerZu(el, dauer, dx = 0.5, dy = 0.55) {
const r = el.getBoundingClientRect();
const x = r.left + r.width * dx, y = r.top + r.height * dy;
zeiger.animate([{ transform: `translate(${zx}px, ${zy}px)` }, { transform: `translate(${x}px, ${y}px)` }],
{ duration: reduziert() ? 1 : dauer, easing: 'cubic-bezier(0.16, 1, 0.3, 1)', fill: 'forwards' });
zx = x; zy = y;
return warte(reduziert() ? 50 : dauer);
}
async function vorfuehrLauf(id) {
const weg = () => id !== lauf;
extern = false;
zuruecksetzen();
zeiger.animate([{ transform: `translate(${innerWidth * 0.82}px, ${innerHeight * 0.95}px)` }], { duration: 1, fill: 'forwards' });
zx = innerWidth * 0.82; zy = innerHeight * 0.95;
await warte(schleifeErlaubt() ? 8600 : 2400); if (weg()) return; // erstes Beispiel ganz, zweites halb
const chip = chips[1];
extern = true; pruefen();
await zeigerZu(chip, 900); if (weg()) return;
chip.classList.add('drueckt'); await warte(160); chip.classList.remove('drueckt');
chipSetzen(chip, false);
await warte(1000); if (weg()) return;
await zeigerZu(senden, 800); if (weg()) return;
senden.classList.add('drueckt'); await warte(160); senden.classList.remove('drueckt');
anlegen(feld.value);
await warte((reduziert() ? 300 : ANLEGEN) + 600); if (weg()) return;
zeigerZu(document.querySelector('.karte'), 1200, 0.78, 1.15);
await warte(3600); if (weg()) return;
extern = false;
vorfuehrLauf(id);
}
function vorfuehren() {
pause();
html.classList.add('vorfuehrung');
vorfuehrLauf(++lauf);
}
function pause() { lauf++; extern = true; pruefen(); }
function weiter() {
if (html.classList.contains('vorfuehrung')) vorfuehren();
else { extern = false; pruefen(); }
}
function neustart() { lauf++; extern = false; html.classList.remove('vorfuehrung'); zuruecksetzen(); }
touch.addEventListener('change', () => { stoppe(false); nr = 0; pos = 0; malen(); pruefen(); });
// .reduziert kann später kommen (Rahmen-Skript, Schalter der Detailseite)
let warReduziert = reduziert();
new MutationObserver(() => { if (reduziert() !== warReduziert) { warReduziert = reduziert(); stoppe(false); pos = 0; malen(); zeigeWerte(); pruefen(); } })
.observe(html, { attributes: true, attributeFilter: ['class'] });
hoehe(); malen(); zeigeWerte(); pruefen();
})();
</script>
</body>
</html>