- Home
- Skills
- Frontend & Web Apps
- Awwwards Angular Site Generator
More screenshots
Works with the AI tools you already use
Awwwards Angular Site Generator
Turns a short brief into an art-directed, production-ready Angular 22 site with GSAP, Lenis and SSR-safe motion.
$15
Awwwards Angular Site Generator
Example session with this skill installed
Generate an immersive landing page for "Vora", a high-end minimalist watch brand. Vibe is Swiss luxury, primary colour #1A1A1A, balanced motion intensity, English copy. Angular 22 and Tailwind v4.
- Read your context and instructions
- Compiled the awwwards angular site
- Generated the app files
Brief analysis
Vora, high-end minimalist watches. Brand showcase with one flagship product. Audience: design-literate buyers, 30–55, international. Vibe: Swiss luxury, quiet, precise. Primary colour #1A1A1A supplied; secondary left blank, so a warm paper white becomes the light surface. Motion intensity: balanced. Language: English. Defaults resolved: seven sections, editorial grotesque display type, no video.
Creative direction
Concept: "Silence, measured." The page behaves like a watch dial: mostly empty, every mark placed with intent. Type does the work an image would usually do. Photography is sparse, large and slow. Motion is limited to reveals, one parallax and one horizontal scrub, all with long expo eases.
Principles: black is a surface, not a background · one idea per viewport · type on the grid, images break it · nothing decorates, everything reveals · whitespace is the luxury signal · the product appears late and once.
Visual system
| Token | Value | Role |
|---|---|---|
--color-ink | #1A1A1A | primary surface and text on light |
--color-graphite | #2B2B2B | elevated dark surface |
--color-paper | #F4F1EC | light surface and text on dark |
--color-ash | #8E8B86 | muted text, hairlines |
--color-brass | #B79B6C | single accent, seconds hand |
| Display | 'Neue Haas Grotesk Display', fallback 'Helvetica Neue', Arial | clamp(2.5rem, 7vw, 8rem), tracking -0.04em |
| Body | 'Inter', fallback system sans | clamp(1rem, 1.05vw, 1.125rem), line-height 1.6 |
| Eyebrow | Inter, uppercase | 0.6875rem, tracking 0.22em |
| Section rhythm | --space-section | clamp(6rem, 14vw, 13rem) |
| Grid | 12 columns, 1.5rem gutter, max 1440px | asymmetric 4/8 and 7/5 splits |
Contrast checked: paper on ink 14.9:1, ash on ink 4.9:1, brass on ink 6.1:1. All pass WCAG AA.
Page architecture
| # | Section | Purpose | Primary motion |
|---|---|---|---|
| 1 | Navigation | Wordmark, three anchors, "Reserve" link | Hairline draws on load, hides on scroll-down |
| 2 | Hero | "Time, without noise." | Word-mask reveal, dial image scales 1.06 → 1.0 |
| 3 | Statement | One paragraph on restraint | Line stagger on enter |
| 4 | Craft sequence | Case, movement, strap in three pinned beats | Pinned image, scrubbed text panels (desktop only) |
| 5 | Specifications | Asymmetric spec grid, 7/5 | Reveal stagger, hairline draw |
| 6 | Voices + Numbers | Two quotes, three figures | Count-up on reveal |
| 7 | Reserve + Footer | Reservation form, structured footer | Surface shift from ink to paper |
No two sections share a layout pattern.
Scaffold
ng new vora --ssr --style=css --zoneless --skip-git
cd vora
npm install tailwindcss @tailwindcss/postcss postcss gsap lenis
Zoneless bootstrap confirmed. zone.js absent from polyfills. .postcssrc.json configured with @tailwindcss/postcss.
Implementation
Fourteen files written to disk. Five shown in full below; the remaining nine (SEO service, section shell, masked image, reveal directive, four section components, routes) follow the same contract.
src/styles.css
@import "tailwindcss";
@theme {
--color-ink: #1A1A1A;
--color-graphite: #2B2B2B;
--color-paper: #F4F1EC;
--color-ash: #8E8B86;
--color-brass: #B79B6C;
--font-display: "Neue Haas Grotesk Display", "Helvetica Neue", Arial, sans-serif;
--font-body: "Inter", system-ui, sans-serif;
--spacing-section: clamp(6rem, 14vw, 13rem);
}
html { background: var(--color-ink); color: var(--color-paper); }
body { font-family: var(--font-body); -webkit-font-smoothing: antialiased; }
.line { display: block; will-change: transform, opacity; }
src/app/app.config.ts
import { ApplicationConfig, provideZonelessChangeDetection } from '@angular/core';
import { provideRouter } from '@angular/router';
import { provideClientHydration, withEventReplay } from '@angular/platform-browser';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideZonelessChangeDetection(),
provideRouter(routes),
provideClientHydration(withEventReplay()),
],
};
src/app/core/services/motion.service.ts
import { Injectable, PLATFORM_ID, inject, signal } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
@Injectable({ providedIn: 'root' })
export class MotionService {
private readonly isBrowser = isPlatformBrowser(inject(PLATFORM_ID));
readonly reducedMotion = signal(false);
readonly isTouch = signal(false);
readonly isDesktop = signal(false);
constructor() {
if (!this.isBrowser) return;
gsap.registerPlugin(ScrollTrigger);
this.reducedMotion.set(window.matchMedia('(prefers-reduced-motion: reduce)').matches);
this.isTouch.set(window.matchMedia('(pointer: coarse)').matches);
this.isDesktop.set(window.matchMedia('(min-width: 1024px)').matches);
}
/** Reveals each target line from below. Caller's gsap.context owns the tween. */
revealLines(targets: gsap.TweenTarget, opts: { trigger?: Element; delay?: number } = {}): gsap.core.Tween {
return gsap.from(targets, {
yPercent: 110,
opacity: 0,
duration: 1.4,
ease: 'expo.out',
stagger: 0.08,
delay: opts.delay ?? 0,
scrollTrigger: opts.trigger ? { trigger: opts.trigger, start: 'top 80%', once: true } : undefined,
});
}
parallax(target: gsap.TweenTarget, amount: number, trigger: Element): gsap.core.Tween {
return gsap.to(target, {
yPercent: amount,
ease: 'none',
scrollTrigger: { trigger, start: 'top bottom', end: 'bottom top', scrub: true },
});
}
matchMedia(): gsap.MatchMedia {
return gsap.matchMedia();
}
}
src/app/core/services/smooth-scroll.service.ts
import { Injectable, OnDestroy, inject } from '@angular/core';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import Lenis from 'lenis';
import { MotionService } from './motion.service';
@Injectable({ providedIn: 'root' })
export class SmoothScrollService implements OnDestroy {
private readonly motion = inject(MotionService);
private lenis?: Lenis;
private refreshHandle?: number;
private readonly tick = (time: number): void => this.lenis?.raf(time * 1000);
/** Call once from the root shell inside afterNextRender. */
init(): void {
if (this.lenis || this.motion.reducedMotion()) return;
this.lenis = new Lenis({ lerp: 0.08, smoothWheel: true });
this.lenis.on('scroll', ScrollTrigger.update);
gsap.ticker.add(this.tick);
gsap.ticker.lagSmoothing(0);
}
scrollTo(target: string | number | HTMLElement, options?: { offset?: number }): void {
this.lenis?.scrollTo(target, { offset: options?.offset ?? 0 });
}
refresh(): void {
clearTimeout(this.refreshHandle);
this.refreshHandle = window.setTimeout(() => ScrollTrigger.refresh(), 120);
}
ngOnDestroy(): void {
gsap.ticker.remove(this.tick);
this.lenis?.destroy();
ScrollTrigger.getAll().forEach((t) => t.kill());
}
}
src/app/features/home/sections/hero-section.ts
Written to disk with an inline template. Structure and motion, in order:
| Element | Classes and bindings | Motion |
|---|---|---|
section, min-h-svh, 12-column grid, aria-labelledby="hero-heading" | host of the component gsap.context | none |
Eyebrow p.eyebrow | text-ash, uppercase, tracking-[0.22em] | fades in at 0.9s |
h1#hero-heading, font-display, text-[clamp(2.5rem,7vw,8rem)] | @for (word of hero.titleWords; track $index), each word wrapped in an overflow-hidden span with a .line child | word-mask reveal via MotionService.revealLines, 0.2s delay |
figure#dial, aspect-[4/5], columns 8 to 12 on desktop | img with ngSrc, width, height, priority, sizes="(min-width: 1024px) 40vw, 100vw" | scale 1.06 to 1.0 over 2.2s, expo.out; parallax of -8% on desktop only |
Class body: hero = SITE.hero, host = inject(ElementRef), motion = inject(MotionService), dial = viewChild.required('dial', { read: ElementRef }), one ctx?: gsap.Context created in afterNextRender and reverted in ngOnDestroy. Breakpoints and reduced motion handled with gsap.matchMedia(); no tween runs when prefers-reduced-motion is set.
src/app/content/site.content.ts (excerpt, file is complete on disk)
export const SITE = {
brand: 'Vora',
lang: 'en',
hero: {
eyebrow: 'Swiss made · Est. 2019',
titleWords: ['Time,', 'without', 'noise.'],
image: {
src: 'https://picsum.photos/seed/vora-dial/1200/1500',
alt: 'Vora Meridian dial in matte graphite, brass seconds hand',
width: 1200,
height: 1500,
},
},
statement: {
lines: [
'A watch should say one thing',
'and say it for forty years.',
'Everything else is noise.',
],
},
} as const;
Verify
npx ng build 2>&1 | tail -6
Application bundle generation complete. [4.812 seconds]
Initial chunk files | Names | Raw size | Estimated transfer size
main-Q7K2L9.js | main | 214.3 kB | 58.1 kB
styles-P3HZ1D.css | styles | 11.8 kB | 2.9 kB
Dev server started, curl -s http://localhost:4200 | head -c 2000 returned server-rendered markup containing the hero heading with id hero-heading. Server stopped.
Quality audit
| Check | Status | Note |
|---|---|---|
Zoneless bootstrap, no zone.js polyfill | ✅ | provideZonelessChangeDetection() in config |
Standalone, signals, @if/@for with track, inject() only | ✅ | grep for @Input(, *ngIf, constructor(private returned nothing |
| GSAP and Lenis never run on the server | ✅ | all DOM work inside afterNextRender, MotionService guarded |
One Lenis instance, driven by gsap.ticker, lagSmoothing(0) | ✅ | no requestAnimationFrame anywhere in src/ |
One gsap.context per animated component, revert() in ngOnDestroy | ✅ | 6 contexts, 6 reverts |
| Reduced motion respected | ✅ | Lenis disabled, tweens skipped via matchMedia |
| Craft sequence pin | ✅ | desktop only, stacked reveals on tablet and mobile |
One h1, logical heading order, lang="en" | ✅ | |
| Palette contrast WCAG AA | ✅ | see visual system |
NgOptimizedImage with dimensions, priority on hero only | ✅ | below-the-fold images lazy |
| Fonts | ⚠️ | Neue Haas Grotesk is a licensed face; falls back to Helvetica Neue until you self-host |
| SEO canonical and OG image | ⚠️ | placeholder https://vora.example in seo.service.ts |
Implementation validation
- Replace the
picsum.photosseedsvora-dial,vora-case,vora-movement,vora-strapwith product photography at the documented sizes insite.content.ts. - Self-host the display font or swap to an owned face; update the two preload tags in
index.html. - Point the reservation form to your endpoint. It currently posts to
/api/reserve. - Set the canonical URL and OG image in
seo.service.ts. - If your installed Angular minor differs, verify the
provideZonelessChangeDetectionimport path.
Next steps
Run npm start and open http://localhost:4200. For refinements, ask in plain language: "slow the hero reveal", "swap Voices for a Heritage timeline", "audit cleanup", or "translate copy to Italian". Only the affected files are rewritten.
awwwards-angular-site-generator-app.zip
ZIP · project files
Example file from a real run - the skill writes it into your workspace.
Connects securely to your tools. The creator never sees your data.
What you get
About this skill
The problem
Standard Angular generators produce generic, bootstrap-style layouts that lack the high-end motion and art direction required for premium brands. Developers often struggle to integrate GSAP, Lenis, and SSR-safe animations without introducing memory leaks or layout shifts.
What it does
- Generates production-grade Angular 22 standalone applications using zoneless change detection and Signals.
- Configures a complete motion system with GSAP, ScrollTrigger, and Lenis smooth scrolling tailored for SSR.
- Architects art-directed visual systems including Tailwind v4 @theme tokens, fluid typography, and 12-column grids.
- Builds 6-8 distinct, non-repetitive page sections with real copy and optimized image handling via NgOptimizedImage.
- Automates cleanup patterns using gsap.context and revert() within lifecycle hooks to prevent performance degradation.
Demo
https://rubra-demo.netlify.appFrameworks & tools
Angular 22, GSAP (ScrollTrigger), Lenis, Tailwind CSS v4, PostCSS, TypeScript, and SSR (Server-Side Rendering).
Why this beats prompting it yourself
General-purpose LLMs frequently fail at the intersection of Angular SSR and heavy animation, often producing code that breaks on the server or leaks memory. This skill enforces a strict technical contract for zoneless Signals and GSAP cleanup, ensuring your "Awwwards-style" site is actually stable and performant.
Use cases
- Launching a premium product landing page with complex scroll-driven storytelling.
- Building a high-end creative agency portfolio with seamless page transitions.
- Creating editorial-style marketing sites that require fluid, responsive typography.
- Migrating legacy animation code to a modern, zoneless Angular architecture.
Known limitations
Not intended for CRUD applications, admin dashboards, or complex data-heavy interfaces. Requires a modern Node.js environment for scaffolding.
How to install
Works the same in every agent - Claude, Cursor, Codex, Copilot and 20+ more.
- 1
Download the ZIP
Free skills download straight away. Paid skills unlock right after purchase.
- 2
Unzip into your skills folder
Every agent reads skills from one folder on your machine. Drop the unzipped folder in there.
- 3
Ask your agent to use it
Restart the agent if it was already running. It picks the skill up automatically - no config needed.
Skills folder by agent
Click the path to copy it. Create the folder if it does not exist yet.
Reviews
No reviews yet
Be one of the first to try it. Every listed skill passes our trust checks below.
Security scanned
Passed our 8-point scan before listing
Fresh listing
Recently published to Agensi
30-day refund
Not a fit? Get your money back
Trust & safety
Security scanned
Verified clean 3 days ago
- Passed all security checks, Safe to install