@autumnsgrove/groveengine 0.7.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/dist/ui/components/content/RoadmapPreview.svelte +91 -0
  2. package/dist/ui/components/content/RoadmapPreview.svelte.d.ts +36 -0
  3. package/dist/ui/components/content/index.d.ts +1 -0
  4. package/dist/ui/components/content/index.js +1 -0
  5. package/dist/ui/components/nature/Logo.svelte +224 -0
  6. package/dist/ui/components/nature/Logo.svelte.d.ts +14 -0
  7. package/dist/ui/components/nature/botanical/Acorn.svelte +48 -0
  8. package/dist/ui/components/nature/botanical/Acorn.svelte.d.ts +8 -0
  9. package/dist/ui/components/nature/botanical/Berry.svelte +67 -0
  10. package/dist/ui/components/nature/botanical/Berry.svelte.d.ts +8 -0
  11. package/dist/ui/components/nature/botanical/DandelionPuff.svelte +98 -0
  12. package/dist/ui/components/nature/botanical/DandelionPuff.svelte.d.ts +8 -0
  13. package/dist/ui/components/nature/botanical/FallingLeavesLayer.svelte +170 -0
  14. package/dist/ui/components/nature/botanical/FallingLeavesLayer.svelte.d.ts +35 -0
  15. package/dist/ui/components/nature/botanical/FallingPetalsLayer.svelte +174 -0
  16. package/dist/ui/components/nature/botanical/FallingPetalsLayer.svelte.d.ts +25 -0
  17. package/dist/ui/components/nature/botanical/Leaf.svelte +77 -0
  18. package/dist/ui/components/nature/botanical/Leaf.svelte.d.ts +10 -0
  19. package/dist/ui/components/nature/botanical/LeafFalling.svelte +186 -0
  20. package/dist/ui/components/nature/botanical/LeafFalling.svelte.d.ts +22 -0
  21. package/dist/ui/components/nature/botanical/PetalFalling.svelte +266 -0
  22. package/dist/ui/components/nature/botanical/PetalFalling.svelte.d.ts +25 -0
  23. package/dist/ui/components/nature/botanical/PineCone.svelte +61 -0
  24. package/dist/ui/components/nature/botanical/PineCone.svelte.d.ts +7 -0
  25. package/dist/ui/components/nature/botanical/Vine.svelte +102 -0
  26. package/dist/ui/components/nature/botanical/Vine.svelte.d.ts +11 -0
  27. package/dist/ui/components/nature/botanical/index.d.ts +10 -0
  28. package/dist/ui/components/nature/botanical/index.js +11 -0
  29. package/dist/ui/components/nature/creatures/Bee.svelte +78 -0
  30. package/dist/ui/components/nature/creatures/Bee.svelte.d.ts +9 -0
  31. package/dist/ui/components/nature/creatures/Bird.svelte +94 -0
  32. package/dist/ui/components/nature/creatures/Bird.svelte.d.ts +11 -0
  33. package/dist/ui/components/nature/creatures/BirdFlying.svelte +83 -0
  34. package/dist/ui/components/nature/creatures/BirdFlying.svelte.d.ts +9 -0
  35. package/dist/ui/components/nature/creatures/Bluebird.svelte +95 -0
  36. package/dist/ui/components/nature/creatures/Bluebird.svelte.d.ts +12 -0
  37. package/dist/ui/components/nature/creatures/Butterfly.svelte +87 -0
  38. package/dist/ui/components/nature/creatures/Butterfly.svelte.d.ts +9 -0
  39. package/dist/ui/components/nature/creatures/Cardinal.svelte +95 -0
  40. package/dist/ui/components/nature/creatures/Cardinal.svelte.d.ts +12 -0
  41. package/dist/ui/components/nature/creatures/Chickadee.svelte +97 -0
  42. package/dist/ui/components/nature/creatures/Chickadee.svelte.d.ts +12 -0
  43. package/dist/ui/components/nature/creatures/Deer.svelte +95 -0
  44. package/dist/ui/components/nature/creatures/Deer.svelte.d.ts +9 -0
  45. package/dist/ui/components/nature/creatures/Firefly.svelte +111 -0
  46. package/dist/ui/components/nature/creatures/Firefly.svelte.d.ts +10 -0
  47. package/dist/ui/components/nature/creatures/Owl.svelte +91 -0
  48. package/dist/ui/components/nature/creatures/Owl.svelte.d.ts +9 -0
  49. package/dist/ui/components/nature/creatures/Rabbit.svelte +90 -0
  50. package/dist/ui/components/nature/creatures/Rabbit.svelte.d.ts +9 -0
  51. package/dist/ui/components/nature/creatures/Robin.svelte +98 -0
  52. package/dist/ui/components/nature/creatures/Robin.svelte.d.ts +12 -0
  53. package/dist/ui/components/nature/creatures/Squirrel.svelte +97 -0
  54. package/dist/ui/components/nature/creatures/Squirrel.svelte.d.ts +9 -0
  55. package/dist/ui/components/nature/creatures/index.d.ts +13 -0
  56. package/dist/ui/components/nature/creatures/index.js +14 -0
  57. package/dist/ui/components/nature/ground/Bush.svelte +57 -0
  58. package/dist/ui/components/nature/ground/Bush.svelte.d.ts +10 -0
  59. package/dist/ui/components/nature/ground/Crocus.svelte +83 -0
  60. package/dist/ui/components/nature/ground/Crocus.svelte.d.ts +12 -0
  61. package/dist/ui/components/nature/ground/Daffodil.svelte +75 -0
  62. package/dist/ui/components/nature/ground/Daffodil.svelte.d.ts +11 -0
  63. package/dist/ui/components/nature/ground/Fern.svelte +72 -0
  64. package/dist/ui/components/nature/ground/Fern.svelte.d.ts +10 -0
  65. package/dist/ui/components/nature/ground/FlowerWild.svelte +60 -0
  66. package/dist/ui/components/nature/ground/FlowerWild.svelte.d.ts +10 -0
  67. package/dist/ui/components/nature/ground/GrassTuft.svelte +49 -0
  68. package/dist/ui/components/nature/ground/GrassTuft.svelte.d.ts +10 -0
  69. package/dist/ui/components/nature/ground/Log.svelte +42 -0
  70. package/dist/ui/components/nature/ground/Log.svelte.d.ts +7 -0
  71. package/dist/ui/components/nature/ground/Mushroom.svelte +48 -0
  72. package/dist/ui/components/nature/ground/Mushroom.svelte.d.ts +9 -0
  73. package/dist/ui/components/nature/ground/MushroomCluster.svelte +41 -0
  74. package/dist/ui/components/nature/ground/MushroomCluster.svelte.d.ts +8 -0
  75. package/dist/ui/components/nature/ground/Rock.svelte +59 -0
  76. package/dist/ui/components/nature/ground/Rock.svelte.d.ts +8 -0
  77. package/dist/ui/components/nature/ground/Stump.svelte +44 -0
  78. package/dist/ui/components/nature/ground/Stump.svelte.d.ts +8 -0
  79. package/dist/ui/components/nature/ground/Tulip.svelte +79 -0
  80. package/dist/ui/components/nature/ground/Tulip.svelte.d.ts +11 -0
  81. package/dist/ui/components/nature/ground/index.d.ts +12 -0
  82. package/dist/ui/components/nature/ground/index.js +13 -0
  83. package/dist/ui/components/nature/index.d.ts +28 -0
  84. package/dist/ui/components/nature/index.js +38 -0
  85. package/dist/ui/components/nature/palette.d.ts +491 -0
  86. package/dist/ui/components/nature/palette.js +384 -0
  87. package/dist/ui/components/nature/sky/Cloud.svelte +122 -0
  88. package/dist/ui/components/nature/sky/Cloud.svelte.d.ts +11 -0
  89. package/dist/ui/components/nature/sky/CloudWispy.svelte +79 -0
  90. package/dist/ui/components/nature/sky/CloudWispy.svelte.d.ts +9 -0
  91. package/dist/ui/components/nature/sky/Moon.svelte +60 -0
  92. package/dist/ui/components/nature/sky/Moon.svelte.d.ts +9 -0
  93. package/dist/ui/components/nature/sky/Rainbow.svelte +101 -0
  94. package/dist/ui/components/nature/sky/Rainbow.svelte.d.ts +8 -0
  95. package/dist/ui/components/nature/sky/Star.svelte +84 -0
  96. package/dist/ui/components/nature/sky/Star.svelte.d.ts +10 -0
  97. package/dist/ui/components/nature/sky/StarCluster.svelte +85 -0
  98. package/dist/ui/components/nature/sky/StarCluster.svelte.d.ts +9 -0
  99. package/dist/ui/components/nature/sky/StarShooting.svelte +90 -0
  100. package/dist/ui/components/nature/sky/StarShooting.svelte.d.ts +9 -0
  101. package/dist/ui/components/nature/sky/Sun.svelte +70 -0
  102. package/dist/ui/components/nature/sky/Sun.svelte.d.ts +9 -0
  103. package/dist/ui/components/nature/sky/index.d.ts +8 -0
  104. package/dist/ui/components/nature/sky/index.js +9 -0
  105. package/dist/ui/components/nature/structural/Birdhouse.svelte +53 -0
  106. package/dist/ui/components/nature/structural/Birdhouse.svelte.d.ts +8 -0
  107. package/dist/ui/components/nature/structural/Bridge.svelte +65 -0
  108. package/dist/ui/components/nature/structural/Bridge.svelte.d.ts +7 -0
  109. package/dist/ui/components/nature/structural/FencePost.svelte +54 -0
  110. package/dist/ui/components/nature/structural/FencePost.svelte.d.ts +8 -0
  111. package/dist/ui/components/nature/structural/GardenGate.svelte +70 -0
  112. package/dist/ui/components/nature/structural/GardenGate.svelte.d.ts +8 -0
  113. package/dist/ui/components/nature/structural/Lantern.svelte +113 -0
  114. package/dist/ui/components/nature/structural/Lantern.svelte.d.ts +10 -0
  115. package/dist/ui/components/nature/structural/Lattice.svelte +89 -0
  116. package/dist/ui/components/nature/structural/Lattice.svelte.d.ts +8 -0
  117. package/dist/ui/components/nature/structural/LatticeWithVine.svelte +89 -0
  118. package/dist/ui/components/nature/structural/LatticeWithVine.svelte.d.ts +11 -0
  119. package/dist/ui/components/nature/structural/StonePath.svelte +48 -0
  120. package/dist/ui/components/nature/structural/StonePath.svelte.d.ts +7 -0
  121. package/dist/ui/components/nature/structural/index.d.ts +8 -0
  122. package/dist/ui/components/nature/structural/index.js +9 -0
  123. package/dist/ui/components/nature/trees/TreeAspen.svelte +163 -0
  124. package/dist/ui/components/nature/trees/TreeAspen.svelte.d.ts +11 -0
  125. package/dist/ui/components/nature/trees/TreeBirch.svelte +186 -0
  126. package/dist/ui/components/nature/trees/TreeBirch.svelte.d.ts +11 -0
  127. package/dist/ui/components/nature/trees/TreeCherry.svelte +108 -0
  128. package/dist/ui/components/nature/trees/TreeCherry.svelte.d.ts +11 -0
  129. package/dist/ui/components/nature/trees/TreePine.svelte +79 -0
  130. package/dist/ui/components/nature/trees/TreePine.svelte.d.ts +11 -0
  131. package/dist/ui/components/nature/trees/index.d.ts +4 -0
  132. package/dist/ui/components/nature/trees/index.js +5 -0
  133. package/dist/ui/components/nature/water/LilyPad.svelte +99 -0
  134. package/dist/ui/components/nature/water/LilyPad.svelte.d.ts +10 -0
  135. package/dist/ui/components/nature/water/Pond.svelte +104 -0
  136. package/dist/ui/components/nature/water/Pond.svelte.d.ts +8 -0
  137. package/dist/ui/components/nature/water/Reeds.svelte +85 -0
  138. package/dist/ui/components/nature/water/Reeds.svelte.d.ts +11 -0
  139. package/dist/ui/components/nature/water/Stream.svelte +98 -0
  140. package/dist/ui/components/nature/water/Stream.svelte.d.ts +8 -0
  141. package/dist/ui/components/nature/water/index.d.ts +4 -0
  142. package/dist/ui/components/nature/water/index.js +5 -0
  143. package/dist/ui/components/nature/weather/SnowfallLayer.svelte +175 -0
  144. package/dist/ui/components/nature/weather/SnowfallLayer.svelte.d.ts +25 -0
  145. package/dist/ui/components/nature/weather/Snowflake.svelte +99 -0
  146. package/dist/ui/components/nature/weather/Snowflake.svelte.d.ts +11 -0
  147. package/dist/ui/components/nature/weather/SnowflakeFalling.svelte +162 -0
  148. package/dist/ui/components/nature/weather/SnowflakeFalling.svelte.d.ts +23 -0
  149. package/dist/ui/components/nature/weather/index.d.ts +3 -0
  150. package/dist/ui/components/nature/weather/index.js +4 -0
  151. package/dist/ui/components/ui/GlassLogo.svelte +422 -0
  152. package/dist/ui/components/ui/GlassLogo.svelte.d.ts +23 -0
  153. package/dist/ui/components/ui/GlassNavbar.svelte +120 -0
  154. package/dist/ui/components/ui/GlassNavbar.svelte.d.ts +42 -0
  155. package/dist/ui/components/ui/Logo.svelte +47 -52
  156. package/dist/ui/components/ui/Logo.svelte.d.ts +4 -3
  157. package/dist/ui/components/ui/index.d.ts +2 -0
  158. package/dist/ui/components/ui/index.js +2 -0
  159. package/dist/ui/styles/grove.css +15 -1
  160. package/package.json +11 -1
