feat: Initialize Angular project with Tailwind CSS

Sets up the fundamental structure for the AURORA LUXE TRAVEL Angular application. Includes project configuration, dependencies for Angular and Tailwind CSS, and basic HTML and TypeScript entry points. Defines static destination, experience, and testimonial data for initial use.
This commit is contained in:
Kenearos 2026-02-14 12:02:13 +01:00
parent 5650ebe79e
commit f3976872b3
18 changed files with 1021 additions and 8 deletions

24
.gitignore vendored Normal file
View file

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View file

@ -1,11 +1,20 @@
<div align="center">
<img width="1200" height="475" alt="GHBanner" src="https://github.com/user-attachments/assets/0aa67016-6eaf-458a-adb2-6e31a0763ed6" />
<h1>Built with AI Studio</h2>
<p>The fastest path from prompt to production with Gemini.</p>
<a href="https://aistudio.google.com/apps">Start building</a>
</div>
# Run and deploy your AI Studio app
This contains everything you need to run your app locally.
View your app in AI Studio: https://ai.studio/apps/drive/1AVM4S6TS4S5BzAuazjp8u72qtb1XvId2
## Run Locally
**Prerequisites:** Node.js
1. Install dependencies:
`npm install`
2. Set the `GEMINI_API_KEY` in [.env.local](.env.local) to your Gemini API key
3. Run the app:
`npm run dev`

52
angular.json Normal file
View file

@ -0,0 +1,52 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "",
"projects": {
"app": {
"projectType": "application",
"root": "",
"sourceRoot": "./",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular/build:application",
"options": {
"outputPath": {
"base": "./dist",
"browser": "."
},
"browser": "index.tsx",
"tsConfig": "tsconfig.json"
},
"configurations": {
"production": {
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true
}
},
"defaultConfiguration": "production"
},
"serve": {
"builder": "@angular/build:dev-server",
"options": {
"port": 3000
},
"configurations": {
"production": {
"buildTarget": "app:build:production"
},
"development": {
"buildTarget": "app:build:development"
}
},
"defaultConfiguration": "development"
}
}
}
}
}

110
index.html Normal file
View file

@ -0,0 +1,110 @@
<!DOCTYPE html>
<html lang="en" class="scroll-smooth">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AURORA LUXE TRAVEL</title>
<meta name="description" content="Beyond First Class. Bespoke luxury travel itineraries for the discerning voyager.">
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Space+Grotesk:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<!-- Tailwind -->
<script src="https://cdn.tailwindcss.com"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
aurora: {
bg: '#050508',
card: '#0F0F16',
border: '#1F1F2E',
}
},
fontFamily: {
sans: ['Inter', 'sans-serif'],
display: ['Space Grotesk', 'sans-serif'],
},
backgroundImage: {
'aurora-gradient': 'linear-gradient(to right, #00f0ff, #7000ff, #ff00aa)',
'glass': 'linear-gradient(145deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.01) 100%)',
},
animation: {
'blob': 'blob 10s infinite',
'shimmer': 'shimmer 2s linear infinite',
},
keyframes: {
blob: {
'0%': { transform: 'translate(0px, 0px) scale(1)' },
'33%': { transform: 'translate(30px, -50px) scale(1.1)' },
'66%': { transform: 'translate(-20px, 20px) scale(0.9)' },
'100%': { transform: 'translate(0px, 0px) scale(1)' },
},
shimmer: {
'0%': { backgroundPosition: '200% 0' },
'100%': { backgroundPosition: '-200% 0' }
}
}
}
}
}
</script>
<style>
body {
background-color: #050508;
color: #ffffff;
}
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
/* Intersection Observer Animation Classes */
.reveal {
opacity: 0;
transform: translateY(30px);
transition: all 0.8s cubic-bezier(0.5, 0, 0, 1);
}
.reveal.active {
opacity: 1;
transform: translateY(0);
}
.glass-panel {
background: rgba(15, 15, 22, 0.6);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.08);
}
.text-glow {
text-shadow: 0 0 20px rgba(0, 240, 255, 0.3);
}
</style>
<script type="importmap">
{
"imports": {
"rxjs": "https://esm.sh/rxjs@^7.8.2?conditions=es2015",
"rxjs/operators": "https://esm.sh/rxjs@^7.8.2/operators?conditions=es2015",
"rxjs/ajax": "https://esm.sh/rxjs@^7.8.2/ajax?conditions=es2015",
"rxjs/webSocket": "https://esm.sh/rxjs@^7.8.2/webSocket?conditions=es2015",
"rxjs/testing": "https://esm.sh/rxjs@^7.8.2/testing?conditions=es2015",
"rxjs/fetch": "https://esm.sh/rxjs@^7.8.2/fetch?conditions=es2015",
"@angular/forms": "https://esm.sh/@angular/forms@^21.1.4?external=rxjs",
"@angular/compiler": "https://esm.sh/@angular/compiler@^21.1.4?external=rxjs",
"@angular/core": "https://esm.sh/@angular/core@^21.1.4?external=rxjs",
"@angular/common": "https://esm.sh/@angular/common@^21.1.4?external=rxjs",
"@angular/platform-browser": "https://esm.sh/@angular/platform-browser@^21.1.4?external=rxjs"
}
}
</script>
</head>
<body>
<app-root></app-root>
</body>
</html>

