@jjlmoya/utils-developer 1.31.0 → 1.33.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 (53) hide show
  1. package/package.json +1 -1
  2. package/src/category/i18n/en.ts +1 -1
  3. package/src/category/i18n/es.ts +1 -1
  4. package/src/category/index.ts +2 -1
  5. package/src/entries.ts +4 -1
  6. package/src/index.ts +1 -0
  7. package/src/pages/[locale]/[slug].astro +3 -3
  8. package/src/tests/bibliography_wellformed_export.test.ts +46 -0
  9. package/src/tests/category_seo_quality.test.ts +76 -0
  10. package/src/tests/locale_completeness.test.ts +1 -1
  11. package/src/tests/seo_translation_completeness.test.ts +69 -0
  12. package/src/tests/seo_wellformed_export.test.ts +65 -0
  13. package/src/tests/tool_validation.test.ts +12 -7
  14. package/src/tool/dualOsIconPreview/assets/day-check.webp +0 -0
  15. package/src/tool/dualOsIconPreview/assets/fast-task.webp +0 -0
  16. package/src/tool/dualOsIconPreview/assets/fortune-cookie.webp +0 -0
  17. package/src/tool/dualOsIconPreview/assets/lexi-crash.webp +0 -0
  18. package/src/tool/dualOsIconPreview/assets/pizzametrics.webp +0 -0
  19. package/src/tool/dualOsIconPreview/assets/sauce-lab.webp +0 -0
  20. package/src/tool/dualOsIconPreview/assets/vesp.webp +0 -0
  21. package/src/tool/dualOsIconPreview/bibliography.astro +6 -0
  22. package/src/tool/dualOsIconPreview/bibliography.ts +7 -0
  23. package/src/tool/dualOsIconPreview/component.astro +117 -0
  24. package/src/tool/dualOsIconPreview/controller.ts +157 -0
  25. package/src/tool/dualOsIconPreview/dom-views.ts +82 -0
  26. package/src/tool/dualOsIconPreview/dual-ios-android-app-icon-preview.css +861 -0
  27. package/src/tool/dualOsIconPreview/entry.ts +31 -0
  28. package/src/tool/dualOsIconPreview/evaluator.ts +20 -0
  29. package/src/tool/dualOsIconPreview/i18n/de.ts +56 -0
  30. package/src/tool/dualOsIconPreview/i18n/en.ts +46 -0
  31. package/src/tool/dualOsIconPreview/i18n/es.ts +56 -0
  32. package/src/tool/dualOsIconPreview/i18n/fr.ts +56 -0
  33. package/src/tool/dualOsIconPreview/i18n/id.ts +56 -0
  34. package/src/tool/dualOsIconPreview/i18n/it.ts +56 -0
  35. package/src/tool/dualOsIconPreview/i18n/ja.ts +56 -0
  36. package/src/tool/dualOsIconPreview/i18n/ko.ts +56 -0
  37. package/src/tool/dualOsIconPreview/i18n/nl.ts +56 -0
  38. package/src/tool/dualOsIconPreview/i18n/pl.ts +56 -0
  39. package/src/tool/dualOsIconPreview/i18n/pt.ts +56 -0
  40. package/src/tool/dualOsIconPreview/i18n/ru.ts +56 -0
  41. package/src/tool/dualOsIconPreview/i18n/sv.ts +56 -0
  42. package/src/tool/dualOsIconPreview/i18n/tr.ts +43 -0
  43. package/src/tool/dualOsIconPreview/i18n/zh.ts +43 -0
  44. package/src/tool/dualOsIconPreview/index.ts +11 -0
  45. package/src/tool/dualOsIconPreview/logic.test.ts +63 -0
  46. package/src/tool/dualOsIconPreview/logic.ts +135 -0
  47. package/src/tool/dualOsIconPreview/seo.astro +15 -0
  48. package/src/tool/dualOsIconPreview/storage.ts +18 -0
  49. package/src/tool/dualOsIconPreview/ui.ts +52 -0
  50. package/src/tool/promoteThisWebsite/i18n/pl.ts +1 -1
  51. package/src/tool/promoteThisWebsite/i18n/ru.ts +1 -1
  52. package/src/tool/promoteThisWebsite/i18n/tr.ts +1 -1
  53. package/src/tools.ts +2 -1
