@jjlmoya/utils-pets 1.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.
Files changed (41) hide show
  1. package/package.json +60 -0
  2. package/src/category/i18n/en.ts +48 -0
  3. package/src/category/i18n/es.ts +48 -0
  4. package/src/category/i18n/fr.ts +48 -0
  5. package/src/category/index.ts +13 -0
  6. package/src/category/seo.astro +14 -0
  7. package/src/components/PreviewNavSidebar.astro +115 -0
  8. package/src/components/PreviewToolbar.astro +142 -0
  9. package/src/data.ts +15 -0
  10. package/src/env.d.ts +4 -0
  11. package/src/index.ts +20 -0
  12. package/src/layouts/PreviewLayout.astro +116 -0
  13. package/src/pages/[locale]/[slug].astro +148 -0
  14. package/src/pages/[locale].astro +270 -0
  15. package/src/pages/index.astro +3 -0
  16. package/src/tests/faq_count.test.ts +23 -0
  17. package/src/tests/mocks/astro_mock.js +1 -0
  18. package/src/tests/seo_length.test.ts +54 -0
  19. package/src/tests/tool_validation.test.ts +139 -0
  20. package/src/tool/petAge/bibliography.astro +14 -0
  21. package/src/tool/petAge/component.astro +267 -0
  22. package/src/tool/petAge/handlers.ts +142 -0
  23. package/src/tool/petAge/i18n/en.ts +160 -0
  24. package/src/tool/petAge/i18n/es.ts +160 -0
  25. package/src/tool/petAge/i18n/fr.ts +160 -0
  26. package/src/tool/petAge/index.ts +64 -0
  27. package/src/tool/petAge/logic.ts +59 -0
  28. package/src/tool/petAge/seo.astro +61 -0
  29. package/src/tool/petAge/style.css +649 -0
  30. package/src/tool/petRation/bibliography.astro +14 -0
  31. package/src/tool/petRation/component.astro +181 -0
  32. package/src/tool/petRation/handlers.ts +139 -0
  33. package/src/tool/petRation/i18n/en.ts +161 -0
  34. package/src/tool/petRation/i18n/es.ts +161 -0
  35. package/src/tool/petRation/i18n/fr.ts +161 -0
  36. package/src/tool/petRation/index.ts +63 -0
  37. package/src/tool/petRation/logic.ts +44 -0
  38. package/src/tool/petRation/seo.astro +61 -0
  39. package/src/tool/petRation/style.css +427 -0
  40. package/src/tools.ts +13 -0
  41. package/src/types.ts +71 -0
