Input
{
"patchText": "*** Begin Patch\n*** Update File: /opt/angular-showcase/src/app/app.ts\n@@\n-import { Component, signal } from '@angular/core';\n-import { RouterOutlet } from '@angular/router';\n+import { Component, computed, signal } from '@angular/core';\n \n @Component({\n selector: 'app-root',\n- imports: [RouterOutlet],\n+ imports: [],\n templateUrl: './app.html',\n styleUrl: './app.css'\n })\n export class App {\n- protected readonly title = signal('angular-showcase');\n+ protected readonly selectedFeature = signal('signals');\n+\n+ protected readonly stats = [\n+ { value: '21.x', label: 'Angular-ready' },\n+ { value: '8', label: 'feature demos' },\n+ { value: '<100ms', label: 'instant UI feel' },\n+ ];\n+\n+ protected readonly features = [\n+ {\n+ id: 'signals',\n+ eyebrow: 'Signals',\n+ title: 'Fine-grained reactivity',\n+ description: 'Model state with signal() and computed() so the template updates exactly where data changes.',\n+ code: \"count = signal(1);\\ntotal = computed(() => count() * 3);\",\n+ },\n+ {\n+ id: 'control-flow',\n+ eyebrow: 'Templates',\n+ title: 'Built-in control flow',\n+ description: 'Use @if, @for, and @switch directly in templates for clearer dynamic views and smaller bundles.',\n+ code: '@for (feature of features; track feature.id) {\\n <article>{{ feature.title }}</article>\\n}',\n+ },\n+ {\n+ id: 'standalone',\n+ eyebrow: 'Architecture',\n+ title: 'Standalone components',\n+ description: 'Ship features without NgModule ceremony. Components, directives, and pipes declare what they use.',\n+ code: '@Component({\\n standalone: true,\\n imports: [RouterLink]\\n})',\n+ },\n+ {\n+ id: 'routing',\n+ eyebrow: 'Routing',\n+ title: 'Lazy, typed navigation',\n+ description: 'Route-level code splitting keeps the first load lean while preserving a cohesive app shell.',\n+ code: \"{ path: 'labs', loadComponent: () => import('./labs') }\",\n+ },\n+ {\n+ id: 'forms',\n+ eyebrow: 'Forms',\n+ title: 'Powerful form APIs',\n+ description: 'Reactive and template-driven forms cover everything from quick inputs to complex validation flows.',\n+ code: \"new FormControl('', { validators: [Validators.required] })\",\n+ },\n+ {\n+ id: 'performance',\n+ eyebrow: 'Performance',\n+ title: 'Hydration and deferrable views',\n+ description: 'Combine SSR hydration, @defer blocks, and modern build optimization for fast production sites.',\n+ code: '@defer (on viewport) {\\n <heavy-visualization />\\n}',\n+ },\n+ ];\n+\n+ protected readonly activeFeature = computed(() =>\n+ this.features.find((feature) => feature.id === this.selectedFeature()) ?? this.features[0],\n+ );\n+\n+ protected selectFeature(featureId: string): void {\n+ this.selectedFeature.set(featureId);\n+ }\n }\n*** Update File: /opt/angular-showcase/src/app/app.html\n@@\n-<!--...-->\n-<router-outlet />\n+<main class=\"shell\">\n+ <section class=\"hero\" aria-labelledby=\"hero-title\">\n+ <nav class=\"nav\" aria-label=\"Primary\">\n+ <a class=\"brand\" href=\"https://angular.gulasch.site\" aria-label=\"Angular Showcase home\">\n+ <span class=\"brand-mark\">A</span>\n+ <span>angular.gulasch.site</span>\n+ </a>\n+ <div class=\"nav-links\">\n+ <a href=\"#features\">Features</a>\n+ <a href=\"#stack\">Stack</a>\n+ <a href=\"#deploy\">Deploy</a>\n+ </div>\n+ </nav>\n+\n+ <div class=\"hero-grid\">\n+ <div class=\"hero-copy\">\n+ <p class=\"eyebrow\">Angular feature showcase</p>\n+ <h1 id=\"hero-title\">A sharp, modern demo site for everything Angular does well.</h1>\n+ <p class=\"hero-text\">\n+ Explore signals, standalone components, template control flow, lazy routing, forms, testing,\n+ and production build workflows in one responsive Angular application.\n+ </p>\n+ <div class=\"hero-actions\">\n+ <a class=\"button primary\" href=\"#features\">Explore features</a>\n+ <a class=\"button secondary\" href=\"#deploy\">Deployment notes</a>\n+ </div>\n+ </div>\n+\n+ <aside class=\"terminal-card\" aria-label=\"Angular CLI preview\">\n+ <div class=\"terminal-top\">\n+ <span></span><span></span><span></span>\n+ <strong>ng showcase</strong>\n+ </div>\n+ <pre><code>ng new angular-showcase --standalone\nng build --configuration production\n# ready for angular.gulasch.site</code></pre>\n+ </aside>\n+ </div>\n+\n+ <div class=\"stats\" aria-label=\"Showcase stats\">\n+ @for (stat of stats; track stat.label) {\n+ <div>\n+ <strong>{{ stat.value }}</strong>\n+ <span>{{ stat.label }}</span>\n+ </div>\n+ }\n+ </div>\n+ </section>\n+\n+ <section id=\"features\" class=\"section feature-section\" aria-labelledby=\"features-title\">\n+ <div class=\"section-heading\">\n+ <p class=\"eyebrow\">Interactive tour</p>\n+ <h2 id=\"features-title\">Angular features worth showing off</h2>\n+ </div>\n+\n+ <div class=\"feature-layout\">\n+ <div class=\"feature-list\" role=\"list\">\n+ @for (feature of features; track feature.id) {\n+ <button\n+ type=\"button\"\n+ class=\"feature-button\"\n+ [class.active]=\"selectedFeature() === feature.id\"\n+ (click)=\"selectFeature(feature.id)\"\n+ >\n+ <span>{{ feature.eyebrow }}</span>\n+ <strong>{{ feature.title }}</strong>\n+ </button>\n+ }\n+ </div>\n+\n+ <article class=\"feature-detail\">\n+ <p class=\"eyebrow\">{{ activeFeature().eyebrow }}</p>\n+ <h3>{{ activeFeature().title }}</h3>\n+ <p>{{ activeFeature().description }}</p>\n+ <pre><code>{{ activeFeature().code }}</code></pre>\n+ </article>\n+ </div>\n+ </section>\n+\n+ <section id=\"stack\" class=\"section cards\" aria-labelledby=\"stack-title\">\n+ <div class=\"section-heading\">\n+ <p class=\"eyebrow\">What is inside</p>\n+ <h2 id=\"stack-title\">Built as a clean Angular application</h2>\n+ </div>\n+\n+ <article>\n+ <span>01</span>\n+ <h3>Standalone app shell</h3>\n+ <p>No module boilerplate. The root component owns the showcase and can grow into routed feature pages.</p>\n+ </article>\n+ <article>\n+ <span>02</span>\n+ <h3>Signal-driven UI</h3>\n+ <p>The selected feature card uses signals and computed state for predictable, minimal updates.</p>\n+ </article>\n+ <article>\n+ <span>03</span>\n+ <h3>Responsive design</h3>\n+ <p>The layout shifts from a split hero to compact cards for phones without losing visual character.</p>\n+ </article>\n+ </section>\n+\n+ <section id=\"deploy\" class=\"section deploy\" aria-labelledby=\"deploy-title\">\n+ <div>\n+ <p class=\"eyebrow\">Hosting target</p>\n+ <h2 id=\"deploy-title\">Ready for angular.gulasch.site</h2>\n+ <p>\n+ The app includes a CNAME file for static hosting and builds to <code>dist/angular-showcase/browser</code>.\n+ </p>\n+ </div>\n+ <a class=\"button primary\" href=\"https://angular.gulasch.site\">Open the site</a>\n+ </section>\n+</main>\n*** Update File: /opt/angular-showcase/src/app/app.css\n@@\n+:host {\n+ display: block;\n+ min-height: 100vh;\n+ color: #f8fbff;\n+ background:\n+ radial-gradient(circle at 15% 5%, rgba(255, 0, 118, 0.35), transparent 30rem),\n+ radial-gradient(circle at 90% 18%, rgba(21, 134, 255, 0.38), transparent 28rem),\n+ linear-gradient(135deg, #160019 0%, #080c1f 52%, #021612 100%);\n+}\n+\n+.shell {\n+ width: min(1180px, calc(100% - 32px));\n+ margin: 0 auto;\n+ padding: 24px 0 56px;\n+}\n+\n+.hero {\n+ min-height: 760px;\n+ padding: 18px;\n+ border: 1px solid rgba(255, 255, 255, 0.13);\n+ border-radius: 34px;\n+ background: rgba(255, 255, 255, 0.07);\n+ box-shadow: 0 30px 90px rgba(0, 0, 0, 0.34);\n+ backdrop-filter: blur(26px);\n+}\n+\n+.nav,\n+.hero-grid,\n+.stats,\n+.feature-layout,\n+.deploy {\n+ display: grid;\n+ gap: 24px;\n+}\n+\n+.nav {\n+ grid-template-columns: 1fr auto;\n+ align-items: center;\n+ padding: 8px;\n+}\n+\n+.brand,\n+.nav-links,\n+.hero-actions {\n+ display: flex;\n+ align-items: center;\n+ gap: 12px;\n+}\n+\n+a {\n+ color: inherit;\n+ text-decoration: none;\n+}\n+\n+.brand {\n+ font-weight: 800;\n+ letter-spacing: -0.03em;\n+}\n+\n+.brand-mark {\n+ display: grid;\n+ width: 42px;\n+ height: 42px;\n+ place-items: center;\n+ border-radius: 14px;\n+ background: linear-gradient(135deg, #ff0f7b, #f89b29);\n+}\n+\n+.nav-links a {\n+ padding: 10px 14px;\n+ border-radius: 999px;\n+ color: rgba(248, 251, 255, 0.76);\n+}\n+\n+.nav-links a:hover,\n+.feature-button:hover {\n+ background: rgba(255, 255, 255, 0.12);\n+}\n+\n+.hero-grid {\n+ grid-template-columns: minmax(0, 1.1fr) minmax(320px, 0.9fr);\n+ align-items: center;\n+ padding: 86px 32px 54px;\n+}\n+\n+.eyebrow {\n+ margin: 0 0 14px;\n+ color: #75ffda;\n+ font-size: 0.78rem;\n+ font-weight: 800;\n+ letter-spacing: 0.18em;\n+ text-transform: uppercase;\n+}\n+\n+h1,\n+h2,\n+h3,\n+p {\n+ margin-top: 0;\n+}\n+\n+h1 {\n+ max-width: 760px;\n+ margin-bottom: 24px;\n+ font-size: clamp(3rem, 9vw, 7.7rem);\n+ line-height: 0.86;\n+ letter-spacing: -0.09em;\n+}\n+\n+h2 {\n+ margin-bottom: 0;\n+ font-size: clamp(2rem, 5vw, 4.5rem);\n+ line-height: 0.95;\n+ letter-spacing: -0.07em;\n+}\n+\n+h3 {\n+ margin-bottom: 14px;\n+ font-size: 1.55rem;\n+ letter-spacing: -0.04em;\n+}\n+\n+.hero-text,\n+.feature-detail p,\n+.cards p,\n+.deploy p {\n+ color: rgba(248, 251, 255, 0.72);\n+ font-size: 1.08rem;\n+ line-height: 1.7;\n+}\n+\n+.hero-text {\n+ max-width: 640px;\n+}\n+\n+.button {\n+ display: inline-flex;\n+ align-items: center;\n+ justify-content: center;\n+ min-height: 48px;\n+ padding: 0 20px;\n+ border: 1px solid rgba(255, 255, 255, 0.18);\n+ border-radius: 999px;\n+ font-weight: 800;\n+}\n+\n+.button.primary {\n+ color: #140017;\n+ background: #75ffda;\n+}\n+\n+.button.secondary {\n+ background: rgba(255, 255, 255, 0.1);\n+}\n+\n+.terminal-card,\n+.feature-detail,\n+.cards article,\n+.deploy {\n+ border: 1px solid rgba(255, 255, 255, 0.14);\n+ border-radius: 28px;\n+ background: rgba(3, 7, 18, 0.62);\n+}\n+\n+.terminal-card {\n+ overflow: hidden;\n+ transform: rotate(2deg);\n+ box-shadow: 24px 24px 0 rgba(117, 255, 218, 0.12);\n+}\n+\n+.terminal-top {\n+ display: flex;\n+ align-items: center;\n+ gap: 8px;\n+ padding: 16px;\n+ border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n+}\n+\n+.terminal-top span {\n+ width: 12px;\n+ height: 12px;\n+ border-radius: 999px;\n+ background: #ff4d8d;\n+}\n+\n+.terminal-top span:nth-child(2) {\n+ background: #ffcb4d;\n+}\n+\n+.terminal-top span:nth-child(3) {\n+ background: #75ffda;\n+}\n+\n+.terminal-top strong {\n+ margin-left: auto;\n+ color: rgba(248, 251, 255, 0.62);\n+}\n+\n+pre {\n+ margin: 0;\n+ padding: 24px;\n+ overflow-x: auto;\n+ color: #75ffda;\n+ font: 700 0.95rem/1.8 ui-monospace, SFMono-Regular, Consolas, monospace;\n+}\n+\n+.stats {\n+ grid-template-columns: repeat(3, 1fr);\n+ padding: 0 32px 32px;\n+}\n+\n+.stats div {\n+ padding: 22px;\n+ border-radius: 24px;\n+ background: rgba(255, 255, 255, 0.09);\n+}\n+\n+.stats strong,\n+.stats span {\n+ display: block;\n+}\n+\n+.stats strong {\n+ font-size: 2rem;\n+}\n+\n+.stats span {\n+ color: rgba(248, 251, 255, 0.64);\n+}\n+\n+.section {\n+ padding: 88px 8px 0;\n+}\n+\n+.section-heading {\n+ max-width: 760px;\n+ margin-bottom: 28px;\n+}\n+\n+.feature-layout {\n+ grid-template-columns: 0.9fr 1.1fr;\n+}\n+\n+.feature-list {\n+ display: grid;\n+ gap: 12px;\n+}\n+\n+.feature-button {\n+ padding: 18px;\n+ border: 1px solid rgba(255, 255, 255, 0.12);\n+ border-radius: 22px;\n+ color: #f8fbff;\n+ text-align: left;\n+ background: rgba(255, 255, 255, 0.06);\n+ cursor: pointer;\n+}\n+\n+.feature-button span,\n+.cards span {\n+ display: block;\n+ margin-bottom: 7px;\n+ color: #ffb6d4;\n+ font-size: 0.78rem;\n+ font-weight: 800;\n+ letter-spacing: 0.14em;\n+ text-transform: uppercase;\n+}\n+\n+.feature-button strong {\n+ font-size: 1.05rem;\n+}\n+\n+.feature-button.active {\n+ color: #130018;\n+ background: linear-gradient(135deg, #75ffda, #f9f871);\n+}\n+\n+.feature-button.active span {\n+ color: rgba(19, 0, 24, 0.7);\n+}\n+\n+.feature-detail {\n+ min-height: 440px;\n+ padding: 34px;\n+}\n+\n+.feature-detail pre {\n+ margin-top: 26px;\n+ border-radius: 22px;\n+ background: #030712;\n+}\n+\n+.cards {\n+ display: grid;\n+ grid-template-columns: repeat(3, 1fr);\n+ gap: 18px;\n+}\n+\n+.cards .section-heading {\n+ grid-column: 1 / -1;\n+}\n+\n+.cards article {\n+ padding: 26px;\n+}\n+\n+.deploy {\n+ grid-template-columns: 1fr auto;\n+ align-items: center;\n+ padding: 34px;\n+}\n+\n+.deploy code {\n+ color: #75ffda;\n+}\n+\n+@media (max-width: 860px) {\n+ .nav,\n+ .hero-grid,\n+ .feature-layout,\n+ .deploy {\n+ grid-template-columns: 1fr;\n+ }\n+\n+ .nav-links {\n+ flex-wrap: wrap;\n+ }\n+\n+ .hero {\n+ min-height: auto;\n+ }\n+\n+ .hero-grid,\n+ .stats {\n+ padding-inline: 10px;\n+ }\n+\n+ .terminal-card {\n+ transform: none;\n+ }\n+\n+ .stats,\n+ .cards {\n+ grid-template-columns: 1fr;\n+ }\n+}\n+\n+@media (max-width: 560px) {\n+ .shell {\n+ width: min(100% - 18px, 1180px);\n+ padding-top: 9px;\n+ }\n+\n+ .hero {\n+ border-radius: 24px;\n+ }\n+\n+ .hero-grid {\n+ padding-top: 48px;\n+ }\n+\n+ .hero-actions {\n+ align-items: stretch;\n+ flex-direction: column;\n+ }\n+}\n*** Update File: /opt/angular-showcase/src/styles.css\n@@\n-/* You can add global styles to this file, and also import other style files */\n+* {\n+ box-sizing: border-box;\n+}\n+\n+html {\n+ scroll-behavior: smooth;\n+}\n+\n+body {\n+ margin: 0;\n+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif;\n+ background: #080c1f;\n+}\n*** Update File: /opt/angular-showcase/src/app/app.spec.ts\n@@\n- it('should render title', async () => {\n+ it('should render the showcase headline', async () => {\n const fixture = TestBed.createComponent(App);\n await fixture.whenStable();\n const compiled = fixture.nativeElement as HTMLElement;\n- expect(compiled.querySelector('h1')?.textContent).toContain('Hello, angular-showcase');\n+ expect(compiled.querySelector('h1')?.textContent).toContain('modern demo site');\n });\n });\n*** Add File: /opt/angular-showcase/public/CNAME\n+angular.gulasch.site\n*** End Patch"
}