@@ -0,0 +1,135 @@
1
+ export type IosAppearance = 'default' | 'dark' | 'clear' | 'tinted';
2
+ export type AndroidShape = 'circle' | 'squircle' | 'rounded' | 'teardrop';
3
+
4
+ export interface IconPreviewSettings {
5
+ appName: string;
6
+ brandColor: string;
7
+ iosAppearance: IosAppearance;
8
+ androidShape: AndroidShape;
9
+ androidThemed: boolean;
10
+ }
11
+
12
+ export interface IconPreviewState extends IconPreviewSettings {
13
+ hasLogo: boolean;
14
+ audit: LogoAudit | null;
15
+ }
16
+
17
+ export interface LogoAuditMetrics {
18
+ width: number;
19
+ height: number;
20
+ leftMargin: number;
21
+ rightMargin: number;
22
+ topMargin: number;
23
+ bottomMargin: number;
24
+ visiblePixels: number;
25
+ }
26
+
27
+ export interface LogoAudit {
28
+ score: number;
29
+ dimensions: string;
30
+ aspect: string;
31
+ resolution: 'pass' | 'review';
32
+ iosMask: 'pass' | 'review';
33
+ androidSafeZone: 'pass' | 'review';
34
+ transparency: 'transparent' | 'full-bleed';
35
+ minimumMargin: number;
36
+ }
37
+
38
+ export const DEFAULT_SETTINGS: IconPreviewSettings = {
39
+ appName: 'Northstar',
40
+ brandColor: '#f4b942',
41
+ iosAppearance: 'default',
42
+ androidShape: 'squircle',
43
+ androidThemed: false,
44
+ };
45
+
46
+ const APPEARANCES: IosAppearance[] = ['default', 'dark', 'clear', 'tinted'];
47
+ const SHAPES: AndroidShape[] = ['circle', 'squircle', 'rounded', 'teardrop'];
48
+
49
+ export function isIosAppearance(value: string): value is IosAppearance {
50
+ return APPEARANCES.includes(value as IosAppearance);
51
+ }
52
+
53
+ export function isAndroidShape(value: string): value is AndroidShape {
54
+ return SHAPES.includes(value as AndroidShape);
55
+ }
56
+
57
+ export function normaliseAppName(value: string): string {
58
+ const clean = value.trim().replace(/\s+/g, ' ');
59
+ return clean.slice(0, 24);
60
+ }
61
+
62
+ export function isHexColor(value: string): boolean {
63
+ return /^#[0-9a-f]{6}$/i.test(value);
64
+ }
65
+
66
+ function resolveAppName(value: string | undefined): string {
67
+ return normaliseAppName(value ?? DEFAULT_SETTINGS.appName) || DEFAULT_SETTINGS.appName;
68
+ }
69
+
70
+ function resolveBrandColor(value: string | undefined): string {
71
+ return isHexColor(value ?? '') ? value! : DEFAULT_SETTINGS.brandColor;
72
+ }
73
+
74
+ function resolveAppearance(value: string | undefined): IosAppearance {
75
+ return isIosAppearance(value ?? '') ? value as IosAppearance : DEFAULT_SETTINGS.iosAppearance;
76
+ }
77
+
78
+ function resolveShape(value: string | undefined): AndroidShape {
79
+ return isAndroidShape(value ?? '') ? value as AndroidShape : DEFAULT_SETTINGS.androidShape;
80
+ }
81
+
82
+ export function sanitiseSettings(input: Partial<IconPreviewSettings>): IconPreviewSettings {
83
+ return {
84
+ appName: resolveAppName(input.appName),
85
+ brandColor: resolveBrandColor(input.brandColor),
86
+ iosAppearance: resolveAppearance(input.iosAppearance),
87
+ androidShape: resolveShape(input.androidShape),
88
+ androidThemed: input.androidThemed === true,
89
+ };
90
+ }
91
+
92
+ export function buildPreviewState(settings: Partial<IconPreviewSettings>, hasLogo: boolean, audit: LogoAudit | null = null): IconPreviewState {
93
+ return { ...sanitiseSettings(settings), hasLogo, audit };
94
+ }
95
+
96
+ export function getIosSurface(appearance: IosAppearance): string {
97
+ if (appearance === 'dark') return 'dark';
98
+ if (appearance === 'clear') return 'clear';
99
+ if (appearance === 'tinted') return 'tinted';
100
+ return 'light';
101
+ }
102
+
103
+ function isSquare(metrics: LogoAuditMetrics): boolean {
104
+ return metrics.width === metrics.height;
105
+ }
106
+
107
+ function hasGoodResolution(metrics: LogoAuditMetrics): boolean {
108
+ return Math.min(metrics.width, metrics.height) >= 512;
109
+ }
110
+
111
+ function getMinimumMargin(metrics: LogoAuditMetrics): number {
112
+ return Math.min(metrics.leftMargin, metrics.rightMargin, metrics.topMargin, metrics.bottomMargin) / Math.max(metrics.width, metrics.height);
113
+ }
114
+
115
+ function getScore(metrics: LogoAuditMetrics, minimumMargin: number): number {
116
+ const squarePoints = isSquare(metrics) ? 25 : 0;
117
+ const resolutionPoints = hasGoodResolution(metrics) ? 25 : 0;
118
+ const iosPoints = minimumMargin >= 0.1 ? 25 : 0;
119
+ const androidPoints = minimumMargin >= 0.195 ? 25 : 0;
120
+ return squarePoints + resolutionPoints + iosPoints + androidPoints;
121
+ }
122
+
123
+ export function auditLogo(metrics: LogoAuditMetrics): LogoAudit {
124
+ const minimumMargin = getMinimumMargin(metrics);
125
+ return {
126
+ score: getScore(metrics, minimumMargin),
127
+ dimensions: `${metrics.width} x ${metrics.height}`,
128
+ aspect: isSquare(metrics) ? '1:1' : `${metrics.width}:${metrics.height}`,
129
+ resolution: hasGoodResolution(metrics) ? 'pass' : 'review',
130
+ iosMask: minimumMargin >= 0.1 && isSquare(metrics) ? 'pass' : 'review',
131
+ androidSafeZone: minimumMargin >= 0.195 && isSquare(metrics) ? 'pass' : 'review',
132
+ transparency: metrics.visiblePixels < metrics.width * metrics.height ? 'transparent' : 'full-bleed',
133
+ minimumMargin,
134
+ };
135
+ }
@@ -0,0 +1,15 @@
1
+ ---
2
+ import { SEORenderer } from '@jjlmoya/utils-shared';
3
+ import { dualOsIconPreview } from './index';
4
+ import type { KnownLocale } from '../../types';
5
+
6
+ interface Props {
7
+ locale?: KnownLocale;
8
+ }
9
+
10
+ const { locale = 'en' } = Astro.props;
11
+ const content = await dualOsIconPreview.i18n[locale]?.();
12
+ if (!content) return null;
13
+ ---
14
+
15
+ {content.seo?.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
@@ -0,0 +1,18 @@
1
+ import type { IconPreviewSettings } from './logic';
2
+
3
+ const STORAGE_KEY = 'jjlmoya:dual-os-icon-preview';
4
+
5
+ export function loadStoredSettings(): Partial<IconPreviewSettings> {
6
+ try {
7
+ const raw = window.localStorage.getItem(STORAGE_KEY);
8
+ return raw ? JSON.parse(raw) as Partial<IconPreviewSettings> : {};
9
+ } catch {
10
+ return {};
11
+ }
12
+ }
13
+
14
+ export function saveStoredSettings(settings: IconPreviewSettings): void {
15
+ try {
16
+ window.localStorage.setItem(STORAGE_KEY, JSON.stringify(settings));
17
+ } catch {}
18
+ }
@@ -0,0 +1,52 @@
1
+ export interface DualOsIconPreviewUI extends Record<string, string> {
2
+ labelUpload: string;
3
+ uploadAction: string;
4
+ uploadHint: string;
5
+ dropHint: string;
6
+ labelAppName: string;
7
+ appNamePlaceholder: string;
8
+ labelBrandColor: string;
9
+ labelIosAppearance: string;
10
+ iosAppearanceHint: string;
11
+ iosDefault: string;
12
+ iosDark: string;
13
+ iosClear: string;
14
+ iosTinted: string;
15
+ labelAndroidShape: string;
16
+ androidShapeHint: string;
17
+ androidCircle: string;
18
+ androidSquircle: string;
19
+ androidRounded: string;
20
+ androidTeardrop: string;
21
+ labelAndroidTheme: string;
22
+ androidThemeHint: string;
23
+ iosDeviceLabel: string;
24
+ androidDeviceLabel: string;
25
+ iosHomeLabel: string;
26
+ androidHomeLabel: string;
27
+ safeZoneLabel: string;
28
+ adaptiveLayerLabel: string;
29
+ monochromeLabel: string;
30
+ nameFallback: string;
31
+ emptyLogo: string;
32
+ fileError: string;
33
+ statusReady: string;
34
+ statusNeedsReview: string;
35
+ stageKicker: string;
36
+ stageNote: string;
37
+ auditTitle: string;
38
+ auditHint: string;
39
+ auditWaiting: string;
40
+ auditNotChecked: string;
41
+ auditFile: string;
42
+ auditAspect: string;
43
+ auditResolution: string;
44
+ auditIosMask: string;
45
+ auditAndroidZone: string;
46
+ auditMargin: string;
47
+ auditTransparency: string;
48
+ auditTransparent: string;
49
+ auditFullBleed: string;
50
+ statusPass: string;
51
+ statusReview: string;
52
+ }
@@ -24,7 +24,7 @@ const localized = {
24
24
  ],
25
25
  seoTitle: 'Oficjalne biuro propagandy jjlmoya i GameBob',
26
26
  seoIntro: 'To nie jest ogólny kompozytor obrazów społecznościowych dla kogoś, kto właśnie odkrył słowo branding. Służy do promowania naszych stron jjlmoya.es i GameBob.dev pod nadzorem kotów.',
27
- seoBody: 'Wklej prawdziwy zrzut lub załaduj adres produkcyjny. Narzędzie czyta tytuł strony zamiast zgadywać go ze sluga i zachowuje widoczny obraz Open Graph podczas układania warstw.',
27
+ seoBody: 'Wklej prawdziwy zrzut lub załaduj adres produkcyjny. Narzędzie czyta tytuł strony zamiast zgadywać go ze sluga i zachowuje widoczny obraz Open Graph podczas układania warstw. Możesz dopasować układ, markę, tło, logo i maskotkę do formatu publikacji, a potem pobrać gotowy PNG bez opuszczania narzędzia.',
28
28
  seoTip: 'Zacznij od adresu produkcyjnego jjlmoya.es lub GameBob.dev. Jeśli tytuł, marka lub obraz się nie ładują, zarząd obwini sieć, człowieka, a na końcu koty.',
29
29
  };
