Compare commits

4 Commits

12 changed files with 193 additions and 36 deletions

View File

@@ -14,6 +14,7 @@
"@angular/forms": "^20.3.0",
"@angular/platform-browser": "^20.3.0",
"@angular/router": "^20.3.0",
"@hugeicons/core-free-icons": "^2.0.0",
"@tailwindcss/postcss": "^4.1.17",
"postcss": "^8.5.6",
"rxjs": "~7.8.0",
@@ -1356,6 +1357,12 @@
"node": ">=18"
}
},
"node_modules/@hugeicons/core-free-icons": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@hugeicons/core-free-icons/-/core-free-icons-2.0.0.tgz",
"integrity": "sha512-OSfv5k0iB0yG61dcfK7jcf00AIK8EXyQOgtcNJzSBFvm88n9VOelkxihZHJnNwDUFpO/jZI3vZSVp6i1dmRvJQ==",
"license": "MIT"
},
"node_modules/@inquirer/ansi": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz",

View File

@@ -28,6 +28,7 @@
"@angular/forms": "^20.3.0",
"@angular/platform-browser": "^20.3.0",
"@angular/router": "^20.3.0",
"@hugeicons/core-free-icons": "^2.0.0",
"@tailwindcss/postcss": "^4.1.17",
"postcss": "^8.5.6",
"rxjs": "~7.8.0",

View File

@@ -66,37 +66,7 @@
<div class="grid md:grid-cols-4 gap-8">
<div *ngFor="let plan of plans"
[class]="'relative flex flex-col bg-slate-800/50 backdrop-blur-sm rounded-xl p-8 border-2 transition-all hover:transform hover:scale-105 ' + (plan.popular ? 'border-green-500 shadow-xl shadow-green-500/20' : 'border-slate-700 hover:border-green-500')">
<div class="flex-grow-1">
<div *ngIf="plan.popular"
class="absolute -top-4 left-1/2 transform -translate-x-1/2 bg-green-500 text-white px-4 py-1 rounded-full text-sm font-bold">
Most Popular
</div>
<div class="text-center mb-6">
<h3 class="text-2xl font-bold text-white mb-2">{{ plan.name }}</h3>
<div class="text-5xl font-bold text-white mb-2">{{ plan.price }}</div>
<div class="text-gray-400">/month</div>
</div>
<div class="space-y-3 mb-8">
<div class="text-center py-2 bg-slate-700/50 rounded-lg">
<div class="font-bold text-white">{{ plan.players }}</div>
</div>
<div class="text-center py-2 bg-slate-700/50 rounded-lg">
<div class="font-bold text-white">{{ plan.ram }}</div>
</div>
<div class="text-center py-2 bg-slate-700/50 rounded-lg">
<div class="font-bold text-white">{{ plan.storage }}</div>
</div>
</div>
<ul class="space-y-3 mb-8">
<li *ngFor="let feature of plan.features" class="flex items-center text-gray-300">
<svg class="w-5 h-5 text-green-400 mr-2 flex-shrink-0" fill="none" stroke="currentColor"
viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
{{ feature }}
</li>
</ul>
</div>
<app-price-card [plan]="plan" />
<button
[class]="'w-full py-3 rounded-lg font-bold transition-all ' + (plan.popular ? 'bg-green-500 hover:bg-green-600 text-white' : 'bg-slate-700 hover:bg-slate-600 text-white')">
@@ -123,5 +93,7 @@
</div>
</section>
<app-footer />
<app-footer [(contactOpen)]="contactOpen" />
<app-contact-modal [(open)]="contactOpen"/>
</div>

View File

