371 lines
19 KiB
PHP
371 lines
19 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
/** @var string $dbStatus */
|
|
/** @var array<int, array<string, mixed>> $categories */
|
|
/** @var array<int, array<string, mixed>> $catalogProducts */
|
|
/** @var array<int, array<string, mixed>> $popularProducts */
|
|
/** @var array<int, array<string, mixed>> $homeCatalogSections */
|
|
/** @var array<string, mixed> $settings */
|
|
/** @var string|null $notice */
|
|
/** @var string $buildVersion */
|
|
$timezone = new DateTimeZone('Europe/Moscow');
|
|
$now = new DateTimeImmutable('now', $timezone);
|
|
$lastArrival = new DateTimeImmutable('today', $timezone);
|
|
|
|
$previousWorkingDay = static function (DateTimeImmutable $date): DateTimeImmutable {
|
|
do {
|
|
$date = $date->modify('-1 day');
|
|
} while ((int) $date->format('N') >= 6);
|
|
|
|
return $date;
|
|
};
|
|
|
|
while ((int) $lastArrival->format('N') >= 6) {
|
|
$lastArrival = $previousWorkingDay($lastArrival);
|
|
}
|
|
|
|
$arrivalSeed = (int) sprintf('%u', crc32($lastArrival->format('Y-m-d') . '|rybstock-arrival'));
|
|
$arrivalSecond = (4 * 3600) + ($arrivalSeed % (3 * 3600 + 1));
|
|
$arrivalRefreshAt = $lastArrival->setTime(
|
|
intdiv($arrivalSecond, 3600),
|
|
intdiv($arrivalSecond % 3600, 60),
|
|
$arrivalSecond % 60
|
|
);
|
|
|
|
if ($now < $arrivalRefreshAt) {
|
|
$lastArrival = $previousWorkingDay($lastArrival);
|
|
}
|
|
|
|
$months = [
|
|
1 => 'января',
|
|
2 => 'февраля',
|
|
3 => 'марта',
|
|
4 => 'апреля',
|
|
5 => 'мая',
|
|
6 => 'июня',
|
|
7 => 'июля',
|
|
8 => 'августа',
|
|
9 => 'сентября',
|
|
10 => 'октября',
|
|
11 => 'ноября',
|
|
12 => 'декабря',
|
|
];
|
|
$arrivalDate = $lastArrival->format('j') . ' ' . $months[(int) $lastArrival->format('n')] . ' ' . $lastArrival->format('Y');
|
|
?>
|
|
<section class="start-screen wide">
|
|
<section class="home-value-system" aria-label="Преимущества Рыбсток">
|
|
<div class="home-value-head">
|
|
<p class="eyebrow">Интернет-склад для дома и бизнеса</p>
|
|
<h1>Рыбсток</h1>
|
|
<p>Рыба, морепродукты, мясо, сыры, овощи, ягоды и бакалея по стоковым ценам для большого домашнего стола, дачи, магазина и кухни.</p>
|
|
<div class="hero-actions">
|
|
<a class="primary-button hero-button" href="/catalog">Смотреть каталог</a>
|
|
<a class="secondary-button" href="tel:+79777363363">+7-977-736-33-63</a>
|
|
</div>
|
|
</div>
|
|
<div class="home-benefit-grid">
|
|
<article class="home-benefit-card is-wide" style="--benefit-image: var(--home-hero-picnic)">
|
|
<div>
|
|
<span>Дом, дача, большой стол</span>
|
|
<h2>Запас продуктов без переплаты</h2>
|
|
<p>Берете коробками и фасовками со склада, а не с витрины. Удобно набрать рыбу, мясо, морепродукты и полуфабрикаты для семьи и гостей.</p>
|
|
</div>
|
|
</article>
|
|
<article class="home-benefit-card" style="--benefit-image: var(--home-warehouse-fish)">
|
|
<div>
|
|
<span>Выгода за наличные</span>
|
|
<h2>Корзина считает сама</h2>
|
|
<p>Минус 300 руб. от 7000 руб., -5% от 20000 руб. и -7% от 50000 руб. Покупателю не нужно считать вручную.</p>
|
|
</div>
|
|
</article>
|
|
<article class="home-benefit-card" style="--benefit-image: var(--home-banner-meat)">
|
|
<div>
|
|
<span>Доставка и оплата</span>
|
|
<h2>Без предоплаты</h2>
|
|
<p>Внутри МКАД бесплатно от 5000 руб., за МКАД - от 10000 руб. до 10 км. Заказ оплачивается только при получении.</p>
|
|
</div>
|
|
</article>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="audience-system" aria-label="Для кого работает Рыбсток">
|
|
<div class="audience-heading">
|
|
<p class="eyebrow">Для кого мы работаем</p>
|
|
<h2>Закрываем разные задачи одной поставкой</h2>
|
|
<p>От домашнего морозильного запаса до понятного прилавка и стабильной кухни: подбираем позиции под реальный спрос, бюджет и объем.</p>
|
|
</div>
|
|
<div class="audience-grid">
|
|
<article class="audience-card" style="--audience-image: var(--home-warehouse-cold)">
|
|
<span>Частным покупателям</span>
|
|
<h3>Наполнить морозильник и спокойно кормить семью</h3>
|
|
<p>Решаем главную боль: чем накормить домочадцев, гостей и дачу без ежедневных закупок и лишней наценки.</p>
|
|
</article>
|
|
<article class="audience-card" style="--audience-image: var(--home-warehouse-fish)">
|
|
<span>Магазинам</span>
|
|
<h3>Собрать ходовой рыбный прилавок</h3>
|
|
<p>Не люкс-витрина ради картинки, а понятные позиции, за которыми покупатель возвращается и встает в очередь.</p>
|
|
</article>
|
|
<article class="audience-card" style="--audience-image: var(--home-banner-seafood)">
|
|
<span>HoReCa</span>
|
|
<h3>Поддержать полную посадку кухни</h3>
|
|
<p>Рыба, мясо, морепродукты, полуфабрикаты и бакалея в одной закупке, чтобы меню не зависело от разрозненных поставок.</p>
|
|
</article>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="warehouse-showcase" aria-label="Склад Рыбсток">
|
|
<div class="warehouse-copy">
|
|
<p class="eyebrow">Склад и хранение</p>
|
|
<h2>Реальный склад, холодовая цепь и свежие поступления</h2>
|
|
<p>Мы показываем склад без лишней витринности: коробки, паллеты, камеры хранения и продукция, которая дальше уезжает к домашнему столу.</p>
|
|
</div>
|
|
<div class="storage-status" aria-label="Температура хранения и поступление товара">
|
|
<div class="arrival-card">
|
|
<span>Последнее поступление</span>
|
|
<strong><?= e((string) $arrivalDate) ?></strong>
|
|
<p>Свежие позиции принимаем по будням утром, после приемки и проверки качества.</p>
|
|
</div>
|
|
<div class="temperature-card freezer">
|
|
<span>Морозильная камера</span>
|
|
<strong><span data-temperature="-18.6" data-min="-19.4" data-max="-17.8">-18.6</span> °C</strong>
|
|
<p>Рабочий режим глубокой заморозки.</p>
|
|
</div>
|
|
<div class="temperature-card chiller">
|
|
<span>Холодильная камера</span>
|
|
<strong><span data-temperature="2.4" data-min="1.6" data-max="3.8">2.4</span> °C</strong>
|
|
<p>Температура для охлажденной продукции.</p>
|
|
</div>
|
|
</div>
|
|
<div class="warehouse-photo-grid">
|
|
<figure class="warehouse-photo-card large" style="--warehouse-image: var(--home-warehouse-main)">
|
|
<figcaption>
|
|
<span>Склад Рыбсток</span>
|
|
<strong>Паллеты, коробки и рабочий порядок</strong>
|
|
</figcaption>
|
|
</figure>
|
|
<figure class="warehouse-photo-card" style="--warehouse-image: var(--home-warehouse-arrival)">
|
|
<figcaption>
|
|
<span>Поступления</span>
|
|
<strong>Рыба и готовая продукция</strong>
|
|
</figcaption>
|
|
</figure>
|
|
<figure class="warehouse-photo-card" style="--warehouse-image: var(--home-warehouse-cold)">
|
|
<figcaption>
|
|
<span>Хранение</span>
|
|
<strong>Камеры и фасовки</strong>
|
|
</figcaption>
|
|
</figure>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="price-request-panel" id="price-request">
|
|
<div class="price-request-cover" aria-hidden="true">
|
|
<span>Для магазинов, кафе, ресторанов и закупщиков</span>
|
|
</div>
|
|
<div class="price-request-content">
|
|
<div class="price-request-copy">
|
|
<p class="eyebrow">Запрос проходных цен</p>
|
|
<h2>Для бизнеса</h2>
|
|
<p>Запросите у нас цену на продукцию. Просто напишите позиции, которые Вас интересуют, необходимый объем и цену, которая Вам нужна. Наш менеджер оптового отдела постарается помочь Вам.</p>
|
|
<div class="business-only-badge">Только для юридических лиц, ИП, HoReCa и закупщиков</div>
|
|
</div>
|
|
|
|
<?php if (($_GET['price_request'] ?? '') === 'sent'): ?>
|
|
<div class="status-box success">Спасибо, запрос принят. Менеджер оптового отдела свяжется с Вами после обработки заявки.</div>
|
|
<?php elseif (($_GET['price_request'] ?? '') === 'error'): ?>
|
|
<div class="status-box danger">Не удалось отправить запрос. Проверьте обязательные поля и попробуйте еще раз.</div>
|
|
<?php endif; ?>
|
|
|
|
<form class="price-request-form" action="/price-request" method="post" enctype="multipart/form-data">
|
|
<label>
|
|
<span>Название организации</span>
|
|
<input type="text" name="company_name" placeholder="Например: кафе, магазин, ИП или ООО" required>
|
|
</label>
|
|
|
|
<fieldset>
|
|
<legend>Форма оплаты</legend>
|
|
<label class="choice-pill">
|
|
<input type="radio" name="payment_type" value="cash" checked>
|
|
<span>Нал</span>
|
|
</label>
|
|
<label class="choice-pill">
|
|
<input type="radio" name="payment_type" value="invoice">
|
|
<span>Расчетный счет</span>
|
|
</label>
|
|
</fieldset>
|
|
|
|
<label>
|
|
<span>Телефон</span>
|
|
<input type="tel" name="phone" placeholder="+7 ___ ___-__-__">
|
|
</label>
|
|
|
|
<label>
|
|
<span>Email</span>
|
|
<input type="email" name="email" placeholder="mail@example.ru">
|
|
</label>
|
|
|
|
<label>
|
|
<span>Тип деятельности</span>
|
|
<input type="text" name="activity_type" placeholder="Ресторан, магазин, производство, кейтеринг">
|
|
</label>
|
|
|
|
<label class="wide">
|
|
<span>Позиции, объем и нужная цена</span>
|
|
<textarea name="request_text" rows="6" placeholder="Например: форель 10 кг до 1200 руб./кг, креветки 5 кг до 900 руб./кг" required></textarea>
|
|
</label>
|
|
|
|
<label class="file-field wide">
|
|
<span>Прикрепить файлы</span>
|
|
<input type="file" name="request_files[]" multiple>
|
|
<small>Можно приложить список, прайс, таблицу или фото заявки.</small>
|
|
</label>
|
|
|
|
<button class="primary-button wide" type="submit">Отправить запрос</button>
|
|
</form>
|
|
</div>
|
|
</section>
|
|
|
|
<?php if ($notice !== null): ?>
|
|
<div class="status-box danger"><?= e($notice) ?></div>
|
|
<?php endif; ?>
|
|
|
|
<section class="home-catalog-showcase" aria-label="Витрина каталога Рыбсток">
|
|
<div class="home-catalog-heading">
|
|
<div>
|
|
<p class="eyebrow">Каталог продукции</p>
|
|
<h2>Акции и новые поступления</h2>
|
|
</div>
|
|
<a class="home-catalog-main-link" href="/catalog">Перейти во весь каталог</a>
|
|
</div>
|
|
|
|
<?php if (($homeCatalogSections ?? []) === []): ?>
|
|
<div class="panel">
|
|
<p class="muted">После наполнения каталога товары появятся здесь по категориям.</p>
|
|
</div>
|
|
<?php else: ?>
|
|
<?php foreach ($homeCatalogSections as $section): ?>
|
|
<?php
|
|
$sectionProducts = $section['products'] ?? [];
|
|
if ($sectionProducts === []) {
|
|
continue;
|
|
}
|
|
$sectionKind = (string) ($section['kind'] ?? 'category');
|
|
?>
|
|
<section class="home-product-section is-<?= e($sectionKind) ?>" id="home-<?= e((string) ($section['key'] ?? 'section')) ?>">
|
|
<div class="home-product-section-head">
|
|
<h3><?= e((string) ($section['title'] ?? 'Категория')) ?></h3>
|
|
<div class="home-product-section-actions">
|
|
<?php if (!empty($section['url'])): ?>
|
|
<a href="<?= e((string) $section['url']) ?>">Показать <?= e((string) ($section['count'] ?? count($sectionProducts))) ?> товаров</a>
|
|
<?php endif; ?>
|
|
<div class="home-strip-controls" aria-label="Прокрутка подборки">
|
|
<button type="button" data-home-strip-prev aria-label="Назад">←</button>
|
|
<button type="button" data-home-strip-next aria-label="Вперед">→</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="home-products-strip" aria-label="<?= e((string) ($section['title'] ?? 'Категория')) ?>">
|
|
<?php foreach ($sectionProducts as $product): ?>
|
|
<?php require base_path('views/partials/product-card.php'); ?>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</section>
|
|
<?php endforeach; ?>
|
|
<?php endif; ?>
|
|
</section>
|
|
|
|
<script>
|
|
document.querySelectorAll('[data-home-strip-prev], [data-home-strip-next]').forEach((button) => {
|
|
button.addEventListener('click', () => {
|
|
const section = button.closest('.home-product-section');
|
|
const strip = section ? section.querySelector('.home-products-strip') : null;
|
|
|
|
if (!strip) {
|
|
return;
|
|
}
|
|
|
|
const direction = button.hasAttribute('data-home-strip-prev') ? -1 : 1;
|
|
strip.scrollBy({
|
|
left: direction * Math.max(260, strip.clientWidth * 0.86),
|
|
behavior: 'smooth',
|
|
});
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<?php if (false): ?>
|
|
<div class="content-grid">
|
|
<section class="panel">
|
|
<h2>Каталог</h2>
|
|
<?php if ($categories === []): ?>
|
|
<p class="muted">Категории пока не найдены. Если схема уже применена, проверьте `database/seed.sql`.</p>
|
|
<?php else: ?>
|
|
<?php
|
|
$items = $categories;
|
|
$depth = 0;
|
|
$currentSlug = '';
|
|
require base_path('views/partials/category-tree.php');
|
|
?>
|
|
<?php endif; ?>
|
|
</section>
|
|
|
|
<section class="panel">
|
|
<h2>Доставка</h2>
|
|
<dl class="settings-list">
|
|
<div>
|
|
<dt>Бесплатно внутри МКАД от</dt>
|
|
<dd><?= e((string) ($settings['inside_mkad_free_from'] ?? '5000')) ?> руб.</dd>
|
|
</div>
|
|
<div>
|
|
<dt>Доставка внутри МКАД</dt>
|
|
<dd><?= e((string) ($settings['inside_mkad_paid_price'] ?? '599')) ?> руб.</dd>
|
|
</div>
|
|
<div>
|
|
<dt>За МКАД</dt>
|
|
<dd><?= e((string) ($settings['outside_mkad_base_price'] ?? '199')) ?> руб. + <?= e((string) ($settings['outside_mkad_km_price'] ?? '60')) ?> руб./км</dd>
|
|
</div>
|
|
<div>
|
|
<dt>Подъем</dt>
|
|
<dd><?= e((string) ($settings['lift_price'] ?? '390')) ?> руб.</dd>
|
|
</div>
|
|
</dl>
|
|
</section>
|
|
</div>
|
|
|
|
<section class="panel products-panel">
|
|
<div class="section-heading">
|
|
<div>
|
|
<p class="eyebrow">Каталог продукции</p>
|
|
<h2>Товары на главной</h2>
|
|
</div>
|
|
<a class="subtle-link" href="/catalog">Весь каталог</a>
|
|
</div>
|
|
|
|
<?php if ($catalogProducts === []): ?>
|
|
<p class="muted">После наполнения базы товары будут отображаться здесь сразу на главной странице.</p>
|
|
<?php else: ?>
|
|
<div class="product-grid">
|
|
<?php foreach ($catalogProducts as $product): ?>
|
|
<?php require base_path('views/partials/product-card.php'); ?>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
</section>
|
|
|
|
<section class="panel products-panel">
|
|
<h2>Популярные товары</h2>
|
|
<?php if ($popularProducts === []): ?>
|
|
<p class="muted">Товары появятся здесь после наполнения каталога и публикации фасовок.</p>
|
|
<?php else: ?>
|
|
<div class="product-grid">
|
|
<?php foreach ($popularProducts as $product): ?>
|
|
<?php require base_path('views/partials/product-card.php'); ?>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
</section>
|
|
<?php endif; ?>
|
|
</section>
|