12
index.tsx Normal file
View file

@ -0,0 +1,12 @@
import '@angular/compiler';
import { bootstrapApplication } from '@angular/platform-browser';
import { provideZonelessChangeDetection } from '@angular/core';
import { AppComponent } from './src/app.component';
bootstrapApplication(AppComponent, {
providers: [
provideZonelessChangeDetection()
]
}).catch(err => console.error(err));
// AI Studio always uses an `index.tsx` file for all project types.

5
metadata.json Normal file
View file

@ -0,0 +1,5 @@
{
"name": "AURORA LUXE TRAVEL",
"description": "Ultra-premium, futuristic luxury travel concierge service.",
"requestFramePermissions": []
}

28
package.json Normal file
View file

@ -0,0 +1,28 @@
{
"name": "aurora-luxe-travel",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "ng serve",
"build": "ng build",
"preview": "ng serve --configuration=production"
},
"dependencies": {
"rxjs": "^7.8.2",
"@angular/forms": "^21.1.4",
"@angular/compiler": "^21.1.0",
"@angular/core": "^21.1.0",
"@angular/common": "^21.1.0",
"@angular/platform-browser": "^21.1.0",
"@angular/build": "^21.1.0",
"@angular/cli": "^21.1.0",
"@angular/compiler-cli": "^21.1.0",
"tailwindcss": "latest"
},
"devDependencies": {
"@types/node": "^22.14.0",
"typescript": "~5.8.2",
"vite": "^6.2.0"
}
}

35
src/app.component.ts Normal file
View file

