create-ng-tailwind 2.0.2 → 2.1.1
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/CHANGELOG.md +65 -0
- package/README.md +81 -177
- package/lib/managers/ProjectManager.js +2 -2
- package/lib/templates/starter/features.js +49 -7
- package/lib/templates/starter/index.js +119 -46
- package/lib/templates/starter/seo-assets.js +141 -0
- package/lib/templates/starter/seo-features.js +290 -0
- package/lib/utils/ai-config.js +97 -58
- package/package.json +3 -4
- package/CLAUDE.md +0 -178
|
@@ -27,6 +27,21 @@ const {
|
|
|
27
27
|
createTooltipDirective,
|
|
28
28
|
} = require('./ui-features');
|
|
29
29
|
|
|
30
|
+
const {
|
|
31
|
+
createSEOService,
|
|
32
|
+
createStructuredDataUtil,
|
|
33
|
+
createRobotsTxt,
|
|
34
|
+
} = require('./seo-features');
|
|
35
|
+
|
|
36
|
+
const {
|
|
37
|
+
createDefaultOGImage,
|
|
38
|
+
createFavicon,
|
|
39
|
+
createAppleTouchIcon,
|
|
40
|
+
createAndroidIcon,
|
|
41
|
+
createAndroidIconLarge,
|
|
42
|
+
createLogo,
|
|
43
|
+
} = require('./seo-assets');
|
|
44
|
+
|
|
30
45
|
const starter = {
|
|
31
46
|
info: {
|
|
32
47
|
name: 'Starter',
|
|
@@ -37,6 +52,10 @@ const starter = {
|
|
|
37
52
|
'HTTP Interceptors (Auth, Error, Loading, Caching)',
|
|
38
53
|
'i18n translation support (English & Arabic) with RTL',
|
|
39
54
|
'Language switcher in header (desktop & mobile)',
|
|
55
|
+
'Comprehensive SEO service (meta tags, Open Graph, Twitter Cards)',
|
|
56
|
+
'Structured data (JSON-LD) for rich search results',
|
|
57
|
+
'Multi-language SEO with hreflang tags',
|
|
58
|
+
'robots.txt and favicon set included',
|
|
40
59
|
'Toast/Notification system with auto-dismiss',
|
|
41
60
|
'Modal/Dialog system with confirm & alert',
|
|
42
61
|
'Essential UI components (Button, Card, Spinner, Toast, Modal)',
|
|
@@ -46,7 +65,7 @@ const starter = {
|
|
|
46
65
|
'Authentication UI (Login, Register, Forgot Password)',
|
|
47
66
|
'3 example pages (Home, About, Contact)',
|
|
48
67
|
'Reactive forms with validation',
|
|
49
|
-
'API, Auth, Storage, Loading, Cache services',
|
|
68
|
+
'API, Auth, Storage, Loading, Cache, SEO services',
|
|
50
69
|
'TypeScript interfaces and models',
|
|
51
70
|
'Responsive Tailwind design system',
|
|
52
71
|
'ESLint + Prettier + Husky pre-commit hooks',
|
|
@@ -74,6 +93,7 @@ const starter = {
|
|
|
74
93
|
await this.createDirectoryStructure(config);
|
|
75
94
|
await this.createEnvironments(config);
|
|
76
95
|
await this.createCoreServices(config);
|
|
96
|
+
await this.createSEOFeatures(config);
|
|
77
97
|
await this.createHttpInterceptors(config);
|
|
78
98
|
await this.createI18n(config);
|
|
79
99
|
await this.createGuards(config);
|
|
@@ -106,8 +126,9 @@ const starter = {
|
|
|
106
126
|
completeStep('Angular 20+ project created');
|
|
107
127
|
completeStep('Tailwind CSS v4 configured');
|
|
108
128
|
completeStep('i18n translation support (English & Arabic)');
|
|
129
|
+
completeStep('SEO optimization (meta tags, Open Graph, structured data)');
|
|
109
130
|
completeStep('HTTP interceptors (Auth, Error, Loading, Cache)');
|
|
110
|
-
completeStep('Core services (
|
|
131
|
+
completeStep('Core services (9 services including SEO)');
|
|
111
132
|
completeStep('UI components (Button, Card, Toast, Modal, Spinner)');
|
|
112
133
|
completeStep('Utility pipes & directives');
|
|
113
134
|
completeStep('Authentication pages & guards');
|
|
@@ -123,6 +144,7 @@ const starter = {
|
|
|
123
144
|
'src/app/core/guards',
|
|
124
145
|
'src/app/core/i18n',
|
|
125
146
|
'src/app/core/interceptors',
|
|
147
|
+
'src/app/core/utils',
|
|
126
148
|
'src/app/shared/components/button',
|
|
127
149
|
'src/app/shared/components/card',
|
|
128
150
|
'src/app/shared/components/loading-spinner',
|
|
@@ -142,8 +164,7 @@ const starter = {
|
|
|
142
164
|
'src/app/layout/auth',
|
|
143
165
|
'public/assets/images',
|
|
144
166
|
'public/assets/i18n',
|
|
145
|
-
'public/
|
|
146
|
-
'public/styles',
|
|
167
|
+
'public/assets/icons',
|
|
147
168
|
];
|
|
148
169
|
|
|
149
170
|
for (const dir of directories) {
|
|
@@ -545,6 +566,70 @@ export class StorageService {
|
|
|
545
566
|
);
|
|
546
567
|
},
|
|
547
568
|
|
|
569
|
+
async createSEOFeatures(config) {
|
|
570
|
+
// Create SEO Service
|
|
571
|
+
const seoService = createSEOService();
|
|
572
|
+
await fs.writeFile(
|
|
573
|
+
path.join(config.fullPath, 'src/app/core/services/seo.service.ts'),
|
|
574
|
+
seoService
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
// Create Structured Data Utility
|
|
578
|
+
const structuredDataUtil = createStructuredDataUtil();
|
|
579
|
+
await fs.writeFile(
|
|
580
|
+
path.join(config.fullPath, 'src/app/core/utils/structured-data.ts'),
|
|
581
|
+
structuredDataUtil
|
|
582
|
+
);
|
|
583
|
+
|
|
584
|
+
// Create robots.txt in public folder
|
|
585
|
+
const robotsTxt = createRobotsTxt();
|
|
586
|
+
await fs.writeFile(
|
|
587
|
+
path.join(config.fullPath, 'public/robots.txt'),
|
|
588
|
+
robotsTxt
|
|
589
|
+
);
|
|
590
|
+
|
|
591
|
+
// Create default OG image (SVG as placeholder)
|
|
592
|
+
const ogImage = createDefaultOGImage(config.projectName);
|
|
593
|
+
await fs.writeFile(
|
|
594
|
+
path.join(config.fullPath, 'public/assets/images/og-default.svg'),
|
|
595
|
+
ogImage
|
|
596
|
+
);
|
|
597
|
+
|
|
598
|
+
// Create favicon
|
|
599
|
+
const favicon = createFavicon(config.projectName);
|
|
600
|
+
await fs.writeFile(
|
|
601
|
+
path.join(config.fullPath, 'public/favicon.svg'),
|
|
602
|
+
favicon
|
|
603
|
+
);
|
|
604
|
+
|
|
605
|
+
// Create Apple Touch Icon (SVG as placeholder)
|
|
606
|
+
const appleTouchIcon = createAppleTouchIcon(config.projectName);
|
|
607
|
+
await fs.writeFile(
|
|
608
|
+
path.join(config.fullPath, 'public/assets/icons/apple-touch-icon.svg'),
|
|
609
|
+
appleTouchIcon
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
// Create Android Chrome icons (SVG as placeholder)
|
|
613
|
+
const androidIcon = createAndroidIcon(config.projectName);
|
|
614
|
+
await fs.writeFile(
|
|
615
|
+
path.join(config.fullPath, 'public/assets/icons/android-chrome-192x192.svg'),
|
|
616
|
+
androidIcon
|
|
617
|
+
);
|
|
618
|
+
|
|
619
|
+
const androidIconLarge = createAndroidIconLarge(config.projectName);
|
|
620
|
+
await fs.writeFile(
|
|
621
|
+
path.join(config.fullPath, 'public/assets/icons/android-chrome-512x512.svg'),
|
|
622
|
+
androidIconLarge
|
|
623
|
+
);
|
|
624
|
+
|
|
625
|
+
// Create logo
|
|
626
|
+
const logo = createLogo(config.projectName);
|
|
627
|
+
await fs.writeFile(
|
|
628
|
+
path.join(config.fullPath, 'public/assets/images/logo.svg'),
|
|
629
|
+
logo
|
|
630
|
+
);
|
|
631
|
+
},
|
|
632
|
+
|
|
548
633
|
async createHttpInterceptors(config) {
|
|
549
634
|
// Create all HTTP interceptors
|
|
550
635
|
await createAuthInterceptor(config);
|
|
@@ -2734,7 +2819,7 @@ export class ForgotPasswordComponent {
|
|
|
2734
2819
|
},
|
|
2735
2820
|
|
|
2736
2821
|
async createHomeComponent(config) {
|
|
2737
|
-
const homeComponentTs = `import { Component, inject } from '@angular/core';
|
|
2822
|
+
const homeComponentTs = `import { Component, OnInit, inject } from '@angular/core';
|
|
2738
2823
|
import { CommonModule } from '@angular/common';
|
|
2739
2824
|
import { RouterModule } from '@angular/router';
|
|
2740
2825
|
import { NgIconComponent, provideIcons } from '@ng-icons/core';
|
|
@@ -2757,6 +2842,7 @@ import { ButtonComponent } from '@shared/components/button/button.component';
|
|
|
2757
2842
|
import { CardComponent } from '@shared/components/card/card.component';
|
|
2758
2843
|
import { ToastService } from '@core/services/toast.service';
|
|
2759
2844
|
import { ModalService } from '@core/services/modal.service';
|
|
2845
|
+
import { SeoService } from '@core/services/seo.service';
|
|
2760
2846
|
|
|
2761
2847
|
@Component({
|
|
2762
2848
|
selector: 'app-home',
|
|
@@ -2778,11 +2864,22 @@ import { ModalService } from '@core/services/modal.service';
|
|
|
2778
2864
|
})],
|
|
2779
2865
|
templateUrl: './home.component.html'
|
|
2780
2866
|
})
|
|
2781
|
-
export class HomeComponent {
|
|
2867
|
+
export class HomeComponent implements OnInit {
|
|
2782
2868
|
protected readonly projectName = '${config.projectName}';
|
|
2783
2869
|
|
|
2784
2870
|
private toast = inject(ToastService);
|
|
2785
2871
|
private modal = inject(ModalService);
|
|
2872
|
+
private seo = inject(SeoService);
|
|
2873
|
+
|
|
2874
|
+
ngOnInit(): void {
|
|
2875
|
+
// Set SEO meta tags for Home page
|
|
2876
|
+
this.seo.updateMeta({
|
|
2877
|
+
title: 'Home',
|
|
2878
|
+
description: 'Welcome to ${config.projectName} - A modern Angular application with Tailwind CSS, SEO optimization, and i18n support.',
|
|
2879
|
+
keywords: 'angular, tailwind, seo, i18n, typescript',
|
|
2880
|
+
ogType: 'website'
|
|
2881
|
+
});
|
|
2882
|
+
}
|
|
2786
2883
|
|
|
2787
2884
|
// Toast Examples
|
|
2788
2885
|
showSuccessToast(): void {
|
|
@@ -3350,7 +3447,7 @@ export class HomeComponent {
|
|
|
3350
3447
|
},
|
|
3351
3448
|
|
|
3352
3449
|
async createAboutComponent(config) {
|
|
3353
|
-
const aboutComponentTs = `import { Component } from '@angular/core';
|
|
3450
|
+
const aboutComponentTs = `import { Component, OnInit, inject } from '@angular/core';
|
|
3354
3451
|
import { CommonModule } from '@angular/common';
|
|
3355
3452
|
import { RouterModule } from '@angular/router';
|
|
3356
3453
|
import { NgIconComponent, provideIcons } from '@ng-icons/core';
|
|
@@ -3364,6 +3461,7 @@ import {
|
|
|
3364
3461
|
} from '@ng-icons/heroicons/outline';
|
|
3365
3462
|
import { TranslateModule } from '@ngx-translate/core';
|
|
3366
3463
|
import { ButtonComponent } from '@shared/components/button/button.component';
|
|
3464
|
+
import { SeoService } from '@core/services/seo.service';
|
|
3367
3465
|
|
|
3368
3466
|
@Component({
|
|
3369
3467
|
selector: 'app-about',
|
|
@@ -3379,8 +3477,19 @@ import { ButtonComponent } from '@shared/components/button/button.component';
|
|
|
3379
3477
|
})],
|
|
3380
3478
|
templateUrl: './about.component.html'
|
|
3381
3479
|
})
|
|
3382
|
-
export class AboutComponent {
|
|
3480
|
+
export class AboutComponent implements OnInit {
|
|
3383
3481
|
protected readonly projectName = '${config.projectName}';
|
|
3482
|
+
private seo = inject(SeoService);
|
|
3483
|
+
|
|
3484
|
+
ngOnInit(): void {
|
|
3485
|
+
// Set SEO meta tags for About page
|
|
3486
|
+
this.seo.updateMeta({
|
|
3487
|
+
title: 'About Us',
|
|
3488
|
+
description: 'Learn more about ${config.projectName} and our mission to create modern web applications.',
|
|
3489
|
+
keywords: 'about, team, mission, company',
|
|
3490
|
+
ogType: 'website'
|
|
3491
|
+
});
|
|
3492
|
+
}
|
|
3384
3493
|
}`;
|
|
3385
3494
|
|
|
3386
3495
|
const aboutComponentHtml = `<!-- Hero Section -->
|
|
@@ -3939,49 +4048,13 @@ npx lint-staged
|
|
|
3939
4048
|
description: `${config.projectName} - Built with Angular and Tailwind CSS`,
|
|
3940
4049
|
icons: [
|
|
3941
4050
|
{
|
|
3942
|
-
src: 'assets/icons/
|
|
3943
|
-
sizes: '72x72',
|
|
3944
|
-
type: 'image/svg+xml',
|
|
3945
|
-
purpose: 'maskable any',
|
|
3946
|
-
},
|
|
3947
|
-
{
|
|
3948
|
-
src: 'assets/icons/icon-96x96.svg',
|
|
3949
|
-
sizes: '96x96',
|
|
3950
|
-
type: 'image/svg+xml',
|
|
3951
|
-
purpose: 'maskable any',
|
|
3952
|
-
},
|
|
3953
|
-
{
|
|
3954
|
-
src: 'assets/icons/icon-128x128.svg',
|
|
3955
|
-
sizes: '128x128',
|
|
3956
|
-
type: 'image/svg+xml',
|
|
3957
|
-
purpose: 'maskable any',
|
|
3958
|
-
},
|
|
3959
|
-
{
|
|
3960
|
-
src: 'assets/icons/icon-144x144.svg',
|
|
3961
|
-
sizes: '144x144',
|
|
3962
|
-
type: 'image/svg+xml',
|
|
3963
|
-
purpose: 'maskable any',
|
|
3964
|
-
},
|
|
3965
|
-
{
|
|
3966
|
-
src: 'assets/icons/icon-152x152.svg',
|
|
3967
|
-
sizes: '152x152',
|
|
3968
|
-
type: 'image/svg+xml',
|
|
3969
|
-
purpose: 'maskable any',
|
|
3970
|
-
},
|
|
3971
|
-
{
|
|
3972
|
-
src: 'assets/icons/icon-192x192.svg',
|
|
4051
|
+
src: 'assets/icons/android-chrome-192x192.svg',
|
|
3973
4052
|
sizes: '192x192',
|
|
3974
4053
|
type: 'image/svg+xml',
|
|
3975
4054
|
purpose: 'maskable any',
|
|
3976
4055
|
},
|
|
3977
4056
|
{
|
|
3978
|
-
src: 'assets/icons/
|
|
3979
|
-
sizes: '384x384',
|
|
3980
|
-
type: 'image/svg+xml',
|
|
3981
|
-
purpose: 'maskable any',
|
|
3982
|
-
},
|
|
3983
|
-
{
|
|
3984
|
-
src: 'assets/icons/icon-512x512.svg',
|
|
4057
|
+
src: 'assets/icons/android-chrome-512x512.svg',
|
|
3985
4058
|
sizes: '512x512',
|
|
3986
4059
|
type: 'image/svg+xml',
|
|
3987
4060
|
purpose: 'maskable any',
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SEO Assets - SVG placeholders for OG images and favicons
|
|
3
|
+
* These are temporary placeholders that users should replace with their own branding
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generate default Open Graph image (1200x630px - recommended size)
|
|
8
|
+
*/
|
|
9
|
+
function createDefaultOGImage(projectName) {
|
|
10
|
+
return `<svg width="1200" height="630" xmlns="http://www.w3.org/2000/svg">
|
|
11
|
+
<defs>
|
|
12
|
+
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
13
|
+
<stop offset="0%" style="stop-color:#3b82f6;stop-opacity:1" />
|
|
14
|
+
<stop offset="100%" style="stop-color:#8b5cf6;stop-opacity:1" />
|
|
15
|
+
</linearGradient>
|
|
16
|
+
</defs>
|
|
17
|
+
<rect width="1200" height="630" fill="url(#grad)"/>
|
|
18
|
+
<text x="600" y="280" font-family="Arial, sans-serif" font-size="72" font-weight="bold" fill="white" text-anchor="middle">
|
|
19
|
+
${projectName}
|
|
20
|
+
</text>
|
|
21
|
+
<text x="600" y="350" font-family="Arial, sans-serif" font-size="32" fill="white" text-anchor="middle" opacity="0.9">
|
|
22
|
+
Modern Angular Application
|
|
23
|
+
</text>
|
|
24
|
+
<text x="600" y="550" font-family="Arial, sans-serif" font-size="20" fill="white" text-anchor="middle" opacity="0.7">
|
|
25
|
+
Replace this image with your own branding
|
|
26
|
+
</text>
|
|
27
|
+
</svg>`;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Generate favicon (32x32px)
|
|
32
|
+
*/
|
|
33
|
+
function createFavicon(projectName) {
|
|
34
|
+
// Get first letter of project name
|
|
35
|
+
const letter = projectName.charAt(0).toUpperCase();
|
|
36
|
+
|
|
37
|
+
return `<svg width="32" height="32" xmlns="http://www.w3.org/2000/svg">
|
|
38
|
+
<defs>
|
|
39
|
+
<linearGradient id="faviconGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
40
|
+
<stop offset="0%" style="stop-color:#3b82f6;stop-opacity:1" />
|
|
41
|
+
<stop offset="100%" style="stop-color:#8b5cf6;stop-opacity:1" />
|
|
42
|
+
</linearGradient>
|
|
43
|
+
</defs>
|
|
44
|
+
<rect width="32" height="32" rx="6" fill="url(#faviconGrad)"/>
|
|
45
|
+
<text x="16" y="23" font-family="Arial, sans-serif" font-size="20" font-weight="bold" fill="white" text-anchor="middle">
|
|
46
|
+
${letter}
|
|
47
|
+
</text>
|
|
48
|
+
</svg>`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Generate Apple Touch Icon (180x180px)
|
|
53
|
+
*/
|
|
54
|
+
function createAppleTouchIcon(projectName) {
|
|
55
|
+
const letter = projectName.charAt(0).toUpperCase();
|
|
56
|
+
|
|
57
|
+
return `<svg width="180" height="180" xmlns="http://www.w3.org/2000/svg">
|
|
58
|
+
<defs>
|
|
59
|
+
<linearGradient id="appleGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
60
|
+
<stop offset="0%" style="stop-color:#3b82f6;stop-opacity:1" />
|
|
61
|
+
<stop offset="100%" style="stop-color:#8b5cf6;stop-opacity:1" />
|
|
62
|
+
</linearGradient>
|
|
63
|
+
</defs>
|
|
64
|
+
<rect width="180" height="180" rx="40" fill="url(#appleGrad)"/>
|
|
65
|
+
<text x="90" y="130" font-family="Arial, sans-serif" font-size="100" font-weight="bold" fill="white" text-anchor="middle">
|
|
66
|
+
${letter}
|
|
67
|
+
</text>
|
|
68
|
+
</svg>`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Generate Android Chrome icon (192x192px)
|
|
73
|
+
*/
|
|
74
|
+
function createAndroidIcon(projectName) {
|
|
75
|
+
const letter = projectName.charAt(0).toUpperCase();
|
|
76
|
+
|
|
77
|
+
return `<svg width="192" height="192" xmlns="http://www.w3.org/2000/svg">
|
|
78
|
+
<defs>
|
|
79
|
+
<linearGradient id="androidGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
80
|
+
<stop offset="0%" style="stop-color:#3b82f6;stop-opacity:1" />
|
|
81
|
+
<stop offset="100%" style="stop-color:#8b5cf6;stop-opacity:1" />
|
|
82
|
+
</linearGradient>
|
|
83
|
+
</defs>
|
|
84
|
+
<rect width="192" height="192" rx="48" fill="url(#androidGrad)"/>
|
|
85
|
+
<text x="96" y="140" font-family="Arial, sans-serif" font-size="110" font-weight="bold" fill="white" text-anchor="middle">
|
|
86
|
+
${letter}
|
|
87
|
+
</text>
|
|
88
|
+
</svg>`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Generate large Android Chrome icon (512x512px)
|
|
93
|
+
*/
|
|
94
|
+
function createAndroidIconLarge(projectName) {
|
|
95
|
+
const letter = projectName.charAt(0).toUpperCase();
|
|
96
|
+
|
|
97
|
+
return `<svg width="512" height="512" xmlns="http://www.w3.org/2000/svg">
|
|
98
|
+
<defs>
|
|
99
|
+
<linearGradient id="androidLargeGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
100
|
+
<stop offset="0%" style="stop-color:#3b82f6;stop-opacity:1" />
|
|
101
|
+
<stop offset="100%" style="stop-color:#8b5cf6;stop-opacity:1" />
|
|
102
|
+
</linearGradient>
|
|
103
|
+
</defs>
|
|
104
|
+
<rect width="512" height="512" rx="128" fill="url(#androidLargeGrad)"/>
|
|
105
|
+
<text x="256" y="370" font-family="Arial, sans-serif" font-size="300" font-weight="bold" fill="white" text-anchor="middle">
|
|
106
|
+
${letter}
|
|
107
|
+
</text>
|
|
108
|
+
</svg>`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Generate logo SVG
|
|
113
|
+
*/
|
|
114
|
+
function createLogo(projectName) {
|
|
115
|
+
const letter = projectName.charAt(0).toUpperCase();
|
|
116
|
+
|
|
117
|
+
return `<svg width="200" height="60" xmlns="http://www.w3.org/2000/svg">
|
|
118
|
+
<defs>
|
|
119
|
+
<linearGradient id="logoGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
120
|
+
<stop offset="0%" style="stop-color:#3b82f6;stop-opacity:1" />
|
|
121
|
+
<stop offset="100%" style="stop-color:#8b5cf6;stop-opacity:1" />
|
|
122
|
+
</linearGradient>
|
|
123
|
+
</defs>
|
|
124
|
+
<rect width="50" height="50" x="5" y="5" rx="10" fill="url(#logoGrad)"/>
|
|
125
|
+
<text x="30" y="42" font-family="Arial, sans-serif" font-size="32" font-weight="bold" fill="white" text-anchor="middle">
|
|
126
|
+
${letter}
|
|
127
|
+
</text>
|
|
128
|
+
<text x="70" y="40" font-family="Arial, sans-serif" font-size="24" font-weight="bold" fill="#1f2937">
|
|
129
|
+
${projectName}
|
|
130
|
+
</text>
|
|
131
|
+
</svg>`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
module.exports = {
|
|
135
|
+
createDefaultOGImage,
|
|
136
|
+
createFavicon,
|
|
137
|
+
createAppleTouchIcon,
|
|
138
|
+
createAndroidIcon,
|
|
139
|
+
createAndroidIconLarge,
|
|
140
|
+
createLogo,
|
|
141
|
+
};
|