30
30
  const ui: PromoteThisWebsiteUI = {
@@ -24,7 +24,7 @@ const localized = {
24
24
  ],
25
25
  seoTitle: 'Официальный отдел пропаганды jjlmoya и GameBob',
26
26
  seoIntro: 'Это не универсальный редактор социальных изображений для человека, который только узнал слово branding. Он нужен для продвижения наших страниц jjlmoya.es и GameBob.dev под наблюдением кошек.',
27
- seoBody: 'Вставьте настоящий снимок или загрузите рабочий URL. Инструмент читает заголовок страницы, а не угадывает его по slug, и оставляет официальное изображение Open Graph видимым.',
27
+ seoBody: 'Вставьте настоящий снимок или загрузите рабочий URL. Инструмент читает заголовок страницы, а не угадывает его по slug, и оставляет официальное изображение Open Graph видимым. Затем настройте композицию, бренд, фон, логотип и маскота под формат публикации и скачайте готовый PNG без выхода из инструмента.',
28
28
  seoTip: 'Начните с рабочего URL jjlmoya.es или GameBob.dev. Если заголовок, бренд или изображение не загрузились, совет обвинит сеть, человека и наконец кошек.',
29
29
  };
30
30
  const ui: PromoteThisWebsiteUI = {
@@ -24,7 +24,7 @@ const localized = {
24
24
  ],
25
25
  seoTitle: 'jjlmoya ve GameBob için resmi propaganda masası',
26
26
  seoIntro: 'Bu, branding kelimesini yeni keşfetmiş biri için genel bir sosyal görsel düzenleyici değil. Kediler denetlerken jjlmoya.es ve GameBob.dev sayfalarımızı tanıtmak için var.',
27
- seoBody: "Gerçek bir ekran görüntüsü yapıştır veya üretim URL'si yükle. Araç slugdan tahmin etmek yerine sayfa başlığını okur ve kompozisyon sırasında resmi Open Graph görselini görünür tutar.",
27
+ seoBody: "Gerçek bir ekran görüntüsü yapıştır veya üretim URL'si yükle. Araç slugdan tahmin etmek yerine sayfa başlığını okur ve kompozisyon sırasında resmi Open Graph görselini görünür tutar. Yerleşimi, markayı, arka planı, logoyu ve maskotu paylaşım formatına göre düzenleyip hazır PNG dosyasını araçtan ayrılmadan indirebilirsin.",
28
28
  seoTip: 'jjlmoya.es veya GameBob.dev üretim URLsiyle başla. Başlık, marka veya görsel yüklenmezse kurul önce ağı, sonra insanı, en son kedileri suçlar.',
29
29
  };