@ -0,0 +1,35 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { NavbarComponent } from './components/navbar.component';
import { HeroComponent } from './components/hero.component';
import { DestinationsComponent } from './components/destinations.component';
import { ExperiencesComponent } from './components/experiences.component';
import { TiersComponent } from './components/tiers.component';
import { ConciergeFormComponent } from './components/concierge-form.component';
import { FooterComponent } from './components/footer.component';
@Component({
selector: 'app-root',
standalone: true,
imports: [
NavbarComponent,
HeroComponent,
DestinationsComponent,
ExperiencesComponent,
TiersComponent,
ConciergeFormComponent,
FooterComponent
],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<main class="w-full min-h-screen bg-aurora-bg text-white selection:bg-cyan-500 selection:text-black">
<app-navbar></app-navbar>
<app-hero></app-hero>
<app-destinations></app-destinations>
<app-experiences></app-experiences>
<app-tiers></app-tiers>
<app-concierge-form></app-concierge-form>
<app-footer></app-footer>
</main>
`
})
export class AppComponent {}

View file

@ -0,0 +1,139 @@
import { Component, ChangeDetectionStrategy, signal } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule, FormBuilder, Validators, FormGroup } from '@angular/forms';
import { FadeInDirective } from '../directives/fade-in.directive';
@Component({
selector: 'app-concierge-form',
standalone: true,
imports: [CommonModule, ReactiveFormsModule, FadeInDirective],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<section id="concierge" class="py-24 relative bg-black">
<div class="container mx-auto px-6 max-w-4xl">
<div class="glass-panel rounded-3xl p-8 md:p-12 border border-white/10 relative overflow-hidden" appFadeIn>
<!-- Decorative Glow -->
<div class="absolute top-0 right-0 w-64 h-64 bg-cyan-500/10 blur-[80px] rounded-full pointer-events-none"></div>
<div class="relative z-10">
<h2 class="font-display text-3xl md:text-4xl font-bold mb-2">Request Itinerary</h2>
<p class="text-gray-400 mb-8">Tell us your dream. We handle the reality.</p>
@if (submitted()) {
<div class="flex flex-col items-center justify-center py-16 text-center animate-fade-in">
<div class="w-16 h-16 rounded-full bg-green-500/20 text-green-400 flex items-center justify-center mb-4 text-3xl"></div>
<h3 class="text-2xl font-bold mb-2">Request Received</h3>
<p class="text-gray-400">A concierge will contact you within 24 hours.</p>
<button (click)="reset()" class="mt-8 text-sm text-cyan-400 hover:text-cyan-300 underline">Send another request</button>
</div>
} @else {
<form [formGroup]="form" (ngSubmit)="onSubmit()" class="space-y-6">
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="space-y-2">
<label class="text-xs uppercase tracking-widest text-gray-500">Name</label>
<input formControlName="name" type="text" class="w-full bg-white/5 border border-white/10 rounded-lg p-3 text-white focus:border-cyan-500 focus:outline-none focus:bg-white/10 transition-colors" placeholder="John Doe">
</div>
<div class="space-y-2">
<label class="text-xs uppercase tracking-widest text-gray-500">Email</label>
<input formControlName="email" type="email" class="w-full bg-white/5 border border-white/10 rounded-lg p-3 text-white focus:border-cyan-500 focus:outline-none focus:bg-white/10 transition-colors" placeholder="john@example.com">
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="space-y-2">
<label class="text-xs uppercase tracking-widest text-gray-500">Dates</label>
<input formControlName="dates" type="text" class="w-full bg-white/5 border border-white/10 rounded-lg p-3 text-white focus:border-cyan-500 focus:outline-none focus:bg-white/10 transition-colors" placeholder="Approximate dates">
</div>
<div class="space-y-2">
<label class="text-xs uppercase tracking-widest text-gray-500">Budget Range</label>
<select formControlName="budget" class="w-full bg-white/5 border border-white/10 rounded-lg p-3 text-white focus:border-cyan-500 focus:outline-none focus:bg-white/10 transition-colors appearance-none">
<option value="" class="bg-gray-900">Select Budget</option>
<option value="10-20k" class="bg-gray-900">10k - 20k</option>
<option value="20-50k" class="bg-gray-900">20k - 50k</option>
<option value="50k+" class="bg-gray-900">50k+</option>
</select>
</div>
</div>
<div class="space-y-2">
<label class="text-xs uppercase tracking-widest text-gray-500">Interests</label>
<div class="flex flex-wrap gap-2">
@for (interest of interests; track interest) {
<button
type="button"
(click)="toggleInterest(interest)"
[class.bg-cyan-500]="selectedInterests().has(interest)"
[class.text-black]="selectedInterests().has(interest)"
[class.bg-white-5]="!selectedInterests().has(interest)"
[class.border-cyan-500]="selectedInterests().has(interest)"
class="px-3 py-1 rounded-full text-sm border border-white/10 hover:border-cyan-500/50 transition-all"
>
{{ interest }}
</button>
}
</div>
</div>
<div class="space-y-2">
<label class="text-xs uppercase tracking-widest text-gray-500">Notes</label>
<textarea formControlName="notes" rows="4" class="w-full bg-white/5 border border-white/10 rounded-lg p-3 text-white focus:border-cyan-500 focus:outline-none focus:bg-white/10 transition-colors" placeholder="Any specific requirements or dreams?"></textarea>
</div>
<button
type="submit"
[disabled]="form.invalid"
class="w-full py-4 bg-gradient-to-r from-cyan-600 to-blue-600 rounded-lg font-bold tracking-wide uppercase hover:from-cyan-500 hover:to-blue-500 transition-all transform hover:scale-[1.01] disabled:opacity-50 disabled:cursor-not-allowed"
>
Submit Request
</button>
</form>
}
</div>
</div>
</div>
</section>
`
})
export class ConciergeFormComponent {
form: FormGroup;
submitted = signal(false);
selectedInterests = signal<Set<string>>(new Set());
interests = ['Fine Dining', 'Adventure', 'Wellness', 'Culture', 'Wildlife', 'Nightlife'];
constructor(private fb: FormBuilder) {
this.form = this.fb.group({
name: ['', Validators.required],
email: ['', [Validators.required, Validators.email]],
dates: [''],
budget: [''],
notes: ['']
});
}
toggleInterest(interest: string) {
const current = new Set(this.selectedInterests());
if (current.has(interest)) {
current.delete(interest);
} else {
current.add(interest);
}
this.selectedInterests.set(current);
}
onSubmit() {
if (this.form.valid) {
// Mock API call
console.log({ ...this.form.value, interests: Array.from(this.selectedInterests()) });
setTimeout(() => {
this.submitted.set(true);
}, 500);
}
}
reset() {
this.form.reset();
this.selectedInterests.set(new Set());
this.submitted.set(false);
}
}

View file

