@jjlmoya/utils-games-development 1.46.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.
- package/package.json +67 -0
- package/scripts/postinstall.mjs +27 -0
- package/src/category/GamesCategorySEO.astro +19 -0
- package/src/category/i18n/de.ts +15 -0
- package/src/category/i18n/en.ts +15 -0
- package/src/category/i18n/es.ts +15 -0
- package/src/category/i18n/fr.ts +15 -0
- package/src/category/i18n/id.ts +10 -0
- package/src/category/i18n/it.ts +10 -0
- package/src/category/i18n/ja.ts +10 -0
- package/src/category/i18n/ko.ts +10 -0
- package/src/category/i18n/nl.ts +10 -0
- package/src/category/i18n/pl.ts +10 -0
- package/src/category/i18n/pt.ts +10 -0
- package/src/category/i18n/ru.ts +10 -0
- package/src/category/i18n/sv.ts +10 -0
- package/src/category/i18n/tr.ts +10 -0
- package/src/category/i18n/zh.ts +10 -0
- package/src/category/index.ts +24 -0
- package/src/category/seo.astro +15 -0
- package/src/components/PreviewNavSidebar.astro +116 -0
- package/src/components/PreviewToolbar.astro +143 -0
- package/src/data.ts +11 -0
- package/src/entries.ts +5 -0
- package/src/env.d.ts +5 -0
- package/src/index.ts +21 -0
- package/src/layouts/PreviewLayout.astro +122 -0
- package/src/pages/[locale]/[slug].astro +165 -0
- package/src/pages/[locale].astro +251 -0
- package/src/pages/index.astro +4 -0
- package/src/tests/diacritics_density.test.ts +118 -0
- package/src/tests/faq_count.test.ts +19 -0
- package/src/tests/i18n_coverage.test.ts +36 -0
- package/src/tests/inverted_punctuation.test.ts +84 -0
- package/src/tests/locale_completeness.test.ts +27 -0
- package/src/tests/mocks/astro_mock.js +2 -0
- package/src/tests/no_en_dash.test.ts +70 -0
- package/src/tests/no_h1_in_components.test.ts +48 -0
- package/src/tests/pagespeed_best_practices.test.ts +198 -0
- package/src/tests/qa-test-helpers.ts +32 -0
- package/src/tests/qa_bibliography_links.test.ts +41 -0
- package/src/tests/qa_claim_evidence.test.ts +69 -0
- package/src/tests/qa_logic_reference_coverage.test.ts +46 -0
- package/src/tests/qa_runtime_i18n.test.ts +100 -0
- package/src/tests/schemas_fulfillment.test.ts +23 -0
- package/src/tests/script_density.test.ts +94 -0
- package/src/tests/seo_length.test.ts +23 -0
- package/src/tests/slug_language_code_format.test.ts +23 -0
- package/src/tests/slug_uniqueness.test.ts +81 -0
- package/src/tests/title_quality.test.ts +56 -0
- package/src/tests/tool_validation.test.ts +17 -0
- package/src/tool/steamCapsuleGenerator/bibliography.astro +6 -0
- package/src/tool/steamCapsuleGenerator/bibliography.ts +6 -0
- package/src/tool/steamCapsuleGenerator/component.astro +289 -0
- package/src/tool/steamCapsuleGenerator/components/SteamLibraryGridMockup.astro +51 -0
- package/src/tool/steamCapsuleGenerator/components/SteamLibraryMockup.astro +64 -0
- package/src/tool/steamCapsuleGenerator/components/SteamMasterControls.astro +2 -0
- package/src/tool/steamCapsuleGenerator/components/SteamStoreMockup.astro +90 -0
- package/src/tool/steamCapsuleGenerator/entry.ts +28 -0
- package/src/tool/steamCapsuleGenerator/i18n/de.ts +144 -0
- package/src/tool/steamCapsuleGenerator/i18n/en.ts +243 -0
- package/src/tool/steamCapsuleGenerator/i18n/es.ts +243 -0
- package/src/tool/steamCapsuleGenerator/i18n/fr.ts +165 -0
- package/src/tool/steamCapsuleGenerator/i18n/id.ts +144 -0
- package/src/tool/steamCapsuleGenerator/i18n/it.ts +144 -0
- package/src/tool/steamCapsuleGenerator/i18n/ja.ts +144 -0
- package/src/tool/steamCapsuleGenerator/i18n/ko.ts +144 -0
- package/src/tool/steamCapsuleGenerator/i18n/nl.ts +144 -0
- package/src/tool/steamCapsuleGenerator/i18n/pl.ts +144 -0
- package/src/tool/steamCapsuleGenerator/i18n/pt.ts +144 -0
- package/src/tool/steamCapsuleGenerator/i18n/ru.ts +144 -0
- package/src/tool/steamCapsuleGenerator/i18n/shared.ts +46 -0
- package/src/tool/steamCapsuleGenerator/i18n/sv.ts +144 -0
- package/src/tool/steamCapsuleGenerator/i18n/tr.ts +144 -0
- package/src/tool/steamCapsuleGenerator/i18n/zh.ts +144 -0
- package/src/tool/steamCapsuleGenerator/index.ts +9 -0
- package/src/tool/steamCapsuleGenerator/logic.test.ts +72 -0
- package/src/tool/steamCapsuleGenerator/logic.ts +259 -0
- package/src/tool/steamCapsuleGenerator/seo.astro +15 -0
- package/src/tool/steamCapsuleGenerator/steam-capsule-generator.css +697 -0
- package/src/tool/steamCapsuleGenerator/ui.ts +23 -0
- package/src/tool/steamCapsuleGenerator/validation.ts +22 -0
- package/src/tools.ts +8 -0
- package/src/types.ts +72 -0
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
export interface SteamAssetSpec {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
hdWidth: number;
|
|
7
|
+
hdHeight: number;
|
|
8
|
+
aspectRatio: string;
|
|
9
|
+
format: 'png' | 'jpg';
|
|
10
|
+
required: boolean;
|
|
11
|
+
category: 'store' | 'library' | 'community';
|
|
12
|
+
safeZone: {
|
|
13
|
+
xRatio: number;
|
|
14
|
+
yRatio: number;
|
|
15
|
+
wRatio: number;
|
|
16
|
+
hRatio: number;
|
|
17
|
+
label: string;
|
|
18
|
+
} | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface CropBounds {
|
|
22
|
+
sx: number;
|
|
23
|
+
sy: number;
|
|
24
|
+
sWidth: number;
|
|
25
|
+
sHeight: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface FocalPoint {
|
|
29
|
+
x: number;
|
|
30
|
+
y: number;
|
|
31
|
+
zoom: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface ToolState {
|
|
35
|
+
gameTitle: string;
|
|
36
|
+
developerName: string;
|
|
37
|
+
publisherName: string;
|
|
38
|
+
description: string;
|
|
39
|
+
discountPct: string;
|
|
40
|
+
originalPrice: string;
|
|
41
|
+
finalPrice: string;
|
|
42
|
+
reviewSummary: string;
|
|
43
|
+
tags: string[];
|
|
44
|
+
focalPoint: FocalPoint;
|
|
45
|
+
selectedTab: 'store' | 'library' | 'assets';
|
|
46
|
+
showSafeZones: boolean;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export const STEAM_ASSETS: SteamAssetSpec[] = [
|
|
50
|
+
{
|
|
51
|
+
id: 'header-capsule',
|
|
52
|
+
name: 'Header Capsule',
|
|
53
|
+
width: 460,
|
|
54
|
+
height: 215,
|
|
55
|
+
hdWidth: 920,
|
|
56
|
+
hdHeight: 430,
|
|
57
|
+
aspectRatio: '2.14:1',
|
|
58
|
+
format: 'jpg',
|
|
59
|
+
required: true,
|
|
60
|
+
category: 'store',
|
|
61
|
+
safeZone: { xRatio: 0.60, yRatio: 0.70, wRatio: 0.40, hRatio: 0.30, label: 'Discount Badge' }
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'small-capsule',
|
|
65
|
+
name: 'Small Capsule',
|
|
66
|
+
width: 231,
|
|
67
|
+
height: 87,
|
|
68
|
+
hdWidth: 462,
|
|
69
|
+
hdHeight: 174,
|
|
70
|
+
aspectRatio: '2.65:1',
|
|
71
|
+
format: 'jpg',
|
|
72
|
+
required: true,
|
|
73
|
+
category: 'store',
|
|
74
|
+
safeZone: { xRatio: 0.55, yRatio: 0.60, wRatio: 0.45, hRatio: 0.40, label: 'Wishlist Badge' }
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: 'main-capsule',
|
|
78
|
+
name: 'Main Capsule',
|
|
79
|
+
width: 616,
|
|
80
|
+
height: 353,
|
|
81
|
+
hdWidth: 1232,
|
|
82
|
+
hdHeight: 706,
|
|
83
|
+
aspectRatio: '1.74:1',
|
|
84
|
+
format: 'jpg',
|
|
85
|
+
required: true,
|
|
86
|
+
category: 'store',
|
|
87
|
+
safeZone: { xRatio: 0.60, yRatio: 0.75, wRatio: 0.40, hRatio: 0.25, label: 'Featured Price' }
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: 'vertical-capsule',
|
|
91
|
+
name: 'Vertical Library Capsule',
|
|
92
|
+
width: 300,
|
|
93
|
+
height: 450,
|
|
94
|
+
hdWidth: 600,
|
|
95
|
+
hdHeight: 900,
|
|
96
|
+
aspectRatio: '2:3',
|
|
97
|
+
format: 'jpg',
|
|
98
|
+
required: true,
|
|
99
|
+
category: 'library',
|
|
100
|
+
safeZone: { xRatio: 0.05, yRatio: 0.80, wRatio: 0.90, hRatio: 0.18, label: 'Library Banner' }
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: 'library-hero',
|
|
104
|
+
name: 'Library Hero',
|
|
105
|
+
width: 1920,
|
|
106
|
+
height: 620,
|
|
107
|
+
hdWidth: 3840,
|
|
108
|
+
hdHeight: 1240,
|
|
109
|
+
aspectRatio: '3.1:1',
|
|
110
|
+
format: 'jpg',
|
|
111
|
+
required: true,
|
|
112
|
+
category: 'library',
|
|
113
|
+
safeZone: { xRatio: 0.05, yRatio: 0.40, wRatio: 0.45, hRatio: 0.50, label: 'Logo Overlay' }
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: 'library-logo',
|
|
117
|
+
name: 'Library Logo',
|
|
118
|
+
width: 1280,
|
|
119
|
+
height: 720,
|
|
120
|
+
hdWidth: 1280,
|
|
121
|
+
hdHeight: 720,
|
|
122
|
+
aspectRatio: '16:9',
|
|
123
|
+
format: 'png',
|
|
124
|
+
required: true,
|
|
125
|
+
category: 'library',
|
|
126
|
+
safeZone: null
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
id: 'community-icon',
|
|
130
|
+
name: 'Community Icon',
|
|
131
|
+
width: 32,
|
|
132
|
+
height: 32,
|
|
133
|
+
hdWidth: 184,
|
|
134
|
+
hdHeight: 184,
|
|
135
|
+
aspectRatio: '1:1',
|
|
136
|
+
format: 'png',
|
|
137
|
+
required: true,
|
|
138
|
+
category: 'community',
|
|
139
|
+
safeZone: null
|
|
140
|
+
}
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
export interface CropInput {
|
|
144
|
+
srcWidth: number;
|
|
145
|
+
srcHeight: number;
|
|
146
|
+
targetWidth: number;
|
|
147
|
+
targetHeight: number;
|
|
148
|
+
focusX: number;
|
|
149
|
+
focusY: number;
|
|
150
|
+
zoom?: number;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function calculateCropBounds(input: CropInput): CropBounds {
|
|
154
|
+
const fx = Math.max(0, Math.min(1, input.focusX));
|
|
155
|
+
const fy = Math.max(0, Math.min(1, input.focusY));
|
|
156
|
+
const zoom = Math.max(1, Math.min(3, input.zoom ?? 1));
|
|
157
|
+
const targetAspect = input.targetWidth / input.targetHeight;
|
|
158
|
+
const srcAspect = input.srcWidth / input.srcHeight;
|
|
159
|
+
|
|
160
|
+
const baseHeight = srcAspect > targetAspect ? input.srcHeight / zoom : (input.srcWidth / zoom) / targetAspect;
|
|
161
|
+
const baseWidth = srcAspect > targetAspect ? baseHeight * targetAspect : input.srcWidth / zoom;
|
|
162
|
+
|
|
163
|
+
const sx = Math.max(0, Math.min(input.srcWidth - baseWidth, fx * input.srcWidth - baseWidth / 2));
|
|
164
|
+
const sy = Math.max(0, Math.min(input.srcHeight - baseHeight, fy * input.srcHeight - baseHeight / 2));
|
|
165
|
+
|
|
166
|
+
return {
|
|
167
|
+
sx: Math.round(sx),
|
|
168
|
+
sy: Math.round(sy),
|
|
169
|
+
sWidth: Math.round(baseWidth),
|
|
170
|
+
sHeight: Math.round(baseHeight)
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function validateImageDimensions(
|
|
175
|
+
srcWidth: number,
|
|
176
|
+
srcHeight: number,
|
|
177
|
+
target: SteamAssetSpec
|
|
178
|
+
): { isSufficient: boolean; ratioDiff: number } {
|
|
179
|
+
const isSufficient = srcWidth >= target.hdWidth && srcHeight >= target.hdHeight;
|
|
180
|
+
const targetAspect = target.hdWidth / target.hdHeight;
|
|
181
|
+
const srcAspect = srcWidth / srcHeight;
|
|
182
|
+
const ratioDiff = Math.abs(targetAspect - srcAspect) / targetAspect;
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
isSufficient,
|
|
186
|
+
ratioDiff: Math.round(ratioDiff * 100) / 100
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function getInitialState(): ToolState {
|
|
191
|
+
return {
|
|
192
|
+
gameTitle: 'Super Crunch Bros: GameBob Edition',
|
|
193
|
+
developerName: 'GameBob Studios',
|
|
194
|
+
publisherName: 'GameBob & Bank Loans Inc.',
|
|
195
|
+
description: 'Developed under extreme crunch with 99% espresso, 1% spaghetti code, and zero sleep.',
|
|
196
|
+
discountPct: '-50%',
|
|
197
|
+
originalPrice: '$29.99',
|
|
198
|
+
finalPrice: '$14.99',
|
|
199
|
+
reviewSummary: 'Overwhelmingly Positive (9,999 Coffee Cups)',
|
|
200
|
+
tags: ['Indie', 'GameBob', 'Crunch', 'OverScoped', 'Singleplayer'],
|
|
201
|
+
focalPoint: { x: 0.5, y: 0.5, zoom: 1 },
|
|
202
|
+
selectedTab: 'store',
|
|
203
|
+
showSafeZones: true
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const STORAGE_KEY = 'steam_capsule_generator_state_v2';
|
|
208
|
+
|
|
209
|
+
export function saveStateToStorage(state: Partial<ToolState>): void {
|
|
210
|
+
if (typeof window === 'undefined') return;
|
|
211
|
+
try {
|
|
212
|
+
const existing = loadStateFromStorage();
|
|
213
|
+
const merged = { ...existing, ...state };
|
|
214
|
+
window.localStorage.setItem(STORAGE_KEY, JSON.stringify(merged));
|
|
215
|
+
} catch {
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function parseStr(val: unknown, fallback: string): string {
|
|
221
|
+
return typeof val === 'string' ? val : fallback;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function parseFocal(raw: unknown): FocalPoint {
|
|
225
|
+
const fallback = getInitialState().focalPoint;
|
|
226
|
+
if (!raw || typeof raw !== 'object') return fallback;
|
|
227
|
+
const obj = raw as Record<string, unknown>;
|
|
228
|
+
return {
|
|
229
|
+
x: typeof obj.x === 'number' ? obj.x : fallback.x,
|
|
230
|
+
y: typeof obj.y === 'number' ? obj.y : fallback.y,
|
|
231
|
+
zoom: typeof obj.zoom === 'number' ? obj.zoom : fallback.zoom
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export function loadStateFromStorage(): ToolState {
|
|
236
|
+
const fallback = getInitialState();
|
|
237
|
+
if (typeof window === 'undefined') return fallback;
|
|
238
|
+
try {
|
|
239
|
+
const raw = window.localStorage.getItem(STORAGE_KEY);
|
|
240
|
+
if (!raw) return fallback;
|
|
241
|
+
const p = JSON.parse(raw);
|
|
242
|
+
return {
|
|
243
|
+
gameTitle: parseStr(p.gameTitle, fallback.gameTitle),
|
|
244
|
+
developerName: parseStr(p.developerName, fallback.developerName),
|
|
245
|
+
publisherName: parseStr(p.publisherName, fallback.publisherName),
|
|
246
|
+
description: parseStr(p.description, fallback.description),
|
|
247
|
+
discountPct: parseStr(p.discountPct, fallback.discountPct),
|
|
248
|
+
originalPrice: parseStr(p.originalPrice, fallback.originalPrice),
|
|
249
|
+
finalPrice: parseStr(p.finalPrice, fallback.finalPrice),
|
|
250
|
+
reviewSummary: parseStr(p.reviewSummary, fallback.reviewSummary),
|
|
251
|
+
tags: Array.isArray(p.tags) ? p.tags : fallback.tags,
|
|
252
|
+
focalPoint: parseFocal(p.focalPoint),
|
|
253
|
+
selectedTab: ['store', 'library', 'assets'].includes(p.selectedTab) ? p.selectedTab : fallback.selectedTab,
|
|
254
|
+
showSafeZones: typeof p.showSafeZones === 'boolean' ? p.showSafeZones : fallback.showSafeZones
|
|
255
|
+
};
|
|
256
|
+
} catch {
|
|
257
|
+
return fallback;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { SEORenderer } from '@jjlmoya/utils-shared';
|
|
3
|
+
import { steamCapsuleGenerator } from './entry';
|
|
4
|
+
import type { KnownLocale } from '../../types';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
locale?: KnownLocale;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const { locale = 'en' } = Astro.props;
|
|
11
|
+
const loader = steamCapsuleGenerator.i18n[locale] ?? steamCapsuleGenerator.i18n.en;
|
|
12
|
+
const content = loader ? await loader() : null;
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
{content && content.seo && content.seo.length > 0 && <SEORenderer content={{ locale, sections: content.seo }} />}
|