30
30
  const ui: PromoteThisWebsiteUI = {
package/src/tools.ts CHANGED
@@ -29,5 +29,6 @@ import { JWT_DECODER_TOOL } from './tool/jwtDecoder/index';
29
29
  import { VISUAL_CSS_GRID_FLEXBOX_GENERATOR_TOOL } from './tool/visualCssGridFlexboxGenerator/index';
30
30
  import { CSS_BOX_SHADOW_GENERATOR_TOOL } from './tool/cssBoxShadowGenerator/index';
31
31
  import { PROMOTE_THIS_WEBSITE_TOOL } from './tool/promoteThisWebsite/index';
32
+ import { DUAL_OS_ICON_PREVIEW_TOOL } from './tool/dualOsIconPreview/index';
32
33
 
33
- export const ALL_TOOLS: ToolDefinition[] = [JSON_FORMATTER_TOOL, SVG_TO_CSS_TOOL, ASPECT_RATIO_TOOL, PLACEHOLDER_GENERATOR_TOOL, URL_ENCODER_DECODER_TOOL, DUPLICATE_CSS_REMOVER_TOOL, CSS_TO_INLINE_CONVERTER_TOOL, CSS_SPECIFICITY_CALCULATOR_TOOL, CRON_GENERATOR_TOOL, KEYCODE_TOOL, LLM_COST_CALCULATOR_TOOL, MUSICAL_TYPOGRAPHY_TOOL, MOBILE_MOCKUP_GENERATOR_TOOL, HASH_GENERATOR_TOOL, PROMPT_LIBRARY_TOOL, COLOR_CONVERTER_TOOL, READABILITY_CALCULATOR_TOOL, SVG_SANITIZER_TOOL, UTM_GENERATOR_TOOL, URL_CLEANER_TOOL, INSPECTOR_CERTIFICADOS_SSL_TOOL, GENERADOR_SECURITY_TXT_TOOL, CALCULADORA_TIEMPO_DATOS_TOOL, CONVERSOR_EXCEL_CSV_HTML_TOOL, SERP_PIXEL_SIMULATOR_TOOL, JWT_DECODER_TOOL, VISUAL_CSS_GRID_FLEXBOX_GENERATOR_TOOL, CSS_BOX_SHADOW_GENERATOR_TOOL, PROMOTE_THIS_WEBSITE_TOOL];
34
+ export const ALL_TOOLS: ToolDefinition[] = [JSON_FORMATTER_TOOL, SVG_TO_CSS_TOOL, ASPECT_RATIO_TOOL, PLACEHOLDER_GENERATOR_TOOL, URL_ENCODER_DECODER_TOOL, DUPLICATE_CSS_REMOVER_TOOL, CSS_TO_INLINE_CONVERTER_TOOL, CSS_SPECIFICITY_CALCULATOR_TOOL, CRON_GENERATOR_TOOL, KEYCODE_TOOL, LLM_COST_CALCULATOR_TOOL, MUSICAL_TYPOGRAPHY_TOOL, MOBILE_MOCKUP_GENERATOR_TOOL, HASH_GENERATOR_TOOL, PROMPT_LIBRARY_TOOL, COLOR_CONVERTER_TOOL, READABILITY_CALCULATOR_TOOL, SVG_SANITIZER_TOOL, UTM_GENERATOR_TOOL, URL_CLEANER_TOOL, INSPECTOR_CERTIFICADOS_SSL_TOOL, GENERADOR_SECURITY_TXT_TOOL, CALCULADORA_TIEMPO_DATOS_TOOL, CONVERSOR_EXCEL_CSV_HTML_TOOL, SERP_PIXEL_SIMULATOR_TOOL, JWT_DECODER_TOOL, VISUAL_CSS_GRID_FLEXBOX_GENERATOR_TOOL, CSS_BOX_SHADOW_GENERATOR_TOOL, PROMOTE_THIS_WEBSITE_TOOL, DUAL_OS_ICON_PREVIEW_TOOL];