@@ -0,0 +1,170 @@
1
+ <!--
2
+ Grove — A place to Be
3
+ Copyright (c) 2025 Autumn Brown
4
+ Licensed under AGPL-3.0
5
+ -->
6
+ <script lang="ts">
7
+ import type { Season } from '../palette';
8
+ import LeafFalling from './LeafFalling.svelte';
9
+
10
+ type TreeType = 'logo' | 'pine' | 'aspen' | 'birch' | 'cherry';
11
+ type LeafVariant = 'simple' | 'maple' | 'cherry' | 'aspen' | 'pine';
12
+
13
+ // Animation constants (defaults)
14
+ const DEFAULT_LEAF_OPACITY = { min: 0.4, max: 0.75 } as const;
15
+ const DEFAULT_FALL_DURATION = { min: 8, max: 14 } as const;
16
+ const DEFAULT_FALL_DISTANCE = { min: 12, max: 20 } as const;
17
+ const DEFAULT_DRIFT_RANGE = 60; // -30 to +30
18
+ const DEFAULT_SPAWN_DELAY_MAX = 15;
19
+
20
+ interface Tree {
21
+ id: number;
22
+ x: number;
23
+ y: number;
24
+ size: number;
25
+ treeType: TreeType;
26
+ zIndex?: number;
27
+ }
28
+
29
+ interface FallingLeaf {
30
+ id: number;
31
+ x: number;
32
+ y: number;
33
+ size: number;
34
+ variant: LeafVariant;
35
+ duration: number;
36
+ delay: number;
37
+ drift: number;
38
+ opacity: number;
39
+ fallDistance: number;
40
+ }
41
+
42
+ interface Props {
43
+ trees: Tree[];
44
+ season?: Season;
45
+ /** Minimum leaves per tree */
46
+ minLeavesPerTree?: number;
47
+ /** Maximum leaves per tree */
48
+ maxLeavesPerTree?: number;
49
+ /** Base z-index for the leaf layer (should be below trees) */
50
+ zIndex?: number;
51
+ /** Override fall distance range (in vh units) - useful for tall sections */
52
+ fallDistance?: { min: number; max: number };
53
+ /** Override fall duration range (in seconds) */
54
+ fallDuration?: { min: number; max: number };
55
+ /** Maximum spawn delay (in seconds) */
56
+ spawnDelayMax?: number;
57
+ }
58
+
59
+ let {
60
+ trees,
61
+ season = 'summer',
62
+ minLeavesPerTree = 2,
63
+ maxLeavesPerTree = 5,
64
+ zIndex = -1,
65
+ fallDistance = DEFAULT_FALL_DISTANCE,
66
+ fallDuration = DEFAULT_FALL_DURATION,
67
+ spawnDelayMax = DEFAULT_SPAWN_DELAY_MAX
68
+ }: Props = $props();
69
+
70
+ // Deterministic hash for pseudo-random distribution (avoids visible patterns)
71
+ function hashSeed(seed: number): number {
72
+ return Math.abs(Math.sin(seed * 12.9898) * 43758.5453);
73
+ }
74
+
75
+ // Map tree types to appropriate leaf variants (deterministic based on leaf id)
76
+ function getLeafVariant(treeType: TreeType, leafId: number): LeafVariant {
77
+ switch (treeType) {
78
+ case 'cherry':
79
+ return 'cherry';
80
+ case 'aspen':
81
+ case 'birch':
82
+ return 'aspen';
83
+ case 'pine':
84
+ return 'pine';
85
+ case 'logo':
86
+ default:
87
+ // Logo and default get a mix of simple and maple (deterministic with natural distribution)
88
+ return Math.floor(hashSeed(leafId)) % 2 === 0 ? 'simple' : 'maple';
89
+ }
90
+ }
91
+
92
+ // Generate falling leaves based on tree positions
93
+ function generateLeaves(treesData: Tree[]): FallingLeaf[] {
94
+ const leaves: FallingLeaf[] = [];
95
+ let leafId = 0;
96
+
97
+ for (const tree of treesData) {
98
+ // Random number of leaves per tree within the configured range
99
+ const baseLeafCount = minLeavesPerTree + Math.floor(Math.random() * (maxLeavesPerTree - minLeavesPerTree + 1));
100
+
101
+ // More leaves for bigger/closer trees, fewer for distant ones
102
+ const treeDepth = tree.zIndex ?? 1;
103
+ const depthMultiplier = 0.5 + (treeDepth / 5) * 0.8; // 0.5x for far trees, up to 1.3x for close ones
104
+ const leafCount = Math.ceil(baseLeafCount * depthMultiplier);
105
+
106
+ // Scale leaf size based on tree size (bigger trees = bigger leaves for perspective)
107
+ const treeSizeFactor = tree.size / 100; // Normalize around 100px tree size
108
+ const baseLeafSize = 6 + treeSizeFactor * 8; // 6-14px base depending on tree size
109
+ const leafSizeVariation = 6 + treeSizeFactor * 4; // Additional random variation
110
+
111
+ for (let i = 0; i < leafCount; i++) {
112
+ // Spawn leaves at or above tree canopy for better falling motion
113
+ // Leaves will animate downward from their spawn point
114
+ const xOffset = (Math.random() - 0.5) * (tree.size / 8); // Horizontal spread based on tree size
115
+ // Vertical variation: spawn leaves slightly above to at tree position
116
+ const yOffset = -2 - Math.random() * 3; // -2% to -5% above tree
117
+
118
+ const currentLeafId = leafId++;
119
+ leaves.push({
120
+ id: currentLeafId,
121
+ x: tree.x + xOffset,
122
+ y: Math.max(0, tree.y + yOffset), // Clamp to not go above viewport
123
+ size: baseLeafSize + Math.random() * leafSizeVariation,
124
+ variant: getLeafVariant(tree.treeType, currentLeafId),
125
+ duration: fallDuration.min + Math.random() * (fallDuration.max - fallDuration.min),
126
+ delay: Math.random() * spawnDelayMax,
127
+ drift: (Math.random() - 0.5) * DEFAULT_DRIFT_RANGE,
128
+ opacity: DEFAULT_LEAF_OPACITY.min + Math.random() * (DEFAULT_LEAF_OPACITY.max - DEFAULT_LEAF_OPACITY.min),
129
+ fallDistance: fallDistance.min + Math.random() * (fallDistance.max - fallDistance.min)
130
+ });
131
+ }
132
+ }
133
+
134
+ return leaves;
135
+ }
136
+
137
+ // Reactive leaves - regenerates when trees or season changes
138
+ let fallingLeaves = $derived(generateLeaves(trees));
139
+ </script>
140
+
141
+ <!-- Falling leaves layer - positioned behind trees -->
142
+ <div
143
+ class="absolute inset-0 pointer-events-none overflow-hidden"
144
+ style="z-index: {zIndex};"
145
+ >
146
+ {#each fallingLeaves as leaf (leaf.id)}
147
+ <div
148
+ class="absolute"
149
+ style="
150
+ left: {leaf.x}%;
151
+ top: {leaf.y}%;
152
+ opacity: {leaf.opacity};
153
+ width: {leaf.size}px;
154
+ height: {leaf.size}px;
155
+ "
156
+ >
157
+ <LeafFalling
158
+ class="w-full h-full"
159
+ variant={leaf.variant}
160
+ {season}
161
+ duration={leaf.duration}
162
+ delay={leaf.delay}
163
+ drift={leaf.drift}
164
+ fallDistance={leaf.fallDistance}
165
+ seed={leaf.id}
166
+ animate={true}
167
+ />
168
+ </div>
169
+ {/each}
170
+ </div>
@@ -0,0 +1,35 @@
1
+ import type { Season } from '../palette';
2
+ type TreeType = 'logo' | 'pine' | 'aspen' | 'birch' | 'cherry';
3
+ interface Tree {
4
+ id: number;
5
+ x: number;
6
+ y: number;
7
+ size: number;
8
+ treeType: TreeType;
9
+ zIndex?: number;
10
+ }
11
+ interface Props {
12
+ trees: Tree[];
13
+ season?: Season;
14
+ /** Minimum leaves per tree */
15
+ minLeavesPerTree?: number;
16
+ /** Maximum leaves per tree */
17
+ maxLeavesPerTree?: number;
18
+ /** Base z-index for the leaf layer (should be below trees) */
19
+ zIndex?: number;
20
+ /** Override fall distance range (in vh units) - useful for tall sections */
21
+ fallDistance?: {
22
+ min: number;
23
+ max: number;
24
+ };
25
+ /** Override fall duration range (in seconds) */
26
+ fallDuration?: {
27
+ min: number;
28
+ max: number;
29
+ };
30
+ /** Maximum spawn delay (in seconds) */
31
+ spawnDelayMax?: number;
32
+ }
33
+ declare const FallingLeavesLayer: import("svelte").Component<Props, {}, "">;
34
+ type FallingLeavesLayer = ReturnType<typeof FallingLeavesLayer>;
35
+ export default FallingLeavesLayer;
@@ -0,0 +1,174 @@
1
+ <!--
2
+ Grove — A place to Be
3
+ Copyright (c) 2025 Autumn Brown
4
+ Licensed under AGPL-3.0
5
+ -->
6
+ <script lang="ts">
7
+ import { browser } from '$app/environment';
8
+ import PetalFalling from './PetalFalling.svelte';
9
+
10
+ type PetalVariant = 'round' | 'pointed' | 'heart' | 'curled' | 'tiny';
11
+
12
+ // Check for reduced motion preference
13
+ const prefersReducedMotion = browser
14
+ ? window.matchMedia('(prefers-reduced-motion: reduce)').matches
15
+ : false;
16
+
17
+ interface Props {
18
+ /** Total number of petals */
19
+ count?: number;
20
+ /** Base z-index for the petal layer */
21
+ zIndex?: number;
22
+ /** Enable petal animation */
23
+ enabled?: boolean;
24
+ /** Opacity range for petals (depth affects final value) */
25
+ opacity?: { min: number; max: number };
26
+ /** Fall duration range in seconds (slower = more dreamy) */
27
+ fallDuration?: { min: number; max: number };
28
+ /** Horizontal drift range in pixels (petals flutter more than snow) */
29
+ driftRange?: number;
30
+ /** Maximum spawn delay in seconds */
31
+ spawnDelay?: number;
32
+ }
33
+
34
+ let {
35
+ count = 80,
36
+ zIndex = 50,
37
+ enabled = true,
38
+ opacity = { min: 0.5, max: 0.9 },
39
+ fallDuration = { min: 12, max: 20 },
40
+ driftRange = 80,
41
+ spawnDelay = 15
42
+ }: Props = $props();
43
+
44
+ // Petals fall gently and drift more than snow
45
+ // Increased distance to ensure they traverse the full viewport
46
+ const FALL_DISTANCE = { min: 120, max: 150 } as const;
47
+
48
+ // Reduce petal count for reduced motion
49
+ const actualCount = $derived(prefersReducedMotion ? Math.floor(count / 4) : count);
50
+
51
+ // Variant distribution - more round and heart shapes for cherry blossoms
52
+ const petalVariants: PetalVariant[] = ['round', 'round', 'pointed', 'heart', 'curled', 'tiny'];
53
+
54
+ interface Petal {
55
+ id: number;
56
+ x: number;
57
+ y: number;
58
+ size: number;
59
+ variant: PetalVariant;
60
+ duration: number;
61
+ delay: number;
62
+ drift: number;
63
+ opacity: number;
64
+ fallDistance: number;
65
+ }
66
+
67
+ // Deterministic hash for natural distribution
68
+ function hashRandom(seed: number): number {
69
+ const hash = Math.abs(Math.sin(seed * 12.9898) * 43758.5453);
70
+ return hash - Math.floor(hash);
71
+ }
72
+
73
+ // Generate petals across the viewport
74
+ function generatePetals(): Petal[] {
75
+ const petals: Petal[] = [];
76
+
77
+ for (let i = 0; i < actualCount; i++) {
78
+ // Prime number multipliers ensure uncorrelated random distributions
79
+ // across different properties (avoids visible patterns in petal placement)
80
+ const xRand = hashRandom(i * 7);
81
+ const yRand = hashRandom(i * 11);
82
+ const depthRand = hashRandom(i * 13);
83
+ const durationRand = hashRandom(i * 17);
84
+ const delayRand = hashRandom(i * 19);
85
+ const driftRand = hashRandom(i * 23);
86
+ const distanceRand = hashRandom(i * 29);
87
+ const variantRand = hashRandom(i * 31);
88
+
89
+ // Distribute across full width
90
+ const x = (i / actualCount) * 100 + (xRand - 0.5) * 15;
91
+
92
+ // Start positions: Mix above viewport and within for continuous rain effect
93
+ // 50% start above for "raining from sky", 50% within for immediate visibility
94
+ const y = yRand < 0.5
95
+ ? -5 - yRand * 15 // Above viewport: -5% to -20%
96
+ : yRand * 40; // Within viewport: 0% to 40%
97
+
98
+ // Depth-based sizing:
99
+ // Far petals are smaller and simpler
100
+ // Close petals are larger and more detailed
101
+ const depthFactor = depthRand;
102
+ const size = 10 + depthFactor * 18; // 10-28px - petals are delicate
103
+
104
+ // Variant based on depth
105
+ let variant: PetalVariant;
106
+ if (depthFactor < 0.25) {
107
+ variant = 'tiny';
108
+ } else {
109
+ const variantIndex = Math.floor(variantRand * (petalVariants.length - 1));
110
+ variant = petalVariants[variantIndex];
111
+ }
112
+
113
+ // Since all petals now start above viewport, stagger delays for continuous rain
114
+ // Shorter delays create more immediate visual interest
115
+ const actualDelay = delayRand * spawnDelay * 0.6; // Reduce overall delay spread
116
+
117
+ // Petals drift more erratically than snow - flutter in the breeze
118
+ // Use sine wave for more organic drift pattern
119
+ const baseDrift = (driftRand - 0.5) * driftRange;
120
+ const driftVariation = Math.sin(i * 0.7) * 20;
121
+
122
+ petals.push({
123
+ id: i,
124
+ x,
125
+ y,
126
+ size,
127
+ variant,
128
+ duration: fallDuration.min + durationRand * (fallDuration.max - fallDuration.min),
129
+ delay: actualDelay,
130
+ drift: baseDrift + driftVariation,
131
+ opacity: opacity.min + depthFactor * (opacity.max - opacity.min),
132
+ fallDistance: FALL_DISTANCE.min + distanceRand * (FALL_DISTANCE.max - FALL_DISTANCE.min)
133
+ });
134
+ }
135
+
136
+ return petals;
137
+ }
138
+
139
+ // Generate petals once
140
+ const petals = $derived(generatePetals());
141
+ </script>
142
+
143
+ {#if enabled}
144
+ <!-- Falling petals layer - cherry blossoms drifting on spring breeze -->
145
+ <div
146
+ class="absolute inset-0 pointer-events-none"
147
+ style="z-index: {zIndex};"
148
+ aria-hidden="true"
149
+ >
150
+ {#each petals as petal (petal.id)}
151
+ <div
152
+ class="absolute"
153
+ style="
154
+ left: {petal.x}%;
155
+ top: {petal.y}%;
156
+ width: {petal.size}px;
157
+ height: {petal.size}px;
158
+ "
159
+ >
160
+ <PetalFalling
161
+ class="w-full h-full"
162
+ variant={petal.variant}
163
+ duration={petal.duration}
164
+ delay={petal.delay}
165
+ drift={petal.drift}
166
+ fallDistance={petal.fallDistance}
167
+ opacity={petal.opacity}
168
+ seed={petal.id}
169
+ animate={true}
170
+ />
171
+ </div>
172
+ {/each}
173
+ </div>
174
+ {/if}
@@ -0,0 +1,25 @@
1
+ interface Props {
2
+ /** Total number of petals */
3
+ count?: number;
4
+ /** Base z-index for the petal layer */
5
+ zIndex?: number;
6
+ /** Enable petal animation */
7
+ enabled?: boolean;
8
+ /** Opacity range for petals (depth affects final value) */
9
+ opacity?: {
10
+ min: number;
11
+ max: number;
12
+ };
13
+ /** Fall duration range in seconds (slower = more dreamy) */
14
+ fallDuration?: {
15
+ min: number;
16
+ max: number;
17
+ };
18
+ /** Horizontal drift range in pixels (petals flutter more than snow) */
19
+ driftRange?: number;
20
+ /** Maximum spawn delay in seconds */
21
+ spawnDelay?: number;
22
+ }
23
+ declare const FallingPetalsLayer: import("svelte").Component<Props, {}, "">;
24
+ type FallingPetalsLayer = ReturnType<typeof FallingPetalsLayer>;
25
+ export default FallingPetalsLayer;
@@ -0,0 +1,77 @@
1
+ <!--
2
+ Grove — A place to Be
3
+ Copyright (c) 2025 Autumn Brown
4
+ Licensed under AGPL-3.0
5
+ -->
6
+ <script lang="ts">
7
+ import type { Season } from '../palette';
8
+ import { greens, autumn } from '../palette';
9
+
10
+ interface Props {
11
+ class?: string;
12
+ color?: string;
13
+ season?: Season;
14
+ variant?: 'oak' | 'maple' | 'simple' | 'aspen';
15
+ }
16
+
17
+ let {
18
+ class: className = 'w-4 h-4',
19
+ color,
20
+ season = 'summer',
21
+ variant = 'simple'
22
+ }: Props = $props();
23
+
24
+ // Leaves change color in autumn
25
+ const defaultColor = $derived(season === 'autumn' ? autumn.amber : greens.grove);
26
+ const leafColor = $derived(color ?? defaultColor);
27
+ const veinColor = $derived(season === 'autumn' ? autumn.rust : greens.deepGreen);
28
+ </script>
29
+
30
+ <!-- Leaf - various shapes -->
31
+ <svg class={className} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 40 50">
32
+ {#if variant === 'simple'}
33
+ <!-- Simple oval leaf -->
34
+ <ellipse fill={leafColor} cx="20" cy="22" rx="15" ry="20" />
35
+ <path fill="none" stroke={veinColor} stroke-width="1" d="M20 5 L20 42" opacity="0.4" />
36
+ <path fill="none" stroke={veinColor} stroke-width="0.5" d="M20 15 Q10 18 8 22" opacity="0.3" />
37
+ <path fill="none" stroke={veinColor} stroke-width="0.5" d="M20 15 Q30 18 32 22" opacity="0.3" />
38
+ <path fill="none" stroke={veinColor} stroke-width="0.5" d="M20 25 Q12 28 8 32" opacity="0.3" />
39
+ <path fill="none" stroke={veinColor} stroke-width="0.5" d="M20 25 Q28 28 32 32" opacity="0.3" />
40
+ <!-- Stem -->
41
+ <path fill="none" stroke={veinColor} stroke-width="2" d="M20 42 L20 50" />
42
+ {:else if variant === 'maple'}
43
+ <!-- Maple leaf (iconic shape) -->
44
+ <path
45
+ fill={leafColor}
46
+ d="M20 2
47
+ L22 10 L30 5 L26 14 L38 12 L28 20 L38 25 L26 26 L32 38 L20 30 L8 38 L14 26 L2 25 L12 20 L2 12 L14 14 L10 5 L18 10 Z"
48
+ />
49
+ <path fill="none" stroke={veinColor} stroke-width="0.5" d="M20 10 L20 30" opacity="0.4" />
50
+ <path fill="none" stroke={veinColor} stroke-width="0.5" d="M20 18 L10 12" opacity="0.3" />
51
+ <path fill="none" stroke={veinColor} stroke-width="0.5" d="M20 18 L30 12" opacity="0.3" />
52
+ <!-- Stem -->
53
+ <path fill="none" stroke={veinColor} stroke-width="2" d="M20 30 L20 50" />
54
+ {:else if variant === 'oak'}
55
+ <!-- Oak leaf (lobed) -->
56
+ <path
57
+ fill={leafColor}
58
+ d="M20 3
59
+ Q25 5 28 10 Q32 8 34 12 Q30 14 32 18 Q36 18 36 23 Q32 24 33 28 Q36 30 34 35 Q30 34 28 38 Q25 42 20 42
60
+ Q15 42 12 38 Q10 34 6 35 Q4 30 7 28 Q8 24 4 23 Q4 18 8 18 Q10 14 6 12 Q8 8 12 10 Q15 5 20 3"
61
+ />
62
+ <path fill="none" stroke={veinColor} stroke-width="1" d="M20 5 L20 42" opacity="0.4" />
63
+ <!-- Stem -->
64
+ <path fill="none" stroke={veinColor} stroke-width="2" d="M20 42 L20 50" />
65
+ {:else if variant === 'aspen'}
66
+ <!-- Aspen leaf (round with pointed tip) -->
67
+ <path
68
+ fill={leafColor}
69
+ d="M20 3 Q35 15 35 28 Q35 42 20 42 Q5 42 5 28 Q5 15 20 3"
70
+ />
71
+ <path fill="none" stroke={veinColor} stroke-width="0.8" d="M20 5 L20 42" opacity="0.4" />
72
+ <path fill="none" stroke={veinColor} stroke-width="0.5" d="M20 15 Q12 20 8 28" opacity="0.3" />
73
+ <path fill="none" stroke={veinColor} stroke-width="0.5" d="M20 15 Q28 20 32 28" opacity="0.3" />
74
+ <!-- Stem -->
75
+ <path fill="none" stroke={veinColor} stroke-width="2" d="M20 42 L20 50" />
76
+ {/if}
77
+ </svg>
@@ -0,0 +1,10 @@
1
+ import type { Season } from '../palette';
2
+ interface Props {
3
+ class?: string;
4
+ color?: string;
5
+ season?: Season;
6
+ variant?: 'oak' | 'maple' | 'simple' | 'aspen';
7
+ }
8
+ declare const Leaf: import("svelte").Component<Props, {}, "">;
9
+ type Leaf = ReturnType<typeof Leaf>;
10
+ export default Leaf;
@@ -0,0 +1,186 @@
1
+ <!--
2
+ Grove — A place to Be
3
+ Copyright (c) 2025 Autumn Brown
4
+ Licensed under AGPL-3.0
5
+ -->
6
+ <script lang="ts">
7
+ import type { Season } from '../palette';
8
+ import { autumn, greens, pinks, autumnReds } from '../palette';
9
+
10
+ type LeafVariant = 'simple' | 'maple' | 'cherry' | 'aspen' | 'pine';
11
+
12
+ // Animation constants
13
+ // Starting offset ensures leaves begin above viewport for natural entrance
14
+ const SPAWN_OFFSET_PX = 50;
15
+
16
+ interface Props {
17
+ class?: string;
18
+ color?: string;
19
+ season?: Season;
20
+ animate?: boolean;
21
+ variant?: LeafVariant;
22
+ /** Animation duration in seconds */
23
+ duration?: number;
24
+ /** Animation delay in seconds */
25
+ delay?: number;
26
+ /** Horizontal drift amount (positive = right, negative = left) */
27
+ drift?: number;
28
+ /** Fall distance in vh units (how far the leaf travels) */
29
+ fallDistance?: number;
30
+ /** Seed for deterministic color selection */
31
+ seed?: number;
32
+ }
33
+
34
+ let {
35
+ class: className = 'w-4 h-4',
36
+ color,
37
+ season = 'autumn',
38
+ animate = true,
39
+ variant = 'simple',
40
+ duration = 5,
41
+ delay = 0,
42
+ drift = 30,
43
+ fallDistance = 40,
44
+ seed = 0
45
+ }: Props = $props();
46
+
47
+ // Color palettes for different leaf types
48
+ const autumnColors = [autumn.rust, autumn.amber, autumn.gold, autumn.pumpkin, autumn.ember];
49
+ const summerColors = [greens.grove, greens.meadow, greens.spring, greens.deepGreen];
50
+ const cherryAutumnColors = [autumnReds.crimson, autumnReds.scarlet, autumnReds.rose];
51
+ const cherrySpringColors = [pinks.pink, pinks.rose, pinks.blush, pinks.palePink];
52
+ const aspenAutumnColors = [autumn.gold, autumn.honey, autumn.straw, autumn.amber];
53
+
54
+ // Deterministic color selection using pseudo-random distribution
55
+ // Uses sine-based hash to avoid visible patterns from sequential IDs
56
+ function pickFromArray<T>(arr: T[]): T {
57
+ const hash = Math.abs(Math.sin(seed * 12.9898) * 43758.5453);
58
+ return arr[Math.floor(hash) % arr.length];
59
+ }
60
+
61
+ // Get default color based on variant and season (deterministic)
62
+ function getDefaultColor(): string {
63
+ if (variant === 'cherry') {
64
+ // Cherry trees: pink blossoms in spring, green in summer, red/orange in autumn
65
+ if (season === 'spring') {
66
+ return pickFromArray(cherrySpringColors);
67
+ } else if (season === 'autumn') {
68
+ return pickFromArray(cherryAutumnColors);
69
+ } else {
70
+ // Summer - cherry trees have regular green leaves
71
+ return pickFromArray(summerColors);
72
+ }
73
+ }
74
+ if (variant === 'aspen') {
75
+ const colors = season === 'autumn' ? aspenAutumnColors : summerColors;
76
+ return pickFromArray(colors);
77
+ }
78
+ if (variant === 'pine') {
79
+ // Pine stays green year-round (evergreen)
80
+ return pickFromArray(summerColors);
81
+ }
82
+ // Default (simple, maple)
83
+ const colors = season === 'autumn' ? autumnColors : summerColors;
84
+ return pickFromArray(colors);
85
+ }
86
+
87
+ const leafColor = $derived(color ?? getDefaultColor());
88
+ </script>
89
+
90
+ <!-- Falling leaf with spin/flutter animation -->
91
+ <svg
92
+ class="{className} {animate ? 'fall' : ''}"
93
+ xmlns="http://www.w3.org/2000/svg"
94
+ viewBox="0 0 30 35"
95
+ style="--fall-duration: {duration}s; --fall-delay: {delay}s; --fall-drift: {drift}px; --fall-distance: {fallDistance}vh; --spawn-offset: {SPAWN_OFFSET_PX}px;"
96
+ >
97
+ <g class={animate ? 'spin' : ''}>
98
+ {#if variant === 'simple'}
99
+ <!-- Simple oval leaf -->
100
+ <ellipse fill={leafColor} cx="15" cy="15" rx="12" ry="14" />
101
+ <path fill="none" stroke="rgba(0,0,0,0.2)" stroke-width="0.8" d="M15 3 L15 29" />
102
+ {:else if variant === 'maple'}
103
+ <!-- Maple shape -->
104
+ <path
105
+ fill={leafColor}
106
+ d="M15 2
107
+ L16 7 L22 4 L19 10 L28 9 L21 15 L28 18 L19 19 L24 28 L15 22 L6 28 L11 19 L2 18 L9 15 L2 9 L11 10 L8 4 L14 7 Z"
108
+ />
109
+ {:else if variant === 'cherry'}
110
+ <!-- Cherry blossom petal - soft rounded oval -->
111
+ <ellipse fill={leafColor} cx="15" cy="13" rx="10" ry="12" />
112
+ <!-- Subtle petal notch at tip -->
113
+ <path fill={leafColor} d="M15 2 Q12 6 15 8 Q18 6 15 2" />
114
+ {:else if variant === 'aspen'}
115
+ <!-- Aspen/Birch - rounded heart shape -->
116
+ <path
117
+ fill={leafColor}
118
+ d="M15 4 Q8 4 6 12 Q4 20 15 28 Q26 20 24 12 Q22 4 15 4"
119
+ />
120
+ <path fill="none" stroke="rgba(0,0,0,0.15)" stroke-width="0.6" d="M15 6 L15 26" />
121
+ {:else if variant === 'pine'}
122
+ <!-- Pine needle cluster - thin elongated shapes -->
123
+ <path fill={leafColor} d="M15 2 L16 28 L14 28 Z" />
124
+ <path fill={leafColor} d="M10 4 L16 26 L14 27 Z" opacity="0.8" />
125
+ <path fill={leafColor} d="M20 4 L14 26 L16 27 Z" opacity="0.8" />
126
+ {/if}
127
+
128
+ <!-- Stem (not for pine needles) -->
129
+ {#if variant !== 'pine'}
130
+ <path fill="none" stroke="rgba(0,0,0,0.3)" stroke-width="1.5" d="M15 28 L15 35" />
131
+ {/if}
132
+ </g>
133
+ </svg>
134
+
135
+ <style>
136
+ @keyframes fall {
137
+ 0% {
138
+ /* Start transparent to avoid "stuck in place" look for leaves with no delay */
139
+ transform: translateY(0) translateX(0);
140
+ opacity: 0;
141
+ }
142
+ 5% {
143
+ /* Fade in quickly as falling begins */
144
+ opacity: 0.5;
145
+ }
146
+ 10% {
147
+ opacity: 0.85;
148
+ }
149
+ 70% {
150
+ opacity: 0.7;
151
+ }
152
+ 90% {
153
+ opacity: 0.3;
154
+ }
155
+ 100% {
156
+ transform: translateY(var(--fall-distance, 40vh)) translateX(var(--fall-drift, 30px));
157
+ opacity: 0;
158
+ }
159
+ }
160
+
161
+ @keyframes spin {
162
+ 0% { transform: rotate(0deg) rotateY(0deg); }
163
+ 25% { transform: rotate(20deg) rotateY(90deg); }
164
+ 50% { transform: rotate(-15deg) rotateY(180deg); }
165
+ 75% { transform: rotate(25deg) rotateY(270deg); }
166
+ 100% { transform: rotate(0deg) rotateY(360deg); }
167
+ }
168
+
169
+ .fall {
170
+ animation: fall var(--fall-duration, 5s) ease-in-out infinite;
171
+ animation-delay: var(--fall-delay, 0s);
172
+ }
173
+
174
+ .spin {
175
+ transform-origin: center center;
176
+ animation: spin calc(var(--fall-duration, 5s) * 0.6) ease-in-out infinite;
177
+ }
178
+
179
+ /* Respect user's motion preferences */
180
+ @media (prefers-reduced-motion: reduce) {
181
+ .fall,
182
+ .spin {
183
+ animation: none;
184
+ }
185
+ }
186
+ </style>