This commit is contained in:
Lars Nolden
2026-08-14 15:36:10 +02:00
parent 661f81eec9
commit 4f250eb1c2
16 changed files with 1889 additions and 174 deletions
+17
View File
@@ -13,7 +13,11 @@ import HomePage from "@/pages/home"
import HowItWorksPage from "@/pages/how-it-works" import HowItWorksPage from "@/pages/how-it-works"
import KlarpreisPage from "@/pages/klarpreis" import KlarpreisPage from "@/pages/klarpreis"
import { SimplePage } from "@/pages/placeholder" import { SimplePage } from "@/pages/placeholder"
import HeizkostenabrechnungProductPage from "@/pages/products/heizkostenabrechnung"
import MesstechnikProductPage from "@/pages/products/messtechnik-infrastruktur"
import ProductsPage from "@/pages/products" import ProductsPage from "@/pages/products"
import RauchwarnmelderProductPage from "@/pages/products/rauchwarnmelder"
import UviProductPage from "@/pages/products/uvi"
import WissenPage from "@/pages/wissen" import WissenPage from "@/pages/wissen"
import DatenschutzSicherheitPage from "@/pages/wissen/datenschutz-sicherheit" import DatenschutzSicherheitPage from "@/pages/wissen/datenschutz-sicherheit"
import GesetzlicheAnforderungenPage from "@/pages/wissen/gesetzliche-anforderungen" import GesetzlicheAnforderungenPage from "@/pages/wissen/gesetzliche-anforderungen"
@@ -39,6 +43,19 @@ export default function App() {
<Route path="/" element={<HomePage />} /> <Route path="/" element={<HomePage />} />
<Route path="/so-funktionierts" element={<HowItWorksPage />} /> <Route path="/so-funktionierts" element={<HowItWorksPage />} />
<Route path="/produkte" element={<ProductsPage />} /> <Route path="/produkte" element={<ProductsPage />} />
<Route path="/produkte/uvi" element={<UviProductPage />} />
<Route
path="/produkte/heizkostenabrechnung"
element={<HeizkostenabrechnungProductPage />}
/>
<Route
path="/produkte/rauchwarnmelder"
element={<RauchwarnmelderProductPage />}
/>
<Route
path="/produkte/messtechnik-infrastruktur"
element={<MesstechnikProductPage />}
/>
<Route path="/fuer-wen" element={<ForWhomPage />} /> <Route path="/fuer-wen" element={<ForWhomPage />} />
<Route <Route
path="/fuer-wen/hauseigentuemer" path="/fuer-wen/hauseigentuemer"
@@ -1,4 +1,5 @@
import * as React from "react" import * as React from "react"
import { ArrowRight } from "lucide-react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
import { IconBadge } from "@/components/solenos/icon-tile" import { IconBadge } from "@/components/solenos/icon-tile"
@@ -155,4 +156,66 @@ function StepRail({
) )
} }
export { ProcessSteps, NumberedList, StepRail } /**
* Arrow-connected step flow on a card (home page, "So funktioniert's",
* steps 15): glass numeral, asset render, title and copy per column,
* hairline-divided, with a teal arrow sitting on each divider at render
* height. Arrows and dividers appear only once the columns sit in one row.
*/
function StepFlow({
steps,
className,
}: {
steps: ProcessStep[]
className?: string
}) {
return (
<ol
className={cn(
"grid grid-cols-1 gap-y-10 rounded-3xl bg-card p-5 shadow-card sm:grid-cols-2 sm:p-7 lg:gap-y-0",
steps.length >= 5 ? "lg:grid-cols-5" : "lg:grid-cols-4",
className
)}
>
{steps.map((step, i) => (
<li
key={i}
className={cn(
"flex flex-col items-center text-center",
/* dividers between side-by-side columns of the current layout */
i % 2 === 1 && "sm:border-l",
i > 0 && "lg:border-l"
)}
>
<GlassNumber n={i + 1} className="size-9" />
<div className="relative mt-7 flex h-32 w-full items-end justify-center gap-1">
{i > 0 ? (
<span
aria-hidden="true"
/* breaks the hairline it sits on, at render height */
className="absolute top-1/2 left-0 hidden -translate-x-1/2 -translate-y-1/2 bg-card px-1.5 text-primary lg:block"
>
<ArrowRight className="size-5" />
</span>
) : null}
{step.media ?? (
<IconBadge variant="outline" shape="squircle" size="xl">
{step.icon}
</IconBadge>
)}
</div>
<h3 className="mt-6 px-3 text-[0.9375rem] font-bold text-balance text-navy">
{step.title}
</h3>
{step.description ? (
<p className="mt-2.5 max-w-[14rem] px-2 text-[0.8125rem] leading-relaxed text-balance text-muted-foreground">
{step.description}
</p>
) : null}
</li>
))}
</ol>
)
}
export { ProcessSteps, NumberedList, StepFlow, StepRail }
@@ -8,6 +8,13 @@ const COLUMNS: { title: string; links: { to: string; label: string }[] }[] = [
title: "Produkt", title: "Produkt",
links: [ links: [
{ to: "/produkte", label: "Produkte" }, { to: "/produkte", label: "Produkte" },
{ to: "/produkte/uvi", label: "UVI" },
{ to: "/produkte/heizkostenabrechnung", label: "Heizkostenabrechnung" },
{ to: "/produkte/rauchwarnmelder", label: "Rauchwarnmelder" },
{
to: "/produkte/messtechnik-infrastruktur",
label: "Messtechnik und Infrastruktur",
},
{ to: "/so-funktionierts", label: "So funktionierts" }, { to: "/so-funktionierts", label: "So funktionierts" },
{ to: "/konfigurator", label: "Konfigurator" }, { to: "/konfigurator", label: "Konfigurator" },
{ to: "/klarpreis", label: "SolenOS Klarpreis" }, { to: "/klarpreis", label: "SolenOS Klarpreis" },
@@ -22,17 +22,28 @@ const AUDIENCES = [
{ to: "/fuer-wen/installateure", label: "Installateure & Heizungsbauer" }, { to: "/fuer-wen/installateure", label: "Installateure & Heizungsbauer" },
] ]
/** mirrors the product registry in `@/pages/products/product-page` */
const PRODUCTS = [
{ to: "/produkte/uvi", label: "UVI" },
{ to: "/produkte/heizkostenabrechnung", label: "Heizkostenabrechnung" },
{ to: "/produkte/rauchwarnmelder", label: "Rauchwarnmelder" },
{
to: "/produkte/messtechnik-infrastruktur",
label: "Messtechnik und Infrastruktur",
},
]
type NavItem = { type NavItem = {
to: string to: string
label: string label: string
/** present ⇒ rendered as a dropdown; `to` stays the section root that marks it active */ /** present ⇒ rendered as a dropdown; `to` stays the section root that marks it active */
menu?: typeof AUDIENCES menu?: { to: string; label: string }[]
} }
/** Source of truth for nav order desktop pill, dropdown and mobile sheet all read it. */ /** Source of truth for nav order desktop pill, dropdown and mobile sheet all read it. */
const NAV_ITEMS: NavItem[] = [ const NAV_ITEMS: NavItem[] = [
{ to: "/", label: "Start" }, { to: "/", label: "Start" },
{ to: "/produkte", label: "Produkte" }, { to: "/produkte", label: "Produkte", menu: PRODUCTS },
{ to: "/so-funktionierts", label: "So funktionierts" }, { to: "/so-funktionierts", label: "So funktionierts" },
{ to: "/fuer-wen", label: "Für wen?", menu: AUDIENCES }, { to: "/fuer-wen", label: "Für wen?", menu: AUDIENCES },
{ to: "/wissen", label: "Wissen" }, { to: "/wissen", label: "Wissen" },
@@ -120,7 +131,7 @@ function useActivePill(activeKey: string | undefined) {
return { navRef, pill } return { navRef, pill }
} }
function AudienceMenu({ item, active }: { item: NavItem; active: boolean }) { function NavMenu({ item, active }: { item: NavItem; active: boolean }) {
return ( return (
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger <DropdownMenuTrigger
@@ -171,7 +182,7 @@ function DesktopNav() {
)} )}
{NAV_ITEMS.map((item) => {NAV_ITEMS.map((item) =>
item.menu ? ( item.menu ? (
<AudienceMenu key={item.to} item={item} active={item.to === activeKey} /> <NavMenu key={item.to} item={item} active={item.to === activeKey} />
) : ( ) : (
<NavLink <NavLink
key={item.to} key={item.to}
@@ -0,0 +1,69 @@
import type * as React from "react"
import { cn } from "@/lib/utils"
import { Card, CardContent } from "@/components/ui/card"
import { IconBadge } from "@/components/solenos/icon-tile"
/** The paragraph of a regulation a property is cited against. */
type LegalSource = {
cite: string
href: string
}
/** One technical property: line icon, title, body, optional legal citation. */
type Topic = {
icon: React.ReactNode
title: string
body: string
source?: LegalSource
}
/** Cited source line, pinned to the card's baseline so rows stay aligned. */
function LegalSourceNote({ source }: { source: LegalSource }) {
return (
<p className="mt-auto pt-4 text-xs text-muted-foreground">
Quelle:{" "}
<a
href={source.href}
target="_blank"
rel="noreferrer"
className="font-medium text-brand-700 underline-offset-2 transition-colors hover:text-brand-800 hover:underline"
>
{source.cite}
</a>
</p>
)
}
/** Property grid: line icon over a navy title, body copy, optional citation. */
function TopicCards({
items,
className,
}: {
items: Topic[]
className?: string
}) {
return (
<div className={cn("mt-10 grid gap-5", className)}>
{items.map((item) => (
<Card key={item.title}>
<CardContent className="flex flex-1 flex-col">
<IconBadge variant="outline" shape="squircle" size="lg">
{item.icon}
</IconBadge>
<h3 className="mt-5 text-base font-bold text-balance text-navy">
{item.title}
</h3>
<p className="mt-2 text-sm leading-relaxed text-pretty text-muted-foreground">
{item.body}
</p>
{item.source ? <LegalSourceNote source={item.source} /> : null}
</CardContent>
</Card>
))}
</div>
)
}
export { LegalSourceNote, TopicCards }
export type { LegalSource, Topic }
+9
View File
@@ -0,0 +1,9 @@
/** gesetze-im-internet base for the Heizkostenverordnung. */
const HEIZKOSTENV = "https://www.gesetze-im-internet.de/heizkostenv"
/** Permalink to a single paragraph, e.g. `heizkostenv("6a")`. */
function heizkostenv(paragraph: string) {
return `${HEIZKOSTENV}/__${paragraph}.html`
}
export { heizkostenv }
+37 -47
View File
@@ -14,7 +14,7 @@ import { Container, Section, SectionHeading } from "@/components/solenos/section
import { PageHero } from "@/components/solenos/page-hero" import { PageHero } from "@/components/solenos/page-hero"
import { HeroArt } from "@/components/solenos/hero-art" import { HeroArt } from "@/components/solenos/hero-art"
import { InfoStrip } from "@/components/solenos/info-strip" import { InfoStrip } from "@/components/solenos/info-strip"
import { ProcessSteps, NumberedList } from "@/components/solenos/process-steps" import { NumberedList, StepFlow } from "@/components/solenos/process-steps"
import { Checklist } from "@/components/solenos/checklist" import { Checklist } from "@/components/solenos/checklist"
import { CtaBanner } from "@/components/solenos/cta-banner" import { CtaBanner } from "@/components/solenos/cta-banner"
import { FaqAccordion, type FaqItem } from "@/components/solenos/faq-accordion" import { FaqAccordion, type FaqItem } from "@/components/solenos/faq-accordion"
@@ -25,7 +25,6 @@ import {
AssetRender, AssetRender,
icons, icons,
renders, renders,
vectors,
type RenderAsset, type RenderAsset,
} from "@/components/solenos/site-assets" } from "@/components/solenos/site-assets"
@@ -113,58 +112,44 @@ const operationsScope = [
}, },
] ]
function StepMedia({ src }: { src: string }) {
return (
<span className="flex size-16 items-center justify-center">
<img src={src} alt="" className="size-16" loading="lazy" />
</span>
)
}
const processSteps = [ const processSteps = [
{ {
media: ( media: <AssetRender render={renders.building} imgClassName="w-52" />,
<AssetRender title: "Gebäude erfassen",
render={renders.building} description: "Adresse, Wohnungen und Heizart angeben.",
className="flex size-16 items-center justify-center"
imgClassName="w-16"
/>
),
title: "Gebäude anlegen & bestellen",
description:
"In nur 30 Minuten konfigurieren und bestellen.",
},
{
media: <StepMedia src={vectors.packageBox} />,
title: "Vorkonfiguriert liefern",
description: "Geräte werden für Ihr Objekt vorbereitet geliefert.",
}, },
{ {
media: ( media: (
<>
<AssetRender render={renders.heatMeter} imgClassName="w-32" />
<AssetRender <AssetRender
render={renders.heatMeter} render={renders.smokeAlarm}
className="flex size-16 items-center justify-center" className="-ml-3 self-start"
imgClassName="w-16" imgClassName="w-24"
/> />
</>
), ),
title: "Ausstattung bestimmen",
description: "SolenOS ermittelt passende Geräte und Mengen.",
},
{
media: (
<AssetRender render={renders.shippingBoxMeters} imgClassName="w-36" />
),
title: "Vorkonfiguriert erhalten",
description: "Geräte bereits jeder Wohnung zugeordnet geliefert.",
},
{
media: <AssetRender render={renders.drillTool} imgClassName="w-32" />,
title: "Geräte montieren", title: "Geräte montieren",
description: "Montage vor Ort ohne aufwendige Softwarekonfiguration.", description: "Selbst installieren oder Montage beauftragen.",
}, },
{ {
media: ( media: (
<AssetRender <AssetRender render={renders.laptopDashboard} imgClassName="w-44" />
render={renders.laptopDashboard}
className="flex size-16 items-center justify-center"
imgClassName="w-16"
/>
), ),
title: "Bewohner einladen", title: "Betrieb starten",
description: "Laden Sie die Bewohner in das SolenOS Portal ein.", description: "Bewohner einladen und digital verwalten.",
},
{
media: <StepMedia src={vectors.wirelessOrb} />,
title: "Automatischer Betrieb",
description: "SolenOS übernimmt Daten, Services und laufenden Betrieb.",
}, },
] ]
@@ -192,6 +177,7 @@ const systemTasks = [
media: <img src={icons.uvi} alt="" className="size-10" loading="lazy" />, media: <img src={icons.uvi} alt="" className="size-10" loading="lazy" />,
title: "UVI", title: "UVI",
text: "Regelmäßige Verbrauchsinformation für Bewohner.", text: "Regelmäßige Verbrauchsinformation für Bewohner.",
to: "/produkte/uvi",
}, },
{ {
media: ( media: (
@@ -199,6 +185,7 @@ const systemTasks = [
), ),
title: "Heizkostenabrechnung", title: "Heizkostenabrechnung",
text: "Verbrauchsdaten strukturiert erfassen und für die jährliche Abrechnung nutzen.", text: "Verbrauchsdaten strukturiert erfassen und für die jährliche Abrechnung nutzen.",
to: "/produkte/heizkostenabrechnung",
}, },
{ {
media: ( media: (
@@ -206,11 +193,13 @@ const systemTasks = [
), ),
title: "Rauchwarnmelder", title: "Rauchwarnmelder",
text: "Rauchwarnmelder gemeinsam mit der übrigen Gebäudetechnik planen und verwalten.", text: "Rauchwarnmelder gemeinsam mit der übrigen Gebäudetechnik planen und verwalten.",
to: "/produkte/rauchwarnmelder",
}, },
{ {
media: <img src={icons.radio} alt="" className="size-10" loading="lazy" />, media: <img src={icons.radio} alt="" className="size-10" loading="lazy" />,
title: "Messtechnik und Infrastruktur", title: "Messtechnik und Infrastruktur",
text: "Fernauslesbare Zähler, Sensoren und Kommunikationsinfrastruktur für den laufenden Betrieb.", text: "Fernauslesbare Zähler, Sensoren und Kommunikationsinfrastruktur für den laufenden Betrieb.",
to: "/produkte/messtechnik-infrastruktur",
}, },
] ]
@@ -472,13 +461,14 @@ export default function HomePage() {
</Section> </Section>
<Section> <Section>
<div className="rounded-3xl bg-gradient-to-b from-secondary to-card px-6 py-10 sm:px-10 sm:py-12 lg:px-12">
<SectionHeading <SectionHeading
align="center" eyebrow="So funktioniert's"
eyebrow="Der Prozess" title="In fünf Schritten zum laufenden Messbetrieb"
title="So funktioniert SolenOS" lead="Statt Geräte, Software und Messdienst getrennt zu organisieren, verbindet SolenOS die einzelnen Schritte zu einem durchgängigen Ablauf."
lead="Von der Bestellung bis zum laufenden Betrieb SolenOS verbindet Bestellung, Konfiguration und Installation miteinander."
/> />
<ProcessSteps steps={processSteps} className="mt-12" /> </div>
<StepFlow steps={processSteps} className="mt-4" />
</Section> </Section>
<Section> <Section>
@@ -529,7 +519,7 @@ export default function HomePage() {
variant="link" variant="link"
className="h-auto shrink-0 self-start p-0 sm:self-center" className="h-auto shrink-0 self-start p-0 sm:self-center"
> >
<Link to="/produkte"> <Link to={task.to}>
Mehr erfahren Mehr erfahren
<ArrowRight /> <ArrowRight />
</Link> </Link>
@@ -0,0 +1,338 @@
import { Link } from "react-router-dom"
import {
ArrowRight,
CalendarClock,
Euro,
FileCheck2,
Flame,
Layers,
Droplets,
UserRoundCog,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
import { Checklist } from "@/components/solenos/checklist"
import {
ComparisonTable,
type ComparisonColumn,
type ComparisonRow,
} from "@/components/solenos/comparison-table"
import { CtaBanner } from "@/components/solenos/cta-banner"
import { FaqAccordion, type FaqItem } from "@/components/solenos/faq-accordion"
import { EuroCircleIcon } from "@/components/solenos/line-icons"
import { NumberedList } from "@/components/solenos/process-steps"
import { Section, SectionHeading } from "@/components/solenos/section"
import {
AssetRender,
renders,
vectors,
} from "@/components/solenos/site-assets"
import { TopicCards, type Topic } from "@/components/solenos/topic-cards"
import { heizkostenv } from "@/lib/legal"
import { ProductCrossLinks, ProductPage } from "@/pages/products/product-page"
/**
* Hero art: the glass money render as the subject the costs being
* distributed with the consumption bars as their basis and the euro orb as
* the accent. Decorative; the copy carries the meaning.
*/
function BillingHeroArt() {
return (
<div className="relative mx-auto flex aspect-[1.3] w-full max-w-md items-center">
<img
src={vectors.euroOrb}
alt=""
className="absolute top-[2%] right-[4%] w-[22%]"
/>
<AssetRender
render={renders.glassMoney}
alt=""
className="mx-auto w-[68%]"
loading="eager"
/>
<AssetRender
render={renders.barChartManyFull}
alt=""
className="absolute bottom-0 left-0 w-[32%]"
/>
</div>
)
}
const DISTRIBUTION: Topic[] = [
{
icon: <Flame />,
title: "Kosten der Wärmeversorgung",
body: "Mindestens 50 und höchstens 70 Prozent der Kosten werden nach dem erfassten Wärmeverbrauch verteilt, die übrigen nach Wohn- oder Nutzfläche beziehungsweise umbautem Raum.",
source: { cite: "§ 7 HeizkostenV", href: heizkostenv("7") },
},
{
icon: <Droplets />,
title: "Kosten der Warmwasserversorgung",
body: "Für die zentrale Warmwasserversorgung gilt derselbe Rahmen: 50 bis 70 Prozent nach erfasstem Warmwasserverbrauch, der Rest nach Wohn- oder Nutzfläche.",
source: { cite: "§ 8 HeizkostenV", href: heizkostenv("8") },
},
{
icon: <Layers />,
title: "Verbundene Anlagen",
body: "Erzeugt eine Anlage Wärme und Warmwasser gemeinsam, werden die einheitlich entstandenen Betriebskosten zuerst zwischen beiden Zwecken aufgeteilt anhand der auf die Warmwasserbereitung entfallenden Wärmemenge, die dafür mit einem Wärmezähler gemessen wird.",
source: { cite: "§ 9 HeizkostenV", href: heizkostenv("9") },
},
{
icon: <UserRoundCog />,
title: "Nutzungszeiträume und Nutzerwechsel",
body: "Weil die Ausstattung fernablesbar ist, werden Zwischenwerte zum Stichtag aus der laufenden Datenerfassung übernommen und die Verbräuche dem jeweiligen Nutzungszeitraum zugeordnet.",
},
]
const YEAR_FLOW = [
{
title: "Abrechnungszeitraum festlegen",
description:
"Zeitraum, Verteilungsmaßstäbe und Gebäudedaten werden im Portal hinterlegt.",
},
{
title: "Verbrauchsdaten laufen automatisch mit",
description:
"Die fernablesbare Messtechnik liefert Monatswerte dieselbe Datenbasis wie für die UVI.",
},
{
title: "Kosten des Abrechnungszeitraums erfassen",
description:
"Brennstoff- oder Wärmelieferkosten, Betriebsstrom, Wartung und Messdienstkosten werden zugeordnet.",
},
{
title: "Abrechnung erstellen und prüfen",
description:
"Verteilung nach den gewählten Maßstäben, inklusive Nutzerwechsel und Leerstand.",
},
{
title: "Dokumente bereitstellen",
description:
"Einzelabrechnungen und Pflichtangaben stehen für Eigentümer, Verwaltung und Bewohner im Portal bereit.",
},
]
const BASIS_COLUMNS: ComparisonColumn[] = [
{ title: "Mit SolenOS", highlight: true },
{ title: "Klassischer Ableseweg", check: false },
]
const BASIS_ROWS: ComparisonRow[] = [
{
label: "Datenerhebung",
cells: ["laufend fernabgelesen", "Ablesetermin je Wohnung"],
},
{
label: "Zugang zur Wohnung",
cells: ["nicht erforderlich", "erforderlich, inklusive Nachtermine"],
},
{
label: "Nutzerwechsel",
cells: ["Zwischenwerte aus der laufenden Erfassung", "Zwischenablesung vor Ort"],
},
{
label: "Unterjährige Information",
cells: ["monatlich aus derselben Datenbasis", "separat zu organisieren"],
},
{
label: "Datenbestand",
cells: [
"Gebäude, Wohnungen und Geräte einmalig strukturiert",
"je Leistung erneut zu übermitteln",
],
},
]
const INCLUDED = [
"Verteilung der Wärme- und Warmwasserkosten nach HeizkostenV",
"Berücksichtigung von Nutzerwechseln und Leerständen",
"Einzelabrechnung je Wohnung mit den Pflichtangaben nach § 6a Absatz 3",
"Bereitstellung der Dokumente im Portal für Verwaltung und Bewohner",
"Übernahme der Verbrauchsdaten aus dem laufenden Messbetrieb",
]
const NOT_INCLUDED = [
"Betriebskostenabrechnung des Gebäudes (kalte Betriebskosten)",
"Beschaffung von Brennstoff oder Wärmelieferung",
"Mietvertragliche Prüfung und Mahnwesen",
]
const FAQS: FaqItem[] = [
{
question: "Brauche ich für die Abrechnung einen Ablesetermin?",
answer:
"Nein. Die Werte stammen aus der fernablesbaren Messtechnik und werden zum Ende des Abrechnungszeitraums automatisch übernommen. Auch Zwischenwerte bei Nutzerwechseln entstehen ohne Termin in der Wohnung.",
},
{
question: "Welche Verteilungsmaßstäbe kann ich wählen?",
answer:
"Für die Wärmekosten sind mindestens 50 und höchstens 70 Prozent nach erfasstem Verbrauch zu verteilen (§ 7 Absatz 1 HeizkostenV); für Warmwasser gilt derselbe Rahmen (§ 8 Absatz 1). Die Wahl der Maßstäbe bleibt dem Gebäudeeigentümer überlassen und wird für künftige Abrechnungszeiträume im Portal hinterlegt (§ 6 Absatz 4).",
},
{
question: "Was passiert, wenn nicht verbrauchsabhängig abgerechnet wird?",
answer:
"Nach § 12 Absatz 1 HeizkostenV kann der Nutzer seinen Kostenanteil um 15 Prozent kürzen, soweit entgegen der Verordnung nicht verbrauchsabhängig abgerechnet wird. Fehlt die fernablesbare Ausstattung oder werden die Informationen nach § 6a nicht vollständig mitgeteilt, sind es 3 Prozent.",
},
{
question: "Kann ich die Abrechnung ohne UVI buchen?",
answer:
"Die Abrechnung setzt erfasste Verbrauchsdaten voraus. Läuft der Messbetrieb über SolenOS, ist die Datenbasis vorhanden und die Abrechnung nutzt sie direkt die monatliche Verbrauchsinformation entsteht dann aus demselben Datenbestand.",
},
{
question: "Wie sehen Bewohner ihre Abrechnung?",
answer:
"Die Einzelabrechnung wird im Mieterportal bereitgestellt und kann als Dokument ausgegeben werden. Bewohner sehen ausschließlich die Unterlagen ihres eigenen Nutzungsverhältnisses.",
},
]
export default function HeizkostenabrechnungProductPage() {
return (
<ProductPage
breadcrumb="Heizkostenabrechnung"
eyebrow="Produkt"
title={<>Heizkosten&shy;abrechnung</>}
tagline="Aus den Daten, die ohnehin laufen."
lead="Die jährliche Abrechnung der Heiz- und Warmwasserkosten entsteht aus denselben fernabgelesenen Verbrauchswerten wie die monatliche UVI verteilt nach den Maßstäben der Heizkostenverordnung, ohne Ablesetermin im Gebäude."
actions={
<>
<Button asChild size="lg">
<Link to="/konfigurator">Preis für mein Gebäude berechnen</Link>
</Button>
<Button asChild variant="outline" size="lg">
<Link to="/produkte/uvi">UVI ansehen</Link>
</Button>
</>
}
note="Wenige Angaben · unverbindliche Kostenschätzung"
visual={<BillingHeroArt />}
features={[
{
icon: <EuroCircleIcon />,
title: "Verbrauchsabhängig verteilt",
sub: "50 bis 70 Prozent nach Verbrauch, der Rest nach Fläche.",
},
{
icon: <CalendarClock strokeWidth={1.6} />,
title: "Nutzerwechsel inklusive",
sub: "Zwischenwerte zum Stichtag ohne Termin in der Wohnung.",
},
{
icon: <FileCheck2 strokeWidth={1.6} />,
title: "Mit Pflichtangaben",
sub: "Energieträger, Entgelte und Vergleichswerte nach § 6a.",
},
{
icon: <Layers strokeWidth={1.6} />,
title: "Ein Datenbestand",
sub: "Gebäude, Wohnungen und Geräte werden einmal strukturiert.",
},
]}
closing={
<Section className="pt-0 sm:pt-0 lg:pt-0">
<CtaBanner
icon={<Euro />}
title="Abrechnung und Messbetrieb aus einer Hand"
description="Wir gehen Abrechnungszeitraum, Verteilungsmaßstäbe und Ausstattung für Ihr Gebäude konkret durch."
actions={
<>
<Button asChild variant="inverse" size="lg">
<Link to="/konfigurator">Preis berechnen</Link>
</Button>
<Button asChild variant="inverseOutline" size="lg">
<Link to="/kontakt">Kontakt aufnehmen</Link>
</Button>
</>
}
/>
</Section>
}
>
<Section className="pt-0 sm:pt-0 lg:pt-0">
<SectionHeading
eyebrow="Grundlagen"
title="Was in die Abrechnung einfließt"
lead="Die Heizkostenverordnung gibt vor, welche Kosten verbrauchsabhängig zu verteilen sind und in welchem Rahmen. SolenOS setzt diese Regeln auf die erfassten Werte des Gebäudes an."
/>
<TopicCards
items={DISTRIBUTION}
className="sm:grid-cols-2 lg:grid-cols-4"
/>
</Section>
<Section className="pt-0 sm:pt-0 lg:pt-0">
<div className="grid items-start gap-10 lg:grid-cols-2 lg:gap-14">
<div>
<SectionHeading
eyebrow="Ablauf"
title="Ein Abrechnungsjahr mit SolenOS"
lead="Die Abrechnung ist kein eigenes Projekt am Jahresende, sondern das Ergebnis des laufenden Messbetriebs."
/>
<Button asChild variant="outline" size="lg" className="mt-8">
<Link to="/so-funktionierts">
So funktioniert SolenOS
<ArrowRight />
</Link>
</Button>
</div>
<Card className="gap-0 p-6 sm:p-8">
<NumberedList steps={YEAR_FLOW} />
</Card>
</div>
</Section>
<Section className="pt-0 sm:pt-0 lg:pt-0">
<SectionHeading
eyebrow="Im Vergleich"
title="Woher die Abrechnungsdaten kommen"
lead="Der Unterschied liegt nicht in der Rechenvorschrift, sondern in der Datenbeschaffung."
/>
<ComparisonTable
className="mt-10"
columns={BASIS_COLUMNS}
rows={BASIS_ROWS}
labelHead="Abrechnungsgrundlage"
/>
</Section>
<Section className="pt-0 sm:pt-0 lg:pt-0">
<SectionHeading
eyebrow="Leistungsumfang"
title="Enthalten und nicht enthalten"
/>
<div className="mt-10 grid gap-6 lg:grid-cols-2">
<Card className="gap-0 p-6 sm:p-7">
<h3 className="text-base font-bold text-navy">Enthalten</h3>
<Checklist items={INCLUDED} className="mt-5" />
</Card>
<Card className="gap-0 p-6 sm:p-7">
<h3 className="text-base font-bold text-navy">
Nicht Teil der Heizkostenabrechnung
</h3>
<Checklist items={NOT_INCLUDED} className="mt-5" tone="excluded" />
<p className="mt-6 text-sm leading-relaxed text-muted-foreground">
Die Kosten der Verbrauchserfassung, ihrer Verwendung und der
Abrechnungs- und Verbrauchsinformationen gehören zu den
umlagefähigen Betriebskosten der Heizungsanlage.
</p>
<p className="mt-2 text-xs text-muted-foreground">
§ 7 Absatz 2 HeizkostenV
</p>
</Card>
</div>
</Section>
<Section className="pt-0 sm:pt-0 lg:pt-0">
<SectionHeading
align="center"
eyebrow="Häufige Fragen"
title="Fragen zur Heizkostenabrechnung"
/>
<FaqAccordion className="mt-10" items={FAQS} />
</Section>
<ProductCrossLinks current="/produkte/heizkostenabrechnung" />
</ProductPage>
)
}
@@ -1,3 +1,4 @@
import type * as React from "react"
import { Link } from "react-router-dom" import { Link } from "react-router-dom"
import { ArrowRight, Euro } from "lucide-react" import { ArrowRight, Euro } from "lucide-react"
@@ -7,36 +8,30 @@ import { CtaBanner } from "@/components/solenos/cta-banner"
import { Container, Section, SectionHeading } from "@/components/solenos/section" import { Container, Section, SectionHeading } from "@/components/solenos/section"
import { PageBreadcrumb } from "@/components/solenos/page-breadcrumb" import { PageBreadcrumb } from "@/components/solenos/page-breadcrumb"
import { AssetRender, renders } from "@/components/solenos/site-assets" import { AssetRender, renders } from "@/components/solenos/site-assets"
import { PRODUCTS } from "@/pages/products/product-page"
const products = [ /**
{ * One illustration per product, keyed by route so the registry stays free of
title: "UVI", * layout decisions. The first three products are the bookable services and get
sub: "Monatliche Verbrauchsinformation", * a card each; the fourth is the technical layer underneath them and closes the
description: * grid as a wide strip.
"Verbrauchsdaten werden automatisch erfasst und die regelmäßigen Verbrauchsinformationen für die Bewohner bereitgestellt.", */
illustration: ( const illustrations: Record<string, React.ReactNode> = {
<div className="relative"> "/produkte/uvi": (
<AssetRender render={renders.gateway} imgClassName="w-28" /> <AssetRender render={renders.uviGlassPane} alt="" className="w-32" />
</div>
), ),
}, "/produkte/heizkostenabrechnung": (
{ <AssetRender render={renders.glassMoney} alt="" className="w-28" />
title: "Heizkostenabrechnung", ),
sub: "Verbrauchsdaten korrekt abrechnen", "/produkte/rauchwarnmelder": (
description: <AssetRender render={renders.glassSmokeDetector} alt="" className="w-24" />
"Verbrauchsdaten strukturiert erfassen und für die jährliche Abrechnung nutzen.", ),
illustration: <AssetRender render={renders.glassDonutShadow} alt="" className="w-28" />, }
},
{
title: "Rauchwarnmelder",
sub: "Sicher überwacht, zentral verwaltet",
description:
"Rauchwarnmelder gemeinsam mit der übrigen Gebäudetechnik planen und verwalten.",
illustration: <AssetRender render={renders.smokeAlarm} imgClassName="w-28" />,
},
]
export default function ProductsPage() { export default function ProductsPage() {
const services = PRODUCTS.slice(0, 3)
const infrastructure = PRODUCTS[3]
return ( return (
<> <>
<div className="bg-hero-glow"> <div className="bg-hero-glow">
@@ -44,34 +39,33 @@ export default function ProductsPage() {
<Section className="pt-6 pb-10 sm:pt-7 sm:pb-12 lg:pt-8 lg:pb-14"> <Section className="pt-6 pb-10 sm:pt-7 sm:pb-12 lg:pt-8 lg:pb-14">
<PageBreadcrumb <PageBreadcrumb
className="mb-4" className="mb-4"
items={[ items={[{ label: "Startseite", to: "/" }, { label: "Produkte" }]}
{ label: "Startseite", to: "/" },
{ label: "Produkte" },
]}
/> />
<SectionHeading <SectionHeading
title="Unsere Produkte" title="Unsere Produkte"
lead="Hier finden Sie eine Übersicht unserer Produkte für Messtechnik, Abrechnung und Verwaltung." lead="Hier finden Sie eine Übersicht unserer Produkte für Messtechnik, Abrechnung und Verwaltung."
/> />
<div className="mt-10 grid gap-6 sm:grid-cols-2 lg:grid-cols-3"> <div className="mt-10 grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{products.map((s) => ( {services.map((product) => (
<Card key={s.title} className="gap-0 p-6 sm:p-7"> <Card key={product.to} className="gap-0 p-6 sm:p-7">
<h3 className="text-xl font-extrabold tracking-tight text-navy"> <h3 className="text-xl font-extrabold tracking-tight text-navy">
{s.title} {product.title}
</h3> </h3>
<p className="mt-1 text-sm text-muted-foreground">{s.sub}</p> <p className="mt-1 text-sm text-muted-foreground">
{product.sub}
</p>
<div className="my-6 flex h-32 flex-1 items-center justify-center"> <div className="my-6 flex h-32 flex-1 items-center justify-center">
{s.illustration} {illustrations[product.to]}
</div> </div>
<p className="text-sm leading-relaxed text-muted-foreground"> <p className="text-sm leading-relaxed text-muted-foreground">
{s.description} {product.summary}
</p> </p>
<Button <Button
variant="link" variant="link"
className="mt-3 self-start px-0" className="mt-3 self-start px-0"
asChild asChild
> >
<Link to="/produkte"> <Link to={product.to}>
Mehr erfahren Mehr erfahren
<ArrowRight /> <ArrowRight />
</Link> </Link>
@@ -87,15 +81,14 @@ export default function ProductsPage() {
/> />
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<h3 className="text-sm font-bold text-navy"> <h3 className="text-sm font-bold text-navy">
Messtechnik und Infrastruktur {infrastructure.title}
</h3> </h3>
<p className="mt-0.5 text-sm leading-relaxed text-muted-foreground"> <p className="mt-0.5 text-sm leading-relaxed text-muted-foreground">
Fernauslesbare Zähler, Sensoren und {infrastructure.summary}
Kommunikationsinfrastruktur für den laufenden Betrieb.
</p> </p>
</div> </div>
<Button variant="link" className="shrink-0 px-0 sm:px-4" asChild> <Button variant="link" className="shrink-0 px-0 sm:px-4" asChild>
<Link to="/produkte"> <Link to={infrastructure.to}>
Mehr erfahren Mehr erfahren
<ArrowRight /> <ArrowRight />
</Link> </Link>
@@ -0,0 +1,396 @@
import { Link } from "react-router-dom"
import {
ArrowRight,
ChevronRight,
Droplets,
Flame,
Gauge,
Lock,
Puzzle,
RadioTower,
Router,
Users,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
import { Checklist } from "@/components/solenos/checklist"
import { CtaBanner } from "@/components/solenos/cta-banner"
import { FaqAccordion, type FaqItem } from "@/components/solenos/faq-accordion"
import {
GermanyIcon,
PreconfiguredIcon,
} from "@/components/solenos/line-icons"
import { NoticeArt, NoticeBanner } from "@/components/solenos/notice-banner"
import { StepFlow } from "@/components/solenos/process-steps"
import { Section, SectionHeading } from "@/components/solenos/section"
import {
AssetRender,
renders,
vectors,
} from "@/components/solenos/site-assets"
import { TopicCards, type Topic } from "@/components/solenos/topic-cards"
import { TrustPanel, TrustShieldArt } from "@/components/solenos/trust-panel"
import { heizkostenv } from "@/lib/legal"
import { ProductCrossLinks, ProductPage } from "@/pages/products/product-page"
const MESSEV_ANLAGE_7 =
"https://www.gesetze-im-internet.de/messev/anlage_7.html"
/**
* Hero art: the gateway as the subject the piece that ties the building
* together with the heat meter beside it and the wireless orb as the accent
* for the radio link between them. Decorative; the copy carries the meaning.
*/
function InfrastructureHeroArt() {
return (
<div className="relative mx-auto flex aspect-[1.3] w-full max-w-md items-center">
<img
src={vectors.wirelessOrb}
alt=""
className="absolute top-[2%] right-[6%] w-[22%]"
/>
<AssetRender
render={renders.gateway}
className="mx-auto w-[62%]"
loading="eager"
/>
<AssetRender
render={renders.heatMeter}
className="absolute bottom-0 left-0 w-[34%]"
/>
</div>
)
}
const COMPONENTS: Topic[] = [
{
icon: <Flame />,
title: "Heizkostenverteiler",
body: "Elektronische Heizkostenverteiler erfassen an jedem Heizkörper den anteiligen Wärmeverbrauch. Sie sind keine Messgeräte im Sinne des Eichrechts und daher nicht eichpflichtig; getauscht werden sie nach Ablauf ihrer Nutzungsdauer.",
source: { cite: "§ 5 HeizkostenV", href: heizkostenv("5") },
},
{
icon: <Gauge />,
title: "Wärmezähler",
body: "Wo die Wärme je Einheit direkt gemessen wird etwa bei Fußbodenheizung oder Wohnungsstationen erfassen Wärmezähler die Wärmemenge in Kilowattstunden. Ihre Eichfrist beträgt sechs Jahre.",
source: { cite: "Anlage 7 MessEV", href: MESSEV_ANLAGE_7 },
},
{
icon: <Droplets />,
title: "Kalt- und Warmwasserzähler",
body: "Warmwasserzähler sind für die verbrauchsabhängige Abrechnung erforderlich, Kaltwasserzähler ergänzen die Erfassung. Für beide gilt eine Eichfrist von sechs Jahren.",
source: { cite: "Anlage 7 MessEV", href: MESSEV_ANLAGE_7 },
},
{
icon: <Router />,
title: "Gateway und Datenübertragung",
body: "Das Gateway empfängt die Funkdaten der Geräte im Gebäude, puffert sie und überträgt sie an SolenOS. Ein Zugang zu den einzelnen Wohnungen ist dafür nicht erforderlich.",
},
]
const DATA_PATH = [
{
media: <AssetRender render={renders.heatMeter} imgClassName="w-28" />,
title: "Gerät misst und funkt",
description:
"Zähler und Heizkostenverteiler senden ihre Werte per Funk; die Nutzdaten sind dabei AES-geschützt.",
},
{
media: <AssetRender render={renders.gateway} imgClassName="w-28" />,
title: "Gateway sammelt",
description:
"Ein Gateway je Gebäude empfängt die Funkpakete aller angebundenen Geräte.",
},
{
media: (
<AssetRender render={renders.refreshLoopCloud} imgClassName="w-28" />
),
title: "Übertragung an SolenOS",
description:
"Die Daten werden TLS-verschlüsselt an die Plattform übertragen und dort verarbeitet.",
},
{
media: (
<AssetRender render={renders.laptopDashboard} imgClassName="w-40" />
),
title: "Nutzung im Portal",
description:
"Aus denselben Werten entstehen Geräteüberwachung, monatliche UVI und Jahresabrechnung.",
},
]
const LIFECYCLE = [
{
device: "Wärmezähler",
interval: "Eichfrist 6 Jahre",
note: "Austausch oder Nacheichung vor Ablauf der Eichfrist",
},
{
device: "Kalt- und Warmwasserzähler",
interval: "Eichfrist 6 Jahre",
note: "seit der Novelle der MessEV einheitlich sechs Jahre",
},
{
device: "Elektronische Heizkostenverteiler",
interval: "nicht eichpflichtig",
note: "Austausch nach Ablauf der Nutzungsdauer des Geräts",
},
{
device: "Rauchwarnmelder",
interval: "Austausch nach 10 Jahren",
note: "jährliche Inspektion nach DIN 14676-1, per Ferninspektion möglich",
},
]
const REUSE_POSSIBLE = [
"Geräte sind fernablesbar, also ohne Zugang zur Wohnung auslesbar",
"die Funkschnittstelle folgt einem offenen Standard (OMS)",
"das Schlüsselmaterial der Geräte liegt vor",
"die Eichfrist der betroffenen Zähler ist nicht abgelaufen",
]
const REUSE_IMPOSSIBLE = [
"nicht fernablesbare Verbrauchserfassung",
"herstellergebundene Funkprotokolle ohne verfügbares Schlüsselmaterial",
"Zähler mit abgelaufener Eichfrist",
]
const TRUST_PROPERTIES = [
{ icon: <GermanyIcon />, label: "Hosting in Deutschland" },
{ icon: <Lock />, label: "AES-geschützte Funkdaten" },
{ icon: <RadioTower />, label: "TLS-Übertragung ab Gateway" },
{ icon: <Users />, label: "Rollenbasierte Zugriffsrechte" },
]
const FAQS: FaqItem[] = [
{
question: "Was kostet die Messtechnik?",
answer:
"Die für den Messbetrieb benötigten Sensoren sind im UVI-Komplettbetrieb enthalten: 10,50 € je Wohnung und Monat, grundsätzlich umlagefähig. Welche Geräte in welcher Anzahl erforderlich sind, ergibt sich aus der Gebäudekonfiguration.",
},
{
question: "Kann ich vorhandene Zähler weiterverwenden?",
answer:
"Das hängt von den Geräten ab. Voraussetzung ist, dass sie fernablesbar sind, auf offenen Standards basieren und das Schlüsselmaterial vorliegt. Vorhandene Messtechnik wird deshalb bei der vollständigen Konfiguration je Gebäude und Einheit erfasst und geprüft.",
},
{
question: "Was bedeutet Interoperabilität konkret?",
answer:
"Nach § 5 Absatz 5 HeizkostenV dürfen seit dem 1. Dezember 2022 nur noch fernauslesbare Ausstattungen installiert werden, die mit Geräten gleicher Art anderer Hersteller interoperabel sind ein anderer Dienstleister muss sie also selbst fernablesen können. Das Schlüsselmaterial ist dem Gebäudeeigentümer kostenfrei zur Verfügung zu stellen.",
},
{
question: "Braucht das Gateway einen Internetanschluss im Gebäude?",
answer:
"Nein. Das Gateway überträgt die gesammelten Daten über Mobilfunk, sodass kein Anschluss im Haus und keine Mitnutzung eines fremden Netzes erforderlich ist.",
},
{
question: "Wird gemeldet, wenn ein Gerät ausfällt?",
answer:
"Ja. Ausbleibende Werte, Batteriewarnungen und Manipulationsmeldungen laufen im Portal auf, sodass ein Gerät vor der nächsten Abrechnung geprüft werden kann nicht erst am Jahresende.",
},
]
export default function MesstechnikProductPage() {
return (
<ProductPage
breadcrumb="Messtechnik und Infrastruktur"
eyebrow="Produkt"
title={
<span className="block text-[2.25rem] text-pretty sm:text-[2.75rem]">
Messtechnik und Infrastruktur
</span>
}
tagline="Die Grundlage für alles andere."
lead="Zähler, Heizkostenverteiler und Gateway erfassen und übertragen die Werte, aus denen UVI und Heizkostenabrechnung entstehen fernablesbar, interoperabel und objektbezogen vorkonfiguriert geliefert."
actions={
<>
<Button asChild size="lg">
<Link to="/konfigurator">Ausstattung und Preis ermitteln</Link>
</Button>
<Button asChild variant="outline" size="lg">
<Link to="/wissen/datenschutz-sicherheit">
Datenschutz & Sicherheit
</Link>
</Button>
</>
}
note="Wenige Angaben · unverbindliche Kostenschätzung"
visual={<InfrastructureHeroArt />}
features={[
{
icon: <RadioTower strokeWidth={1.6} />,
title: "Fernablesbar",
sub: "Ablesung ohne Zugang zu den einzelnen Wohnungen.",
},
{
icon: <Puzzle strokeWidth={1.6} />,
title: "Interoperabel",
sub: "Offene Funkstandards, Schlüsselmaterial bleibt verfügbar.",
},
{
icon: <PreconfiguredIcon />,
title: "Vorkonfiguriert",
sub: "Geräte kommen Gebäude und Einheit zugeordnet an.",
},
{
icon: <GermanyIcon />,
title: "Daten in Deutschland",
sub: "Verschlüsselte Übertragung, Hosting in Falkenstein.",
},
]}
closing={
<Section className="pt-0 sm:pt-0 lg:pt-0">
<CtaBanner
icon={<Gauge />}
title="Welche Ausstattung braucht Ihr Gebäude?"
description="Aus Gebäudedaten, Wärmeverteilung und Wohnungszahl ermitteln wir Geräteart und Menge und daraus den Preis."
actions={
<>
<Button asChild variant="inverse" size="lg">
<Link to="/konfigurator">Ausstattung ermitteln</Link>
</Button>
<Button asChild variant="inverseOutline" size="lg">
<Link to="/kontakt">Bestand prüfen lassen</Link>
</Button>
</>
}
/>
</Section>
}
>
<Section className="pt-0 sm:pt-0 lg:pt-0">
<SectionHeading
eyebrow="Komponenten"
title="Was im Gebäude verbaut wird"
lead="Welche Geräte zum Einsatz kommen, hängt von der Wärmeverteilung und der Einbausituation ab. Zusammengestellt und vorbereitet wird die Ausstattung objektbezogen."
/>
<TopicCards
items={COMPONENTS}
className="sm:grid-cols-2 lg:grid-cols-4"
/>
</Section>
<Section className="pt-0 sm:pt-0 lg:pt-0">
<SectionHeading
eyebrow="Datenweg"
title="Vom Heizkörper bis ins Portal"
lead="Jeder Abschnitt hat seine eigene Schutzmaßnahme von der Funkstrecke in der Wohnung bis zur Verarbeitung auf der Plattform."
/>
<StepFlow steps={DATA_PATH} className="mt-10" />
</Section>
<Section className="pt-0 sm:pt-0 lg:pt-0">
<NoticeBanner
title="Nachrüstung bis 31.12.2026 erforderlich"
source={{ cite: "§ 5 HeizkostenV", href: heizkostenv("5") }}
visual={<NoticeArt className="max-lg:hidden" />}
action={
<Button
asChild
variant="link"
className="h-auto p-0 text-[15px] font-bold text-brand-800 underline decoration-brand-500 decoration-2 underline-offset-[6px] hover:text-navy hover:decoration-brand-800"
>
<Link to="/wissen/gesetzliche-anforderungen">
Mehr erfahren
<ChevronRight />
</Link>
</Button>
}
>
Nicht fernablesbare Verbrauchserfassung muss grundsätzlich bis zum
31. Dezember 2026 durch Nachrüstung oder Austausch die Anforderungen
an Fernablesbarkeit und Interoperabilität erfüllen.
</NoticeBanner>
</Section>
<Section className="pt-0 sm:pt-0 lg:pt-0">
<SectionHeading
eyebrow="Bestand"
title="Was übernommen werden kann"
lead="Nicht jedes Gebäude beginnt bei null. Entscheidend ist, ob die vorhandenen Geräte fernablesbar und offen angebunden sind."
/>
<div className="mt-10 grid gap-6 lg:grid-cols-2">
<Card className="gap-0 p-6 sm:p-7">
<h3 className="text-base font-bold text-navy">
Weiterverwendung möglich, wenn
</h3>
<Checklist items={REUSE_POSSIBLE} className="mt-5" />
</Card>
<Card className="gap-0 p-6 sm:p-7">
<h3 className="text-base font-bold text-navy">
Austausch erforderlich bei
</h3>
<Checklist
items={REUSE_IMPOSSIBLE}
className="mt-5"
tone="excluded"
/>
<p className="mt-6 text-sm leading-relaxed text-muted-foreground">
Was übernommen werden kann, wirkt sich unmittelbar auf die
Geräteliste und damit auf den Preis aus. Der Bestand wird deshalb
bei der vollständigen Objektkonfiguration je Einheit erfasst.
</p>
</Card>
</div>
</Section>
<Section className="pt-0 sm:pt-0 lg:pt-0">
<SectionHeading
eyebrow="Fristen"
title="Eichfristen und Nutzungsdauern"
lead="Wann ein Gerät getauscht werden muss, ergibt sich aus dem Eichrecht oder aus seiner Nutzungsdauer. Fällige Termine werden je Gebäude im Portal geführt."
/>
<Card className="mt-10 gap-0 divide-y divide-border p-0">
{LIFECYCLE.map((item) => (
<div
key={item.device}
className="flex flex-col gap-1 p-5 sm:flex-row sm:items-center sm:gap-6 sm:px-7"
>
<h3 className="text-sm font-bold text-navy sm:w-72 sm:shrink-0">
{item.device}
</h3>
<p className="text-sm font-semibold text-primary sm:w-56 sm:shrink-0">
{item.interval}
</p>
<p className="text-sm leading-relaxed text-muted-foreground">
{item.note}
</p>
</div>
))}
</Card>
<p className="mt-5 text-xs leading-relaxed text-muted-foreground">
Eichfristen nach Anlage 7 der Mess- und Eichverordnung. Elektronische
Heizkostenverteiler unterliegen nicht dem Eichrecht.
</p>
</Section>
<Section className="pt-0 sm:pt-0 lg:pt-0">
<TrustPanel
title="Messdaten sind auf jedem Abschnitt geschützt"
items={TRUST_PROPERTIES}
visual={<TrustShieldArt />}
action={
<Button asChild variant="link" className="h-auto p-0 font-bold">
<Link to="/wissen/datenschutz-sicherheit">
Mehr zu Datenschutz & Sicherheit
<ArrowRight />
</Link>
</Button>
}
/>
</Section>
<Section className="pt-0 sm:pt-0 lg:pt-0">
<SectionHeading
align="center"
eyebrow="Häufige Fragen"
title="Fragen zu Messtechnik und Infrastruktur"
/>
<FaqAccordion className="mt-10" items={FAQS} />
</Section>
<ProductCrossLinks current="/produkte/messtechnik-infrastruktur" />
</ProductPage>
)
}
@@ -0,0 +1,196 @@
import * as React from "react"
import { Link } from "react-router-dom"
import { ArrowRight } from "lucide-react"
import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
import { FeatureBand, type FeatureBandItem } from "@/components/solenos/feature-band"
import { PageBreadcrumb } from "@/components/solenos/page-breadcrumb"
import { PageHero } from "@/components/solenos/page-hero"
import { Container, Section, SectionHeading } from "@/components/solenos/section"
import { icons } from "@/components/solenos/site-assets"
type Product = {
to: string
/** short label for navigation and cross-links */
label: string
/** headline used on the overview cards */
title: string
/** one-line positioning under the card title */
sub: string
summary: string
/** baked-in disc icon, used wherever the four products are listed */
icon: string
}
/**
* The four products, in the order they are presented everywhere
* (overview grid, header menu, cross-links at the foot of each product page).
*/
const PRODUCTS: Product[] = [
{
to: "/produkte/uvi",
label: "UVI",
title: "UVI",
sub: "Monatliche Verbrauchsinformation",
summary:
"Verbrauchsdaten werden automatisch erfasst und die monatliche Verbrauchsinformation für die Bewohner erzeugt und bereitgestellt ohne Ablesetermin in der Wohnung.",
icon: icons.uvi,
},
{
to: "/produkte/heizkostenabrechnung",
label: "Heizkostenabrechnung",
title: "Heizkostenabrechnung",
sub: "Verbrauchsdaten korrekt abrechnen",
summary:
"Die jährliche Abrechnung entsteht aus denselben erfassten Verbrauchsdaten: Verteilung nach HeizkostenV, Nutzerwechsel berücksichtigt, Dokumente im Portal.",
icon: icons.billing,
},
{
to: "/produkte/rauchwarnmelder",
label: "Rauchwarnmelder",
title: "Rauchwarnmelder",
sub: "Sicher überwacht, zentral verwaltet",
summary:
"Ferninspizierbare Rauchwarnmelder inklusive Montage, jährlicher Inspektion nach DIN 14676-1 und lückenloser Dokumentation im Portal.",
icon: icons.smokeAlarm,
},
{
to: "/produkte/messtechnik-infrastruktur",
label: "Messtechnik und Infrastruktur",
title: "Messtechnik und Infrastruktur",
sub: "Die technische Grundlage",
summary:
"Fernablesbare Zähler und Heizkostenverteiler, Gateway und Datenübertragung vorkonfiguriert geliefert und laufend überwacht.",
icon: icons.radio,
},
]
function ProductIcon({
src,
className = "size-12",
}: {
src: string
className?: string
}) {
return <img src={src} alt="" className={className} loading="lazy" />
}
function ProductBreadcrumb({ current }: { current: string }) {
return (
<PageBreadcrumb
items={[
{ label: "Startseite", to: "/" },
{ label: "Produkte", to: "/produkte" },
{ label: current },
]}
/>
)
}
/**
* Shared product page shell: split PageHero with the
* Startseite → Produkte → product breadcrumb, a feature band summarising the
* product in four properties, the product's own sections, then the closing
* slot (CtaBanner).
*/
function ProductPage({
breadcrumb,
eyebrow,
title,
tagline,
lead,
actions,
note,
visual,
features,
children,
closing,
}: {
/** last breadcrumb crumb; also the product's own name */
breadcrumb: string
eyebrow?: React.ReactNode
title: React.ReactNode
/** short bold subline under the H1 */
tagline?: React.ReactNode
lead?: React.ReactNode
actions?: React.ReactNode
note?: React.ReactNode
visual?: React.ReactNode
features: FeatureBandItem[]
/** the product's spec sections between feature band and closing */
children?: React.ReactNode
closing?: React.ReactNode
}) {
return (
<>
<PageHero
breadcrumb={<ProductBreadcrumb current={breadcrumb} />}
eyebrow={eyebrow}
title={title}
lead={
<>
{tagline ? (
<span className="mb-2 block text-lg font-bold text-navy sm:text-xl">
{tagline}
</span>
) : null}
{lead}
</>
}
actions={actions}
note={note}
visual={visual}
/>
<Container>
<Section className="pt-2 sm:pt-4 lg:pt-6">
<FeatureBand items={features} />
</Section>
{children}
{closing}
</Container>
</>
)
}
/**
* Closes a product page out with the three other products, so the four pages
* form one navigable set instead of dead ends.
*/
function ProductCrossLinks({ current }: { current: string }) {
const others = PRODUCTS.filter((product) => product.to !== current)
return (
<Section className="pt-0 sm:pt-0 lg:pt-0">
<SectionHeading
eyebrow="Weitere Produkte"
title="Läuft auf derselben Grundlage"
lead="Alle SolenOS-Leistungen nutzen dieselbe Gebäudestruktur und dieselbe Messtechnik einzeln buchbar, jederzeit erweiterbar."
/>
<div className="mt-10 grid gap-5 sm:grid-cols-2 lg:grid-cols-3">
{others.map((product) => (
<Card key={product.to} className="gap-0 p-6 sm:p-7">
<ProductIcon src={product.icon} />
<h3 className="mt-5 text-lg font-extrabold tracking-tight text-balance text-navy">
{product.title}
</h3>
<p className="mt-3 text-sm leading-relaxed text-pretty text-muted-foreground">
{product.summary}
</p>
<div className="mt-6 flex-1 content-end">
<Button asChild variant="outline">
<Link to={product.to}>
Mehr erfahren
<ArrowRight />
</Link>
</Button>
</div>
</Card>
))}
</div>
</Section>
)
}
export { PRODUCTS, ProductCrossLinks, ProductIcon, ProductPage }
export type { Product }
@@ -0,0 +1,346 @@
import { Link } from "react-router-dom"
import {
ArrowRight,
BellRing,
ClipboardCheck,
ScrollText,
ShieldCheck,
Timer,
Wifi,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
import { Checklist } from "@/components/solenos/checklist"
import {
ComparisonTable,
type ComparisonColumn,
type ComparisonRow,
} from "@/components/solenos/comparison-table"
import { CtaBanner } from "@/components/solenos/cta-banner"
import { FaqAccordion, type FaqItem } from "@/components/solenos/faq-accordion"
import { PortalGaugeIcon } from "@/components/solenos/line-icons"
import { PriceCard } from "@/components/solenos/price-card"
import { StepRail } from "@/components/solenos/process-steps"
import { Section, SectionHeading } from "@/components/solenos/section"
import {
AssetRender,
renders,
vectors,
} from "@/components/solenos/site-assets"
import { TopicCards, type Topic } from "@/components/solenos/topic-cards"
import { SmokeDetectorIllustration } from "@/components/solenos/illustrations"
import { ProductCrossLinks, ProductPage } from "@/pages/products/product-page"
/**
* Hero art: the glass smoke detector as the subject with the real device
* beside it and the check orb as the accent device plus verified operational
* readiness. Decorative; the copy carries the meaning.
*/
function SmokeAlarmHeroArt() {
return (
<div className="relative mx-auto flex aspect-[1.3] w-full max-w-md items-center">
<img
src={vectors.checkOrb}
alt=""
className="absolute top-[4%] right-[6%] w-[20%]"
/>
<AssetRender
render={renders.glassSmokeDetector}
alt=""
className="mx-auto w-[64%]"
loading="eager"
/>
<AssetRender
render={renders.smokeAlarmAlpha}
className="absolute bottom-[2%] left-[2%] w-[30%]"
/>
</div>
)
}
const DUTIES: Topic[] = [
{
icon: <ScrollText />,
title: "Einbau und Betriebsbereitschaft",
body: "Rauchwarnmelder sind in Wohnungen nach den Bauordnungen der Länder vorgeschrieben. Wer für die Sicherstellung der Betriebsbereitschaft verantwortlich ist, regelt das jeweilige Landesrecht in den meisten Ländern der Eigentümer.",
},
{
icon: <ClipboardCheck />,
title: "Jährliche Inspektion",
body: "Die Anwendungsnorm DIN 14676-1 verlangt eine Inspektion mindestens einmal jährlich: Energieversorgung, Rauchzutrittsöffnungen und Umfeld des Melders werden geprüft.",
},
{
icon: <Wifi />,
title: "Ferninspektion nach Norm",
body: "Die DIN 14676-1 kennt neben der Vor-Ort-Inspektion die Teil- und die vollständige Ferninspektion. Geeignete Melder melden Verschmutzung, Batteriezustand und Demontage selbst ohne Termin in der Wohnung.",
},
{
icon: <Timer />,
title: "Austausch nach zehn Jahren",
body: "Rauchwarnmelder werden spätestens nach zehn Jahren Nutzungsdauer ausgetauscht. Verbaute Melder, Einbauorte und Austauschtermine sind im Portal je Wohnung dokumentiert.",
},
]
const ROLLOUT_STEPS = [
{
media: <AssetRender render={renders.houseSmallIso} imgClassName="w-24" />,
title: "Bedarf ermitteln",
description:
"Aus Wohnungen und Zimmerzahl ergibt sich, wie viele Melder je Einheit benötigt werden.",
},
{
media: (
<AssetRender render={renders.shippingBoxClosed} imgClassName="w-32" />
),
title: "Vorkonfiguriert erhalten",
description:
"Die Melder kommen bereits dem Gebäude und den geplanten Einheiten zugeordnet an.",
},
{
media: <AssetRender render={renders.drillTool} imgClassName="w-28" />,
title: "Montieren",
description:
"Montage durch Sie, Ihren Fachbetrieb oder als Komplettleistung durch SolenOS.",
},
{
media: <AssetRender render={renders.gateway} imgClassName="w-24" />,
title: "Anbinden",
description:
"Die Melder funken über dieselbe Gebäudeinfrastruktur wie die Verbrauchserfassung.",
},
{
media: (
<AssetRender render={renders.laptopDashboard} imgClassName="w-40" />
),
title: "Ferninspektion läuft",
description:
"Status, Meldungen und Inspektionsnachweise stehen laufend im Portal bereit.",
},
]
const INSPECTION_COLUMNS: ComparisonColumn[] = [
{ title: "Ferninspektion", highlight: true },
{ title: "Vor-Ort-Inspektion", check: false },
]
const INSPECTION_ROWS: ComparisonRow[] = [
{
label: "Termin in der Wohnung",
cells: ["nicht erforderlich", "je Wohnung, inklusive Nachterminen"],
},
{
label: "Prüfung",
cells: ["laufend durch den Melder", "einmal jährlich vor Ort"],
},
{
label: "Störungen",
cells: ["werden gemeldet, sobald sie auftreten", "fallen beim Jahrestermin auf"],
},
{
label: "Nachweis",
cells: ["automatisch dokumentiert", "Protokoll je Begehung"],
},
{
label: "Aufwand für Bewohner",
cells: ["keine Anwesenheitspflicht", "Anwesenheit erforderlich"],
},
]
const INCLUDED = [
"Rauchwarnmelder inklusive Montage und Eigentumsübertragung",
"Bedarfsermittlung je Wohnung aus den Gebäudedaten",
"regelmäßige Ferninspektion und Störungsmeldungen",
"Inspektionsnachweise je Wohnung im Portal",
"Support für Rückfragen von Verwaltung und Bewohnern",
]
const FAQS: FaqItem[] = [
{
question: "Wie oft müssen Rauchwarnmelder geprüft werden?",
answer:
"Mindestens einmal jährlich. Die Anwendungsnorm DIN 14676-1 lässt dafür neben der Vor-Ort-Inspektion auch die Teil- und die vollständige Ferninspektion zu, sofern die eingesetzten Melder dafür geeignet sind.",
},
{
question: "Ersetzt die Ferninspektion die Vor-Ort-Prüfung vollständig?",
answer:
"Bei Meldern, die für die vollständige Ferninspektion geeignet sind, kann die Sicherstellung der Betriebsbereitschaft über die Gerätelebensdauer aus der Ferne erfolgen. Anlassbezogene Einsätze bleiben möglich etwa wenn ein Melder demontiert wurde, dauerhaft verschmutzt ist oder ausgetauscht werden muss.",
},
{
question: "Wer ist für die Rauchwarnmelder verantwortlich?",
answer:
"Einbau und Sicherstellung der Betriebsbereitschaft richten sich nach der Bauordnung des jeweiligen Landes. In den meisten Ländern trägt der Eigentümer die Verantwortung für die Betriebsbereitschaft; SolenOS liefert die Melder, die Inspektion und die Nachweise, mit denen diese Pflicht erfüllt werden kann.",
},
{
question: "Wann müssen die Melder ausgetauscht werden?",
answer:
"Spätestens nach zehn Jahren Nutzungsdauer. Einbaudatum und fälliger Austausch sind je Gerät und Wohnung im Portal hinterlegt, sodass der Austausch mit dem übrigen Gebäudebetrieb geplant werden kann.",
},
{
question: "Sind die Kosten umlagefähig?",
answer:
"Die laufenden Kosten der Ferninspektion sind grundsätzlich als Betriebskosten umlagefähig. Die einmaligen Kosten der Hardware inklusive Montage und Eigentumsübertragung sind es nicht sie sind Anschaffungskosten des Eigentümers.",
},
{
question: "Kann ich Rauchwarnmelder später ergänzen?",
answer:
"Ja. Läuft der Messbetrieb bereits über SolenOS, wird die vorhandene Gebäudestruktur weiterverwendet: Wohnungen und Einheiten sind erfasst, die Melder werden lediglich ergänzt und angebunden.",
},
]
export default function RauchwarnmelderProductPage() {
return (
<ProductPage
breadcrumb="Rauchwarnmelder"
eyebrow="Produkt"
title="Rauchwarnmelder"
tagline="Geprüft, ohne zu klingeln."
lead="Ferninspizierbare Rauchwarnmelder inklusive Montage, jährlicher Inspektion nach DIN 14676-1 und lückenloser Dokumentation über dieselbe Gebäudeinfrastruktur wie die Verbrauchserfassung."
actions={
<>
<Button asChild size="lg">
<Link to="/konfigurator">Preis für mein Gebäude berechnen</Link>
</Button>
<Button asChild variant="outline" size="lg">
<Link to="/klarpreis">Preise ansehen</Link>
</Button>
</>
}
note="Wenige Angaben · unverbindliche Kostenschätzung"
visual={<SmokeAlarmHeroArt />}
features={[
{
icon: <Wifi strokeWidth={1.6} />,
title: "Ferninspizierbar",
sub: "Prüfung ohne Termin und ohne Anwesenheit der Bewohner.",
},
{
icon: <BellRing strokeWidth={1.6} />,
title: "Störung fällt sofort auf",
sub: "Verschmutzung, Batterie und Demontage werden gemeldet.",
},
{
icon: <ClipboardCheck strokeWidth={1.6} />,
title: "Nachweis je Wohnung",
sub: "Inspektionen und Austauschfristen sind dokumentiert.",
},
{
icon: <PortalGaugeIcon />,
title: "Im selben Portal",
sub: "Melder stehen neben Zählern und Verbrauchsdaten.",
},
]}
closing={
<Section className="pt-0 sm:pt-0 lg:pt-0">
<CtaBanner
icon={<ShieldCheck />}
title="Rauchwarnmelder für Ihr Gebäude"
description="Bedarf, Montage und Ferninspektion in einem Schritt auch zusätzlich zu einem laufenden Messbetrieb."
actions={
<>
<Button asChild variant="inverse" size="lg">
<Link to="/konfigurator">Preis berechnen</Link>
</Button>
<Button asChild variant="inverseOutline" size="lg">
<Link to="/kontakt">Kontakt aufnehmen</Link>
</Button>
</>
}
/>
</Section>
}
>
<Section className="pt-0 sm:pt-0 lg:pt-0">
<SectionHeading
eyebrow="Pflichten"
title="Was für Rauchwarnmelder gilt"
lead="Einbaupflicht, jährliche Inspektion und Austausch nach zehn Jahren sind gesetzt. Unterschiedlich ist nur, wie viel Aufwand daraus entsteht."
/>
<TopicCards items={DUTIES} className="sm:grid-cols-2 lg:grid-cols-4" />
</Section>
<Section className="pt-0 sm:pt-0 lg:pt-0">
<SectionHeading
eyebrow="Schritt für Schritt"
title="Von der Bedarfsermittlung zur laufenden Inspektion"
/>
<StepRail steps={ROLLOUT_STEPS} className="mt-10" />
</Section>
<Section className="pt-0 sm:pt-0 lg:pt-0">
<SectionHeading
eyebrow="Im Vergleich"
title="Ferninspektion oder Vor-Ort-Inspektion"
lead="Beide Verfahren erfüllen die Norm. Der Unterschied liegt im Aufwand für Verwaltung und Bewohner und darin, wie früh eine Störung auffällt."
/>
<ComparisonTable
className="mt-10"
columns={INSPECTION_COLUMNS}
rows={INSPECTION_ROWS}
labelHead="Inspektionsverfahren"
/>
</Section>
<Section className="pt-0 sm:pt-0 lg:pt-0">
<SectionHeading
eyebrow="Preis"
title="Hardware einmalig, Betrieb monatlich"
lead="Die Rauchwarnmelder werden einmalig abgerechnet und gehen in Ihr Eigentum über. Die laufende Ferninspektion wird monatlich je Wohnung berechnet."
/>
<div className="mt-10 grid gap-6 lg:grid-cols-[1fr_1fr_1.1fr]">
<PriceCard
title="RWM-Hardware"
cadence="Einmalig"
price="42,00 €"
unit="je Wohnung einmalig"
allocation={{ label: "Nicht umlagefähig", tone: "negative" }}
description="Rauchwarnmelder inklusive Montage und Eigentumsübertragung."
media={<SmokeDetectorIllustration />}
/>
<PriceCard
title="Ferninspektion"
cadence="Monatlich"
price="2,20 €"
unit="je Wohnung / Monat"
allocation={{
label: "Grundsätzlich umlagefähig",
tone: "positive",
}}
description="Regelmäßige Prüfung, Nachweise, Meldungen und Support."
media={
<div className="relative">
<SmokeDetectorIllustration />
<Wifi className="absolute top-1 -right-1 size-5 text-primary" />
</div>
}
/>
<Card className="gap-0 p-6 sm:p-7">
<h3 className="text-base font-bold text-navy">Enthalten</h3>
<Checklist items={INCLUDED} className="mt-5" />
<Button
asChild
variant="link"
className="mt-6 h-auto self-start p-0 font-bold"
>
<Link to="/klarpreis">
Alle Preise im SolenOS Klarpreis
<ArrowRight />
</Link>
</Button>
</Card>
</div>
</Section>
<Section className="pt-0 sm:pt-0 lg:pt-0">
<SectionHeading
align="center"
eyebrow="Häufige Fragen"
title="Fragen zu Rauchwarnmeldern"
/>
<FaqAccordion className="mt-10" items={FAQS} />
</Section>
<ProductCrossLinks current="/produkte/rauchwarnmelder" />
</ProductPage>
)
}
@@ -0,0 +1,348 @@
import { Link } from "react-router-dom"
import {
ArrowRight,
CalendarClock,
Euro,
FileCheck2,
RadioTower,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card"
import { Checklist } from "@/components/solenos/checklist"
import { CtaBanner } from "@/components/solenos/cta-banner"
import { FaqAccordion, type FaqItem } from "@/components/solenos/faq-accordion"
import { PortalGaugeIcon } from "@/components/solenos/line-icons"
import { NoticeArt, NoticeBanner } from "@/components/solenos/notice-banner"
import { PriceCard } from "@/components/solenos/price-card"
import { StepRail } from "@/components/solenos/process-steps"
import { Section, SectionHeading } from "@/components/solenos/section"
import {
AssetRender,
renders,
vectors,
} from "@/components/solenos/site-assets"
import { heizkostenv } from "@/lib/legal"
import { ProductCrossLinks, ProductPage } from "@/pages/products/product-page"
/**
* Hero art: the UVI pane as the subject what the resident receives with
* the consumption bars behind it and the consumption ring as the accent.
* Decorative; the copy carries the meaning.
*/
function UviHeroArt() {
return (
<div className="relative mx-auto flex aspect-[1.3] w-full max-w-md items-center">
<img
src={vectors.consumptionRing}
alt=""
className="absolute top-0 right-[2%] w-[24%]"
/>
<AssetRender
render={renders.uviGlassPane}
alt=""
className="mx-auto w-[76%]"
loading="eager"
/>
<AssetRender
render={renders.barChart}
alt=""
className="absolute bottom-[2%] left-0 w-[28%]"
/>
</div>
)
}
const MONTHLY_STEPS = [
{
media: <AssetRender render={renders.heatMeter} imgClassName="w-24" />,
title: "Verbrauch erfassen",
description:
"Wärmezähler, Heizkostenverteiler und Wasserzähler messen laufend je Wohnung.",
},
{
media: <AssetRender render={renders.gateway} imgClassName="w-24" />,
title: "Werte übertragen",
description:
"Das Gateway sammelt die Funkdaten im Gebäude und überträgt sie verschlüsselt an SolenOS.",
},
{
media: <AssetRender render={renders.refreshLoopCloud} imgClassName="w-28" />,
title: "Daten prüfen und zuordnen",
description:
"Die Werte werden plausibilisiert und Wohnung, Gerät und Nutzungszeitraum zugeordnet.",
},
{
media: <AssetRender render={renders.uviGlassPane} alt="" className="w-28" />,
title: "Verbrauchsinformation erzeugen",
description:
"Monatswert, Vergleichswerte und die nach § 6a HeizkostenV geforderten Angaben werden aufbereitet.",
},
{
media: (
<AssetRender render={renders.laptopDashboard} imgClassName="w-40" />
),
title: "Bewohnern bereitstellen",
description:
"Bewohner rufen ihre Information im Mieterportal ab, Eigentümer sehen den Versandstatus.",
},
]
const MONTHLY_CONTENT = [
"Verbrauch des Nutzers im letzten Monat in Kilowattstunden",
"Vergleich mit dem Vormonat desselben Nutzers",
"Vergleich mit dem entsprechenden Monat des Vorjahres, soweit die Daten erhoben wurden",
"Vergleich mit dem Verbrauch eines normierten Durchschnittsnutzers derselben Nutzerkategorie",
]
const BILLING_CONTENT = [
"Anteil der eingesetzten Energieträger, bei Fernwärme zusätzlich Treibhausgasemissionen und Primärenergiefaktor",
"erhobene Steuern, Abgaben und Zölle",
"Entgelte für Messtechnik, Eichung, Ablesung und Abrechnung",
"grafischer Vergleich des witterungsbereinigten Energieverbrauchs mit dem vorherigen Abrechnungszeitraum",
"Kontaktinformationen zu Energieeffizienz und Hinweise zur Streitbeilegung",
]
const INCLUDED = [
"fernablesbare Messtechnik für Wärme und Warmwasser",
"Gateway, Datenübertragung und Geräteüberwachung",
"monatliche Erzeugung der Verbrauchsinformation",
"Bereitstellung im Mieterportal für jede Wohnung",
"Nutzerwechsel ohne Ablesetermin vor Ort",
"Portalzugang für Eigentümer und Verwaltung",
]
const NOT_INCLUDED = [
"Jahresabrechnung der Heiz- und Warmwasserkosten",
"Rauchwarnmelder und deren Inspektion",
"Betriebskostenabrechnung des gesamten Gebäudes",
]
const FAQS: FaqItem[] = [
{
question: "Ab wann muss die Verbrauchsinformation monatlich erfolgen?",
answer:
"Sobald fernablesbare Ausstattungen zur Verbrauchserfassung installiert sind, sind die Verbrauchsinformationen nach § 6a Absatz 1 Nummer 2 HeizkostenV seit dem 1. Januar 2022 monatlich mitzuteilen. Für Abrechnungszeiträume ab dem 1. Dezember 2021 galt zunächst ein mindestens vierteljährlicher bzw. halbjährlicher Rhythmus.",
},
{
question: "Wie erhalten die Bewohner ihre Verbrauchsinformation?",
answer:
"Über das Mieterportal. Jeder Bewohner erhält einen eigenen Zugang und sieht ausschließlich die Werte der eigenen Wohnung und des eigenen Nutzungszeitraums nicht die Daten von Vor- oder Nachmietern.",
},
{
question: "Was passiert, wenn ein Bewohner keinen digitalen Zugang nutzt?",
answer:
"Die Information wird im Portal bereitgestellt und kann als Dokument ausgegeben werden, sodass die Verwaltung sie auf dem für das Gebäude vereinbarten Weg zustellen kann. Die Pflicht trifft den Gebäudeeigentümer; SolenOS liefert die dafür benötigten Inhalte vollständig aufbereitet.",
},
{
question: "Muss für die UVI jemand in die Wohnung?",
answer:
"Nein. Fernablesbar heißt, dass die Ausstattung ohne Zugang zu den einzelnen Nutzeinheiten abgelesen werden kann (§ 5 Absatz 2 HeizkostenV). Auch bei einem Nutzerwechsel werden die Werte zum Stichtag aus der laufenden Datenerfassung übernommen.",
},
{
question: "Kann ich die UVI ohne Heizkostenabrechnung buchen?",
answer:
"Ja. Der UVI-Komplettbetrieb umfasst die benötigte Messtechnik und die zugehörigen digitalen Dienstleistungen. Die Heizkostenabrechnung baut auf denselben erfassten Verbrauchsdaten auf und kann jederzeit ergänzt werden.",
},
]
export default function UviProductPage() {
return (
<ProductPage
breadcrumb="UVI"
eyebrow="Produkt"
title={
<span className="block text-[2.25rem] text-pretty sm:text-[2.75rem]">
Unterjährige Verbrauchs&shy;information
</span>
}
tagline="Monatlich. Automatisch. Nachweisbar."
lead="Sind fernablesbare Zähler installiert, muss jeder Nutzer monatlich über seinen Verbrauch informiert werden. SolenOS erzeugt diese Information aus den laufend erfassten Werten und stellt sie den Bewohnern im Mieterportal bereit."
actions={
<>
<Button asChild size="lg">
<Link to="/konfigurator">Preis für mein Gebäude berechnen</Link>
</Button>
<Button asChild variant="outline" size="lg">
<Link to="/wissen/gesetzliche-anforderungen">
Gesetzliche Anforderungen
</Link>
</Button>
</>
}
note="Wenige Angaben · unverbindliche Kostenschätzung"
visual={<UviHeroArt />}
features={[
{
icon: <CalendarClock strokeWidth={1.6} />,
title: "Monatlich automatisch",
sub: "Erfassung, Aufbereitung und Bereitstellung laufen ohne Zutun.",
},
{
icon: <RadioTower strokeWidth={1.6} />,
title: "Ohne Wohnungszugang",
sub: "Fernablesung statt Ablesetermin auch beim Nutzerwechsel.",
},
{
icon: <PortalGaugeIcon />,
title: "Eigener Bewohnerzugang",
sub: "Jede Wohnung sieht ausschließlich ihren eigenen Verbrauch.",
},
{
icon: <FileCheck2 strokeWidth={1.6} />,
title: "Pflichtangaben enthalten",
sub: "Monatswert und Vergleiche nach § 6a HeizkostenV.",
},
]}
closing={
<Section className="pt-0 sm:pt-0 lg:pt-0">
<CtaBanner
icon={<Euro />}
title="Was kostet die UVI für Ihr Gebäude?"
description="Mit wenigen Angaben zu Gebäude, Wohnungen und Wärmeverteilung erhalten Sie eine erste Preisindikation."
actions={
<>
<Button asChild variant="inverse" size="lg">
<Link to="/konfigurator">Preis berechnen</Link>
</Button>
<Button asChild variant="inverseOutline" size="lg">
<Link to="/kontakt">Kontakt aufnehmen</Link>
</Button>
</>
}
/>
</Section>
}
>
<Section className="pt-0 sm:pt-0 lg:pt-0">
<NoticeBanner
title="Monatliche Verbrauchsinformation ist Pflicht"
source={{ cite: "§ 6a HeizkostenV", href: heizkostenv("6a") }}
visual={<NoticeArt className="max-lg:hidden" />}
action={
<Button
asChild
variant="link"
className="h-auto p-0 text-[15px] font-bold text-brand-800 underline decoration-brand-500 decoration-2 underline-offset-[6px] hover:text-navy hover:decoration-brand-800"
>
<Link to="/wissen/gesetzliche-anforderungen">
Anforderungen im Detail
<ArrowRight />
</Link>
</Button>
}
>
Wo fernablesbare Ausstattungen installiert sind, sind die
Verbrauchsinformationen seit dem 1. Januar 2022 monatlich
mitzuteilen. Werden sie nicht oder nicht vollständig mitgeteilt,
können Nutzer ihren Kostenanteil um 3 % kürzen.
</NoticeBanner>
</Section>
<Section className="pt-0 sm:pt-0 lg:pt-0">
<SectionHeading
eyebrow="Inhalte"
title="Was die Bewohner erhalten"
lead="Die monatliche Information und die Angaben zur Jahresabrechnung sind in der HeizkostenV unterschiedlich geregelt. SolenOS bereitet beide Sätze aus derselben Datenbasis auf."
/>
<div className="mt-10 grid gap-6 lg:grid-cols-2">
<Card className="gap-0 p-6 sm:p-7">
<p className="text-xs font-bold tracking-[0.14em] text-primary uppercase">
Monatlich
</p>
<h3 className="mt-2 text-lg font-extrabold tracking-tight text-navy">
Verbrauchsinformation je Wohnung
</h3>
<Checklist items={MONTHLY_CONTENT} className="mt-5" />
<p className="mt-5 text-xs text-muted-foreground">
§ 6a Absatz 2 HeizkostenV
</p>
</Card>
<Card className="gap-0 p-6 sm:p-7">
<p className="text-xs font-bold tracking-[0.14em] text-primary uppercase">
Zur Abrechnung
</p>
<h3 className="mt-2 text-lg font-extrabold tracking-tight text-navy">
Zusätzliche Informationen
</h3>
<Checklist items={BILLING_CONTENT} className="mt-5" />
<p className="mt-5 text-xs text-muted-foreground">
§ 6a Absatz 3 HeizkostenV
</p>
</Card>
</div>
</Section>
<Section className="pt-0 sm:pt-0 lg:pt-0">
<SectionHeading
eyebrow="Schritt für Schritt"
title="So entsteht die monatliche UVI"
lead="Vom Messwert in der Wohnung bis zur Information im Mieterportal ein Ablauf, der nach der Installation ohne weitere Eingriffe läuft."
/>
<StepRail steps={MONTHLY_STEPS} className="mt-10" />
</Section>
<Section className="pt-0 sm:pt-0 lg:pt-0">
<SectionHeading
eyebrow="Leistungsumfang"
title="Was im UVI-Betrieb enthalten ist"
/>
<div className="mt-10 grid gap-6 lg:grid-cols-[1.15fr_0.85fr]">
<Card className="gap-0 p-6 sm:p-7">
<h3 className="text-base font-bold text-navy">Enthalten</h3>
<Checklist items={INCLUDED} className="mt-5" />
<h3 className="mt-8 text-base font-bold text-navy">
Nicht im UVI-Betrieb enthalten
</h3>
<Checklist items={NOT_INCLUDED} className="mt-5" tone="excluded" />
<p className="mt-5 text-sm leading-relaxed text-muted-foreground">
Heizkostenabrechnung und Rauchwarnmelder sind eigene Leistungen
einzeln buchbar und jederzeit ergänzbar.
</p>
</Card>
<div>
<PriceCard
title={
<span className="text-[1.15rem] whitespace-nowrap">
UVI-Komplettbetrieb
</span>
}
cadence="Monatlich"
price="10,50 €"
unit="je Wohnung / Monat"
allocation={{
label: "Grundsätzlich umlagefähig",
tone: "positive",
}}
description="Sensoren, Daten, Portal und Verbrauchsinformation inklusive."
media={
<AssetRender
render={renders.uviGlassPane}
alt=""
className="w-full"
/>
}
/>
<Button asChild variant="link" className="mt-4 h-auto p-0 font-bold">
<Link to="/klarpreis">
Alle Preise im SolenOS Klarpreis
<ArrowRight />
</Link>
</Button>
</div>
</div>
</Section>
<Section className="pt-0 sm:pt-0 lg:pt-0">
<SectionHeading
align="center"
eyebrow="Häufige Fragen"
title="Fragen zur Verbrauchsinformation"
/>
<FaqAccordion className="mt-10" items={FAQS} />
</Section>
<ProductCrossLinks current="/produkte/uvi" />
</ProductPage>
)
}
@@ -11,10 +11,9 @@ import {
} from "@/components/solenos/site-assets" } from "@/components/solenos/site-assets"
import { import {
TopicCards, TopicCards,
WissenArticle,
WissenRelated,
type Topic, type Topic,
} from "@/pages/wissen/wissen-article" } from "@/components/solenos/topic-cards"
import { WissenArticle, WissenRelated } from "@/pages/wissen/wissen-article"
const safeguards: Topic[] = [ const safeguards: Topic[] = [
{ {
@@ -11,19 +11,18 @@ import {
vectors, vectors,
} from "@/components/solenos/site-assets" } from "@/components/solenos/site-assets"
import { import {
HEIZKOSTENV,
TopicCards, TopicCards,
WissenArticle,
WissenRelated,
type Topic, type Topic,
} from "@/pages/wissen/wissen-article" } from "@/components/solenos/topic-cards"
import { heizkostenv } from "@/lib/legal"
import { WissenArticle, WissenRelated } from "@/pages/wissen/wissen-article"
const requirements: Topic[] = [ const requirements: Topic[] = [
{ {
icon: <BarChart3 />, icon: <BarChart3 />,
title: "Unterjährige Verbrauchsinformation", title: "Unterjährige Verbrauchsinformation",
body: "Monatliche Verbrauchsinformationen auf Basis der erfassten Verbrauchs- und Ablesewerte zur Umsetzung der Anforderungen des § 6a HeizkostenV.", body: "Monatliche Verbrauchsinformationen auf Basis der erfassten Verbrauchs- und Ablesewerte zur Umsetzung der Anforderungen des § 6a HeizkostenV.",
source: { cite: "§ 6a HeizkostenV", href: `${HEIZKOSTENV}/__6a.html` }, source: { cite: "§ 6a HeizkostenV", href: heizkostenv("6a") },
}, },
{ {
icon: <RadioTower />, icon: <RadioTower />,
@@ -34,7 +33,7 @@ const requirements: Topic[] = [
icon: <Puzzle />, icon: <Puzzle />,
title: "Interoperable Messtechnik", title: "Interoperable Messtechnik",
body: "Die eingesetzten Verbrauchserfassungsgeräte sind OMS-fähig. Soweit für die jeweilige Ausstattung anwendbar, erfüllt die gelieferte Messtechnik die Anforderungen an Interoperabilität und Stand der Technik nach § 5 HeizkostenV.", body: "Die eingesetzten Verbrauchserfassungsgeräte sind OMS-fähig. Soweit für die jeweilige Ausstattung anwendbar, erfüllt die gelieferte Messtechnik die Anforderungen an Interoperabilität und Stand der Technik nach § 5 HeizkostenV.",
source: { cite: "§ 5 HeizkostenV", href: `${HEIZKOSTENV}/__5.html` }, source: { cite: "§ 5 HeizkostenV", href: heizkostenv("5") },
}, },
] ]
@@ -2,76 +2,11 @@ import type * as React from "react"
import { Link } from "react-router-dom" import { Link } from "react-router-dom"
import { ArrowRight } from "lucide-react" import { ArrowRight } from "lucide-react"
import { cn } from "@/lib/utils"
import { Card, CardContent } from "@/components/ui/card"
import { IconBadge } from "@/components/solenos/icon-tile" import { IconBadge } from "@/components/solenos/icon-tile"
import { PageBreadcrumb } from "@/components/solenos/page-breadcrumb" import { PageBreadcrumb } from "@/components/solenos/page-breadcrumb"
import { PageHero } from "@/components/solenos/page-hero" import { PageHero } from "@/components/solenos/page-hero"
import { Container, Section } from "@/components/solenos/section" import { Container, Section } from "@/components/solenos/section"
/** The paragraph of the HeizkostenV a property is cited against. */
type LegalSource = {
cite: string
href: string
}
/** One technical property: line icon, title, body, optional legal citation. */
type Topic = {
icon: React.ReactNode
title: string
body: string
source?: LegalSource
}
const HEIZKOSTENV = "https://www.gesetze-im-internet.de/heizkostenv"
/** Cited source line, pinned to the card's baseline so rows stay aligned. */
function LegalSourceNote({ source }: { source: LegalSource }) {
return (
<p className="mt-auto pt-4 text-xs text-muted-foreground">
Quelle:{" "}
<a
href={source.href}
target="_blank"
rel="noreferrer"
className="font-medium text-brand-700 underline-offset-2 transition-colors hover:text-brand-800 hover:underline"
>
{source.cite}
</a>
</p>
)
}
/** Property grid: line icon over a navy title, body copy, optional citation. */
function TopicCards({
items,
className,
}: {
items: Topic[]
className?: string
}) {
return (
<div className={cn("mt-10 grid gap-5", className)}>
{items.map((item) => (
<Card key={item.title}>
<CardContent className="flex flex-1 flex-col">
<IconBadge variant="outline" shape="squircle" size="lg">
{item.icon}
</IconBadge>
<h3 className="mt-5 text-base font-bold text-balance text-navy">
{item.title}
</h3>
<p className="mt-2 text-sm leading-relaxed text-pretty text-muted-foreground">
{item.body}
</p>
{item.source ? <LegalSourceNote source={item.source} /> : null}
</CardContent>
</Card>
))}
</div>
)
}
/** /**
* Shared shell for the /wissen articles: split PageHero with the * Shared shell for the /wissen articles: split PageHero with the
* Startseite → Wissen → article breadcrumb, then the article's own sections. * Startseite → Wissen → article breadcrumb, then the article's own sections.
@@ -165,5 +100,4 @@ function WissenRelated({
) )
} }
export { HEIZKOSTENV, TopicCards, WissenArticle, WissenRelated } export { WissenArticle, WissenRelated }
export type { LegalSource, Topic }