docs(docs-infra): new homepage

This commit is contained in:
Matthieu Riegler
2026-01-22 03:31:18 +01:00
committed by Matthew Beck (Berry)
parent 8de0d25ffa
commit bd1cdac54d
28 changed files with 2999 additions and 861 deletions
+25
View File
@@ -60,6 +60,7 @@ APPLICATION_DEPS = [
":node_modules/@lezer/highlight",
":node_modules/@lezer/javascript",
":node_modules/@stackblitz/sdk",
":node_modules/shiki",
":node_modules/@typescript/vfs",
":node_modules/@webcontainer/api",
":node_modules/@xterm/addon-fit",
@@ -91,6 +92,30 @@ APPLICATION_DEPS = [
":node_modules/crelt",
":node_modules/style-mod",
":node_modules/w3c-keyname",
#
":node_modules/@shikijs/themes",
":node_modules/@shikijs/langs",
":node_modules/@shikijs/engine-oniguruma",
":node_modules/@shikijs/core",
":node_modules/@shikijs/engine-javascript",
":node_modules/@shikijs/types",
":node_modules/@shikijs/vscode-textmate",
":node_modules/hast-util-to-html",
# ":node_modules/oniguruma-to-es",
":node_modules/ccount",
":node_modules/comma-separated-tokens",
":node_modules/hast-util-whitespace",
":node_modules/html-void-elements",
# ":node_modules/oniguruma-parser",
":node_modules/property-information",
# ":node_modules/regex-recursion",
# ":node_modules/regex",
":node_modules/space-separated-tokens",
":node_modules/stringify-entities",
":node_modules/zwitch",
":node_modules/character-entities-html4",
":node_modules/character-entities-legacy",
# ":node_modules/regex-utilities",
]
TEST_FILES = APPLICATION_FILES + glob(["src/app/**/*.spec.ts"])
+19 -1
View File
@@ -40,6 +40,13 @@
"@lezer/lr": "1.4.8",
"@lezer/sass": "1.1.0",
"@marijn/find-cluster-break": "1.0.2",
"@shikijs/core": "^3.21.0",
"@shikijs/engine-javascript": "^3.21.0",
"@shikijs/engine-oniguruma": "^3.21.0",
"@shikijs/langs": "^3.21.0",
"@shikijs/themes": "^3.21.0",
"@shikijs/types": "^3.21.0",
"@shikijs/vscode-textmate": "^10.0.2",
"@stackblitz/sdk": "1.11.0",
"@types/dom-navigation": "1.0.6",
"@types/jasmine": "6.0.0",
@@ -51,10 +58,17 @@
"@xterm/xterm": "6.0.0",
"algoliasearch": "5.48.0",
"angular-split": "20.0.0",
"ccount": "^2.0.1",
"character-entities-html4": "^2.1.0",
"character-entities-legacy": "^3.0.0",
"comma-separated-tokens": "^2.0.3",
"crelt": "1.0.6",
"diff": "8.0.3",
"emoji-regex": "10.6.0",
"fflate": "0.8.2",
"hast-util-to-html": "^9.0.5",
"hast-util-whitespace": "^3.0.0",
"html-void-elements": "^3.0.0",
"jsdom": "28.0.0",
"jasmine-core": "6.0.0",
"karma-chrome-launcher": "3.2.0",
@@ -69,13 +83,17 @@
"preact": "10.28.3",
"preact-render-to-string": "6.6.5",
"prettier": "3.8.1",
"property-information": "^7.1.0",
"rxjs": "7.8.2",
"shiki": "3.22.0",
"space-separated-tokens": "^2.0.2",
"stringify-entities": "^4.0.4",
"style-mod": "4.1.3",
"tinyglobby": "0.2.15",
"tslib": "2.8.1",
"typescript": "6.0.0-beta",
"w3c-keyname": "2.2.8"
"w3c-keyname": "2.2.8",
"zwitch": "^2.0.4"
},
"devDependencies": {
"autoprefixer": "10.4.24",
@@ -95,9 +95,6 @@ export class SearchDialog {
if (!this.dialog().nativeElement.open) {
this.dialog().nativeElement.showModal?.();
}
// We want to select the pre-existing text on opening
// In order to change the search input with minimal user interaction.
this.searchForm().focusBoundControl();
},
});
@@ -28,10 +28,10 @@ import {IconComponent} from '../icon/icon.component';
class: 'docs-form-element',
},
})
export class TextField implements FormValueControl<string | null> {
export class TextField implements FormValueControl<string> {
readonly input = viewChild.required<ElementRef<HTMLInputElement>>('inputRef');
readonly name = input<string>('');
readonly value = model<string | null>(null);
readonly value = model<string>('');
readonly placeholder = input<string | null>(null);
readonly disabled = model<boolean>(false);
readonly hideIcon = input<boolean>(false);
+38 -38
View File
@@ -5,6 +5,19 @@
// as well as better human-readability
// --> https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch
// Grays
$gray-1000: oklch(16.93% 0.004 285.95); // #0f0f11
$gray-900: oklch(19.37% 0.006 300.98); // #151417
$gray-800: oklch(25.16% 0.008 308.11); // #232125
$gray-700: oklch(36.98% 0.014 302.71); // #413e46
$gray-600: oklch(44% 0.019 306.08); // #55505b
$gray-500: oklch(54.84% 0.023 304.99); // #746e7c
$gray-400: oklch(70.9% 0.015 304.04); // #a39fa9
$gray-300: oklch(84.01% 0.009 308.34); // #ccc9cf
$gray-200: oklch(91.75% 0.004 301.42); // #e4e3e6
$gray-100: oklch(97.12% 0.002 325.59); // #f6f5f6
$gray-50: oklch(98.81% 0 0); // #fbfbfb
@mixin root-definitions() {
// PRIMITIVES
// Colors
@@ -47,19 +60,6 @@
--symbolic-brown: oklch(49.06% 0.128 46.41); // #994411
--symbolic-lime: oklch(70.33% 0.2078857836035299 135.66843631046476); // #5dba00
// Grays
--gray-1000: oklch(16.93% 0.004 285.95); // #0f0f11
--gray-900: oklch(19.37% 0.006 300.98); // #151417
--gray-800: oklch(25.16% 0.008 308.11); // #232125
--gray-700: oklch(36.98% 0.014 302.71); // #413e46
--gray-600: oklch(44% 0.019 306.08); // #55505b
--gray-500: oklch(54.84% 0.023 304.99); // #746e7c
--gray-400: oklch(70.9% 0.015 304.04); // #a39fa9
--gray-300: oklch(84.01% 0.009 308.34); // #ccc9cf
--gray-200: oklch(91.75% 0.004 301.42); // #e4e3e6
--gray-100: oklch(97.12% 0.002 325.59); // #f6f5f6
--gray-50: oklch(98.81% 0 0); // #fbfbfb
// GRADIENTS
--red-to-pink-horizontal-gradient: linear-gradient(
90deg,
@@ -124,7 +124,7 @@
--pink-to-white-horizontal-gradient: linear-gradient(
90deg,
var(--vivid-pink) 0%,
var(--gray-200) 60%
#{$gray-200} 60%
);
--light-pink-to-light-purple-horizontal-gradient: linear-gradient(
@@ -166,7 +166,7 @@
--black-to-gray-vertical-gradient: linear-gradient(
0deg,
var(--primary-contrast) 0%,
var(--gray-400) 100%
#{$gray-400} 100%
);
--red-to-pink-vertical-gradient: linear-gradient(0deg, var(--hot-red) 0%, var(--vivid-pink) 100%);
@@ -217,35 +217,35 @@
// LIGHT MODE is default
// contrast - light mode
--full-contrast: black;
--primary-contrast: var(--gray-900);
--secondary-contrast: var(--gray-800);
--tertiary-contrast: var(--gray-700);
--quaternary-contrast: var(--gray-500);
--quinary-contrast: var(--gray-300);
--senary-contrast: var(--gray-200);
--septenary-contrast: var(--gray-100);
--octonary-contrast: var(--gray-50);
--primary-contrast: #{$gray-900};
--secondary-contrast: #{$gray-800};
--tertiary-contrast: #{$gray-700};
--quaternary-contrast: #{$gray-500};
--quinary-contrast: #{$gray-300};
--senary-contrast: #{$gray-200};
--septenary-contrast: #{$gray-100};
--octonary-contrast: #{$gray-50};
--page-background: white;
// Home page
// for the "unfilled" portion of the word that hasn't
// been highlighted by the gradient
--gray-unfilled: var(--gray-400);
--webgl-page-background: var(var(--page-background)) --webgl-gray-unfilled: var(--gray-400);
--gray-unfilled: #{$gray-400};
--webgl-page-background: var(var(--page-background)) --webgl-gray-unfilled: #{$gray-400};
}
@mixin dark-mode-definitions() {
// Contrasts
--full-contrast: white;
--primary-contrast: var(--gray-50);
--secondary-contrast: var(--gray-300);
--tertiary-contrast: var(--gray-300);
--quaternary-contrast: var(--gray-400);
--quinary-contrast: var(--gray-500);
--senary-contrast: var(--gray-700);
--septenary-contrast: var(--gray-800);
--octonary-contrast: var(--gray-900);
--page-background: var(--gray-1000);
--primary-contrast: #{$gray-50};
--secondary-contrast: #{$gray-300};
--tertiary-contrast: #{$gray-300};
--quaternary-contrast: #{$gray-400};
--quinary-contrast: #{$gray-500};
--senary-contrast: #{$gray-700};
--septenary-contrast: #{$gray-800};
--octonary-contrast: #{$gray-900};
--page-background: #{$gray-1000};
--bright-blue: color-mix(in srgb, oklch(51.01% 0.274 263.83), var(--full-contrast) 60%);
--indigo-blue: color-mix(in srgb, oklch(51.64% 0.229 281.65), var(--full-contrast) 70%);
@@ -260,7 +260,7 @@
--orange-red: color-mix(in srgb, oklch(63.32% 0.24 31.68), var(--full-contrast) 60%);
--super-green: color-mix(in srgb, oklch(79.12% 0.257 155.13), var(--full-contrast) 70%);
--light-vivid-pink-to-light-electric-violet-horizontal-gradient:
linear-gradient(hsl(from var(--gray-1000) h s l / 0.9), hsl(from var(--gray-1000) h s l / 0.9)),
linear-gradient(hsl(from #{$gray-1000} h s l / 0.9), hsl(from #{$gray-1000} h s l / 0.9)),
linear-gradient(
hsl(from var(--full-contrast) h s l / 0.2),
hsl(from var(--full-contrast) h s l / 0.2)
@@ -319,9 +319,9 @@
);
// Home page - dark mode
--gray-unfilled: var(--gray-700);
--webgl-page-background: var(--gray-1000);
--webgl-gray-unfilled: var(--gray-700);
--gray-unfilled: #{$gray-700};
--webgl-page-background: #{$gray-1000};
--webgl-gray-unfilled: #{$gray-700};
.docs-toggle {
input {
@@ -0,0 +1,42 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {Injectable, OnDestroy} from '@angular/core';
import angularHtml from '@shikijs/langs/angular-html';
import angularTs from '@shikijs/langs/angular-ts';
import githubDark from '@shikijs/themes/github-dark';
import githubLight from '@shikijs/themes/github-light';
import {CodeToHastOptions, createHighlighterCoreSync, HighlighterCore} from 'shiki/core';
import {createOnigurumaEngine} from 'shiki/engine/oniguruma';
@Injectable({providedIn: 'root'})
export class CodeHighligher implements OnDestroy {
private cachedHighligher: HighlighterCore | undefined;
async codeToHtml(code: string, options: CodeToHastOptions): Promise<string> {
const highlighter = await this.getHighlighter();
return highlighter.codeToHtml(code, options);
}
ngOnDestroy(): void {
this.cachedHighligher?.dispose();
}
private async getHighlighter() {
if (!this.cachedHighligher) {
const engine = await createOnigurumaEngine(import('shiki/wasm'));
this.cachedHighligher = createHighlighterCoreSync({
themes: [githubLight, githubDark],
langs: [angularTs, angularHtml],
engine,
});
}
return this.cachedHighligher;
}
}
@@ -0,0 +1,44 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {AsyncPipe} from '@angular/common';
import {ChangeDetectionStrategy, Component, computed, inject, input} from '@angular/core';
import {DomSanitizer} from '@angular/platform-browser';
import {ThemeManager} from '../../../../core/services/theme-manager.service';
import {CodeHighligher} from '../../code-highlighting/code-highlighter';
@Component({
selector: 'adev-code-block',
template: `<pre><code [innerHTML]="highlightedCode() | async"></code></pre>`,
imports: [AsyncPipe],
styles: `
::ng-deep pre {
margin: 0;
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CodeBlock {
codeHighlighter = inject(CodeHighligher);
code = input.required<string>();
language = input<'angular-html' | 'angular-ts'>('angular-ts');
sanitizer = inject(DomSanitizer);
theme = inject(ThemeManager);
highlightedCode = computed(() => {
return this.codeHighlighter
.codeToHtml(this.code(), {
cssVariablePrefix: '--shiki-',
lang: this.language(),
theme: this.theme.theme() === 'light' ? 'github-light' : 'github-dark',
})
.then((hightlightedHtml) => {
return this.sanitizer.bypassSecurityTrustHtml(hightlightedHtml);
});
});
}
@@ -0,0 +1,27 @@
<div class="two-column-step">
<div class="step-description">
<div class="header">Control Flow</div>
<div class="description">
Angular's new control flow syntax makes templates easier to write and understand.
</div>
<button
class="docs-primary-btn"
[attr.text]="'Learn more about Control Flow'"
routerLink="/guide/templates/control-flow"
>
Learn more about Control Flow
</button>
</div>
<div class="step-code">
<div class="code-block">
<div class="code-header">
<span>my-template.html</span>
<span class="copy-icon"></span>
</div>
<adev-code-block [code]="Example" language="angular-html" />
</div>
</div>
</div>
@@ -0,0 +1,80 @@
@use '@angular/docs/styles/media-queries' as mq;
.two-column-step {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 3rem;
text-align: left;
width: 100%;
max-width: 1200px;
margin: 0 auto;
box-sizing: border-box;
background-color: var(--card-background);
border: 1px solid var(--senary-contrast);
border-radius: 0.5rem;
padding: 2rem;
@include mq.for-tablet-down {
grid-template-columns: 1fr;
gap: 2rem;
padding: 1.5rem;
}
}
.step-description {
display: flex;
flex-direction: column;
gap: 1.5rem;
.header {
font-size: 1.5rem;
font-weight: 500;
}
.description {
font-size: 1.1rem;
line-height: 1.6;
color: var(--secondary-contrast);
}
}
.step-code {
display: flex;
flex-direction: column;
gap: 1.5rem;
.code-block {
background: var(--card-background);
border: 1px solid var(--senary-contrast);
border-radius: 0.5rem;
font-size: 0.9rem;
.code-header {
display: flex;
padding: 0.75rem;
background: var(--subtle-purple);
border-radius: 0.25rem 0.25rem 0 0;
transition: background 0.3s ease;
& > span {
background-image: var(--purple-to-blue-horizontal-gradient);
background-clip: text;
color: transparent;
font-size: 0.875rem;
font-style: normal;
font-weight: 400;
line-height: 1.4rem;
letter-spacing: -0.00875rem;
margin: 0;
word-wrap: break-word;
width: fit-content;
}
}
pre {
font-family: 'JetBrains Mono', monospace;
margin: 0;
color: #a9b7c6;
}
}
}
@@ -0,0 +1,50 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {ChangeDetectionStrategy, Component} from '@angular/core';
import {RouterLink} from '@angular/router';
import {CodeBlock} from '../code-block/code-block';
@Component({
selector: 'adev-control-flow-example',
imports: [RouterLink, CodeBlock],
templateUrl: './control-flow-example.html',
styleUrls: ['./control-flow-example.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ControlFlowExample {
Example = example;
}
const example = `
@if(meal() === 'burger') {
<p>You selected a burger!</p>
} @else if(lang meal === 'pizza') {
<p>You selected a pizza!</p>
} @else {
<p>Please select a meal.</p>
}
@for(item of ShoppingList; track $index) {
<p>{{ item }}</p>
}
@switch(user.role) {
@case('user') {
<p>Welcome, {{user.name}}</p>
}
@case('moderator')
@case('admin') {
<p>Welcome</p>
}
@default {
<p>Welcome, guest!</p>
}
}
`.trim();
@@ -0,0 +1,38 @@
<div class="two-column-step">
<div class="step-description">
<div class="header">Deferrable Views</div>
<div class="description">
Deferrable views (also known as <code>&#64;defer</code> blocks) reduce the initial bundle size
of your application by deferring the loading of code that is not strictly necessary for the
initial rendering of a page.
</div>
<button
class="docs-primary-btn"
[attr.text]="'Learn more about deferred loading'"
routerLink="/guide/templates/defer"
>
Learn more about deferred loading
</button>
</div>
<div class="step-code">
<div class="code-block">
<div class="code-header">
<span>data-visualization-page.html</span>
<span class="copy-icon"></span>
</div>
<adev-code-block [code]="exampleHtml" language="angular-html" />
</div>
<div class="code-block">
<div class="code-header">
<span>data-visualization-page.ts</span>
<span class="copy-icon"></span>
</div>
<adev-code-block [code]="exampleTs" />
</div>
</div>
</div>
@@ -0,0 +1,82 @@
@use '@angular/docs/styles/media-queries' as mq;
.two-column-step {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 3rem;
text-align: left;
width: 100%;
max-width: 1200px;
margin: 0 auto;
box-sizing: border-box;
background-color: var(--card-background);
border: 1px solid var(--senary-contrast);
border-radius: 0.5rem;
padding: 2rem;
@include mq.for-tablet-down {
grid-template-columns: 1fr;
gap: 2rem;
padding: 1.5rem;
}
}
.step-description {
display: flex;
flex-direction: column;
gap: 1.5rem;
.header {
font-size: 1.5rem;
font-weight: 500;
}
.description {
font-size: 1.1rem;
line-height: 1.6;
color: var(--secondary-contrast);
}
}
.step-code {
display: flex;
flex-direction: column;
gap: 1.5rem;
min-width: 0;
.code-block {
background: var(--card-background);
border: 1px solid var(--senary-contrast);
border-radius: 0.5rem;
font-size: 0.9rem;
overflow: auto;
.code-header {
display: flex;
padding: 0.75rem;
background: var(--subtle-purple);
border-radius: 0.25rem 0.25rem 0 0;
transition: background 0.3s ease;
& > span {
background-image: var(--purple-to-blue-horizontal-gradient);
background-clip: text;
color: transparent;
font-size: 0.875rem;
font-style: normal;
font-weight: 400;
line-height: 1.4rem;
letter-spacing: -0.00875rem;
margin: 0;
word-wrap: break-word;
width: fit-content;
}
}
pre {
font-family: 'JetBrains Mono', monospace;
margin: 0;
color: #a9b7c6;
}
}
}
@@ -0,0 +1,44 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {ChangeDetectionStrategy, Component} from '@angular/core';
import {RouterLink} from '@angular/router';
import {CodeBlock} from '../code-block/code-block';
@Component({
selector: 'adev-defer-example',
imports: [RouterLink, CodeBlock],
templateUrl: './deferrable-views-example.html',
styleUrls: ['./deferrable-views-example.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class DeferrableViewsExample {
exampleHtml = exampleHtml;
exampleTs = exampleTs;
}
const exampleTs = `
@Component({
// importing the component that will be loaded lazily
imports: [DataVisualization],
})
export class DataVisualizationPage {
dataService = inject(DataService);
dataSet = this.dataService.getData();
}
`.trim();
const exampleHtml = `
<section>
<h2>Data visualization</h2>
@defer {
<!-- lazy loading a heavy component -->
<app-data-visualization [data]="dataSet">
}
</section>
`.trim();
@@ -1,65 +0,0 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
DestroyRef,
EnvironmentInjector,
inject,
input,
} from '@angular/core';
import {takeUntilDestroyed} from '@angular/core/rxjs-interop';
import {forkJoin, switchMap} from 'rxjs';
import {
EmbeddedEditor,
injectEmbeddedTutorialManager,
injectNodeRuntimeSandbox,
} from '../../../editor';
@Component({
selector: 'adev-code-editor',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [EmbeddedEditor],
template: `<embedded-editor />`,
})
export class CodeEditorComponent {
private readonly cdRef = inject(ChangeDetectorRef);
private readonly environmentInjector = inject(EnvironmentInjector);
private readonly destroyRef = inject(DestroyRef);
readonly tutorialFiles = input.required<string>();
constructor() {
this.loadEmbeddedEditor();
}
private loadEmbeddedEditor() {
// If using `async-await`, `this` will be captured until the function is executed
// and completed, which can lead to a memory leak if the user navigates away from
// this component to another page.
forkJoin([
injectNodeRuntimeSandbox(this.environmentInjector),
injectEmbeddedTutorialManager(this.environmentInjector),
])
.pipe(
switchMap(([nodeRuntimeSandbox, embeddedTutorialManager]) =>
embeddedTutorialManager
.fetchAndSetTutorialFiles(this.tutorialFiles())
.then(() => nodeRuntimeSandbox),
),
takeUntilDestroyed(this.destroyRef),
)
.subscribe((nodeRuntimeSandbox) => {
this.cdRef.markForCheck();
nodeRuntimeSandbox.init();
});
}
}
@@ -0,0 +1,216 @@
<div class="header">Hydration</div>
<div class="description">
Hydration is the process that restores the server-side rendered application on the client
<p>
This demo showcases Angular's hydration strategies, allowing you to simulate network latency and
observe how different components hydrate based on their configured triggers.
</p>
</div>
<div class="demo-container">
<div class="max-width-wrapper">
<div class="main-layout">
<!-- Controls & Logs -->
<div class="sidebar">
<div class="card config-card">
<h3 class="sidebar-title font-mono">
<docs-icon>schedule</docs-icon>
Simulation Config
</h3>
<div class="config-controls">
<div class="control-group">
<div class="label-row">
<label>Network Latency</label>
<span class="latency-value font-mono">{{ latency() }}ms</span>
</div>
<input
type="range"
min="0"
max="4000"
step="100"
[value]="latency()"
(input)="updateLatency($event)"
class="range-input"
/>
</div>
<button
(click)="resetSimulation()"
[disabled]="isSimulationRunning()"
class="btn-reset"
>
<docs-icon>refresh</docs-icon>
Reset Lifecycle
</button>
</div>
</div>
<!-- Terminal Log -->
<div class="terminal">
<h3 class="terminal-title font-mono">Hydration Engine Log</h3>
<div class="terminal-body font-mono custom-scrollbar">
@for (evt of eventQueue(); track $index) {
<div class="log-entry animate-in">
<span class="log-index">0{{ $index + 1 }}</span>
<span class="log-tag">event:</span>
<span class="log-msg">{{ evt }}</span>
</div>
}
@if (eventQueue().length === 0) {
<div class="log-placeholder">Awaiting app bootstrap...</div>
}
</div>
</div>
</div>
<!-- Simulator Stage -->
<div class="simulator-stage">
<div class="browser-window">
<!-- Browser UI -->
<div class="browser-header">
<div class="window-controls">
<div class="dot-win"></div>
<div class="dot-win"></div>
<div class="dot-win"></div>
</div>
<div class="address-bar font-mono">
<docs-icon>lock</docs-icon>
angular.dev/hydration-strategies
</div>
</div>
<!-- Viewport Content -->
<div class="viewport">
@if (currentState() === 'Server-Side Rendering...') {
<div class="ssr-overlay animate-fade">
<div class="ssr-spinner">
<div class="spinner-outer"></div>
<div class="spinner-inner"></div>
</div>
<p class="ssr-title">Generating SSR Markup...</p>
</div>
}
<div class="viewport-flex">
<div class="viewport-header">
<div class="header-text">
<h4 class="viewport-title">Component Strategies</h4>
<p class="viewport-subtitle">Mixing eager and deferred hydration triggers.</p>
</div>
<div class="viewport-stats">
<div class="stats-label">Main App</div>
<div class="stats-value font-tabular">{{ progress() }}%</div>
</div>
</div>
<div class="component-grid">
@for (card of [card1(), card2(), card3(), card4()]; track $index) {
<div
(click)="handleCardClick(card.id)"
class="component-card transition-all"
[class]="{
'interactive': isInteractive() || card.strategy === 'on-interaction',
'static': !isInteractive() && card.strategy !== 'on-interaction',
'active-card': card.clickCount > 0,
'dashed-border': !card.isHydrated,
}"
>
<div class="card-top">
<div
class="icon-box transition-all"
[class]="card.isHydrated ? 'icon-hydrated' : 'icon-static'"
>
@if (card.isHydrating) {
<div class="loading-spinner"></div>
} @else {
<docs-icon>water_drop</docs-icon>
}
</div>
@if (card.clickCount > 0) {
<div class="click-badge font-tabular animate-zoom">
<span class="badge-label">CLICKS:</span> {{ card.clickCount }}
</div>
}
</div>
<div class="card-body">
<h5 class="card-title">{{ card.title }}</h5>
<p class="card-desc">{{ card.description }}</p>
</div>
<div class="card-footer">
@if (card.isHydrating) {
<div class="tag tag-deferred animate-slide-in">
<span class="tag-dot spin"></span>
Hydrating...
</div>
} @else if (!card.isHydrated) {
<div class="tag tag-deferred">
<span class="tag-dot pulse"></span>
Static
</div>
} @else {
<div class="tag tag-hydrated animate-slide-in">
<span class="tag-dot"></span>
Hydrated
</div>
}
</div>
@if (card.syntax) {
<div
class="syntax-badge font-mono"
[class]="card.isHydrated ? 'syntax-inactive' : 'syntax-active'"
>
{{ card.syntax }}
</div>
}
</div>
}
</div>
</div>
</div>
<!-- Main Lifecycle Progress -->
<div class="lifecycle-progress-bar">
<div class="progress-fill transition-all" [style.width.%]="progress()"></div>
</div>
</div>
</div>
</div>
<!-- Explanatory Feature Cards -->
<div class="explainer-grid">
<div class="explainer-item">
<h5 class="explainer-title"><span class="dot-red"></span> Eager Hydration</h5>
<p class="explainer-text">
Essential components (like navigation or top-fold content) hydrate as soon as the main
application bundle is ready.
</p>
</div>
<div class="explainer-item">
<h5 class="explainer-title"><span class="dot-violet"></span> Incremental Hydration</h5>
<p class="explainer-text">
Use hydration triggers like <code>on interaction</code> or <code>on timer</code> to delay
JS execution for non-critical elements.
</p>
</div>
<div class="explainer-item">
<h5 class="explainer-title"><span class="dot-green"></span> Event Replay</h5>
<p class="explainer-text">
User clicks are never lost. Angular records events during the "Static" phase and
re-dispatches them once hydrated.
</p>
</div>
</div>
</div>
<button
class="docs-primary-btn"
[attr.text]="'Learn more about hydration'"
routerLink="/guide/hydration"
>
Learn more about hydration
</button>
</div>
@@ -0,0 +1,746 @@
@use '@angular/docs/styles/media-queries' as mq;
:host {
display: flex;
flex-direction: column;
gap: 1rem;
padding: 2rem 1rem;
border: 1px solid var(--senary-contrast);
border-radius: 0.5rem;
--angular-gradient: linear-gradient(135deg, var(--orange-red) 0%, var(--electric-violet) 100%);
}
.header {
font-size: 1.5rem;
font-weight: 500;
}
.description {
font-size: 1.1rem;
line-height: 1.6;
color: var(--secondary-contrast);
}
.demo-container {
color: var(--primary-contrast);
font-family:
system-ui,
-apple-system,
sans-serif;
}
.max-width-wrapper {
max-width: 1152px;
margin: 0 auto;
}
.font-mono {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
}
.font-tabular {
font-variant-numeric: tabular-nums;
}
.transition-all {
transition: all 0.3s ease-out;
}
/* Animations */
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@keyframes pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
@keyframes slide-up {
from {
opacity: 0;
transform: translateY(2rem);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes zoom-in {
from {
opacity: 0;
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
}
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.animate {
&-spin {
animation: spin 1s linear infinite;
}
&-slide-up {
animation: slide-up 0.4s ease-out forwards;
}
&-zoom {
animation: zoom-in 0.2s ease-out forwards;
}
&-fade {
animation: fade-in 0.5s ease-out forwards;
}
}
/* Layout */
.status {
&-header {
display: flex;
justify-content: flex-end;
margin-bottom: 2rem;
}
&-pill {
display: flex;
align-items: center;
gap: 1rem;
background: var(--page-background);
border: 1px solid var(--senary-contrast);
padding: 0.5rem 1rem;
border-radius: 1rem;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
&-indicator {
display: flex;
align-items: center;
gap: 0.5rem;
}
&-text {
font-size: 11px;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--tertiary-contrast);
}
}
.dot {
width: 10px;
height: 10px;
border-radius: 50%;
&.pulse {
animation: pulse 2s infinite;
}
&.bg-zinc-300 {
background-color: var(--quinary-contrast);
}
&.bg-orange-500 {
background-color: var(--symbolic-yellow);
}
&.bg-blue-500 {
background-color: var(--bright-blue);
}
&.bg-emerald-500 {
background-color: var(--super-green);
}
}
.divider {
height: 1rem;
width: 1px;
background-color: var(--senary-contrast);
}
.progress-percentage {
font-size: 11px;
font-weight: bold;
color: var(--electric-violet);
}
.main-layout {
display: grid;
grid-template-columns: 1fr;
gap: 2rem;
@media (min-width: 1024px) {
grid-template-columns: repeat(12, 1fr);
}
}
@media (min-width: 1024px) {
.sidebar {
grid-column: span 4;
}
.simulator-stage {
grid-column: span 8;
}
}
/* Sidebar & Config */
.sidebar {
display: flex;
flex-direction: column;
gap: 2rem;
}
.card {
background: var(--page-background);
border: 1px solid var(--senary-contrast);
border-radius: 1.5rem;
padding: 1.5rem;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
.sidebar-title {
font-size: 12px;
font-weight: bold;
color: var(--quaternary-contrast);
text-transform: uppercase;
letter-spacing: 0.1em;
margin-bottom: 1.5rem;
margin-top: 0;
display: flex;
align-items: center;
gap: 0.5rem;
}
.config-controls {
display: flex;
flex-direction: column;
gap: 1.5rem;
}
.label-row {
display: flex;
justify-content: space-between;
margin-bottom: 0.75rem;
label {
font-size: 14px;
font-weight: bold;
}
}
.latency-value {
font-size: 14px;
color: var(--electric-violet);
font-weight: bold;
}
.range-input {
width: 100%;
height: 6px;
background: var(--septenary-contrast);
border-radius: 8px;
appearance: none;
cursor: pointer;
&::-webkit-slider-thumb {
appearance: none;
width: 18px;
height: 18px;
background: var(--electric-violet);
border: 4px solid var(--page-background);
border-radius: 50%;
box-shadow: 0 4px 6px color-mix(in srgb, var(--electric-violet), transparent 80%);
}
}
.btn-reset {
width: 100%;
padding: 1rem;
background: var(--primary-contrast);
color: var(--page-background);
border: none;
border-radius: 1rem;
font-weight: bold;
font-size: 14px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
transition: transform 0.1s;
&:hover {
background: var(--secondary-contrast);
}
&:active {
transform: scale(0.98);
}
&[disabled] {
pointer-events: none;
opacity: 0.5;
}
}
/* Terminal */
.terminal {
background: var(--primary-contrast);
border-radius: 1.5rem;
padding: 1.5rem;
color: var(--page-background);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1);
position: relative;
&-title {
font-size: 10px;
font-weight: bold;
color: var(--quinary-contrast);
text-transform: uppercase;
letter-spacing: 0.2em;
margin-bottom: 1rem;
margin-top: 0;
}
&-body {
min-height: 160px;
max-height: 220px;
overflow-y: auto;
font-size: 11px;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
}
.log {
&-entry {
display: flex;
align-items: center;
gap: 0.75rem;
}
&-index {
color: var(--senary-contrast);
}
&-tag {
color: var(--senary-contrast);
}
&-msg {
color: var(--septenary-contrast);
}
&-placeholder {
color: var(--tertiary-contrast);
font-style: italic;
}
}
/* Browser Mock */
.browser {
&-window {
background: var(--page-background);
border: 1px solid var(--senary-contrast);
border-radius: 2.5rem;
overflow: hidden;
display: flex;
flex-direction: column;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.05);
min-height: 520px;
}
&-header {
background: var(--octonary-contrast);
border-bottom: 1px solid var(--senary-contrast);
padding: 1rem 1.5rem;
display: flex;
align-items: center;
gap: 1rem;
}
}
.window-controls {
display: flex;
gap: 6px;
}
.dot-win {
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--quinary-contrast);
}
.address-bar {
flex: 1;
background: var(--page-background);
border: 1px solid var(--senary-contrast);
border-radius: 0.75rem;
padding: 0.375rem 1rem;
font-size: 11px;
color: var(--quaternary-contrast);
display: flex;
align-items: center;
gap: 0.5rem;
docs-icon {
font-size: 12px;
}
}
.viewport {
padding: 2rem;
flex: 1;
position: relative;
&-flex {
display: flex;
flex-direction: column;
height: 100%;
}
&-header {
display: flex;
margin-bottom: 1rem;
width: 100%;
}
&-title {
font-size: 24px;
font-weight: 900;
letter-spacing: -0.025em;
margin-bottom: 0.25rem;
margin-top: 0;
}
&-subtitle {
font-size: 12px;
color: var(--quaternary-contrast);
font-weight: 500;
}
&-stats {
text-align: right;
margin-left: auto;
}
}
.ssr {
&-overlay {
position: absolute;
inset: 0;
background: color-mix(in srgb, var(--page-background), transparent 5%);
z-index: 50;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
&-spinner {
position: relative;
width: 48px;
height: 48px;
margin-bottom: 1rem;
}
&-title {
font-weight: bold;
color: var(--primary-contrast);
letter-spacing: -0.01em;
}
}
.spinner {
&-outer {
position: absolute;
inset: 0;
background: var(--angular-gradient);
border-radius: 0.75rem;
opacity: 0.2;
animation: spin 1s linear infinite;
}
&-inner {
position: absolute;
inset: 8px;
background: var(--angular-gradient);
border-radius: 0.5rem;
animation: spin 1.5s linear infinite;
}
}
.stats {
&-label {
font-size: 9px;
font-weight: bold;
text-transform: uppercase;
color: var(--quaternary-contrast);
letter-spacing: 0.1em;
margin-bottom: 0.25rem;
}
&-value {
font-size: 32px;
font-weight: 900;
letter-spacing: -0.05em;
}
}
.component-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
flex: 1;
@media (min-width: 640px) {
grid-template-columns: 1fr 1fr;
}
}
/* Card States */
.component-card {
position: relative;
padding: 1.5rem;
border: 1px solid var(--senary-contrast);
border-radius: 1rem;
display: flex;
flex-direction: column;
justify-content: space-between;
user-select: none;
overflow: hidden;
&.interactive {
cursor: pointer;
&:hover {
border-color: var(--electric-violet);
box-shadow: 0 20px 25px -5px color-mix(in srgb, var(--electric-violet), transparent 95%);
}
}
&.static {
background: color-mix(in srgb, var(--octonary-contrast), transparent 50%);
border-color: transparent;
}
&.active-card {
box-shadow: inset 0 2px 4px color-mix(in srgb, var(--electric-violet), transparent 95%);
overflow: hidden;
}
&.dashed-border {
border-style: dashed;
border-color: var(--quinary-contrast);
}
}
.card {
&-top {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
&-body {
margin-top: 1rem;
}
&-title {
font-size: 18px;
font-weight: 900;
margin-bottom: 0.125rem;
}
&-desc {
font-size: 10px;
color: var(--quaternary-contrast);
font-weight: 500;
line-height: 1.25;
}
&-footer {
margin-top: 1rem;
}
}
.icon {
&-box {
width: 48px;
height: 48px;
border-radius: 1rem;
display: flex;
align-items: center;
justify-content: center;
}
&-hydrated {
background: var(--septenary-contrast);
.component-card.interactive:hover & {
background: var(--electric-violet);
color: var(--page-background);
}
}
&-static {
background: var(--octonary-contrast);
color: var(--senary-contrast);
}
}
.loading-spinner {
width: 24px;
height: 24px;
border: 2px solid var(--senary-contrast);
border-top-color: var(--electric-violet);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
.click {
&-badge {
background: var(--electric-violet);
color: var(--page-background);
padding: 0.25rem 0.625rem;
border-radius: 9999px;
font-size: 10px;
font-weight: 900;
letter-spacing: 0.05em;
display: flex;
align-items: center;
gap: 4px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
}
.badge-label {
opacity: 0.7;
}
.tag {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 9px;
font-weight: 900;
text-transform: uppercase;
letter-spacing: 0.1em;
padding: 0.25rem 0.5rem;
border-radius: 0.375rem;
width: fit-content;
&-dot {
width: 6px;
height: 6px;
border-radius: 50%;
}
&-deferred {
color: var(--symbolic-yellow);
background: color-mix(in srgb, var(--symbolic-yellow), transparent 90%);
.tag-dot {
background: var(--symbolic-yellow);
}
}
&-hydrated {
color: var(--super-green);
background: color-mix(in srgb, var(--super-green), transparent 90%);
.tag-dot {
background: var(--super-green);
}
}
}
.syntax {
&-badge {
position: absolute;
top: 0;
right: 0;
padding: 0.25rem 0.75rem;
font-size: 8px;
font-weight: bold;
border-left: 1px solid;
border-bottom: 1px solid;
border-bottom-left-radius: 0.75rem;
}
&-active {
background: color-mix(in srgb, var(--electric-violet), transparent 95%);
color: var(--electric-violet);
border-color: color-mix(in srgb, var(--electric-violet), transparent 90%);
}
&-inactive {
background: var(--septenary-contrast);
color: var(--quaternary-contrast);
border-color: var(--senary-contrast);
}
}
/* Progress bar */
.lifecycle-progress-bar {
height: 6px;
background: var(--septenary-contrast);
width: 100%;
position: relative;
}
.progress-fill {
position: absolute;
left: 0;
top: 0;
height: 100%;
background: var(--angular-gradient);
box-shadow: 0 0 10px color-mix(in srgb, var(--electric-violet), transparent 70%);
}
/* Explainer */
.explainer {
&-grid {
margin-top: 1rem;
display: grid;
grid-template-columns: 1fr;
gap: 3rem;
border-top: 1px solid var(--senary-contrast);
padding-top: 3rem;
margin-bottom: 1rem;
@media (min-width: 768px) {
grid-template-columns: repeat(3, 1fr);
}
}
&-item {
display: flex;
flex-direction: column;
gap: 1rem;
}
&-title {
font-weight: bold;
display: flex;
align-items: center;
gap: 0.75rem;
font-size: 16px;
margin: 0;
span {
width: 8px;
height: 8px;
border-radius: 50%;
}
}
&-text {
font-size: 14px;
color: var(--quaternary-contrast);
line-height: 1.6;
}
}
.dot {
&-red {
background: var(--orange-red);
}
&-violet {
background: var(--electric-violet);
}
&-green {
background: var(--super-green);
}
}
code {
background: var(--septenary-contrast);
padding: 0 4px;
border-radius: 4px;
color: var(--electric-violet);
font-family: inherit;
}
/* Scrollbar */
.custom-scrollbar {
&::-webkit-scrollbar {
width: 4px;
}
&::-webkit-scrollbar-track {
background: transparent;
}
&::-webkit-scrollbar-thumb {
background: var(--tertiary-contrast);
border-radius: 10px;
}
}
@@ -0,0 +1,243 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {ChangeDetectionStrategy, Component, signal, WritableSignal} from '@angular/core';
import {IconComponent} from '@angular/docs';
import {RouterLink} from '@angular/router';
interface SimulationCard {
id: number;
clickCount: number;
penddingEvents: number;
isHydrated: boolean;
isHydrating: boolean;
strategy?: 'on-interaction' | 'on-timer';
syntax?: string;
title: string;
description: string;
}
@Component({
selector: 'adev-hydration-example',
imports: [RouterLink, IconComponent],
templateUrl: './hydration-example.html',
styleUrls: ['./hydration-example.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class HydrationExample {
latency = signal(500);
progress = signal(0);
currentState = signal<
'Server-Side Rendering...' | 'Initial Paint' | 'Loading Bundle' | 'Hydrated'
>('Server-Side Rendering...');
isInteractive = signal(false);
eventQueue = signal<string[]>([]);
isSimulationRunning = signal(false);
card1 = signal<SimulationCard>({
id: 1,
clickCount: 0,
penddingEvents: 0,
isHydrated: false,
isHydrating: true,
title: 'Eager Module',
description: 'Part of initial bundle.',
});
card2 = signal<SimulationCard>({
id: 2,
clickCount: 0,
penddingEvents: 0,
isHydrated: false,
isHydrating: true,
title: 'Eager Module',
description: 'Part of initial bundle.',
});
card3 = signal<SimulationCard>({
id: 3,
clickCount: 0,
penddingEvents: 0,
isHydrated: false,
isHydrating: false,
strategy: 'on-interaction',
syntax: '@defer (hydrate on interaction)',
title: 'Interaction Module',
description: 'Logic loads on click.',
});
card4 = signal<SimulationCard>({
id: 4,
clickCount: 0,
penddingEvents: 0,
isHydrated: false,
isHydrating: false,
strategy: 'on-timer',
syntax: '@defer (hydrate on timer(2s))',
title: 'Timer Module',
description: 'Hydrates after 2s delay.',
});
constructor() {
this.startLifecycle();
}
protected async startLifecycle() {
if (this.isSimulationRunning()) return;
this.isSimulationRunning.set(true);
this.currentState.set('Server-Side Rendering...');
this.progress.set(0);
this.isInteractive.set(false);
this.eventQueue.set([]);
const resetCard = (card: SimulationCard): SimulationCard => ({
...card,
clickCount: 0,
penddingEvents: 0,
isHydrated: false,
isHydrating: card.strategy ? false : true,
});
this.card1.update(resetCard);
this.card2.update(resetCard);
this.card3.update(resetCard);
this.card4.update(resetCard);
await this.wait(1000);
this.currentState.set('Initial Paint');
this.progress.set(20);
await this.wait(300);
this.currentState.set('Loading Bundle');
let currentP = 20;
const interval = setInterval(
() => {
currentP += 1;
if (currentP <= 100) this.progress.set(currentP);
if (currentP >= 100) clearInterval(interval);
},
Math.max(this.latency() / 80, 5),
);
await this.wait(this.latency());
this.currentState.set('Hydrated');
this.isInteractive.set(true);
this.progress.set(100);
const hydrateCard = (card: SimulationCard): SimulationCard =>
!card.strategy ? {...card, isHydrated: true, isHydrating: false} : card;
this.card1.update(hydrateCard);
this.card2.update(hydrateCard);
const pendingEvents = this.card1().penddingEvents + this.card2().penddingEvents;
await this.wait(800);
if (pendingEvents > 0) {
this.logEvent(`Replaying ${pendingEvents} queued event(s)...`);
this.processPendingEvents(this.card1);
this.processPendingEvents(this.card2);
}
await this.triggerTimerHydration(4);
this.isSimulationRunning.set(false);
}
protected async triggerTimerHydration(id: number) {
const cardSignal = this.getCardSignal(id);
await this.wait(2000);
if (!this.card4().isHydrated) {
this.logEvent(`Timer fired: Hydrating Module#4`);
cardSignal.update((c) => ({...c, isHydrating: true}));
await this.wait(800);
this.card4.update((c) => ({...c, isHydrating: false, isHydrated: true}));
if (cardSignal().penddingEvents > 0) {
this.logEvent(`Replaying ${cardSignal().penddingEvents} queued event(s)...`);
await this.wait(600);
this.processPendingEvents(cardSignal);
}
}
}
protected async handleCardClick(id: number) {
const card = this.getCardSignal(id);
if (card().isHydrated) {
this.processAction(card);
return;
}
if (card().strategy === 'on-interaction' && !card().isHydrated) {
if (!card().isHydrating) {
this.logEvent(`Manual trigger: Hydrating Module#${card().id}`);
this.logEvent(`Queued click for Module#${card().id}`);
card.update((c) => ({...c, isHydrating: true, penddingEvents: 1}));
await this.wait(600);
if (card().penddingEvents > 0) {
this.logEvent(`Replaying ${card().penddingEvents} queued event(s)...`);
}
this.processPendingEvents(card);
card.update((c) => ({...c, isHydrating: false, isHydrated: true}));
return;
}
}
if (!card().isHydrated) {
this.queueClick(card);
return;
}
}
protected updateLatency(ev: any) {
this.latency.set(parseInt(ev.target.value));
}
protected resetSimulation() {
this.startLifecycle();
}
private async queueClick(card: WritableSignal<SimulationCard>) {
card.update((c) => ({...c, penddingEvents: c.penddingEvents + 1}));
this.logEvent(`Queued click for Module#${card().id}`);
}
private processAction(card: WritableSignal<SimulationCard>) {
card.update((c) => ({...c, clickCount: c.clickCount + 1}));
}
private processPendingEvents(card: WritableSignal<SimulationCard>) {
const pending = card().penddingEvents;
card.update((c) => ({...c, penddingEvents: 0}));
for (let i = 0; i < pending; i++) {
this.processAction(card);
}
}
private logEvent(msg: string) {
this.eventQueue.update((q) => [...q, msg]);
}
private wait(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
private getCardSignal(id: number): WritableSignal<SimulationCard> {
switch (id) {
case 1:
return this.card1;
case 2:
return this.card2;
case 3:
return this.card3;
case 4:
return this.card4;
default:
throw new Error('Invalid card id');
}
}
}
@@ -0,0 +1,51 @@
<div class="two-column-step">
<div class="step-description">
<div class="header">Signals: Fine-Grained Reactivity</div>
<div class="description">
Type in the search box to filter the list. A
<code>computed</code> signal automatically recalculates the
<code>filteredItems</code> whenever the <code>searchTerm</code> signal changes.
</div>
<div class="demo-display">
<input
[value]="searchTerm()"
(input)="onSearch($event)"
placeholder="Search for fruit..."
class="demo-input"
/>
<div class="demo-list">
@for (item of filteredItems(); track item) {
<div class="demo-item">{{ item }}</div>
}
</div>
</div>
<button
class="docs-primary-btn"
[attr.text]="'Learn more about signals'"
routerLink="/guide/signals"
>
Learn more about signals
</button>
</div>
<div class="step-code">
<div class="code-block">
<div class="code-header">
<span>component.ts</span>
<span class="copy-icon"></span>
</div>
<adev-code-block [code]="tsExample" />
</div>
<div class="code-block">
<div class="code-header">
<span>component.html</span>
<span class="copy-icon"></span>
</div>
<adev-code-block [code]="htmlExample" language="angular-html" />
</div>
</div>
</div>
@@ -0,0 +1,125 @@
@use '@angular/docs/styles/media-queries' as mq;
.two-column-step {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 3rem;
text-align: left;
width: 100%;
max-width: 1200px;
margin: 0 auto;
box-sizing: border-box;
background-color: var(--card-background);
border: 1px solid var(--senary-contrast);
border-radius: 0.5rem;
padding: 2rem;
@include mq.for-tablet-down {
grid-template-columns: 1fr;
gap: 2rem;
padding: 1.5rem;
}
}
.step-description {
display: flex;
flex-direction: column;
gap: 1.5rem;
.header {
font-size: 1.5rem;
font-weight: 500;
}
.description {
font-size: 1.1rem;
line-height: 1.6;
color: var(--secondary-contrast);
}
.demo-display {
display: flex;
flex-direction: column;
gap: 1rem;
height: 250px;
margin-bottom: 2rem;
.demo-input {
width: 100%;
padding: 1rem;
border-radius: 0.5rem;
border: 1px solid var(--senary-contrast);
background: var(--septenary-contrast);
color: var(--primary-contrast);
font-size: 1rem;
box-sizing: border-box;
&:focus {
outline: 2px solid var(--primary-color);
border-color: transparent;
}
}
.demo-list {
display: flex;
flex: 1;
flex-direction: column;
gap: 0.25rem;
padding-right: 0.5rem;
border-radius: 0.5rem;
overflow-y: auto;
border: 1px solid var(--senary-contrast);
background: var(--septenary-contrast);
color: var(--primary-contrast);
.demo-item {
padding: 0.25rem 1rem;
color: var(--secondary-contrast);
border-radius: 0.5rem;
transition: background 0.2s;
margin: 0.2rem;
&:hover {
background: var(--senary-contrast);
}
}
}
}
}
.step-code {
display: flex;
flex-direction: column;
gap: 1.5rem;
min-width: 0;
.code-block {
background: var(--card-background);
border: 1px solid var(--senary-contrast);
border-radius: 0.5rem;
font-size: 0.9rem;
overflow: auto;
.code-header {
display: flex;
padding: 0.75rem;
background: var(--subtle-purple);
border-radius: 0.25rem 0.25rem 0 0;
transition: background 0.3s ease;
& > span {
background-image: var(--purple-to-blue-horizontal-gradient);
background-clip: text;
color: transparent;
font-size: 0.875rem;
font-style: normal;
font-weight: 400;
line-height: 1.4rem;
letter-spacing: -0.00875rem;
margin: 0;
word-wrap: break-word;
width: fit-content;
}
}
}
}
@@ -0,0 +1,69 @@
/*!
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {ChangeDetectionStrategy, Component, computed, signal} from '@angular/core';
import {RouterLink} from '@angular/router';
import {CodeBlock} from '../code-block/code-block';
@Component({
selector: 'adev-signals-demo',
imports: [RouterLink, CodeBlock],
templateUrl: './signals-demo.html',
styleUrls: ['./signals-demo.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SignalsDemo {
tsExample = tsExample;
htmlExample = htmlExample;
items = signal([
'Apple',
'Apricot',
'Avocado',
'Banana',
'Blueberry',
'Cherry',
'Date',
'Dragonfruit',
]);
searchTerm = signal('');
// A computed signal that derives the filtered list.
// It automatically re-runs when a dependency changes.
filteredItems = computed(() =>
this.items().filter((item) => item.toLowerCase().includes(this.searchTerm().toLowerCase())),
);
onSearch(event: Event) {
this.searchTerm.set((event.target as HTMLInputElement).value);
}
}
const tsExample = `
// Source signals for state.
items = signal(['Apple', 'Banana', /*...*/ ]);
searchTerm = signal('');
// A computed signal that derives the filtered list.
// It automatically re-runs when a dependency changes.
filteredItems = computed(() =>
this.items.filter(item =>
item.toLowerCase().includes (
this.searchTerm().toLowerCase()
)
)
) ;
`.trim();
const htmlExample = `
<input [valuel="searchTerm()" (input)="searchTerm.set($event)" />
<ul>
@for (item of filteredItems (); track item) {
<li>{{ item }}</Li>
}
</ul>
`.trim();
+228 -475
View File
@@ -1,489 +1,242 @@
<main class="adev-home" #home>
<div class="banners-layer layer">
<div class="adev-banner-container">
<a href="events/v21" class="adev-banner" target="_blank">
<h1 tabindex="-1">Angular v21 is here!</h1>
<p class="adev-banner-cta">🕹️ Start your adventure</p>
</a>
</div>
</div>
<section class="logo-section">
@if (!isUwu) {
<!-- Angular logo -->
<div class="logo-layer">
<svg class="svg" viewBox="0 0 1280 400" xmlns="http://www.w3.org/2000/svg">
<defs>
<!-- Do not remove `gradientTransform` attribute. Safari needs it in order to start the animation. -->
<linearGradient
id="gradient"
gradientUnits="userSpaceOnUse"
gradientTransform="rotate(0 640 125)"
>
<stop offset="0" stop-color="#ed0100" />
<stop offset="0.25" stop-color="#e449b4" />
<stop offset="0.5" stop-color="#ae3bfc" />
<stop offset="1" stop-color="#7f15fd" />
<animateTransform
attributeName="gradientTransform"
type="rotate"
from="0 640 125"
to="360 640 125"
dur="6.5s"
repeatCount="indefinite"
/>
</linearGradient>
</defs>
<g class="wrapper" fill="url(#gradient)">
<g class="logo">
<g class="shield">
<path
class="shield-base"
d="M311.8,101.7L302,255.6L208.3,53.8L311.8,101.7ZM246.9,300.7L176.1,341.1L105.3,300.7L246.9,300.7ZM50.1,255.6L40.4,101.7L143.9,53.8L50.1,255.6Z"
/>
<path class="shield-middle" d="M176.1,130.4L213.2,220.6L139,220.6L176.1,130.4Z" />
<path
class="shield-bottom-a-arc"
d="M106.419,301.339L105.3,300.7L119.7,265.8L232.5,265.8L246.9,300.7L245.781,301.339L106.419,301.339Z"
/>
</g>
<path
class="capt-a-letter letter"
d="M515.4,288L553.1,288L484.4,92.7L440.7,92.7L372,288L409.7,288L425.8,239.9L499.2,239.9L515.4,288ZM435.4,211.4L461.8,132.8L463.3,132.8L489.7,211.4L435.4,211.4Z"
/>
<path
class="n-letter letter"
d="M590.4,202.2L590.4,288L555.9,288L555.9,141.6L588.9,141.6L588.9,166.5L590.6,166.5C594,158.3 599.4,151.8 606.8,147C614.2,142.2 623.4,139.8 634.3,139.8C644.4,139.8 653.2,142 660.8,146.3C668.3,150.6 674.2,156.9 678.3,165.1C682.5,173.3 684.5,183.2 684.4,194.9L684.4,288L649.9,288L649.9,200.1C649.9,190.3 647.4,182.7 642.3,177.1C637.2,171.6 630.3,168.8 621.4,168.8C615.4,168.8 610,170.1 605.3,172.8C600.6,175.4 597,179.2 594.3,184.2C591.8,189.1 590.4,195.1 590.4,202.2Z"
/>
<path
class="g-letter letter"
d="M760.5,345.9C748.1,345.9 737.5,344.2 728.6,340.9C719.7,337.6 712.6,333.1 707.2,327.5C701.8,321.9 698,315.7 696,308.9L727.1,301.4C728.5,304.3 730.5,307.1 733.2,309.9C735.9,312.7 739.5,315.1 744,317C748.5,318.9 754.3,319.8 761.2,319.8C771,319.8 779.1,317.4 785.5,312.7C791.9,308 795.1,300.2 795.1,289.4L795.1,261.7L793.4,261.7C791.6,265.3 789,268.9 785.6,272.7C782.2,276.5 777.7,279.6 772.2,282.1C766.6,284.6 759.7,285.9 751.3,285.9C740.1,285.9 729.9,283.2 720.7,277.9C711.6,272.6 704.3,264.6 699,254.1C693.6,243.5 690.9,230.3 690.9,214.3C690.9,198.2 693.6,184.6 699,173.5C704.4,162.4 711.6,154 720.8,148.2C730,142.4 740.2,139.6 751.4,139.6C760,139.6 767.1,141 772.6,143.9C778.2,146.8 782.6,150.3 785.9,154.3C789.2,158.3 791.7,162.1 793.4,165.7L795.3,165.7L795.3,141.5L829.3,141.5L829.3,290.3C829.3,302.8 826.3,313.2 820.3,321.4C814.3,329.6 806.2,335.7 795.8,339.8C785.4,343.9 773.6,345.9 760.5,345.9ZM760.8,258.8C768.1,258.8 774.3,257 779.5,253.5C784.6,249.9 788.6,244.8 791.2,238.2C793.9,231.5 795.2,223.5 795.2,214.2C795.2,205 793.9,196.9 791.2,190C788.6,183.1 784.7,177.7 779.6,173.8C774.5,170 768.2,168 760.8,168C753.1,168 746.7,170 741.5,174C736.4,178 732.5,183.5 729.9,190.4C727.3,197.4 726,205.3 726,214.1C726,223.1 727.3,230.9 730,237.6C732.6,244.3 736.5,249.5 741.7,253.2C746.8,256.9 753.2,258.8 760.8,258.8Z"
/>
<path
class="u-letter letter"
d="M933.8,226.4L933.8,141.6L968.3,141.6L968.3,288L934.8,288L934.8,262L933.3,262C930,270.2 924.6,276.9 917,282.1C909.5,287.3 900.2,289.9 889.2,289.9C879.6,289.9 871.1,287.8 863.8,283.5C856.5,279.2 850.7,273 846.6,264.7C842.5,256.5 840.4,246.5 840.4,234.8L840.4,141.6L875,141.6L875,229.5C875,238.8 877.5,246.2 882.6,251.6C887.7,257.1 894.4,259.8 902.6,259.8C907.7,259.8 912.6,258.6 917.4,256.1C922.2,253.6 926.1,249.9 929.2,245C932.3,240 933.8,233.8 933.8,226.4Z"
/>
<rect class="l-letter letter" x="979.6" y="92.7" width="34.5" height="195.3" />
<path
class="a-letter letter"
d="M1068.9,290.9C1059.6,290.9 1051.3,289.2 1043.9,285.9C1036.5,282.6 1030.6,277.6 1026.4,271.1C1022.1,264.6 1020,256.5 1020,246.9C1020,238.6 1021.5,231.8 1024.6,226.4C1027.7,221 1031.8,216.7 1037.1,213.4C1042.4,210.2 1048.3,207.7 1055,206C1061.6,204.3 1068.5,203.1 1075.6,202.3C1084.2,201.4 1091.1,200.6 1096.5,199.9C1101.8,199.2 1105.7,198 1108.2,196.5C1110.6,194.9 1111.9,192.5 1111.9,189.2L1111.9,188.6C1111.9,181.4 1109.8,175.9 1105.5,171.9C1101.2,168 1095.1,166 1087.1,166C1078.6,166 1072,167.8 1067,171.5C1062.1,175.2 1058.8,179.5 1057,184.6L1024.8,180C1027.3,171.1 1031.5,163.7 1037.4,157.6C1043.2,151.6 1050.4,147.1 1058.8,144.1C1067.3,141.1 1076.6,139.6 1086.8,139.6C1093.9,139.6 1100.9,140.4 1107.9,142.1C1114.9,143.8 1121.3,146.5 1127.1,150.3C1132.9,154.1 1137.5,159.2 1141.1,165.7C1144.6,172.2 1146.4,180.3 1146.4,190L1146.4,288L1113.2,288L1113.2,267.9L1112.1,267.9C1110,272 1107.1,275.8 1103.3,279.3C1099.5,282.8 1094.8,285.7 1089.1,287.8C1083.2,289.9 1076.6,290.9 1068.9,290.9ZM1077.9,265.6C1084.8,265.6 1090.8,264.2 1095.9,261.5C1101,258.7 1104.9,255.1 1107.7,250.5C1110.5,245.9 1111.8,240.9 1111.8,235.5L1111.8,218.2C1110.7,219.1 1108.9,219.9 1106.3,220.7C1103.7,221.5 1100.9,222.1 1097.7,222.7C1094.5,223.3 1091.4,223.8 1088.3,224.2C1085.2,224.6 1082.5,225 1080.2,225.3C1075.1,226 1070.4,227.1 1066.4,228.7C1062.3,230.3 1059.1,232.5 1056.8,235.3C1054.4,238.1 1053.3,241.8 1053.3,246.2C1053.3,252.6 1055.6,257.4 1060.3,260.6C1064.8,264 1070.7,265.6 1077.9,265.6Z"
/>
<path
class="r-letter letter"
d="M1156.9,288L1156.9,141.6L1190.4,141.6L1190.4,166L1191.9,166C1194.6,157.5 1199.2,151 1205.7,146.4C1212.2,141.8 1219.7,139.5 1228.1,139.5C1230,139.5 1232.2,139.6 1234.5,139.7C1236.9,139.9 1238.9,140.1 1240.5,140.4L1240.5,172.1C1239,171.6 1236.7,171.1 1233.6,170.7C1230.5,170.3 1227.4,170.1 1224.5,170.1C1218.2,170.1 1212.6,171.5 1207.6,174.2C1202.6,176.9 1198.7,180.6 1195.8,185.4C1192.9,190.2 1191.5,195.7 1191.5,201.9L1191.5,288L1156.9,288Z"
/>
</g>
</g>
</svg>
<h2>The framework for building scalable web apps with confidence</h2>
</div>
} @else {
<!-- UwU logo -->
<div layerId="uwu" class="uwu-layer">
<img src="assets/images/uwu.png" alt="Angular logo" />
</div>
}
<div class="pattern"></div>
<div class="content">
<div class="adev-banner-container">
<a href="events/v21" class="adev-banner" target="_blank">
<h1 tabindex="-1">Angular v21 is here!</h1>
<p class="adev-banner-cta">🕹️ Start your adventure</p>
</a>
<div class="learn-angular">
<a href="#learn-more" class="adev-cta">
<button class="docs-primary-btn" [attr.text]="'Get Started'" aria-label="Get Started">
Get Started
</button>
</a>
<div class="adev-arrow"></div>
</div>
</section>
<section class="features">
<h2>Productivity meets scalability</h2>
<div class="features-grid">
<div class="feature">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g><rect fill="none" height="24" width="24" x="0" /></g>
<g>
<g>
<polygon points="19,9 20.25,6.25 23,5 20.25,3.75 19,1 17.75,3.75 15,5 17.75,6.25" />
<polygon
points="19,15 17.75,17.75 15,19 17.75,20.25 19,23 20.25,20.25 23,19 20.25,17.75"
/>
<path
d="M11.5,9.5L9,4L6.5,9.5L1,12l5.5,2.5L9,20l2.5-5.5L17,12L11.5,9.5z M9.99,12.99L9,15.17l-0.99-2.18L5.83,12l2.18-0.99 L9,8.83l0.99,2.18L12.17,12L9.99,12.99z"
/>
</g>
</g>
</svg>
<h3>AI-forward</h3>
<p>Resources and integrations to supercharge your development with AI</p>
</div>
<div class="feature">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960">
<path
d="m480-400-80-80 80-80 80 80-80 80Zm-85-235L295-735l185-185 185 185-100 100-85-85-85 85ZM225-295 40-480l185-185 100 100-85 85 85 85-100 100Zm510 0L635-395l85-85-85-85 100-100 185 185-185 185ZM480-40 295-225l100-100 85 85 85-85 100 100L480-40Z"
/>
</svg>
<h3>Opinionated & versatile</h3>
<p>Organized yet modular thanks to Angular components and dependency injection</p>
</div>
<div class="feature">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960">
<path
d="M197-197q-54-55-85.5-127.5T80-480q0-84 31.5-156.5T197-763l57 57q-44 44-69 102t-25 124q0 67 25 125t69 101l-57 57Zm113-113q-32-33-51-76.5T240-480q0-51 19-94.5t51-75.5l57 57q-22 22-34.5 51T320-480q0 33 12.5 62t34.5 51l-57 57Zm170-90q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Zm170 90-57-57q22-22 34.5-51t12.5-62q0-33-12.5-62T593-593l57-57q32 32 51 75.5t19 94.5q0 50-19 93.5T650-310Zm113 113-57-57q44-44 69-102t25-124q0-67-25-125t-69-101l57-57q54 54 85.5 126.5T880-480q0 83-31.5 155.5T763-197Z"
/>
</svg>
<h3>Reactive</h3>
<p>Fast state updates with fine-grained reactivity based on Angular Signals</p>
</div>
<div class="feature">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960">
<path
d="M220-260q-92 0-156-64T0-480q0-92 64-156t156-64q37 0 71 13t61 37l68 62-60 54-62-56q-16-14-36-22t-42-8q-58 0-99 41t-41 99q0 58 41 99t99 41q22 0 42-8t36-22l310-280q27-24 61-37t71-13q92 0 156 64t64 156q0 92-64 156t-156 64q-37 0-71-13t-61-37l-68-62 60-54 62 56q16 14 36 22t42 8q58 0 99-41t41-99q0-58-41-99t-99-41q-22 0-42 8t-36 22L352-310q-27 24-61 37t-71 13Z"
/>
</svg>
<h3>Fully featured</h3>
<p>
Everything works together with Angular's first-party modules for forms, routing, and more
</p>
</div>
</div>
</section>
<!-- -->
<section class="performance">
<div class="section-content">
<div class="performance-half">
<h2>When performance matters</h2>
<p class="secondary">
Trusted by millions to build fast, reliable applications that scale with the size of your
team
</p>
<div class="docs-card-grid">
<a routerLink="/guide/signals" class="docs-card">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 -960 960 960"
height="24px"
width="24px"
>
<path
d="M197-197q-54-55-85.5-127.5T80-480q0-84 31.5-156.5T197-763l57 57q-44 44-69 102t-25 124q0 67 25 125t69 101l-57 57Zm113-113q-32-33-51-76.5T240-480q0-51 19-94.5t51-75.5l57 57q-22 22-34.5 51T320-480q0 33 12.5 62t34.5 51l-57 57Zm170-90q-33 0-56.5-23.5T400-480q0-33 23.5-56.5T480-560q33 0 56.5 23.5T560-480q0 33-23.5 56.5T480-400Zm170 90-57-57q22-22 34.5-51t12.5-62q0-33-12.5-62T593-593l57-57q32 32 51 75.5t19 94.5q0 50-19 93.5T650-310Zm113 113-57-57q44-44 69-102t25-124q0-67-25-125t-69-101l57-57q54 54 85.5 126.5T880-480q0 83-31.5 155.5T763-197Z"
/>
</svg>
<div class="card-content">
<p>Reactivity with signals makes it a breeze to manage your state</p>
</div>
<span>Explore signals</span>
</a>
<a routerLink="/guide/routing/rendering-strategies" class="docs-card">
<svg
xmlns="http://www.w3.org/2000/svg"
height="24px"
viewBox="0 -960 960 960"
width="24px"
>
<path
d="M200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v560q0 33-23.5 56.5T760-120H200Zm0-507h560v-133H200v133Zm0 214h560v-134H200v134Zm0 213h560v-133H200v133Zm40-454v-80h80v80h-80Zm0 214v-80h80v80h-80Zm0 214v-80h80v80h-80Z"
/>
</svg>
<div class="card-content">
<p>
Meet your performance targets with SSR, SSG, hydration, and next generation deferred
loading
</p>
</div>
<span>Learn about rendering strategies</span>
</a>
<div class="search-field">
<docs-text-field placeholder="search" (click)="openSearch()" />
</div>
</div>
<div class="performance-half">
<svg
width="558"
height="365"
viewBox="0 0 558 365"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M228.166 59.3864L234.455 85.9391C241.018 84.5704 247.58 83.2017 254.143 82.3805L250.588 55.2803C242.932 56.3752 235.549 57.7439 228.166 59.3864Z"
fill="url(#paint0_linear_194_759)"
/>
<path
d="M98.2778 145.614L120.427 161.765C124.255 156.29 128.631 151.089 133.006 146.162L112.771 127.821C107.575 133.57 102.653 139.592 98.2778 145.614Z"
fill="url(#paint1_linear_194_759)"
/>
<path
d="M63.8232 217.06L90.0743 224.451C91.715 218.155 93.9026 211.585 96.3636 205.563L70.9329 195.435C68.1984 202.552 66.0108 209.669 63.8232 217.06Z"
fill="url(#paint2_linear_194_759)"
/>
<path
d="M155.429 90.8662L170.468 113.587C175.937 110.028 181.68 106.469 187.696 103.458L174.844 79.3691C168.281 82.654 161.718 86.7601 155.429 90.8662Z"
fill="url(#paint3_linear_194_759)"
/>
<path
d="M424.502 146.435C428.877 151.363 433.252 156.564 437.081 162.038L459.23 145.888C454.855 139.866 449.933 133.843 445.011 128.095L424.502 146.435Z"
fill="url(#paint4_linear_194_759)"
/>
<path
d="M382.391 79.3691L369.539 103.458C375.281 106.469 381.023 110.028 386.766 113.587L401.806 90.8662C395.79 86.7601 389.227 82.9278 382.391 79.3691Z"
fill="url(#paint5_linear_194_759)"
/>
<path
d="M486.301 195.435L460.871 205.289C463.332 211.585 465.519 217.881 467.16 224.177L493.411 216.786C491.497 209.943 489.036 202.552 486.301 195.435Z"
fill="url(#paint6_linear_194_759)"
/>
<path
d="M363.523 13.6719L353.952 39.4034C454.581 71.1572 527.865 164.502 529.779 275.093H333.99L306.919 302.467H556.304L556.851 288.507C556.851 285.495 557.124 282.484 557.124 279.199C556.851 154.922 475.637 49.5318 363.523 13.6719Z"
fill="url(#paint7_linear_194_759)"
/>
<path
d="M227.892 274.82H27.7281C29.9157 138.224 141.483 27.6328 278.754 27.6328C285.317 27.6328 291.879 27.9065 298.169 28.454L322.506 3.81741C308.286 1.6275 293.52 0.258789 278.48 0.258789C125.349 0.258789 0.383301 125.358 0.383301 278.926C0.383301 281.937 0.383301 284.948 0.656749 288.233L1.20365 302.194H217.501L227.892 274.82Z"
fill="url(#paint8_linear_194_759)"
/>
<path
d="M341.373 13.9453L162.265 194.614L268.363 224.999L215.861 364.606L394.969 183.938L288.871 153.553L341.373 13.9453Z"
fill="url(#paint9_linear_194_759)"
/>
<defs>
<linearGradient
id="paint0_linear_194_759"
x1="224.18"
y1="87.8605"
x2="263.436"
y2="48.6467"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#F941EA" />
<stop offset="1" stop-color="#9138FF" />
</linearGradient>
<linearGradient
id="paint1_linear_194_759"
x1="102.734"
y1="158.687"
x2="131.291"
y2="130.162"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#F941EA" />
<stop offset="1" stop-color="#9138FF" />
</linearGradient>
<linearGradient
id="paint2_linear_194_759"
x1="63.1336"
y1="227.404"
x2="100.224"
y2="190.353"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#F941EA" />
<stop offset="1" stop-color="#9138FF" />
</linearGradient>
<linearGradient
id="paint3_linear_194_759"
x1="157.935"
y1="110.828"
x2="190.444"
y2="78.3545"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#F941EA" />
<stop offset="1" stop-color="#9138FF" />
</linearGradient>
<linearGradient
id="paint4_linear_194_759"
x1="426.244"
y1="161.616"
x2="461.128"
y2="126.768"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#F941EA" />
<stop offset="1" stop-color="#9138FF" />
</linearGradient>
<linearGradient
id="paint5_linear_194_759"
x1="368.074"
y1="113.184"
x2="405.694"
y2="75.6042"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#F941EA" />
<stop offset="1" stop-color="#9138FF" />
</linearGradient>
<linearGradient
id="paint6_linear_194_759"
x1="459.571"
y1="228.959"
x2="499.852"
y2="188.721"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#F941EA" />
<stop offset="1" stop-color="#9138FF" />
</linearGradient>
<linearGradient
id="paint7_linear_194_759"
x1="311.154"
y1="306.775"
x2="505.716"
y2="112.419"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#F941EA" />
<stop offset="1" stop-color="#9138FF" />
</linearGradient>
<linearGradient
id="paint8_linear_194_759"
x1="20.4484"
y1="321.544"
x2="330.571"
y2="11.7518"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#F941EA" />
<stop offset="1" stop-color="#9138FF" />
</linearGradient>
<linearGradient
id="paint9_linear_194_759"
x1="223.592"
y1="-19.7554"
x2="484.225"
y2="256.555"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#F941EA" />
<stop offset="1" stop-color="#9138FF" />
</linearGradient>
</defs>
</svg>
@if (!isUwu) {
<div class="title">
<h2>Productivity<br />Meets scalability</h2>
<h3>The framework for building scalable web apps with confidence</h3>
</div>
} @else {
<!-- UwU logo -->
<div class="uwu">
<img src="assets/images/uwu.png" alt="Angular logo" />
</div>
}
<div class="learn-angular">
<a href="#learn-more" class="adev-cta">
<button class="docs-primary-btn" [attr.text]="'Get Started'" aria-label="Get Started">
Get Started
</button>
</a>
<div class="adev-arrow"></div>
</div>
</div>
</section>
<!-- -->
<section class="features-section">
<h2 id="features">Features that actually <br />help you solve problems</h2>
<section class="learn-more">
<div class="section-content">
<h2 id="learn-more">Want to learn more?</h2>
<div class="docs-card-grid">
<a routerLink="/tutorials/learn-angular" class="docs-card">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960">
<path
d="m188.15-529.46 108.7 46.15q19.15-38.3 41.57-75.42 22.43-37.11 48.5-71.04l-75.53-14.84q-3.08-.77-5.97.19-2.88.96-5.19 3.27L188.15-529.46Zm144.77 73.69 127.62 127.62q55.46-26 104.88-59 49.42-33 84.66-68.23 75.23-75.23 111.11-154.27 35.89-79.04 41.19-188.35-109.69 5.31-188.34 41-78.66 35.69-153.89 110.92-35.23 35.24-68.23 84.85-33 49.62-59 105.46Zm229.77-102.92q-18.08-17.69-18.08-43.92t18.08-44.31Q580.77-665 607.19-665t44.5 18.08q18.08 18.08 18.08 44.31 0 26.23-18.08 43.92-18.08 18.08-44.5 18.08t-44.5-18.08Zm-29.23 374.92 112.08-111.69q2.3-2.31 3.07-5.39.77-3.07.39-5.77l-15.23-75.53q-33.93 25.69-70.85 48.11-36.92 22.42-75.61 41.96l46.15 108.31Zm314.08-659q5.53 126.39-34.97 228.19-40.5 101.81-129.11 190.04-2.54 2.54-4.88 5.08-2.35 2.54-4.89 4.69l19.69 98.85q2.62 14.61-1.69 28.03-4.31 13.43-14.54 23.66L518.08-104.54l-75.77-177.61-156.16-155.77-177.22-76.54 159.3-158.69q10.62-10.23 24.04-14.73t28.04-1.5l99.23 19.69q2.54-2.15 4.69-4.39 2.16-2.23 4.69-4.38 88.62-88.61 190.23-129.23 101.62-40.61 228.39-35.08ZM175.16-320q29.23-29.61 71.07-29.54 41.85.08 71.46 29.31 29.23 29.23 28.92 71.08-.3 41.84-29.53 71.46-23.7 23.3-72.43 38.96-48.73 15.65-134.26 26.04 10.38-85.54 25.73-134.77 15.34-49.23 39.04-72.54Zm31.61 33q-13.61 14.61-24.42 44.88-10.81 30.27-17.27 74.5 44.23-6.84 74.5-17.65 30.27-10.81 44.88-24.81 17.08-15.46 17.39-38.46.3-23-16.16-40.08-17.07-16.46-40.07-15.96T206.77-287Z"
/>
</svg>
<div class="card-content">
<h3>New to Angular?</h3>
<p>
Try our completely in browser tutorial lessons designed to give you hands on
experience with Angular.
</p>
<p class="secondary">
Recommended for developers new to Angular looking to dive right into the code
</p>
<span>Start coding</span>
</div>
</a>
<a routerLink="/essentials" class="docs-card">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -960 960 960">
<path
d="M557.69-577.46v-38q32.62-15.15 68.27-22.73 35.66-7.58 74.04-7.58 24.08 0 46.77 3.42 22.69 3.43 45.54 9.04v35.54q-22.46-7.85-44.66-11.38-22.19-3.54-47.65-3.54-38.38 0-74.35 9.11-35.96 9.12-67.96 26.12Zm0 219.23v-39.38q31.85-14.85 68.08-22.27 36.23-7.43 74.23-7.43 24.08 0 46.77 3.43 22.69 3.42 45.54 9.03v35.54q-22.46-7.84-44.66-11.38-22.19-3.54-47.65-3.54-38.38 0-74.35 9.19-35.96 9.19-67.96 26.81Zm0-109.23v-38.77q32.62-15.15 68.27-22.73 35.66-7.58 74.04-7.58 24.08 0 46.77 3.42 22.69 3.43 45.54 9.04v35.54q-22.46-7.85-44.66-11.38-22.19-3.54-47.65-3.54-38.38 0-74.35 9.5-35.96 9.5-67.96 26.5ZM251.08-304.62q54.53 0 106.11 12.89 51.58 12.88 101.73 39.42v-417.38q-45-31.92-99.04-48.81-54.04-16.88-108.8-16.88-37.61 0-69.5 7.38t-67.73 20.23q-4.62 1.54-6.54 4.42-1.92 2.89-1.92 6.35v406.31q0 5 3.84 7.5 3.85 2.5 8.46.96 25.62-11.31 60.7-16.85 35.07-5.54 72.69-5.54Zm253.23 52.31q50.38-26.54 99.92-39.42 49.54-12.89 104.69-12.89 37.62 0 73.5 5.23 35.89 5.24 59.89 13.31 4.61 1.93 8.46-.57 3.84-2.5 3.84-7.89V-697q0-3.46-1.92-6.15-1.92-2.7-6.54-4.62-33.61-14.69-66.43-21.15-32.83-6.46-70.8-6.46-55.15 0-107.57 16.88-52.43 16.89-97.04 48.81v417.38Zm-22.89 67.69q-50.8-35.69-109.65-54.84-58.85-19.16-120.69-19.16-32.47 0-63.77 6.7-31.31 6.69-62.85 17.38-23.1 10.23-43.78-4.34T60-279.46v-420.31q0-15.77 7.77-29t22.54-19.92q37.77-16.54 78.34-24.31t82.43-7.77q62.19 0 120.75 17.39Q430.38-746 481.62-711q50.23-35 108.15-52.38 57.92-17.39 119.15-17.39 41.65 0 82.02 7.77t78.14 24.31q14.76 6.61 22.84 19.88t8.08 29.04v420.31q0 27.12-22.31 41.67-22.31 14.56-45.08 2.48-30.15-10.92-61.15-17.11-31-6.2-62.54-6.2-61.94 0-119.32 19.46-57.37 19.47-108.18 54.54ZM282.15-493.23Z"
/>
</svg>
<div class="card-content">
<h3>More of a reader?</h3>
<p>
Our essentials guides are designed to help you understand Angular fundamentals in the
time it would take to finish a cup of coffee (or tea).
</p>
<p class="secondary">
Recommended for developers with framework experience but want quick overview of
concepts
</p>
<span>Angular concepts</span>
</div>
</a>
<a routerLink="/overview" class="docs-card">
<svg class="angular-logo" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 223 236">
<g clip-path="url(#a)">
<path
fill="url(#b)"
d="m222.077 39.192-8.019 125.923L137.387 0l84.69 39.192Zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301l11.783 28.556ZM111.039 62.675l30.357 73.803H80.681l30.358-73.803ZM7.937 165.115 0 39.192 84.69 0 7.937 165.115Z"
/>
<path
fill="url(#c)"
d="m222.077 39.192-8.019 125.923L137.387 0l84.69 39.192Zm-53.105 162.825-57.933 33.056-57.934-33.056 11.783-28.556h92.301l11.783 28.556ZM111.039 62.675l30.357 73.803H80.681l30.358-73.803ZM7.937 165.115 0 39.192 84.69 0 7.937 165.115Z"
/>
</g>
<defs>
<linearGradient
id="b"
x1="49.009"
x2="225.829"
y1="213.75"
y2="129.722"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#E40035" />
<stop offset=".24" stop-color="#F60A48" />
<stop offset=".352" stop-color="#F20755" />
<stop offset=".494" stop-color="#DC087D" />
<stop offset=".745" stop-color="#9717E7" />
<stop offset="1" stop-color="#6C00F5" />
</linearGradient>
<linearGradient
id="c"
x1="41.025"
x2="156.741"
y1="28.344"
y2="160.344"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="#FF31D9" />
<stop offset="1" stop-color="#FF5BE1" stop-opacity="0" />
</linearGradient>
<clipPath id="a">
<path fill="#fff" d="M0 0h223v236H0z" />
</clipPath>
</defs>
</svg>
<div class="card-content">
<h3>Why Angular?</h3>
<p>Learn about Angular, its benefits, and if it's right for you.</p>
<p class="secondary">
Recommended for developers seeking to learn more about the Angular framework
</p>
<span>About Angular</span>
</div>
</a>
<a routerLink="/ai" class="docs-card">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<g>
<polygon points="19,9 20.25,6.25 23,5 20.25,3.75 19,1 17.75,3.75 15,5 17.75,6.25" />
<polygon
points="19,15 17.75,17.75 15,19 17.75,20.25 19,23 20.25,20.25 23,19 20.25,17.75"
/>
<path
fill="none"
stroke="currentColor"
stroke-width="1.2"
d="M 11.5 9.5 L 9 4 L 6.5 9.5 L 1 12 l 5.5 2.5 L 9 20 l 2.5 -5.5 L 17 12 L 11.5 9.5 z z"
/>
</g>
</svg>
<div class="card-content">
<h3>Are you an LLM or curious about AI in Angular?</h3>
<p>
Head over to our AI section to find prompts, strategies, best practices, examples, and
more.
</p>
<p class="secondary">
Recommended for robots... and developers who want to get the most out of Angular and
AI
</p>
<span>Angular + AI</span>
</div>
</a>
<div ngTabs class="material-tabs">
<div ngTabList selectionMode="follow" selectedTab="signals">
<div class="tab-background"></div>
<div ngTab value="signals">Signals</div>
<div ngTab value="control-flow">Control Flow</div>
<div ngTab value="deferrable-views">Deferrable Views</div>
<div ngTab value="hydration">Hydration</div>
</div>
<div class="sliding-window">
<div ngTabPanel [preserveContent]="true" value="signals">
<ng-template ngTabContent>
@defer (on idle) {
<adev-signals-demo />
}
</ng-template>
</div>
<div ngTabPanel [preserveContent]="true" value="control-flow">
<ng-template ngTabContent>
@defer (on idle) {
<adev-control-flow-example />
}
</ng-template>
</div>
<div ngTabPanel [preserveContent]="true" value="deferrable-views">
<ng-template ngTabContent>
@defer {
<adev-defer-example />
}
</ng-template>
</div>
<div ngTabPanel [preserveContent]="true" value="hydration">
<ng-template ngTabContent>
@defer {
<adev-hydration-example />
}
</ng-template>
</div>
</div>
</div>
</section>
<section class="selling-points">
<h2>Enabling you to build<br />smarter and faster</h2>
<div class="cards">
<a class="card" routerLink="/ai/develop-with-ai">
<div class="icon-wrapper">
<docs-icon>auto_awesome</docs-icon>
</div>
<div class="card-content">
<h3>
<strong>AI-forward</strong> resources and integrations to supercharge your development
with AI
</h3>
</div>
</a>
<a class="card" routerLink="/essentials">
<div class="icon-wrapper">
<docs-icon>grid_view</docs-icon>
</div>
<div class="card-content">
<h3>
<strong>Opinionated & versatile,</strong> organized yet modular thanks to Angular
components and dependency injection
</h3>
</div>
</a>
<a class="card" routerLink="/essentials/signals">
<div class="icon-wrapper">
<docs-icon>sensors</docs-icon>
</div>
<div class="card-content">
<h3>
<strong>Reactive</strong> and fast state updates with fine-grained reactivity based on
Angular Signals
</h3>
</div>
</a>
<a class="card" routerLink="/overview">
<div class="icon-wrapper">
<docs-icon>layers</docs-icon>
</div>
<div class="card-content">
<h3>
<strong>Fully featured</strong> platform where everything works together with Angular's
first-party modules for forms, routing, and more
</h3>
</div>
</a>
</div>
</section>
<section class="performance-section" id="performance">
<div class="wrapper">
<div class="pattern"></div>
<div class="content">
<h2>Where performance matters</h2>
<h3>
Trusted by millions to build fast, reliable applications that scale with the size of your
team
</h3>
<button
class="docs-primary-btn"
[attr.text]="'Get started'"
routerLink="/guide/performance"
>
<!-- TODO: LINK -->
Get started
</button>
</div>
</div>
</section>
<section class="explore-section" id="learn-more">
<div class="title">
<h2>What to learn more about Angular?</h2>
<div class="pattern"></div>
</div>
<div class="cards-grid">
<!-- Card 1: Tutorials -->
<a routerLink="/tutorials/learn-angular" class="explore-card">
<div class="card-content">
<p>
<span class="emphasis">New to Angular?</span>
Try our completely in browser tutorial lessons designed to give you hands on experience
with Angular.
</p>
<span class="fake-button">Start coding <docs-icon>arrow_forward</docs-icon></span>
<div class="editor-background"></div>
</div>
</a>
<!-- Card 2: AI -->
<a routerLink="/ai" class="explore-card">
<div class="card-content">
<p>
<span class="emphasis">Are you an LLM or curious about AI in Angular?</span>
Head over to our AI section to find prompts, strategies, best practices, examples, and
more.
</p>
<span class="fake-button">Angular + AI <docs-icon>arrow_forward</docs-icon></span>
<div class="angular-mcp-container">
<div class="mcp-content">
<div class="mcp-label">Angular MCP</div>
<div class="mcp-pills">
<span class="mcp-pill">get_best_practices</span>
<span class="mcp-pill">ai_tutor</span>
<span class="mcp-pill">find_examples</span>
</div>
</div>
</div>
</div>
</a>
<!-- Card 3: Overview -->
<a routerLink="/overview" class="explore-card">
<div class="card-content">
<p>
<span class="emphasis">Why Angular?</span>
Learn about Angular, its benefits, and if it's right for you.
</p>
<div class="logo-background"></div>
</div>
</a>
<!-- Card 4: Essentials -->
<a routerLink="/essentials" class="explore-card">
<div class="card-content">
<p>
<span class="emphasis">More of a reader?</span>
Our essentials guides are designed to help you understand Angular fundamentals in the
time it would take to finish a cup of coffee (or a tea).
</p>
<span class="fake-button">Angular concepts <docs-icon>arrow_forward</docs-icon></span>
</div>
</a>
</div>
</section>
<section class="learn-more-bottom-section" id="bottom">
<div class="pattern"></div>
<div class="content">
<h2>Want to learn more about Angular ?</h2>
<button
class="docs-primary-btn"
[attr.text]="'Learn more'"
aria-label="Learn more"
routerLink="/overview"
>
Learn more
</button>
</div>
</section>
</main>
File diff suppressed because it is too large Load Diff
+30 -1
View File
@@ -6,12 +6,34 @@
* found in the LICENSE file at https://angular.dev/license
*/
import {Tab, TabContent, TabList, TabPanel, Tabs} from '@angular/aria/tabs';
import {A11yModule} from '@angular/cdk/a11y';
import {ChangeDetectionStrategy, Component, inject} from '@angular/core';
import {IS_SEARCH_DIALOG_OPEN, IconComponent, TextField} from '@angular/docs';
import {ActivatedRoute, RouterLink} from '@angular/router';
import {CodeHighligher} from './code-highlighting/code-highlighter';
import {ControlFlowExample} from './components/control-flow/control-flow-example';
import {DeferrableViewsExample} from './components/deferrable-views-example/deferrable-views-example';
import {HydrationExample} from './components/hydration-example/hydration-example';
import {SignalsDemo} from './components/signals-demo/signals-demo';
@Component({
selector: 'adev-home',
imports: [RouterLink],
imports: [
RouterLink,
TextField,
TabList,
Tab,
Tabs,
TabPanel,
TabContent,
IconComponent,
A11yModule,
SignalsDemo,
ControlFlowExample,
DeferrableViewsExample,
HydrationExample,
],
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
@@ -19,4 +41,11 @@ import {ActivatedRoute, RouterLink} from '@angular/router';
export default class Home {
private readonly activatedRoute = inject(ActivatedRoute);
protected readonly isUwu = 'uwu' in this.activatedRoute.snapshot.queryParams;
private readonly codeHighlighter = inject(CodeHighligher);
protected readonly displaySearchDialog = inject(IS_SEARCH_DIALOG_OPEN);
openSearch() {
this.displaySearchDialog.set(true);
}
}
@@ -0,0 +1 @@
<svg xmlns='http://www.w3.org/2000/svg' width='489.63' height='453.44' viewBox='4.516 2.755 489.63 453.44'><filter id='filter3_n_5746_17015' x='339.133' y='4855' width='490.001' height='453.444' filterUnits='userSpaceOnUse' color-interpolation-filters='sRGB'><feFlood flood-opacity='0' result='BackgroundImageFix'/><feBlend mode='normal' in='SourceGraphic' in2='BackgroundImageFix' result='shape'/><feTurbulence type='fractalNoise' baseFrequency='4.6613855361938477 4.6613855361938477' stitchTiles='stitch' numOctaves='3' result='noise' seed='9369'/><feColorMatrix in='noise' type='luminanceToAlpha' result='alphaNoise'/><feComponentTransfer in='alphaNoise' result='coloredNoise1'><feFuncA type='discrete' tableValues='1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 '/></feComponentTransfer><feComposite operator='in' in2='shape' in='coloredNoise1' result='noise1Clipped'/><feFlood flood-color='rgba(0, 0, 0, 0.25)' result='color1Flood'/><feComposite operator='in' in2='noise1Clipped' in='color1Flood' result='color1'/><feMerge result='effect1_noise_5746_17015'><feMergeNode in='shape'/><feMergeNode in='color1'/></feMerge></filter><g xmlns='http://www.w3.org/2000/svg' filter='url(#filter3_n_5746_17015)' opacity='.5' transform='translate(-334.617 -4852.245)'><path fill='#121114' d='M828.763 5154.15 589.175 4855l172.14 72.78z'/><mask id='a' width='240' height='300' x='589' y='4855' maskUnits='userSpaceOnUse' style='mask-type: luminance'><path fill='#fff' d='M828.763 5154.15 589.175 4855l172.14 72.78z'/></mask><g mask='url(#a)'><mask id='b' width='240' height='300' x='589' y='4855' maskUnits='userSpaceOnUse' style='mask-type: luminance'><path fill='#fff' d='M828.763 5154.15 589.175 4855l172.14 72.78z'/></mask><g mask='url(#b)'><path fill='#9d43f7' d='M828.763 5154.15 589.175 4855l172.14 72.78z'/></g></g><path fill='#33264e' d='m828.762 5154.15-20.165 27.05-239.588-299.15 20.166-27.05z'/><path fill='#381e37' d='m664.565 5096.27-20.154 27.05-114.121 7.97 20.165-27.04z'/><path fill='#121114' d='m550.455 5104.25 16.865-139.15 97.245 131.17z'/><mask id='c' width='115' height='140' x='550' y='4965' maskUnits='userSpaceOnUse' style='mask-type: luminance'><path fill='#fff' d='m550.455 5104.25 16.865-139.15 97.245 131.17z'/></mask><g mask='url(#c)'><mask id='d' width='115' height='140' x='550' y='4965' maskUnits='userSpaceOnUse' style='mask-type: luminance'><path fill='#fff' d='m550.455 5104.25 16.865-139.15 97.245 131.17z'/></mask><g mask='url(#d)'><path fill='#f85de3' d='m550.455 5104.25 16.865-139.15 97.245 131.17z'/></g></g><path fill='#33264e' d='m550.455 5104.25-20.165 27.04 16.864-139.14 20.166-27.05z'/><path fill='#1a1a1a' d='m752.201 5212.53-20.166 27.05-90.643 68.86 20.165-27.05z'/><path fill='#121114' d='m541.015 5174.27 173.48-12.13 37.706 50.39-90.643 68.86-127.145-53.63z'/><mask id='e' width='219' height='120' x='534' y='5162' maskUnits='userSpaceOnUse' style='mask-type: luminance'><path fill='#fff' d='m541.015 5174.27 173.48-12.13 37.706 50.39-90.643 68.86-127.145-53.63z'/></mask><g mask='url(#e)'><path fill='#f759ce' d='m661.558 5281.39-127.145-53.63 6.602-53.49 173.48-12.13 37.706 50.39z'/></g><path fill='#171218' d='m661.557 5281.39-20.165 27.05-127.145-53.64 20.166-27.04z'/><path fill='#1a131a' d='m534.413 5227.76-20.166 27.04 6.602-53.48 20.165-27.05z'/><path fill='#121114' d='m426.746 5182.26-67.447-226.38 122.785-93.39z'/><mask id='f' width='124' height='321' x='359' y='4862' maskUnits='userSpaceOnUse' style='mask-type: luminance'><path fill='#fff' d='m426.746 5182.26-67.447-226.38 122.785-93.39z'/></mask><g mask='url(#f)'><mask id='g' width='124' height='321' x='359' y='4862' maskUnits='userSpaceOnUse' style='mask-type: luminance'><path fill='#fff' d='m482.084 4862.49-55.338 319.77-67.447-226.38z'/></mask><g mask='url(#g)'><path fill='#852c49' d='m482.084 4862.49-55.338 319.77-67.447-226.38z'/></g></g><path fill='#311822' d='m426.747 5182.26-20.166 27.05-67.448-226.38 20.166-27.05z'/></g></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

@@ -0,0 +1 @@
<svg xmlns='http://www.w3.org/2000/svg' viewBox='1.5743 2.3442 489.63 453.4399' width='489.63px' height='453.44px'><filter id='filter3_n_5746_17015' x='339.133' y='4855' width='490.001' height='453.444' filterUnits='userSpaceOnUse' color-interpolation-filters='sRGB'><feFlood flood-opacity='0' result='BackgroundImageFix'/><feBlend mode='normal' in='SourceGraphic' in2='BackgroundImageFix' result='shape'/><feTurbulence type='fractalNoise' baseFrequency='4.6613855361938477 4.6613855361938477' stitchTiles='stitch' numOctaves='3' result='noise' seed='9369'/><feColorMatrix in='noise' type='luminanceToAlpha' result='alphaNoise'/><feComponentTransfer in='alphaNoise' result='coloredNoise1'><feFuncA type='discrete' tableValues='1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 '/></feComponentTransfer><feComposite operator='in' in2='shape' in='coloredNoise1' result='noise1Clipped'/><feFlood flood-color='rgba(0, 0, 0, 0.25)' result='color1Flood'/><feComposite operator='in' in2='noise1Clipped' in='color1Flood' result='color1'/><feMerge result='effect1_noise_5746_17015'><feMergeNode in='shape'/><feMergeNode in='color1'/></feMerge></filter><g xmlns='http://www.w3.org/2000/svg' filter='url(#filter3_n_5746_17015)' opacity='.5' transform='matrix(1, 0, 0, 1, -337.55868530273443, -4852.65576171875)'><path fill='#121114' d='M828.763 5154.15 589.175 4855l172.14 72.78 67.448 226.37Z'/><mask id='a' width='240' height='300' x='589' y='4855' maskUnits='userSpaceOnUse' style='mask-type: luminance'><path fill='#fff' d='M828.763 5154.15 589.175 4855l172.14 72.78 67.448 226.37Z'/></mask><g mask='url(#a)'><mask id='b' width='240' height='300' x='589' y='4855' maskUnits='userSpaceOnUse' style='mask-type: luminance'><path fill='#fff' d='M828.763 5154.15 589.175 4855l172.14 72.78 67.448 226.37Z'/></mask><g mask='url(#b)'><path fill='#8514F5' d='M828.763 5154.15 589.175 4855l172.14 72.78 67.448 226.37Z'/></g></g><path fill='#33264E' d='m828.762 5154.15-20.165 27.05-239.588-299.15 20.166-27.05 239.587 299.15Z' opacity='.2'/><path fill='#381E37' d='m664.565 5096.27-20.154 27.05-114.121 7.97 20.165-27.04 114.11-7.98Z'/><path fill='#121114' d='m550.455 5104.25 16.865-139.15 97.245 131.17-114.11 7.98Z'/><mask id='c' width='115' height='140' x='550' y='4965' maskUnits='userSpaceOnUse' style='mask-type: luminance'><path fill='#fff' d='m550.455 5104.25 16.865-139.15 97.245 131.17-114.11 7.98Z'/></mask><g mask='url(#c)'><mask id='d' width='115' height='140' x='550' y='4965' maskUnits='userSpaceOnUse' style='mask-type: luminance'><path fill='#fff' d='m550.455 5104.25 16.865-139.15 97.245 131.17-114.11 7.98Z'/></mask><g mask='url(#d)'><path fill='#F637E3' d='m550.455 5104.25 16.865-139.15 97.245 131.17-114.11 7.98Z'/></g></g><path fill='#33264E' d='m550.455 5104.25-20.165 27.04 16.864-139.14 20.166-27.05-16.865 139.15Z' opacity='.2'/><path fill='#1A1A1A' d='m752.201 5212.53-20.166 27.05-90.643 68.86 20.165-27.05 90.644-68.86Z' opacity='.2'/><path fill='#121114' d='m541.015 5174.27 173.48-12.13 37.706 50.39-90.643 68.86-127.145-53.63 6.602-53.49Z'/><mask id='e' width='219' height='120' x='534' y='5162' maskUnits='userSpaceOnUse' style='mask-type: luminance'><path fill='#fff' d='m541.015 5174.27 173.48-12.13 37.706 50.39-90.643 68.86-127.145-53.63 6.602-53.49Z'/></mask><g mask='url(#e)'><path fill='#E90464' d='m661.558 5281.39-127.145-53.63 6.602-53.49 173.48-12.13 37.706 50.39-90.643 68.86Z'/></g><path fill='#171218' d='m661.557 5281.39-20.165 27.05-127.145-53.64 20.166-27.04 127.144 53.63Z' opacity='.2'/><path fill='#1A131A' d='m534.413 5227.76-20.166 27.04 6.602-53.48 20.165-27.05-6.601 53.49Z' opacity='.2'/><path fill='#121114' d='m426.746 5182.26-67.447-226.38 122.785-93.39-55.338 319.77Z'/><mask id='f' width='124' height='321' x='359' y='4862' maskUnits='userSpaceOnUse' style='mask-type: luminance'><path fill='#fff' d='m426.746 5182.26-67.447-226.38 122.785-93.39-55.338 319.77Z'/></mask><g mask='url(#f)'><mask id='g' width='124' height='321' x='359' y='4862' maskUnits='userSpaceOnUse' style='mask-type: luminance'><path fill='#fff' d='m482.084 4862.49-55.338 319.77-67.447-226.38 122.785-93.39Z'/></mask><g mask='url(#g)'><path fill='#F637E3' d='m482.084 4862.49-55.338 319.77-67.447-226.38 122.785-93.39Z'/></g></g><path fill='#311822' d='m426.747 5182.26-20.166 27.05-67.448-226.38 20.166-27.05 67.448 226.38Z' opacity='.2'/></g></svg>

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

+54
View File
@@ -579,6 +579,27 @@ importers:
'@marijn/find-cluster-break':
specifier: 1.0.2
version: 1.0.2
'@shikijs/core':
specifier: ^3.21.0
version: 3.21.0
'@shikijs/engine-javascript':
specifier: ^3.21.0
version: 3.21.0
'@shikijs/engine-oniguruma':
specifier: ^3.21.0
version: 3.21.0
'@shikijs/langs':
specifier: ^3.21.0
version: 3.21.0
'@shikijs/themes':
specifier: ^3.21.0
version: 3.21.0
'@shikijs/types':
specifier: ^3.21.0
version: 3.21.0
'@shikijs/vscode-textmate':
specifier: ^10.0.2
version: 10.0.2
'@stackblitz/sdk':
specifier: 1.11.0
version: 1.11.0
@@ -612,6 +633,18 @@ importers:
angular-split:
specifier: 20.0.0
version: 20.0.0(@angular/common@packages+common)(@angular/core@packages+core)(rxjs@7.8.2)
ccount:
specifier: ^2.0.1
version: 2.0.1
character-entities-html4:
specifier: ^2.1.0
version: 2.1.0
character-entities-legacy:
specifier: ^3.0.0
version: 3.0.0
comma-separated-tokens:
specifier: ^2.0.3
version: 2.0.3
crelt:
specifier: 1.0.6
version: 1.0.6
@@ -624,6 +657,15 @@ importers:
fflate:
specifier: 0.8.2
version: 0.8.2
hast-util-to-html:
specifier: ^9.0.5
version: 9.0.5
hast-util-whitespace:
specifier: ^3.0.0
version: 3.0.0
html-void-elements:
specifier: ^3.0.0
version: 3.0.0
jasmine-core:
specifier: 6.0.0
version: 6.0.0
@@ -666,12 +708,21 @@ importers:
prettier:
specifier: 3.8.1
version: 3.8.1
property-information:
specifier: ^7.1.0
version: 7.1.0
rxjs:
specifier: 7.8.2
version: 7.8.2
shiki:
specifier: 3.22.0
version: 3.22.0
space-separated-tokens:
specifier: ^2.0.2
version: 2.0.2
stringify-entities:
specifier: ^4.0.4
version: 4.0.4
style-mod:
specifier: 4.1.3
version: 4.1.3
@@ -687,6 +738,9 @@ importers:
w3c-keyname:
specifier: 2.2.8
version: 2.2.8
zwitch:
specifier: ^2.0.4
version: 2.0.4
devDependencies:
autoprefixer:
specifier: 10.4.24