@autumnsgrove/groveengine 0.6.5 → 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 (183) hide show
  1. package/LICENSE +378 -0
  2. package/dist/auth/index.d.ts +1 -2
  3. package/dist/auth/index.js +8 -4
  4. package/dist/auth/session.d.ts +12 -31
  5. package/dist/auth/session.js +5 -103
  6. package/dist/components/custom/ContentWithGutter.svelte +22 -25
  7. package/dist/ui/components/content/RoadmapPreview.svelte +91 -0
  8. package/dist/ui/components/content/RoadmapPreview.svelte.d.ts +36 -0
  9. package/dist/ui/components/content/index.d.ts +1 -0
  10. package/dist/ui/components/content/index.js +1 -0
  11. package/dist/ui/components/nature/Logo.svelte +224 -0
  12. package/dist/ui/components/nature/Logo.svelte.d.ts +14 -0
  13. package/dist/ui/components/nature/botanical/Acorn.svelte +48 -0
  14. package/dist/ui/components/nature/botanical/Acorn.svelte.d.ts +8 -0
  15. package/dist/ui/components/nature/botanical/Berry.svelte +67 -0
  16. package/dist/ui/components/nature/botanical/Berry.svelte.d.ts +8 -0
  17. package/dist/ui/components/nature/botanical/DandelionPuff.svelte +98 -0
  18. package/dist/ui/components/nature/botanical/DandelionPuff.svelte.d.ts +8 -0
  19. package/dist/ui/components/nature/botanical/FallingLeavesLayer.svelte +170 -0
  20. package/dist/ui/components/nature/botanical/FallingLeavesLayer.svelte.d.ts +35 -0
  21. package/dist/ui/components/nature/botanical/FallingPetalsLayer.svelte +174 -0
  22. package/dist/ui/components/nature/botanical/FallingPetalsLayer.svelte.d.ts +25 -0
  23. package/dist/ui/components/nature/botanical/Leaf.svelte +77 -0
  24. package/dist/ui/components/nature/botanical/Leaf.svelte.d.ts +10 -0
  25. package/dist/ui/components/nature/botanical/LeafFalling.svelte +186 -0
  26. package/dist/ui/components/nature/botanical/LeafFalling.svelte.d.ts +22 -0
  27. package/dist/ui/components/nature/botanical/PetalFalling.svelte +266 -0
  28. package/dist/ui/components/nature/botanical/PetalFalling.svelte.d.ts +25 -0
  29. package/dist/ui/components/nature/botanical/PineCone.svelte +61 -0
  30. package/dist/ui/components/nature/botanical/PineCone.svelte.d.ts +7 -0
  31. package/dist/ui/components/nature/botanical/Vine.svelte +102 -0
  32. package/dist/ui/components/nature/botanical/Vine.svelte.d.ts +11 -0
  33. package/dist/ui/components/nature/botanical/index.d.ts +10 -0
  34. package/dist/ui/components/nature/botanical/index.js +11 -0
  35. package/dist/ui/components/nature/creatures/Bee.svelte +78 -0
  36. package/dist/ui/components/nature/creatures/Bee.svelte.d.ts +9 -0
  37. package/dist/ui/components/nature/creatures/Bird.svelte +94 -0
  38. package/dist/ui/components/nature/creatures/Bird.svelte.d.ts +11 -0
  39. package/dist/ui/components/nature/creatures/BirdFlying.svelte +83 -0
  40. package/dist/ui/components/nature/creatures/BirdFlying.svelte.d.ts +9 -0
  41. package/dist/ui/components/nature/creatures/Bluebird.svelte +95 -0
  42. package/dist/ui/components/nature/creatures/Bluebird.svelte.d.ts +12 -0
  43. package/dist/ui/components/nature/creatures/Butterfly.svelte +87 -0
  44. package/dist/ui/components/nature/creatures/Butterfly.svelte.d.ts +9 -0
  45. package/dist/ui/components/nature/creatures/Cardinal.svelte +95 -0
  46. package/dist/ui/components/nature/creatures/Cardinal.svelte.d.ts +12 -0
  47. package/dist/ui/components/nature/creatures/Chickadee.svelte +97 -0
  48. package/dist/ui/components/nature/creatures/Chickadee.svelte.d.ts +12 -0
  49. package/dist/ui/components/nature/creatures/Deer.svelte +95 -0
  50. package/dist/ui/components/nature/creatures/Deer.svelte.d.ts +9 -0
  51. package/dist/ui/components/nature/creatures/Firefly.svelte +111 -0
  52. package/dist/ui/components/nature/creatures/Firefly.svelte.d.ts +10 -0
  53. package/dist/ui/components/nature/creatures/Owl.svelte +91 -0
  54. package/dist/ui/components/nature/creatures/Owl.svelte.d.ts +9 -0
  55. package/dist/ui/components/nature/creatures/Rabbit.svelte +90 -0
  56. package/dist/ui/components/nature/creatures/Rabbit.svelte.d.ts +9 -0
  57. package/dist/ui/components/nature/creatures/Robin.svelte +98 -0
  58. package/dist/ui/components/nature/creatures/Robin.svelte.d.ts +12 -0
  59. package/dist/ui/components/nature/creatures/Squirrel.svelte +97 -0
  60. package/dist/ui/components/nature/creatures/Squirrel.svelte.d.ts +9 -0
  61. package/dist/ui/components/nature/creatures/index.d.ts +13 -0
  62. package/dist/ui/components/nature/creatures/index.js +14 -0
  63. package/dist/ui/components/nature/ground/Bush.svelte +57 -0
  64. package/dist/ui/components/nature/ground/Bush.svelte.d.ts +10 -0
  65. package/dist/ui/components/nature/ground/Crocus.svelte +83 -0
  66. package/dist/ui/components/nature/ground/Crocus.svelte.d.ts +12 -0
  67. package/dist/ui/components/nature/ground/Daffodil.svelte +75 -0
  68. package/dist/ui/components/nature/ground/Daffodil.svelte.d.ts +11 -0
  69. package/dist/ui/components/nature/ground/Fern.svelte +72 -0
  70. package/dist/ui/components/nature/ground/Fern.svelte.d.ts +10 -0
  71. package/dist/ui/components/nature/ground/FlowerWild.svelte +60 -0
  72. package/dist/ui/components/nature/ground/FlowerWild.svelte.d.ts +10 -0
  73. package/dist/ui/components/nature/ground/GrassTuft.svelte +49 -0
  74. package/dist/ui/components/nature/ground/GrassTuft.svelte.d.ts +10 -0
  75. package/dist/ui/components/nature/ground/Log.svelte +42 -0
  76. package/dist/ui/components/nature/ground/Log.svelte.d.ts +7 -0
  77. package/dist/ui/components/nature/ground/Mushroom.svelte +48 -0
  78. package/dist/ui/components/nature/ground/Mushroom.svelte.d.ts +9 -0
  79. package/dist/ui/components/nature/ground/MushroomCluster.svelte +41 -0
  80. package/dist/ui/components/nature/ground/MushroomCluster.svelte.d.ts +8 -0
  81. package/dist/ui/components/nature/ground/Rock.svelte +59 -0
  82. package/dist/ui/components/nature/ground/Rock.svelte.d.ts +8 -0
  83. package/dist/ui/components/nature/ground/Stump.svelte +44 -0
  84. package/dist/ui/components/nature/ground/Stump.svelte.d.ts +8 -0
  85. package/dist/ui/components/nature/ground/Tulip.svelte +79 -0
  86. package/dist/ui/components/nature/ground/Tulip.svelte.d.ts +11 -0
  87. package/dist/ui/components/nature/ground/index.d.ts +12 -0
  88. package/dist/ui/components/nature/ground/index.js +13 -0
  89. package/dist/ui/components/nature/index.d.ts +28 -0
  90. package/dist/ui/components/nature/index.js +38 -0
  91. package/dist/ui/components/nature/palette.d.ts +491 -0
  92. package/dist/ui/components/nature/palette.js +384 -0
  93. package/dist/ui/components/nature/sky/Cloud.svelte +122 -0
  94. package/dist/ui/components/nature/sky/Cloud.svelte.d.ts +11 -0
  95. package/dist/ui/components/nature/sky/CloudWispy.svelte +79 -0
  96. package/dist/ui/components/nature/sky/CloudWispy.svelte.d.ts +9 -0
  97. package/dist/ui/components/nature/sky/Moon.svelte +60 -0
  98. package/dist/ui/components/nature/sky/Moon.svelte.d.ts +9 -0
  99. package/dist/ui/components/nature/sky/Rainbow.svelte +101 -0
  100. package/dist/ui/components/nature/sky/Rainbow.svelte.d.ts +8 -0
  101. package/dist/ui/components/nature/sky/Star.svelte +84 -0
  102. package/dist/ui/components/nature/sky/Star.svelte.d.ts +10 -0
  103. package/dist/ui/components/nature/sky/StarCluster.svelte +85 -0
  104. package/dist/ui/components/nature/sky/StarCluster.svelte.d.ts +9 -0
  105. package/dist/ui/components/nature/sky/StarShooting.svelte +90 -0
  106. package/dist/ui/components/nature/sky/StarShooting.svelte.d.ts +9 -0
  107. package/dist/ui/components/nature/sky/Sun.svelte +70 -0
  108. package/dist/ui/components/nature/sky/Sun.svelte.d.ts +9 -0
  109. package/dist/ui/components/nature/sky/index.d.ts +8 -0
  110. package/dist/ui/components/nature/sky/index.js +9 -0
  111. package/dist/ui/components/nature/structural/Birdhouse.svelte +53 -0
  112. package/dist/ui/components/nature/structural/Birdhouse.svelte.d.ts +8 -0
  113. package/dist/ui/components/nature/structural/Bridge.svelte +65 -0
  114. package/dist/ui/components/nature/structural/Bridge.svelte.d.ts +7 -0
  115. package/dist/ui/components/nature/structural/FencePost.svelte +54 -0
  116. package/dist/ui/components/nature/structural/FencePost.svelte.d.ts +8 -0
  117. package/dist/ui/components/nature/structural/GardenGate.svelte +70 -0
  118. package/dist/ui/components/nature/structural/GardenGate.svelte.d.ts +8 -0
  119. package/dist/ui/components/nature/structural/Lantern.svelte +113 -0
  120. package/dist/ui/components/nature/structural/Lantern.svelte.d.ts +10 -0
  121. package/dist/ui/components/nature/structural/Lattice.svelte +89 -0
  122. package/dist/ui/components/nature/structural/Lattice.svelte.d.ts +8 -0
  123. package/dist/ui/components/nature/structural/LatticeWithVine.svelte +89 -0
  124. package/dist/ui/components/nature/structural/LatticeWithVine.svelte.d.ts +11 -0
  125. package/dist/ui/components/nature/structural/StonePath.svelte +48 -0
  126. package/dist/ui/components/nature/structural/StonePath.svelte.d.ts +7 -0
  127. package/dist/ui/components/nature/structural/index.d.ts +8 -0
  128. package/dist/ui/components/nature/structural/index.js +9 -0
  129. package/dist/ui/components/nature/trees/TreeAspen.svelte +163 -0
  130. package/dist/ui/components/nature/trees/TreeAspen.svelte.d.ts +11 -0
  131. package/dist/ui/components/nature/trees/TreeBirch.svelte +186 -0
  132. package/dist/ui/components/nature/trees/TreeBirch.svelte.d.ts +11 -0
  133. package/dist/ui/components/nature/trees/TreeCherry.svelte +108 -0
  134. package/dist/ui/components/nature/trees/TreeCherry.svelte.d.ts +11 -0
  135. package/dist/ui/components/nature/trees/TreePine.svelte +79 -0
  136. package/dist/ui/components/nature/trees/TreePine.svelte.d.ts +11 -0
  137. package/dist/ui/components/nature/trees/index.d.ts +4 -0
  138. package/dist/ui/components/nature/trees/index.js +5 -0
  139. package/dist/ui/components/nature/water/LilyPad.svelte +99 -0
  140. package/dist/ui/components/nature/water/LilyPad.svelte.d.ts +10 -0
  141. package/dist/ui/components/nature/water/Pond.svelte +104 -0
  142. package/dist/ui/components/nature/water/Pond.svelte.d.ts +8 -0
  143. package/dist/ui/components/nature/water/Reeds.svelte +85 -0
  144. package/dist/ui/components/nature/water/Reeds.svelte.d.ts +11 -0
  145. package/dist/ui/components/nature/water/Stream.svelte +98 -0
  146. package/dist/ui/components/nature/water/Stream.svelte.d.ts +8 -0
  147. package/dist/ui/components/nature/water/index.d.ts +4 -0
  148. package/dist/ui/components/nature/water/index.js +5 -0
  149. package/dist/ui/components/nature/weather/SnowfallLayer.svelte +175 -0
  150. package/dist/ui/components/nature/weather/SnowfallLayer.svelte.d.ts +25 -0
  151. package/dist/ui/components/nature/weather/Snowflake.svelte +99 -0
  152. package/dist/ui/components/nature/weather/Snowflake.svelte.d.ts +11 -0
  153. package/dist/ui/components/nature/weather/SnowflakeFalling.svelte +162 -0
  154. package/dist/ui/components/nature/weather/SnowflakeFalling.svelte.d.ts +23 -0
  155. package/dist/ui/components/nature/weather/index.d.ts +3 -0
  156. package/dist/ui/components/nature/weather/index.js +4 -0
  157. package/dist/ui/components/ui/Glass.svelte +158 -0
  158. package/dist/ui/components/ui/Glass.svelte.d.ts +52 -0
  159. package/dist/ui/components/ui/GlassButton.svelte +157 -0
  160. package/dist/ui/components/ui/GlassButton.svelte.d.ts +39 -0
  161. package/dist/ui/components/ui/GlassCard.svelte +160 -0
  162. package/dist/ui/components/ui/GlassCard.svelte.d.ts +39 -0
  163. package/dist/ui/components/ui/GlassConfirmDialog.svelte +208 -0
  164. package/dist/ui/components/ui/GlassConfirmDialog.svelte.d.ts +52 -0
  165. package/dist/ui/components/ui/GlassLogo.svelte +422 -0
  166. package/dist/ui/components/ui/GlassLogo.svelte.d.ts +23 -0
  167. package/dist/ui/components/ui/GlassNavbar.svelte +120 -0
  168. package/dist/ui/components/ui/GlassNavbar.svelte.d.ts +42 -0
  169. package/dist/ui/components/ui/GlassOverlay.svelte +93 -0
  170. package/dist/ui/components/ui/GlassOverlay.svelte.d.ts +33 -0
  171. package/dist/ui/components/ui/Logo.svelte +47 -52
  172. package/dist/ui/components/ui/Logo.svelte.d.ts +4 -3
  173. package/dist/ui/components/ui/index.d.ts +7 -0
  174. package/dist/ui/components/ui/index.js +8 -0
  175. package/dist/ui/styles/grove.css +151 -1
  176. package/dist/utils/gutter.d.ts +2 -8
  177. package/dist/utils/markdown.d.ts +1 -0
  178. package/dist/utils/markdown.js +32 -11
  179. package/package.json +31 -22
  180. package/static/fonts/alagard.ttf +0 -0
  181. package/static/robots.txt +34 -1
  182. package/dist/auth/jwt.d.ts +0 -20
  183. package/dist/auth/jwt.js +0 -123
