@autumnsgrove/groveengine 0.7.0 → 0.8.5

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 (230) hide show
  1. package/dist/components/OnboardingChecklist.svelte +2 -2
  2. package/dist/components/WispButton.svelte +83 -0
  3. package/dist/components/WispButton.svelte.d.ts +49 -0
  4. package/dist/components/WispPanel.svelte +1093 -0
  5. package/dist/components/WispPanel.svelte.d.ts +49 -0
  6. package/dist/components/custom/TableOfContents.svelte +12 -1
  7. package/dist/components/quota/UpgradePrompt.svelte +1 -0
  8. package/dist/config/wisp.d.ts +145 -0
  9. package/dist/config/wisp.js +175 -0
  10. package/dist/index.d.ts +2 -0
  11. package/dist/index.js +3 -0
  12. package/dist/server/inference-client.d.ts +139 -0
  13. package/dist/server/inference-client.js +294 -0
  14. package/dist/ui/components/content/RoadmapPreview.svelte +91 -0
  15. package/dist/ui/components/content/RoadmapPreview.svelte.d.ts +36 -0
  16. package/dist/ui/components/content/index.d.ts +1 -0
  17. package/dist/ui/components/content/index.js +1 -0
  18. package/dist/ui/components/nature/Logo.svelte +260 -0
  19. package/dist/ui/components/nature/Logo.svelte.d.ts +14 -0
  20. package/dist/ui/components/nature/botanical/Acorn.svelte +48 -0
  21. package/dist/ui/components/nature/botanical/Acorn.svelte.d.ts +8 -0
  22. package/dist/ui/components/nature/botanical/Berry.svelte +67 -0
  23. package/dist/ui/components/nature/botanical/Berry.svelte.d.ts +8 -0
  24. package/dist/ui/components/nature/botanical/DandelionPuff.svelte +98 -0
  25. package/dist/ui/components/nature/botanical/DandelionPuff.svelte.d.ts +8 -0
  26. package/dist/ui/components/nature/botanical/FallingLeavesLayer.svelte +170 -0
  27. package/dist/ui/components/nature/botanical/FallingLeavesLayer.svelte.d.ts +35 -0
  28. package/dist/ui/components/nature/botanical/FallingPetalsLayer.svelte +174 -0
  29. package/dist/ui/components/nature/botanical/FallingPetalsLayer.svelte.d.ts +25 -0
  30. package/dist/ui/components/nature/botanical/Leaf.svelte +77 -0
  31. package/dist/ui/components/nature/botanical/Leaf.svelte.d.ts +10 -0
  32. package/dist/ui/components/nature/botanical/LeafFalling.svelte +186 -0
  33. package/dist/ui/components/nature/botanical/LeafFalling.svelte.d.ts +22 -0
  34. package/dist/ui/components/nature/botanical/PetalFalling.svelte +266 -0
  35. package/dist/ui/components/nature/botanical/PetalFalling.svelte.d.ts +25 -0
  36. package/dist/ui/components/nature/botanical/PineCone.svelte +61 -0
  37. package/dist/ui/components/nature/botanical/PineCone.svelte.d.ts +7 -0
  38. package/dist/ui/components/nature/botanical/Vine.svelte +102 -0
  39. package/dist/ui/components/nature/botanical/Vine.svelte.d.ts +11 -0
  40. package/dist/ui/components/nature/botanical/index.d.ts +10 -0
  41. package/dist/ui/components/nature/botanical/index.js +11 -0
  42. package/dist/ui/components/nature/creatures/Bee.svelte +78 -0
  43. package/dist/ui/components/nature/creatures/Bee.svelte.d.ts +9 -0
  44. package/dist/ui/components/nature/creatures/Bird.svelte +94 -0
  45. package/dist/ui/components/nature/creatures/Bird.svelte.d.ts +11 -0
  46. package/dist/ui/components/nature/creatures/BirdFlying.svelte +83 -0
  47. package/dist/ui/components/nature/creatures/BirdFlying.svelte.d.ts +9 -0
  48. package/dist/ui/components/nature/creatures/Bluebird.svelte +95 -0
  49. package/dist/ui/components/nature/creatures/Bluebird.svelte.d.ts +12 -0
  50. package/dist/ui/components/nature/creatures/Butterfly.svelte +87 -0
  51. package/dist/ui/components/nature/creatures/Butterfly.svelte.d.ts +9 -0
  52. package/dist/ui/components/nature/creatures/Cardinal.svelte +95 -0
  53. package/dist/ui/components/nature/creatures/Cardinal.svelte.d.ts +12 -0
  54. package/dist/ui/components/nature/creatures/Chickadee.svelte +97 -0
  55. package/dist/ui/components/nature/creatures/Chickadee.svelte.d.ts +12 -0
  56. package/dist/ui/components/nature/creatures/Deer.svelte +95 -0
  57. package/dist/ui/components/nature/creatures/Deer.svelte.d.ts +9 -0
  58. package/dist/ui/components/nature/creatures/Firefly.svelte +111 -0
  59. package/dist/ui/components/nature/creatures/Firefly.svelte.d.ts +10 -0
  60. package/dist/ui/components/nature/creatures/Owl.svelte +91 -0
  61. package/dist/ui/components/nature/creatures/Owl.svelte.d.ts +9 -0
  62. package/dist/ui/components/nature/creatures/Rabbit.svelte +90 -0
  63. package/dist/ui/components/nature/creatures/Rabbit.svelte.d.ts +9 -0
  64. package/dist/ui/components/nature/creatures/Robin.svelte +98 -0
  65. package/dist/ui/components/nature/creatures/Robin.svelte.d.ts +12 -0
  66. package/dist/ui/components/nature/creatures/Squirrel.svelte +97 -0
  67. package/dist/ui/components/nature/creatures/Squirrel.svelte.d.ts +9 -0
  68. package/dist/ui/components/nature/creatures/index.d.ts +13 -0
  69. package/dist/ui/components/nature/creatures/index.js +14 -0
  70. package/dist/ui/components/nature/ground/Bush.svelte +57 -0
  71. package/dist/ui/components/nature/ground/Bush.svelte.d.ts +10 -0
  72. package/dist/ui/components/nature/ground/Crocus.svelte +83 -0
  73. package/dist/ui/components/nature/ground/Crocus.svelte.d.ts +12 -0
  74. package/dist/ui/components/nature/ground/Daffodil.svelte +75 -0
  75. package/dist/ui/components/nature/ground/Daffodil.svelte.d.ts +11 -0
  76. package/dist/ui/components/nature/ground/Fern.svelte +72 -0
  77. package/dist/ui/components/nature/ground/Fern.svelte.d.ts +10 -0
  78. package/dist/ui/components/nature/ground/FlowerWild.svelte +60 -0
  79. package/dist/ui/components/nature/ground/FlowerWild.svelte.d.ts +10 -0
  80. package/dist/ui/components/nature/ground/GrassTuft.svelte +49 -0
  81. package/dist/ui/components/nature/ground/GrassTuft.svelte.d.ts +10 -0
  82. package/dist/ui/components/nature/ground/Log.svelte +42 -0
  83. package/dist/ui/components/nature/ground/Log.svelte.d.ts +7 -0
  84. package/dist/ui/components/nature/ground/Mushroom.svelte +48 -0
  85. package/dist/ui/components/nature/ground/Mushroom.svelte.d.ts +9 -0
  86. package/dist/ui/components/nature/ground/MushroomCluster.svelte +41 -0
  87. package/dist/ui/components/nature/ground/MushroomCluster.svelte.d.ts +8 -0
  88. package/dist/ui/components/nature/ground/Rock.svelte +59 -0
  89. package/dist/ui/components/nature/ground/Rock.svelte.d.ts +8 -0
  90. package/dist/ui/components/nature/ground/Stump.svelte +44 -0
  91. package/dist/ui/components/nature/ground/Stump.svelte.d.ts +8 -0
  92. package/dist/ui/components/nature/ground/Tulip.svelte +79 -0
  93. package/dist/ui/components/nature/ground/Tulip.svelte.d.ts +11 -0
  94. package/dist/ui/components/nature/ground/index.d.ts +12 -0
  95. package/dist/ui/components/nature/ground/index.js +13 -0
  96. package/dist/ui/components/nature/index.d.ts +28 -0
  97. package/dist/ui/components/nature/index.js +38 -0
  98. package/dist/ui/components/nature/palette.d.ts +602 -0
  99. package/dist/ui/components/nature/palette.js +472 -0
  100. package/dist/ui/components/nature/sky/Cloud.svelte +122 -0
  101. package/dist/ui/components/nature/sky/Cloud.svelte.d.ts +11 -0
  102. package/dist/ui/components/nature/sky/CloudWispy.svelte +79 -0
  103. package/dist/ui/components/nature/sky/CloudWispy.svelte.d.ts +9 -0
  104. package/dist/ui/components/nature/sky/Moon.svelte +60 -0
  105. package/dist/ui/components/nature/sky/Moon.svelte.d.ts +9 -0
  106. package/dist/ui/components/nature/sky/Rainbow.svelte +101 -0
  107. package/dist/ui/components/nature/sky/Rainbow.svelte.d.ts +8 -0
  108. package/dist/ui/components/nature/sky/Star.svelte +84 -0
  109. package/dist/ui/components/nature/sky/Star.svelte.d.ts +10 -0
  110. package/dist/ui/components/nature/sky/StarCluster.svelte +85 -0
  111. package/dist/ui/components/nature/sky/StarCluster.svelte.d.ts +9 -0
  112. package/dist/ui/components/nature/sky/StarShooting.svelte +90 -0
  113. package/dist/ui/components/nature/sky/StarShooting.svelte.d.ts +9 -0
  114. package/dist/ui/components/nature/sky/Sun.svelte +70 -0
  115. package/dist/ui/components/nature/sky/Sun.svelte.d.ts +9 -0
  116. package/dist/ui/components/nature/sky/index.d.ts +8 -0
  117. package/dist/ui/components/nature/sky/index.js +9 -0
  118. package/dist/ui/components/nature/structural/Birdhouse.svelte +53 -0
  119. package/dist/ui/components/nature/structural/Birdhouse.svelte.d.ts +8 -0
  120. package/dist/ui/components/nature/structural/Bridge.svelte +65 -0
  121. package/dist/ui/components/nature/structural/Bridge.svelte.d.ts +7 -0
  122. package/dist/ui/components/nature/structural/FencePost.svelte +54 -0
  123. package/dist/ui/components/nature/structural/FencePost.svelte.d.ts +8 -0
  124. package/dist/ui/components/nature/structural/GardenGate.svelte +70 -0
  125. package/dist/ui/components/nature/structural/GardenGate.svelte.d.ts +8 -0
  126. package/dist/ui/components/nature/structural/Lantern.svelte +113 -0
  127. package/dist/ui/components/nature/structural/Lantern.svelte.d.ts +10 -0
  128. package/dist/ui/components/nature/structural/Lattice.svelte +89 -0
  129. package/dist/ui/components/nature/structural/Lattice.svelte.d.ts +8 -0
  130. package/dist/ui/components/nature/structural/LatticeWithVine.svelte +89 -0
  131. package/dist/ui/components/nature/structural/LatticeWithVine.svelte.d.ts +11 -0
  132. package/dist/ui/components/nature/structural/StonePath.svelte +48 -0
  133. package/dist/ui/components/nature/structural/StonePath.svelte.d.ts +7 -0
  134. package/dist/ui/components/nature/structural/index.d.ts +8 -0
  135. package/dist/ui/components/nature/structural/index.js +9 -0
  136. package/dist/ui/components/nature/trees/TreeAspen.svelte +163 -0
  137. package/dist/ui/components/nature/trees/TreeAspen.svelte.d.ts +11 -0
  138. package/dist/ui/components/nature/trees/TreeBirch.svelte +186 -0
  139. package/dist/ui/components/nature/trees/TreeBirch.svelte.d.ts +11 -0
  140. package/dist/ui/components/nature/trees/TreeCherry.svelte +108 -0
  141. package/dist/ui/components/nature/trees/TreeCherry.svelte.d.ts +11 -0
  142. package/dist/ui/components/nature/trees/TreePine.svelte +79 -0
  143. package/dist/ui/components/nature/trees/TreePine.svelte.d.ts +11 -0
  144. package/dist/ui/components/nature/trees/index.d.ts +4 -0
  145. package/dist/ui/components/nature/trees/index.js +5 -0
  146. package/dist/ui/components/nature/water/LilyPad.svelte +99 -0
  147. package/dist/ui/components/nature/water/LilyPad.svelte.d.ts +10 -0
  148. package/dist/ui/components/nature/water/Pond.svelte +104 -0
  149. package/dist/ui/components/nature/water/Pond.svelte.d.ts +8 -0
  150. package/dist/ui/components/nature/water/Reeds.svelte +85 -0
  151. package/dist/ui/components/nature/water/Reeds.svelte.d.ts +11 -0
  152. package/dist/ui/components/nature/water/Stream.svelte +98 -0
  153. package/dist/ui/components/nature/water/Stream.svelte.d.ts +8 -0
  154. package/dist/ui/components/nature/water/index.d.ts +4 -0
  155. package/dist/ui/components/nature/water/index.js +5 -0
  156. package/dist/ui/components/nature/weather/SnowfallLayer.svelte +175 -0
  157. package/dist/ui/components/nature/weather/SnowfallLayer.svelte.d.ts +25 -0
  158. package/dist/ui/components/nature/weather/Snowflake.svelte +99 -0
  159. package/dist/ui/components/nature/weather/Snowflake.svelte.d.ts +11 -0
  160. package/dist/ui/components/nature/weather/SnowflakeFalling.svelte +162 -0
  161. package/dist/ui/components/nature/weather/SnowflakeFalling.svelte.d.ts +23 -0
  162. package/dist/ui/components/nature/weather/index.d.ts +3 -0
  163. package/dist/ui/components/nature/weather/index.js +4 -0
  164. package/dist/ui/components/primitives/textarea/textarea.svelte +1 -1
  165. package/dist/ui/components/typography/Alagard.svelte +17 -0
  166. package/dist/ui/components/typography/Alagard.svelte.d.ts +10 -0
  167. package/dist/ui/components/typography/Atkinson.svelte +17 -0
  168. package/dist/ui/components/typography/Atkinson.svelte.d.ts +10 -0
  169. package/dist/ui/components/typography/BodoniModa.svelte +17 -0
  170. package/dist/ui/components/typography/BodoniModa.svelte.d.ts +10 -0
  171. package/dist/ui/components/typography/Calistoga.svelte +17 -0
  172. package/dist/ui/components/typography/Calistoga.svelte.d.ts +10 -0
  173. package/dist/ui/components/typography/Caveat.svelte +17 -0
  174. package/dist/ui/components/typography/Caveat.svelte.d.ts +10 -0
  175. package/dist/ui/components/typography/Cormorant.svelte +17 -0
  176. package/dist/ui/components/typography/Cormorant.svelte.d.ts +10 -0
  177. package/dist/ui/components/typography/Cozette.svelte +17 -0
  178. package/dist/ui/components/typography/Cozette.svelte.d.ts +10 -0
  179. package/dist/ui/components/typography/EBGaramond.svelte +17 -0
  180. package/dist/ui/components/typography/EBGaramond.svelte.d.ts +10 -0
  181. package/dist/ui/components/typography/FontProvider.svelte +98 -0
  182. package/dist/ui/components/typography/FontProvider.svelte.d.ts +17 -0
  183. package/dist/ui/components/typography/Fraunces.svelte +17 -0
  184. package/dist/ui/components/typography/Fraunces.svelte.d.ts +10 -0
  185. package/dist/ui/components/typography/IBMPlexMono.svelte +17 -0
  186. package/dist/ui/components/typography/IBMPlexMono.svelte.d.ts +10 -0
  187. package/dist/ui/components/typography/InstrumentSans.svelte +17 -0
  188. package/dist/ui/components/typography/InstrumentSans.svelte.d.ts +10 -0
  189. package/dist/ui/components/typography/Lexend.svelte +17 -0
  190. package/dist/ui/components/typography/Lexend.svelte.d.ts +10 -0
  191. package/dist/ui/components/typography/Lora.svelte +17 -0
  192. package/dist/ui/components/typography/Lora.svelte.d.ts +10 -0
  193. package/dist/ui/components/typography/Luciole.svelte +17 -0
  194. package/dist/ui/components/typography/Luciole.svelte.d.ts +10 -0
  195. package/dist/ui/components/typography/Manrope.svelte +17 -0
  196. package/dist/ui/components/typography/Manrope.svelte.d.ts +10 -0
  197. package/dist/ui/components/typography/Merriweather.svelte +17 -0
  198. package/dist/ui/components/typography/Merriweather.svelte.d.ts +10 -0
  199. package/dist/ui/components/typography/Nunito.svelte +17 -0
  200. package/dist/ui/components/typography/Nunito.svelte.d.ts +10 -0
  201. package/dist/ui/components/typography/OpenDyslexic.svelte +17 -0
  202. package/dist/ui/components/typography/OpenDyslexic.svelte.d.ts +10 -0
  203. package/dist/ui/components/typography/PlusJakartaSans.svelte +17 -0
  204. package/dist/ui/components/typography/PlusJakartaSans.svelte.d.ts +10 -0
  205. package/dist/ui/components/typography/Quicksand.svelte +17 -0
  206. package/dist/ui/components/typography/Quicksand.svelte.d.ts +10 -0
  207. package/dist/ui/components/typography/README.md +153 -0
  208. package/dist/ui/components/typography/index.d.ts +23 -0
  209. package/dist/ui/components/typography/index.js +42 -0
  210. package/dist/ui/components/ui/GlassCarousel.svelte +446 -0
  211. package/dist/ui/components/ui/GlassCarousel.svelte.d.ts +57 -0
  212. package/dist/ui/components/ui/GlassConfirmDialog.svelte +2 -1
  213. package/dist/ui/components/ui/GlassLogo.svelte +423 -0
  214. package/dist/ui/components/ui/GlassLogo.svelte.d.ts +23 -0
  215. package/dist/ui/components/ui/GlassNavbar.svelte +120 -0
  216. package/dist/ui/components/ui/GlassNavbar.svelte.d.ts +42 -0
  217. package/dist/ui/components/ui/GlassOverlay.svelte +1 -1
  218. package/dist/ui/components/ui/Logo.svelte +47 -52
  219. package/dist/ui/components/ui/Logo.svelte.d.ts +4 -3
  220. package/dist/ui/components/ui/index.d.ts +3 -0
  221. package/dist/ui/components/ui/index.js +3 -0
  222. package/dist/ui/index.d.ts +1 -0
  223. package/dist/ui/index.js +2 -0
  224. package/dist/ui/styles/grove.css +15 -1
  225. package/dist/ui/vineyard/index.d.ts +9 -0
  226. package/dist/ui/vineyard/index.js +8 -0
  227. package/dist/utils/csrf.js +5 -2
  228. package/dist/utils/readability.d.ts +89 -0
  229. package/dist/utils/readability.js +204 -0
  230. package/package.json +27 -1
