Boden-Stufe: durchgehende Wiese mit Blumen und flatternden Schmetterlingen statt schwebender Fragmente

This commit is contained in:
schmop 2026-05-31 17:56:40 +02:00
parent 2adcd0d545
commit 72aa9837ce
3 changed files with 164 additions and 20 deletions

View File

@ -4,7 +4,8 @@
import Cloud from '../svg/Cloud.svelte';
import Moon from '../svg/Moon.svelte';
import Star from '../svg/Star.svelte';
import Ground from '../svg/Ground.svelte';
import Meadow from '../svg/Meadow.svelte';
import Butterfly from '../svg/Butterfly.svelte';
let { stage }: { stage: Stage } = $props();
@ -57,15 +58,6 @@
{ l: 93, t: 61, s: 30, sc: 3, rot: 10, d: 2.5, dl: 0.2 },
];
const grass: Deco[] = [
{ l: 5, b: 1, s: 66, d: 2.4, dl: 0 },
{ l: 19, b: 2, s: 48, flip: true, d: 2.8, dl: 0.4 },
{ l: 33, b: 1, s: 56, d: 2.2, dl: 0.7 },
{ l: 63, b: 2, s: 50, flip: true, d: 2.6, dl: 0.2 },
{ l: 78, b: 1, s: 70, d: 2.3, dl: 0.9 },
{ l: 90, b: 2, s: 46, flip: true, d: 2.7, dl: 0.5 },
];
function baseStyle(d: Deco): string {
const vert = d.b != null ? `bottom:${d.b}%` : `top:${d.t}%`;
return `left:${d.l}%; ${vert}; transform: scaleX(${d.flip ? -1 : 1}) rotate(${d.rot ?? 0}deg);`;
@ -98,11 +90,10 @@
</span>
{/each}
{:else}
{#each grass as d, i (i)}
<span class="deco" style={baseStyle(d)}>
<span class="anim sway" style={fx(d)}><Ground size={d.s} /></span>
</span>
{/each}
<!-- Boden-Stufe: durchgehende Wiese unten + flatternde Schmetterlinge. -->
<Meadow />
<span class="butterfly b1"><Butterfly size={50} color="#ff8aa8" stroke="#c25578" flap={0.45} /></span>
<span class="butterfly b2"><Butterfly size={40} color="#8ad1ff" stroke="#3a82c2" flap={0.55} /></span>
{/if}
</div>
@ -139,10 +130,22 @@
50% { opacity: 1; transform: scale(1.35); }
}
.sway { animation: sway var(--dur, 2.5s) ease-in-out infinite alternate; transform-origin: bottom center; }
@keyframes sway {
from { transform: rotate(-12deg); }
to { transform: rotate(12deg); }
.butterfly { position: absolute; }
.b1 { animation: fly1 9s ease-in-out infinite; }
.b2 { animation: fly2 11s ease-in-out infinite; }
@keyframes fly1 {
0% { left: 12%; top: 62%; }
25% { left: 24%; top: 44%; }
50% { left: 35%; top: 56%; }
75% { left: 20%; top: 50%; }
100% { left: 12%; top: 62%; }
}
@keyframes fly2 {
0% { left: 64%; top: 42%; }
25% { left: 80%; top: 58%; }
50% { left: 88%; top: 46%; }
75% { left: 72%; top: 38%; }
100% { left: 64%; top: 42%; }
}
.moon {
@ -179,6 +182,6 @@
}
@media (prefers-reduced-motion: reduce) {
.anim, .moon { animation: none; }
.anim, .moon, .butterfly { animation: none; }
}
</style>

View File

@ -0,0 +1,46 @@
<script lang="ts">
// Schmetterling von oben: zwei Flügelpaare, schmaler Körper, Fühler — keine Gesichter.
// Die Flügel flattern (scaleX-Puls um die Körpermitte).
let {
size = 48,
color = '#ff8aa8',
stroke = '#c25578',
flap = 0.5,
}: { size?: number; color?: string; stroke?: string; flap?: number } = $props();
</script>
<svg
width={size}
height={size * 0.85}
viewBox="0 0 64 54"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
style="--flap:{flap}s"
>
<g class="wings" {stroke} stroke-width="1.5">
<ellipse cx="19" cy="19" rx="16" ry="13" fill={color} />
<ellipse cx="45" cy="19" rx="16" ry="13" fill={color} />
<ellipse cx="23" cy="37" rx="11" ry="10" fill={color} />
<ellipse cx="41" cy="37" rx="11" ry="10" fill={color} />
<circle cx="17" cy="18" r="3.5" fill="#ffffff" opacity="0.5" stroke="none" />
<circle cx="47" cy="18" r="3.5" fill="#ffffff" opacity="0.5" stroke="none" />
</g>
<ellipse cx="32" cy="28" rx="2.6" ry="15" fill="#4a3a2a" />
<path d="M32 14 Q28 6 23 5" stroke="#4a3a2a" stroke-width="1.5" fill="none" stroke-linecap="round" />
<path d="M32 14 Q36 6 41 5" stroke="#4a3a2a" stroke-width="1.5" fill="none" stroke-linecap="round" />
</svg>
<style>
.wings {
transform-box: fill-box;
transform-origin: center;
animation: flap var(--flap, 0.5s) ease-in-out infinite;
}
@keyframes flap {
0%, 100% { transform: scaleX(1); }
50% { transform: scaleX(0.55); }
}
@media (prefers-reduced-motion: reduce) {
.wings { animation: none; }
}
</style>

View File

@ -0,0 +1,95 @@
<script lang="ts">
// Wehende Wiese über die volle Breite: Erdschicht, zweilagige Grasnarbe mit welliger
// Oberkante und darauf verteilte, sich wiegende Grashalme und Blümchen.
type Tuft = {
l: number; type: 'blade' | 'flower'; color: string; h: number; d: number; dl: number; flip: boolean;
};
const tufts: Tuft[] = [
{ l: 5, type: 'blade', color: '#4ea34e', h: 38, d: 2.6, dl: 0, flip: false },
{ l: 13, type: 'flower', color: '#ff7aa8', h: 44, d: 3.0, dl: 0.5, flip: false },
{ l: 23, type: 'blade', color: '#3f8f3f', h: 30, d: 2.4, dl: 0.8, flip: true },
{ l: 33, type: 'flower', color: '#ffd24a', h: 48, d: 3.2, dl: 0.2, flip: false },
{ l: 44, type: 'blade', color: '#4ea34e', h: 34, d: 2.7, dl: 1.0, flip: false },
{ l: 55, type: 'flower', color: '#c79aff', h: 42, d: 2.9, dl: 0.4, flip: true },
{ l: 65, type: 'blade', color: '#3f8f3f', h: 32, d: 2.5, dl: 0.7, flip: false },
{ l: 75, type: 'flower', color: '#ff9d5c', h: 46, d: 3.1, dl: 0.1, flip: false },
{ l: 85, type: 'blade', color: '#4ea34e', h: 36, d: 2.6, dl: 0.9, flip: true },
{ l: 93, type: 'flower', color: '#ff7aa8', h: 38, d: 2.8, dl: 0.3, flip: false },
];
</script>
<div class="meadow" aria-hidden="true">
<div class="soil"></div>
<svg class="grass back" preserveAspectRatio="none" viewBox="0 0 100 24">
<path d="M0 24 L0 13 Q5 6 10 13 Q15 6 20 13 Q25 6 30 13 Q35 6 40 13 Q45 6 50 13 Q55 6 60 13 Q65 6 70 13 Q75 6 80 13 Q85 6 90 13 Q95 6 100 13 L100 24 Z" />
</svg>
<svg class="grass front" preserveAspectRatio="none" viewBox="0 0 100 24">
<path d="M0 24 L0 15 Q4 9 9 15 Q14 9 19 15 Q24 9 29 15 Q34 9 39 15 Q44 9 49 15 Q54 9 59 15 Q64 9 69 15 Q74 9 79 15 Q84 9 89 15 Q94 9 99 15 L100 24 Z" />
</svg>
{#each tufts as t, i (i)}
<span class="tuft" style="left:{t.l}%; transform: scaleX({t.flip ? -1 : 1});">
<span class="sway" style="--dur:{t.d}s; animation-delay:{t.dl}s;">
{#if t.type === 'blade'}
<svg width={t.h * 0.45} height={t.h} viewBox="0 0 18 40" xmlns="http://www.w3.org/2000/svg">
<path d="M9 40 Q3 22 8 2 Q12 22 11 40 Z" fill={t.color} />
</svg>
{:else}
<svg width={t.h * 0.6} height={t.h} viewBox="0 0 24 40" xmlns="http://www.w3.org/2000/svg">
<path d="M12 40 L12 18" stroke="#3f8f3f" stroke-width="2.5" />
<circle cx="12" cy="11" r="6" fill={t.color} />
<circle cx="6" cy="13" r="4.5" fill={t.color} />
<circle cx="18" cy="13" r="4.5" fill={t.color} />
<circle cx="9" cy="6" r="4.5" fill={t.color} />
<circle cx="15" cy="6" r="4.5" fill={t.color} />
<circle cx="12" cy="10" r="3" fill="#fff3b0" />
</svg>
{/if}
</span>
</span>
{/each}
</div>
<style>
.meadow {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 128px;
}
.soil {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 60px;
background: linear-gradient(to top, #6e4527, #9c6a3c);
}
.grass {
position: absolute;
left: 0;
right: 0;
width: 100%;
}
.grass path { fill: currentColor; }
.grass.back { bottom: 52px; height: 44px; color: #3f8f3f; }
.grass.front { bottom: 44px; height: 44px; color: #5fb85f; }
.tuft {
position: absolute;
bottom: 58px;
}
.sway {
display: inline-block;
transform-origin: bottom center;
animation: sway var(--dur, 2.6s) ease-in-out infinite alternate;
}
@keyframes sway {
from { transform: rotate(-9deg); }
to { transform: rotate(9deg); }
}
@media (prefers-reduced-motion: reduce) {
.sway { animation: none; }
}
</style>