@@ -1,18 +1,21 @@
import { CommonModule } from '@angular/common';
import { Component, OnDestroy, OnInit, HostListener } from '@angular/core';
import { Component, OnDestroy, OnInit, HostListener, signal } from '@angular/core';
import { Navigation } from './components/navigation/navigation';
import { Feature } from './interfaces/feature';
import { Plan } from './interfaces/plan';
import { Stat } from './interfaces/stat';
import { Footer } from './components/footer/footer';
import { ContactModal } from './components/contact-modal/contact-modal';
import { PriceCard } from './components/price-card/price-card';
@Component({
selector: 'app-root',
imports: [CommonModule, Navigation, Footer],
imports: [CommonModule, Navigation, Footer, ContactModal, PriceCard],
templateUrl: './app.html',
styleUrl: './app.css'
})
export class App implements OnInit, OnDestroy {
contactOpen = signal<boolean>(false);
mobileMenuOpen = false;
scrolled = false;

View File

@@ -0,0 +1,55 @@
<div class="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/80 backdrop-blur-sm" [class.hidden]="!open()"
(click)="toggleOpen()">
<div class="relative w-full max-w-lg bg-gray-900 rounded-3xl border border-gray-800 p-8 shadow-2xl"
(click)="toggleOpen()">
<button (click)="toggleOpen()"
class="absolute top-6 right-6 w-10 h-10 rounded-full border border-gray-700 flex items-center justify-center hover:border-green-500 hover:text-green-500 transition-all">
<!-- <X class="w-5 h-5" /> -->
</button>
<h2 class="text-3xl font-bold mb-2 bg-clip-text text-transparent bg-gradient-to-r from-green-400 to-pink-400">
Get In Touch
</h2>
<p class="text-gray-400 mb-8">
Let's discuss your next project
</p>
<form (ngSubmit)="handleSubmit()" [formGroup]="contactForm" class="space-y-6">
<div>
<label for="name" class="block text-sm font-medium mb-2 text-gray-300">
Name
</label>
<input type="text" id="name" name="name" formControlName="name" required
class="w-full px-4 py-3 bg-black border border-gray-700 rounded-xl focus:outline-none focus:border-green-500 transition-colors text-white"
placeholder="Your name" />
</div>
<div>
<label for="email" class="block text-sm font-medium mb-2 text-gray-300">
Email
</label>
<input type="email" id="email" name="email" formControlName="email" required
class="w-full px-4 py-3 bg-black border border-gray-700 rounded-xl focus:outline-none focus:border-green-500 transition-colors text-white"
placeholder="your@email.com" />
</div>
<div>
<label for="message" class="block text-sm font-medium mb-2 text-gray-300">
Message
</label>
<input type="text" formControlName="message" required
class="w-full px-4 py-3 bg-black border border-gray-700 rounded-xl focus:outline-none focus:border-green-500 transition-colors text-white resize-none"
placeholder="Tell me something" />
<!-- <textarea id="message" name="message" formControlName="message" required rows="5"
class="w-full px-4 py-3 bg-black border border-gray-700 rounded-xl focus:outline-none focus:border-green-500 transition-colors text-white resize-none"
placeholder="Tell me about your project..."> -->
</div>
<button type="submit"
class="w-full px-8 py-4 bg-gradient-to-r from-green-300 to-green-500 rounded-xl font-semibold hover:scale-[1.02] transition-transform flex items-center justify-center gap-2">
Send Message
<!-- <ArrowRight class="w-5 h-5" /> -->
</button>
</form>
</div>
</div>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ContactModal } from './contact-modal';
describe('ContactModal', () => {
let component: ContactModal;
let fixture: ComponentFixture<ContactModal>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [ContactModal]
})
.compileComponents();
fixture = TestBed.createComponent(ContactModal);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,26 @@
import { Component, model } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
@Component({
selector: 'app-contact-modal',
imports: [ReactiveFormsModule],
templateUrl: './contact-modal.html',
styles: ``,
})
export class ContactModal {
open = model.required<boolean>();
contactForm = new FormGroup({
name: new FormControl(''),
email: new FormControl('', Validators.email),
message: new FormControl(''),
}, { validators: Validators.required })
toggleOpen() {
this.open.update(prev => !prev);
}
handleSubmit() {
alert(this.contactForm.value);
}
}

View File

@@ -11,7 +11,7 @@
<div class="flex justify-center space-x-6 text-sm">
<a href="#" class="hover:text-white transition">Terms</a>
<a href="#" class="hover:text-white transition">Privacy</a>
<a href="#" class="hover:text-white transition">Contact</a>
<a (click)="toggleContactModal()" class="hover:text-white transition">Contact</a>
</div>
</div>
</footer>

View File

@@ -1,4 +1,4 @@
import { Component } from '@angular/core';
import { Component, model } from '@angular/core';
@Component({
selector: 'app-footer',
@@ -7,5 +7,9 @@ import { Component } from '@angular/core';
styles: ``,
})
export class Footer {
contactOpen = model.required<boolean>();
toggleContactModal() {
this.contactOpen.update(prev => !prev);
}
}

View File

@@ -0,0 +1,30 @@
<div class="flex-grow-1">
<div *ngIf="plan().popular"
class="absolute -top-4 left-1/2 transform -translate-x-1/2 bg-green-500 text-white px-4 py-1 rounded-full text-sm font-bold">
Most Popular
</div>
<div class="text-center mb-6">
<h3 class="text-2xl font-bold text-white mb-2">{{ plan().name }}</h3>
<div class="text-5xl font-bold text-white mb-2">{{ plan().price }}</div>
<div class="text-gray-400">/month</div>
</div>
<div class="space-y-3 mb-8">
<div class="text-center py-2 bg-slate-700/50 rounded-lg">
<div class="font-bold text-white">{{ plan().players }}</div>
</div>
<div class="text-center py-2 bg-slate-700/50 rounded-lg">
<div class="font-bold text-white">{{ plan().ram }}</div>
</div>
<div class="text-center py-2 bg-slate-700/50 rounded-lg">
<div class="font-bold text-white">{{ plan().storage }}</div>
</div>
</div>
<ul class="space-y-3 mb-8">
<li *ngFor="let feature of plan().features" class="flex items-center text-gray-300">
<svg class="w-5 h-5 text-green-400 mr-2 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
{{ feature }}
</li>
</ul>
</div>

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PriceCard } from './price-card';
describe('PriceCard', () => {
let component: PriceCard;
let fixture: ComponentFixture<PriceCard>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [PriceCard]
})
.compileComponents();
fixture = TestBed.createComponent(PriceCard);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,13 @@
import { Component, input } from '@angular/core';
import { Plan } from '../../interfaces/plan';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-price-card',
imports: [CommonModule],
templateUrl: './price-card.html',
styles: ``,
})
export class PriceCard {
plan = input.required<Plan>();
}