create-ng-tailwind 3.1.0 → 4.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +81 -350
  2. package/README.md +93 -157
  3. package/lib/cli/index.js +29 -3
  4. package/lib/cli/interactive.js +26 -1
  5. package/lib/managers/ProjectManager.js +0 -4
  6. package/lib/templates/base/components.js +243 -0
  7. package/lib/templates/base/index.js +207 -0
  8. package/lib/templates/base/infrastructure.js +314 -0
  9. package/lib/templates/base/linting.js +359 -0
  10. package/lib/templates/base/pwa.js +103 -0
  11. package/lib/templates/base/services.js +362 -0
  12. package/lib/templates/blog/app.js +250 -0
  13. package/lib/templates/blog/components.js +360 -0
  14. package/lib/templates/blog/i18n.js +77 -0
  15. package/lib/templates/blog/index.js +126 -0
  16. package/lib/templates/blog/pages.js +554 -0
  17. package/lib/templates/blog/services.js +390 -0
  18. package/lib/templates/dashboard/app.js +320 -0
  19. package/lib/templates/dashboard/charts.js +305 -0
  20. package/lib/templates/dashboard/components.js +410 -0
  21. package/lib/templates/dashboard/i18n.js +340 -0
  22. package/lib/templates/dashboard/index.js +141 -0
  23. package/lib/templates/dashboard/layout.js +310 -0
  24. package/lib/templates/dashboard/pages.js +681 -0
  25. package/lib/templates/ecommerce/app.js +315 -0
  26. package/lib/templates/ecommerce/components.js +496 -0
  27. package/lib/templates/ecommerce/i18n.js +389 -0
  28. package/lib/templates/ecommerce/index.js +152 -0
  29. package/lib/templates/ecommerce/layout.js +270 -0
  30. package/lib/templates/ecommerce/pages.js +969 -0
  31. package/lib/templates/ecommerce/services.js +300 -0
  32. package/lib/templates/index.js +12 -0
  33. package/lib/templates/landing/index.js +1117 -0
  34. package/lib/templates/portfolio/index.js +1160 -0
  35. package/lib/templates/saas/index.js +1371 -0
  36. package/lib/templates/starter/app.js +364 -0
  37. package/lib/templates/starter/i18n.js +856 -0
  38. package/lib/templates/starter/index.js +52 -4060
  39. package/lib/templates/starter/layout.js +852 -0
  40. package/lib/templates/starter/pages.js +1241 -0
  41. package/package.json +1 -1
  42. package/lib/templates/starter/features.js +0 -867
  43. package/lib/utils/ai-config.js +0 -641
  44. /package/lib/templates/{starter → base}/advanced-features.js +0 -0
  45. /package/lib/templates/{starter → base}/seo-assets.js +0 -0
  46. /package/lib/templates/{starter → base}/seo-features.js +0 -0
  47. /package/lib/templates/{starter → base}/ui-features.js +0 -0