@@ -0,0 +1,649 @@
1
+ .hidden {
2
+ display: none;
3
+ }
4
+
5
+ /* ── Container ─────────────────────────────────── */
6
+ .pet-age-calculator {
7
+ position: relative;
8
+ max-width: 650px;
9
+ margin: 2rem auto;
10
+ background: var(--color-surface, #fff);
11
+ border-radius: 2rem;
12
+ padding: 2.5rem;
13
+ box-shadow: 0 20px 50px -15px rgba(0, 0, 0, 0.08);
14
+ color: var(--color-text, #334155);
15
+ border: 1px solid var(--color-border, #f1f5f9);
16
+ min-height: 400px;
17
+ }
18
+
19
+ .theme-dark .pet-age-calculator {
20
+ background: var(--color-surface-dark, #1e293b);
21
+ color: var(--color-text-dark, #e2e8f0);
22
+ border-color: var(--color-border-dark, #334155);
23
+ box-shadow: 0 20px 50px -15px rgba(0, 0, 0, 0.3);
24
+ }
25
+
26
+ /* ── Loader ─────────────────────────────────────── */
27
+ .age-loader {
28
+ position: absolute;
29
+ top: 0; left: 0;
30
+ width: 100%; height: 100%;
31
+ background: var(--color-surface, #fff);
32
+ display: flex;
33
+ align-items: center;
34
+ justify-content: center;
35
+ border-radius: 2rem;
36
+ z-index: 50;
37
+ transition: opacity 0.4s cubic-bezier(0.4, 0, 0.2, 1), visibility 0.4s;
38
+ }
39
+
40
+ .theme-dark .age-loader {
41
+ background: var(--color-surface-dark, #1e293b);
42
+ }
43
+
44
+ .age-loader.hidden {
45
+ opacity: 0;
46
+ visibility: hidden;
47
+ pointer-events: none;
48
+ }
49
+
50
+ .age-loader-spinner {
51
+ width: 3rem;
52
+ height: 3rem;
53
+ color: var(--color-accent, #f59e0b);
54
+ animation: age-spin 2s infinite linear;
55
+ }
56
+
57
+ @keyframes age-spin {
58
+ from { transform: rotate(0deg); }
59
+ to { transform: rotate(360deg); }
60
+ }
61
+
62
+ /* ── Inner layout ────────────────────────────────── */
63
+ .age-container {
64
+ display: flex;
65
+ flex-direction: column;
66
+ gap: 3rem;
67
+ animation: age-fade-up 0.6s cubic-bezier(0.4, 0, 0.2, 1);
68
+ }
69
+
70
+ @keyframes age-fade-up {
71
+ from {
72
+ opacity: 0;
73
+ transform: translateY(10px);
74
+ }
75
+ to {
76
+ opacity: 1;
77
+ transform: translateY(0);
78
+ }
79
+ }
80
+
81
+ .age-section {
82
+ border-bottom: 1px dashed var(--color-border-dashed, #e2e8f0);
83
+ padding-bottom: 2rem;
84
+ }
85
+
86
+ .age-section:last-of-type {
87
+ border-bottom: none;
88
+ }
89
+
90
+ .theme-dark .age-section {
91
+ border-bottom-color: var(--color-border-dark, #334155);
92
+ }
93
+
94
+ .age-section-title {
95
+ font-size: 1.1rem;
96
+ font-weight: 950;
97
+ color: var(--color-heading, #0f172a);
98
+ margin-bottom: 2rem;
99
+ display: flex;
100
+ align-items: center;
101
+ gap: 0.75rem;
102
+ text-transform: uppercase;
103
+ letter-spacing: 0.05em;
104
+ }
105
+
106
+ .theme-dark .age-section-title {
107
+ color: var(--color-heading-dark, #f8fafc);
108
+ }
109
+
110
+ .age-section-title svg {
111
+ width: 1.25rem;
112
+ height: 1.25rem;
113
+ }
114
+
115
+ /* ── Input items ─────────────────────────────────── */
116
+ .age-input-item {
117
+ margin-bottom: 2rem;
118
+ }
119
+
120
+ .age-input-item:last-child {
121
+ margin-bottom: 0;
122
+ }
123
+
124
+ .age-label {
125
+ display: block;
126
+ font-size: 0.75rem;
127
+ font-weight: 800;
128
+ text-transform: uppercase;
129
+ color: var(--color-label, #94a3b8);
130
+ margin-bottom: 1rem;
131
+ letter-spacing: 0.1em;
132
+ }
133
+
134
+ /* Text input */
135
+ .age-text-input {
136
+ width: 100%;
137
+ padding: 0.875rem 1.25rem;
138
+ background: var(--color-btn-bg, #f8fafc);
139
+ border: 2px solid var(--color-btn-border, #e2e8f0);
140
+ border-radius: 1rem;
141
+ font-size: 1rem;
142
+ font-weight: 700;
143
+ color: var(--color-text, #334155);
144
+ outline: none;
145
+ transition: border-color 0.2s, box-shadow 0.2s;
146
+ box-sizing: border-box;
147
+ }
148
+
149
+ .age-text-input::placeholder {
150
+ color: var(--color-label, #94a3b8);
151
+ font-weight: 500;
152
+ }
153
+
154
+ .age-text-input:focus {
155
+ border-color: var(--color-accent, #f59e0b);
156
+ box-shadow: 0 0 0 4px rgba(245, 158, 11, 0.1);
157
+ }
158
+
159
+ .theme-dark .age-text-input {
160
+ background: var(--color-btn-bg-dark, #0f172a);
161
+ border-color: var(--color-btn-border-dark, #334155);
162
+ color: var(--color-text-dark, #e2e8f0);
163
+ }
164
+
165
+ /* ── Species buttons ─────────────────────────────── */
166
+ .age-species-selector {
167
+ display: flex;
168
+ gap: 1rem;
169
+ }
170
+
171
+ .pet-type-btn {
172
+ flex: 1;
173
+ display: flex;
174
+ align-items: center;
175
+ justify-content: center;
176
+ gap: 0.5rem;
177
+ padding: 1.25rem 0.75rem;
178
+ background: var(--color-btn-bg, #f8fafc);
179
+ border: 2px solid var(--color-btn-border, #e2e8f0);
180
+ border-radius: 1rem;
181
+ cursor: pointer;
182
+ font-weight: 800;
183
+ font-size: 1rem;
184
+ color: var(--color-text, #334155);
185
+ transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);
186
+ }
187
+
188
+ .theme-dark .pet-type-btn {
189
+ background: var(--color-btn-bg-dark, #0f172a);
190
+ border-color: var(--color-btn-border-dark, #334155);
191
+ color: var(--color-text-dark, #e2e8f0);
192
+ }
193
+
194
+ .pet-type-btn.active {
195
+ background: var(--color-surface, #fff);
196
+ border-color: var(--color-accent, #f59e0b);
197
+ color: var(--color-accent, #f59e0b);
198
+ box-shadow: 0 4px 15px rgba(245, 158, 11, 0.15);
199
+ transform: translateY(-2px);
200
+ }
201
+
202
+ .theme-dark .pet-type-btn.active {
203
+ background: var(--color-surface-elevated-dark, #1e293b);
204
+ }
205
+
206
+ .age-icon {
207
+ width: 1.5rem;
208
+ height: 1.5rem;
209
+ }
210
+
211
+ /* ── Segmented (dog size) ────────────────────────── */
212
+ .age-segmented {
213
+ display: flex;
214
+ background: var(--color-btn-bg, #f8fafc);
215
+ padding: 6px;
216
+ border-radius: 1rem;
217
+ border: 1px solid var(--color-btn-border, #e2e8f0);
218
+ }
219
+
220
+ .theme-dark .age-segmented {
221
+ background: var(--color-btn-bg-dark, #0f172a);
222
+ border-color: var(--color-btn-border-dark, #334155);
223
+ }
224
+
225
+ .size-btn {
226
+ flex: 1;
227
+ text-align: center;
228
+ padding: 0.85rem;
229
+ border-radius: 0.75rem;
230
+ cursor: pointer;
231
+ font-weight: 700;
232
+ font-size: 0.85rem;
233
+ border: none;
234
+ background: transparent;
235
+ color: var(--color-muted, #64748b);
236
+ transition: all 0.2s;
237
+ }
238
+
239
+ .size-btn.active {
240
+ background: var(--color-accent, #f59e0b);
241
+ color: var(--color-on-accent, #fff);
242
+ box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3);
243
+ }
244
+
245
+ /* ── Year input ──────────────────────────────────── */
246
+ .age-year-wrap {
247
+ display: flex;
248
+ justify-content: center;
249
+ }
250
+
251
+ .age-year-input {
252
+ width: 200px;
253
+ text-align: center;
254
+ padding: 1rem;
255
+ background: var(--color-btn-bg, #f8fafc);
256
+ border: 2px solid var(--color-btn-border, #e2e8f0);
257
+ border-radius: 1rem;
258
+ font-size: 2.5rem;
259
+ font-weight: 900;
260
+ color: var(--color-text, #334155);
261
+ outline: none;
262
+ transition: border-color 0.2s, box-shadow 0.2s;
263
+ box-sizing: border-box;
264
+ }
265
+
266
+ .age-year-input::placeholder {
267
+ color: var(--color-label, #94a3b8);
268
+ font-weight: 400;
269
+ font-size: 1rem;
270
+ }
271
+
272
+ .age-year-input:focus,
273
+ .age-year-input.age-input-valid {
274
+ border-color: var(--color-accent, #f59e0b);
275
+ box-shadow: 0 0 0 4px rgba(245, 158, 11, 0.1);
276
+ }
277
+
278
+ .age-year-input.age-input-error {
279
+ border-color: #ef4444;
280
+ box-shadow: 0 0 0 4px rgba(239, 68, 68, 0.1);
281
+ }
282
+
283
+ .theme-dark .age-year-input {
284
+ background: var(--color-btn-bg-dark, #0f172a);
285
+ border-color: var(--color-btn-border-dark, #334155);
286
+ color: var(--color-text-dark, #e2e8f0);
287
+ }
288
+
289
+ .age-year-input::-webkit-inner-spin-button,
290
+ .age-year-input::-webkit-outer-spin-button {
291
+ -webkit-appearance: none;
292
+ margin: 0;
293
+ }
294
+ .age-year-input[type="number"] {
295
+ -moz-appearance: textfield;
296
+ }
297
+
298
+ /* ── Results ─────────────────────────────────────── */
299
+ .age-results {
300
+ background: var(--color-results-bg, #0f172a);
301
+ border-radius: 2rem;
302
+ padding: 2.5rem;
303
+ color: var(--color-results-text, #fff);
304
+ box-shadow: 0 20px 40px -10px rgba(15, 23, 42, 0.3);
305
+ }
306
+
307
+ .theme-dark .age-results {
308
+ background: var(--color-accent, #f59e0b);
309
+ color: var(--color-results-text-dark, #0f172a);
310
+ box-shadow: 0 20px 40px -10px rgba(245, 158, 11, 0.2);
311
+ }
312
+
313
+ .age-res-main {
314
+ text-align: center;
315
+ margin-bottom: 1.5rem;
316
+ }
317
+
318
+ .age-res-label {
319
+ display: block;
320
+ font-size: 0.8rem;
321
+ font-weight: 900;
322
+ text-transform: uppercase;
323
+ color: var(--color-accent, #f59e0b);
324
+ letter-spacing: 0.15em;
325
+ margin-bottom: 0.5rem;
326
+ }
327
+
328
+ .theme-dark .age-res-label {
329
+ color: var(--color-results-bg, #0f172a);
330
+ opacity: 0.7;
331
+ }
332
+
333
+ .age-res-number {
334
+ font-size: 5rem;
335
+ font-weight: 950;
336
+ line-height: 1;
337
+ display: flex;
338
+ align-items: baseline;
339
+ justify-content: center;
340
+ gap: 0.5rem;
341
+ }
342
+
343
+ .age-res-number small {
344
+ font-size: 1.5rem;
345
+ font-weight: 700;
346
+ opacity: 0.6;
347
+ }
348
+
349
+ .age-res-divider {
350
+ height: 1px;
351
+ background: rgba(255, 255, 255, 0.1);
352
+ margin: 1.5rem 0;
353
+ }
354
+
355
+ .theme-dark .age-res-divider {
356
+ background: rgba(0, 0, 0, 0.1);
357
+ }
358
+
359
+ .age-res-grid {
360
+ display: grid;
361
+ grid-template-columns: 1fr 1fr;
362
+ gap: 1rem;
363
+ margin-bottom: 1.5rem;
364
+ }
365
+
366
+ .age-res-card {
367
+ background: rgba(255, 255, 255, 0.08);
368
+ border: 1px solid rgba(255, 255, 255, 0.1);
369
+ border-radius: 1rem;
370
+ padding: 1rem;
371
+ display: flex;
372
+ flex-direction: column;
373
+ align-items: center;
374
+ gap: 0.5rem;
375
+ text-align: center;
376
+ }
377
+
378
+ .theme-dark .age-res-card {
379
+ background: rgba(0, 0, 0, 0.1);
380
+ border-color: rgba(0, 0, 0, 0.1);
381
+ }
382
+
383
+ .age-res-card-label {
384
+ font-size: 0.7rem;
385
+ font-weight: 900;
386
+ text-transform: uppercase;
387
+ letter-spacing: 0.1em;
388
+ opacity: 0.6;
389
+ }
390
+
391
+ .age-res-card-val {
392
+ font-size: 0.9rem;
393
+ font-weight: 700;
394
+ line-height: 1.2;
395
+ }
396
+
397
+ .age-res-footer {
398
+ display: flex;
399
+ justify-content: center;
400
+ padding-top: 1.5rem;
401
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
402
+ }
403
+
404
+ .theme-dark .age-res-footer {
405
+ border-top-color: rgba(0, 0, 0, 0.1);
406
+ }
407
+
408
+ .age-share-btn {
409
+ display: flex;
410
+ align-items: center;
411
+ gap: 0.5rem;
412
+ background: rgba(255, 255, 255, 0.1);
413
+ border: 1px solid rgba(255, 255, 255, 0.2);
414
+ color: #fff;
415
+ padding: 0.75rem 1.5rem;
416
+ border-radius: 2rem;
417
+ font-weight: 700;
418
+ font-size: 0.85rem;
419
+ cursor: pointer;
420
+ transition: all 0.2s;
421
+ }
422
+
423
+ .age-share-btn:hover {
424
+ background: rgba(255, 255, 255, 0.2);
425
+ }
426
+
427
+ .theme-dark .age-share-btn {
428
+ background: rgba(0, 0, 0, 0.15);
429
+ border-color: rgba(0, 0, 0, 0.2);
430
+ color: var(--color-results-bg, #0f172a);
431
+ }
432
+
433
+ .age-share-btn.age-btn-success {
434
+ background: rgba(255, 255, 255, 0.25);
435
+ }
436
+
437
+ .age-icon-sm {
438
+ width: 1rem;
439
+ height: 1rem;
440
+ }
441
+
442
+ /* ── Share overlay ───────────────────────────────── */
443
+ .age-share-overlay {
444
+ position: fixed;
445
+ inset: 0;
446
+ z-index: 100;
447
+ display: none;
448
+ flex-direction: column;
449
+ align-items: center;
450
+ justify-content: center;
451
+ background: #0f172a;
452
+ padding: 1.5rem;
453
+ }
454
+
455
+ .age-share-overlay.visible {
456
+ display: flex;
457
+ }
458
+
459
+ .age-share-wrap {
460
+ width: 100%;
461
+ max-width: 420px;
462
+ display: flex;
463
+ flex-direction: column;
464
+ align-items: center;
465
+ gap: 2rem;
466
+ }
467
+
468
+ .age-share-card {
469
+ width: 100%;
470
+ background: rgba(255, 255, 255, 0.05);
471
+ border: 1px solid rgba(255, 255, 255, 0.1);
472
+ border-radius: 2.5rem;
473
+ overflow: hidden;
474
+ color: #fff;
475
+ }
476
+
477
+ .age-share-card-header {
478
+ padding: 2rem;
479
+ display: flex;
480
+ flex-direction: column;
481
+ align-items: center;
482
+ gap: 1rem;
483
+ border-bottom: 1px solid rgba(255, 255, 255, 0.05);
484
+ }
485
+
486
+ .age-share-badge {
487
+ background: rgba(99, 102, 241, 0.8);
488
+ color: #fff;
489
+ font-size: 0.65rem;
490
+ font-weight: 900;
491
+ text-transform: uppercase;
492
+ letter-spacing: 0.2em;
493
+ padding: 0.4rem 1rem;
494
+ border-radius: 2rem;
495
+ }
496
+
497
+ .age-share-animal {
498
+ width: 5rem;
499
+ height: 5rem;
500
+ background: rgba(15, 23, 42, 0.8);
501
+ border-radius: 50%;
502
+ border: 2px solid rgba(255, 255, 255, 0.15);
503
+ display: flex;
504
+ align-items: center;
505
+ justify-content: center;
506
+ color: #a5b4fc;
507
+ }
508
+
509
+ .age-share-animal svg {
510
+ width: 3rem;
511
+ height: 3rem;
512
+ }
513
+
514
+ #share-pet-name {
515
+ font-size: 2rem;
516
+ font-weight: 900;
517
+ text-align: center;
518
+ color: #fff;
519
+ margin: 0;
520
+ }
521
+
522
+ .age-share-card-body {
523
+ padding: 2rem;
524
+ display: flex;
525
+ flex-direction: column;
526
+ align-items: center;
527
+ gap: 0.75rem;
528
+ }
529
+
530
+ .age-share-human-label {
531
+ font-size: 0.7rem;
532
+ font-weight: 900;
533
+ text-transform: uppercase;
534
+ letter-spacing: 0.2em;
535
+ color: rgba(165, 180, 252, 0.7);
536
+ }
537
+
538
+ #share-human-age {
539
+ font-size: 7rem;
540
+ font-weight: 900;
541
+ line-height: 0.9;
542
+ color: #fff;
543
+ }
544
+
545
+ .age-share-years-label {
546
+ font-size: 1.5rem;
547
+ font-weight: 900;
548
+ text-transform: uppercase;
549
+ letter-spacing: 0.1em;
550
+ color: rgba(255, 255, 255, 0.4);
551
+ margin-top: -0.5rem;
552
+ }
553
+
554
+ .age-share-info-grid {
555
+ display: grid;
556
+ grid-template-columns: 1fr 1fr;
557
+ gap: 0.75rem;
558
+ width: 100%;
559
+ margin-top: 0.75rem;
560
+ }
561
+
562
+ .age-share-info-item {
563
+ background: rgba(255, 255, 255, 0.05);
564
+ border: 1px solid rgba(255, 255, 255, 0.05);
565
+ border-radius: 1rem;
566
+ padding: 0.75rem;
567
+ display: flex;
568
+ flex-direction: column;
569
+ align-items: center;
570
+ gap: 0.25rem;
571
+ text-align: center;
572
+ }
573
+
574
+ .age-share-info-label {
575
+ font-size: 0.65rem;
576
+ font-weight: 900;
577
+ text-transform: uppercase;
578
+ letter-spacing: 0.1em;
579
+ color: rgba(165, 180, 252, 0.5);
580
+ }
581
+
582
+ .age-share-info-val {
583
+ font-size: 0.85rem;
584
+ font-weight: 700;
585
+ color: #fff;
586
+ }
587
+
588
+ .age-share-card-footer {
589
+ padding: 1rem;
590
+ background: rgba(0, 0, 0, 0.2);
591
+ border-top: 1px solid rgba(255, 255, 255, 0.05);
592
+ text-align: center;
593
+ font-size: 0.65rem;
594
+ font-weight: 900;
595
+ letter-spacing: 0.3em;
596
+ text-transform: uppercase;
597
+ color: rgba(255, 255, 255, 0.3);
598
+ }
599
+
600
+ .age-share-new-btn {
601
+ display: flex;
602
+ align-items: center;
603
+ gap: 0.5rem;
604
+ background: rgba(255, 255, 255, 0.05);
605
+ border: 1px solid rgba(255, 255, 255, 0.1);
606
+ color: rgba(255, 255, 255, 0.6);
607
+ padding: 0.75rem 1.5rem;
608
+ border-radius: 2rem;
609
+ font-size: 0.75rem;
610
+ font-weight: 700;
611
+ text-transform: uppercase;
612
+ letter-spacing: 0.1em;
613
+ cursor: pointer;
614
+ transition: all 0.2s;
615
+ }
616
+
617
+ .age-share-new-btn:hover {
618
+ background: rgba(255, 255, 255, 0.1);
619
+ color: #fff;
620
+ }
621
+
622
+ .age-share-new-btn svg {
623
+ width: 1rem;
624
+ height: 1rem;
625
+ }
626
+
627
+ /* ── Responsive ──────────────────────────────────── */
628
+ @media (max-width: 600px) {
629
+ .pet-age-calculator {
630
+ padding: 1.5rem;
631
+ margin: 1rem;
632
+ }
633
+
634
+ .age-species-selector {
635
+ flex-direction: column;
636
+ }
637
+
638
+ .age-res-grid {
639
+ grid-template-columns: 1fr;
640
+ }
641
+
642
+ .age-res-number {
643
+ font-size: 4rem;
644
+ }
645
+
646
+ .age-year-input {
647
+ font-size: 2rem;
648
+ }
649
+ }
@@ -0,0 +1,14 @@
1
+ ---
2
+ import { Bibliography as SharedBibliography } from '@jjlmoya/utils-shared';
3
+ import { petRation } from './index';
4
+ import type { KnownLocale } from '../../types';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'es' } = Astro.props;
11
+ const content = await petRation.i18n[locale]?.();
12
+ ---
13
+
14
+ {content && <SharedBibliography links={content.bibliography} />}