Загрузка данных
<main class="landing-page">
<section class="hero">
<div class="hero-content">
<p class="eyebrow">Онлайн-сервис для гостей</p>
<h1>Оценка наследственного имущества без лишней бюрократии</h1>
<p class="hero-description">
Продажная страница для знакомства с возможностями платформы: понятные этапы, прозрачные сроки, сопровождение
и быстрый переход к подаче заявки.
</p>
<div class="hero-actions">
<a routerLink="/auth" class="btn btn-primary">Начать сейчас</a>
<button type="button" class="btn btn-ghost" (click)="onRequestDemo()">Запросить демо</button>
</div>
<p class="stub-message" *ngIf="actionMessage">{{ actionMessage }}</p>
</div>
<aside class="hero-card">
<h2>Быстрый расчет</h2>
<p>Выберите тип имущества и получите ориентир по сроку и стоимости.</p>
<div class="calc-actions">
<button type="button" [class.active]="selectedObjectType === 'Квартира'" (click)="calculateEstimate('Квартира')">
Квартира
</button>
<button type="button" [class.active]="selectedObjectType === 'Дом'" (click)="calculateEstimate('Дом')">Дом</button>
<button
type="button"
[class.active]="selectedObjectType === 'Земельный участок'"
(click)="calculateEstimate('Земельный участок')"
>
Участок
</button>
</div>
<div class="estimate">
<div>
<span>Срок</span>
<strong>{{ estimatedDays }} дн.</strong>
</div>
<div>
<span>Стоимость</span>
<strong>{{ estimatedPrice | number: '1.0-0' }} ₽</strong>
</div>
</div>
</aside>
</section>
<section class="features">
<h2>Почему выбирают наш сервис</h2>
<div class="features-grid">
<article class="feature-card" *ngFor="let feature of features">
<div class="feature-icon">{{ feature.icon }}</div>
<h3>{{ feature.title }}</h3>
<p>{{ feature.description }}</p>
</article>
</div>
</section>
<section class="scenarios">
<h2>Сценарии использования для гостя</h2>
<ul>
<li *ngFor="let scenario of scenarios">{{ scenario }}</li>
</ul>
</section>
<section class="plans">
<h2>Тарифы и пакеты</h2>
<div class="plans-grid">
<article class="plan-card" [class.highlighted]="plan.highlighted" *ngFor="let plan of plans">
<p class="plan-name">{{ plan.name }}</p>
<p class="plan-price">{{ plan.price }}</p>
<p class="plan-description">{{ plan.description }}</p>
<ul>
<li *ngFor="let benefit of plan.benefits">{{ benefit }}</li>
</ul>
</article>
</div>
</section>
<section class="faq">
<h2>Частые вопросы</h2>
<div class="faq-list">
<article class="faq-item" *ngFor="let item of faq; let i = index">
<button type="button" class="faq-question" (click)="toggleFaq(i)">
<span>{{ item.question }}</span>
<span class="faq-toggle" [class.open]="item.opened">+</span>
</button>
<p class="faq-answer" *ngIf="item.opened">{{ item.answer }}</p>
</article>
</div>
</section>
<section class="cta">
<h2>Готовы начать?</h2>
<p>Создайте учетную запись и отправьте первую заявку на оценку имущества уже сегодня.</p>
<div class="cta-actions">
<a routerLink="/auth" class="btn btn-primary">Перейти к регистрации</a>
<button type="button" class="btn btn-ghost" (click)="onStartNow()">Открыть сценарий (stub)</button>
</div>
</section>
</main>
___________________________________CSS__________________________________
.landing-page {
--primary: #4f46e5;
--primary-soft: #e0e7ff;
--primary-hover: #4338ca;
--text-main: #111827;
--text-secondary: #6b7280;
--surface: #fff;
--surface-soft: #f8fafc;
--border: #e5e7eb;
--radius: 16px;
display: flex;
flex-direction: column;
gap: 2rem;
padding: 2rem 1.5rem 3rem;
color: var(--text-main);
.hero,
.features,
.scenarios,
.plans,
.faq,
.cta {
width: 100%;
max-width: 1250px;
margin: 0 auto;
}
.hero {
display: grid;
grid-template-columns: 1.5fr 1fr;
gap: 1.5rem;
align-items: stretch;
}
.hero-content,
.hero-card,
.features,
.scenarios,
.plans,
.faq,
.cta {
padding: 1.75rem;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--surface);
box-shadow: 0 10px 30px rgb(15 23 42 / 4%);
}
.eyebrow {
display: inline-flex;
margin: 0 0 0.75rem;
padding: 0.35rem 0.75rem;
border-radius: 999px;
font-size: 0.8rem;
font-weight: 700;
letter-spacing: 0.03em;
color: var(--primary);
background: var(--primary-soft);
}
h1 {
margin: 0;
font-size: clamp(1.9rem, 3.5vw, 3rem);
line-height: 1.15;
letter-spacing: -0.02em;
}
h2 {
margin: 0 0 1rem;
font-size: clamp(1.2rem, 2vw, 1.8rem);
letter-spacing: -0.01em;
}
h3 {
margin: 0.8rem 0 0.6rem;
font-size: 1.08rem;
}
p {
margin: 0;
color: var(--text-secondary);
}
.hero-description {
max-width: 60ch;
margin-top: 1rem;
line-height: 1.6;
}
.hero-actions,
.cta-actions {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
margin-top: 1.25rem;
}
.btn {
cursor: pointer;
padding: 0.72rem 1.1rem;
border: 1px solid transparent;
border-radius: 10px;
font-size: 0.95rem;
font-weight: 600;
text-decoration: none;
transition: all 0.2s ease;
}
.btn-primary {
color: #fff;
background: var(--primary);
&:hover {
background: var(--primary-hover);
transform: translateY(-1px);
}
}
.btn-ghost {
color: var(--primary);
background: #fff;
border-color: #c7d2fe;
&:hover {
background: var(--primary-soft);
}
}
.stub-message {
margin-top: 0.8rem;
padding: 0.75rem;
border: 1px dashed #c7d2fe;
border-radius: 10px;
font-size: 0.9rem;
color: #4338ca;
background: #eef2ff;
}
.hero-card {
display: flex;
flex-direction: column;
gap: 1rem;
background: linear-gradient(180deg, #fff 0%, #f8faff 100%);
}
.calc-actions {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
button {
cursor: pointer;
padding: 0.5rem 0.8rem;
border: 1px solid var(--border);
border-radius: 8px;
font-size: 0.85rem;
color: #374151;
background: #fff;
transition: all 0.2s ease;
}
.active {
border-color: #a5b4fc;
color: #3730a3;
background: #eef2ff;
}
}
.estimate {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 0.75rem;
div {
padding: 0.75rem;
border: 1px solid #dbeafe;
border-radius: 10px;
background: #f8fbff;
}
span {
display: block;
margin-bottom: 0.3rem;
font-size: 0.8rem;
color: #64748b;
}
strong {
font-size: 1.1rem;
color: #1e293b;
}
}
.features-grid,
.plans-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 1rem;
margin-top: 1rem;
}
.feature-card,
.plan-card {
padding: 1rem;
border: 1px solid var(--border);
border-radius: 12px;
background: var(--surface-soft);
}
.feature-icon {
display: flex;
align-items: center;
justify-content: center;
width: 34px;
height: 34px;
border-radius: 8px;
font-weight: 700;
color: #fff;
background: var(--primary);
}
.scenarios ul,
.plan-card ul {
margin: 1rem 0 0;
padding: 0 0 0 1.2rem;
color: #475569;
}
.scenarios li,
.plan-card li {
margin-bottom: 0.45rem;
line-height: 1.45;
}
.plan-name {
font-weight: 700;
color: #0f172a;
}
.plan-price {
margin-top: 0.35rem;
font-size: 1.3rem;
font-weight: 800;
color: var(--primary);
}
.plan-description {
margin-top: 0.35rem;
}
.plan-card.highlighted {
border-color: #a5b4fc;
background: #eef2ff;
}
.faq-list {
display: flex;
flex-direction: column;
gap: 0.8rem;
margin-top: 1rem;
}
.faq-item {
border: 1px solid var(--border);
border-radius: 12px;
background: var(--surface-soft);
}
.faq-question {
cursor: pointer;
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 0.95rem 1rem;
border: 0;
font: inherit;
font-weight: 600;
text-align: left;
color: #1f2937;
background: transparent;
}
.faq-toggle {
font-size: 1.2rem;
transition: transform 0.2s ease;
}
.faq-toggle.open {
transform: rotate(45deg);
}
.faq-answer {
margin: 0;
padding: 0 1rem 1rem;
line-height: 1.6;
}
.cta {
text-align: center;
background: linear-gradient(180deg, #fff 0%, #f8faff 100%);
}
.cta p {
max-width: 62ch;
margin: 0 auto;
}
.cta-actions {
justify-content: center;
}
@media (width <= 980px) {
.hero {
grid-template-columns: 1fr;
}
.features-grid,
.plans-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (width <= 640px) {
padding: 1rem 0.75rem 2rem;
gap: 1rem;
.hero-content,
.hero-card,
.features,
.scenarios,
.plans,
.faq,
.cta {
padding: 1rem;
border-radius: 12px;
}
.features-grid,
.plans-grid,
.estimate {
grid-template-columns: 1fr;
}
}
}
_____________________________________landing page spec ts_______________________________________________________________
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { LandingPage } from './landing-page';
describe('LandingPage', () => {
let component: LandingPage;
let fixture: ComponentFixture<LandingPage>;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [LandingPage],
}).compileComponents();
fixture = TestBed.createComponent(LandingPage);
component = fixture.componentInstance;
await fixture.whenStable();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
______________________________spec ts шляпа________________________________________
import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { RouterLink } from '@angular/router';
type FaqItem = {
question: string;
answer: string;
opened?: boolean;
};
@Component({
selector: 'lib-landing-page',
imports: [CommonModule, RouterLink],
templateUrl: './landing-page.html',
styleUrl: './landing-page.scss',
})
export class LandingPage {
readonly features = [
{
title: 'Проверка документов онлайн',
description: 'Система подсказывает, каких данных не хватает, и помогает собрать полный пакет без лишних визитов.',
icon: '✓',
},
{
title: 'Прозрачные статусы заявки',
description: 'Каждый этап виден в личном кабинете: от подачи до готового отчета и оплаты.',
icon: '◉',
},
{
title: 'Поддержка специалистов',
description: 'Гость может получить консультацию до регистрации и быстро перейти к оформлению заявки.',
icon: '∞',
},
];
readonly scenarios = [
'Наследство: оценка квартиры, дома, участка, гаража',
'Подготовка к нотариальным действиям и подаче документов',
'Получение копий нотариальных документов с трекингом статуса',
'Быстрый переход в личный кабинет после регистрации',
];
readonly plans = [
{
name: 'Базовый',
price: '0 ₽',
description: 'Для знакомства с сервисом',
benefits: ['Просмотр справочного раздела', 'Калькулятор стоимости', 'Запрос консультации'],
highlighted: false,
},
{
name: 'Старт',
price: 'от 1 990 ₽',
description: 'Для первой оценки наследственного имущества',
benefits: ['Подача заявки', 'Загрузка документов', 'Отслеживание статусов'],
highlighted: true,
},
{
name: 'Профи',
price: 'Индивидуально',
description: 'Для регулярной работы с оценкой и документами',
benefits: ['Приоритетная поддержка', 'Расширенные уведомления', 'История и выгрузка операций'],
highlighted: false,
},
];
readonly faq: FaqItem[] = [
{
question: 'Можно ли пользоваться сервисом без регистрации?',
answer:
'Да, гость может изучить возможности платформы, просчитать примерные сроки и стоимость, а также отправить запрос на консультацию.',
opened: true,
},
{
question: 'Сколько занимает подготовка отчета по оценке?',
answer:
'Срок зависит от типа имущества и полноты документов. В среднем подготовка занимает от 1 до 3 рабочих дней.',
},
{
question: 'Как проходит оплата?',
answer:
'После создания заявки система покажет доступные тарифы и шаги оплаты. История платежей сохраняется в личном кабинете.',
},
];
estimatedDays = 2;
estimatedPrice = 3490;
selectedObjectType = 'Квартира';
actionMessage = '';
onRequestDemo(): void {
this.actionMessage = 'Демо-запрос отправлен. Это учебный stub: здесь будет интеграция с формой.';
}
onStartNow(): void {
this.actionMessage = 'Переход к регистрации доступен по кнопке "Войти". Это интерактив-заглушка.';
}
toggleFaq(index: number): void {
this.faq[index].opened = !this.faq[index].opened;
}
calculateEstimate(objectType: string): void {
this.selectedObjectType = objectType;
if (objectType === 'Квартира') {
this.estimatedDays = 2;
this.estimatedPrice = 3490;
return;
}
if (objectType === 'Дом') {
this.estimatedDays = 3;
this.estimatedPrice = 4990;
return;
}
this.estimatedDays = 4;
this.estimatedPrice = 6290;
}
}