@@ -0,0 +1,99 @@
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 { accents, greens } from '../palette';
8
+
9
+ interface Props {
10
+ class?: string;
11
+ padColor?: string;
12
+ flowerColor?: string;
13
+ hasFlower?: boolean;
14
+ animate?: boolean;
15
+ }
16
+
17
+ let {
18
+ class: className = 'w-8 h-6',
19
+ padColor,
20
+ flowerColor,
21
+ hasFlower = false,
22
+ animate = true
23
+ }: Props = $props();
24
+
25
+ const pad = $derived(padColor ?? greens.grove);
26
+ const flower = $derived(flowerColor ?? accents.flower.white);
27
+ const padDark = $derived(greens.deepGreen);
28
+ </script>
29
+
30
+ <!-- Lily pad with optional flower -->
31
+ <svg class="{className} {animate ? 'float' : ''}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 60 45">
32
+ <!-- Pad shadow -->
33
+ <ellipse fill="rgba(0,0,0,0.1)" cx="30" cy="32" rx="25" ry="12" />
34
+
35
+ <!-- Lily pad -->
36
+ <path
37
+ fill={pad}
38
+ d="M30 20
39
+ Q55 20 55 32
40
+ Q55 42 30 42
41
+ Q5 42 5 32
42
+ Q5 20 30 20
43
+ L30 32 Z"
44
+ />
45
+
46
+ <!-- Pad notch (V-cut) -->
47
+ <path
48
+ fill="#7dd3fc"
49
+ d="M30 20 L25 32 L30 32 L35 32 L30 20"
50
+ opacity="0.5"
51
+ />
52
+
53
+ <!-- Pad veins -->
54
+ <path fill="none" stroke={padDark} stroke-width="0.5" d="M30 32 Q20 28 10 32" opacity="0.3" />
55
+ <path fill="none" stroke={padDark} stroke-width="0.5" d="M30 32 Q40 28 50 32" opacity="0.3" />
56
+ <path fill="none" stroke={padDark} stroke-width="0.5" d="M30 32 Q25 38 15 40" opacity="0.3" />
57
+ <path fill="none" stroke={padDark} stroke-width="0.5" d="M30 32 Q35 38 45 40" opacity="0.3" />
58
+
59
+ {#if hasFlower}
60
+ <!-- Water lily flower -->
61
+ <g class={animate ? 'bloom' : ''}>
62
+ <!-- Outer petals -->
63
+ <ellipse fill={flower} cx="30" cy="15" rx="4" ry="8" />
64
+ <ellipse fill={flower} cx="22" cy="18" rx="4" ry="7" transform="rotate(-30 22 18)" />
65
+ <ellipse fill={flower} cx="38" cy="18" rx="4" ry="7" transform="rotate(30 38 18)" />
66
+ <ellipse fill={flower} cx="20" cy="24" rx="3" ry="6" transform="rotate(-60 20 24)" />
67
+ <ellipse fill={flower} cx="40" cy="24" rx="3" ry="6" transform="rotate(60 40 24)" />
68
+
69
+ <!-- Inner petals -->
70
+ <ellipse fill={flower} cx="30" cy="17" rx="3" ry="5" />
71
+ <ellipse fill={flower} cx="26" cy="19" rx="2.5" ry="4" transform="rotate(-20 26 19)" />
72
+ <ellipse fill={flower} cx="34" cy="19" rx="2.5" ry="4" transform="rotate(20 34 19)" />
73
+
74
+ <!-- Center -->
75
+ <circle fill="#fbbf24" cx="30" cy="20" r="3" />
76
+ </g>
77
+ {/if}
78
+ </svg>
79
+
80
+ <style>
81
+ @keyframes float {
82
+ 0%, 100% { transform: translateY(0) rotate(0deg); }
83
+ 50% { transform: translateY(-2px) rotate(1deg); }
84
+ }
85
+
86
+ @keyframes bloom {
87
+ 0%, 100% { transform: scale(1); }
88
+ 50% { transform: scale(1.02); }
89
+ }
90
+
91
+ .float {
92
+ animation: float 4s ease-in-out infinite;
93
+ }
94
+
95
+ .bloom {
96
+ transform-origin: center bottom;
97
+ animation: bloom 5s ease-in-out infinite;
98
+ }
99
+ </style>
@@ -0,0 +1,10 @@
1
+ interface Props {
2
+ class?: string;
3
+ padColor?: string;
4
+ flowerColor?: string;
5
+ hasFlower?: boolean;
6
+ animate?: boolean;
7
+ }
8
+ declare const LilyPad: import("svelte").Component<Props, {}, "">;
9
+ type LilyPad = ReturnType<typeof LilyPad>;
10
+ export default LilyPad;
@@ -0,0 +1,104 @@
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 { accents } from '../palette';
8
+
9
+ interface Props {
10
+ class?: string;
11
+ color?: string;
12
+ animate?: boolean;
13
+ }
14
+
15
+ let {
16
+ class: className = 'w-20 h-12',
17
+ color,
18
+ animate = true
19
+ }: Props = $props();
20
+
21
+ const waterColor = $derived(color ?? accents.water.surface);
22
+ const deepColor = $derived(accents.water.deep);
23
+ </script>
24
+
25
+ <!-- Pond with subtle ripple effect -->
26
+ <svg class={className} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 60">
27
+ <defs>
28
+ <ellipse id="pond-shape" cx="60" cy="35" rx="55" ry="25" />
29
+ <clipPath id="pond-clip">
30
+ <use href="#pond-shape" />
31
+ </clipPath>
32
+ </defs>
33
+
34
+ <!-- Water body -->
35
+ <ellipse fill={deepColor} cx="60" cy="35" rx="55" ry="25" />
36
+ <ellipse fill={waterColor} cx="60" cy="32" rx="52" ry="22" opacity="0.7" />
37
+
38
+ <!-- Ripples -->
39
+ <g clip-path="url(#pond-clip)">
40
+ <ellipse
41
+ fill="none"
42
+ stroke="white"
43
+ stroke-width="1"
44
+ cx="40"
45
+ cy="30"
46
+ rx="8"
47
+ ry="4"
48
+ opacity="0.3"
49
+ class={animate ? 'ripple ripple-1' : ''}
50
+ />
51
+ <ellipse
52
+ fill="none"
53
+ stroke="white"
54
+ stroke-width="0.5"
55
+ cx="40"
56
+ cy="30"
57
+ rx="15"
58
+ ry="7"
59
+ opacity="0.2"
60
+ class={animate ? 'ripple ripple-2' : ''}
61
+ />
62
+ <ellipse
63
+ fill="none"
64
+ stroke="white"
65
+ stroke-width="1"
66
+ cx="75"
67
+ cy="38"
68
+ rx="6"
69
+ ry="3"
70
+ opacity="0.25"
71
+ class={animate ? 'ripple ripple-3' : ''}
72
+ />
73
+ </g>
74
+
75
+ <!-- Reflection highlight -->
76
+ <ellipse fill="white" cx="45" cy="28" rx="12" ry="5" opacity="0.15" />
77
+
78
+ <!-- Edge shadow -->
79
+ <ellipse
80
+ fill="none"
81
+ stroke="rgba(0,0,0,0.1)"
82
+ stroke-width="2"
83
+ cx="60"
84
+ cy="35"
85
+ rx="55"
86
+ ry="25"
87
+ />
88
+ </svg>
89
+
90
+ <style>
91
+ @keyframes ripple {
92
+ 0% { transform: scale(0.8); opacity: 0.4; }
93
+ 100% { transform: scale(1.5); opacity: 0; }
94
+ }
95
+
96
+ .ripple {
97
+ transform-origin: center center;
98
+ animation: ripple 3s ease-out infinite;
99
+ }
100
+
101
+ .ripple-1 { animation-delay: 0s; }
102
+ .ripple-2 { animation-delay: 0.5s; }
103
+ .ripple-3 { animation-delay: 1.5s; }
104
+ </style>
@@ -0,0 +1,8 @@
1
+ interface Props {
2
+ class?: string;
3
+ color?: string;
4
+ animate?: boolean;
5
+ }
6
+ declare const Pond: import("svelte").Component<Props, {}, "">;
7
+ type Pond = ReturnType<typeof Pond>;
8
+ export default Pond;
@@ -0,0 +1,85 @@
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, bark } from '../palette';
9
+
10
+ interface Props {
11
+ class?: string;
12
+ color?: string;
13
+ season?: Season;
14
+ animate?: boolean;
15
+ variant?: 'cattail' | 'grass';
16
+ }
17
+
18
+ let {
19
+ class: className = 'w-6 h-12',
20
+ color,
21
+ season = 'summer',
22
+ animate = true,
23
+ variant = 'cattail'
24
+ }: Props = $props();
25
+
26
+ // Reeds turn golden-brown in autumn
27
+ const defaultColor = $derived(
28
+ season === 'autumn' ? autumn.straw : greens.deepGreen
29
+ );
30
+ const reedColor = $derived(color ?? defaultColor);
31
+ const cattailColor = $derived(bark.bark);
32
+ </script>
33
+
34
+ <!-- Reeds/Cattails -->
35
+ <svg class={className} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 50 100">
36
+ {#if variant === 'cattail'}
37
+ <!-- Cattail stalks with heads -->
38
+ <g class={animate ? 'sway sway-1' : ''}>
39
+ <path fill="none" stroke={reedColor} stroke-width="2" d="M15 100 Q14 70 16 40 Q17 25 15 15" />
40
+ <ellipse fill={cattailColor} cx="15" cy="22" rx="4" ry="10" />
41
+ <path fill={reedColor} d="M15 12 L13 8 L15 10 L17 8 Z" />
42
+ </g>
43
+
44
+ <g class={animate ? 'sway sway-2' : ''}>
45
+ <path fill="none" stroke={reedColor} stroke-width="2" d="M30 100 Q32 65 30 35 Q28 18 30 5" />
46
+ <ellipse fill={cattailColor} cx="30" cy="15" rx="4" ry="12" />
47
+ <path fill={reedColor} d="M30 3 L28 0 L30 2 L32 0 Z" />
48
+ </g>
49
+
50
+ <g class={animate ? 'sway sway-3' : ''}>
51
+ <path fill="none" stroke={reedColor} stroke-width="1.5" d="M42 100 Q40 75 42 50 Q44 30 42 20" />
52
+ <ellipse fill={cattailColor} cx="42" cy="27" rx="3" ry="8" />
53
+ <path fill={reedColor} d="M42 19 L40 16 L42 18 L44 16 Z" />
54
+ </g>
55
+ {:else}
56
+ <!-- Simple grass reeds -->
57
+ <g class={animate ? 'sway sway-1' : ''}>
58
+ <path fill={reedColor} d="M10 100 L8 100 Q6 60 10 20 Q12 15 10 10 Q12 15 14 20 Q18 60 16 100 Z" />
59
+ </g>
60
+
61
+ <g class={animate ? 'sway sway-2' : ''}>
62
+ <path fill={reedColor} d="M25 100 L23 100 Q20 55 25 15 Q27 8 25 2 Q28 8 30 15 Q35 55 32 100 Z" />
63
+ </g>
64
+
65
+ <g class={animate ? 'sway sway-3' : ''}>
66
+ <path fill={reedColor} d="M40 100 L38 100 Q36 65 40 30 Q42 22 40 15 Q43 22 45 30 Q48 65 46 100 Z" />
67
+ </g>
68
+ {/if}
69
+ </svg>
70
+
71
+ <style>
72
+ @keyframes sway {
73
+ 0%, 100% { transform: rotate(0deg); }
74
+ 50% { transform: rotate(2deg); }
75
+ }
76
+
77
+ .sway {
78
+ transform-origin: center bottom;
79
+ animation: sway 3s ease-in-out infinite;
80
+ }
81
+
82
+ .sway-1 { animation-delay: 0s; }
83
+ .sway-2 { animation-delay: 0.4s; }
84
+ .sway-3 { animation-delay: 0.8s; }
85
+ </style>
@@ -0,0 +1,11 @@
1
+ import type { Season } from '../palette';
2
+ interface Props {
3
+ class?: string;
4
+ color?: string;
5
+ season?: Season;
6
+ animate?: boolean;
7
+ variant?: 'cattail' | 'grass';
8
+ }
9
+ declare const Reeds: import("svelte").Component<Props, {}, "">;
10
+ type Reeds = ReturnType<typeof Reeds>;
11
+ export default Reeds;
@@ -0,0 +1,98 @@
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 { accents } from '../palette';
8
+
9
+ interface Props {
10
+ class?: string;
11
+ color?: string;
12
+ animate?: boolean;
13
+ }
14
+
15
+ let {
16
+ class: className = 'w-24 h-8',
17
+ color,
18
+ animate = true
19
+ }: Props = $props();
20
+
21
+ const waterColor = $derived(color ?? accents.water.surface);
22
+ const deepColor = $derived(accents.water.deep);
23
+ </script>
24
+
25
+ <!-- Flowing stream -->
26
+ <svg class={className} xmlns="http://www.w3.org/2000/svg" viewBox="0 0 150 40">
27
+ <defs>
28
+ <linearGradient id="stream-gradient" x1="0%" y1="0%" x2="0%" y2="100%">
29
+ <stop offset="0%" stop-color={waterColor} />
30
+ <stop offset="100%" stop-color={deepColor} />
31
+ </linearGradient>
32
+ </defs>
33
+
34
+ <!-- Stream bed -->
35
+ <path
36
+ fill="url(#stream-gradient)"
37
+ d="M0 15 Q30 10 50 18 Q80 28 100 20 Q120 12 150 17
38
+ L150 30 Q120 35 100 28 Q80 22 50 30 Q30 35 0 28 Z"
39
+ />
40
+
41
+ <!-- Flow lines -->
42
+ <g class={animate ? 'flow' : ''}>
43
+ <path
44
+ fill="none"
45
+ stroke="white"
46
+ stroke-width="1"
47
+ d="M10 20 Q25 18 40 22"
48
+ opacity="0.3"
49
+ />
50
+ <path
51
+ fill="none"
52
+ stroke="white"
53
+ stroke-width="1"
54
+ d="M50 24 Q70 20 90 23"
55
+ opacity="0.25"
56
+ />
57
+ <path
58
+ fill="none"
59
+ stroke="white"
60
+ stroke-width="1"
61
+ d="M100 21 Q120 18 140 22"
62
+ opacity="0.3"
63
+ />
64
+ </g>
65
+
66
+ <!-- Sparkles/highlights -->
67
+ <g class={animate ? 'sparkle' : ''}>
68
+ <circle fill="white" cx="30" cy="20" r="1.5" opacity="0.4" />
69
+ <circle fill="white" cx="75" cy="23" r="1" opacity="0.3" />
70
+ <circle fill="white" cx="115" cy="20" r="1.5" opacity="0.35" />
71
+ </g>
72
+
73
+ <!-- Bank shadows -->
74
+ <path
75
+ fill="rgba(0,0,0,0.1)"
76
+ d="M0 15 Q30 10 50 18 Q80 28 100 20 Q120 12 150 17 L150 15 Q120 10 100 18 Q80 26 50 16 Q30 8 0 13 Z"
77
+ />
78
+ </svg>
79
+
80
+ <style>
81
+ @keyframes flow {
82
+ 0% { transform: translateX(0); }
83
+ 100% { transform: translateX(10px); }
84
+ }
85
+
86
+ @keyframes sparkle {
87
+ 0%, 100% { opacity: 1; }
88
+ 50% { opacity: 0.5; }
89
+ }
90
+
91
+ .flow {
92
+ animation: flow 2s linear infinite;
93
+ }
94
+
95
+ .sparkle {
96
+ animation: sparkle 1.5s ease-in-out infinite;
97
+ }
98
+ </style>
@@ -0,0 +1,8 @@
1
+ interface Props {
2
+ class?: string;
3
+ color?: string;
4
+ animate?: boolean;
5
+ }
6
+ declare const Stream: import("svelte").Component<Props, {}, "">;
7
+ type Stream = ReturnType<typeof Stream>;
8
+ export default Stream;
@@ -0,0 +1,4 @@
1
+ export { default as LilyPad } from './LilyPad.svelte';
2
+ export { default as Pond } from './Pond.svelte';
3
+ export { default as Reeds } from './Reeds.svelte';
4
+ export { default as Stream } from './Stream.svelte';
@@ -0,0 +1,5 @@
1
+ // Water components - ponds, streams, and aquatic elements
2
+ export { default as LilyPad } from './LilyPad.svelte';
3
+ export { default as Pond } from './Pond.svelte';
4
+ export { default as Reeds } from './Reeds.svelte';
5
+ export { default as Stream } from './Stream.svelte';
@@ -0,0 +1,175 @@
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 { winter } from '../palette';
9
+ import SnowflakeFalling from './SnowflakeFalling.svelte';
10
+
11
+ type SnowflakeVariant = 'crystal' | 'simple' | 'star' | 'delicate' | 'dot';
12
+
13
+ // Check for reduced motion preference
14
+ const prefersReducedMotion = browser
15
+ ? window.matchMedia('(prefers-reduced-motion: reduce)').matches
16
+ : false;
17
+
18
+ interface Props {
19
+ /** Total number of snowflakes */
20
+ count?: number;
21
+ /** Base z-index for the snow layer */
22
+ zIndex?: number;
23
+ /** Enable snowfall animation */
24
+ enabled?: boolean;
25
+ /** Opacity range for snowflakes (depth affects final value) */
26
+ opacity?: { min: number; max: number };
27
+ /** Fall duration range in seconds (slower = gentler snowfall) */
28
+ fallDuration?: { min: number; max: number };
29
+ /** Horizontal drift range in vw units (higher = more wind) */
30
+ driftRange?: number;
31
+ /** Maximum spawn delay in seconds (staggers initial appearance) */
32
+ spawnDelay?: number;
33
+ }
34
+
35
+ let {
36
+ count = 60,
37
+ zIndex = 50,
38
+ enabled = true,
39
+ opacity = { min: 0.4, max: 0.9 },
40
+ fallDuration = { min: 10, max: 18 },
41
+ driftRange = 15,
42
+ spawnDelay = 12
43
+ }: Props = $props();
44
+
45
+ // Derived animation constants from props
46
+ const FALL_DISTANCE = { min: 100, max: 120 } as const;
47
+
48
+ // Reduce snowflake count for reduced motion or use fewer for performance
49
+ const actualCount = $derived(prefersReducedMotion ? Math.floor(count / 4) : count);
50
+
51
+ const snowflakeVariants: SnowflakeVariant[] = ['crystal', 'simple', 'star', 'delicate', 'dot'];
52
+
53
+ interface Snowflake {
54
+ id: number;
55
+ x: number;
56
+ y: number;
57
+ size: number;
58
+ variant: SnowflakeVariant;
59
+ duration: number;
60
+ delay: number;
61
+ drift: number;
62
+ opacity: number;
63
+ fallDistance: number;
64
+ }
65
+
66
+ // Deterministic hash for natural distribution (returns 0-1 range)
67
+ // Using different prime multipliers for each property ensures varied but consistent results
68
+ function hashRandom(seed: number): number {
69
+ const hash = Math.abs(Math.sin(seed * 12.9898) * 43758.5453);
70
+ return hash - Math.floor(hash); // Fractional part gives 0-1 range
71
+ }
72
+
73
+ // Generate snowflakes across the viewport
74
+ // All randomization is deterministic to prevent SSR hydration mismatches
75
+ function generateSnowflakes(): Snowflake[] {
76
+ const snowflakes: Snowflake[] = [];
77
+
78
+ for (let i = 0; i < actualCount; i++) {
79
+ // Use different seed multipliers for each property to get varied distributions
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
+
88
+ // Distribute across full width with some randomness
89
+ const x = (i / actualCount) * 100 + (xRand - 0.5) * 10;
90
+
91
+ // Start positions: most above viewport, some within for immediate visibility
92
+ // This creates a natural "already snowing" effect on page load
93
+ const y = yRand < 0.3
94
+ ? yRand * 80 // 30% start within viewport (0-24% from top)
95
+ : -5 - yRand * 15; // 70% start above viewport
96
+
97
+ // Depth-based sizing algorithm:
98
+ // depthFactor 0.0 = far away (small, simple dots, lower opacity)
99
+ // depthFactor 1.0 = close up (large, detailed crystals, higher opacity)
100
+ // This creates a parallax-like depth effect with smaller background flakes
101
+ const depthFactor = depthRand;
102
+ const size = 8 + depthFactor * 16; // 8-24px range for better visibility
103
+
104
+ // Variant selection based on depth:
105
+ // - Far (0.0-0.3): tiny dots for distant snow
106
+ // - Mid (0.3-0.5): simple shapes
107
+ // - Close (0.5-1.0): detailed crystal/star/delicate variants (excludes dot)
108
+ let variant: SnowflakeVariant;
109
+ if (depthFactor < 0.3) {
110
+ variant = 'dot';
111
+ } else if (depthFactor < 0.5) {
112
+ variant = 'simple';
113
+ } else {
114
+ // Use % 4 intentionally to select from first 4 variants (crystal, simple, star, delicate)
115
+ // This excludes 'dot' which is reserved for distant snowflakes
116
+ variant = snowflakeVariants[Math.floor(hashRandom(i) * 4)] as SnowflakeVariant;
117
+ }
118
+
119
+ // Snowflakes starting in viewport get minimal delay for immediate visibility
120
+ const isInViewport = y >= 0;
121
+ const actualDelay = isInViewport ? delayRand * 2 : delayRand * spawnDelay;
122
+
123
+ snowflakes.push({
124
+ id: i,
125
+ x,
126
+ y,
127
+ size,
128
+ variant,
129
+ duration: fallDuration.min + durationRand * (fallDuration.max - fallDuration.min),
130
+ delay: actualDelay,
131
+ drift: (driftRand - 0.5) * driftRange,
132
+ opacity: opacity.min + depthFactor * (opacity.max - opacity.min),
133
+ fallDistance: FALL_DISTANCE.min + distanceRand * (FALL_DISTANCE.max - FALL_DISTANCE.min)
134
+ });
135
+ }
136
+
137
+ return snowflakes;
138
+ }
139
+
140
+ // Generate snowflakes once
141
+ const snowflakes = $derived(generateSnowflakes());
142
+ </script>
143
+
144
+ {#if enabled}
145
+ <!-- Snowfall layer - covers the page content area -->
146
+ <div
147
+ class="absolute inset-0 pointer-events-none overflow-hidden"
148
+ style="z-index: {zIndex};"
149
+ >
150
+ {#each snowflakes as flake (flake.id)}
151
+ <div
152
+ class="absolute"
153
+ style="
154
+ left: {flake.x}%;
155
+ top: {flake.y}%;
156
+ width: {flake.size}px;
157
+ height: {flake.size}px;
158
+ "
159
+ >
160
+ <SnowflakeFalling
161
+ class="w-full h-full"
162
+ variant={flake.variant}
163
+ color={winter.snow}
164
+ duration={flake.duration}
165
+ delay={flake.delay}
166
+ drift={flake.drift}
167
+ fallDistance={flake.fallDistance}
168
+ opacity={flake.opacity}
169
+ seed={flake.id}
170
+ animate={true}
171
+ />
172
+ </div>
173
+ {/each}
174
+ </div>
175
+ {/if}
@@ -0,0 +1,25 @@
1
+ interface Props {
2
+ /** Total number of snowflakes */
3
+ count?: number;
4
+ /** Base z-index for the snow layer */
5
+ zIndex?: number;
6
+ /** Enable snowfall animation */
7
+ enabled?: boolean;
8
+ /** Opacity range for snowflakes (depth affects final value) */
9
+ opacity?: {
10
+ min: number;
11
+ max: number;
12
+ };
13
+ /** Fall duration range in seconds (slower = gentler snowfall) */
14
+ fallDuration?: {
15
+ min: number;
16
+ max: number;
17
+ };
18
+ /** Horizontal drift range in vw units (higher = more wind) */
19
+ driftRange?: number;
20
+ /** Maximum spawn delay in seconds (staggers initial appearance) */
21
+ spawnDelay?: number;
22
+ }
23
+ declare const SnowfallLayer: import("svelte").Component<Props, {}, "">;
24
+ type SnowfallLayer = ReturnType<typeof SnowfallLayer>;
25
+ export default SnowfallLayer;