home page

This commit is contained in:
sdarbinyan
2026-02-14 01:28:08 +04:00
parent 88ac37ebc4
commit 751ad48489
14 changed files with 522 additions and 155 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

@@ -16,6 +16,9 @@
</div> </div>
} @else { } @else {
<app-header></app-header> <app-header></app-header>
@if (!isHomePage()) {
<app-back-button />
}
<main class="main-content"> <main class="main-content">
<router-outlet></router-outlet> <router-outlet></router-outlet>
</main> </main>

View File

@@ -1,19 +1,20 @@
import { Component, OnInit, OnDestroy, signal, ApplicationRef } from '@angular/core'; import { Component, OnInit, OnDestroy, signal, ApplicationRef } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { RouterOutlet } from '@angular/router'; import { Router, RouterOutlet, NavigationEnd } from '@angular/router';
import { Title } from '@angular/platform-browser'; import { Title } from '@angular/platform-browser';
import { HeaderComponent } from './components/header/header.component'; import { HeaderComponent } from './components/header/header.component';
import { FooterComponent } from './components/footer/footer.component'; import { FooterComponent } from './components/footer/footer.component';
import { BackButtonComponent } from './components/back-button/back-button.component';
import { ApiService } from './services'; import { ApiService } from './services';
import { Subscription, interval, concat } from 'rxjs'; import { Subscription, interval, concat } from 'rxjs';
import { first } from 'rxjs/operators'; import { filter, first } from 'rxjs/operators';
import { environment } from '../environments/environment'; import { environment } from '../environments/environment';
import { SwUpdate } from '@angular/service-worker'; import { SwUpdate } from '@angular/service-worker';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
imports: [RouterOutlet, HeaderComponent, FooterComponent, CommonModule], imports: [RouterOutlet, HeaderComponent, FooterComponent, BackButtonComponent, CommonModule],
templateUrl: './app.html', templateUrl: './app.html',
styleUrl: './app.scss' styleUrl: './app.scss'
}) })
@@ -21,14 +22,17 @@ export class App implements OnInit, OnDestroy {
protected title = environment.brandName; protected title = environment.brandName;
serverAvailable = signal(true); serverAvailable = signal(true);
checkingServer = signal(true); checkingServer = signal(true);
isHomePage = signal(true);
private pingSubscription?: Subscription; private pingSubscription?: Subscription;
private updateSubscription?: Subscription; private updateSubscription?: Subscription;
private routerSubscription?: Subscription;
constructor( constructor(
private apiService: ApiService, private apiService: ApiService,
private titleService: Title, private titleService: Title,
private swUpdate: SwUpdate, private swUpdate: SwUpdate,
private appRef: ApplicationRef private appRef: ApplicationRef,
private router: Router
) {} ) {}
ngOnInit(): void { ngOnInit(): void {
@@ -36,6 +40,14 @@ export class App implements OnInit, OnDestroy {
this.titleService.setTitle(`${environment.brandFullName} - Маркетплейс товаров и услуг`); this.titleService.setTitle(`${environment.brandFullName} - Маркетплейс товаров и услуг`);
this.checkServerHealth(); this.checkServerHealth();
this.setupAutoUpdates(); this.setupAutoUpdates();
// Track route changes to show/hide back button
this.routerSubscription = this.router.events
.pipe(filter(event => event instanceof NavigationEnd))
.subscribe((event) => {
const url = (event as NavigationEnd).urlAfterRedirects || (event as NavigationEnd).url;
this.isHomePage.set(url === '/' || url === '/home' || url === '');
});
} }
checkServerHealth(): void { checkServerHealth(): void {
@@ -84,6 +96,7 @@ export class App implements OnInit, OnDestroy {
ngOnDestroy(): void { ngOnDestroy(): void {
this.pingSubscription?.unsubscribe(); this.pingSubscription?.unsubscribe();
this.updateSubscription?.unsubscribe(); this.updateSubscription?.unsubscribe();
this.routerSubscription?.unsubscribe();
} }
retryConnection(): void { retryConnection(): void {

View File

@@ -0,0 +1,69 @@
import { Component } from '@angular/core';
import { Location } from '@angular/common';
import { environment } from '../../../environments/environment';
@Component({
selector: 'app-back-button',
standalone: true,
template: `
@if (!isnovo) {
<button class="dexar-back-btn" (click)="goBack()" aria-label="Назад">
<svg width="37" height="24" viewBox="0 0 37 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.73 11.46c-.97-.52-.97-1.4 0-1.92L20.39 1.21c1.48-.79 3.89-.19 3.89.95v3.74h6.94c1.28 0 2.31.59 2.31 1.31v6.56c0 .73-1.03 1.31-2.31 1.31h-6.94v3.74c0 1.15-2.42 1.74-3.89.96L4.73 11.46Z"
fill="#497671" fill-opacity="0.75" stroke="white" stroke-opacity="0.6" stroke-linejoin="round"/>
</svg>
</button>
}
`,
styles: [`
.dexar-back-btn {
position: fixed;
top: 76px;
left: 20px;
z-index: 100;
background: none;
border: none;
cursor: pointer;
padding: 4px;
transition: transform 0.2s ease;
svg path {
transition: fill 0.2s ease, fill-opacity 0.2s ease;
}
&:hover {
transform: scale(1.08);
svg path {
fill: #A1B4B5;
fill-opacity: 1;
}
}
&:active {
transform: scale(0.95);
}
}
@media (max-width: 768px) {
.dexar-back-btn {
top: 68px;
left: 12px;
svg {
width: 30px;
height: 20px;
}
}
}
`]
})
export class BackButtonComponent {
isnovo = environment.theme === 'novo';
constructor(private location: Location) {}
goBack(): void {
this.location.back();
}
}

View File

@@ -49,49 +49,60 @@
</div> </div>
</footer> </footer>
} @else { } @else {
<!-- DEXAR VERSION - Original --> <!-- DEXAR VERSION - Redesigned -->
<footer class="footer"> <footer class="dexar-footer">
<div class="footer-container"> <div class="dexar-footer-bg">
<div class="footer-section"> <div class="dexar-footer-container">
<h3>Информация</h3> <div class="dexar-footer-top">
<ul> <div class="dexar-footer-logo">
<li><a routerLink="/about">О компании</a></li> <img src="/assets/images/dexar-logo.svg" alt="Dexar" class="dexar-footer-logo-desktop" loading="lazy" />
<li><a routerLink="/contacts">Контакты</a></li> <img src="/assets/images/dexar-logo-small.svg" alt="Dexar" class="dexar-footer-logo-mobile" loading="lazy" />
<li><a routerLink="/company-details">Реквизиты организации</a></li> </div>
</ul>
</div> <div class="dexar-footer-columns">
<div class="dexar-footer-col">
<h4>Информация</h4>
<ul>
<li><a routerLink="/about">О компании</a></li>
<li><a routerLink="/contacts">Контакты</a></li>
<li><a routerLink="/company-details">Реквизиты</a></li>
</ul>
</div>
<div class="footer-section"> <div class="dexar-footer-col">
<h3>Документы</h3> <h4>Документы</h4>
<ul> <ul>
<li><a routerLink="/payment-terms">Правила оплаты</a></li> <li><a routerLink="/payment-terms">Правила оплаты</a></li>
<li><a routerLink="/return-policy">Политика возврата</a></li> <li><a routerLink="/return-policy">Политика возврата</a></li>
<li><a routerLink="/public-offer">Публичная оферта</a></li> <li><a routerLink="/public-offer">Публичная оферта</a></li>
<li><a routerLink="/privacy-policy">Политика конфиденциальности</a></li> <li><a routerLink="/privacy-policy">Конфиденциальность</a></li>
</ul> </ul>
</div> </div>
<div class="footer-section"> <div class="dexar-footer-col">
<h3>Помощь</h3> <h4>Помощь</h4>
<ul> <ul>
<li><a routerLink="/faq">Часто задаваемые вопросы</a></li> <li><a routerLink="/faq">FAQ</a></li>
<li><a routerLink="/delivery">Доставка</a></li> <li><a routerLink="/delivery">Доставка</a></li>
<li><a routerLink="/guarantee">Гарантия</a></li> <li><a routerLink="/guarantee">Гарантия</a></li>
</ul> </ul>
</div> </div>
<div class="footer-section payment-systems"> <div class="dexar-footer-col">
<h3>Способы оплаты</h3> <h4>Оплата</h4>
<div class="payment-logos"> <div class="dexar-payment-logos">
<img src="/assets/images/mir-logo.svg" alt="МИР" class="payment-logo" loading="lazy" decoding="async" width="60" height="40" /> <img src="/assets/images/mir-logo.svg" alt="МИР" loading="lazy" width="48" height="32" />
<img src="/assets/images/visa-logo.svg" alt="Visa" class="payment-logo" loading="lazy" decoding="async" width="60" height="40" /> <img src="/assets/images/visa-logo.svg" alt="Visa" loading="lazy" width="48" height="32" />
<img src="/assets/images/mastercard-logo.svg" alt="Mastercard" class="payment-logo" loading="lazy" decoding="async" width="60" height="40" /> <img src="/assets/images/mastercard-logo.svg" alt="Mastercard" loading="lazy" width="48" height="32" />
</div>
</div>
</div>
</div>
<div class="dexar-footer-bottom">
<p>&copy; {{ currentYear }} {{ brandName }}. Все права защищены.</p>
</div> </div>
</div> </div>
</div> </div>
<div class="footer-bottom">
<p>&copy; {{ currentYear }} {{ brandName }}. Все права защищены.</p>
</div>
</footer> </footer>
} }

View File

@@ -1,4 +1,9 @@
.footer { :host {
display: block;
width: 100%;
}
.footer {
background-color: #1a1a1a; background-color: #1a1a1a;
color: #ffffff; color: #ffffff;
margin-top: auto; margin-top: auto;
@@ -217,3 +222,192 @@
text-align: center; text-align: center;
} }
} }
// ========== DEXAR FOOTER STYLES ==========
.dexar-footer {
margin-top: auto;
}
.dexar-footer-bg {
background-image: url('/assets/images/footer_bg.webp');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
inset: 0;
background: rgba(30, 60, 56, 0.85);
}
}
.dexar-footer-container {
position: relative;
z-index: 1;
max-width: 1440px;
margin: 0 auto;
padding: 48px 40px 24px;
}
.dexar-footer-top {
display: flex;
gap: 48px;
padding-bottom: 32px;
border-bottom: 1px solid rgba(255, 255, 255, 0.15);
}
.dexar-footer-logo {
flex-shrink: 0;
display: flex;
align-items: flex-start;
padding-top: 4px;
.dexar-footer-logo-desktop {
display: block;
height: 40px;
width: auto;
filter: brightness(0) invert(1);
}
.dexar-footer-logo-mobile {
display: none;
height: 32px;
width: auto;
filter: brightness(0) invert(1);
}
}
.dexar-footer-columns {
flex: 1;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 32px;
}
.dexar-footer-col {
h4 {
font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-size: 15px;
font-weight: 600;
color: #ffffff;
margin: 0 0 16px 0;
}
ul {
list-style: none;
padding: 0;
margin: 0;
li {
margin-bottom: 10px;
a {
font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-size: 13px;
font-weight: 400;
color: rgba(255, 255, 255, 0.7);
text-decoration: none;
transition: color 0.2s ease;
&:hover {
color: #ffffff;
}
}
}
}
}
.dexar-payment-logos {
display: flex;
gap: 10px;
flex-wrap: wrap;
align-items: center;
img {
height: 28px;
max-height: 28px;
width: auto;
max-width: 60px;
background: rgba(255, 255, 255, 0.9);
padding: 4px 8px;
border-radius: 4px;
transition: opacity 0.2s;
object-fit: contain;
&:hover {
opacity: 0.85;
}
}
}
.dexar-footer-bottom {
padding: 20px 0 0;
text-align: center;
p {
font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-size: 12px;
color: rgba(255, 255, 255, 0.5);
margin: 0;
}
}
// Responsive
@media (max-width: 992px) {
.dexar-footer-container {
padding: 40px 32px 20px;
}
.dexar-footer-top {
flex-direction: column;
gap: 32px;
}
.dexar-footer-columns {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.dexar-footer-container {
padding: 32px 20px 16px;
}
.dexar-footer-logo {
.dexar-footer-logo-desktop {
display: none;
}
.dexar-footer-logo-mobile {
display: block;
}
}
.dexar-footer-columns {
grid-template-columns: repeat(2, 1fr);
gap: 24px;
}
.dexar-footer-col h4 {
font-size: 14px;
margin-bottom: 12px;
}
.dexar-footer-col ul li a {
font-size: 12px;
}
}
@media (max-width: 480px) {
.dexar-footer-container {
padding: 24px 16px 12px;
}
.dexar-footer-columns {
grid-template-columns: 1fr;
gap: 20px;
}
}

View File

@@ -88,8 +88,8 @@
<div class="dexar-actions"> <div class="dexar-actions">
<!-- Cart Button --> <!-- Cart Button -->
<a routerLink="/cart" routerLinkActive="dexar-cart-active" class="dexar-cart-btn" (click)="closeMenu()"> <a routerLink="/cart" routerLinkActive="dexar-cart-active" class="dexar-cart-btn" (click)="closeMenu()">
<svg width="48" height="32" viewBox="0 0 48 32" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="32" height="24" viewBox="0 0 48 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 0.5H36C42.3513 0.5 47.5 5.64873 47.5 12V20C47.5 26.3513 42.3513 31.5 36 31.5H12C5.64873 31.5 0.5 26.3513 0.5 20V12C0.5 5.64873 5.64873 0.5 12 0.5Z" fill="#497671" fill-opacity="0.3" /> <path d="M12 0.5H36C42.3513 0.5 47.5 5.64873 47.5 12V20C47.5 26.3513 42.3513 31.5 36 31.5H12C5.64873 31.5 0.5 26.3513 0.5 20V12C0.5 5.64873 5.64873 0.5 12 0.5Z" fill="transparent" />
<path d="M12 0.5H36C42.3513 0.5 47.5 5.64873 47.5 12V20C47.5 26.3513 42.3513 31.5 36 31.5H12C5.64873 31.5 0.5 26.3513 0.5 20V12C0.5 5.64873 5.64873 0.5 12 0.5Z" stroke="#677B78" /> <path d="M12 0.5H36C42.3513 0.5 47.5 5.64873 47.5 12V20C47.5 26.3513 42.3513 31.5 36 31.5H12C5.64873 31.5 0.5 26.3513 0.5 20V12C0.5 5.64873 5.64873 0.5 12 0.5Z" stroke="#677B78" />
<path d="M10 3.9C10 3.40294 10.4029 3 10.9 3H13.6C14.013 3 14.373 3.28107 14.4731 3.68172L15.2027 6.6H36.1C36.3677 6.6 36.6216 6.7192 36.7925 6.92523C36.9635 7.13125 37.0339 7.40271 36.9846 7.66586L34.2846 22.0659C34.2048 22.4915 33.8331 22.8 33.4 22.8H31.6H19H17.2C16.7669 22.8 16.3952 22.4915 16.3154 22.0659L13.6204 7.69224L12.8973 4.8H10.9C10.4029 4.8 10 4.39706 10 3.9ZM15.5844 8.4L17.9469 21H32.6531L35.0156 8.4H15.5844ZM19 22.8C17.0118 22.8 15.4 24.4118 15.4 26.4C15.4 28.3882 17.0118 30 19 30C20.9882 30 22.6 28.3882 22.6 26.4C22.6 24.4118 20.9882 22.8 19 22.8ZM31.6 22.8C29.6118 22.8 28 24.4118 28 26.4C28 28.3882 29.6118 30 31.6 30C33.5882 30 35.2 28.3882 35.2 26.4C35.2 24.4118 33.5882 22.8 31.6 22.8ZM19 24.6C19.9941 24.6 20.8 25.4059 20.8 26.4C20.8 27.3941 19.9941 28.2 19 28.2C18.0059 28.2 17.2 27.3941 17.2 26.4C17.2 25.4059 18.0059 24.6 19 24.6ZM31.6 24.6C32.5941 24.6 33.4 25.4059 33.4 26.4C33.4 27.3941 32.5941 28.2 31.6 28.2C30.6059 28.2 29.8 27.3941 29.8 26.4C29.8 25.4059 30.6059 24.6 31.6 24.6Z" fill="#1E3C38" /> <path d="M10 3.9C10 3.40294 10.4029 3 10.9 3H13.6C14.013 3 14.373 3.28107 14.4731 3.68172L15.2027 6.6H36.1C36.3677 6.6 36.6216 6.7192 36.7925 6.92523C36.9635 7.13125 37.0339 7.40271 36.9846 7.66586L34.2846 22.0659C34.2048 22.4915 33.8331 22.8 33.4 22.8H31.6H19H17.2C16.7669 22.8 16.3952 22.4915 16.3154 22.0659L13.6204 7.69224L12.8973 4.8H10.9C10.4029 4.8 10 4.39706 10 3.9ZM15.5844 8.4L17.9469 21H32.6531L35.0156 8.4H15.5844ZM19 22.8C17.0118 22.8 15.4 24.4118 15.4 26.4C15.4 28.3882 17.0118 30 19 30C20.9882 30 22.6 28.3882 22.6 26.4C22.6 24.4118 20.9882 22.8 19 22.8ZM31.6 22.8C29.6118 22.8 28 24.4118 28 26.4C28 28.3882 29.6118 30 31.6 30C33.5882 30 35.2 28.3882 35.2 26.4C35.2 24.4118 33.5882 22.8 31.6 22.8ZM19 24.6C19.9941 24.6 20.8 25.4059 20.8 26.4C20.8 27.3941 19.9941 28.2 19 28.2C18.0059 28.2 17.2 27.3941 17.2 26.4C17.2 25.4059 18.0059 24.6 19 24.6ZM31.6 24.6C32.5941 24.6 33.4 25.4059 33.4 26.4C33.4 27.3941 32.5941 28.2 31.6 28.2C30.6059 28.2 29.8 27.3941 29.8 26.4C29.8 25.4059 30.6059 24.6 31.6 24.6Z" fill="#1E3C38" />
</svg> </svg>

View File

@@ -453,7 +453,7 @@
// ========== DEXAR REDESIGN 2026 STYLES ========== // ========== DEXAR REDESIGN 2026 STYLES ==========
.dexar-header { .dexar-header {
background: rgba(117, 121, 124, 0.1); background: rgba(117, 121, 124, 0.1);
padding: 14px 0; padding: 8px 0;
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 1000; z-index: 1000;
@@ -463,11 +463,11 @@
.dexar-header-container { .dexar-header-container {
max-width: 1440px; max-width: 1440px;
margin: 0 auto; margin: 0 auto;
padding: 0 56px; padding: 0 40px;
display: flex; display: flex;
align-items: center; align-items: center;
gap: 57px; gap: 32px;
height: 56px; height: 48px;
} }
.dexar-logo { .dexar-logo {
@@ -477,8 +477,8 @@
flex-shrink: 0; flex-shrink: 0;
::ng-deep .logo-img { ::ng-deep .logo-img {
width: 148px; width: 120px;
height: 48px; height: 38px;
object-fit: contain; object-fit: contain;
} }
} }
@@ -498,13 +498,13 @@
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
padding: 10px 48px; padding: 6px 32px;
height: 49px; height: 38px;
border: 1px solid #d3dad9; border: 1px solid #d3dad9;
background: rgba(255, 255, 255, 0.74); background: rgba(255, 255, 255, 0.74);
font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-weight: 600; font-weight: 600;
font-size: 22px; font-size: 15px;
color: #1e3c38; color: #1e3c38;
text-decoration: none; text-decoration: none;
cursor: pointer; cursor: pointer;
@@ -523,45 +523,45 @@
} }
.dexar-nav-btn-left { .dexar-nav-btn-left {
border-radius: 13px 0 0 13px; border-radius: 10px 0 0 10px;
padding: 10px 48px; padding: 6px 32px;
} }
.dexar-nav-btn-middle { .dexar-nav-btn-middle {
padding: 10px 63px; padding: 6px 40px;
border-left: none; border-left: none;
} }
.dexar-nav-btn-right { .dexar-nav-btn-right {
border-radius: 0 13px 13px 0; border-radius: 0 10px 10px 0;
padding: 10px 42px; padding: 6px 28px;
border-left: none; border-left: none;
} }
// Search Box // Search Box
.dexar-search-wrapper { .dexar-search-wrapper {
flex: 1; flex: 1;
max-width: 234px; max-width: 200px;
margin-left: auto; margin-left: auto;
} }
.dexar-search-box { .dexar-search-box {
position: relative; position: relative;
width: 100%; width: 100%;
height: 49px; height: 38px;
background: rgba(255, 255, 255, 0.74); background: rgba(255, 255, 255, 0.74);
border: 1px solid #d2dad9; border: 1px solid #d2dad9;
border-radius: 22px; border-radius: 19px;
box-shadow: 0 3px 4px rgba(0, 0, 0, 0.15); box-shadow: 0 2px 3px rgba(0, 0, 0, 0.12);
display: flex; display: flex;
align-items: center; align-items: center;
padding: 0 20px; padding: 0 14px;
gap: 10px; gap: 8px;
} }
.dexar-search-icon { .dexar-search-icon {
width: 28px; width: 20px;
height: 28px; height: 20px;
flex-shrink: 0; flex-shrink: 0;
} }
@@ -572,7 +572,7 @@
outline: none; outline: none;
font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-weight: 600; font-weight: 600;
font-size: 22px; font-size: 15px;
color: #828e8d; color: #828e8d;
cursor: pointer; cursor: pointer;
@@ -591,8 +591,8 @@
.dexar-cart-btn { .dexar-cart-btn {
position: relative; position: relative;
width: 48px; width: 32px;
height: 32px; height: 24px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@@ -601,8 +601,8 @@
transition: opacity 0.3s ease; transition: opacity 0.3s ease;
svg { svg {
width: 48px; width: 32px;
height: 32px; height: 24px;
} }
&:hover { &:hover {

View File

@@ -155,18 +155,18 @@
// Dexar header specific styles // Dexar header specific styles
:host-context(.dexar-header) { :host-context(.dexar-header) {
.language-selector { .language-selector {
width: 67px; width: 52px;
height: 32px; height: 26px;
} }
.language-button { .language-button {
width: 100%; width: 100%;
height: 100%; height: 100%;
padding: 6px; padding: 4px;
gap: 8px; gap: 4px;
background: rgba(255, 255, 255, 0.3); background: rgba(255, 255, 255, 0.3);
border: 1px solid #677b78; border: 1px solid #677b78;
border-radius: 12px; border-radius: 8px;
color: #1e3c38; color: #1e3c38;
justify-content: center; justify-content: center;
@@ -182,7 +182,7 @@
.language-code { .language-code {
font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-weight: 500; font-weight: 500;
font-size: 24px; font-size: 15px;
color: #1e3c38; color: #1e3c38;
letter-spacing: 0; letter-spacing: 0;
line-height: 1; line-height: 1;
@@ -190,8 +190,8 @@
.dropdown-arrow { .dropdown-arrow {
display: block; display: block;
width: 9px; width: 7px;
height: 14px; height: 10px;
opacity: 1; opacity: 1;
path { path {

View File

@@ -83,7 +83,7 @@
<p class="dexar-hero-tagline">просто и удобно</p> <p class="dexar-hero-tagline">просто и удобно</p>
<div class="dexar-hero-actions"> <div class="dexar-hero-actions">
<a routerLink="/search" class="dexar-btn-primary"> <a (click)="scrollToCatalog()" class="dexar-btn-primary">
Перейти в каталог Перейти в каталог
</a> </a>
<button (click)="navigateToSearch()" class="dexar-btn-secondary"> <button (click)="navigateToSearch()" class="dexar-btn-secondary">
@@ -115,8 +115,8 @@
} }
@if (!loading() && !error()) { @if (!loading() && !error()) {
<section class="dexar-categories"> <section class="dexar-categories" id="catalog">
<h2 class="dexar-categories-title">Категории</h2> <h2 class="dexar-categories-title">Каталог товаров</h2>
@if (getTopLevelCategories().length === 0) { @if (getTopLevelCategories().length === 0) {
<div class="dexar-empty-categories"> <div class="dexar-empty-categories">
<div class="dexar-empty-icon">📦</div> <div class="dexar-empty-icon">📦</div>
@@ -126,18 +126,19 @@
} @else { } @else {
<div class="dexar-categories-grid"> <div class="dexar-categories-grid">
@for (category of getTopLevelCategories(); track category.categoryID) { @for (category of getTopLevelCategories(); track category.categoryID) {
<div class="dexar-category-card"> <a [routerLink]="['/category', category.categoryID]" class="dexar-category-card">
<a [routerLink]="['/category', category.categoryID]" class="dexar-category-link"> <div class="dexar-category-image">
<div class="dexar-category-media"> @if (category.icon) {
@if (category.icon) { <img [src]="category.icon" [alt]="category.name" loading="lazy" decoding="async" />
<img [src]="category.icon" [alt]="category.name" loading="lazy" decoding="async" /> } @else {
} @else { <div class="dexar-category-fallback">{{ category.name.charAt(0) }}</div>
<div class="dexar-category-fallback">{{ category.name }}</div> }
} </div>
</div> <div class="dexar-category-info">
<h3>{{ category.name }}</h3> <h3 class="dexar-category-name">{{ category.name }}</h3>
</a> <p class="dexar-category-count">{{ getItemCount(category.categoryID) }} товаров</p>
</div> </div>
</a>
} }
</div> </div>
} }

View File

@@ -705,6 +705,7 @@
// ========== DEXAR REDESIGN 2026 STYLES ========== // ========== DEXAR REDESIGN 2026 STYLES ==========
.dexar-home { .dexar-home {
width: 100%; width: 100%;
overflow-x: hidden;
} }
// Hero Section // Hero Section
@@ -908,7 +909,7 @@
// Categories Section // Categories Section
.dexar-categories { .dexar-categories {
max-width: 1200px; max-width: 1440px;
margin: 50px auto; margin: 50px auto;
padding: 0 56px; padding: 0 56px;
} }
@@ -950,74 +951,44 @@
.dexar-categories-grid { .dexar-categories-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); grid-template-columns: repeat(4, 1fr);
gap: 30px; gap: 30px;
animation: fadeIn 0.6s ease-in 0.3s both; animation: fadeIn 0.6s ease-in 0.3s both;
width: 100%;
} }
.dexar-category-card { .dexar-category-card {
background: white; width: 100%;
border-radius: 16px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
height: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; text-decoration: none;
position: relative; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, #497671 0%, #a7ceca 100%);
transform: scaleX(0);
transition: transform 0.3s ease;
}
&:hover { &:hover {
transform: translateY(-8px); transform: translateY(-4px);
box-shadow: 0 12px 32px rgba(73, 118, 113, 0.2);
&::before { .dexar-category-image {
transform: scaleX(1); box-shadow: 0 6px 8px 0 rgba(0, 0, 0, 0.2);
}
.dexar-category-info {
box-shadow: 0 6px 8px 0 rgba(0, 0, 0, 0.2);
} }
} }
} }
.dexar-category-link { .dexar-category-image {
display: flex;
flex-direction: column;
height: 100%;
text-decoration: none;
color: inherit;
padding: 20px;
h3 {
font-size: 1.3rem;
font-weight: 600;
color: #1e3c38;
margin: 16px 0 0 0;
transition: color 0.3s ease;
}
&:hover h3 {
color: #497671;
}
}
.dexar-category-media {
width: 100%; width: 100%;
height: 200px; aspect-ratio: 4 / 3;
background: #f5f5f5; border: 1px solid #d3dad9;
border-radius: 12px; border-radius: 13px 13px 0 0;
box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.15);
overflow: hidden; overflow: hidden;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: #f5f5f5;
position: relative;
img { img {
width: 100%; width: 100%;
@@ -1037,12 +1008,51 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
font-size: 3rem; font-size: 5rem;
font-weight: 700; font-weight: 700;
color: #497671; color: #497671;
background: linear-gradient(135deg, #f5f5f5 0%, #e0e0e0 100%); background: linear-gradient(135deg, #f5f5f5 0%, #e0e0e0 100%);
} }
.dexar-category-info {
width: 100%;
border: 1px solid #d3dad9;
border-top: none;
border-radius: 0 0 13px 13px;
padding: 12px 16px;
box-shadow: 0 3px 4px 0 rgba(0, 0, 0, 0.15);
background: #f5f3f9;
display: flex;
flex-direction: column;
justify-content: center;
gap: 2px;
transition: background 0.3s ease;
}
.dexar-category-name {
font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-weight: 600;
font-size: clamp(14px, 1.4vw, 18px);
color: #1e3c38;
margin: 0;
line-height: 1.3;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
min-height: calc(2 * 1.3em);
}
.dexar-category-count {
font-family: "DM Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
font-weight: 600;
font-size: clamp(11px, 1vw, 13px);
color: #697777;
margin: 0;
line-height: 1.2;
}
// Responsive Design // Responsive Design
@media (max-width: 1200px) { @media (max-width: 1200px) {
.dexar-hero { .dexar-hero {
@@ -1065,6 +1075,11 @@
.dexar-categories { .dexar-categories {
padding: 0 40px; padding: 0 40px;
} }
.dexar-categories-grid {
grid-template-columns: repeat(3, 1fr);
gap: 24px;
}
} }
@media (max-width: 992px) { @media (max-width: 992px) {
@@ -1102,8 +1117,8 @@
} }
.dexar-categories-grid { .dexar-categories-grid {
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); grid-template-columns: repeat(3, 1fr);
gap: 24px; gap: 20px;
} }
} }
@@ -1156,12 +1171,12 @@
} }
.dexar-categories-grid { .dexar-categories-grid {
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); grid-template-columns: repeat(2, 1fr);
gap: 20px; gap: 16px;
} }
.dexar-category-media { .dexar-category-info {
height: 160px; padding: 10px 12px;
} }
} }
@@ -1189,12 +1204,20 @@
font-size: 18px; font-size: 18px;
} }
.dexar-categories {
padding: 0 16px;
}
.dexar-categories-grid { .dexar-categories-grid {
grid-template-columns: 1fr; grid-template-columns: repeat(2, 1fr);
gap: 16px; gap: 12px;
}
.dexar-category-info {
padding: 8px 10px;
} }
.dexar-category-card:hover { .dexar-category-card:hover {
transform: translateY(-4px); transform: translateY(-2px);
} }
} }

View File

@@ -1,6 +1,7 @@
import { Component, OnInit, signal, computed, ChangeDetectionStrategy } from '@angular/core'; import { Component, OnInit, signal, computed, ChangeDetectionStrategy } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { Router, RouterLink } from '@angular/router'; import { Router, RouterLink } from '@angular/router';
import { forkJoin } from 'rxjs';
import { ApiService } from '../../services'; import { ApiService } from '../../services';
import { Category } from '../../models'; import { Category } from '../../models';
import { environment } from '../../../environments/environment'; import { environment } from '../../../environments/environment';
@@ -18,6 +19,7 @@ export class HomeComponent implements OnInit {
brandName = environment.brandFullName; brandName = environment.brandFullName;
isnovo = environment.theme === 'novo'; isnovo = environment.theme === 'novo';
categories = signal<Category[]>([]); categories = signal<Category[]>([]);
itemCounts = signal<Map<number, number>>(new Map());
loading = signal(true); loading = signal(true);
error = signal<string | null>(null); error = signal<string | null>(null);
@@ -52,6 +54,7 @@ export class HomeComponent implements OnInit {
next: (categories) => { next: (categories) => {
this.categories.set(categories); this.categories.set(categories);
this.loading.set(false); this.loading.set(false);
this.loadItemCounts();
}, },
error: (err) => { error: (err) => {
this.error.set('Failed to load categories'); this.error.set('Failed to load categories');
@@ -61,6 +64,31 @@ export class HomeComponent implements OnInit {
}); });
} }
private loadItemCounts(): void {
const topLevel = this.topLevelCategories();
if (topLevel.length === 0) return;
const requests: Record<string, ReturnType<ApiService['getCategoryItems']>> = {};
topLevel.forEach(cat => {
requests[cat.categoryID.toString()] = this.apiService.getCategoryItems(cat.categoryID, 1000);
});
forkJoin(requests).subscribe({
next: (results) => {
const counts = new Map<number, number>();
Object.entries(results).forEach(([id, items]) => {
counts.set(Number(id), items.length);
});
this.itemCounts.set(counts);
},
error: (err) => console.error('Error loading item counts:', err)
});
}
getItemCount(categoryID: number): number {
return this.itemCounts().get(categoryID) || 0;
}
getTopLevelCategories(): Category[] { getTopLevelCategories(): Category[] {
return this.topLevelCategories(); return this.topLevelCategories();
} }
@@ -72,4 +100,28 @@ export class HomeComponent implements OnInit {
navigateToSearch(): void { navigateToSearch(): void {
this.router.navigate(['/search']); this.router.navigate(['/search']);
} }
scrollToCatalog(): void {
const target = document.getElementById('catalog');
if (!target) return;
const targetY = target.getBoundingClientRect().top + window.scrollY;
const startY = window.scrollY;
const distance = targetY - startY;
const duration = 1200;
let start: number | null = null;
const easeInOutCubic = (t: number) =>
t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
const step = (timestamp: number) => {
if (!start) start = timestamp;
const elapsed = timestamp - start;
const progress = Math.min(elapsed / duration, 1);
window.scrollTo(0, startY + distance * easeInOutCubic(progress));
if (progress < 1) requestAnimationFrame(step);
};
requestAnimationFrame(step);
}
} }

View File

@@ -68,6 +68,7 @@ body {
color: var(--text-primary); color: var(--text-primary);
background: linear-gradient(to bottom, #f8f9fa 0%, #e9ecef 100%); background: linear-gradient(to bottom, #f8f9fa 0%, #e9ecef 100%);
min-height: 100vh; min-height: 100vh;
overflow-x: hidden;
} }
/* Smooth Transitions */ /* Smooth Transitions */