not-a-spinner 0.1.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.
@@ -0,0 +1,353 @@
1
+ // src/phrases.ts
2
+ var phrases = {
3
+ en: [
4
+ "Contemplating the meaning of null",
5
+ "Asking the rubber duck",
6
+ "Reticulating splines",
7
+ "Consulting the ancient scrolls",
8
+ "Negotiating with the API gods",
9
+ "Untangling spaghetti code",
10
+ "Feeding the hamsters that power the server",
11
+ "Converting caffeine to code",
12
+ "Aligning the cosmic bits",
13
+ "Performing mass speculation",
14
+ "Warming up the neural pathways",
15
+ "Bribing the cache fairy",
16
+ "Dividing by almost zero",
17
+ "Searching for the missing semicolon",
18
+ "Teaching electrons to think",
19
+ "Compiling excuses",
20
+ "Reversing entropy locally",
21
+ "Summoning the async wizard",
22
+ "Polishing the pixels",
23
+ "Calculating the meaning of life",
24
+ "Downloading more RAM",
25
+ "Defragmenting the thought process",
26
+ "Poking the language model",
27
+ "Waiting for the stars to align",
28
+ "Generating plausible nonsense",
29
+ "Consulting the magic 8-ball API",
30
+ "Herding digital cats",
31
+ "Overthinking at machine speed",
32
+ "Pretending to be busy",
33
+ "Running on vibes and vectors"
34
+ ],
35
+ zh: [
36
+ "\u8BA9AI\u518D\u60F3\u60F3",
37
+ "\u6B63\u5728\u6478\u9C7C\u5F0F\u601D\u8003",
38
+ "\u6B63\u5728\u7FFB\u9605\u7956\u4F20\u4EE3\u7801",
39
+ "\u6B63\u5728\u548Cbug\u8C08\u5224",
40
+ "\u75AF\u72C2\u6697\u793A\u670D\u52A1\u5668\u52A0\u901F",
41
+ "\u6B63\u5728\u8585\u7F8A\u6BDB\u5F0F\u8BA1\u7B97",
42
+ "\u5047\u88C5\u5F88\u5FD9\u7684\u6837\u5B50",
43
+ "\u6B63\u5728\u91CF\u5B50\u7EA0\u7F20\u4E2D",
44
+ "\u8BA9\u5B50\u5F39\u98DE\u4E00\u4F1A\u513F",
45
+ "\u6B63\u5728\u52A0\u8F7D\u4EBA\u751F\u7684\u610F\u4E49",
46
+ "\u6B63\u5728\u6478\u7D22\u5B87\u5B99\u7684\u89C4\u5F8B",
47
+ "\u8DDF\u7F13\u5B58\u7CBE\u7075\u8BA8\u4EF7\u8FD8\u4EF7",
48
+ "\u6B63\u5728\u70BC\u4E39",
49
+ "\u8BF7\u6C42\u795E\u7ECF\u7F51\u7EDC\u5F00\u4E2A\u540E\u95E8",
50
+ "\u6B63\u5728\u6D88\u5316\u4F60\u7684\u8BF7\u6C42",
51
+ "\u6316\u77FF\u4E2D\u8BF7\u7A0D\u5019",
52
+ "\u6B63\u5728\u7FFB\u8BD1\u673A\u5668\u8BED\u8A00",
53
+ "\u5927\u8111\u6B63\u5728\u8D85\u9891\u8FD0\u884C",
54
+ "\u6B63\u5728\u627E\u7075\u611F",
55
+ "\u5411\u670D\u52A1\u5668\u53D1\u9001\u8D3F\u8D42",
56
+ "\u6B63\u5728\u8FDB\u884C\u610F\u5FF5\u52A0\u901F",
57
+ "\u8BA9\u4EE3\u7801\u5148\u8DD1\u4E00\u4F1A\u513F",
58
+ "\u6B63\u5728\u65BD\u5C55\u6570\u636E\u9B54\u6CD5",
59
+ "\u5077\u5077\u67E5\u770B\u7B54\u6848\u4E2D",
60
+ "\u6B63\u5728\u89E3\u5F00\u859B\u5B9A\u8C14\u7684Bug",
61
+ "\u62FC\u547D\u601D\u8003\u4E0D\u8981\u50AC",
62
+ "\u6B63\u5728\u548CAI\u540C\u4E8B\u5F00\u4F1A",
63
+ "\u7A7F\u8D8A\u6570\u636E\u7684\u661F\u8FB0\u5927\u6D77",
64
+ "\u6B63\u5728\u8FDB\u884C\u7075\u9B42\u62F7\u95EE",
65
+ "\u7B49\u7B49\u9A6C\u4E0A\u5C31\u597D"
66
+ ],
67
+ ja: [
68
+ "AI\u304C\u60A9\u3093\u3067\u3044\u307E\u3059",
69
+ "\u30B9\u30D1\u30B2\u30C3\u30C6\u30A3\u30B3\u30FC\u30C9\u3092\u89E3\u8AAD\u4E2D",
70
+ "\u30B4\u30E0\u306E\u30A2\u30D2\u30EB\u306B\u76F8\u8AC7\u4E2D",
71
+ "\u30B5\u30FC\u30D0\u30FC\u306E\u30CF\u30E0\u30B9\u30BF\u30FC\u3092\u8D77\u52D5\u4E2D",
72
+ "\u30AB\u30D5\u30A7\u30A4\u30F3\u3092\u30B3\u30FC\u30C9\u306B\u5909\u63DB\u4E2D",
73
+ "\u5B87\u5B99\u306E\u30D3\u30C3\u30C8\u3092\u6574\u5217\u4E2D",
74
+ "\u30BB\u30DF\u30B3\u30ED\u30F3\u3092\u63A2\u7D22\u4E2D",
75
+ "\u96FB\u5B50\u306B\u8003\u3048\u308B\u3053\u3068\u3092\u6559\u80B2\u4E2D",
76
+ "\u8A00\u3044\u8A33\u3092\u30B3\u30F3\u30D1\u30A4\u30EB\u4E2D",
77
+ "\u975E\u540C\u671F\u306E\u9B54\u6CD5\u4F7F\u3044\u3092\u53EC\u559A\u4E2D",
78
+ "\u30D4\u30AF\u30BB\u30EB\u3092\u78E8\u304D\u4E2D",
79
+ "\u4EBA\u751F\u306E\u610F\u5473\u3092\u8A08\u7B97\u4E2D",
80
+ "RAM\u3092\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9\u4E2D",
81
+ "\u601D\u8003\u30D7\u30ED\u30BB\u30B9\u3092\u30C7\u30D5\u30E9\u30B0\u4E2D",
82
+ "\u30DE\u30B7\u30F3\u901F\u5EA6\u3067\u8003\u3048\u3059\u304E\u4E2D",
83
+ "\u91CF\u5B50\u306E\u3082\u3064\u308C\u3092\u89E3\u6D88\u4E2D",
84
+ "API\u306E\u795E\u69D8\u3068\u4EA4\u6E09\u4E2D",
85
+ "\u30C7\u30B8\u30BF\u30EB\u732B\u3092\u8FFD\u3044\u304B\u3051\u4E2D",
86
+ "\u30CB\u30E5\u30FC\u30E9\u30EB\u30D1\u30B9\u3092\u6696\u3081\u4E2D",
87
+ "\u30C7\u30FC\u30BF\u306E\u6D77\u3092\u6CF3\u3044\u3067\u3044\u307E\u3059",
88
+ "\u30D0\u30B0\u3068\u548C\u89E3\u4EA4\u6E09\u4E2D",
89
+ "\u5FD9\u3057\u3044\u3075\u308A\u3092\u3057\u3066\u3044\u307E\u3059",
90
+ "\u30A2\u30EB\u30B4\u30EA\u30BA\u30E0\u304C\u7791\u60F3\u4E2D",
91
+ "\u9B54\u6CD5\u306E8\u30DC\u30FC\u30EB\u306B\u805E\u3044\u3066\u307E\u3059",
92
+ "\u3082\u3046\u3061\u3087\u3063\u3068\u5F85\u3063\u3066\u306D",
93
+ "\u5168\u529B\u3067\u51E6\u7406\u3057\u3066\u3044\u307E\u3059",
94
+ "\u7B54\u3048\u306E\u8FD1\u304F\u306B\u3044\u307E\u3059",
95
+ "\u5929\u624D\u7684\u306A\u3072\u3089\u3081\u304D\u3092\u5F85\u6A5F\u4E2D",
96
+ "\u30B3\u30FC\u30D2\u30FC\u30D6\u30EC\u30A4\u30AF\u4E2D\u3067\u3059",
97
+ "\u9811\u5F35\u3063\u3066\u8003\u3048\u4E2D"
98
+ ],
99
+ es: [
100
+ "Consultando al or\xE1culo digital",
101
+ "Pensando intensamente",
102
+ "Negociando con los dioses del API",
103
+ "Desenredando c\xF3digo espagueti",
104
+ "Alimentando los h\xE1msters del servidor",
105
+ "Convirtiendo cafe\xEDna en c\xF3digo",
106
+ "Alineando los bits c\xF3smicos",
107
+ "Buscando el punto y coma perdido",
108
+ "Ense\xF1ando a pensar a los electrones",
109
+ "Compilando excusas creativas",
110
+ "Invocando al mago as\xEDncrono",
111
+ "Puliendo los p\xEDxeles",
112
+ "Calculando el sentido de la vida",
113
+ "Descargando m\xE1s RAM",
114
+ "Preguntando al pato de goma",
115
+ "Pastoreando gatos digitales",
116
+ "Sobrepensando a velocidad m\xE1quina",
117
+ "Fingiendo estar ocupado",
118
+ "Sobornando al hada del cach\xE9",
119
+ "Dividiendo por casi cero",
120
+ "Generando tonter\xEDas plausibles",
121
+ "Calentando las rutas neuronales",
122
+ "Esperando que se alineen las estrellas",
123
+ "Haciendo magia con los datos",
124
+ "Meditando algor\xEDtmicamente",
125
+ "Revolviendo la sopa cu\xE1ntica",
126
+ "Susurrando al servidor",
127
+ "Recalibrando la realidad",
128
+ "Casi casi ya casi",
129
+ "Un momento de genialidad en curso"
130
+ ],
131
+ fr: [
132
+ "Consultation de l'oracle num\xE9rique",
133
+ "R\xE9flexion intense en cours",
134
+ "N\xE9gociation avec les dieux de l'API",
135
+ "D\xE9m\xEAlage de code spaghetti",
136
+ "Conversion de caf\xE9ine en code",
137
+ "Alignement des bits cosmiques",
138
+ "Recherche du point-virgule perdu",
139
+ "Compilation d'excuses cr\xE9atives",
140
+ "Invocation du sorcier asynchrone",
141
+ "Polissage des pixels",
142
+ "Calcul du sens de la vie",
143
+ "T\xE9l\xE9chargement de RAM suppl\xE9mentaire",
144
+ "Consultation du canard en caoutchouc",
145
+ "Rassemblement de chats num\xE9riques",
146
+ "Surr\xE9flexion \xE0 vitesse machine",
147
+ "Simulation d'activit\xE9 intense",
148
+ "Corruption de la f\xE9e du cache",
149
+ "R\xE9chauffement des voies neuronales",
150
+ "G\xE9n\xE9ration de b\xEAtises plausibles",
151
+ "M\xE9ditation algorithmique",
152
+ "Persuasion quantique en cours",
153
+ "Interrogation de la boule magique",
154
+ "D\xE9fragmentation de la pens\xE9e",
155
+ "Dressage des \xE9lectrons pensants",
156
+ "Attente de l'alignement stellaire",
157
+ "Magie des donn\xE9es en cours",
158
+ "Murmures au serveur",
159
+ "Recalibrage de la r\xE9alit\xE9",
160
+ "Presque presque bient\xF4t",
161
+ "Un \xE9clair de g\xE9nie arrive"
162
+ ],
163
+ de: [
164
+ "Algorithmus gr\xFCbelt nach",
165
+ "Daten werden meditiert",
166
+ "Verhandlung mit den API-G\xF6ttern",
167
+ "Spaghetti-Code wird entwirrt",
168
+ "Server-Hamster werden gef\xFCttert",
169
+ "Koffein wird in Code umgewandelt",
170
+ "Kosmische Bits werden ausgerichtet",
171
+ "Das fehlende Semikolon wird gesucht",
172
+ "Elektronen lernen das Denken",
173
+ "Ausreden werden kompiliert",
174
+ "Der asynchrone Zauberer wird beschworen",
175
+ "Pixel werden poliert",
176
+ "Der Sinn des Lebens wird berechnet",
177
+ "Mehr RAM wird heruntergeladen",
178
+ "Die Gummiente wird befragt",
179
+ "Digitale Katzen werden geh\xFCtet",
180
+ "Maschinengeschwindigkeit-\xDCberdenken",
181
+ "Besch\xE4ftigt-Sein wird simuliert",
182
+ "Die Cache-Fee wird bestochen",
183
+ "Beinahe durch Null geteilt",
184
+ "Plausibeler Unsinn wird generiert",
185
+ "Neuronale Pfade werden aufgew\xE4rmt",
186
+ "Quantensuppe wird umger\xFChrt",
187
+ "Sternenausrichtung wird abgewartet",
188
+ "Datenmagie wird ausgef\xFChrt",
189
+ "Dem Server wird zugefl\xFCstert",
190
+ "Realit\xE4t wird neu kalibriert",
191
+ "Gleich gleich fast fertig",
192
+ "Ein Geistesblitz ist unterwegs",
193
+ "Intensives Nachdenken l\xE4uft"
194
+ ],
195
+ ko: [
196
+ "AI\uAC00 \uACE0\uBBFC \uC911",
197
+ "\uB370\uC774\uD130\uB97C \uBA85\uC0C1 \uC911",
198
+ "\uACE0\uBB34 \uC624\uB9AC\uC5D0\uAC8C \uC0C1\uB2F4 \uC911",
199
+ "\uC11C\uBC84 \uD584\uC2A4\uD130\uC5D0\uAC8C \uBC25 \uC8FC\uB294 \uC911",
200
+ "\uCE74\uD398\uC778\uC744 \uCF54\uB4DC\uB85C \uBCC0\uD658 \uC911",
201
+ "\uC6B0\uC8FC \uBE44\uD2B8\uB97C \uC815\uB82C \uC911",
202
+ "\uC783\uC5B4\uBC84\uB9B0 \uC138\uBBF8\uCF5C\uB860 \uCC3E\uB294 \uC911",
203
+ "\uC804\uC790\uC5D0\uAC8C \uC0DD\uAC01\uD558\uB294 \uBC95 \uAC00\uB974\uCE58\uB294 \uC911",
204
+ "\uBCC0\uBA85\uC744 \uCEF4\uD30C\uC77C \uC911",
205
+ "\uBE44\uB3D9\uAE30 \uB9C8\uBC95\uC0AC \uC18C\uD658 \uC911",
206
+ "\uD53D\uC140\uC744 \uB2E6\uB294 \uC911",
207
+ "\uC778\uC0DD\uC758 \uC758\uBBF8 \uACC4\uC0B0 \uC911",
208
+ "RAM \uB2E4\uC6B4\uB85C\uB4DC \uC911",
209
+ "\uB514\uC9C0\uD138 \uACE0\uC591\uC774 \uBAB0\uC774 \uC911",
210
+ "\uBA38\uC2E0 \uC18D\uB3C4\uB85C \uACFC\uB3C4\uD558\uAC8C \uC0DD\uAC01 \uC911",
211
+ "\uBC14\uC05C \uCC99\uD558\uB294 \uC911",
212
+ "\uCE90\uC2DC \uC694\uC815\uC5D0\uAC8C \uB1CC\uBB3C \uC8FC\uB294 \uC911",
213
+ "\uAC70\uC758 0\uC73C\uB85C \uB098\uB204\uB294 \uC911",
214
+ "\uADF8\uB7F4\uB4EF\uD55C \uD5DB\uC18C\uB9AC \uC0DD\uC131 \uC911",
215
+ "\uC2E0\uACBD \uACBD\uB85C \uC6CC\uBC0D\uC5C5 \uC911",
216
+ "\uC591\uC790 \uC218\uD504 \uC813\uB294 \uC911",
217
+ "\uBCC4\uC774 \uC815\uB82C\uB418\uAE38 \uAE30\uB2E4\uB9AC\uB294 \uC911",
218
+ "\uB370\uC774\uD130 \uB9C8\uBC95 \uC2DC\uC804 \uC911",
219
+ "\uC11C\uBC84\uC5D0\uAC8C \uC18D\uC0AD\uC774\uB294 \uC911",
220
+ "\uD604\uC2E4 \uC7AC\uBCF4\uC815 \uC911",
221
+ "\uAC70\uC758 \uAC70\uC758 \uB2E4 \uB410\uC5B4\uC694",
222
+ "\uCC9C\uC7AC\uC801 \uC601\uAC10 \uB300\uAE30 \uC911",
223
+ "\uC2A4\uD30C\uAC8C\uD2F0 \uCF54\uB4DC \uD574\uB3C5 \uC911",
224
+ "API \uC2E0\uACFC \uD611\uC0C1 \uC911",
225
+ "\uC5F4\uC2EC\uD788 \uC0DD\uAC01\uD558\uB294 \uC911"
226
+ ]
227
+ };
228
+
229
+ // src/server.ts
230
+ var DEFAULT_SYSTEM_PROMPT = `You are a creative writer that generates short, funny, witty "loading" or "thinking" messages for a software application. These messages replace boring spinners with entertaining text.
231
+
232
+ Rules:
233
+ - Output ONLY the phrase, nothing else
234
+ - Keep it under 50 characters
235
+ - Be creative, nerdy, and humorous
236
+ - Match the tone of these examples: "Reticulating splines", "Asking the rubber duck", "Downloading more RAM"
237
+ - Do not repeat previous phrases
238
+ - Do not use quotes around the phrase`;
239
+ function getLocalizedPrompt(locale) {
240
+ const localeInstructions = {
241
+ en: "Write the phrase in English.",
242
+ zh: "Write the phrase in Chinese (Simplified). Match the tone of: \u6B63\u5728\u6478\u9C7C\u5F0F\u601D\u8003, \u8BA9AI\u518D\u60F3\u60F3, \u6B63\u5728\u70BC\u4E39",
243
+ ja: "Write the phrase in Japanese. Match the tone of: AI\u304C\u60A9\u3093\u3067\u3044\u307E\u3059, \u30D4\u30AF\u30BB\u30EB\u3092\u78E8\u304D\u4E2D, \u3082\u3046\u3061\u3087\u3063\u3068\u5F85\u3063\u3066\u306D",
244
+ es: "Write the phrase in Spanish. Match the tone of: Consultando al or\xE1culo digital, Fingiendo estar ocupado",
245
+ fr: "Write the phrase in French. Match the tone of: M\xE9ditation algorithmique, Simulation d'activit\xE9 intense",
246
+ de: "Write the phrase in German. Match the tone of: Algorithmus gr\xFCbelt nach, Mehr RAM wird heruntergeladen",
247
+ ko: "Write the phrase in Korean. Match the tone of: AI\uAC00 \uACE0\uBBFC \uC911, \uBC14\uC05C \uCC99\uD558\uB294 \uC911, \uC5F4\uC2EC\uD788 \uC0DD\uAC01\uD558\uB294 \uC911"
248
+ };
249
+ return localeInstructions[locale] || localeInstructions.en;
250
+ }
251
+ async function fetchFromOpenAI(apiKey, model, systemPrompt, userPrompt) {
252
+ const res = await fetch("https://api.openai.com/v1/chat/completions", {
253
+ method: "POST",
254
+ headers: {
255
+ "Content-Type": "application/json",
256
+ Authorization: `Bearer ${apiKey}`
257
+ },
258
+ body: JSON.stringify({
259
+ model,
260
+ messages: [
261
+ { role: "system", content: systemPrompt },
262
+ { role: "user", content: userPrompt }
263
+ ],
264
+ max_tokens: 60,
265
+ temperature: 1
266
+ })
267
+ });
268
+ if (!res.ok) {
269
+ throw new Error(`OpenAI API error: ${res.status}`);
270
+ }
271
+ const data = await res.json();
272
+ return data.choices?.[0]?.message?.content?.trim() || "";
273
+ }
274
+ async function fetchFromAnthropic(apiKey, model, systemPrompt, userPrompt) {
275
+ const res = await fetch("https://api.anthropic.com/v1/messages", {
276
+ method: "POST",
277
+ headers: {
278
+ "Content-Type": "application/json",
279
+ "x-api-key": apiKey,
280
+ "anthropic-version": "2023-06-01"
281
+ },
282
+ body: JSON.stringify({
283
+ model,
284
+ system: systemPrompt,
285
+ messages: [{ role: "user", content: userPrompt }],
286
+ max_tokens: 60,
287
+ temperature: 1
288
+ })
289
+ });
290
+ if (!res.ok) {
291
+ throw new Error(`Anthropic API error: ${res.status}`);
292
+ }
293
+ const data = await res.json();
294
+ return data.content?.[0]?.text?.trim() || "";
295
+ }
296
+ function createThinkingHandler(options) {
297
+ const {
298
+ provider,
299
+ apiKey,
300
+ model,
301
+ locale: defaultLocale = "en",
302
+ systemPrompt = DEFAULT_SYSTEM_PROMPT
303
+ } = options;
304
+ const resolvedModel = model || (provider === "openai" ? "gpt-4o-mini" : "claude-haiku-4-5-20241022");
305
+ return async (req) => {
306
+ try {
307
+ let requestLocale = defaultLocale;
308
+ try {
309
+ const body = await req.json();
310
+ if (body.locale && phrases[body.locale]) {
311
+ requestLocale = body.locale;
312
+ }
313
+ } catch {
314
+ }
315
+ const localizedPrompt = getLocalizedPrompt(requestLocale);
316
+ const userPrompt = `Generate one funny, creative loading/thinking message. ${localizedPrompt}`;
317
+ let phrase;
318
+ if (provider === "openai") {
319
+ phrase = await fetchFromOpenAI(
320
+ apiKey,
321
+ resolvedModel,
322
+ systemPrompt,
323
+ userPrompt
324
+ );
325
+ } else {
326
+ phrase = await fetchFromAnthropic(
327
+ apiKey,
328
+ resolvedModel,
329
+ systemPrompt,
330
+ userPrompt
331
+ );
332
+ }
333
+ if (!phrase) {
334
+ const pool = phrases[requestLocale] || phrases.en;
335
+ phrase = pool[Math.floor(Math.random() * pool.length)];
336
+ }
337
+ return new Response(JSON.stringify({ phrase }), {
338
+ status: 200,
339
+ headers: { "Content-Type": "application/json" }
340
+ });
341
+ } catch (error) {
342
+ const pool = phrases[defaultLocale] || phrases.en;
343
+ const fallbackPhrase = pool[Math.floor(Math.random() * pool.length)];
344
+ return new Response(JSON.stringify({ phrase: fallbackPhrase }), {
345
+ status: 200,
346
+ headers: { "Content-Type": "application/json" }
347
+ });
348
+ }
349
+ };
350
+ }
351
+ export {
352
+ createThinkingHandler
353
+ };
@@ -0,0 +1,98 @@
1
+ /* not-a-spinner — Base */
2
+ .nas-base {
3
+ display: inline-flex;
4
+ align-items: center;
5
+ position: relative;
6
+ font-weight: 500;
7
+ color: var(--nas-color, #6b7280);
8
+ }
9
+
10
+ .nas-sm { font-size: 0.75rem; line-height: 1rem; }
11
+ .nas-default { font-size: 0.875rem; line-height: 1.25rem; }
12
+ .nas-lg { font-size: 1rem; line-height: 1.5rem; }
13
+
14
+ /* Text wrapper */
15
+ .nas-text-wrapper {
16
+ position: relative;
17
+ overflow: hidden;
18
+ display: inline-block;
19
+ }
20
+
21
+ /* Fade */
22
+ .nas-fade-enter { animation: nas-fade-in 300ms ease-in forwards; }
23
+ .nas-fade-exit { animation: nas-fade-out 300ms ease-out forwards; }
24
+
25
+ @keyframes nas-fade-in {
26
+ from { opacity: 0; }
27
+ to { opacity: 1; }
28
+ }
29
+
30
+ @keyframes nas-fade-out {
31
+ from { opacity: 1; }
32
+ to { opacity: 0; }
33
+ }
34
+
35
+ /* Slide */
36
+ .nas-slide-enter { animation: nas-slide-in-up 300ms ease-out forwards; }
37
+ .nas-slide-exit { animation: nas-slide-out-up 300ms ease-in forwards; }
38
+
39
+ @keyframes nas-slide-in-up {
40
+ from { transform: translateY(100%); opacity: 0; }
41
+ to { transform: translateY(0); opacity: 1; }
42
+ }
43
+
44
+ @keyframes nas-slide-out-up {
45
+ from { transform: translateY(0); opacity: 1; }
46
+ to { transform: translateY(-100%); opacity: 0; }
47
+ }
48
+
49
+ /* Blur */
50
+ .nas-blur-enter { animation: nas-blur-in 400ms ease-out forwards; }
51
+ .nas-blur-exit { animation: nas-blur-out 400ms ease-in forwards; }
52
+
53
+ @keyframes nas-blur-in {
54
+ from { filter: blur(8px); opacity: 0; }
55
+ to { filter: blur(0); opacity: 1; }
56
+ }
57
+
58
+ @keyframes nas-blur-out {
59
+ from { filter: blur(0); opacity: 1; }
60
+ to { filter: blur(8px); opacity: 0; }
61
+ }
62
+
63
+ /* Typewriter cursor */
64
+ .nas-cursor {
65
+ display: inline-block;
66
+ width: 2px;
67
+ height: 1em;
68
+ margin-left: 1px;
69
+ vertical-align: text-bottom;
70
+ background-color: currentColor;
71
+ animation: nas-blink 1s step-end infinite;
72
+ }
73
+
74
+ @keyframes nas-blink {
75
+ 0%, 100% { opacity: 0; }
76
+ 50% { opacity: 1; }
77
+ }
78
+
79
+ /* Dots */
80
+ .nas-dots {
81
+ margin-left: 2px;
82
+ animation: nas-pulse 1.5s ease-in-out infinite;
83
+ }
84
+
85
+ @keyframes nas-pulse {
86
+ 0%, 100% { opacity: 0.4; }
87
+ 50% { opacity: 1; }
88
+ }
89
+
90
+ /* Reduced motion */
91
+ @media (prefers-reduced-motion: reduce) {
92
+ .nas-base,
93
+ .nas-base * {
94
+ animation-duration: 0.01ms !important;
95
+ animation-iteration-count: 1 !important;
96
+ transition-duration: 0.01ms !important;
97
+ }
98
+ }
package/package.json ADDED
@@ -0,0 +1,84 @@
1
+ {
2
+ "name": "not-a-spinner",
3
+ "version": "0.1.0",
4
+ "description": "Because modern AI doesn't spin, it thinks. Replace loading spinners with AI-style rotating thinking phrases.",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.mjs"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ },
20
+ "./styles.css": "./dist/styles.css",
21
+ "./server": {
22
+ "import": {
23
+ "types": "./dist/server.d.ts",
24
+ "default": "./dist/server.mjs"
25
+ },
26
+ "require": {
27
+ "types": "./dist/server.d.cts",
28
+ "default": "./dist/server.cjs"
29
+ }
30
+ }
31
+ },
32
+ "files": [
33
+ "dist"
34
+ ],
35
+ "sideEffects": [
36
+ "*.css"
37
+ ],
38
+ "scripts": {
39
+ "build": "tsup && cp src/styles.css dist/styles.css",
40
+ "dev": "tsup --watch",
41
+ "prepublishOnly": "npm run build",
42
+ "registry:build": "shadcn build"
43
+ },
44
+ "keywords": [
45
+ "react",
46
+ "loading",
47
+ "spinner",
48
+ "thinking",
49
+ "ai",
50
+ "component",
51
+ "animation",
52
+ "tailwind"
53
+ ],
54
+ "license": "MIT",
55
+ "peerDependencies": {
56
+ "react": ">=18",
57
+ "react-dom": ">=18"
58
+ },
59
+ "peerDependenciesMeta": {
60
+ "openai": {
61
+ "optional": true
62
+ },
63
+ "@anthropic-ai/sdk": {
64
+ "optional": true
65
+ }
66
+ },
67
+ "dependencies": {
68
+ "clsx": "^2.1.1",
69
+ "tailwind-merge": "^2.6.0",
70
+ "class-variance-authority": "^0.7.1"
71
+ },
72
+ "devDependencies": {
73
+ "tsup": "^8.3.0",
74
+ "typescript": "^5.7.0",
75
+ "react": "^19.0.0",
76
+ "react-dom": "^19.0.0",
77
+ "@types/react": "^19.0.0",
78
+ "@types/react-dom": "^19.0.0",
79
+ "shadcn": "^2.9.0"
80
+ },
81
+ "workspaces": [
82
+ "playground"
83
+ ]
84
+ }