Списки: каталог, проекты и главная
«Каталог» сам соберёт карточки кухонь с ценами, «Проекты» покажут работы по пять на странице, а главная станет витриной с кухнями и свежими проектами.
Карточки кухонь
Кухни лежат внутри «Каталога». Значит, шаблону страницы «Каталог» нужно взять её дочерние страницы и нарисовать по карточке на каждую. Создайте шаблон Catalog:
<script>
import Content from '$core/Content';
import Layout from '$components/Layout';
import { children, image, link } from '$core';
let { page } = $props();
const kitchens = children(page.id);
const rub = new Intl.NumberFormat('ru');
</script>
<Layout {page}>
<h1>{page.title}</h1>
<Content of={page} />
<ul class="cards">
{#each kitchens as kitchen (kitchen.id)}
{@const photo = kitchen.fields.photo ? image(kitchen.fields.photo) : null}
<li>
<a href={link(kitchen.id)}>
{#if photo}
<img src={photo.src} width={photo.width} height={photo.height} alt="" />
{/if}
<span class="name">{kitchen.title}</span>
<span class="meta">
{kitchen.fields.plan} · от {rub.format(kitchen.fields.price)} ₽ за метр
</span>
</a>
</li>
{/each}
</ul>
</Layout>
<style>
.cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
gap: 1rem;
padding: 0;
list-style: none;
}
.cards a {
display: grid;
gap: 0.35rem;
padding: 0.75rem 0.75rem 1rem;
border-radius: 1.25rem;
background: #fff;
color: inherit;
text-decoration: none;
}
.cards img {
aspect-ratio: 4 / 3;
object-fit: cover;
border-radius: 0.9rem;
}
.name {
font-weight: 700;
}
.meta {
color: #6b6862;
font-size: 0.9rem;
}
</style>children(page.id): дочерние страницы текущей, в порядке дерева. В пятой главе мы звалиchildren(null)для верхнего уровня, здесь та же функция.kitchen.fields.price: у каждой страницы из списка есть её поля, как уpageв шаблоне кухни.{@const photo = …}: заводит значение прямо внутри{#each}, своё для каждой кухни.aspect-ratio: 4 / 3иobject-fit: cover: все фото в карточках одной высоты, даже если снимки разных пропорций.grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr)): карточки встанут в столько колонок, сколько поместится. На телефоне одна, на ноутбуке три.
Откройте страницу «Каталог» в менеджере, выберите ей шаблон Catalog и сохраните. Порядок карточек тот же, что в дереве: перетащите кухни, чтобы поменять его.

Раздел проектов
Создайте на верхнем уровне страницу «Проекты» (адрес proekty), а внутри неё три работы. У каждой заполните поле Анонс: это одно-два предложения, которые попадут в список. Проектам оставьте шаблон Base.
Для пейджера, ссылок «Назад» и «Вперёд» между страницами списка, возьмём готовый компонент. Щёлкните в шапке значок Элементы, откройте вкладку Набор компонентов и нажмите Установить у компонента Pagination. Он появится среди компонентов сайта, и его можно будет править, как свой.
Теперь шаблон Projects:
<script>
import Layout from '$components/Layout';
import Pagination from '$components/Pagination';
import { link, listing } from '$core';
let { page } = $props();
const projects = listing(page.id, { perPage: 5 });
const date = new Intl.DateTimeFormat('ru', {
dateStyle: 'long',
timeZone: 'UTC',
});
</script>
<Layout {page}>
<h1>{page.title}</h1>
{#each projects.items as project (project.id)}
<article class="project">
<h2><a href={link(project.id)}>{project.title}</a></h2>
<p class="date">{date.format(new Date(project.publishedAt))}</p>
{#if project.introText}<p>{project.introText}</p>{/if}
</article>
{:else}
<p>Проектов пока нет.</p>
{/each}
<Pagination
items={projects.pager()}
prev={projects.url(projects.number - 1)}
next={projects.url(projects.number + 1)}
/>
</Layout>
<style>
.project {
padding: 1.25rem 0;
border-bottom: 1px solid #e2dfd9;
}
.project h2 {
margin: 0;
font-size: 1.375rem;
}
.project a {
color: inherit;
}
.date {
margin: 0.25rem 0 0;
color: #6b6862;
font-size: 0.9rem;
}
</style>listing(page.id, { perPage: 5 }): дочерние страницы порциями по пять. Сначала новые: последняя опубликованная работа окажется сверху.projects.items: работы текущей порции. На втором экране списка это работы с шестой по десятую.{:else}внутри{#each}: что показать, если список пуст.projects.pager(),projects.url(…): номера страниц списка и адреса соседних. Компонент Pagination рисует из них пейджер, а при одной странице не рисует ничего.
Выберите странице «Проекты» шаблон Projects. Вторая порция откроется по адресу /proekty/page-2, когда работ станет больше пяти: Eile сам понимает такие адреса.
Главная: витрина студии
Список можно собрать и из другого раздела. Главная покажет крупный заголовок, кнопку в каталог, большое фото, три кухни с ценами и два свежих проекта. Создайте шаблон Home:
<script>
import Content from '$core/Content';
import Layout from '$components/Layout';
import { children, image, link, listing } from '$core';
let { page } = $props();
// Разделы находим по адресу: так шаблону не нужны номера страниц.
const top = children(null);
const catalog = top.find((_) => _.path === '/katalog');
const projects = top.find((_) => _.path === '/proekty');
const contacts = top.find((_) => _.path === '/kontakty');
const kitchens = catalog ? children(catalog.id).slice(0, 3) : [];
const fresh = projects
? listing(projects.id, { perPage: 2, paged: false }).items
: [];
const cover = kitchens[0]?.fields.photo ? image(kitchens[0].fields.photo) : null;
const rub = new Intl.NumberFormat('ru');
</script>
<Layout {page}>
<section class="hero">
<h1>{page.longTitle || page.title}</h1>
<Content of={page} />
<p class="actions">
{#if catalog}<a class="button" href={link(catalog.id)}>Смотреть каталог</a>{/if}
{#if contacts}<a href={link(contacts.id)}>Записаться на замер</a>{/if}
</p>
{#if cover}
<img
src={cover.src}
srcset={cover.srcset}
sizes="(min-width: 48rem) 45rem, 100vw"
width={cover.width}
height={cover.height}
alt={cover.alt}
/>
{/if}
</section>
{#if kitchens.length}
<h2>Из каталога</h2>
<ul class="kitchens">
{#each kitchens as kitchen (kitchen.id)}
<li>
<a href={link(kitchen.id)}>{kitchen.title}</a>
<span>от {rub.format(kitchen.fields.price)} ₽ за метр</span>
</li>
{/each}
</ul>
{/if}
{#if fresh.length}
<h2>Готовые проекты</h2>
{#each fresh as project (project.id)}
<p class="project"><a href={link(project.id)}>{project.title}</a></p>
{/each}
{/if}
</Layout>
<style>
.hero img {
margin-top: 1.5rem;
border-radius: 1.5rem;
}
.actions {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 1.25rem;
}
.button {
padding: 0.8rem 1.4rem;
border-radius: 999px;
background: #1f1e1c;
color: #fff;
text-decoration: none;
}
.actions a:not(.button) {
color: inherit;
}
.kitchens {
display: grid;
gap: 0.5rem;
padding: 0;
list-style: none;
}
.kitchens li {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 0.25rem 1rem;
padding: 0.9rem 1.1rem;
border-radius: 1rem;
background: #fff;
}
.kitchens span {
color: #6b6862;
}
.kitchens a,
.project a {
color: inherit;
}
</style>top.find((_) => _.path === '/katalog'): ищем среди страниц верхнего уровня ту, у которой адрес/katalog. Знак_здесь просто имя для «очередной страницы».children(catalog.id).slice(0, 3): первые три кухни из «Каталога». Поменяете порядок в дереве, поменяются и они.listing(projects.id, { perPage: 2, paged: false }): два свежих проекта.paged: falseговорит, что этот список не постраничный: у главной не появятся адреса вида/page-2.kitchens[0]?.fields.photo: фото первой кухни для обложки. Знак?.не даёт шаблону упасть, если кухонь в каталоге пока нет.
Выберите главной шаблон Home. Крупный заголовок возьмите из поля Длинный заголовок на вкладке Поиск и соцсети: напишите там «Кухни по вашим размерам». Он же попадёт в заголовок вкладки браузера, а в меню сайта останется короткое «Главная».
