Fix missing H1 on list-style pages via SectionHeading template

SectionHeading always rendered its title as <h2>, so every page whose
only heading came from a shared section component (services hub,
promotions, cases, faq, contact, reviews, blog list) had no <h1> at
all. Add a level prop (default h2) and pass headingLevel="h1" from
each page where that section is the page's own top-level heading.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013otXCiTZsxdZ4SJKZ9wUky
This commit is contained in:
ogrechkoandClaude Sonnet 5 committed 2026-08-26 00:00:24 +03:00
1 parent 30e1aea20b
commit f53c8b1ed7
14 files changed
+28 -18

No files matched your search

+1 -1
View File
@@ -16,7 +16,7 @@ export default async function PromotionsPage() {
return ( return (
<div> <div>
<Header /> <Header />
<Promotions promotions={promotions} showMailNote /> <Promotions promotions={promotions} showMailNote headingLevel="h1" />
<Footer /> <Footer />
</div> </div>
); );
+6 -1
View File
@@ -24,7 +24,12 @@ export default async function BlogListPage() {
<div> <div>
<Header /> <Header />
<section className="max-w-3xl mx-auto px-5 sm:px-8 py-20 min-h-[60vh]"> <section className="max-w-3xl mx-auto px-5 sm:px-8 py-20 min-h-[60vh]">
<SectionHeading eyebrow="blog --articles" title="Блог" desc="Заметки об IT-инфраструктуре, кибербезопасности и автоматизации." /> <SectionHeading
eyebrow="blog --articles"
title="Блог"
desc="Заметки об IT-инфраструктуре, кибербезопасности и автоматизации."
level="h1"
/>
{posts.length === 0 && <p className="text-sm text-slate-400">Пока нет опубликованных статей - загляните позже.</p>} {posts.length === 0 && <p className="text-sm text-slate-400">Пока нет опубликованных статей - загляните позже.</p>}
+1 -1
View File
@@ -32,7 +32,7 @@ export default async function FaqPage() {
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(faqSchema) }} /> <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(faqSchema) }} />
)} )}
<Header /> <Header />
<Faq faqs={faqs} /> <Faq faqs={faqs} headingLevel="h1" />
<Footer /> <Footer />
</div> </div>
); );
+1 -1
View File
@@ -16,7 +16,7 @@ export default async function CasesPage() {
return ( return (
<div> <div>
<Header /> <Header />
<Cases cases={cases} showDisclaimer /> <Cases cases={cases} showDisclaimer headingLevel="h1" />
<Footer /> <Footer />
</div> </div>
); );
+1 -1
View File
@@ -15,7 +15,7 @@ export default async function ContactPage() {
return ( return (
<div> <div>
<Header /> <Header />
<Contact contact={contact} /> <Contact contact={contact} headingLevel="h1" />
<Footer /> <Footer />
</div> </div>
); );
+1 -1
View File
@@ -15,7 +15,7 @@ export default async function ReviewsPage() {
return ( return (
<div> <div>
<Header /> <Header />
<Reviews reviews={reviews} /> <Reviews reviews={reviews} headingLevel="h1" />
<Footer /> <Footer />
</div> </div>
); );
+1 -1
View File
@@ -16,7 +16,7 @@ export default async function ServicesPage() {
return ( return (
<div> <div>
<Header /> <Header />
<Services categories={services} /> <Services categories={services} headingLevel="h1" />
<Footer /> <Footer />
</div> </div>
); );
+3 -2
View File
@@ -1,10 +1,11 @@
import Reveal from './Reveal.jsx'; import Reveal from './Reveal.jsx';
export default function SectionHeading({ eyebrow, title, desc }) { export default function SectionHeading({ eyebrow, title, desc, level = 'h2' }) {
const Heading = level;
return ( return (
<Reveal className="mb-10 max-w-2xl"> <Reveal className="mb-10 max-w-2xl">
<p className="text-xs uppercase tracking-widest text-emerald-500 mb-3 font-mono">&gt; {eyebrow}</p> <p className="text-xs uppercase tracking-widest text-emerald-500 mb-3 font-mono">&gt; {eyebrow}</p>
<h2 className="text-2xl sm:text-3xl font-bold mb-3 font-display">{title}</h2> <Heading className="text-2xl sm:text-3xl font-bold mb-3 font-display">{title}</Heading>
{desc && <p className="text-sm sm:text-base text-slate-600 dark:text-slate-400">{desc}</p>} {desc && <p className="text-sm sm:text-base text-slate-600 dark:text-slate-400">{desc}</p>}
</Reveal> </Reveal>
); );
+2 -2
View File
@@ -3,13 +3,13 @@ import Reveal from '../Reveal.jsx';
import SectionHeading from '../SectionHeading.jsx'; import SectionHeading from '../SectionHeading.jsx';
import StatusDot from '../StatusDot.jsx'; import StatusDot from '../StatusDot.jsx';
export default function Cases({ cases, limit, moreHref, showDisclaimer = false }) { export default function Cases({ cases, limit, moreHref, showDisclaimer = false, headingLevel = 'h2' }) {
if (cases.length === 0) return null; if (cases.length === 0) return null;
const list = limit ? cases.slice(0, limit) : cases; const list = limit ? cases.slice(0, limit) : cases;
return ( return (
<section id="cases" className="max-w-6xl mx-auto px-5 sm:px-8 py-20"> <section id="cases" className="max-w-6xl mx-auto px-5 sm:px-8 py-20">
<SectionHeading eyebrow="cases --log" title="Кейсы и портфолио" /> <SectionHeading eyebrow="cases --log" title="Кейсы и портфолио" level={headingLevel} />
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-5"> <div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-5">
{list.map((c, i) => ( {list.map((c, i) => (
<Reveal key={c.id} delay={i * 60}> <Reveal key={c.id} delay={i * 60}>
+2 -2
View File
@@ -3,7 +3,7 @@ import { MapPin, Phone, Mail, Clock, ArrowRight } from 'lucide-react';
import Reveal from '../Reveal.jsx'; import Reveal from '../Reveal.jsx';
import SectionHeading from '../SectionHeading.jsx'; import SectionHeading from '../SectionHeading.jsx';
export default function Contact({ contact, compact = false }) { export default function Contact({ contact, compact = false, headingLevel = 'h2' }) {
const rows = contact const rows = contact
? [ ? [
{ icon: MapPin, label: 'Адрес', value: contact.address }, { icon: MapPin, label: 'Адрес', value: contact.address },
@@ -39,7 +39,7 @@ export default function Contact({ contact, compact = false }) {
return ( return (
<section id="contact" className="max-w-6xl mx-auto px-5 sm:px-8 py-20"> <section id="contact" className="max-w-6xl mx-auto px-5 sm:px-8 py-20">
<SectionHeading eyebrow="contact --reach-us" title="Контакты" /> <SectionHeading eyebrow="contact --reach-us" title="Контакты" level={headingLevel} />
<div className="grid md:grid-cols-2 gap-5"> <div className="grid md:grid-cols-2 gap-5">
<Reveal> <Reveal>
<div className="rounded-lg border p-5 h-full bg-white border-slate-200 dark:bg-slate-900 dark:border-slate-800"> <div className="rounded-lg border p-5 h-full bg-white border-slate-200 dark:bg-slate-900 dark:border-slate-800">
+2 -2
View File
@@ -23,13 +23,13 @@ function FaqItem({ item }) {
); );
} }
export default function Faq({ faqs, limit, moreHref }) { export default function Faq({ faqs, limit, moreHref, headingLevel = 'h2' }) {
if (faqs.length === 0) return null; if (faqs.length === 0) return null;
const list = limit ? faqs.slice(0, limit) : faqs; const list = limit ? faqs.slice(0, limit) : faqs;
return ( return (
<section id="faq" className="max-w-3xl mx-auto px-5 sm:px-8 py-20"> <section id="faq" className="max-w-3xl mx-auto px-5 sm:px-8 py-20">
<SectionHeading eyebrow="faq --common" title="Частые вопросы" /> <SectionHeading eyebrow="faq --common" title="Частые вопросы" level={headingLevel} />
<Reveal> <Reveal>
<div> <div>
{list.map((item) => ( {list.map((item) => (
@@ -3,7 +3,7 @@ import { ArrowUpRight } from 'lucide-react';
import Reveal from '../Reveal.jsx'; import Reveal from '../Reveal.jsx';
import SectionHeading from '../SectionHeading.jsx'; import SectionHeading from '../SectionHeading.jsx';
export default function Promotions({ promotions, limit, moreHref, showMailNote = false }) { export default function Promotions({ promotions, limit, moreHref, showMailNote = false, headingLevel = 'h2' }) {
if (promotions.length === 0) return null; if (promotions.length === 0) return null;
const list = limit ? promotions.slice(0, limit) : promotions; const list = limit ? promotions.slice(0, limit) : promotions;
@@ -14,6 +14,7 @@ export default function Promotions({ promotions, limit, moreHref, showMailNote =
eyebrow="promotions --active" eyebrow="promotions --active"
title="Акции и специальные предложения" title="Акции и специальные предложения"
desc="Актуальные сроки и условия уточняйте у менеджера при обращении." desc="Актуальные сроки и условия уточняйте у менеджера при обращении."
level={headingLevel}
/> />
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-5"> <div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-5">
{list.map((p, i) => ( {list.map((p, i) => (
+3 -1
View File
@@ -8,7 +8,7 @@ function cx(...args) {
return args.filter(Boolean).join(' '); return args.filter(Boolean).join(' ');
} }
export default function Reviews({ reviews, limit, moreHref, showForm = true }) { export default function Reviews({ reviews, limit, moreHref, showForm = true, headingLevel = 'h2' }) {
const list = limit ? reviews.slice(0, limit) : reviews; const list = limit ? reviews.slice(0, limit) : reviews;
// Пустая витрина отзывов на тизере (главная) выглядит слабо - лучше не показывать // Пустая витрина отзывов на тизере (главная) выглядит слабо - лучше не показывать
@@ -23,6 +23,7 @@ export default function Reviews({ reviews, limit, moreHref, showForm = true }) {
eyebrow="reviews --approved" eyebrow="reviews --approved"
title="Отзывы клиентов" title="Отзывы клиентов"
desc="Отзывов пока нет - будьте первым, кто расскажет о работе с нами. Публикуется после проверки менеджером." desc="Отзывов пока нет - будьте первым, кто расскажет о работе с нами. Публикуется после проверки менеджером."
level={headingLevel}
/> />
<Reveal> <Reveal>
<ReviewForm /> <ReviewForm />
@@ -39,6 +40,7 @@ export default function Reviews({ reviews, limit, moreHref, showForm = true }) {
eyebrow="reviews --approved" eyebrow="reviews --approved"
title="Отзывы клиентов" title="Отзывы клиентов"
desc="Публикуются после проверки менеджером - это защищает страницу от спама и фейков." desc="Публикуются после проверки менеджером - это защищает страницу от спама и фейков."
level={headingLevel}
/> />
<div className={cx('grid gap-10', showForm && 'md:grid-cols-2')}> <div className={cx('grid gap-10', showForm && 'md:grid-cols-2')}>
<Reveal> <Reveal>
+2 -1
View File
@@ -30,12 +30,13 @@ export default function Services({
eyebrow = 'services --price-list', eyebrow = 'services --price-list',
title = 'Услуги IT-аутсорсинга и техподдержки: прайс-лист', title = 'Услуги IT-аутсорсинга и техподдержки: прайс-лист',
desc = 'Гибкие тарифы для бизнеса любого масштаба. Возможна индивидуальная настройка под ваши задачи.', desc = 'Гибкие тарифы для бизнеса любого масштаба. Возможна индивидуальная настройка под ваши задачи.',
headingLevel = 'h2',
}) { }) {
const list = limit ? categories.slice(0, limit) : categories; const list = limit ? categories.slice(0, limit) : categories;
return ( return (
<section id="services" className="max-w-6xl mx-auto px-5 sm:px-8 py-20"> <section id="services" className="max-w-6xl mx-auto px-5 sm:px-8 py-20">
<SectionHeading eyebrow={eyebrow} title={title} desc={desc} /> <SectionHeading eyebrow={eyebrow} title={title} desc={desc} level={headingLevel} />
{list.length === 0 && <p className="text-sm text-slate-400">Прайс-лист пока пуст.</p>} {list.length === 0 && <p className="text-sm text-slate-400">Прайс-лист пока пуст.</p>}