Загрузка данных


<div class="landing">
  <header class="header">
    <button class="brand" type="button" (click)="scrollToSection('top')" aria-label="В начало">
      EstateValue
    </button>
    <button class="menu-toggle" type="button" (click)="toggleMobileMenu()" aria-label="Открыть меню">
      <span></span>
      <span></span>
      <span></span>
    </button>
    <nav class="nav" [class.nav_open]="isMobileMenuOpen" aria-label="Навигация по секциям">
      <button type="button" (click)="scrollToSection('benefits')">Преимущества</button>
      <button type="button" (click)="scrollToSection('pricing')">Тарифы</button>
      <button type="button" (click)="scrollToSection('lead-form')">Заявка</button>
      <button class="button button_secondary" type="button" (click)="scrollToSection('lead-form')">
        Оставить заявку
      </button>
    </nav>
  </header>

  <main id="top">
    <section class="hero">
      <div class="hero__content">
        <p class="hero__badge">Роль: Гость</p>
        <h1>Оценка имущества без визита в офис</h1>
        <p class="hero__description">
          Оставьте заявку, выберите тариф и получите консультацию в тот же день.
        </p>
        <div class="hero__chips">
          <span>До 30 мин на ответ</span>
          <span>Прозрачные тарифы</span>
          <span>Поддержка 7 дней</span>
        </div>
        <div class="hero__actions">
          <button class="button" type="button" (click)="scrollToSection('lead-form')">Начать сейчас</button>
          <button class="button button_secondary" type="button" (click)="scrollToSection('pricing')">
            Смотреть тарифы
          </button>
        </div>
      </div>
      <aside class="hero__card" aria-label="Ключевые показатели">
        <p class="hero__card-title">Коротко о главном</p>
        <ul>
          <li>Ответ менеджера до 30 минут</li>
          <li>Прозрачная цена без скрытых платежей</li>
          <li>Шаблоны под нотариальные требования</li>
        </ul>
      </aside>
    </section>

    <section id="benefits" class="section">
      <h2>Преимущества сервиса</h2>
      <div class="grid grid_3">
        @for (benefit of benefits; track benefit.title) {
          <article class="card">
            <h3>{{ benefit.title }}</h3>
            <p>{{ benefit.description }}</p>
          </article>
        }
      </div>
    </section>

    <section class="section section_muted">
      <h2>Как это работает</h2>
      <div class="steps">
        @for (step of steps; track step.title; let idx = $index) {
          <article class="step">
            <span class="step__index">0{{ idx + 1 }}</span>
            <h3>{{ step.title }}</h3>
            <p>{{ step.description }}</p>
          </article>
        }
      </div>
    </section>

    <section id="pricing" class="section">
      <div class="section__head">
        <h2>Тарифы</h2>
        <p>Выбранный тариф: <strong>{{ selectedPlanTitle }}</strong></p>
      </div>
      <div class="grid grid_3">
        @for (plan of plans; track plan.title) {
          <article class="card card_pricing" [class.card_active]="selectedPlanTitle === plan.title">
            <h3>{{ plan.title }}</h3>
            <p class="price">{{ plan.price }} ₽</p>
            <p class="period">{{ plan.period }}</p>
            <ul>
              @for (feature of plan.features; track feature) {
                <li>{{ feature }}</li>
              }
            </ul>
            <button class="button" type="button" (click)="selectPlan(plan.title)">{{ plan.cta }}</button>
          </article>
        }
      </div>
    </section>

    <section id="lead-form" class="section section_muted">
      <h2>Оставьте заявку</h2>
      <p>Заполнив форму, вы получите звонок и персональный расчет.</p>
      <form class="lead-form" #leadFormRef>
        <label>
          Имя
          <input #nameInput type="text" name="name" placeholder="Ваше имя" />
        </label>
        <label>
          Телефон
          <input #phoneInput type="tel" name="phone" placeholder="+7 (___) ___-__-__" />
        </label>
        <button class="button" type="button" (click)="submitLead(nameInput.value, phoneInput.value)">
          Отправить заявку
        </button>
      </form>
      @if (leadStatus) {
        <p class="status" role="status">{{ leadStatus }}</p>
      }
    </section>

    <section id="faq" class="section">
      <h2>FAQ</h2>
      <div class="faq">
        @for (item of faq; track item.question; let idx = $index) {
          <article class="faq__item">
            <button class="faq__question" type="button" (click)="toggleFaq(idx)">
              <span>{{ item.question }}</span>
              <span class="faq__icon">{{ isFaqOpen(idx) ? '−' : '+' }}</span>
            </button>
            @if (isFaqOpen(idx)) {
              <p class="faq__answer">{{ item.answer }}</p>
            }
          </article>
        }
      </div>
    </section>
  </main>