@ -0,0 +1,75 @@
import { Component, inject, ChangeDetectionStrategy } from '@angular/core';
import { CommonModule, NgOptimizedImage } from '@angular/common';
import { DataService, Destination } from '../services/data.service';
import { FadeInDirective } from '../directives/fade-in.directive';
@Component({
selector: 'app-destinations',
standalone: true,
imports: [CommonModule, NgOptimizedImage, FadeInDirective],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<section id="destinations" class="py-24 bg-aurora-bg relative">
<div class="container mx-auto px-6">
<div class="flex flex-col md:flex-row justify-between items-end mb-16" appFadeIn>
<div>
<h2 class="font-display text-4xl md:text-5xl font-bold mb-4">Curated Worlds</h2>
<p class="text-gray-400 max-w-md">Hand-picked sanctuaries where luxury knows no bounds.</p>
</div>
<button class="hidden md:block text-cyan-400 hover:text-cyan-300 transition-colors uppercase text-sm tracking-widest border-b border-cyan-400/30 pb-1">View All Locations</button>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
@for (dest of destinations(); track dest.id) {
<div
appFadeIn
class="group relative h-[500px] w-full rounded-2xl overflow-hidden cursor-pointer"
>
<!-- Image -->
<img
[ngSrc]="dest.image"
fill
class="object-cover transition-transform duration-700 group-hover:scale-110"
[alt]="dest.name"
>
<!-- Gradient Overlay -->
<div class="absolute inset-0 bg-gradient-to-t from-black/90 via-black/20 to-transparent opacity-80 group-hover:opacity-90 transition-opacity"></div>
<!-- Content Bottom -->
<div class="absolute bottom-0 left-0 w-full p-8 translate-y-4 group-hover:translate-y-0 transition-transform duration-500">
<div class="flex justify-between items-start mb-2">
<div>
<span class="text-cyan-400 text-xs font-bold tracking-widest uppercase mb-2 block">{{ dest.region }}</span>
<h3 class="font-display text-2xl font-bold text-white mb-1">{{ dest.name }}</h3>
</div>
</div>
<p class="text-gray-300 text-sm mb-4 italic font-light">"{{ dest.vibe }}"</p>
<!-- Expanded Content -->
<div class="h-0 overflow-hidden group-hover:h-auto transition-all duration-500 opacity-0 group-hover:opacity-100">
<div class="flex flex-wrap gap-2 mb-4">
@for (feat of dest.features; track feat) {
<span class="text-[10px] px-2 py-1 bg-white/10 rounded backdrop-blur-sm border border-white/5 text-gray-200">{{ feat }}</span>
}
</div>
<div class="flex justify-between items-center border-t border-white/10 pt-4">
<span class="text-white font-medium">{{ dest.price }}</span>
<span class="w-8 h-8 rounded-full bg-white text-black flex items-center justify-center transform -rotate-45 group-hover:rotate-0 transition-transform">
</span>
</div>
</div>
</div>
</div>
}
</div>
</div>
</section>
`
})
export class DestinationsComponent {
private dataService = inject(DataService);
destinations = this.dataService.destinations;
}

View file

@ -0,0 +1,49 @@
import { Component, inject, ChangeDetectionStrategy } from '@angular/core';
import { CommonModule } from '@angular/common';
import { DataService } from '../services/data.service';
import { FadeInDirective } from '../directives/fade-in.directive';
@Component({
selector: 'app-experiences',
standalone: true,
imports: [CommonModule, FadeInDirective],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<section id="experiences" class="py-24 bg-[#08080c] overflow-hidden">
<div class="container mx-auto px-6 relative">
<!-- Decoration -->
<div class="absolute top-0 right-0 w-[400px] h-[400px] bg-purple-600/10 blur-[100px] rounded-full pointer-events-none"></div>
<div class="text-center mb-20" appFadeIn>
<h2 class="font-display text-4xl md:text-5xl font-bold mb-4">Signature Experiences</h2>
<p class="text-gray-400">Beyond the destination. It's about the story you return with.</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
@for (exp of experiences(); track exp.id) {
<div appFadeIn class="glass-panel p-8 rounded-2xl hover:bg-white/10 transition-colors duration-300 group">
<div class="w-12 h-12 mb-6 rounded-lg bg-gradient-to-br from-cyan-500/20 to-purple-500/20 flex items-center justify-center text-cyan-400 group-hover:scale-110 transition-transform duration-300">
<!-- Simple Icons -->
@if(exp.icon === 'rocket') {
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09z"/><path d="m12 15-3-3a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 0 1-4 2z"/><path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0"/><path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5"/></svg>
}
@if(exp.icon === 'water') {
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2 17a5 5 0 0 0 10 0c0-2.76-2.5-5-5-3-2.5-2-5 .24-5 3Z"/><path d="M12 17a5 5 0 0 0 10 0c0-2.76-2.5-5-5-3-2.5-2-5 .24-5 3Z"/><path d="m7 14 5-10 5 10"/></svg>
}
@if(exp.icon === 'snowflake') {
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m10 14-4.75 2.75"/><path d="m14 10 4.75-2.75"/><path d="m14 14 4.75 2.75"/><path d="m10 10-4.75-2.75"/><path d="m7 17 4.75-2.75"/><path d="m17 7-4.75 2.75"/><line x1="12" x2="12" y1="2" y2="22"/><line x1="2" x2="22" y1="12" y2="12"/><line x1="19.07" x2="4.93" y1="4.93" y2="19.07"/><line x1="19.07" x2="4.93" y1="19.07" y2="4.93"/></svg>
}
</div>
<h3 class="text-xl font-bold mb-3">{{ exp.title }}</h3>
<p class="text-gray-400 text-sm leading-relaxed">{{ exp.description }}</p>
</div>
}
</div>
</div>
</section>
`
})
export class ExperiencesComponent {
private dataService = inject(DataService);
experiences = this.dataService.experiences;
}

