черновая шапка и первый скрол с лого завершено
This commit is contained in:
+90
-7
@@ -8,6 +8,13 @@ declare(strict_types=1);
|
||||
/** @var string $metaKeywords */
|
||||
/** @var array<int, array{title: string, url?: string}> $breadcrumbs */
|
||||
/** @var bool $showBackButton */
|
||||
$megaCategories = [];
|
||||
|
||||
try {
|
||||
$megaCategories = (new \App\Repositories\CategoryRepository())->tree();
|
||||
} catch (\Throwable) {
|
||||
$megaCategories = [];
|
||||
}
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="ru">
|
||||
@@ -27,12 +34,12 @@ declare(strict_types=1);
|
||||
<header class="site-topbar">
|
||||
<div class="site-topbar-inner">
|
||||
<a class="brand-link" href="/">
|
||||
<?php if (is_file(base_path('public/assets/images/logo_300.png'))): ?>
|
||||
<img src="/assets/images/logo_300.png" alt="Рыбсток">
|
||||
<?php if (is_file(base_path('public/assets/images/logo-rybstock.svg'))): ?>
|
||||
<img src="/assets/images/logo-rybstock.svg" alt="Рыбсток">
|
||||
<?php else: ?>
|
||||
<span>Рыбсток</span>
|
||||
<span>РЫБ<span>СТОК</span></span>
|
||||
<small>Качественные продукты по стоковым ценам</small>
|
||||
<?php endif; ?>
|
||||
<small>Качественные продукты по стоковым ценам</small>
|
||||
</a>
|
||||
<nav class="main-nav" aria-label="Главное меню">
|
||||
<a href="/">Главная</a>
|
||||
@@ -48,6 +55,22 @@ declare(strict_types=1);
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<section class="smart-search-bar" aria-label="Поиск по каталогу">
|
||||
<div class="smart-search-inner">
|
||||
<form class="smart-search-form" action="/catalog" method="get">
|
||||
<label class="smart-search-label" for="site-search">Умный поиск</label>
|
||||
<div class="smart-search-control">
|
||||
<input id="site-search" name="q" type="search" placeholder="Например: форель стейки, креветки 1 кг, красная рыба, что взять на ужин">
|
||||
<button class="voice-search-button" type="button" aria-label="Голосовой ввод" title="Голосовой ввод">Голос</button>
|
||||
<button class="smart-search-submit" type="submit">Найти</button>
|
||||
</div>
|
||||
<p>Понимает склонения, похожие запросы и бытовые формулировки. Обучаемый поиск подключим к товарам после загрузки каталога.</p>
|
||||
</form>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php require base_path('views/partials/mega-category-menu.php'); ?>
|
||||
|
||||
<header class="site-utility">
|
||||
<?php if ($showBackButton): ?>
|
||||
<button class="back-button" type="button" onclick="history.length > 1 ? history.back() : location.href='/'">Вернуться назад</button>
|
||||
@@ -64,13 +87,12 @@ declare(strict_types=1);
|
||||
<div class="site-footer-inner">
|
||||
<section class="footer-brand">
|
||||
<a class="footer-logo" href="/">
|
||||
<?php if (is_file(base_path('public/assets/images/logo_300.png'))): ?>
|
||||
<img src="/assets/images/logo_300.png" alt="Рыбсток">
|
||||
<?php if (is_file(base_path('public/assets/images/logo-rybstock.svg'))): ?>
|
||||
<img src="/assets/images/logo-rybstock.svg" alt="Рыбсток">
|
||||
<?php else: ?>
|
||||
<span>Рыбсток</span>
|
||||
<?php endif; ?>
|
||||
</a>
|
||||
<p>Качественные продукты по стоковым ценам</p>
|
||||
</section>
|
||||
|
||||
<section class="footer-column">
|
||||
@@ -113,5 +135,66 @@ declare(strict_types=1);
|
||||
<p>Информация на сайте носит справочный характер и не является публичной офертой. Наличие, фасовки, цены и условия доставки подтверждаются менеджером при обработке заказа.</p>
|
||||
</div>
|
||||
</footer>
|
||||
<script>
|
||||
(() => {
|
||||
const button = document.querySelector('.voice-search-button');
|
||||
const input = document.querySelector('#site-search');
|
||||
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
||||
|
||||
if (!button || !input) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!SpeechRecognition) {
|
||||
button.addEventListener('click', () => {
|
||||
input.focus();
|
||||
input.placeholder = 'Голосовой ввод не поддерживается этим браузером';
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const recognition = new SpeechRecognition();
|
||||
recognition.lang = 'ru-RU';
|
||||
recognition.interimResults = false;
|
||||
|
||||
button.addEventListener('click', () => {
|
||||
button.classList.add('is-listening');
|
||||
recognition.start();
|
||||
});
|
||||
|
||||
recognition.addEventListener('result', (event) => {
|
||||
const phrase = event.results[0]?.[0]?.transcript || '';
|
||||
input.value = phrase;
|
||||
input.focus();
|
||||
});
|
||||
|
||||
recognition.addEventListener('end', () => {
|
||||
button.classList.remove('is-listening');
|
||||
});
|
||||
})();
|
||||
(() => {
|
||||
const nodes = document.querySelectorAll('[data-temperature]');
|
||||
|
||||
if (nodes.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
||||
|
||||
nodes.forEach((node) => {
|
||||
let current = Number(node.dataset.temperature || node.textContent || 0);
|
||||
const min = Number(node.dataset.min || current - 1);
|
||||
const max = Number(node.dataset.max || current + 1);
|
||||
|
||||
const update = () => {
|
||||
const drift = (Math.random() - 0.5) * 0.28;
|
||||
current = clamp(current + drift, min, max);
|
||||
node.textContent = current.toFixed(1);
|
||||
};
|
||||
|
||||
window.setInterval(update, 4200 + Math.random() * 2400);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+88
-10
@@ -9,38 +9,115 @@ declare(strict_types=1);
|
||||
/** @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-hero">
|
||||
<p class="eyebrow">Интернет-склад качественной продукции</p>
|
||||
<h1>Рыбсток</h1>
|
||||
<p class="hero-lead">Рыба, морепродукты, мясо, сыры, овощи, ягоды и бакалея по стоковым ценам.</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 class="hero-copy">
|
||||
<p class="eyebrow">Интернет-склад качественной продукции</p>
|
||||
<h1>Рыбсток</h1>
|
||||
<p class="hero-lead">Рыба, морепродукты, мясо, сыры, овощи, ягоды и бакалея по стоковым ценам.</p>
|
||||
<div class="hero-points" aria-label="Ключевые условия">
|
||||
<span>Доставка день в день до 14:00</span>
|
||||
<span>Без предоплаты</span>
|
||||
<span>Самовывоз после подтверждения</span>
|
||||
</div>
|
||||
<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="hero-media">
|
||||
<img src="/assets/images/brand-hero-products.svg" alt="Ассортимент Рыбсток">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section 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>
|
||||
</section>
|
||||
|
||||
<section class="home-banners" aria-label="Преимущества Рыбсток">
|
||||
<div class="home-banner">
|
||||
<div class="home-banner delivery-image">
|
||||
<strong>Бесплатная доставка внутри МКАД от 5000 руб.</strong>
|
||||
<span>Привезем заказ после подтверждения по телефону.</span>
|
||||
</div>
|
||||
<div class="home-banner">
|
||||
<div class="home-banner region-image">
|
||||
<strong>Бесплатная доставка за МКАД от 10000 руб.</strong>
|
||||
<span>Для адресов до 10 км от МКАД, дальше расчет по расстоянию.</span>
|
||||
</div>
|
||||
<div class="home-banner">
|
||||
<div class="home-banner day-image">
|
||||
<strong>Можно приобрести день в день</strong>
|
||||
<span>Привезем заказ день в день, если он оформлен в будний день до 14:00.</span>
|
||||
</div>
|
||||
<div class="home-banner">
|
||||
<div class="home-banner payment-image">
|
||||
<strong>Без предоплаты</strong>
|
||||
<span>Оплата после подтверждения заказа и получения товара.</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="promo-strip" aria-label="Промокоды и цены">
|
||||
<div>
|
||||
<span>Промокод</span>
|
||||
<strong>Рыбсток7000</strong>
|
||||
<small>300 руб. скидка на заказы от 7 000 руб.</small>
|
||||
</div>
|
||||
<div>
|
||||
<span>Промокод</span>
|
||||
<strong>Рыбсток20000</strong>
|
||||
@@ -59,6 +136,7 @@ declare(strict_types=1);
|
||||
</section>
|
||||
|
||||
<section class="price-request-panel" id="price-request">
|
||||
<div class="price-request-image" aria-hidden="true"></div>
|
||||
<div>
|
||||
<p class="eyebrow">Запрос проходных цен</p>
|
||||
<h2>Пришлите список позиций, а мы сверим цены</h2>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/** @var array<int, array<string, mixed>> $megaCategories */
|
||||
|
||||
if (!function_exists('render_mega_category_children')) {
|
||||
/**
|
||||
* @param array<int, array<string, mixed>> $children
|
||||
*/
|
||||
function render_mega_category_children(array $children, int $depth = 1): void
|
||||
{
|
||||
if ($children === [] || $depth > 3) {
|
||||
return;
|
||||
}
|
||||
?>
|
||||
<ul class="mega-submenu depth-<?= e((string) $depth) ?>">
|
||||
<?php foreach ($children as $child): ?>
|
||||
<li>
|
||||
<a href="/catalog/<?= e((string) $child['slug']) ?>"><?= e((string) $child['name']) ?></a>
|
||||
<?php render_mega_category_children($child['children'] ?? [], $depth + 1); ?>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?php
|
||||
}
|
||||
}
|
||||
?>
|
||||
<?php if ($megaCategories !== []): ?>
|
||||
<nav class="category-mega-bar" aria-label="Категории товаров">
|
||||
<div class="category-mega-inner">
|
||||
<?php foreach ($megaCategories as $category): ?>
|
||||
<div class="mega-item">
|
||||
<a class="mega-root" href="/catalog/<?= e((string) $category['slug']) ?>"><?= e((string) $category['name']) ?></a>
|
||||
<?php render_mega_category_children($category['children'] ?? []); ?>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</nav>
|
||||
<?php endif; ?>
|
||||
Reference in New Issue
Block a user