@@ -0,0 +1,1093 @@
1
+ <script>
2
+ import { slide, fade } from "svelte/transition";
3
+ import { Button } from "../ui/components/primitives/button";
4
+ import { MAX_CONTENT_LENGTH } from "../config/wisp.js";
5
+
6
+ /**
7
+ * @typedef {Object} Props
8
+ * @property {string} content - Content to analyze
9
+ * @property {boolean} enabled - Whether Wisp is enabled
10
+ * @property {string} postTitle - Optional post title for context
11
+ * @property {string} postSlug - Optional post slug for logging
12
+ * @property {(original: string, suggestion: string) => void} onApplyFix - Callback when user applies a fix
13
+ */
14
+
15
+ /** @type {Props} */
16
+ let {
17
+ content = "",
18
+ enabled = false,
19
+ postTitle = "",
20
+ postSlug = "",
21
+ onApplyFix = (original, suggestion) => {},
22
+ } = $props();
23
+
24
+ // Panel state
25
+ let isOpen = $state(false);
26
+ let isMinimized = $state(true);
27
+
28
+ // Analysis state
29
+ let isAnalyzing = $state(false);
30
+ let analysisError = $state(null);
31
+ let results = $state(null);
32
+ let activeTab = $state("grammar");
33
+ let selectedMode = $state("quick"); // quick or thorough
34
+
35
+ // ASCII art vibes - text landscapes that create atmosphere
36
+ const vibes = {
37
+ idle: `
38
+ . * . . *
39
+ . _ . .
40
+ . / \\ * .
41
+ / ~ ~ \\ . .
42
+ / \\______
43
+ ~~~~~~~~~~~~~~~~~~~`,
44
+
45
+ analyzing: `
46
+ . * . analyzing . *
47
+ \\ | /
48
+ -- (o.o) -- thinking
49
+ / | \\
50
+ ~~~~~~~~~~~~~~~~~
51
+ words flowing...`,
52
+
53
+ success: `
54
+ *
55
+ . * /|\\ .
56
+ * . / | \\ *
57
+ /__|__\\
58
+ ~~~~~/ \\~~~~
59
+ all clear `,
60
+
61
+ grammarGood: `
62
+ .-~~~-.
63
+ .' '.
64
+ / ^ ^ \\
65
+ | (o) (o) | nice!
66
+ \\ <=> /
67
+ '-.___.-'`,
68
+
69
+ toneWarm: `
70
+ __/\\__
71
+ \\ /
72
+ <( ~~~~ )>
73
+ / \\
74
+ / ^^ \\
75
+ warm & cozy`,
76
+
77
+ error: `
78
+ . x .
79
+ /|\\
80
+ / | \\ oops
81
+ / | \\
82
+ _____|_____
83
+ try again?`,
84
+ };
85
+
86
+ // Get current vibe based on state
87
+ let currentVibe = $derived(() => {
88
+ if (isAnalyzing) return vibes.analyzing;
89
+ if (analysisError) return vibes.error;
90
+ if (results?.grammar?.overallScore >= 90) return vibes.grammarGood;
91
+ if (results?.tone) return vibes.toneWarm;
92
+ if (results) return vibes.success;
93
+ return vibes.idle;
94
+ });
95
+
96
+ // Seasonal vibe rotation for idle state
97
+ const seasonalVibes = [
98
+ // Forest morning
99
+ `
100
+ . * . . *
101
+ . _ . .
102
+ / \\ * .
103
+ / ~ ~ \\ . .
104
+ / \\______
105
+ ~~~~~~~~~~~~~~~~~~~`,
106
+ // Starry grove
107
+ `
108
+ * . * . * . *
109
+ . * * .
110
+ _/\\_ *
111
+ . / \\ .
112
+ ___/ \\___
113
+ ~~~~~~ ~~~~~~~~~`,
114
+ // Mountain vista
115
+ `
116
+ /\\
117
+ . / \\ . *
118
+ / \\ .
119
+ * / /\\ \\ .
120
+ __/ / \\ \\__
121
+ ~~~~~~~~~~~~~~~~`,
122
+ // Meadow
123
+ `
124
+ . * . * . * . * .
125
+ ~ ~ ~ ~ ~
126
+ , , , , , ,
127
+ v v v v v v v v v
128
+ | | | | | | | | |
129
+ ==================`,
130
+ // Night grove
131
+ `
132
+ * . . * . . * . *
133
+ . * .
134
+ \\ | /
135
+ --- (._.) ---
136
+ / | \\
137
+ ~~~quiet night~~~`,
138
+ ];
139
+
140
+ let vibeIndex = $state(0);
141
+ let panelRef = $state(null);
142
+
143
+ // Content length status
144
+ let contentLengthStatus = $derived(() => {
145
+ const len = content.length;
146
+ const pct = Math.round((len / MAX_CONTENT_LENGTH) * 100);
147
+ if (len > MAX_CONTENT_LENGTH) return { status: "over", pct: 100, len };
148
+ if (pct > 80) return { status: "warn", pct, len };
149
+ return { status: "ok", pct, len };
150
+ });
151
+
152
+ // Rotate through vibes when idle
153
+ $effect(() => {
154
+ if (!isOpen || isAnalyzing || results) return;
155
+
156
+ const interval = setInterval(() => {
157
+ vibeIndex = (vibeIndex + 1) % seasonalVibes.length;
158
+ }, 8000);
159
+
160
+ return () => clearInterval(interval);
161
+ });
162
+
163
+ // Handle keyboard navigation
164
+ function handleKeydown(e) {
165
+ if (e.key === "Escape" && isOpen) {
166
+ e.preventDefault();
167
+ minimize();
168
+ }
169
+ }
170
+
171
+ // Get the display vibe
172
+ let displayVibe = $derived(() => {
173
+ if (isAnalyzing) return vibes.analyzing;
174
+ if (analysisError) return vibes.error;
175
+ if (results) return currentVibe();
176
+ return seasonalVibes[vibeIndex];
177
+ });
178
+
179
+ // Run analysis
180
+ async function runAnalysis(action = "all") {
181
+ if (!content.trim()) {
182
+ analysisError = "Write something first!";
183
+ return;
184
+ }
185
+
186
+ if (content.length > MAX_CONTENT_LENGTH) {
187
+ analysisError = `Content too long (${content.length.toLocaleString()} chars). Max ${MAX_CONTENT_LENGTH.toLocaleString()}.`;
188
+ return;
189
+ }
190
+
191
+ isAnalyzing = true;
192
+ analysisError = null;
193
+
194
+ try {
195
+ const res = await fetch("/api/grove/wisp", {
196
+ method: "POST",
197
+ headers: { "Content-Type": "application/json" },
198
+ body: JSON.stringify({
199
+ content,
200
+ action,
201
+ mode: selectedMode,
202
+ context: { title: postTitle, slug: postSlug }
203
+ })
204
+ });
205
+
206
+ if (res.ok) {
207
+ results = await res.json();
208
+ if (action === "grammar") activeTab = "grammar";
209
+ else if (action === "tone") activeTab = "tone";
210
+ else if (action === "readability") activeTab = "readability";
211
+ } else {
212
+ const error = await res.json();
213
+ analysisError = error.error || "Analysis failed";
214
+ }
215
+ } catch (err) {
216
+ analysisError = "Could not connect to Wisp";
217
+ } finally {
218
+ isAnalyzing = false;
219
+ }
220
+ }
221
+
222
+ // Apply a grammar fix
223
+ function applyFix(suggestion) {
224
+ onApplyFix(suggestion.original, suggestion.suggestion);
225
+ // Remove from list
226
+ if (results?.grammar?.suggestions) {
227
+ results.grammar.suggestions = results.grammar.suggestions.filter(
228
+ s => s.original !== suggestion.original
229
+ );
230
+ }
231
+ }
232
+
233
+ // Clear results
234
+ function clearResults() {
235
+ results = null;
236
+ analysisError = null;
237
+ }
238
+
239
+ // Toggle panel
240
+ function togglePanel() {
241
+ if (isMinimized) {
242
+ isMinimized = false;
243
+ isOpen = true;
244
+ } else {
245
+ isOpen = !isOpen;
246
+ }
247
+ }
248
+
249
+ // Minimize to tab
250
+ function minimize() {
251
+ isMinimized = true;
252
+ isOpen = false;
253
+ }
254
+
255
+ // Severity colors
256
+ function getSeverityClass(severity) {
257
+ switch (severity) {
258
+ case "error": return "severity-error";
259
+ case "warning": return "severity-warning";
260
+ default: return "severity-style";
261
+ }
262
+ }
263
+
264
+ // Format score as visual bar
265
+ function formatScore(score) {
266
+ if (score === null || score === undefined) return "░░░░░░░░░░";
267
+ const filled = Math.round(score / 10);
268
+ const empty = 10 - filled;
269
+ return "█".repeat(filled) + "░".repeat(empty);
270
+ }
271
+ </script>
272
+
273
+ <svelte:window onkeydown={handleKeydown} />
274
+
275
+ {#if enabled}
276
+ <!-- Minimized tab on the side -->
277
+ {#if isMinimized}
278
+ <button
279
+ class="wisp-tab"
280
+ onclick={togglePanel}
281
+ title="Open Wisp"
282
+ aria-label="Open Wisp writing assistant"
283
+ transition:fade={{ duration: 150 }}
284
+ >
285
+ <span class="tab-icon" aria-hidden="true">~</span>
286
+ <span class="tab-text">wisp</span>
287
+ </button>
288
+ {/if}
289
+
290
+ <!-- Main panel -->
291
+ {#if isOpen && !isMinimized}
292
+ <aside
293
+ class="wisp-panel"
294
+ role="complementary"
295
+ aria-label="Wisp writing assistant"
296
+ bind:this={panelRef}
297
+ transition:slide={{ axis: "x", duration: 200 }}
298
+ >
299
+ <!-- Header -->
300
+ <header class="panel-header">
301
+ <h3>wisp</h3>
302
+ <div class="header-actions">
303
+ <button class="icon-btn" onclick={minimize} title="Minimize" aria-label="Minimize panel">
304
+ <span aria-hidden="true">−</span>
305
+ </button>
306
+ <button class="icon-btn" onclick={() => isOpen = false} title="Close (Esc)" aria-label="Close panel">
307
+ <span aria-hidden="true">×</span>
308
+ </button>
309
+ </div>
310
+ </header>
311
+
312
+ <!-- Content length indicator -->
313
+ <div
314
+ class="content-length"
315
+ class:warn={contentLengthStatus().status === "warn"}
316
+ class:over={contentLengthStatus().status === "over"}
317
+ aria-live="polite"
318
+ >
319
+ <span class="length-text">
320
+ {contentLengthStatus().len.toLocaleString()} / {MAX_CONTENT_LENGTH.toLocaleString()}
321
+ </span>
322
+ <div class="length-bar">
323
+ <div class="length-fill" style="width: {contentLengthStatus().pct}%"></div>
324
+ </div>
325
+ </div>
326
+
327
+ <!-- Vibes section - the ASCII art atmosphere -->
328
+ <div class="vibes-section">
329
+ <pre class="ascii-vibe" aria-hidden="true">{displayVibe()}</pre>
330
+ </div>
331
+
332
+ <!-- Mode selector -->
333
+ <div class="mode-selector">
334
+ <label>
335
+ <input type="radio" bind:group={selectedMode} value="quick" />
336
+ <span>quick</span>
337
+ </label>
338
+ <label>
339
+ <input type="radio" bind:group={selectedMode} value="thorough" />
340
+ <span>thorough</span>
341
+ </label>
342
+ </div>
343
+
344
+ <!-- Action buttons -->
345
+ <div class="actions" role="group" aria-label="Analysis actions">
346
+ <button
347
+ class="action-btn"
348
+ onclick={() => runAnalysis("grammar")}
349
+ disabled={isAnalyzing || contentLengthStatus().status === "over"}
350
+ aria-busy={isAnalyzing}
351
+ >
352
+ grammar
353
+ </button>
354
+ <button
355
+ class="action-btn"
356
+ onclick={() => runAnalysis("tone")}
357
+ disabled={isAnalyzing || contentLengthStatus().status === "over"}
358
+ aria-busy={isAnalyzing}
359
+ >
360
+ tone
361
+ </button>
362
+ <button
363
+ class="action-btn"
364
+ onclick={() => runAnalysis("readability")}
365
+ disabled={isAnalyzing}
366
+ aria-busy={isAnalyzing}
367
+ >
368
+ reading
369
+ </button>
370
+ <button
371
+ class="action-btn action-full"
372
+ onclick={() => runAnalysis("all")}
373
+ disabled={isAnalyzing || contentLengthStatus().status === "over"}
374
+ aria-busy={isAnalyzing}
375
+ >
376
+ {isAnalyzing ? "thinking..." : "full check"}
377
+ </button>
378
+ </div>
379
+
380
+ <!-- Error message -->
381
+ {#if analysisError}
382
+ <div class="error-message" transition:slide>
383
+ <p>{analysisError}</p>
384
+ <button onclick={() => analysisError = null}>dismiss</button>
385
+ </div>
386
+ {/if}
387
+
388
+ <!-- Results -->
389
+ {#if results}
390
+ <div class="results" transition:slide>
391
+ <!-- Tabs -->
392
+ <div class="tabs">
393
+ {#if results.grammar}
394
+ <button
395
+ class="tab"
396
+ class:active={activeTab === "grammar"}
397
+ onclick={() => activeTab = "grammar"}
398
+ >
399
+ grammar
400
+ </button>
401
+ {/if}
402
+ {#if results.tone}
403
+ <button
404
+ class="tab"
405
+ class:active={activeTab === "tone"}
406
+ onclick={() => activeTab = "tone"}
407
+ >
408
+ tone
409
+ </button>
410
+ {/if}
411
+ {#if results.readability}
412
+ <button
413
+ class="tab"
414
+ class:active={activeTab === "readability"}
415
+ onclick={() => activeTab = "readability"}
416
+ >
417
+ reading
418
+ </button>
419
+ {/if}
420
+ </div>
421
+
422
+ <!-- Grammar Results -->
423
+ {#if activeTab === "grammar" && results.grammar}
424
+ <div class="tab-content">
425
+ <div class="score-display">
426
+ <span class="score-label">clarity</span>
427
+ <span class="score-bar">{formatScore(results.grammar.overallScore)}</span>
428
+ <span class="score-num">{results.grammar.overallScore ?? "—"}</span>
429
+ </div>
430
+
431
+ {#if results.grammar.suggestions?.length > 0}
432
+ <div class="suggestions">
433
+ {#each results.grammar.suggestions as suggestion}
434
+ <div class="suggestion {getSeverityClass(suggestion.severity)}">
435
+ <div class="suggestion-original">
436
+ <span class="strike">{suggestion.original}</span>
437
+ </div>
438
+ <div class="suggestion-fix">
439
+ <span class="arrow">→</span>
440
+ <span class="fix-text">{suggestion.suggestion}</span>
441
+ </div>
442
+ <div class="suggestion-reason">{suggestion.reason}</div>
443
+ <button class="apply-btn" onclick={() => applyFix(suggestion)}>
444
+ apply
445
+ </button>
446
+ </div>
447
+ {/each}
448
+ </div>
449
+ {:else}
450
+ <p class="no-issues">looking good!</p>
451
+ {/if}
452
+ </div>
453
+ {/if}
454
+
455
+ <!-- Tone Results -->
456
+ {#if activeTab === "tone" && results.tone}
457
+ <div class="tab-content">
458
+ <p class="tone-analysis">{results.tone.analysis}</p>
459
+
460
+ {#if results.tone.traits?.length > 0}
461
+ <div class="traits">
462
+ {#each results.tone.traits as trait}
463
+ <div class="trait">
464
+ <span class="trait-name">{trait.trait}</span>
465
+ <div class="trait-bar-container">
466
+ <div class="trait-bar" style="width: {trait.score}%"></div>
467
+ </div>
468
+ <span class="trait-score">{trait.score}</span>
469
+ </div>
470
+ {/each}
471
+ </div>
472
+ {/if}
473
+
474
+ {#if results.tone.suggestions?.length > 0}
475
+ <div class="tone-suggestions">
476
+ {#each results.tone.suggestions as sug}
477
+ <p class="tone-sug">• {sug}</p>
478
+ {/each}
479
+ </div>
480
+ {/if}
481
+ </div>
482
+ {/if}
483
+
484
+ <!-- Readability Results -->
485
+ {#if activeTab === "readability" && results.readability}
486
+ <div class="tab-content">
487
+ <div class="readability-stats">
488
+ <div class="stat">
489
+ <span class="stat-label">grade level</span>
490
+ <span class="stat-value">{results.readability.fleschKincaid}</span>
491
+ </div>
492
+ <div class="stat">
493
+ <span class="stat-label">reading time</span>
494
+ <span class="stat-value">{results.readability.readingTime}</span>
495
+ </div>
496
+ <div class="stat">
497
+ <span class="stat-label">words</span>
498
+ <span class="stat-value">{results.readability.wordCount}</span>
499
+ </div>
500
+ <div class="stat">
501
+ <span class="stat-label">sentences</span>
502
+ <span class="stat-value">{results.readability.sentenceCount}</span>
503
+ </div>
504
+ <div class="stat">
505
+ <span class="stat-label">avg sentence</span>
506
+ <span class="stat-value">{results.readability.sentenceStats.average} words</span>
507
+ </div>
508
+ <div class="stat">
509
+ <span class="stat-label">longest</span>
510
+ <span class="stat-value">{results.readability.sentenceStats.longest} words</span>
511
+ </div>
512
+ </div>
513
+
514
+ {#if results.readability.suggestions?.length > 0}
515
+ <div class="readability-suggestions">
516
+ {#each results.readability.suggestions as sug}
517
+ <p class="read-sug">• {sug}</p>
518
+ {/each}
519
+ </div>
520
+ {/if}
521
+ </div>
522
+ {/if}
523
+
524
+ <!-- Usage info -->
525
+ <div class="usage-info">
526
+ {#if results.meta?.tokensUsed}
527
+ <span>tokens: {results.meta.tokensUsed}</span>
528
+ <span>cost: ${results.meta.cost?.toFixed(4) || "0.0000"}</span>
529
+ {/if}
530
+ <button class="clear-btn" onclick={clearResults}>clear</button>
531
+ </div>
532
+ </div>
533
+ {/if}
534
+
535
+ <!-- Footer note -->
536
+ <footer class="panel-footer" aria-label="Wisp philosophy: analyzes your writing but never generates content">
537
+ <p>a helper, not a writer</p>
538
+ </footer>
539
+ </aside>
540
+ {/if}
541
+ {/if}
542
+
543
+ <style>
544
+ /* Minimized tab */
545
+ .wisp-tab {
546
+ position: fixed;
547
+ right: 0;
548
+ top: 50%;
549
+ transform: translateY(-50%);
550
+ background: var(--color-surface, #2a2a2a);
551
+ border: 1px solid var(--color-border, #3a3a3a);
552
+ border-right: none;
553
+ border-radius: 8px 0 0 8px;
554
+ padding: 0.75rem 0.5rem;
555
+ cursor: pointer;
556
+ display: flex;
557
+ flex-direction: column;
558
+ align-items: center;
559
+ gap: 0.25rem;
560
+ z-index: 100;
561
+ transition: background-color 0.2s, transform 0.2s;
562
+ }
563
+
564
+ .wisp-tab:hover {
565
+ background: var(--color-primary, #2d5a2d);
566
+ transform: translateY(-50%) translateX(-2px);
567
+ }
568
+
569
+ .tab-icon {
570
+ font-family: monospace;
571
+ font-size: 1.2rem;
572
+ color: var(--color-accent, #8bc48b);
573
+ }
574
+
575
+ .tab-text {
576
+ font-size: 0.6rem;
577
+ text-transform: lowercase;
578
+ letter-spacing: 0.1em;
579
+ color: var(--color-muted-foreground, #888);
580
+ writing-mode: vertical-rl;
581
+ text-orientation: mixed;
582
+ }
583
+
584
+ /* Main panel */
585
+ .wisp-panel {
586
+ position: fixed;
587
+ right: 0;
588
+ top: 0;
589
+ bottom: 0;
590
+ width: 280px;
591
+ background: var(--color-background, #1e1e1e);
592
+ border-left: 1px solid var(--color-border, #3a3a3a);
593
+ display: flex;
594
+ flex-direction: column;
595
+ z-index: 100;
596
+ font-size: 0.85rem;
597
+ overflow: hidden;
598
+ }
599
+
600
+ /* Header */
601
+ .panel-header {
602
+ display: flex;
603
+ justify-content: space-between;
604
+ align-items: center;
605
+ padding: 0.75rem 1rem;
606
+ border-bottom: 1px solid var(--color-border, #3a3a3a);
607
+ }
608
+
609
+ .panel-header h3 {
610
+ margin: 0;
611
+ font-size: 0.9rem;
612
+ font-weight: 500;
613
+ color: var(--color-accent, #8bc48b);
614
+ letter-spacing: 0.05em;
615
+ }
616
+
617
+ .header-actions {
618
+ display: flex;
619
+ gap: 0.25rem;
620
+ }
621
+
622
+ .icon-btn {
623
+ background: none;
624
+ border: none;
625
+ color: var(--color-muted-foreground, #888);
626
+ cursor: pointer;
627
+ padding: 0.25rem 0.5rem;
628
+ font-size: 1rem;
629
+ line-height: 1;
630
+ border-radius: 4px;
631
+ transition: background-color 0.15s, color 0.15s;
632
+ }
633
+
634
+ .icon-btn:hover {
635
+ background: var(--color-surface, #2a2a2a);
636
+ color: var(--color-foreground, #d4d4d4);
637
+ }
638
+
639
+ /* Content length indicator */
640
+ .content-length {
641
+ padding: 0.25rem 0.75rem;
642
+ border-bottom: 1px solid var(--color-border, #3a3a3a);
643
+ font-size: 0.65rem;
644
+ color: var(--color-muted-foreground, #888);
645
+ }
646
+
647
+ .content-length.warn {
648
+ background: rgba(255, 193, 7, 0.1);
649
+ }
650
+
651
+ .content-length.warn .length-text {
652
+ color: #ffc107;
653
+ }
654
+
655
+ .content-length.over {
656
+ background: rgba(220, 53, 69, 0.1);
657
+ }
658
+
659
+ .content-length.over .length-text {
660
+ color: #dc3545;
661
+ }
662
+
663
+ .length-text {
664
+ display: block;
665
+ margin-bottom: 0.25rem;
666
+ }
667
+
668
+ .length-bar {
669
+ height: 2px;
670
+ background: var(--color-border, #3a3a3a);
671
+ border-radius: 1px;
672
+ overflow: hidden;
673
+ }
674
+
675
+ .length-fill {
676
+ height: 100%;
677
+ background: var(--color-accent, #8bc48b);
678
+ transition: width 0.2s ease;
679
+ }
680
+
681
+ .content-length.warn .length-fill {
682
+ background: #ffc107;
683
+ }
684
+
685
+ .content-length.over .length-fill {
686
+ background: #dc3545;
687
+ }
688
+
689
+ /* Vibes section */
690
+ .vibes-section {
691
+ padding: 0.5rem;
692
+ text-align: center;
693
+ border-bottom: 1px solid var(--color-border, #3a3a3a);
694
+ background: var(--color-surface, #2a2a2a);
695
+ }
696
+
697
+ .ascii-vibe {
698
+ margin: 0;
699
+ font-family: monospace;
700
+ font-size: 0.6rem;
701
+ line-height: 1.2;
702
+ color: var(--color-accent, #8bc48b);
703
+ opacity: 0.8;
704
+ white-space: pre;
705
+ -webkit-user-select: none;
706
+ -moz-user-select: none;
707
+ user-select: none;
708
+ }
709
+
710
+ /* Mode selector */
711
+ .mode-selector {
712
+ display: flex;
713
+ gap: 1rem;
714
+ padding: 0.5rem 1rem;
715
+ border-bottom: 1px solid var(--color-border, #3a3a3a);
716
+ font-size: 0.75rem;
717
+ }
718
+
719
+ .mode-selector label {
720
+ display: flex;
721
+ align-items: center;
722
+ gap: 0.25rem;
723
+ cursor: pointer;
724
+ color: var(--color-muted-foreground, #888);
725
+ }
726
+
727
+ .mode-selector input[type="radio"] {
728
+ accent-color: var(--color-accent, #8bc48b);
729
+ }
730
+
731
+ /* Actions */
732
+ .actions {
733
+ display: grid;
734
+ grid-template-columns: 1fr 1fr;
735
+ gap: 0.5rem;
736
+ padding: 0.75rem;
737
+ }
738
+
739
+ .action-btn {
740
+ background: var(--color-surface, #2a2a2a);
741
+ border: 1px solid var(--color-border, #3a3a3a);
742
+ border-radius: 4px;
743
+ padding: 0.5rem;
744
+ color: var(--color-foreground, #d4d4d4);
745
+ cursor: pointer;
746
+ font-size: 0.75rem;
747
+ transition: background-color 0.15s, border-color 0.15s;
748
+ }
749
+
750
+ .action-btn:hover:not(:disabled) {
751
+ background: var(--color-primary, #2d5a2d);
752
+ border-color: var(--color-accent, #8bc48b);
753
+ }
754
+
755
+ .action-btn:disabled {
756
+ opacity: 0.5;
757
+ cursor: not-allowed;
758
+ }
759
+
760
+ .action-full {
761
+ grid-column: 1 / -1;
762
+ background: var(--color-primary, #2d5a2d);
763
+ border-color: var(--color-accent, #8bc48b);
764
+ }
765
+
766
+ /* Error message */
767
+ .error-message {
768
+ margin: 0.5rem;
769
+ padding: 0.5rem;
770
+ background: rgba(220, 53, 69, 0.1);
771
+ border: 1px solid rgba(220, 53, 69, 0.3);
772
+ border-radius: 4px;
773
+ color: #ff6b6b;
774
+ font-size: 0.75rem;
775
+ }
776
+
777
+ .error-message button {
778
+ background: none;
779
+ border: none;
780
+ color: inherit;
781
+ text-decoration: underline;
782
+ cursor: pointer;
783
+ padding: 0;
784
+ margin-top: 0.25rem;
785
+ }
786
+
787
+ /* Results */
788
+ .results {
789
+ flex: 1;
790
+ overflow-y: auto;
791
+ display: flex;
792
+ flex-direction: column;
793
+ }
794
+
795
+ /* Tabs */
796
+ .tabs {
797
+ display: flex;
798
+ border-bottom: 1px solid var(--color-border, #3a3a3a);
799
+ }
800
+
801
+ .tab {
802
+ flex: 1;
803
+ background: none;
804
+ border: none;
805
+ padding: 0.5rem;
806
+ color: var(--color-muted-foreground, #888);
807
+ cursor: pointer;
808
+ font-size: 0.7rem;
809
+ text-transform: lowercase;
810
+ border-bottom: 2px solid transparent;
811
+ transition: color 0.15s, border-color 0.15s;
812
+ }
813
+
814
+ .tab:hover {
815
+ color: var(--color-foreground, #d4d4d4);
816
+ }
817
+
818
+ .tab.active {
819
+ color: var(--color-accent, #8bc48b);
820
+ border-bottom-color: var(--color-accent, #8bc48b);
821
+ }
822
+
823
+ /* Tab content */
824
+ .tab-content {
825
+ flex: 1;
826
+ overflow-y: auto;
827
+ padding: 0.75rem;
828
+ }
829
+
830
+ /* Score display */
831
+ .score-display {
832
+ display: flex;
833
+ align-items: center;
834
+ gap: 0.5rem;
835
+ margin-bottom: 0.75rem;
836
+ font-size: 0.75rem;
837
+ }
838
+
839
+ .score-label {
840
+ color: var(--color-muted-foreground, #888);
841
+ }
842
+
843
+ .score-bar {
844
+ font-family: monospace;
845
+ color: var(--color-accent, #8bc48b);
846
+ letter-spacing: -0.05em;
847
+ }
848
+
849
+ .score-num {
850
+ color: var(--color-foreground, #d4d4d4);
851
+ font-weight: 600;
852
+ }
853
+
854
+ /* Suggestions */
855
+ .suggestions {
856
+ display: flex;
857
+ flex-direction: column;
858
+ gap: 0.75rem;
859
+ }
860
+
861
+ .suggestion {
862
+ background: var(--color-surface, #2a2a2a);
863
+ border-radius: 4px;
864
+ padding: 0.5rem;
865
+ border-left: 3px solid var(--color-border, #3a3a3a);
866
+ }
867
+
868
+ .suggestion.severity-error {
869
+ border-left-color: #dc3545;
870
+ }
871
+
872
+ .suggestion.severity-warning {
873
+ border-left-color: #ffc107;
874
+ }
875
+
876
+ .suggestion.severity-style {
877
+ border-left-color: var(--color-accent, #8bc48b);
878
+ }
879
+
880
+ .suggestion-original {
881
+ margin-bottom: 0.25rem;
882
+ }
883
+
884
+ .strike {
885
+ text-decoration: line-through;
886
+ color: var(--color-muted-foreground, #888);
887
+ font-style: italic;
888
+ }
889
+
890
+ .suggestion-fix {
891
+ display: flex;
892
+ align-items: center;
893
+ gap: 0.25rem;
894
+ margin-bottom: 0.25rem;
895
+ }
896
+
897
+ .arrow {
898
+ color: var(--color-accent, #8bc48b);
899
+ }
900
+
901
+ .fix-text {
902
+ color: var(--color-accent, #8bc48b);
903
+ }
904
+
905
+ .suggestion-reason {
906
+ font-size: 0.7rem;
907
+ color: var(--color-muted-foreground, #888);
908
+ margin-bottom: 0.5rem;
909
+ }
910
+
911
+ .apply-btn {
912
+ background: var(--color-primary, #2d5a2d);
913
+ border: none;
914
+ border-radius: 3px;
915
+ padding: 0.25rem 0.5rem;
916
+ color: white;
917
+ cursor: pointer;
918
+ font-size: 0.65rem;
919
+ transition: background-color 0.15s;
920
+ }
921
+
922
+ .apply-btn:hover {
923
+ background: var(--color-accent, #8bc48b);
924
+ }
925
+
926
+ .no-issues {
927
+ color: var(--color-accent, #8bc48b);
928
+ font-style: italic;
929
+ text-align: center;
930
+ padding: 1rem;
931
+ }
932
+
933
+ /* Tone results */
934
+ .tone-analysis {
935
+ color: var(--color-foreground, #d4d4d4);
936
+ margin-bottom: 0.75rem;
937
+ line-height: 1.4;
938
+ }
939
+
940
+ .traits {
941
+ display: flex;
942
+ flex-direction: column;
943
+ gap: 0.5rem;
944
+ margin-bottom: 0.75rem;
945
+ }
946
+
947
+ .trait {
948
+ display: grid;
949
+ grid-template-columns: 80px 1fr 30px;
950
+ align-items: center;
951
+ gap: 0.5rem;
952
+ font-size: 0.7rem;
953
+ }
954
+
955
+ .trait-name {
956
+ color: var(--color-muted-foreground, #888);
957
+ text-transform: lowercase;
958
+ }
959
+
960
+ .trait-bar-container {
961
+ background: var(--color-surface, #2a2a2a);
962
+ height: 6px;
963
+ border-radius: 3px;
964
+ overflow: hidden;
965
+ }
966
+
967
+ .trait-bar {
968
+ height: 100%;
969
+ background: var(--color-accent, #8bc48b);
970
+ border-radius: 3px;
971
+ transition: width 0.3s ease;
972
+ }
973
+
974
+ .trait-score {
975
+ text-align: right;
976
+ color: var(--color-muted-foreground, #888);
977
+ }
978
+
979
+ .tone-suggestions {
980
+ border-top: 1px solid var(--color-border, #3a3a3a);
981
+ padding-top: 0.5rem;
982
+ }
983
+
984
+ .tone-sug {
985
+ color: var(--color-muted-foreground, #888);
986
+ font-size: 0.7rem;
987
+ margin: 0.25rem 0;
988
+ }
989
+
990
+ /* Readability results */
991
+ .readability-stats {
992
+ display: grid;
993
+ grid-template-columns: 1fr 1fr;
994
+ gap: 0.5rem;
995
+ margin-bottom: 0.75rem;
996
+ }
997
+
998
+ .stat {
999
+ background: var(--color-surface, #2a2a2a);
1000
+ padding: 0.5rem;
1001
+ border-radius: 4px;
1002
+ }
1003
+
1004
+ .stat-label {
1005
+ display: block;
1006
+ font-size: 0.65rem;
1007
+ color: var(--color-muted-foreground, #888);
1008
+ text-transform: lowercase;
1009
+ margin-bottom: 0.25rem;
1010
+ }
1011
+
1012
+ .stat-value {
1013
+ font-size: 0.9rem;
1014
+ color: var(--color-foreground, #d4d4d4);
1015
+ font-weight: 500;
1016
+ }
1017
+
1018
+ .readability-suggestions {
1019
+ border-top: 1px solid var(--color-border, #3a3a3a);
1020
+ padding-top: 0.5rem;
1021
+ }
1022
+
1023
+ .read-sug {
1024
+ color: var(--color-muted-foreground, #888);
1025
+ font-size: 0.7rem;
1026
+ margin: 0.25rem 0;
1027
+ }
1028
+
1029
+ /* Usage info */
1030
+ .usage-info {
1031
+ display: flex;
1032
+ justify-content: space-between;
1033
+ align-items: center;
1034
+ padding: 0.5rem 0.75rem;
1035
+ border-top: 1px solid var(--color-border, #3a3a3a);
1036
+ font-size: 0.65rem;
1037
+ color: var(--color-muted-foreground, #888);
1038
+ }
1039
+
1040
+ .clear-btn {
1041
+ background: none;
1042
+ border: none;
1043
+ color: var(--color-muted-foreground, #888);
1044
+ cursor: pointer;
1045
+ text-decoration: underline;
1046
+ font-size: inherit;
1047
+ }
1048
+
1049
+ .clear-btn:hover {
1050
+ color: var(--color-foreground, #d4d4d4);
1051
+ }
1052
+
1053
+ /* Footer */
1054
+ .panel-footer {
1055
+ padding: 0.5rem;
1056
+ text-align: center;
1057
+ border-top: 1px solid var(--color-border, #3a3a3a);
1058
+ background: var(--color-surface, #2a2a2a);
1059
+ }
1060
+
1061
+ .panel-footer p {
1062
+ margin: 0;
1063
+ font-size: 0.6rem;
1064
+ color: var(--color-muted-foreground, #888);
1065
+ font-style: italic;
1066
+ letter-spacing: 0.05em;
1067
+ }
1068
+
1069
+ /* Scrollbar styling */
1070
+ .results::-webkit-scrollbar,
1071
+ .tab-content::-webkit-scrollbar {
1072
+ width: 4px;
1073
+ }
1074
+
1075
+ .results::-webkit-scrollbar-track,
1076
+ .tab-content::-webkit-scrollbar-track {
1077
+ background: transparent;
1078
+ }
1079
+
1080
+ .results::-webkit-scrollbar-thumb,
1081
+ .tab-content::-webkit-scrollbar-thumb {
1082
+ background: var(--color-border, #3a3a3a);
1083
+ border-radius: 2px;
1084
+ }
1085
+
1086
+ /* Responsive */
1087
+ @media (max-width: 768px) {
1088
+ .wisp-panel {
1089
+ width: 100%;
1090
+ max-width: 320px;
1091
+ }
1092
+ }
1093
+ </style>