create-ng-tailwind 2.0.2 → 3.0.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.
@@ -2,8 +2,7 @@ const fs = require('fs-extra');
2
2
  const path = require('path');
3
3
 
4
4
  async function createContactComponent(config) {
5
- const contactComponentTs = `import { Component, inject } from '@angular/core';
6
- import { CommonModule } from '@angular/common';
5
+ const contactComponentTs = `import { Component, OnInit, inject } from '@angular/core';
7
6
  import { ReactiveFormsModule, FormBuilder, Validators } from '@angular/forms';
8
7
  import { NgIconComponent, provideIcons } from '@ng-icons/core';
9
8
  import { TranslateModule } from '@ngx-translate/core';
@@ -16,11 +15,12 @@ import {
16
15
  heroPaperAirplane,
17
16
  } from '@ng-icons/heroicons/outline';
18
17
  import { ButtonComponent } from '@shared/components/button/button.component';
18
+ import { SeoService } from '@core/services/seo.service';
19
19
 
20
20
  @Component({
21
21
  selector: 'app-contact',
22
22
  standalone: true,
23
- imports: [CommonModule, ReactiveFormsModule, NgIconComponent, ButtonComponent, TranslateModule],
23
+ imports: [ReactiveFormsModule, NgIconComponent, ButtonComponent, TranslateModule],
24
24
  viewProviders: [provideIcons({
25
25
  heroEnvelope,
26
26
  heroMapPin,
@@ -30,12 +30,23 @@ import { ButtonComponent } from '@shared/components/button/button.component';
30
30
  })],
31
31
  templateUrl: './contact.component.html'
32
32
  })
33
- export class ContactComponent {
33
+ export class ContactComponent implements OnInit {
34
34
  private fb = inject(FormBuilder);
35
+ private seo = inject(SeoService);
35
36
 
36
37
  isSubmitting = false;
37
38
  submitted = false;
38
39
 
40
+ ngOnInit(): void {
41
+ // Set SEO meta tags for Contact page
42
+ this.seo.updateMeta({
43
+ title: 'Contact Us',
44
+ description: 'Get in touch with us. We would love to hear from you and answer any questions you may have.',
45
+ keywords: 'contact, support, get in touch, email',
46
+ ogType: 'website'
47
+ });
48
+ }
49
+
39
50
  contactForm = this.fb.group({
40
51
  name: ['', [Validators.required, Validators.minLength(2)]],
41
52
  email: ['', [Validators.required, Validators.email]],
@@ -68,11 +79,11 @@ export class ContactComponent {
68
79
  }`;
69
80
 
70
81
  const contactComponentHtml = `<!-- Hero Section -->
71
- <section class="relative py-20 bg-linear-to-br from-blue-600 via-cyan-600 to-teal-600 overflow-hidden">
82
+ <section class="relative py-20 bg-linear-to-br from-primary-600 via-secondary-600 to-accent-600 overflow-hidden">
72
83
  <div class="absolute inset-0 opacity-10">
73
84
  <div class="absolute top-0 -left-4 w-96 h-96 bg-white rounded-full mix-blend-multiply filter blur-3xl animate-blob"></div>
74
- <div class="absolute top-0 -right-4 w-96 h-96 bg-yellow-200 rounded-full mix-blend-multiply filter blur-3xl animate-blob animation-delay-2000"></div>
75
- <div class="absolute bottom-0 left-1/2 w-96 h-96 bg-green-200 rounded-full mix-blend-multiply filter blur-3xl animate-blob animation-delay-4000"></div>
85
+ <div class="absolute top-0 -right-4 w-96 h-96 bg-warning-200 rounded-full mix-blend-multiply filter blur-3xl animate-blob animation-delay-2000"></div>
86
+ <div class="absolute bottom-0 left-1/2 w-96 h-96 bg-success-200 rounded-full mix-blend-multiply filter blur-3xl animate-blob animation-delay-4000"></div>
76
87
  </div>
77
88
 
78
89
  <div class="relative container mx-auto px-4 text-center">
@@ -83,7 +94,7 @@ export class ContactComponent {
83
94
  <h1 class="text-5xl md:text-6xl lg:text-7xl font-bold text-white mb-6 drop-shadow-lg">
84
95
  {{ 'contact.title' | translate }}
85
96
  </h1>
86
- <p class="text-xl md:text-2xl text-cyan-50 max-w-3xl mx-auto leading-relaxed">
97
+ <p class="text-xl md:text-2xl text-secondary-50 max-w-3xl mx-auto leading-relaxed">
87
98
  {{ 'contact.subtitle' | translate }}
88
99
  </p>
89
100
  </div>
@@ -91,14 +102,14 @@ export class ContactComponent {
91
102
  </section>
92
103
 
93
104
  <!-- Main Content -->
94
- <div class="bg-linear-to-br from-gray-50 to-blue-50 py-16 -mt-8">
105
+ <div class="bg-linear-to-br from-gray-50 to-primary-50 py-16 -mt-8">
95
106
  <div class="container mx-auto px-4 sm:px-6 lg:px-8 max-w-6xl">
96
107
  <div class="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-12">
97
108
 
98
109
  <!-- Contact Form Card -->
99
110
  <div class="bg-white rounded-2xl shadow-xl p-8 border border-gray-100 hover:shadow-2xl transition-shadow animate-fade-in">
100
111
  <div class="mb-6">
101
- <div class="inline-flex items-center justify-center w-12 h-12 rounded-xl bg-linear-to-br from-blue-500 to-cyan-500 mb-4">
112
+ <div class="inline-flex items-center justify-center w-12 h-12 rounded-xl bg-linear-to-br from-primary-500 to-secondary-500 mb-4">
102
113
  <ng-icon name="heroPaperAirplane" size="24" style="color: white;"></ng-icon>
103
114
  </div>
104
115
  <h2 class="text-2xl font-bold text-gray-900">{{ 'contact.form.title' | translate }}</h2>
@@ -115,12 +126,13 @@ export class ContactComponent {
115
126
  id="name"
116
127
  type="text"
117
128
  formControlName="name"
129
+ autocomplete="name"
118
130
  placeholder="John Doe"
119
- class="w-full px-4 py-3 border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all"
120
- [class.border-red-500]="contactForm.get('name')?.invalid && contactForm.get('name')?.touched"
121
- [class.focus:ring-red-500]="contactForm.get('name')?.invalid && contactForm.get('name')?.touched">
131
+ class="w-full px-4 py-3 border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-primary-500 transition-all"
132
+ [class.border-danger-500]="contactForm.get('name')?.invalid && contactForm.get('name')?.touched"
133
+ [class.focus:ring-danger-500]="contactForm.get('name')?.invalid && contactForm.get('name')?.touched">
122
134
  @if (contactForm.get('name')?.invalid && contactForm.get('name')?.touched) {
123
- <div class="mt-2 text-sm text-red-600 flex items-center gap-1">
135
+ <div class="mt-2 text-sm text-danger-600 flex items-center gap-1">
124
136
  <svg class="h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
125
137
  <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
126
138
  </svg>
@@ -143,12 +155,13 @@ export class ContactComponent {
143
155
  id="email"
144
156
  type="email"
145
157
  formControlName="email"
158
+ autocomplete="email"
146
159
  placeholder="john@example.com"
147
- class="w-full px-4 py-3 border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all"
148
- [class.border-red-500]="contactForm.get('email')?.invalid && contactForm.get('email')?.touched"
149
- [class.focus:ring-red-500]="contactForm.get('email')?.invalid && contactForm.get('email')?.touched">
160
+ class="w-full px-4 py-3 border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-primary-500 transition-all"
161
+ [class.border-danger-500]="contactForm.get('email')?.invalid && contactForm.get('email')?.touched"
162
+ [class.focus:ring-danger-500]="contactForm.get('email')?.invalid && contactForm.get('email')?.touched">
150
163
  @if (contactForm.get('email')?.invalid && contactForm.get('email')?.touched) {
151
- <div class="mt-2 text-sm text-red-600 flex items-center gap-1">
164
+ <div class="mt-2 text-sm text-danger-600 flex items-center gap-1">
152
165
  <svg class="h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
153
166
  <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
154
167
  </svg>
@@ -172,11 +185,11 @@ export class ContactComponent {
172
185
  type="text"
173
186
  formControlName="subject"
174
187
  placeholder="How can we help?"
175
- class="w-full px-4 py-3 border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all"
176
- [class.border-red-500]="contactForm.get('subject')?.invalid && contactForm.get('subject')?.touched"
177
- [class.focus:ring-red-500]="contactForm.get('subject')?.invalid && contactForm.get('subject')?.touched">
188
+ class="w-full px-4 py-3 border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-primary-500 transition-all"
189
+ [class.border-danger-500]="contactForm.get('subject')?.invalid && contactForm.get('subject')?.touched"
190
+ [class.focus:ring-danger-500]="contactForm.get('subject')?.invalid && contactForm.get('subject')?.touched">
178
191
  @if (contactForm.get('subject')?.invalid && contactForm.get('subject')?.touched) {
179
- <div class="mt-2 text-sm text-red-600 flex items-center gap-1">
192
+ <div class="mt-2 text-sm text-danger-600 flex items-center gap-1">
180
193
  <svg class="h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
181
194
  <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
182
195
  </svg>
@@ -195,12 +208,11 @@ export class ContactComponent {
195
208
  formControlName="message"
196
209
  rows="5"
197
210
  placeholder="Tell us more about your inquiry..."
198
- class="w-full px-4 py-3 border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-blue-500 focus:border-blue-500 transition-all resize-vertical"
199
- [class.border-red-500]="contactForm.get('message')?.invalid && contactForm.get('message')?.touched"
200
- [class.focus:ring-red-500]="contactForm.get('message')?.invalid && contactForm.get('message')?.touched">
201
- </textarea>
211
+ class="w-full px-4 py-3 border-2 border-gray-200 rounded-xl focus:ring-2 focus:ring-primary-500 focus:border-primary-500 transition-all resize-vertical"
212
+ [class.border-danger-500]="contactForm.get('message')?.invalid && contactForm.get('message')?.touched"
213
+ [class.focus:ring-danger-500]="contactForm.get('message')?.invalid && contactForm.get('message')?.touched"></textarea>
202
214
  @if (contactForm.get('message')?.invalid && contactForm.get('message')?.touched) {
203
- <div class="mt-2 text-sm text-red-600 flex items-center gap-1">
215
+ <div class="mt-2 text-sm text-danger-600 flex items-center gap-1">
204
216
  <svg class="h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
205
217
  <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/>
206
218
  </svg>
@@ -216,10 +228,10 @@ export class ContactComponent {
216
228
 
217
229
  <!-- Success Message -->
218
230
  @if (submitted && !contactForm.invalid) {
219
- <div class="p-4 bg-linear-to-r from-green-50 to-emerald-50 border-2 border-green-300 rounded-xl animate-fade-in">
231
+ <div class="p-4 bg-linear-to-r from-success-50 to-success-100 border-2 border-success-300 rounded-xl animate-fade-in">
220
232
  <div class="flex items-center gap-2">
221
- <ng-icon name="heroCheckCircle" size="20" style="color: #059669;"></ng-icon>
222
- <p class="text-green-700 font-medium">
233
+ <ng-icon name="heroCheckCircle" size="20" style="color: var(--color-success-600);"></ng-icon>
234
+ <p class="text-success-700 font-medium">
223
235
  {{ 'contact.form.success' | translate }}
224
236
  </p>
225
237
  </div>
@@ -245,13 +257,13 @@ export class ContactComponent {
245
257
  <!-- Contact Info & Resources -->
246
258
  <div class="space-y-6 animate-fade-in animation-delay-200">
247
259
  <!-- Contact Information Card -->
248
- <div class="bg-linear-to-br from-blue-600 to-cyan-600 rounded-2xl shadow-xl p-8 text-white">
260
+ <div class="bg-linear-to-br from-primary-600 to-secondary-600 rounded-2xl shadow-xl p-8 text-white">
249
261
  <div class="mb-6">
250
262
  <div class="inline-flex items-center justify-center w-12 h-12 rounded-xl bg-white/20 backdrop-blur-sm mb-4">
251
263
  <ng-icon name="heroMapPin" size="24" style="color: white;"></ng-icon>
252
264
  </div>
253
265
  <h2 class="text-2xl font-bold">{{ 'contact.info.title' | translate }}</h2>
254
- <p class="text-cyan-100 mt-1">{{ 'contact.info.description' | translate }}</p>
266
+ <p class="text-primary-100 mt-1">{{ 'contact.info.description' | translate }}</p>
255
267
  </div>
256
268
 
257
269
  <div class="space-y-6">
@@ -263,8 +275,8 @@ export class ContactComponent {
263
275
  </div>
264
276
  <div>
265
277
  <h3 class="text-lg font-semibold mb-1">{{ 'contact.info.email.label' | translate }}</h3>
266
- <p class="text-cyan-100">hello&#64;example.com</p>
267
- <p class="text-sm text-cyan-200 mt-1">{{ 'contact.info.email.description' | translate }}</p>
278
+ <p class="text-primary-100">hello&#64;example.com</p>
279
+ <p class="text-sm text-primary-200 mt-1">{{ 'contact.info.email.description' | translate }}</p>
268
280
  </div>
269
281
  </div>
270
282
 
@@ -276,8 +288,8 @@ export class ContactComponent {
276
288
  </div>
277
289
  <div>
278
290
  <h3 class="text-lg font-semibold mb-1">{{ 'contact.info.location.label' | translate }}</h3>
279
- <p class="text-cyan-100">{{ 'contact.info.location.value' | translate }}</p>
280
- <p class="text-sm text-cyan-200 mt-1">{{ 'contact.info.location.description' | translate }}</p>
291
+ <p class="text-primary-100">{{ 'contact.info.location.value' | translate }}</p>
292
+ <p class="text-sm text-primary-200 mt-1">{{ 'contact.info.location.description' | translate }}</p>
281
293
  </div>
282
294
  </div>
283
295
 
@@ -289,8 +301,8 @@ export class ContactComponent {
289
301
  </div>
290
302
  <div>
291
303
  <h3 class="text-lg font-semibold mb-1">{{ 'contact.info.responseTime.label' | translate }}</h3>
292
- <p class="text-cyan-100">{{ 'contact.info.responseTime.value' | translate }}</p>
293
- <p class="text-sm text-cyan-200 mt-1">{{ 'contact.info.responseTime.description' | translate }}</p>
304
+ <p class="text-primary-100">{{ 'contact.info.responseTime.value' | translate }}</p>
305
+ <p class="text-sm text-primary-200 mt-1">{{ 'contact.info.responseTime.description' | translate }}</p>
294
306
  </div>
295
307
  </div>
296
308
  </div>
@@ -299,7 +311,7 @@ export class ContactComponent {
299
311
  <!-- Resources Card -->
300
312
  <div class="bg-white rounded-2xl shadow-xl p-8 border border-gray-100">
301
313
  <div class="mb-6">
302
- <div class="inline-flex items-center justify-center w-12 h-12 rounded-xl bg-linear-to-br from-purple-500 to-pink-500 mb-4">
314
+ <div class="inline-flex items-center justify-center w-12 h-12 rounded-xl bg-linear-to-br from-accent-500 to-secondary-500 mb-4">
303
315
  <svg class="h-6 w-6 text-white" fill="none" stroke="currentColor" viewBox="0 0 24 24">
304
316
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253"></path>
305
317
  </svg>
@@ -310,31 +322,31 @@ export class ContactComponent {
310
322
 
311
323
  <ul class="space-y-3">
312
324
  <li>
313
- <a href="https://angular.dev/" target="_blank" class="group flex items-center justify-between p-3 rounded-xl hover:bg-blue-50 transition-colors">
325
+ <a href="https://angular.dev/" target="_blank" class="group flex items-center justify-between p-3 rounded-xl hover:bg-primary-50 transition-colors">
314
326
  <div class="flex items-center gap-3">
315
- <div class="h-10 w-10 bg-red-100 rounded-lg flex items-center justify-center group-hover:scale-110 transition-transform">
316
- <svg class="h-5 w-5 text-red-600" fill="currentColor" viewBox="0 0 24 24">
327
+ <div class="h-10 w-10 bg-danger-100 rounded-lg flex items-center justify-center group-hover:scale-110 transition-transform">
328
+ <svg class="h-5 w-5 text-danger-600" fill="currentColor" viewBox="0 0 24 24">
317
329
  <path d="M12 2L2 6l10 5 10-5-10-4zm0 18.5l-7-3.5v-6l7 3.5 7-3.5v6l-7 3.5z"/>
318
330
  </svg>
319
331
  </div>
320
- <span class="font-medium text-gray-700 group-hover:text-blue-600 transition-colors">{{ 'contact.help.links.angular' | translate }}</span>
332
+ <span class="font-medium text-gray-700 group-hover:text-primary-600 transition-colors">{{ 'contact.help.links.angular' | translate }}</span>
321
333
  </div>
322
- <svg class="h-5 w-5 text-gray-400 group-hover:text-blue-600 group-hover:translate-x-1 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
334
+ <svg class="h-5 w-5 text-gray-400 group-hover:text-primary-600 group-hover:translate-x-1 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
323
335
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
324
336
  </svg>
325
337
  </a>
326
338
  </li>
327
339
  <li>
328
- <a href="https://tailwindcss.com/" target="_blank" class="group flex items-center justify-between p-3 rounded-xl hover:bg-blue-50 transition-colors">
340
+ <a href="https://tailwindcss.com/" target="_blank" class="group flex items-center justify-between p-3 rounded-xl hover:bg-primary-50 transition-colors">
329
341
  <div class="flex items-center gap-3">
330
- <div class="h-10 w-10 bg-cyan-100 rounded-lg flex items-center justify-center group-hover:scale-110 transition-transform">
331
- <svg class="h-5 w-5 text-cyan-600" fill="currentColor" viewBox="0 0 24 24">
342
+ <div class="h-10 w-10 bg-secondary-100 rounded-lg flex items-center justify-center group-hover:scale-110 transition-transform">
343
+ <svg class="h-5 w-5 text-secondary-600" fill="currentColor" viewBox="0 0 24 24">
332
344
  <path d="M12 6c-2.67 0-4.33 1.33-5 4 1-1.33 2.17-1.83 3.5-1.5.76.19 1.31.75 1.91 1.36.98 1 2.09 2.14 4.59 2.14 2.67 0 4.33-1.33 5-4-1 1.33-2.17 1.83-3.5 1.5-.76-.19-1.3-.75-1.91-1.36C15.61 7.14 14.5 6 12 6zM7 12c-2.67 0-4.33 1.33-5 4 1-1.33 2.17-1.83 3.5-1.5.76.19 1.3.75 1.91 1.36C8.39 16.86 9.5 18 12 18c2.67 0 4.33-1.33 5-4-1 1.33-2.17 1.83-3.5 1.5-.76-.19-1.3-.75-1.91-1.36C10.61 13.14 9.5 12 7 12z"/>
333
345
  </svg>
334
346
  </div>
335
- <span class="font-medium text-gray-700 group-hover:text-blue-600 transition-colors">{{ 'contact.help.links.tailwind' | translate }}</span>
347
+ <span class="font-medium text-gray-700 group-hover:text-primary-600 transition-colors">{{ 'contact.help.links.tailwind' | translate }}</span>
336
348
  </div>
337
- <svg class="h-5 w-5 text-gray-400 group-hover:text-blue-600 group-hover:translate-x-1 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
349
+ <svg class="h-5 w-5 text-gray-400 group-hover:text-primary-600 group-hover:translate-x-1 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
338
350
  <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
339
351
  </svg>
340
352
  </a>
@@ -499,18 +511,19 @@ export const appConfig: ApplicationConfig = {
499
511
  }
500
512
 
501
513
  async function createAppComponent(config) {
502
- const app = `import { Component, inject } from '@angular/core';
503
- import { CommonModule } from '@angular/common';
514
+ const app = `import { Component, OnInit, inject } from '@angular/core';
504
515
  import { RouterOutlet } from '@angular/router';
505
516
  import { HeaderComponent } from './layout/header/header.component';
506
517
  import { FooterComponent } from './layout/footer/footer.component';
507
518
  import { ToastComponent } from '@shared/components/toast/toast.component';
508
519
  import { ModalComponent } from '@shared/components/modal/modal.component';
509
520
  import { TranslationService } from '@core/i18n/translation.service';
521
+ import { SeoService } from '@core/services/seo.service';
522
+ import { createOrganizationSchema, createWebSiteSchema } from '@core/utils/structured-data';
510
523
 
511
524
  @Component({
512
525
  selector: 'app-root',
513
- imports: [CommonModule, RouterOutlet, HeaderComponent, FooterComponent, ToastComponent, ModalComponent],
526
+ imports: [RouterOutlet, HeaderComponent, FooterComponent, ToastComponent, ModalComponent],
514
527
  template: \`
515
528
  <div class="min-h-screen flex flex-col bg-white">
516
529
  <app-header />
@@ -529,10 +542,39 @@ import { TranslationService } from '@core/i18n/translation.service';
529
542
  </div>
530
543
  \`
531
544
  })
532
- export class App {
533
- // Inject translation service to initialize it (auto-detects and loads language)
545
+ export class App implements OnInit {
546
+ // Inject services
534
547
  private translationService = inject(TranslationService);
548
+ private seo = inject(SeoService);
549
+
535
550
  title = '${config.projectName}';
551
+
552
+ ngOnInit(): void {
553
+ // Add global Organization structured data
554
+ const organizationData = createOrganizationSchema({
555
+ name: '${config.projectName}',
556
+ url: 'https://example.com', // TODO: Update with your production URL
557
+ logo: '/assets/images/logo.svg',
558
+ description: 'A modern Angular application built with best practices',
559
+ sameAs: [
560
+ // 'https://twitter.com/yourhandle',
561
+ // 'https://facebook.com/yourpage',
562
+ // 'https://linkedin.com/company/yourcompany'
563
+ ]
564
+ });
565
+
566
+ const websiteData = createWebSiteSchema({
567
+ name: '${config.projectName}',
568
+ url: 'https://example.com', // TODO: Update with your production URL
569
+ description: 'A modern Angular application with Tailwind CSS, SEO optimization, and i18n support'
570
+ });
571
+
572
+ // Add structured data to the page
573
+ this.seo.addStructuredData({
574
+ '@context': 'https://schema.org',
575
+ '@graph': [organizationData, websiteData]
576
+ });
577
+ }
536
578
  }`;
537
579
 
538
580
  const appHtml = `<!-- This file is not used in standalone components -->
@@ -544,15 +586,140 @@ export class App {
544
586
  }
545
587
 
546
588
  async function createStyles(config) {
547
- // Main styles
589
+ // Main styles with Tailwind v4 theme system
548
590
  const mainStyles = `@import "tailwindcss";
549
591
 
550
- /* Custom CSS variables for theming */
551
- :root {
552
- --color-primary: 59 130 246; /* blue-500 */
553
- --color-primary-dark: 37 99 235; /* blue-600 */
592
+ /* ============================================
593
+ TAILWIND V4 THEME CONFIGURATION
594
+ ============================================
595
+
596
+ This section defines the theme colors for your application.
597
+ Tailwind v4 uses the @theme directive to create custom color scales
598
+ that automatically generate utility classes (bg-*, text-*, border-*, etc.)
599
+
600
+ To customize your theme:
601
+ 1. Change the color values below to match your brand
602
+ 2. All components will automatically use the new colors
603
+ 3. Color format: Use hex colors (e.g., #3b82f6) or rgb (e.g., rgb(59 130 246))
604
+
605
+ Example: Change primary-500 from #3b82f6 to #8b5cf6 for purple theme
606
+ */
607
+
608
+ @theme {
609
+ /* PRIMARY COLOR SCALE - Main brand color */
610
+ --color-primary-50: #eff6ff;
611
+ --color-primary-100: #dbeafe;
612
+ --color-primary-200: #bfdbfe;
613
+ --color-primary-300: #93c5fd;
614
+ --color-primary-400: #60a5fa;
615
+ --color-primary-500: #3b82f6; /* Main primary color - CHANGE THIS for your brand */
616
+ --color-primary-600: #2563eb; /* Primary hover state */
617
+ --color-primary-700: #1d4ed8;
618
+ --color-primary-800: #1e40af;
619
+ --color-primary-900: #1e3a8a;
620
+ --color-primary-950: #172554;
621
+
622
+ /* SECONDARY COLOR SCALE - Secondary brand color */
623
+ --color-secondary-50: #ecfeff;
624
+ --color-secondary-100: #cffafe;
625
+ --color-secondary-200: #a5f3fc;
626
+ --color-secondary-300: #67e8f9;
627
+ --color-secondary-400: #22d3ee;
628
+ --color-secondary-500: #06b6d4; /* Main secondary color - CHANGE THIS for your brand */
629
+ --color-secondary-600: #0891b2; /* Secondary hover state */
630
+ --color-secondary-700: #0e7490;
631
+ --color-secondary-800: #155e75;
632
+ --color-secondary-900: #164e63;
633
+ --color-secondary-950: #083344;
634
+
635
+ /* ACCENT COLOR SCALE - Accent/highlight color */
636
+ --color-accent-50: #faf5ff;
637
+ --color-accent-100: #f3e8ff;
638
+ --color-accent-200: #e9d5ff;
639
+ --color-accent-300: #d8b4fe;
640
+ --color-accent-400: #c084fc;
641
+ --color-accent-500: #a855f7; /* Main accent color */
642
+ --color-accent-600: #9333ea; /* Accent hover state */
643
+ --color-accent-700: #7e22ce;
644
+ --color-accent-800: #6b21a8;
645
+ --color-accent-900: #581c87;
646
+ --color-accent-950: #3b0764;
647
+
648
+ /* SUCCESS COLOR SCALE - For success states */
649
+ --color-success-50: #f0fdf4;
650
+ --color-success-100: #dcfce7;
651
+ --color-success-200: #bbf7d0;
652
+ --color-success-300: #86efac;
653
+ --color-success-400: #4ade80;
654
+ --color-success-500: #22c55e; /* Main success color */
655
+ --color-success-600: #16a34a;
656
+ --color-success-700: #15803d;
657
+ --color-success-800: #166534;
658
+ --color-success-900: #14532d;
659
+ --color-success-950: #052e16;
660
+
661
+ /* DANGER COLOR SCALE - For error/danger states */
662
+ --color-danger-50: #fef2f2;
663
+ --color-danger-100: #fee2e2;
664
+ --color-danger-200: #fecaca;
665
+ --color-danger-300: #fca5a5;
666
+ --color-danger-400: #f87171;
667
+ --color-danger-500: #ef4444; /* Main danger color */
668
+ --color-danger-600: #dc2626;
669
+ --color-danger-700: #b91c1c;
670
+ --color-danger-800: #991b1b;
671
+ --color-danger-900: #7f1d1d;
672
+ --color-danger-950: #450a0a;
673
+
674
+ /* WARNING COLOR SCALE - For warning states */
675
+ --color-warning-50: #fffbeb;
676
+ --color-warning-100: #fef3c7;
677
+ --color-warning-200: #fde68a;
678
+ --color-warning-300: #fcd34d;
679
+ --color-warning-400: #fbbf24;
680
+ --color-warning-500: #f59e0b; /* Main warning color */
681
+ --color-warning-600: #d97706;
682
+ --color-warning-700: #b45309;
683
+ --color-warning-800: #92400e;
684
+ --color-warning-900: #78350f;
685
+ --color-warning-950: #451a03;
686
+
687
+ /* INFO COLOR SCALE - For informational states */
688
+ --color-info-50: #eff6ff;
689
+ --color-info-100: #dbeafe;
690
+ --color-info-200: #bfdbfe;
691
+ --color-info-300: #93c5fd;
692
+ --color-info-400: #60a5fa;
693
+ --color-info-500: #3b82f6; /* Main info color */
694
+ --color-info-600: #2563eb;
695
+ --color-info-700: #1d4ed8;
696
+ --color-info-800: #1e40af;
697
+ --color-info-900: #1e3a8a;
698
+ --color-info-950: #172554;
554
699
  }
555
700
 
701
+ /* ============================================
702
+ USAGE EXAMPLES
703
+ ============================================
704
+
705
+ After defining colors in @theme, you can use them in your templates:
706
+
707
+ - bg-primary-500 (primary background)
708
+ - text-primary-600 (primary text)
709
+ - border-primary-500 (primary border)
710
+ - hover:bg-primary-600 (primary hover)
711
+ - focus:ring-primary-500 (primary focus ring)
712
+
713
+ - bg-secondary-500 (secondary background)
714
+ - bg-accent-500 (accent background)
715
+ - bg-success-500 (success background)
716
+ - bg-danger-500 (danger background)
717
+ - bg-warning-500 (warning background)
718
+ - bg-info-500 (info background)
719
+
720
+ All Tailwind utilities work: bg-*, text-*, border-*, ring-*, etc.
721
+ */
722
+
556
723
  /* RTL Support */
557
724
  html[dir="rtl"] {
558
725
  direction: rtl;
@@ -615,9 +782,9 @@ html[dir="rtl"] .text-right {
615
782
  @apply bg-gray-400;
616
783
  }
617
784
 
618
- /* Focus styles for accessibility */
785
+ /* Focus styles for accessibility - now using theme colors */
619
786
  .focus-visible {
620
- @apply focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2;
787
+ @apply focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2;
621
788
  }
622
789
 
623
790
  /* Animation classes */