diff --git a/adev/src/app/features/home/components/home-animation/animation-definition.ts b/adev/src/app/features/home/components/home-animation/animation-definition.ts deleted file mode 100644 index ee95fef7328..00000000000 --- a/adev/src/app/features/home/components/home-animation/animation-definition.ts +++ /dev/null @@ -1,437 +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 {AnimationDefinition, Styles} from '../../animation'; -import {AnimationRule} from '../../animation/types'; - -/** - * CONSTANTS - */ - -// Represents percentage of the total. -// Avoid using large waves (the total shouldn't be too big as well). -// Check meteorShower function for more details. -const FIRST_WAVE_METEORS = 0.05; -const SECOND_WAVE_METEORS = 0.15; -const THIRD_WAVE_METEORS = 0.25; - -// Use to increase or decrease the animation duration (i.e. a fine tuning parameter). -// Employed by `timeframe()` and `at()`. -const TIMING_MULTIPLIER = 1.55; - -export const ANIM_TIMESTEP = 10; // In milliseconds - -/** - * SELECTORS - */ - -const BANNERS_LAYER_ID = 'banners'; -const ADEV_BANNER = `${BANNERS_LAYER_ID} >> .adev-banner`; -const LEARN_ANGULAR_BTN = `${BANNERS_LAYER_ID} >> .learn-angular`; - -const LOGO_LAYER_ID = 'logo'; -const LOGO = `${LOGO_LAYER_ID} >> .logo`; -const SHIELD = `${LOGO_LAYER_ID} >> .shield`; -const SHIELD_MIDDLE = `${LOGO_LAYER_ID} >> .shield-middle`; -const SHIELD_BOTTOM_A_ARC = `${LOGO_LAYER_ID} >> .shield-bottom-a-arc`; -const SHIELD_BOTTOM_EXTENSION = `${LOGO_LAYER_ID} >> .shield-bottom-extension`; -const CAPITAL_A_LETTER = `${LOGO_LAYER_ID} >> .capt-a-letter`; -const N_LETTER = `${LOGO_LAYER_ID} >> .n-letter`; -const G_LETTER = `${LOGO_LAYER_ID} >> .g-letter`; -const U_LETTER = `${LOGO_LAYER_ID} >> .u-letter`; -const L_LETTER = `${LOGO_LAYER_ID} >> .l-letter`; -const A_LETTER = `${LOGO_LAYER_ID} >> .a-letter`; -const R_LETTER = `${LOGO_LAYER_ID} >> .r-letter`; - -const UWU_LAYER_ID = 'uwu'; - -const WORKS_AT_ANY_SCALE_LAYER_ID = 'works-at-any-scale'; - -const METEOR_FIELD_LAYER_ID = 'meteor-field'; -const METEOR_FIELD = `${METEOR_FIELD_LAYER_ID} >> .field`; -const METEORS = `${METEOR_FIELD_LAYER_ID} >> .meteor`; -const METEOR_ID = (id: number) => `${METEOR_FIELD_LAYER_ID} >> .mt-${id}`; - -const LOVED_BY_MILLIONS_LAYER_ID = 'loved-by-millions'; - -const BUILD_FOR_EVERYONE_LAYER_ID = 'build-for-everyone'; -const BUILD_FOR_EVERYONE_TITLE = `${BUILD_FOR_EVERYONE_LAYER_ID} >> .title`; - -/** - * ANIMATION/HELPER FUNCTIONS - */ - -// Timing functions (they employ `TIMING_MULTIPLIER`) – Use when setting the time of a rule. -const at = (at: number): number => at * TIMING_MULTIPLIER; -const timeframe = (from: number, to: number): [number, number] => [ - from * TIMING_MULTIPLIER, - to * TIMING_MULTIPLIER, -]; - -/** Duration: 1 second */ -function hideLetter(selector: string, startTime: number): AnimationRule { - return { - selector, - timeframe: timeframe(startTime, startTime + 1), - from: { - opacity: '1', - }, - to: { - opacity: '0', - }, - }; -} - -/** Duration: 1 to 2 seconds */ -function showMeteor(selector: string, startTime: number): AnimationRule { - const randomizedStartTime = startTime + Math.random(); // Up to +1 second (excl.) - return { - selector, - timeframe: timeframe(randomizedStartTime, randomizedStartTime + 1), - from: { - opacity: '0', - transform: 'translate(200%, 200%) scale(0.3)', - }, - to: { - opacity: '1', - transform: 'translate(0, 0) scale(1)', - }, - }; -} - -/** Duration: 1 to 2 seconds */ -function meteorShower( - startTime: number, - size: number, - total: number, - inUse: Set, -): AnimationDefinition { - const animations: AnimationRule[] = []; - - while (animations.length < size) { - // We pick a random meteor ID. - // If `inUse` is nearly full relative to `total`, - // we might run into a excessive amount of iterations - // until we fill `animation`. This is why we should keep - // the wave sizes (and their total) relatively small. - const id = Math.round(Math.random() * (total - 1) + 1); - - if (!inUse.has(id)) { - animations.push(showMeteor(METEOR_ID(id), startTime)); - inUse.add(id); - } - } - - return animations; -} - -/** - * DEFINITION - */ - -/** Generate the animation definition for the home page animation. */ -export function generateHomeAnimationDefinition( - isUwu: boolean, - meteorCount: number, -): AnimationDefinition { - // Banners and buttons layer - // ************************* - const bannersLayerAnim: AnimationDefinition = [ - { - selector: ADEV_BANNER, - timeframe: timeframe(2, 3), - from: { - transform: 'translateY(0)', - }, - to: { - transform: 'translateY(-200px)', - }, - }, - { - selector: LEARN_ANGULAR_BTN, - timeframe: timeframe(2.5, 3.5), - from: { - opacity: '1', - }, - to: { - opacity: '0', - }, - }, - { - selector: LEARN_ANGULAR_BTN, - at: at(4), - styles: { - visibility: 'hidden', - }, - }, - ]; - - // Logo layer animation - // ******************** - const logoLayerAnim: AnimationDefinition = [ - { - selector: LOGO, - timeframe: timeframe(0, 5), - from: { - transform: 'translateX(0)', - }, - to: { - transform: 'translateX(467px)', // Value based on the 1280x400 SVG view box - }, - }, - hideLetter(R_LETTER, 1), - hideLetter(A_LETTER, 1.5), - hideLetter(L_LETTER, 2), - hideLetter(U_LETTER, 2.5), - hideLetter(G_LETTER, 3), - hideLetter(N_LETTER, 3.5), - // Make sure that the last letter disappears at the end of layer transition, - // i.e. 4 + 1 = 5th second end time - hideLetter(CAPITAL_A_LETTER, 4), - { - selector: SHIELD_MIDDLE, - timeframe: timeframe(5.5, 5.6), - from: { - transform: 'scale(1)', - }, - to: { - transform: 'scale(0)', - }, - }, - { - selector: SHIELD_BOTTOM_A_ARC, - timeframe: timeframe(5.5, 5.6), - from: { - transform: 'scaleY(1)', - }, - to: { - transform: 'scaleY(0)', - }, - }, - { - selector: SHIELD_BOTTOM_EXTENSION, - timeframe: timeframe(5.5, 5.6), - from: { - transform: 'scale(0)', - }, - to: { - transform: 'scale(1)', - }, - }, - { - selector: SHIELD, - timeframe: timeframe(5.5, 10), - from: { - transform: 'scale(1) rotate(0deg)', - }, - to: { - transform: 'scale(50) rotate(-360deg)', - }, - }, - ]; - - // "UwU logo" layer animation - // ************************** - const uwuLayerAnimation: AnimationDefinition = [ - { - selector: UWU_LAYER_ID, - timeframe: timeframe(0, 5.5), - from: { - transform: 'scale(1)', - }, - to: { - transform: 'scale(0)', - }, - }, - { - selector: UWU_LAYER_ID, - timeframe: timeframe(4, 5.5), - from: { - opacity: '1', - }, - to: { - opacity: '0', - }, - }, - ]; - - // "Works at any scale" layer animation - // ************************************ - const waasLayerAnim: AnimationDefinition = [ - { - selector: WORKS_AT_ANY_SCALE_LAYER_ID, - timeframe: timeframe(5.7, 8), // Make sure it appears after SHIELD_MIDDLE disappears. - from: { - transform: 'scale(0.1)', - opacity: '0', - }, - to: { - transform: 'scale(1)', - opacity: '1', - }, - }, - { - selector: WORKS_AT_ANY_SCALE_LAYER_ID, - timeframe: timeframe(11, 12.5), - from: { - transform: 'scale(1)', - opacity: '1', - }, - to: { - transform: 'scale(1.5)', - opacity: '0', - }, - }, - ]; - - // Meteor field layer animation - // **************************** - const firstWaveSize = meteorCount * FIRST_WAVE_METEORS; - const secondWaveSize = meteorCount * SECOND_WAVE_METEORS; - const thirdWaveSize = meteorCount * THIRD_WAVE_METEORS; - - const meteorsInUse = new Set(); - const firstWave = meteorShower(8, firstWaveSize, meteorCount, meteorsInUse); - const secondWave = meteorShower(10, secondWaveSize, meteorCount, meteorsInUse); - const thirdWave = meteorShower(12, thirdWaveSize, meteorCount, meteorsInUse); - const lastWaveStart = 16; - - // For the last wave, just use the remaining meteors (don't use `meteorShower`). - const lastWave: AnimationRule[] = []; - for (let id = 1; id <= meteorCount; id++) { - if (!meteorsInUse.has(id)) { - lastWave.push(showMeteor(METEOR_ID(id), lastWaveStart)); - } - } - - const meteorFieldLayerAnim: AnimationDefinition = [ - { - selector: METEOR_FIELD, - at: at(7), - styles: { - display: 'flex', - }, - }, - { - selector: METEOR_FIELD, - timeframe: timeframe(8, 18), - from: { - transform: 'scale(1.42)', - }, - to: { - transform: 'scale(1)', - }, - }, - ...firstWave, - ...secondWave, - ...thirdWave, - ...lastWave, - { - selector: METEORS, - timeframe: timeframe(19.5, 21), - from: { - transform: 'translate(0, 0) scale(1)', - }, - to: { - transform: 'translate(-200%, -200%) scale(0.3)', - }, - }, - { - selector: METEOR_FIELD, - timeframe: timeframe(19.5, 21), - from: { - opacity: '1', - }, - to: { - opacity: '0', - }, - }, - { - selector: METEOR_FIELD, - at: at(22), - styles: { - display: 'none', - }, - }, - ]; - - // "Loved by millions" layer animation - // *********************************** - const lovedByMillionsAnim: AnimationDefinition = [ - { - selector: LOVED_BY_MILLIONS_LAYER_ID, - timeframe: timeframe(14, 15.5), - from: { - transform: 'scale(0.75)', - opacity: '0', - }, - to: { - transform: 'scale(1)', - opacity: '1', - }, - }, - { - selector: LOVED_BY_MILLIONS_LAYER_ID, - timeframe: timeframe(19, 20.5), - from: { - transform: 'scale(1)', - opacity: '1', - }, - to: { - transform: 'scale(1.5)', - opacity: '0', - }, - }, - ]; - - // "Build for everyone" layer - // ************************** - const buildForEveryoneAnim: AnimationDefinition = [ - { - selector: BUILD_FOR_EVERYONE_LAYER_ID, - timeframe: timeframe(22, 25), - from: { - transform: 'scale(0.75)', - opacity: '0', - }, - to: { - transform: 'scale(1)', - opacity: '1', - }, - }, - { - selector: BUILD_FOR_EVERYONE_TITLE, - timeframe: timeframe(23, 25), - from: { - 'background-position-x': '100%', - }, - to: { - 'background-position-x': '0', - }, - }, - { - selector: BUILD_FOR_EVERYONE_LAYER_ID, - timeframe: timeframe(29, 31.5), - from: { - opacity: '1', - }, - to: { - opacity: '0', - }, - }, - ]; - - return [ - ...bannersLayerAnim, - ...(!isUwu ? logoLayerAnim : uwuLayerAnimation), - ...waasLayerAnim, - ...meteorFieldLayerAnim, - ...lovedByMillionsAnim, - ...buildForEveryoneAnim, - ]; -} diff --git a/adev/src/app/features/home/components/home-animation/home-animation.component.html b/adev/src/app/features/home/components/home-animation/home-animation.component.html deleted file mode 100644 index ba4628ba4e3..00000000000 --- a/adev/src/app/features/home/components/home-animation/home-animation.component.html +++ /dev/null @@ -1,122 +0,0 @@ -
- @if (!isUwu()) { - -
- - - - - - - - - - - - - - - -
- } @else { - -
- Angular logo -
- } - - -
-

