feat: add immersive store header with carousel and Smooch font
- Implemented a new immersive store header component with a background image, logo, hero content, and a continuous image carousel. - Integrated the Smooch font for enhanced typography in the header. - Updated the store layout to support the new immersive header type. - Added necessary styles and templates for the immersive header and carousel components. - Enhanced unit tests to cover the new immersive header functionality.
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
@if (trackImages().length) {
|
||||
<div class="continuous-carousel" role="region" [attr.aria-label]="ariaLabel()">
|
||||
<div class="continuous-carousel__track" [style.--duration]="durationSeconds() + 's'">
|
||||
@for (set of [0, 1]; track set) {
|
||||
<div class="continuous-carousel__set" [attr.aria-hidden]="set === 1 ? 'true' : null">
|
||||
@for (image of trackImages(); track $index) {
|
||||
<img
|
||||
class="continuous-carousel__image"
|
||||
[src]="image"
|
||||
alt=""
|
||||
[loading]="set === 0 && $index < 5 ? 'eager' : 'lazy'"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
:host {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.continuous-carousel {
|
||||
--gap: 1rem;
|
||||
--card-width: calc((100vw - 4 * var(--gap)) / 5);
|
||||
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.continuous-carousel__track {
|
||||
display: flex;
|
||||
width: max-content;
|
||||
animation: continuous-carousel-scroll var(--duration) linear infinite;
|
||||
}
|
||||
|
||||
.continuous-carousel__set {
|
||||
display: flex;
|
||||
flex: none;
|
||||
gap: var(--gap);
|
||||
padding-right: var(--gap);
|
||||
}
|
||||
|
||||
.continuous-carousel__image {
|
||||
display: block;
|
||||
flex: none;
|
||||
width: var(--card-width);
|
||||
aspect-ratio: 1.45;
|
||||
object-fit: cover;
|
||||
border-radius: 2px;
|
||||
background: #e5e5e5;
|
||||
}
|
||||
|
||||
.continuous-carousel:hover .continuous-carousel__track,
|
||||
.continuous-carousel:focus-within .continuous-carousel__track {
|
||||
animation-play-state: paused;
|
||||
}
|
||||
|
||||
@keyframes continuous-carousel-scroll {
|
||||
to {
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.continuous-carousel {
|
||||
--gap: 0.75rem;
|
||||
--card-width: calc((100vw - 2 * var(--gap)) / 3);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.continuous-carousel {
|
||||
--card-width: calc((100vw - var(--gap)) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.continuous-carousel {
|
||||
overflow-x: auto;
|
||||
}
|
||||
.continuous-carousel__track {
|
||||
animation: none;
|
||||
}
|
||||
.continuous-carousel__set[aria-hidden='true'] {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { ChangeDetectionStrategy, Component, computed, input } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-continuous-image-carousel',
|
||||
templateUrl: './continuous-image-carousel.component.html',
|
||||
styleUrl: './continuous-image-carousel.component.scss',
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class ContinuousImageCarouselComponent {
|
||||
readonly images = input.required<readonly string[]>();
|
||||
readonly ariaLabel = input('Imágenes destacadas');
|
||||
|
||||
// One full set must cover the viewport before the duplicate set takes over.
|
||||
protected readonly trackImages = computed(() => {
|
||||
const images = this.images().filter(Boolean);
|
||||
if (images.length === 0) return [];
|
||||
|
||||
return Array.from(
|
||||
{ length: Math.max(images.length, 5) },
|
||||
(_, index) => images[index % images.length],
|
||||
);
|
||||
});
|
||||
protected readonly durationSeconds = computed(() => Math.max(this.trackImages().length * 4, 16));
|
||||
}
|
||||
Reference in New Issue
Block a user