View file

@ -0,0 +1,37 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-footer',
standalone: true,
imports: [CommonModule],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<footer class="bg-black py-12 border-t border-white/5">
<div class="container mx-auto px-6">
<div class="flex flex-col md:flex-row justify-between items-center gap-8">
<div class="text-center md:text-left">
<div class="text-xl font-display font-bold tracking-tighter text-white flex items-center justify-center md:justify-start gap-2 mb-2">
<div class="w-4 h-4 rounded-full bg-cyan-500"></div>
AURORA<span class="font-light opacity-70">LUXE</span>
</div>
<p class="text-gray-500 text-sm">© 2024 Aurora Luxe Travel. All rights reserved.</p>
</div>
<div class="flex gap-6">
<a href="#" class="text-gray-500 hover:text-white transition-colors">Instagram</a>
<a href="#" class="text-gray-500 hover:text-white transition-colors">Twitter</a>
<a href="#" class="text-gray-500 hover:text-white transition-colors">LinkedIn</a>
</div>
</div>
<div class="mt-8 pt-8 border-t border-white/5 text-center md:text-right text-xs text-gray-700">
<p>Images via Unsplash. Fictional Brand.</p>
</div>
</div>
</footer>
`
})
export class FooterComponent {}

View file

@ -0,0 +1,76 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { CommonModule, NgOptimizedImage } from '@angular/common';
import { FadeInDirective } from '../directives/fade-in.directive';
@Component({
selector: 'app-hero',
standalone: true,
imports: [CommonModule, NgOptimizedImage, FadeInDirective],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<section class="relative w-full h-screen overflow-hidden flex items-center justify-center">
<!-- Background Image -->
<div class="absolute inset-0 z-0">
<img
ngSrc="https://images.unsplash.com/photo-1578683010236-ad711b9215f7"
width="2400"
height="1600"
priority
alt="Luxury Resort"
class="object-cover w-full h-full scale-105 animate-[pulse_10s_ease-in-out_infinite]"
/>
<!-- Cinematic Overlays -->
<div class="absolute inset-0 bg-black/40"></div>
<div class="absolute inset-0 bg-gradient-to-t from-aurora-bg via-transparent to-black/30"></div>
<div class="absolute inset-0 bg-gradient-to-r from-aurora-bg/50 via-transparent to-transparent"></div>
</div>
<!-- Content -->
<div class="relative z-10 container mx-auto px-6 text-center pt-20">
<!-- Aurora Blob -->
<div class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[500px] h-[300px] bg-gradient-to-r from-cyan-500/20 to-purple-500/20 blur-3xl rounded-full pointer-events-none mix-blend-screen animate-blob"></div>
<div appFadeIn class="space-y-8">
<span class="inline-block px-4 py-1.5 rounded-full border border-white/10 bg-white/5 backdrop-blur-md text-xs font-medium tracking-[0.2em] text-cyan-300 uppercase">
The Future of Travel
</span>
<h1 class="font-display text-6xl md:text-8xl font-bold tracking-tighter text-white leading-tight text-glow">
BEYOND <br />
<span class="text-transparent bg-clip-text bg-gradient-to-r from-white via-gray-200 to-gray-400">FIRST CLASS</span>
</h1>
<p class="max-w-xl mx-auto text-lg text-gray-300 font-light leading-relaxed">
Curating moments that defy imagination. From sub-orbital flights to private island sanctuaries, we design the impossible.
</p>
<div class="flex flex-col sm:flex-row gap-4 justify-center items-center pt-8">
<button (click)="scrollToForm()" class="group relative px-8 py-4 bg-white text-black font-bold tracking-wide rounded-full overflow-hidden hover:scale-105 transition-transform duration-300">
<span class="relative z-10">DESIGN MY TRIP</span>
<div class="absolute inset-0 bg-cyan-200 transform scale-x-0 origin-left group-hover:scale-x-100 transition-transform duration-500 ease-out z-0"></div>
</button>
<button (click)="scrollToDestinations()" class="px-8 py-4 text-white border border-white/20 rounded-full font-medium tracking-wide hover:bg-white/5 hover:border-white/40 transition-all backdrop-blur-sm">
EXPLORE DESTINATIONS
</button>
</div>
</div>
</div>
<!-- Scroll Indicator -->
<div class="absolute bottom-10 left-1/2 -translate-x-1/2 flex flex-col items-center gap-2 opacity-60 animate-bounce">
<span class="text-[10px] tracking-widest uppercase">Scroll</span>
<div class="w-px h-12 bg-gradient-to-b from-white to-transparent"></div>
</div>
</section>
`
})
export class HeroComponent {
scrollToForm() {
document.getElementById('concierge')?.scrollIntoView({ behavior: 'smooth' });
}
scrollToDestinations() {
document.getElementById('destinations')?.scrollIntoView({ behavior: 'smooth' });
}
}