Works at any scale

-

- Angular lets you start small on a well-lit path and supports you as your team and apps grow. -

-
- - - @if (meteorFieldData(); as meteorFieldData) { -
-
- @for (type of meteors(); track $index) { -
- } -
-
- } - - -
-

Loved by millions

-

- Join the millions of developers all over the world building with Angular in a thriving and - friendly community. -

-
- - -
-

Build for everyone

-

- Rely on Angular's built-in hydration, internationalization, security, and accessibility - support to build for everyone around the world. -

-
-
diff --git a/adev/src/app/features/home/components/home-animation/home-animation.component.scss b/adev/src/app/features/home/components/home-animation/home-animation.component.scss deleted file mode 100644 index 11b08711071..00000000000 --- a/adev/src/app/features/home/components/home-animation/home-animation.component.scss +++ /dev/null @@ -1,403 +0,0 @@ -@use 'sass:math'; -@use '@angular/docs/styles/media-queries' as mq; - -/* - * Global transition. - * - * Scrolling via mouse scroll wheel will result in a non-continuous scrollY, - * unlike a touchpad, which in turn results in "gaps" in the animation calculations. - * Therefore, in order to keep things smooth, we are systematically adding CSS - * transitions to the CSS properties that else look jagged when scrolling. - */ -$transition: 200ms linear; - -:host { - display: block; - position: relative; - - .animation { - position: fixed; - top: 0; - bottom: 0; - right: 0; - left: 0; - - .layer { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; - - h2 { - font-size: 4vw; - font-weight: 600; - white-space: nowrap; - margin-top: 0; - margin-bottom: 0.5em; - z-index: 1; - - @include mq.for-tablet-landscape-down { - & { - font-size: 2rem; - } - } - } - - p { - font-weight: 400; - color: var(--quaternary-contrast); - font-size: clamp(1rem, 1vw, 2rem); - line-height: 1.5; - width: clamp(345px, 50%, 600px); - margin: 0 auto; - } - - h2, - p { - background-color: var(--page-background); - box-shadow: 0 0 20px 20px var(--page-background); - - @include mq.for-desktop-down { - box-shadow: 0 0 10px 10px var(--page-background); - } - } - } - - .banners-layer { - z-index: 10; - - .adev-banner-container { - display: flex; - flex-direction: column; - gap: 0.5rem; - position: absolute; - transition: - background 0.3s ease, - border 0.3s ease, - transform $transition; - top: var(--layout-padding); - left: calc(var(--layout-padding) + var(--primary-nav-width)); - - @include mq.for-tablet-landscape-down { - top: 6rem; - left: var(--layout-padding); - /* Assuming the container width is identical to the viewport width (mobile device). */ - max-width: calc(100% - var(--layout-padding) * 2); - } - - @include mq.for-phone-only { - & { - top: 5rem; - } - } - } - - .adev-banner { - display: flex; - align-items: center; - flex-wrap: wrap; - gap: 0.5rem; - border: 1px solid var(--senary-contrast); - background: var(--page-background); - border-radius: 0.25rem; - padding: 10px; - max-width: 100%; - width: fit-content; - box-sizing: border-box; - - h1, - p { - display: inline; - font-size: 0.875rem; - margin: 0; - background-image: var(--red-to-pink-to-purple-horizontal-gradient); - background-clip: text; - color: transparent; - width: fit-content; - font-weight: 500; - box-shadow: none; - position: relative; - - &.adev-banner-cta { - color: var(--tertiary-contrast); - - &::after { - content: ''; - position: absolute; - width: 100%; - transform: scaleX(0); - height: 1px; - bottom: -2px; - left: 0; - background: var(--red-to-pink-to-purple-horizontal-gradient); - transform-origin: bottom right; - transition: transform 0.3s ease; - } - } - } - - &:hover { - .adev-banner-cta { - &::after { - transform: scaleX(1); - transform-origin: bottom left; - } - } - } - } - - .learn-angular { - position: absolute; - left: 50%; - bottom: 5%; - transform: translateX(-50%); - transition: opacity $transition; - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; - gap: 50px; - - button { - font-size: 1rem; - padding: 1rem 1.75rem; - - &::after { - font-size: 1rem; - } - } - - .adev-arrow { - transform: rotate(45deg); - border: solid var(--primary-contrast); - border-width: 0 2px 2px 0; - display: inline-block; - padding: 7px; - } - } - } - - .logo-layer { - user-select: none; - pointer-events: none; - - .svg { - position: absolute; - width: 100%; - height: 100%; - fill-rule: evenodd; - clip-rule: evenodd; - stroke-linejoin: round; - stroke-miterlimit: 2; - } - - .wrapper { - transform: scale(0.55); - transform-origin: 50% 50%; - - @include mq.for-tablet-down { - & { - transform: scale(0.8); - } - } - - @include mq.for-extra-large-desktop-up { - & { - transform: scale(0.5); - } - } - - .logo { - transition: transform $transition; - - .shield { - /* `transform-origin` values are calculated based on the 1280x400 SVG view box */ - - &, - .shield-middle { - transform-origin: 177px 200px; - transition: transform $transition; - } - - .shield-bottom-a-arc { - transform-origin: 0 302px; - transition: transform $transition; - } - - .shield-bottom-extension { - transform-origin: 177px 340px; - transition: transform $transition; - transform: scale(0); - } - } - - .letter { - transition: opacity $transition; - } - } - } - } - - .uwu-layer { - display: flex; - align-items: center; - justify-content: center; - transition: - transform $transition, - opacity $transition; - - img { - user-select: none; - width: max(calc(700 * min(100vw, 2560px) / 1470), 350px); - height: auto; - } - } - - .works-at-any-scale-layer, - .loved-by-millions-layer, - .build-for-everyone-layer { - user-select: none; - pointer-events: none; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - text-align: center; - opacity: 0; - z-index: 1; - transition: - transform $transition, - opacity $transition; - } - - .works-at-any-scale-layer { - transform: scale(0); - } - - .loved-by-millions-layer, - .build-for-everyone-layer { - transform: scale(0.75); - } - - .build-for-everyone-layer { - .title { - color: transparent; - display: inline-block; - background: linear-gradient(110deg, #f31a5b 0, #8737e9 50%, #3a373f 0%); - background-size: 205% 100%; - background-clip: text; - background-size: 205% 100%; - background-position-x: 100%; - transition: background-position-x $transition; - } - } - - .meteor-field-layer { - overflow: hidden; - - .field { - --math-pi: #{math.$pi}rad; - - position: absolute; - flex-wrap: wrap; - align-content: flex-start; - gap: var(--meteor-gap); - display: none; - transform: scale(1.42); - top: 0; - left: 0; - transform-origin: 50% 50%; - transition: - opacity $transition, - transform $transition; - - .meteor { - position: relative; - width: var(--meteor-width); - height: var(--meteor-height); - opacity: 0; - transform: translate(200%, 200%) scale(0.3); - transition: - opacity $transition, - transform $transition; - - &::after { - content: ''; - position: absolute; - width: 4px; - height: var(--meteor-tail-length); - border-radius: 2px; - transform-origin: top center; - transform: rotate(var(--meteor-tilt-angle)); - top: 0; - left: 0; - } - - @mixin meteor-gradient($startColor, $endColor) { - background: linear-gradient( - calc(var(--math-pi) + var(--meteor-tilt-angle)), - $startColor 0, - $endColor 66%, - transparent 100% - ); - } - - &.type-1::after { - @include meteor-gradient(rgb(228, 49, 85), rgb(219, 64, 219)); - } - - &.type-2::after { - @include meteor-gradient(rgb(152, 56, 226), rgb(217, 18, 167)); - } - - &.type-3::after { - @include meteor-gradient(rgb(214, 53, 150), rgb(229, 91, 229)); - } - } - } - } - - &.reduced-motion { - position: relative; - - .banners-layer { - height: 100vh; - - @include mq.for-phone-only { - .adev-banner { - top: 6rem; - } - } - - @include mq.for-tablet-landscape-up { - .adev-banner { - left: var(--layout-padding); - } - } - } - - .logo-layer { - position: relative; - height: 100vh; - - @include mq.for-tablet-landscape-down { - margin-top: -75px; - } - } - - .works-at-any-scale-layer, - .loved-by-millions-layer, - .build-for-everyone-layer { - position: relative; - height: 120vh; - opacity: 1; - transform: scale(1); - } - - .build-for-everyone-layer > .title { - background-position-x: 0; - } - } - } -} diff --git a/adev/src/app/features/home/components/home-animation/home-animation.component.ts b/adev/src/app/features/home/components/home-animation/home-animation.component.ts deleted file mode 100644 index ad0d70aa03d..00000000000 --- a/adev/src/app/features/home/components/home-animation/home-animation.component.ts +++ /dev/null @@ -1,170 +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 { - afterNextRender, - Component, - DestroyRef, - ElementRef, - inject, - Injector, - input, - output, - signal, - viewChildren, -} from '@angular/core'; -import {isIos, shouldReduceMotion, WINDOW} from '@angular/docs'; -import {RouterLink} from '@angular/router'; - -import {AnimationCreatorService, AnimationLayerDirective} from '../../animation'; -import {AnimationScrollHandler} from '../../animation/plugins/animation-scroll-handler'; -import {ANIM_TIMESTEP, generateHomeAnimationDefinition} from './animation-definition'; - -export const METEOR_HW_RATIO = 1.42; // Height to width ratio -export const METEOR_GAP_RATIO = 1.33; // Use 0.7 for WebGL-like field. Renders a lot of elements though. - -// A map with screen size to meteor width -export const METEOR_WIDTH_MAP = [ - [800, 60], - [1100, 90], -]; - -export const METEOR_WIDTH_DEFAULT = 120; // For screens larger than 1100px - -type MeteorDimensions = { - width: number; - height: number; - tailLength: number; - gap: number; - tiltAngle: number; // In radians -}; - -type MeteorFieldData = { - width: number; - height: number; - count: number; - marginLeft: number; - marginTop: number; -}; - -@Component({ - selector: 'adev-home-animation', - imports: [AnimationLayerDirective, RouterLink], - templateUrl: './home-animation.component.html', - styleUrl: './home-animation.component.scss', -}) -export class HomeAnimationComponent { - private readonly win = inject(WINDOW); - private readonly animCreator = inject(AnimationCreatorService); - private readonly injector = inject(Injector); - private readonly elementRef = inject(ElementRef); - private readonly destroyRef = inject(DestroyRef); - - readonly animationLayers = viewChildren(AnimationLayerDirective); - - readonly ctaLink = isIos ? 'overview' : 'tutorials/learn-angular'; - - readonly isUwu = input.required(); - readonly ready = output(); - - readonly reducedMotion = signal(shouldReduceMotion()); - readonly meteorFieldData = signal(null); - readonly meteors = signal([]); - - constructor() { - if (!this.reducedMotion()) { - this.initAnimation(); - } else { - this.ready.emit(true); - } - } - - private initAnimation() { - // Limitation: Meteor dimensions won't change on page resize - const meteorDimensions = this.calculateMeteorDimensions(); - const data = this.calculateMeteorFieldData(meteorDimensions); - this.setCssVariables(meteorDimensions); - this.meteorFieldData.set(data); - - // Generate a meteor field. The number represents the type [1, 3] - this.meteors.set(new Array(data.count).fill(1).map(() => Math.round(Math.random() * 2 + 1))); - - afterNextRender({ - read: () => { - const animation = this.animCreator - .createAnimation(this.animationLayers(), {timestep: ANIM_TIMESTEP}) - .define(generateHomeAnimationDefinition(this.isUwu(), this.meteors().length)) - .addPlugin(new AnimationScrollHandler(this.elementRef, this.injector)); - - this.ready.emit(true); - this.destroyRef.onDestroy(() => animation.dispose()); - }, - }); - } - - /** Calculate the dimensions and sizes of a meteor – width, height, tail, tilt angle, etc. */ - private calculateMeteorDimensions(): MeteorDimensions { - let width = METEOR_WIDTH_DEFAULT; - - for (const [screenSize, meteorWidth] of METEOR_WIDTH_MAP) { - if (this.win.innerWidth <= screenSize) { - width = meteorWidth; - } - } - - const height = width * METEOR_HW_RATIO; - const gap = width * METEOR_GAP_RATIO; - - // Pythagorean theorem + some trigonometry - const tailLength = Math.sqrt(width * width + height * height); - const tiltAngle = -Math.asin(width / tailLength); - - return { - width, - height, - gap, - tailLength, - tiltAngle, - }; - } - - /** Calculate the number of meteors and size of the field. */ - private calculateMeteorFieldData(meteorDim: MeteorDimensions): MeteorFieldData { - const mW = meteorDim.width + meteorDim.gap; - const mH = meteorDim.height + meteorDim.gap; - let rows = 1; - let cols = 1; - - while (cols * mW - meteorDim.gap <= this.win.innerWidth) { - cols++; - } - while (rows * mH - meteorDim.gap <= this.win.innerHeight) { - rows++; - } - - const width = cols * mW - meteorDim.gap; - const height = rows * mH - meteorDim.gap; - - return { - count: rows * cols, - width, - height, - marginLeft: -(width - this.win.innerWidth) / 2, - marginTop: -(height - this.win.innerHeight) / 2, - }; - } - - private setCssVariables({width, height, tailLength, tiltAngle, gap}: MeteorDimensions) { - const styleRef = this.elementRef.nativeElement.style; - styleRef.setProperty('--meteor-width', width + 'px'); - styleRef.setProperty('--meteor-height', height + 'px'); - styleRef.setProperty('--meteor-tail-length', tailLength + 'px'); - styleRef.setProperty('--meteor-tilt-angle', tiltAngle + 'rad'); - styleRef.setProperty('--meteor-gap', gap + 'px'); - } -}