</div>


___________________________________CSS__________________________________

:host {
  display: block;
  background:
    radial-gradient(circle at 0% 0%, #eaf0ff 0%, transparent 38%),
    radial-gradient(circle at 100% 10%, #efe7ff 0%, transparent 34%),
    linear-gradient(180deg, #f7f9ff 0%, #ffffff 360px);
  color: #17213a;
  font-family: Inter, Arial, sans-serif;
}

*,
*::before,
*::after {
  box-sizing: border-box;
}

.landing {
  max-width: 1080px;
  margin: 0 auto;
  padding: 18px 20px 46px;
}

.header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 8px 0 16px;
}

.brand {
  border: 0;
  background: transparent;
  color: #1a2854;
  font-size: 1.2rem;
  font-weight: 800;
  letter-spacing: 0.01em;
  cursor: pointer;
}

.menu-toggle {
  display: none;
  border: 0;
  background: transparent;
  cursor: pointer;
  padding: 6px;
}

.menu-toggle span {
  display: block;
  width: 22px;
  height: 2px;
  background: #18223a;
  margin: 4px 0;
}

.nav {
  display: flex;
  align-items: center;
  gap: 14px;
}

.nav button {
  border: 0;
  background: transparent;
  color: #2c3b66;
  font: inherit;
  font-weight: 500;
  cursor: pointer;
  border-radius: 10px;
  padding: 8px 10px;
  transition: background 0.2s ease;
}

.nav button:hover {
  background: #e9efff;
}

.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid #3f61ff;
  border-radius: 10px;
  background: linear-gradient(135deg, #4a6bff 0%, #3654de 100%);
  color: #fff;
  text-decoration: none;
  padding: 10px 16px;
  font-weight: 600;
  cursor: pointer;
  transition: 0.2s ease;
}

.button:hover {
  transform: translateY(-1px);
  box-shadow: 0 10px 18px rgb(53 84 222 / 24%);
}

.button_secondary {
  background: #fff;
  color: #3454d6;
  border-color: #d3ddff;
}

.button_secondary:hover {
  background: #f3f6ff;
  box-shadow: none;
}

.hero {
  display: grid;
  grid-template-columns: 1.2fr 0.8fr;
  gap: 18px;
  align-items: stretch;
  margin-bottom: 10px;
}

.hero__content,
.hero__card {
  background: #ffffff;
  border: 1px solid #e0e8ff;
  border-radius: 18px;
  padding: 22px;
  box-shadow: 0 14px 36px rgb(33 56 130 / 9%);
}

.hero__badge {
  display: inline-block;
  font-size: 0.85rem;
  margin: 0 0 10px;
  color: #2c4bc7;
  background: #ecf1ff;
  padding: 4px 10px;
  border-radius: 999px;
}

h1 {
  margin: 0;
  font-size: clamp(1.8rem, 2.8vw, 2.8rem);
  line-height: 1.18;
}

.hero__description {
  margin: 14px 0 0;
  line-height: 1.6;
  color: #455987;
}

.hero__chips {
  margin-top: 14px;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.hero__chips span {
  border: 1px solid #dce5ff;
  border-radius: 999px;
  padding: 6px 10px;
  font-size: 0.84rem;
  color: #3550b3;
  background: #f6f9ff;
}

.hero__actions {
  margin-top: 18px;
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.hero__card-title {
  margin: 0 0 12px;
  font-weight: 800;
  color: #21325f;
}

.hero__card ul {
  margin: 0;
  padding-left: 18px;
  display: grid;
  gap: 10px;
}

.section {
  margin-top: 24px;
  scroll-margin-top: 18px;
}

.section h2 {
  margin: 0 0 16px;
  font-size: clamp(1.4rem, 2vw, 2rem);
}

.section_muted {
  background: linear-gradient(180deg, #f9fbff 0%, #f4f8ff 100%);
  border-radius: 16px;
  border: 1px solid #e8efff;
  padding: 20px;
}

.section__head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
}

.grid {
  display: grid;
  gap: 14px;
}

.grid_3 {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.card {
  background: #ffffff;
  border: 1px solid #e1e9ff;
  border-radius: 14px;
  padding: 16px;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.card:hover,
.step:hover {
  transform: translateY(-3px);
  box-shadow: 0 12px 22px rgb(54 79 157 / 12%);
}

.card h3 {
  margin: 0 0 10px;
}

.card p {
  margin: 0;
  line-height: 1.5;
  color: #455883;
}

.steps {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
}

.step {
  background: #ffffff;
  border: 1px solid #e2eaff;
  border-radius: 14px;
  padding: 16px;
  transition: transform 0.2s ease, box-shadow 0.2s ease;
}

.step__index {
  color: #3a5bd0;
  font-weight: 700;
}

.step h3 {
  margin: 10px 0 8px;
}

.step p {
  margin: 0;
  color: #475c89;
}

.card_pricing ul {
  margin: 0 0 14px;
  padding-left: 18px;
  color: #3f5180;
}

.price {
  margin: 0;
  font-size: 1.9rem;
  font-weight: 700;
  color: #13254f;
}

.period {
  margin: 4px 0 12px;
  color: #4f6493;
}

.card_active {
  border-color: #4162f5;
  box-shadow: 0 0 0 2px #e3eaff;
}

.lead-form {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
  align-items: end;
}

.lead-form label {
  display: grid;
  gap: 6px;
  font-weight: 600;
  color: #2e406b;
}

.lead-form input {
  border: 1px solid #cad7ff;
  border-radius: 10px;
  padding: 11px 12px;
  font: inherit;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}

.lead-form input:focus {
  outline: none;
  border-color: #4c6cff;
  box-shadow: 0 0 0 3px rgb(76 108 255 / 18%);
}

.status {
  margin: 12px 0 0;
  color: #2447bb;
}

.faq {
  display: grid;
  gap: 10px;
}

.faq__item {
  border: 1px solid #dde7ff;
  border-radius: 14px;
  background: #fff;
  overflow: hidden;
}

.faq__question {
  width: 100%;
  background: #fff;
  border: 0;
  padding: 14px 16px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  text-align: left;
  font: inherit;
  font-weight: 600;
  color: #253968;
  cursor: pointer;
}

.faq__icon {
  width: 24px;
  height: 24px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: #eef3ff;
  color: #3454d6;
  font-size: 1.1rem;
}

.faq__answer {
  margin: 0;
  padding: 0 16px 14px;
  color: #516698;
  line-height: 1.55;
}

@media (max-width: 960px) {
  .hero {
    grid-template-columns: 1fr;
  }

  .grid_3,
  .steps {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }

  .lead-form {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 760px) {
  .landing {
    padding: 14px 14px 28px;
  }

  .menu-toggle {
    display: block;
  }

  .nav {
    display: none;
    position: absolute;
    top: 62px;
    right: 14px;
    left: 14px;
    background: #ffffff;
    border-radius: 12px;
    padding: 10px;
    border: 1px solid #e4e9f9;
    box-shadow: 0 10px 24px rgb(23 38 81 / 10%);
    flex-direction: column;
    align-items: stretch;
    z-index: 2;
  }

  .nav_open {
    display: flex;
  }

  .section__head {
    flex-direction: column;
    align-items: flex-start;
  }

  .grid_3,
  .steps,
  .lead-form {
    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';

type Plan = {
  title: string;
  price: string;
  period: string;
  features: string[];
  cta: string;
};

type FaqItem = {
  question: string;
  answer: string;
};

@Component({
  selector: 'lib-landing-page',
  imports: [CommonModule],
  templateUrl: './landing-page.html',
  styleUrl: './landing-page.scss',
})
export class LandingPage {
  isMobileMenuOpen = false;
  selectedPlanTitle = 'Старт';
  leadStatus = '';

  readonly benefits = [
    {
      title: 'Оценка без визита',
      description:
        'Заполняйте заявку онлайн и получайте результат без личного присутствия в офисе.',
    },
    {
      title: 'Прозрачные сроки',
      description:
        'Фиксированные этапы выполнения с уведомлениями о каждом изменении статуса.',
    },
    {
      title: 'Юридическая точность',
      description:
        'Шаблоны и проверки помогают подготовить документы к нотариальному процессу.',
    },
  ];

  readonly steps = [
    {
      title: 'Заявка за 1 минуту',
      description: 'Оставьте контакты и получите обратную связь.',
    },
    {
      title: 'Проверка документов',
      description: 'Подскажем, чего не хватает для быстрого старта.',
    },
    {
      title: 'Готовый отчет',
      description: 'Запускаем оценку и отправляем итог в личный кабинет.',
    },
  ];

  readonly plans: Plan[] = [
    {
      title: 'Старт',
      price: '2 900',
      period: 'за заявку',
      cta: 'Выбрать Старт',
      features: ['Проверка документов', 'Подсказки по заполнению', 'Email-уведомления'],
    },
    {
      title: 'Стандарт',
      price: '4 900',
      period: 'за заявку',
      cta: 'Выбрать Стандарт',
      features: ['Все из Старт', 'Приоритетная обработка', 'Поддержка в чате'],
    },
    {
      title: 'Эксперт',
      price: '7 900',
      period: 'за заявку',
      cta: 'Выбрать Эксперт',
      features: ['Все из Стандарт', 'Персональный консультант', 'Расширенный итоговый отчет'],
    },
  ];
  readonly faq: FaqItem[] = [
    {
      question: 'Можно ли начать как гость без регистрации?',
      answer:
        'Да, вы можете оставить заявку и получить консультацию. Для статусов и документов менеджер поможет завершить регистрацию.',
    },
    {
      question: 'Сколько занимает оценка?',
      answer:
        'В среднем 1-3 рабочих дня. Срок зависит от полноты документов и выбранного тарифа.',
    },
    {
      question: 'Есть ли скрытые платежи?',
      answer: 'Нет, цена фиксируется выбранным тарифом. Дополнительные услуги согласовываются заранее.',
    },
  ];
  readonly openedFaqIndexes = new Set<number>([0]);

  toggleMobileMenu(): void {
    this.isMobileMenuOpen = !this.isMobileMenuOpen;
  }

  scrollToSection(sectionId: string): void {
    const section = globalThis.document?.getElementById(sectionId);
    if (!section) {
      return;
    }

    section.scrollIntoView({ behavior: 'smooth', block: 'start' });
    this.isMobileMenuOpen = false;
  }

  selectPlan(planTitle: string): void {
    this.selectedPlanTitle = planTitle;
    this.leadStatus = `Выбран тариф: ${planTitle}. Это stub-действие для будущей интеграции оплаты.`;
  }

  toggleFaq(index: number): void {
    if (this.openedFaqIndexes.has(index)) {
      this.openedFaqIndexes.delete(index);
      return;
    }
    this.openedFaqIndexes.add(index);
  }

  isFaqOpen(index: number): boolean {
    return this.openedFaqIndexes.has(index);
  }

  submitLead(name: string, phone: string): void {
    if (!name.trim() || !phone.trim()) {
      this.leadStatus = 'Заполните имя и телефон, чтобы отправить заявку.';
      return;
    }

    this.leadStatus = `Спасибо, ${name}! Заявка принята. Мы перезвоним по номеру ${phone}. (stub)`;
  }
}