@@ -0,0 +1,1117 @@
1
+ const fs = require('fs-extra');
2
+ const path = require('path');
3
+ const starter = require('../starter');
4
+
5
+ /**
6
+ * Landing Page Template
7
+ * Single-page marketing template with:
8
+ * - Animated hero section
9
+ * - Feature highlights
10
+ * - How it works section
11
+ * - Testimonials
12
+ * - Pricing section
13
+ * - FAQ
14
+ * - CTA sections
15
+ * - Newsletter signup
16
+ */
17
+ const landing = {
18
+ info: {
19
+ name: 'Landing Page',
20
+ description:
21
+ 'Marketing landing page with hero, features, testimonials, pricing, and CTA sections',
22
+ features: [
23
+ ...starter.info.features,
24
+ 'Animated hero with CTA',
25
+ 'Feature cards with icons',
26
+ 'How it works section',
27
+ 'Social proof / logos',
28
+ 'Testimonials carousel',
29
+ 'Pricing comparison',
30
+ 'FAQ accordion',
31
+ 'Newsletter signup',
32
+ 'Sticky navigation',
33
+ ],
34
+ },
35
+
36
+ async apply(config, spinner) {
37
+ const chalk = require('chalk');
38
+
39
+ const completeStep = (message) => {
40
+ if (spinner) {
41
+ spinner.stop();
42
+ console.log(chalk.green(' ✔') + chalk.white(' ' + message));
43
+ spinner.start();
44
+ }
45
+ };
46
+
47
+ // Step 1: Apply starter template
48
+ if (spinner) spinner.update('Setting up starter foundation...');
49
+ await starter.apply(config, null);
50
+
51
+ // Step 2: Create landing-specific directories
52
+ if (spinner) spinner.update('Setting up landing page structure...');
53
+ await this.createDirectories(config);
54
+
55
+ // Step 3: Create components
56
+ await this.createComponents(config);
57
+
58
+ // Step 4: Create landing page
59
+ await this.createLandingPage(config);
60
+
61
+ // Step 5: Update layout (sticky header)
62
+ await this.updateLayout(config);
63
+
64
+ // Step 6: Update routing
65
+ await this.createRouting(config);
66
+
67
+ // Step 7: Add i18n translations
68
+ await this.updateI18n(config);
69
+
70
+ // Step 8: Format code
71
+ const base = require('../base');
72
+ await base.formatCode(config);
73
+
74
+ if (spinner) spinner.stop();
75
+
76
+ console.log('');
77
+ completeStep('Landing page template created');
78
+ completeStep('Hero section with animations');
79
+ completeStep('Features & benefits');
80
+ completeStep('Pricing section');
81
+ completeStep('Testimonials');
82
+ completeStep('FAQ & Newsletter');
83
+ console.log('');
84
+ },
85
+
86
+ async createDirectories(config) {
87
+ const directories = [
88
+ 'src/app/features/landing',
89
+ 'src/app/shared/components/hero',
90
+ 'src/app/shared/components/features-section',
91
+ 'src/app/shared/components/how-it-works',
92
+ 'src/app/shared/components/social-proof',
93
+ 'src/app/shared/components/pricing-section',
94
+ 'src/app/shared/components/testimonials-section',
95
+ 'src/app/shared/components/faq-section',
96
+ 'src/app/shared/components/newsletter',
97
+ 'src/app/shared/components/cta-section',
98
+ ];
99
+
100
+ for (const dir of directories) {
101
+ await fs.ensureDir(path.join(config.fullPath, dir));
102
+ }
103
+ },
104
+
105
+ async createComponents(config) {
106
+ // Hero Component
107
+ const hero = `import { Component, Input } from '@angular/core';
108
+ import { RouterModule } from '@angular/router';
109
+ import { NgIconComponent, provideIcons } from '@ng-icons/core';
110
+ import { heroPlay, heroArrowRight } from '@ng-icons/heroicons/outline';
111
+ import { heroStarSolid } from '@ng-icons/heroicons/solid';
112
+
113
+ @Component({
114
+ selector: 'app-hero',
115
+ standalone: true,
116
+ imports: [RouterModule, NgIconComponent],
117
+ viewProviders: [provideIcons({ heroPlay, heroArrowRight, heroStarSolid })],
118
+ template: \`
119
+ <section class="relative min-h-screen overflow-hidden bg-linear-to-br from-slate-900 via-purple-900 to-slate-900">
120
+ <!-- Animated Background -->
121
+ <div class="absolute inset-0">
122
+ <div class="animate-blob absolute -left-4 top-20 h-96 w-96 rounded-full bg-purple-500 opacity-20 mix-blend-multiply blur-3xl"></div>
123
+ <div class="animate-blob animation-delay-2000 absolute right-0 top-0 h-96 w-96 rounded-full bg-cyan-500 opacity-20 mix-blend-multiply blur-3xl"></div>
124
+ <div class="animate-blob animation-delay-4000 absolute bottom-0 left-1/3 h-96 w-96 rounded-full bg-pink-500 opacity-20 mix-blend-multiply blur-3xl"></div>
125
+ </div>
126
+
127
+ <!-- Grid Pattern -->
128
+ <div class="absolute inset-0 bg-[linear-gradient(to_right,#ffffff08_1px,transparent_1px),linear-gradient(to_bottom,#ffffff08_1px,transparent_1px)] bg-[size:4rem_4rem]"></div>
129
+
130
+ <div class="relative mx-auto max-w-7xl px-4 py-20 sm:px-6 lg:px-8 lg:py-32">
131
+ <div class="text-center">
132
+ <!-- Badge -->
133
+ <div class="mb-8 inline-flex items-center gap-2 rounded-full bg-white/10 px-4 py-2 backdrop-blur-sm">
134
+ <span class="relative flex h-2 w-2">
135
+ <span class="absolute inline-flex h-full w-full animate-ping rounded-full bg-green-400 opacity-75"></span>
136
+ <span class="relative inline-flex h-2 w-2 rounded-full bg-green-400"></span>
137
+ </span>
138
+ <span class="text-sm font-medium text-white">{{ badge }}</span>
139
+ </div>
140
+
141
+ <!-- Headline -->
142
+ <h1 class="mx-auto max-w-4xl text-4xl font-bold tracking-tight text-white sm:text-6xl lg:text-7xl">
143
+ {{ title }}
144
+ <span class="bg-gradient-to-r from-cyan-400 to-purple-400 bg-clip-text text-transparent">{{ highlight }}</span>
145
+ </h1>
146
+
147
+ <!-- Subtitle -->
148
+ <p class="mx-auto mt-6 max-w-2xl text-lg text-gray-300 sm:text-xl">
149
+ {{ subtitle }}
150
+ </p>
151
+
152
+ <!-- CTA Buttons -->
153
+ <div class="mt-10 flex flex-wrap justify-center gap-4">
154
+ <a
155
+ [routerLink]="primaryCta.link"
156
+ class="group inline-flex items-center gap-2 rounded-full bg-white px-8 py-4 font-semibold text-gray-900 shadow-xl transition-all hover:shadow-2xl hover:scale-105">
157
+ {{ primaryCta.text }}
158
+ <ng-icon name="heroArrowRight" size="20" class="transition-transform group-hover:translate-x-1"></ng-icon>
159
+ </a>
160
+ @if (secondaryCta) {
161
+ <button
162
+ (click)="onWatchDemo()"
163
+ class="inline-flex items-center gap-2 rounded-full border-2 border-white/30 px-8 py-4 font-semibold text-white backdrop-blur-sm transition-all hover:bg-white/10">
164
+ <ng-icon name="heroPlay" size="20"></ng-icon>
165
+ {{ secondaryCta.text }}
166
+ </button>
167
+ }
168
+ </div>
169
+
170
+ <!-- Social Proof -->
171
+ <div class="mt-16">
172
+ <p class="mb-4 text-sm text-gray-400">Trusted by 10,000+ companies worldwide</p>
173
+ <div class="flex flex-wrap justify-center items-center gap-8 opacity-60">
174
+ @for (logo of logos; track logo) {
175
+ <div class="text-2xl font-bold text-white">{{ logo }}</div>
176
+ }
177
+ </div>
178
+ </div>
179
+
180
+ <!-- Stats -->
181
+ <div class="mt-16 grid grid-cols-2 gap-8 sm:grid-cols-4">
182
+ @for (stat of stats; track stat.label) {
183
+ <div class="text-center">
184
+ <div class="text-3xl font-bold text-white sm:text-4xl">{{ stat.value }}</div>
185
+ <div class="mt-1 text-sm text-gray-400">{{ stat.label }}</div>
186
+ </div>
187
+ }
188
+ </div>
189
+ </div>
190
+ </div>
191
+
192
+ <!-- Scroll Indicator -->
193
+ <div class="absolute bottom-8 left-1/2 -translate-x-1/2">
194
+ <div class="flex flex-col items-center gap-2">
195
+ <span class="text-xs text-gray-400">Scroll to explore</span>
196
+ <div class="h-12 w-6 rounded-full border-2 border-white/30 p-1">
197
+ <div class="h-2 w-2 animate-bounce rounded-full bg-white"></div>
198
+ </div>
199
+ </div>
200
+ </div>
201
+ </section>
202
+ \`,
203
+ })
204
+ export class HeroComponent {
205
+ @Input() badge = 'New Feature Available';
206
+ @Input() title = 'Build something ';
207
+ @Input() highlight = 'amazing today';
208
+ @Input() subtitle = 'The all-in-one platform to build, launch, and grow your digital products faster than ever.';
209
+ @Input() primaryCta = { text: 'Get Started Free', link: '/auth/register' };
210
+ @Input() secondaryCta?: { text: string } = { text: 'Watch Demo' };
211
+ @Input() logos = ['Google', 'Microsoft', 'Airbnb', 'Spotify', 'Slack'];
212
+ @Input() stats = [
213
+ { value: '10K+', label: 'Active Users' },
214
+ { value: '99.9%', label: 'Uptime' },
215
+ { value: '50M+', label: 'API Requests' },
216
+ { value: '4.9/5', label: 'User Rating' },
217
+ ];
218
+
219
+ onWatchDemo(): void {
220
+ // Open video modal or navigate to demo
221
+ console.log('Watch demo clicked');
222
+ }
223
+ }`;
224
+
225
+ await fs.writeFile(
226
+ path.join(config.fullPath, 'src/app/shared/components/hero/hero.component.ts'),
227
+ hero
228
+ );
229
+
230
+ // Features Section
231
+ const features = `import { Component, Input } from '@angular/core';
232
+ import { NgIconComponent, provideIcons } from '@ng-icons/core';
233
+ import {
234
+ heroBolt,
235
+ heroShieldCheck,
236
+ heroChartBar,
237
+ heroCog,
238
+ heroUsers,
239
+ heroGlobeAlt,
240
+ } from '@ng-icons/heroicons/outline';
241
+
242
+ export interface Feature {
243
+ icon: string;
244
+ title: string;
245
+ description: string;
246
+ iconBg?: string;
247
+ iconColor?: string;
248
+ }
249
+
250
+ @Component({
251
+ selector: 'app-features-section',
252
+ standalone: true,
253
+ imports: [NgIconComponent],
254
+ viewProviders: [provideIcons({ heroBolt, heroShieldCheck, heroChartBar, heroCog, heroUsers, heroGlobeAlt })],
255
+ template: \`
256
+ <section class="py-24 bg-white" [id]="sectionId">
257
+ <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
258
+ <div class="text-center mb-16">
259
+ <span class="text-primary-600 font-semibold text-sm uppercase tracking-wider">{{ badge }}</span>
260
+ <h2 class="mt-2 text-3xl font-bold text-gray-900 sm:text-4xl">{{ title }}</h2>
261
+ <p class="mt-4 text-xl text-gray-600 max-w-2xl mx-auto">{{ subtitle }}</p>
262
+ </div>
263
+
264
+ <div class="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
265
+ @for (feature of features; track feature.title) {
266
+ <div class="group rounded-2xl border border-gray-200 bg-white p-8 transition-all hover:shadow-xl hover:-translate-y-1 hover:border-primary-200">
267
+ <div
268
+ class="flex h-14 w-14 items-center justify-center rounded-xl transition-colors"
269
+ [class]="feature.iconBg || 'bg-primary-100 group-hover:bg-primary-500'">
270
+ <ng-icon
271
+ [name]="feature.icon"
272
+ size="28"
273
+ [class]="feature.iconColor || 'text-primary-600 group-hover:text-white'">
274
+ </ng-icon>
275
+ </div>
276
+ <h3 class="mt-6 text-xl font-bold text-gray-900">{{ feature.title }}</h3>
277
+ <p class="mt-2 text-gray-600">{{ feature.description }}</p>
278
+ </div>
279
+ }
280
+ </div>
281
+ </div>
282
+ </section>
283
+ \`,
284
+ })
285
+ export class FeaturesSectionComponent {
286
+ @Input() sectionId = 'features';
287
+ @Input() badge = 'Features';
288
+ @Input() title = 'Everything you need';
289
+ @Input() subtitle = 'Powerful features to help you build, launch, and scale faster';
290
+ @Input() features: Feature[] = [
291
+ { icon: 'heroBolt', title: 'Lightning Fast', description: 'Optimized for speed with sub-millisecond response times and global CDN distribution.' },
292
+ { icon: 'heroShieldCheck', title: 'Secure by Default', description: 'Enterprise-grade security with end-to-end encryption and SOC 2 compliance.' },
293
+ { icon: 'heroChartBar', title: 'Advanced Analytics', description: 'Real-time insights and custom dashboards to track your key metrics.' },
294
+ { icon: 'heroCog', title: 'Easy Integration', description: 'Connect with 100+ tools through our REST API and native integrations.' },
295
+ { icon: 'heroUsers', title: 'Team Collaboration', description: 'Real-time collaboration with role-based access and audit logs.' },
296
+ { icon: 'heroGlobeAlt', title: 'Global Scale', description: 'Deploy worldwide with automatic scaling and 99.99% uptime guarantee.' },
297
+ ];
298
+ }`;
299
+
300
+ await fs.writeFile(
301
+ path.join(
302
+ config.fullPath,
303
+ 'src/app/shared/components/features-section/features-section.component.ts'
304
+ ),
305
+ features
306
+ );
307
+
308
+ // How It Works
309
+ const howItWorks = `import { Component, Input } from '@angular/core';
310
+ import { NgIconComponent, provideIcons } from '@ng-icons/core';
311
+ import { heroArrowRight } from '@ng-icons/heroicons/outline';
312
+
313
+ export interface Step {
314
+ number: number;
315
+ title: string;
316
+ description: string;
317
+ image?: string;
318
+ }
319
+
320
+ @Component({
321
+ selector: 'app-how-it-works',
322
+ standalone: true,
323
+ imports: [NgIconComponent],
324
+ viewProviders: [provideIcons({ heroArrowRight })],
325
+ template: \`
326
+ <section class="py-24 bg-gray-50" [id]="sectionId">
327
+ <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
328
+ <div class="text-center mb-16">
329
+ <span class="text-primary-600 font-semibold text-sm uppercase tracking-wider">{{ badge }}</span>
330
+ <h2 class="mt-2 text-3xl font-bold text-gray-900 sm:text-4xl">{{ title }}</h2>
331
+ <p class="mt-4 text-xl text-gray-600 max-w-2xl mx-auto">{{ subtitle }}</p>
332
+ </div>
333
+
334
+ <div class="relative">
335
+ <!-- Connection Line -->
336
+ <div class="absolute left-1/2 top-0 h-full w-0.5 -translate-x-1/2 bg-gray-200 hidden lg:block"></div>
337
+
338
+ <div class="space-y-12 lg:space-y-24">
339
+ @for (step of steps; track step.number; let i = $index) {
340
+ <div class="relative grid items-center gap-8 lg:grid-cols-2" [class.lg:flex-row-reverse]="i % 2 === 1">
341
+ <!-- Step Number -->
342
+ <div class="absolute left-1/2 -translate-x-1/2 hidden lg:flex h-12 w-12 items-center justify-center rounded-full bg-primary-500 text-white font-bold text-lg z-10">
343
+ {{ step.number }}
344
+ </div>
345
+
346
+ <!-- Content -->
347
+ <div [class.lg:text-right]="i % 2 === 0" [class.lg:pr-16]="i % 2 === 0" [class.lg:pl-16]="i % 2 === 1">
348
+ <div class="lg:hidden mb-4 inline-flex h-10 w-10 items-center justify-center rounded-full bg-primary-500 text-white font-bold">
349
+ {{ step.number }}
350
+ </div>
351
+ <h3 class="text-2xl font-bold text-gray-900">{{ step.title }}</h3>
352
+ <p class="mt-4 text-gray-600">{{ step.description }}</p>
353
+ </div>
354
+
355
+ <!-- Image/Illustration -->
356
+ <div [class.lg:order-first]="i % 2 === 1">
357
+ @if (step.image) {
358
+ <img [src]="step.image" [alt]="step.title" class="rounded-2xl shadow-xl" />
359
+ } @else {
360
+ <div class="aspect-video rounded-2xl bg-linear-to-br from-primary-100 to-purple-100 flex items-center justify-center">
361
+ <span class="text-6xl font-bold text-primary-300">{{ step.number }}</span>
362
+ </div>
363
+ }
364
+ </div>
365
+ </div>
366
+ }
367
+ </div>
368
+ </div>
369
+ </div>
370
+ </section>
371
+ \`,
372
+ })
373
+ export class HowItWorksComponent {
374
+ @Input() sectionId = 'how-it-works';
375
+ @Input() badge = 'How It Works';
376
+ @Input() title = 'Get started in minutes';
377
+ @Input() subtitle = 'Simple steps to transform your workflow';
378
+ @Input() steps: Step[] = [
379
+ { number: 1, title: 'Create Your Account', description: 'Sign up in seconds with just your email. No credit card required to get started.' },
380
+ { number: 2, title: 'Configure Your Workspace', description: 'Set up your workspace, invite team members, and customize your settings.' },
381
+ { number: 3, title: 'Start Building', description: 'Use our intuitive tools and templates to build your first project in minutes.' },
382
+ { number: 4, title: 'Launch & Scale', description: 'Deploy with one click and scale automatically as your business grows.' },
383
+ ];
384
+ }`;
385
+
386
+ await fs.writeFile(
387
+ path.join(
388
+ config.fullPath,
389
+ 'src/app/shared/components/how-it-works/how-it-works.component.ts'
390
+ ),
391
+ howItWorks
392
+ );
393
+
394
+ // Testimonials Section
395
+ const testimonials = `import { Component, Input, signal } from '@angular/core';
396
+ import { NgIconComponent, provideIcons } from '@ng-icons/core';
397
+ import { heroChevronLeft, heroChevronRight } from '@ng-icons/heroicons/outline';
398
+ import { heroStarSolid } from '@ng-icons/heroicons/solid';
399
+
400
+ export interface LandingTestimonial {
401
+ content: string;
402
+ author: string;
403
+ role: string;
404
+ company: string;
405
+ avatar: string;
406
+ rating: number;
407
+ }
408
+
409
+ @Component({
410
+ selector: 'app-testimonials-section',
411
+ standalone: true,
412
+ imports: [NgIconComponent],
413
+ viewProviders: [provideIcons({ heroChevronLeft, heroChevronRight, heroStarSolid })],
414
+ template: \`
415
+ <section class="py-24 bg-white" [id]="sectionId">
416
+ <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
417
+ <div class="text-center mb-16">
418
+ <span class="text-primary-600 font-semibold text-sm uppercase tracking-wider">{{ badge }}</span>
419
+ <h2 class="mt-2 text-3xl font-bold text-gray-900 sm:text-4xl">{{ title }}</h2>
420
+ <p class="mt-4 text-xl text-gray-600 max-w-2xl mx-auto">{{ subtitle }}</p>
421
+ </div>
422
+
423
+ <!-- Testimonials Grid -->
424
+ <div class="grid gap-8 md:grid-cols-2 lg:grid-cols-3">
425
+ @for (testimonial of testimonials; track testimonial.author) {
426
+ <div class="rounded-2xl border border-gray-200 bg-white p-8 shadow-sm hover:shadow-lg transition-shadow">
427
+ <!-- Rating -->
428
+ <div class="flex gap-1 mb-4">
429
+ @for (star of getStars(testimonial.rating); track $index) {
430
+ <ng-icon name="heroStarSolid" size="20" class="text-amber-400"></ng-icon>
431
+ }
432
+ </div>
433
+
434
+ <!-- Content -->
435
+ <p class="text-gray-700 italic leading-relaxed">"{{ testimonial.content }}"</p>
436
+
437
+ <!-- Author -->
438
+ <div class="mt-6 flex items-center gap-4">
439
+ <img [src]="testimonial.avatar" [alt]="testimonial.author" class="h-12 w-12 rounded-full object-cover" />
440
+ <div>
441
+ <p class="font-semibold text-gray-900">{{ testimonial.author }}</p>
442
+ <p class="text-sm text-gray-500">{{ testimonial.role }}, {{ testimonial.company }}</p>
443
+ </div>
444
+ </div>
445
+ </div>
446
+ }
447
+ </div>
448
+ </div>
449
+ </section>
450
+ \`,
451
+ })
452
+ export class TestimonialsSectionComponent {
453
+ @Input() sectionId = 'testimonials';
454
+ @Input() badge = 'Testimonials';
455
+ @Input() title = 'Loved by thousands';
456
+ @Input() subtitle = 'See what our customers have to say about us';
457
+ @Input() testimonials: LandingTestimonial[] = [
458
+ {
459
+ content: 'This platform has completely transformed how we work. The speed and reliability are unmatched.',
460
+ author: 'Sarah Chen',
461
+ role: 'CTO',
462
+ company: 'TechStart Inc',
463
+ avatar: 'https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=150',
464
+ rating: 5,
465
+ },
466
+ {
467
+ content: 'We migrated our entire infrastructure and saw a 40% improvement in performance immediately.',
468
+ author: 'Michael Rodriguez',
469
+ role: 'Engineering Lead',
470
+ company: 'DataFlow',
471
+ avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=150',
472
+ rating: 5,
473
+ },
474
+ {
475
+ content: 'The customer support is incredible. Any question we have is answered within minutes.',
476
+ author: 'Emily Watson',
477
+ role: 'Product Manager',
478
+ company: 'Scale Labs',
479
+ avatar: 'https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=150',
480
+ rating: 5,
481
+ },
482
+ ];
483
+
484
+ getStars(rating: number): number[] {
485
+ return Array.from({ length: rating }, (_, i) => i);
486
+ }
487
+ }`;
488
+
489
+ await fs.writeFile(
490
+ path.join(
491
+ config.fullPath,
492
+ 'src/app/shared/components/testimonials-section/testimonials-section.component.ts'
493
+ ),
494
+ testimonials
495
+ );
496
+
497
+ // Pricing Section
498
+ const pricing = `import { Component, Input, signal } from '@angular/core';
499
+ import { RouterModule } from '@angular/router';
500
+ import { CurrencyPipe } from '@angular/common';
501
+ import { NgIconComponent, provideIcons } from '@ng-icons/core';
502
+ import { heroCheck } from '@ng-icons/heroicons/outline';
503
+
504
+ export interface PricingPlan {
505
+ name: string;
506
+ description: string;
507
+ price: number;
508
+ priceYearly: number;
509
+ features: string[];
510
+ cta: string;
511
+ highlighted?: boolean;
512
+ }
513
+
514
+ @Component({
515
+ selector: 'app-pricing-section',
516
+ standalone: true,
517
+ imports: [RouterModule, CurrencyPipe, NgIconComponent],
518
+ viewProviders: [provideIcons({ heroCheck })],
519
+ template: \`
520
+ <section class="py-24 bg-gray-50" [id]="sectionId">
521
+ <div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
522
+ <div class="text-center mb-16">
523
+ <span class="text-primary-600 font-semibold text-sm uppercase tracking-wider">{{ badge }}</span>
524
+ <h2 class="mt-2 text-3xl font-bold text-gray-900 sm:text-4xl">{{ title }}</h2>
525
+ <p class="mt-4 text-xl text-gray-600 max-w-2xl mx-auto">{{ subtitle }}</p>
526
+
527
+ <!-- Billing Toggle -->
528
+ <div class="mt-8 flex items-center justify-center gap-4">
529
+ <span [class.text-gray-900]="!isYearly()" [class.text-gray-500]="isYearly()">Monthly</span>
530
+ <button
531
+ (click)="isYearly.set(!isYearly())"
532
+ class="relative h-8 w-14 rounded-full bg-primary-500 transition-colors">
533
+ <span
534
+ class="absolute top-1 h-6 w-6 rounded-full bg-white transition-all"
535
+ [class.left-1]="!isYearly()"
536
+ [class.left-7]="isYearly()">
537
+ </span>
538
+ </button>
539
+ <span [class.text-gray-900]="isYearly()" [class.text-gray-500]="!isYearly()">
540
+ Yearly
541
+ <span class="ml-1 rounded-full bg-success-100 px-2 py-0.5 text-xs font-medium text-success-700">Save 20%</span>
542
+ </span>
543
+ </div>
544
+ </div>
545
+
546
+ <div class="grid gap-8 lg:grid-cols-3">
547
+ @for (plan of plans; track plan.name) {
548
+ <div
549
+ class="relative rounded-2xl border-2 bg-white p-8 shadow-sm transition-all hover:shadow-xl"
550
+ [class.border-primary-500]="plan.highlighted"
551
+ [class.border-gray-200]="!plan.highlighted"
552
+ [class.scale-105]="plan.highlighted">
553
+
554
+ @if (plan.highlighted) {
555
+ <div class="absolute -top-4 left-1/2 -translate-x-1/2">
556
+ <span class="rounded-full bg-primary-500 px-4 py-1 text-sm font-semibold text-white">Most Popular</span>
557
+ </div>
558
+ }
559
+
560
+ <div class="text-center">
561
+ <h3 class="text-xl font-bold text-gray-900">{{ plan.name }}</h3>
562
+ <p class="mt-2 text-gray-600">{{ plan.description }}</p>
563
+
564
+ <div class="mt-6">
565
+ <span class="text-5xl font-bold text-gray-900">
566
+ {{ (isYearly() ? plan.priceYearly : plan.price) | currency:'USD':'symbol':'1.0-0' }}
567
+ </span>
568
+ @if (plan.price > 0) {
569
+ <span class="text-gray-500">/{{ isYearly() ? 'year' : 'month' }}</span>
570
+ }
571
+ </div>
572
+ </div>
573
+
574
+ <ul class="mt-8 space-y-4">
575
+ @for (feature of plan.features; track feature) {
576
+ <li class="flex items-center gap-3">
577
+ <ng-icon name="heroCheck" size="20" class="text-success-500"></ng-icon>
578
+ <span class="text-gray-700">{{ feature }}</span>
579
+ </li>
580
+ }
581
+ </ul>
582
+
583
+ <a
584
+ [routerLink]="['/auth/register']"
585
+ [queryParams]="{ plan: plan.name.toLowerCase() }"
586
+ class="mt-8 block w-full rounded-lg py-3 text-center font-semibold transition-all"
587
+ [class.bg-primary-500]="plan.highlighted"
588
+ [class.text-white]="plan.highlighted"
589
+ [class.hover:bg-primary-600]="plan.highlighted"
590
+ [class.bg-gray-100]="!plan.highlighted"
591
+ [class.text-gray-900]="!plan.highlighted"
592
+ [class.hover:bg-gray-200]="!plan.highlighted">
593
+ {{ plan.cta }}
594
+ </a>
595
+ </div>
596
+ }
597
+ </div>
598
+ </div>
599
+ </section>
600
+ \`,
601
+ })
602
+ export class PricingSectionComponent {
603
+ @Input() sectionId = 'pricing';
604
+ @Input() badge = 'Pricing';
605
+ @Input() title = 'Simple, transparent pricing';
606
+ @Input() subtitle = 'Choose the plan that fits your needs. No hidden fees.';
607
+ @Input() plans: PricingPlan[] = [
608
+ {
609
+ name: 'Starter',
610
+ description: 'Perfect for individuals',
611
+ price: 0,
612
+ priceYearly: 0,
613
+ features: ['1 user', '5 projects', '1GB storage', 'Basic support'],
614
+ cta: 'Get Started Free',
615
+ },
616
+ {
617
+ name: 'Pro',
618
+ description: 'Best for growing teams',
619
+ price: 29,
620
+ priceYearly: 290,
621
+ features: ['10 users', 'Unlimited projects', '100GB storage', 'Priority support', 'Advanced analytics'],
622
+ cta: 'Start Free Trial',
623
+ highlighted: true,
624
+ },
625
+ {
626
+ name: 'Enterprise',
627
+ description: 'For large organizations',
628
+ price: 99,
629
+ priceYearly: 990,
630
+ features: ['Unlimited users', 'Unlimited projects', '1TB storage', '24/7 support', 'Custom integrations', 'SLA'],
631
+ cta: 'Contact Sales',
632
+ },
633
+ ];
634
+
635
+ isYearly = signal(false);
636
+ }`;
637
+
638
+ await fs.writeFile(
639
+ path.join(
640
+ config.fullPath,
641
+ 'src/app/shared/components/pricing-section/pricing-section.component.ts'
642
+ ),
643
+ pricing
644
+ );
645
+
646
+ // FAQ Section
647
+ const faq = `import { Component, Input, signal } from '@angular/core';
648
+ import { NgIconComponent, provideIcons } from '@ng-icons/core';
649
+ import { heroChevronDown } from '@ng-icons/heroicons/outline';
650
+
651
+ export interface FaqItem {
652
+ question: string;
653
+ answer: string;
654
+ }
655
+
656
+ @Component({
657
+ selector: 'app-faq-section',
658
+ standalone: true,
659
+ imports: [NgIconComponent],
660
+ viewProviders: [provideIcons({ heroChevronDown })],
661
+ template: \`
662
+ <section class="py-24 bg-white" [id]="sectionId">
663
+ <div class="mx-auto max-w-3xl px-4 sm:px-6 lg:px-8">
664
+ <div class="text-center mb-16">
665
+ <span class="text-primary-600 font-semibold text-sm uppercase tracking-wider">{{ badge }}</span>
666
+ <h2 class="mt-2 text-3xl font-bold text-gray-900 sm:text-4xl">{{ title }}</h2>
667
+ <p class="mt-4 text-xl text-gray-600">{{ subtitle }}</p>
668
+ </div>
669
+
670
+ <div class="space-y-4">
671
+ @for (item of items; track item.question; let i = $index) {
672
+ <div class="rounded-xl border border-gray-200 bg-white overflow-hidden">
673
+ <button
674
+ (click)="toggle(i)"
675
+ class="flex w-full items-center justify-between p-6 text-left hover:bg-gray-50">
676
+ <span class="font-semibold text-gray-900">{{ item.question }}</span>
677
+ <ng-icon
678
+ name="heroChevronDown"
679
+ size="20"
680
+ class="text-gray-500 transition-transform"
681
+ [class.rotate-180]="openIndex() === i">
682
+ </ng-icon>
683
+ </button>
684
+ @if (openIndex() === i) {
685
+ <div class="border-t border-gray-200 px-6 py-4 bg-gray-50">
686
+ <p class="text-gray-600">{{ item.answer }}</p>
687
+ </div>
688
+ }
689
+ </div>
690
+ }
691
+ </div>
692
+ </div>
693
+ </section>
694
+ \`,
695
+ })
696
+ export class FaqSectionComponent {
697
+ @Input() sectionId = 'faq';
698
+ @Input() badge = 'FAQ';
699
+ @Input() title = 'Frequently asked questions';
700
+ @Input() subtitle = 'Everything you need to know';
701
+ @Input() items: FaqItem[] = [
702
+ { question: 'How do I get started?', answer: 'Simply sign up for a free account and you can start using the platform immediately. No credit card required.' },
703
+ { question: 'Can I change my plan later?', answer: 'Yes, you can upgrade or downgrade your plan at any time. Changes take effect at the start of your next billing cycle.' },
704
+ { question: 'Is there a free trial?', answer: 'Yes! All paid plans come with a 14-day free trial. No credit card required to start.' },
705
+ { question: 'What payment methods do you accept?', answer: 'We accept all major credit cards (Visa, MasterCard, American Express) as well as PayPal and bank transfers for enterprise plans.' },
706
+ { question: 'Can I cancel anytime?', answer: 'Absolutely. You can cancel your subscription at any time with no penalties or hidden fees.' },
707
+ ];
708
+
709
+ openIndex = signal<number | null>(0);
710
+
711
+ toggle(index: number): void {
712
+ this.openIndex.set(this.openIndex() === index ? null : index);
713
+ }
714
+ }`;
715
+
716
+ await fs.writeFile(
717
+ path.join(config.fullPath, 'src/app/shared/components/faq-section/faq-section.component.ts'),
718
+ faq
719
+ );
720
+
721
+ // Newsletter
722
+ const newsletter = `import { Component, Input, signal } from '@angular/core';
723
+ import { FormsModule } from '@angular/forms';
724
+ import { NgIconComponent, provideIcons } from '@ng-icons/core';
725
+ import { heroPaperAirplane } from '@ng-icons/heroicons/outline';
726
+
727
+ @Component({
728
+ selector: 'app-newsletter',
729
+ standalone: true,
730
+ imports: [FormsModule, NgIconComponent],
731
+ viewProviders: [provideIcons({ heroPaperAirplane })],
732
+ template: \`
733
+ <section class="py-24 bg-gradient-to-r from-primary-600 to-purple-600">
734
+ <div class="mx-auto max-w-4xl px-4 text-center sm:px-6 lg:px-8">
735
+ <h2 class="text-3xl font-bold text-white sm:text-4xl">{{ title }}</h2>
736
+ <p class="mt-4 text-xl text-primary-100">{{ subtitle }}</p>
737
+
738
+ @if (!submitted()) {
739
+ <form (ngSubmit)="subscribe()" class="mt-8 flex flex-col gap-4 sm:flex-row sm:justify-center">
740
+ <input
741
+ type="email"
742
+ [(ngModel)]="email"
743
+ name="email"
744
+ [placeholder]="placeholder"
745
+ required
746
+ class="w-full rounded-full px-6 py-4 text-gray-900 placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-white sm:w-80"
747
+ />
748
+ <button
749
+ type="submit"
750
+ [disabled]="loading()"
751
+ class="inline-flex items-center justify-center gap-2 rounded-full bg-white px-8 py-4 font-semibold text-primary-600 hover:bg-gray-100 disabled:opacity-50">
752
+ @if (loading()) {
753
+ <span class="h-5 w-5 animate-spin rounded-full border-2 border-primary-600 border-t-transparent"></span>
754
+ } @else {
755
+ <ng-icon name="heroPaperAirplane" size="20"></ng-icon>
756
+ }
757
+ {{ buttonText }}
758
+ </button>
759
+ </form>
760
+ <p class="mt-4 text-sm text-primary-200">{{ disclaimer }}</p>
761
+ } @else {
762
+ <div class="mt-8 rounded-2xl bg-white/10 p-8 backdrop-blur-sm">
763
+ <p class="text-xl font-semibold text-white">{{ successMessage }}</p>
764
+ </div>
765
+ }
766
+ </div>
767
+ </section>
768
+ \`,
769
+ })
770
+ export class NewsletterComponent {
771
+ @Input() title = 'Stay in the loop';
772
+ @Input() subtitle = 'Get the latest updates, tips, and special offers delivered to your inbox.';
773
+ @Input() placeholder = 'Enter your email';
774
+ @Input() buttonText = 'Subscribe';
775
+ @Input() disclaimer = 'No spam, unsubscribe at any time.';
776
+ @Input() successMessage = 'Thanks for subscribing! Check your inbox for confirmation.';
777
+
778
+ email = '';
779
+ loading = signal(false);
780
+ submitted = signal(false);
781
+
782
+ subscribe(): void {
783
+ if (!this.email) return;
784
+
785
+ this.loading.set(true);
786
+ // Simulate API call
787
+ setTimeout(() => {
788
+ this.loading.set(false);
789
+ this.submitted.set(true);
790
+ }, 1000);
791
+ }
792
+ }`;
793
+
794
+ await fs.writeFile(
795
+ path.join(config.fullPath, 'src/app/shared/components/newsletter/newsletter.component.ts'),
796
+ newsletter
797
+ );
798
+
799
+ // CTA Section
800
+ const cta = `import { Component, Input } from '@angular/core';
801
+ import { RouterModule } from '@angular/router';
802
+ import { NgIconComponent, provideIcons } from '@ng-icons/core';
803
+ import { heroArrowRight } from '@ng-icons/heroicons/outline';
804
+
805
+ @Component({
806
+ selector: 'app-cta-section',
807
+ standalone: true,
808
+ imports: [RouterModule, NgIconComponent],
809
+ viewProviders: [provideIcons({ heroArrowRight })],
810
+ template: \`
811
+ <section class="py-24 bg-gray-900">
812
+ <div class="mx-auto max-w-4xl px-4 text-center sm:px-6 lg:px-8">
813
+ <h2 class="text-3xl font-bold text-white sm:text-4xl">{{ title }}</h2>
814
+ <p class="mt-4 text-xl text-gray-400">{{ subtitle }}</p>
815
+
816
+ <div class="mt-10 flex flex-wrap justify-center gap-4">
817
+ <a
818
+ [routerLink]="primaryCta.link"
819
+ class="group inline-flex items-center gap-2 rounded-full bg-white px-8 py-4 font-semibold text-gray-900 hover:bg-gray-100 transition-all">
820
+ {{ primaryCta.text }}
821
+ <ng-icon name="heroArrowRight" size="20" class="transition-transform group-hover:translate-x-1"></ng-icon>
822
+ </a>
823
+ @if (secondaryCta) {
824
+ <a
825
+ [routerLink]="secondaryCta.link"
826
+ class="inline-flex items-center gap-2 rounded-full border-2 border-white/30 px-8 py-4 font-semibold text-white hover:bg-white/10 transition-all">
827
+ {{ secondaryCta.text }}
828
+ </a>
829
+ }
830
+ </div>
831
+ </div>
832
+ </section>
833
+ \`,
834
+ })
835
+ export class CtaSectionComponent {
836
+ @Input() title = 'Ready to get started?';
837
+ @Input() subtitle = 'Join thousands of satisfied customers using our platform.';
838
+ @Input() primaryCta = { text: 'Start Free Trial', link: '/auth/register' };
839
+ @Input() secondaryCta?: { text: string; link: string } = { text: 'Contact Sales', link: '/contact' };
840
+ }`;
841
+
842
+ await fs.writeFile(
843
+ path.join(config.fullPath, 'src/app/shared/components/cta-section/cta-section.component.ts'),
844
+ cta
845
+ );
846
+ },
847
+
848
+ async createLandingPage(config) {
849
+ const landingPage = `import { Component } from '@angular/core';
850
+ import { HeroComponent } from '@shared/components/hero/hero.component';
851
+ import { FeaturesSectionComponent } from '@shared/components/features-section/features-section.component';
852
+ import { HowItWorksComponent } from '@shared/components/how-it-works/how-it-works.component';
853
+ import { TestimonialsSectionComponent } from '@shared/components/testimonials-section/testimonials-section.component';
854
+ import { PricingSectionComponent } from '@shared/components/pricing-section/pricing-section.component';
855
+ import { FaqSectionComponent } from '@shared/components/faq-section/faq-section.component';
856
+ import { NewsletterComponent } from '@shared/components/newsletter/newsletter.component';
857
+ import { CtaSectionComponent } from '@shared/components/cta-section/cta-section.component';
858
+
859
+ @Component({
860
+ selector: 'app-landing',
861
+ standalone: true,
862
+ imports: [
863
+ HeroComponent,
864
+ FeaturesSectionComponent,
865
+ HowItWorksComponent,
866
+ TestimonialsSectionComponent,
867
+ PricingSectionComponent,
868
+ FaqSectionComponent,
869
+ NewsletterComponent,
870
+ CtaSectionComponent,
871
+ ],
872
+ template: \`
873
+ <app-hero></app-hero>
874
+ <app-features-section></app-features-section>
875
+ <app-how-it-works></app-how-it-works>
876
+ <app-testimonials-section></app-testimonials-section>
877
+ <app-pricing-section></app-pricing-section>
878
+ <app-faq-section></app-faq-section>
879
+ <app-newsletter></app-newsletter>
880
+ <app-cta-section></app-cta-section>
881
+ \`,
882
+ })
883
+ export class LandingComponent {}`;
884
+
885
+ await fs.writeFile(
886
+ path.join(config.fullPath, 'src/app/features/landing/landing.component.ts'),
887
+ landingPage
888
+ );
889
+ },
890
+
891
+ async updateLayout(config) {
892
+ // Update header to be sticky and transparent on landing
893
+ const header = `import { Component, inject, signal, HostListener } from '@angular/core';
894
+ import { RouterModule } from '@angular/router';
895
+ import { TranslateModule } from '@ngx-translate/core';
896
+ import { NgIconComponent, provideIcons } from '@ng-icons/core';
897
+ import { heroBars3, heroXMark, heroGlobeAlt } from '@ng-icons/heroicons/outline';
898
+ import { TranslationService } from '@core/i18n/translation.service';
899
+
900
+ @Component({
901
+ selector: 'app-header',
902
+ standalone: true,
903
+ imports: [RouterModule, TranslateModule, NgIconComponent],
904
+ viewProviders: [provideIcons({ heroBars3, heroXMark, heroGlobeAlt })],
905
+ template: \`
906
+ <header
907
+ class="fixed top-0 left-0 right-0 z-50 transition-all duration-300"
908
+ [class.bg-white]="scrolled()"
909
+ [class.shadow-md]="scrolled()"
910
+ [class.bg-transparent]="!scrolled()">
911
+ <nav class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
912
+ <div class="flex h-16 items-center justify-between lg:h-20">
913
+ <!-- Logo -->
914
+ <a routerLink="/" class="text-xl font-bold" [class.text-gray-900]="scrolled()" [class.text-white]="!scrolled()">
915
+ {{ 'app.name' | translate }}
916
+ </a>
917
+
918
+ <!-- Desktop Navigation -->
919
+ <div class="hidden lg:flex lg:items-center lg:gap-8">
920
+ @for (link of navLinks; track link.path) {
921
+ <a
922
+ [routerLink]="link.path"
923
+ [fragment]="link.fragment"
924
+ class="text-sm font-medium transition-colors"
925
+ [class.text-gray-700]="scrolled()"
926
+ [class.hover:text-primary-600]="scrolled()"
927
+ [class.text-white/80]="!scrolled()"
928
+ [class.hover:text-white]="!scrolled()">
929
+ {{ link.label | translate }}
930
+ </a>
931
+ }
932
+ </div>
933
+
934
+ <!-- Right Actions -->
935
+ <div class="flex items-center gap-4">
936
+ <!-- Language Switcher -->
937
+ <button
938
+ (click)="toggleLanguage()"
939
+ class="flex items-center gap-1 rounded-lg p-2 transition-colors"
940
+ [class.text-gray-600]="scrolled()"
941
+ [class.hover:bg-gray-100]="scrolled()"
942
+ [class.text-white/80]="!scrolled()"
943
+ [class.hover:bg-white/10]="!scrolled()">
944
+ <ng-icon name="heroGlobeAlt" size="20"></ng-icon>
945
+ <span class="text-sm font-medium">{{ translationService.currentLang() === 'en' ? 'AR' : 'EN' }}</span>
946
+ </button>
947
+
948
+ <!-- Auth Buttons -->
949
+ <div class="hidden lg:flex lg:items-center lg:gap-3">
950
+ <a
951
+ routerLink="/auth/login"
952
+ class="text-sm font-medium transition-colors"
953
+ [class.text-gray-700]="scrolled()"
954
+ [class.hover:text-primary-600]="scrolled()"
955
+ [class.text-white/80]="!scrolled()"
956
+ [class.hover:text-white]="!scrolled()">
957
+ {{ 'nav.login' | translate }}
958
+ </a>
959
+ <a
960
+ routerLink="/auth/register"
961
+ class="rounded-full px-4 py-2 text-sm font-semibold transition-all"
962
+ [class.bg-primary-500]="scrolled()"
963
+ [class.text-white]="scrolled()"
964
+ [class.hover:bg-primary-600]="scrolled()"
965
+ [class.bg-white]="!scrolled()"
966
+ [class.text-gray-900]="!scrolled()"
967
+ [class.hover:bg-gray-100]="!scrolled()">
968
+ {{ 'nav.getStarted' | translate }}
969
+ </a>
970
+ </div>
971
+
972
+ <!-- Mobile Menu Button -->
973
+ <button
974
+ (click)="mobileMenuOpen.set(!mobileMenuOpen())"
975
+ class="lg:hidden p-2 rounded-lg"
976
+ [class.text-gray-600]="scrolled()"
977
+ [class.text-white]="!scrolled()">
978
+ <ng-icon [name]="mobileMenuOpen() ? 'heroXMark' : 'heroBars3'" size="24"></ng-icon>
979
+ </button>
980
+ </div>
981
+ </div>
982
+
983
+ <!-- Mobile Menu -->
984
+ @if (mobileMenuOpen()) {
985
+ <div class="lg:hidden border-t border-gray-200 bg-white py-4">
986
+ @for (link of navLinks; track link.path) {
987
+ <a
988
+ [routerLink]="link.path"
989
+ [fragment]="link.fragment"
990
+ (click)="mobileMenuOpen.set(false)"
991
+ class="block px-4 py-2 text-gray-700 hover:bg-gray-50">
992
+ {{ link.label | translate }}
993
+ </a>
994
+ }
995
+ <div class="mt-4 border-t border-gray-200 pt-4 px-4 space-y-2">
996
+ <a routerLink="/auth/login" class="block py-2 text-gray-700">{{ 'nav.login' | translate }}</a>
997
+ <a routerLink="/auth/register" class="block rounded-lg bg-primary-500 py-2 text-center text-white">
998
+ {{ 'nav.getStarted' | translate }}
999
+ </a>
1000
+ </div>
1001
+ </div>
1002
+ }
1003
+ </nav>
1004
+ </header>
1005
+ \`,
1006
+ })
1007
+ export class HeaderComponent {
1008
+ translationService = inject(TranslationService);
1009
+
1010
+ mobileMenuOpen = signal(false);
1011
+ scrolled = signal(false);
1012
+
1013
+ navLinks = [
1014
+ { path: '/', fragment: 'features', label: 'nav.features' },
1015
+ { path: '/', fragment: 'how-it-works', label: 'nav.howItWorks' },
1016
+ { path: '/', fragment: 'testimonials', label: 'nav.testimonials' },
1017
+ { path: '/', fragment: 'pricing', label: 'nav.pricing' },
1018
+ { path: '/', fragment: 'faq', label: 'nav.faq' },
1019
+ ];
1020
+
1021
+ @HostListener('window:scroll')
1022
+ onScroll(): void {
1023
+ this.scrolled.set(window.scrollY > 50);
1024
+ }
1025
+
1026
+ toggleLanguage(): void {
1027
+ const newLang = this.translationService.currentLang() === 'en' ? 'ar' : 'en';
1028
+ this.translationService.setLanguage(newLang);
1029
+ }
1030
+ }`;
1031
+
1032
+ await fs.writeFile(
1033
+ path.join(config.fullPath, 'src/app/layout/header/header.component.ts'),
1034
+ header
1035
+ );
1036
+ },
1037
+
1038
+ async createRouting(config) {
1039
+ const routes = `import { Routes } from '@angular/router';
1040
+
1041
+ export const routes: Routes = [
1042
+ // Landing page as home
1043
+ {
1044
+ path: '',
1045
+ loadComponent: () => import('./features/landing/landing.component').then(c => c.LandingComponent)
1046
+ },
1047
+ {
1048
+ path: 'about',
1049
+ loadComponent: () => import('./features/about/about.component').then(c => c.AboutComponent)
1050
+ },
1051
+ {
1052
+ path: 'contact',
1053
+ loadComponent: () => import('./features/contact/contact.component').then(c => c.ContactComponent)
1054
+ },
1055
+
1056
+ // Auth routes
1057
+ {
1058
+ path: 'auth',
1059
+ loadComponent: () => import('./layout/auth/auth-layout.component').then(c => c.AuthLayoutComponent),
1060
+ children: [
1061
+ { path: '', redirectTo: 'login', pathMatch: 'full' },
1062
+ { path: 'login', loadComponent: () => import('./features/auth/login/login.component').then(c => c.LoginComponent) },
1063
+ { path: 'register', loadComponent: () => import('./features/auth/register/register.component').then(c => c.RegisterComponent) },
1064
+ { path: 'forgot-password', loadComponent: () => import('./features/auth/forgot-password/forgot-password.component').then(c => c.ForgotPasswordComponent) }
1065
+ ]
1066
+ },
1067
+
1068
+ { path: '**', redirectTo: '' }
1069
+ ];`;
1070
+
1071
+ await fs.writeFile(path.join(config.fullPath, 'src/app/app.routes.ts'), routes);
1072
+ },
1073
+
1074
+ async updateI18n(config) {
1075
+ const enPath = path.join(config.fullPath, 'public/assets/i18n/en.json');
1076
+ const arPath = path.join(config.fullPath, 'public/assets/i18n/ar.json');
1077
+
1078
+ let en = {};
1079
+ let ar = {};
1080
+
1081
+ try {
1082
+ en = JSON.parse(await fs.readFile(enPath, 'utf-8'));
1083
+ ar = JSON.parse(await fs.readFile(arPath, 'utf-8'));
1084
+ } catch {
1085
+ // Files don't exist yet
1086
+ }
1087
+
1088
+ const landingEn = {
1089
+ nav: {
1090
+ features: 'Features',
1091
+ howItWorks: 'How It Works',
1092
+ testimonials: 'Testimonials',
1093
+ pricing: 'Pricing',
1094
+ faq: 'FAQ',
1095
+ login: 'Log In',
1096
+ getStarted: 'Get Started',
1097
+ },
1098
+ };
1099
+
1100
+ const landingAr = {
1101
+ nav: {
1102
+ features: 'الميزات',
1103
+ howItWorks: 'كيف يعمل',
1104
+ testimonials: 'الشهادات',
1105
+ pricing: 'الأسعار',
1106
+ faq: 'الأسئلة الشائعة',
1107
+ login: 'تسجيل الدخول',
1108
+ getStarted: 'ابدأ الآن',
1109
+ },
1110
+ };
1111
+
1112
+ await fs.writeFile(enPath, JSON.stringify({ ...en, ...landingEn }, null, 2));
1113
+ await fs.writeFile(arPath, JSON.stringify({ ...ar, ...landingAr }, null, 2));
1114
+ },
1115
+ };
1116
+
1117
+ module.exports = landing;