View file

@ -0,0 +1,84 @@
import { Component, signal, HostListener, ChangeDetectionStrategy } from '@angular/core';
import { CommonModule } from '@angular/common';
@Component({
selector: 'app-navbar',
standalone: true,
imports: [CommonModule],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<nav
class="fixed top-0 left-0 w-full z-50 transition-all duration-300 border-b border-transparent"
[class.bg-aurora-bg-90]="scrolled()"
[class.backdrop-blur-xl]="scrolled()"
[class.border-white-10]="scrolled()"
[class.py-4]="!scrolled()"
[class.py-3]="scrolled()"
style="border-color: rgba(255,255,255,0.05)"
>
<div class="max-w-7xl mx-auto px-6 flex justify-between items-center">
<!-- Logo -->
<a href="#" class="text-2xl font-display font-bold tracking-tighter text-white flex items-center gap-2 group">
<div class="w-8 h-8 rounded-full bg-gradient-to-tr from-cyan-500 to-purple-600 group-hover:rotate-180 transition-transform duration-700"></div>
AURORA<span class="font-light opacity-70">LUXE</span>
</a>
<!-- Desktop Menu -->
<div class="hidden md:flex items-center gap-8">
@for (item of navItems; track item) {
<a [href]="item.href" class="text-sm font-medium text-gray-300 hover:text-white transition-colors relative group">
{{ item.label }}
<span class="absolute -bottom-1 left-0 w-0 h-0.5 bg-cyan-400 transition-all group-hover:w-full"></span>
</a>
}
</div>
<!-- CTA -->
<button (click)="scrollToForm()" class="hidden md:block relative px-6 py-2 rounded-full overflow-hidden group border border-white/20 hover:border-cyan-400/50 transition-colors">
<div class="absolute inset-0 bg-white/5 group-hover:bg-cyan-400/10 transition-colors"></div>
<span class="relative text-sm font-medium tracking-wide">REQUEST ITINERARY</span>
</button>
<!-- Mobile Toggle (Simplified) -->
<button class="md:hidden text-white" (click)="toggleMobile()">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</button>
</div>
<!-- Mobile Menu -->
@if (mobileOpen()) {
<div class="md:hidden absolute top-full left-0 w-full bg-aurora-card/95 backdrop-blur-xl border-b border-white/10 p-6 flex flex-col gap-4 animate-fade-in-down">
@for (item of navItems; track item) {
<a [href]="item.href" (click)="toggleMobile()" class="text-lg text-gray-200">{{ item.label }}</a>
}
<button (click)="scrollToForm(); toggleMobile()" class="mt-2 w-full py-3 bg-white text-black font-bold rounded">REQUEST ITINERARY</button>
</div>
}
</nav>
`
})
export class NavbarComponent {
scrolled = signal(false);
mobileOpen = signal(false);
navItems = [
{ label: 'Destinations', href: '#destinations' },
{ label: 'Experiences', href: '#experiences' },
{ label: 'Membership', href: '#membership' },
];
@HostListener('window:scroll')
onWindowScroll() {
this.scrolled.set(window.scrollY > 50);
}
toggleMobile() {
this.mobileOpen.update(v => !v);
}
scrollToForm() {
document.getElementById('concierge')?.scrollIntoView({ behavior: 'smooth' });
}
}

View file

@ -0,0 +1,88 @@
import { Component, ChangeDetectionStrategy } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FadeInDirective } from '../directives/fade-in.directive';
@Component({
selector: 'app-tiers',
standalone: true,
imports: [CommonModule, FadeInDirective],
changeDetection: ChangeDetectionStrategy.OnPush,
template: `
<section id="membership" class="py-24 bg-aurora-bg relative overflow-hidden">
<!-- Background Glow -->
<div class="absolute bottom-0 left-0 w-full h-1/2 bg-gradient-to-t from-cyan-900/10 to-transparent"></div>
<div class="container mx-auto px-6 relative z-10">
<div class="text-center mb-16" appFadeIn>
<h2 class="font-display text-4xl md:text-5xl font-bold mb-4">Membership</h2>
<p class="text-gray-400">Unlock a world of privileges.</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-8 max-w-6xl mx-auto">
<!-- Silver -->
<div appFadeIn class="glass-panel rounded-2xl p-8 border border-white/10 flex flex-col hover:border-white/30 transition-all">
<h3 class="text-2xl font-display font-bold mb-2">Silver</h3>
<p class="text-sm text-gray-400 mb-8">For the occasional voyager.</p>
<ul class="space-y-4 mb-8 flex-1">
<li class="flex items-center gap-3 text-sm text-gray-300">
<span class="text-cyan-400"></span> Priority Booking
</li>
<li class="flex items-center gap-3 text-sm text-gray-300">
<span class="text-cyan-400"></span> Room Upgrades (Subject to availability)
</li>
<li class="flex items-center gap-3 text-sm text-gray-300">
<span class="text-cyan-400"></span> 24/7 Support
</li>
</ul>
<div class="text-2xl font-bold mb-4">5,000 <span class="text-xs font-normal text-gray-500">/ yr</span></div>
<button class="w-full py-3 rounded border border-white/20 hover:bg-white hover:text-black transition-colors text-sm tracking-wide">APPLY</button>
</div>
<!-- Black (Featured) -->
<div appFadeIn class="relative rounded-2xl p-8 bg-gradient-to-b from-gray-900 to-black border border-cyan-500/30 flex flex-col transform md:-translate-y-4 shadow-2xl shadow-cyan-900/20">
<div class="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-cyan-500 to-purple-500"></div>
<h3 class="text-2xl font-display font-bold mb-2 text-white">Black</h3>
<p class="text-sm text-cyan-200 mb-8">The standard for luxury.</p>
<ul class="space-y-4 mb-8 flex-1">
<li class="flex items-center gap-3 text-sm text-gray-300">
<span class="text-cyan-400"></span> Guaranteed Upgrades
</li>
<li class="flex items-center gap-3 text-sm text-gray-300">
<span class="text-cyan-400"></span> Private Jet Access
</li>
<li class="flex items-center gap-3 text-sm text-gray-300">
<span class="text-cyan-400"></span> Dedicated Lifestyle Manager
</li>
<li class="flex items-center gap-3 text-sm text-gray-300">
<span class="text-cyan-400"></span> Exclusive Event Invites
</li>
</ul>
<div class="text-2xl font-bold mb-4">25,000 <span class="text-xs font-normal text-gray-500">/ yr</span></div>
<button class="w-full py-3 rounded bg-white text-black hover:bg-cyan-50 transition-colors text-sm font-bold tracking-wide">APPLY</button>
</div>
<!-- Obsidian -->
<div appFadeIn class="glass-panel rounded-2xl p-8 border border-white/10 flex flex-col hover:border-white/30 transition-all">
<h3 class="text-2xl font-display font-bold mb-2 text-transparent bg-clip-text bg-gradient-to-r from-purple-400 to-pink-400">Obsidian</h3>
<p class="text-sm text-gray-400 mb-8">Limitless possibilities.</p>
<ul class="space-y-4 mb-8 flex-1">
<li class="flex items-center gap-3 text-sm text-gray-300">
<span class="text-purple-400"></span> Invitation Only
</li>
<li class="flex items-center gap-3 text-sm text-gray-300">
<span class="text-purple-400"></span> Anything, Anywhere, Anytime
</li>
<li class="flex items-center gap-3 text-sm text-gray-300">
<span class="text-purple-400"></span> Royal/Diplomatic Access
</li>
</ul>
<div class="text-2xl font-bold mb-4">Inquire <span class="text-xs font-normal text-gray-500">for price</span></div>
<button class="w-full py-3 rounded border border-white/20 hover:bg-white hover:text-black transition-colors text-sm tracking-wide">CONTACT</button>
</div>
</div>
</div>
</section>
`
})
export class TiersComponent {}

View file

@ -0,0 +1,34 @@
import { Directive, ElementRef, OnInit, OnDestroy, inject } from '@angular/core';
@Directive({
selector: '[appFadeIn]',
standalone: true
})
export class FadeInDirective implements OnInit, OnDestroy {
private element = inject(ElementRef);
private observer: IntersectionObserver | null = null;
ngOnInit() {
this.element.nativeElement.classList.add('reveal');
this.observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.element.nativeElement.classList.add('active');
this.observer?.unobserve(entry.target);
}
});
}, {
threshold: 0.1,
rootMargin: '0px 0px -50px 0px'
});
this.observer.observe(this.element.nativeElement);
}
ngOnDestroy() {
if (this.observer) {
this.observer.disconnect();
}
}
}

View file

@ -0,0 +1,123 @@
import { Injectable, signal } from '@angular/core';
export interface Destination {
id: string;
name: string;
region: string;
vibe: string;
price: string;
image: string;
features: string[];
}
export interface Experience {
id: string;
title: string;
description: string;
icon: string;
}
export interface Testimonial {
name: string;
role: string;
quote: string;
image: string;
}
@Injectable({
providedIn: 'root'
})
export class DataService {
destinations = signal<Destination[]>([
{
id: '1',
name: 'Velaa Private Island',
region: 'Maldives',
vibe: 'Absolute Seclusion',
price: '€15,000 / night',
image: 'https://images.unsplash.com/photo-1514282401047-d79a71a590e8?q=80&w=1200',
features: ['Private Butler', 'Underwater Dining', 'Golf Academy']
},
{
id: '2',
name: 'Aman Tokyo',
region: 'Japan',
vibe: 'Urban Sanctuary',
price: '€4,500 / night',
image: 'https://images.unsplash.com/photo-1540959733332-eab4deabeeaf?q=80&w=1200',
features: ['Sake Masterclass', 'Mt. Fuji View', 'Otemachi Forest']
},
{
id: '3',
name: 'Zermatt Chalet Peak',
region: 'Switzerland',
vibe: 'Alpine Majesty',
price: '€22,000 / week',
image: 'https://images.unsplash.com/photo-1502082553048-f009c37129b9?q=80&w=1200',
features: ['Private Chef', 'Heli-Skiing', 'Glass-roof Spa']
},
{
id: '4',
name: 'Amangiri',
region: 'Utah, USA',
vibe: 'Desert Silence',
price: '€3,800 / night',
image: 'https://images.unsplash.com/photo-1504280506541-aca06b2056d2?q=80&w=1200', // Desert vibe
features: ['Canyon Hiking', 'Navajo Storytelling', 'Floatation Pavilion']
},
{
id: '5',
name: 'Singita Lebombo',
region: 'South Africa',
vibe: 'Wild Luxury',
price: '€3,200 / night',
image: 'https://images.unsplash.com/photo-1516426122078-c23e76319801?q=80&w=1200',
features: ['Big Five', 'Wine Studio', 'Contemporary Design']
},
{
id: '6',
name: 'Riviera Yacht Charter',
region: 'Mediterranean',
vibe: 'Floating Palace',
price: '€120,000 / week',
image: 'https://images.unsplash.com/photo-1569263979104-865ab7dd8d36?q=80&w=1200',
features: ['Michelin Chef', 'Jet Skis', 'Monaco Docking']
}
]);
experiences = signal<Experience[]>([
{
id: 'e1',
title: 'Orbital Edge',
description: 'Experience weightlessness and view the curvature of Earth from the edge of space.',
icon: 'rocket'
},
{
id: 'e2',
title: 'Deep Blue Dining',
description: 'A private submarine dive to the Titanic wreck followed by a champagne dinner.',
icon: 'water'
},
{
id: 'e3',
title: 'Arctic Glass Igloo',
description: 'Sleep under the Aurora Borealis in a temperature-controlled glass sanctuary.',
icon: 'snowflake'
}
]);
testimonials = signal<Testimonial[]>([
{
name: "Alexander V.",
role: "Tech Entrepreneur",
quote: "Aurora didn't just book a trip; they curated a life moment. The private jet to Patagonia was seamless.",
image: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=100&h=100&fit=crop&crop=faces"
},
{
name: "Elena R.",
role: "Fashion Director",
quote: "Absolute perfection. From the welcome champagne to the exclusive gallery access in Paris.",
image: "https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=100&h=100&fit=crop&crop=faces"
}
]);
}

33
tsconfig.json Normal file
View file

@ -0,0 +1,33 @@
{
"compilerOptions": {
"target": "ES2022",
"experimentalDecorators": true,
"useDefineForClassFields": false,
"module": "ESNext",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"skipLibCheck": true,
"types": [
"node"
],
"moduleResolution": "bundler",
"isolatedModules": true,
"moduleDetection": "force",
"allowJs": true,
"jsx": "react-jsx",
"paths": {
"@/*": [
"./*"
]
}
},
"files": [
"./index.tsx"
],
"angularCompilerOptions": {
"disableTypeScriptVersionCheck": true
}
}