This commit is contained in:
Lars Nolden
2026-08-14 13:34:12 +02:00
parent 728e2ec270
commit b6ed4118d1
41 changed files with 1071 additions and 81 deletions
+28 -2
View File
@@ -13,7 +13,7 @@ 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 SolutionsPage from "@/pages/solutions" import ProductsPage from "@/pages/products"
function ScrollToTop() { function ScrollToTop() {
const { pathname } = useLocation() const { pathname } = useLocation()
@@ -35,7 +35,7 @@ export default function App() {
<Routes> <Routes>
<Route path="/" element={<HomePage />} /> <Route path="/" element={<HomePage />} />
<Route path="/so-funktionierts" element={<HowItWorksPage />} /> <Route path="/so-funktionierts" element={<HowItWorksPage />} />
<Route path="/loesungen" element={<SolutionsPage />} /> <Route path="/produkte" element={<ProductsPage />} />
<Route path="/fuer-wen" element={<ForWhomPage />} /> <Route path="/fuer-wen" element={<ForWhomPage />} />
<Route <Route
path="/fuer-wen/hauseigentuemer" path="/fuer-wen/hauseigentuemer"
@@ -55,6 +55,32 @@ export default function App() {
/> />
<Route path="/konfigurator" element={<ConfiguratorPage />} /> <Route path="/konfigurator" element={<ConfiguratorPage />} />
<Route path="/klarpreis" element={<KlarpreisPage />} /> <Route path="/klarpreis" element={<KlarpreisPage />} />
<Route
path="/wissen"
element={
<SimplePage
breadcrumb={[
{ label: "Startseite", to: "/" },
{ label: "Wissen" },
]}
title="Wissen"
lead="Hintergründe zu Messtechnik, Abrechnung und gesetzlichen Vorgaben rund um Wohngebäude."
/>
}
/>
<Route
path="/technik-sicherheit"
element={
<SimplePage
breadcrumb={[
{ label: "Startseite", to: "/" },
{ label: "Technik & Sicherheit" },
]}
title="Technik & Sicherheit"
lead="Messtechnik, Datenübertragung, Hosting und Zugriffsrechte wie GebOS den laufenden Messbetrieb technisch und organisatorisch absichert."
/>
}
/>
<Route <Route
path="/unternehmen" path="/unternehmen"
element={ element={
Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 112 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

@@ -0,0 +1,84 @@
import * as React from "react"
import { Plus } from "lucide-react"
import { cn } from "@/lib/utils"
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion"
type FaqItem = {
question: React.ReactNode
answer: React.ReactNode
}
/**
* Toggle disc: hairline teal plus that fills in and turns into an × once its
* item opens. Driven by the trigger's own `group`, so hovering the answer body
* below leaves the disc alone.
*/
function FaqIndicator() {
return (
<span
aria-hidden="true"
className="flex size-8 shrink-0 items-center justify-center rounded-full border border-brand-100 bg-card text-primary shadow-pill transition-[transform,color,background-color,border-color] duration-200 group-hover:border-brand-200 group-hover:bg-accent group-data-[state=open]:rotate-45 group-data-[state=open]:border-transparent group-data-[state=open]:bg-primary group-data-[state=open]:text-primary-foreground"
>
<Plus className="size-4" strokeWidth={2.5} />
</span>
)
}
/**
* FAQ list: one white card per question, plus disc as the toggle, and the open
* card tinted brand-50 behind a mint hairline so the answer reads as its own
* panel. Single-open and collapsible — the block only ever moves one card's
* worth of height, and every answer can be closed again.
*
* Sized for a reading column (centred `max-w-3xl`) instead of the full 6xl
* container; override the width via `className`.
*/
function FaqAccordion({
items,
defaultOpen = 0,
className,
}: {
items: FaqItem[]
/** index of the item open on first paint; `null` starts fully collapsed */
defaultOpen?: number | null
className?: string
}) {
return (
<Accordion
type="single"
collapsible
defaultValue={defaultOpen == null ? undefined : `faq-${defaultOpen}`}
className={cn("mx-auto flex w-full max-w-3xl flex-col gap-3", className)}
>
{items.map((item, i) => (
<AccordionItem
key={i}
value={`faq-${i}`}
/* the all-round `border` supersedes the primitive's divider width,
which leaves `last:border-b-0` to strip the final card's bottom
edge — `last:border-b` puts it back. */
className="rounded-2xl border border-border/70 bg-card px-5 shadow-card transition-colors duration-200 last:border-b hover:border-brand-200 data-[state=open]:border-brand-200 data-[state=open]:bg-brand-50/50 sm:px-6"
>
<AccordionTrigger
className="group items-center gap-5 rounded-xl py-5 text-base leading-snug font-bold text-navy"
indicator={<FaqIndicator />}
>
{item.question}
</AccordionTrigger>
<AccordionContent className="pb-6 leading-relaxed text-pretty sm:pr-13">
{item.answer}
</AccordionContent>
</AccordionItem>
))}
</Accordion>
)
}
export { FaqAccordion }
export type { FaqItem }
@@ -20,7 +20,7 @@ function HeroArt({ className }: { className?: string }) {
role="img" role="img"
> >
<AssetRender <AssetRender
render={renders.barChart} render={renders.cubesRound}
alt="" alt=""
className="absolute bottom-[0%] right-[10%] w-[16%] scale-200 z-10" className="absolute bottom-[0%] right-[10%] w-[16%] scale-200 z-10"
/> />
@@ -0,0 +1,64 @@
import * as React from "react"
import { cn } from "@/lib/utils"
type InfoStripItem = {
/** self-contained visual (asset-kit render / icon) */
media: React.ReactNode
eyebrow: React.ReactNode
title: React.ReactNode
}
/**
* Wide white strip directly under a hero: hairline-divided columns (2 up on
* mobile, 3 from `sm`, 5 from `lg`), each a glass render over a teal eyebrow
* and a navy title. Dividers are drawn per column for the current layout so
* they stay inset from the card's padding.
*/
function InfoStrip({
items,
className,
}: {
items: InfoStripItem[]
className?: string
}) {
return (
<div
className={cn(
"grid grid-cols-2 rounded-3xl border border-border bg-card p-2 shadow-card sm:grid-cols-3 sm:p-3 lg:grid-cols-5",
className
)}
>
{items.map((item, i) => (
<div
key={i}
className={cn(
"flex flex-col items-center px-3 py-4 text-center sm:px-4",
/* dividers for the 2-up layout … */
i % 2 !== 0 ? "border-l border-border" : "border-l-0",
i >= 2 ? "border-t border-border" : "border-t-0",
/* … the 3-up layout … */
i % 3 !== 0 ? "sm:border-l" : "sm:border-l-0",
i >= 3 ? "sm:border-t" : "sm:border-t-0",
/* … and the 5-up layout */
i % 5 !== 0 ? "lg:border-l" : "lg:border-l-0",
"lg:border-t-0"
)}
>
<span className="flex h-14 items-center justify-center">
{item.media}
</span>
<p className="mt-3 text-[0.625rem] font-bold tracking-[0.14em] text-primary uppercase">
{item.eyebrow}
</p>
<div className="mt-1.5 text-xs leading-snug font-semibold text-balance text-navy/75 sm:text-[0.8125rem]">
{item.title}
</div>
</div>
))}
</div>
)
}
export { InfoStrip }
export type { InfoStripItem }
@@ -115,10 +115,24 @@ function PortalGaugeIcon(props: React.ComponentProps<"svg">) {
) )
} }
/**
* Germany's silhouette: the metering data never leaves the country. Traced
* from the border's waypoints, projected equirectangular at 51° N so the
* proportions hold, then thinned to the points that survive at icon size.
*/
function GermanyIcon(props: React.ComponentProps<"svg">) {
return (
<LineIcon {...props}>
<path d="M8.62 1.7 10.34 2.23 11.5 3.6 13.21 3.33 12.74 4.59 13.73 4.72 17.03 2.93 18.42 4.67 18.24 7.23 19.67 12.07 19.38 12.84 14.98 14.47 17.06 18.96 16.04 20.06 16.32 21.55 14.98 21.34 11.75 22.3 9.76 21.39 7.22 21.61 7.24 19.68 8 17.86 5.98 17.41 4.77 15.06 4.33 12.33 4.72 10.2 5.95 9.26 5.73 8.33 6.32 8.06 6.57 6.46 6.23 6.19 9.09 4.77 9.34 3.12Z" />
</LineIcon>
)
}
export { export {
CloudAutomationIcon, CloudAutomationIcon,
CrossedToolsIcon, CrossedToolsIcon,
EuroCircleIcon, EuroCircleIcon,
GermanyIcon,
LineIcon, LineIcon,
PortalGaugeIcon, PortalGaugeIcon,
PreconfiguredIcon, PreconfiguredIcon,
@@ -0,0 +1,157 @@
import * as React from "react"
import { cn } from "@/lib/utils"
import { AssetRender, renders } from "@/components/gebos/site-assets"
/**
* Filled warning triangle. lucide only ships the stroked outline, and at badge
* size the fill is what carries the signal. The mark is knocked out of the fill
* (`evenodd`, one path) so the plate underneath shows through instead of being
* painted over — the glyph stays correct on any backing colour.
*/
function WarningGlyph({ className, ...props }: React.ComponentProps<"svg">) {
return (
<svg
viewBox="0 0 24 24"
aria-hidden="true"
className={cn("shrink-0", className)}
{...props}
>
<path
fill="currentColor"
fillRule="evenodd"
d="M10.26 4.02a2 2 0 0 1 3.48 0l8.15 14.26a2 2 0 0 1-1.74 3.02H3.85a2 2 0 0 1-1.74-3.02L10.26 4.02Z
M12 8.6a1.02 1.02 0 0 0-1.02 1.02v4.16a1.02 1.02 0 0 0 2.04 0V9.62A1.02 1.02 0 0 0 12 8.6Z
M12 16.05a1.15 1.15 0 1 0 0 2.3 1.15 1.15 0 0 0 0-2.3Z"
/>
</svg>
)
}
/**
* Glass disc holding a white plate with the notice glyph: the same optics as
* the glass buttons, sized off the disc so it scales as one object.
*/
function NoticeSigil({
children,
className,
}: {
children: React.ReactNode
className?: string
}) {
return (
<div
className={cn(
"glass-veil inset-shadow-glass shadow-glass flex size-24 shrink-0 items-center justify-center rounded-full bg-white/25 backdrop-blur-sm sm:size-32 lg:size-36",
className
)}
>
<div className="bg-card shadow-card flex size-[65%] items-center justify-center rounded-full">
<span className="text-brand-600 flex size-[54%] items-center justify-center">
{children}
</span>
</div>
</div>
)
}
/**
* Notice card for a regulatory deadline: glass sigil left, headline and body
* centre, cited source underneath, brand art and the follow-up link right.
* The teal wash bleeds in behind the sigil and dissolves before the copy.
*/
function NoticeBanner({
title,
children,
source,
action,
visual,
icon,
className,
}: {
title: React.ReactNode
/** the notice body */
children: React.ReactNode
source?: {
/** what is being cited, e.g. "§ 5 HeizkostenV" */
cite: React.ReactNode
href: string
/** link text; defaults to `href` without its scheme */
label?: React.ReactNode
}
/** follow-up affordance, rendered under the visual */
action?: React.ReactNode
visual?: React.ReactNode
/** overrides the warning glyph in the sigil */
icon?: React.ReactNode
className?: string
}) {
return (
<aside
className={cn(
"border-border bg-card shadow-card relative isolate overflow-hidden rounded-3xl border",
className
)}
>
<span
aria-hidden="true"
className="bg-notice-wash pointer-events-none absolute inset-y-0 left-0 -z-10 w-2/5 opacity-70 sm:opacity-100"
/>
<div className="flex flex-col gap-6 p-6 sm:flex-row sm:flex-wrap sm:items-center sm:gap-8 sm:p-8 lg:flex-nowrap lg:gap-10 lg:p-10">
<NoticeSigil>
{icon ?? <WarningGlyph className="size-full" />}
</NoticeSigil>
<div className="min-w-0 flex-1">
<h2 className="text-navy text-2xl font-extrabold tracking-tight text-balance sm:text-3xl">
{title}
</h2>
<p className="text-navy/85 mt-3 max-w-2xl text-base leading-relaxed text-pretty sm:text-lg">
{children}
</p>
{source ? (
<p className="text-muted-foreground mt-5 text-xs sm:text-[0.8125rem]">
Quelle: {source.cite} {" "}
<a
href={source.href}
target="_blank"
rel="noreferrer"
className="text-brand-700 hover:text-brand-800 decoration-brand-300 hover:decoration-brand-600 font-medium break-words underline-offset-2 transition-colors hover:underline"
>
{source.label ?? source.href.replace(/^https?:\/\/(www\.)?/, "")}
</a>
</p>
) : null}
</div>
{visual || action ? (
<div className="flex w-full shrink-0 flex-col items-start gap-4 sm:items-end lg:w-[30%]">
{visual}
{action}
</div>
) : null}
</div>
</aside>
)
}
/**
* The notice's brand art: glass bars with the consumption donut settling in
* front of them. Decorative — the copy next to it carries the meaning.
*/
function NoticeArt({ className }: { className?: string }) {
return (
<div className={cn("relative aspect-[1.2] w-full", className)}>
<AssetRender
render={renders.barChart}
alt=""
className="absolute top-0 left-[4%] w-[64%]"
/>
<AssetRender
render={renders.glassDonutShadow}
alt=""
className="absolute right-0 bottom-0 w-[62%]"
/>
</div>
)
}
export { NoticeArt, NoticeBanner }
@@ -4,7 +4,7 @@ import { cn } from "@/lib/utils"
import { Container } from "@/components/gebos/section" import { Container } from "@/components/gebos/section"
/** /**
* Split hero: copy left (breadcrumb/eyebrow, H1, lead, CTAs, extra), * Split hero: copy left (breadcrumb/eyebrow, H1, lead, CTAs, note, extra),
* visual right, soft radial glow behind. * visual right, soft radial glow behind.
*/ */
function PageHero({ function PageHero({
@@ -12,6 +12,7 @@ function PageHero({
title, title,
lead, lead,
actions, actions,
note,
extra, extra,
visual, visual,
className, className,
@@ -20,6 +21,8 @@ function PageHero({
title: React.ReactNode title: React.ReactNode
lead?: React.ReactNode lead?: React.ReactNode
actions?: React.ReactNode actions?: React.ReactNode
/** microcopy tied to the actions (e.g. "Wenige Angaben · …") */
note?: React.ReactNode
/** slot below the actions (e.g. hint pill) */ /** slot below the actions (e.g. hint pill) */
extra?: React.ReactNode extra?: React.ReactNode
visual?: React.ReactNode visual?: React.ReactNode
@@ -51,6 +54,9 @@ function PageHero({
{actions} {actions}
</div> </div>
) : null} ) : null}
{note ? (
<p className="mt-3 text-xs text-muted-foreground">{note}</p>
) : null}
{extra ? <div className="mt-6">{extra}</div> : null} {extra ? <div className="mt-6">{extra}</div> : null}
</div> </div>
{visual ? <div className="relative w-full">{visual}</div> : null} {visual ? <div className="relative w-full">{visual}</div> : null}
@@ -51,10 +51,27 @@ import glassDonutShadowAvif from "@/assets/gebos/glass-donut-shadow.avif"
import glassDonutShadowWebp from "@/assets/gebos/glass-donut-shadow.webp" import glassDonutShadowWebp from "@/assets/gebos/glass-donut-shadow.webp"
import glassDonutAvif from "@/assets/gebos/glass-donut.avif" import glassDonutAvif from "@/assets/gebos/glass-donut.avif"
import glassDonutWebp from "@/assets/gebos/glass-donut.webp" import glassDonutWebp from "@/assets/gebos/glass-donut.webp"
import glassGatewayAvif from "@/assets/gebos/glass-gateway.avif"
import glassGatewayWebp from "@/assets/gebos/glass-gateway.webp"
import glassLaptopAvif from "@/assets/gebos/glass-laptop.avif"
import glassLaptopWebp from "@/assets/gebos/glass-laptop.webp"
import glassLockAvif from "@/assets/gebos/glass-lock.avif"
import glassLockWebp from "@/assets/gebos/glass-lock.webp"
import glassMoneyAvif from "@/assets/gebos/glass-money.avif"
import glassMoneyWebp from "@/assets/gebos/glass-money.webp"
import glassShieldLockAvif from "@/assets/gebos/glass-shield-lock.avif"
import glassShieldLockWebp from "@/assets/gebos/glass-shield-lock.webp"
import glassSmokeDetectorAvif from "@/assets/gebos/glass-smoke-detector.avif"
import glassSmokeDetectorWebp from "@/assets/gebos/glass-smoke-detector.webp"
import glassWaterMeterAvif from "@/assets/gebos/glass-water-meter.avif"
import glassWaterMeterWebp from "@/assets/gebos/glass-water-meter.webp"
import heatMeterAvif from "@/assets/gebos/heat-meter.avif" import heatMeterAvif from "@/assets/gebos/heat-meter.avif"
import heatMeterWebp from "@/assets/gebos/heat-meter.webp" import heatMeterWebp from "@/assets/gebos/heat-meter.webp"
import houseSmallIsoAvif from "@/assets/gebos/house-small-isometric.avif" import houseSmallIsoAvif from "@/assets/gebos/house-small-isometric.avif"
import houseSmallIsoWebp from "@/assets/gebos/house-small-isometric.webp" import houseSmallIsoWebp from "@/assets/gebos/house-small-isometric.webp"
import houseWithSprinterMp4 from "@/assets/gebos/house-with-sprinter.mp4"
import houseWithSprinterPosterWebp from "@/assets/gebos/house-with-sprinter-poster.webp"
import houseWithSprinterWebm from "@/assets/gebos/house-with-sprinter.webm"
import housesSmallIsoAvif from "@/assets/gebos/houses-small-isometric.avif" import housesSmallIsoAvif from "@/assets/gebos/houses-small-isometric.avif"
import housesSmallIsoWebp from "@/assets/gebos/houses-small-isometric.webp" import housesSmallIsoWebp from "@/assets/gebos/houses-small-isometric.webp"
import iconBilling from "@/assets/gebos/icon-billing.svg" import iconBilling from "@/assets/gebos/icon-billing.svg"
@@ -87,6 +104,8 @@ import tealBars from "@/assets/gebos/teal-bars.svg"
import technicianPipeAvif from "@/assets/gebos/technician-pipe.avif" import technicianPipeAvif from "@/assets/gebos/technician-pipe.avif"
import technicianPipeWebp from "@/assets/gebos/technician-pipe.webp" import technicianPipeWebp from "@/assets/gebos/technician-pipe.webp"
import uviBadge from "@/assets/gebos/uvi-badge.svg" import uviBadge from "@/assets/gebos/uvi-badge.svg"
import uviGlassPaneAvif from "@/assets/gebos/uvi-glass-pane.avif"
import uviGlassPaneWebp from "@/assets/gebos/uvi-glass-pane.webp"
import wirelessOrb from "@/assets/gebos/wireless-orb.svg" import wirelessOrb from "@/assets/gebos/wireless-orb.svg"
import wordmarkDarkAvif from "@/assets/gebos/wordmark-cubes-dark.avif" import wordmarkDarkAvif from "@/assets/gebos/wordmark-cubes-dark.avif"
import wordmarkDarkWebp from "@/assets/gebos/wordmark-cubes-dark.webp" import wordmarkDarkWebp from "@/assets/gebos/wordmark-cubes-dark.webp"
@@ -257,6 +276,54 @@ const renders = {
alt: "", alt: "",
matte: "alpha", matte: "alpha",
}, },
uviGlassPane: {
avif: uviGlassPaneAvif,
webp: uviGlassPaneWebp,
alt: "",
matte: "alpha",
},
glassMoney: {
avif: glassMoneyAvif,
webp: glassMoneyWebp,
alt: "",
matte: "alpha",
},
glassLaptop: {
avif: glassLaptopAvif,
webp: glassLaptopWebp,
alt: "",
matte: "alpha",
},
glassGateway: {
avif: glassGatewayAvif,
webp: glassGatewayWebp,
alt: "",
matte: "alpha",
},
glassWaterMeter: {
avif: glassWaterMeterAvif,
webp: glassWaterMeterWebp,
alt: "",
matte: "alpha",
},
glassSmokeDetector: {
avif: glassSmokeDetectorAvif,
webp: glassSmokeDetectorWebp,
alt: "",
matte: "alpha",
},
glassLock: {
avif: glassLockAvif,
webp: glassLockWebp,
alt: "",
matte: "alpha",
},
glassShieldLock: {
avif: glassShieldLockAvif,
webp: glassShieldLockWebp,
alt: "",
matte: "alpha",
},
/** glossy numbered badge "1" process-step numeral, genuine alpha */ /** glossy numbered badge "1" process-step numeral, genuine alpha */
glassBadge1: { glassBadge1: {
avif: glassBadge1Avif, avif: glassBadge1Avif,
@@ -499,4 +566,66 @@ function GlassNumber({
) )
} }
export { AssetRender, GlassNumber, icons, renders, vectors } export interface VideoAsset {
mp4: string
webm: string
/** static frame shown before playback starts / while loading */
poster: string
alt: string
}
/** Looping background/product clips (opaque compositions, not matte assets). */
const videos = {
houseWithSprinter: {
mp4: houseWithSprinterMp4,
webm: houseWithSprinterWebm,
poster: houseWithSprinterPosterWebp,
alt: "Servicefahrzeug fährt zum Gebäude vor",
},
} satisfies Record<string, VideoAsset>
/**
* Renders a kit video as WebM→MP4 <video>, muted/looping/autoplaying by
* default for ambient hero use. Falls back to the poster frame if the
* browser can't play either source.
*/
function VideoRender({
video,
className,
autoPlay = true,
loop = true,
muted = true,
playsInline = true,
}: {
video: VideoAsset
className?: string
autoPlay?: boolean
loop?: boolean
muted?: boolean
playsInline?: boolean
}) {
return (
<video
className={cn("object-contain", className)}
poster={video.poster}
autoPlay={autoPlay}
loop={loop}
muted={muted}
playsInline={playsInline}
aria-label={video.alt}
>
<source src={video.webm} type="video/webm" />
<source src={video.mp4} type="video/mp4" />
</video>
)
}
export {
AssetRender,
GlassNumber,
icons,
renders,
VideoRender,
vectors,
videos,
}
@@ -7,7 +7,7 @@ const COLUMNS: { title: string; links: { to: string; label: string }[] }[] = [
{ {
title: "Produkt", title: "Produkt",
links: [ links: [
{ to: "/loesungen", label: "Lösungen" }, { to: "/produkte", label: "Produkte" },
{ to: "/so-funktionierts", label: "So funktionierts" }, { to: "/so-funktionierts", label: "So funktionierts" },
{ to: "/konfigurator", label: "Konfigurator" }, { to: "/konfigurator", label: "Konfigurator" },
{ to: "/klarpreis", label: "GebOS Klarpreis" }, { to: "/klarpreis", label: "GebOS Klarpreis" },
@@ -32,9 +32,10 @@ type NavItem = {
/** 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: "/loesungen", label: "Lösungen" }, { to: "/produkte", label: "Produkte" },
{ 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: "/konfigurator", label: "Konfigurator" }, { to: "/konfigurator", label: "Konfigurator" },
] ]
@@ -222,6 +223,9 @@ function MobileMenu() {
<DropdownMenuItem asChild> <DropdownMenuItem asChild>
<Link to="/klarpreis">Klarpreis</Link> <Link to="/klarpreis">Klarpreis</Link>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem asChild>
<a href="https://app.gebos.online">Login</a>
</DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
) )
@@ -242,8 +246,11 @@ function SiteHeader() {
<DesktopNav /> <DesktopNav />
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Button asChild size="sm" className="hidden sm:inline-flex"> {/* <Button asChild size="sm" className="hidden sm:inline-flex">
<Link to="/konfigurator">Preis berechnen</Link> <Link to="/konfigurator">Preis berechnen</Link>
</Button> */}
<Button asChild variant="ghost" size="sm" className="hidden sm:inline-flex">
<a href="https://app.gebos.online">Login</a>
</Button> </Button>
<MobileMenu /> <MobileMenu />
</div> </div>
@@ -0,0 +1,81 @@
import * as React from "react"
import { cn } from "@/lib/utils"
import { AssetRender, renders } from "@/components/gebos/site-assets"
type TrustPanelItem = {
icon: React.ReactNode
label: React.ReactNode
}
/**
* Trust panel: a pale plate carrying the headline and the technical properties
* as white pills (teal line icon beside a short label). Three pills across from
* `lg`, so five items settle into 3 + 2 as in the design, with the security
* render alongside and the follow-up link on the baseline underneath.
*/
function TrustPanel({
title,
items,
visual,
action,
className,
}: {
title: React.ReactNode
items: TrustPanelItem[]
visual?: React.ReactNode
/** follow-up affordance, right-aligned under the pills */
action?: React.ReactNode
className?: string
}) {
return (
<div
className={cn(
"border-border shadow-card rounded-3xl border bg-gradient-to-br from-white via-white to-secondary p-6 sm:p-8 lg:p-10",
className
)}
>
<div className="flex flex-col gap-8 lg:flex-row lg:items-center lg:gap-10">
<div className="min-w-0 flex-1">
<h2 className="text-navy text-2xl font-extrabold tracking-tight text-balance sm:text-[1.75rem]">
{title}
</h2>
<div className="mt-6 grid gap-4 sm:grid-cols-2 lg:grid-cols-3">
{items.map((item, i) => (
<div
key={i}
className="bg-card shadow-card flex items-center gap-4 rounded-2xl px-5 py-4"
>
<span className="text-brand-600 flex shrink-0 [&_svg]:size-8">
{item.icon}
</span>
<span className="text-navy/80 text-[0.9375rem] leading-snug font-semibold text-balance">
{item.label}
</span>
</div>
))}
</div>
</div>
{visual ? <div className="shrink-0 self-center">{visual}</div> : null}
</div>
{action ? <div className="mt-8 flex justify-end">{action}</div> : null}
</div>
)
}
/**
* The panel's security render: the glass shield with the lock inside it.
* Decorative — the pills next to it carry the meaning.
*/
function TrustShieldArt({ className }: { className?: string }) {
return (
<AssetRender
render={renders.glassShieldLock}
alt=""
className={cn("mx-auto block w-40 sm:w-44", className)}
/>
)
}
export { TrustPanel, TrustShieldArt }
export type { TrustPanelItem }
@@ -26,8 +26,12 @@ function AccordionItem({
function AccordionTrigger({ function AccordionTrigger({
className, className,
children, children,
indicator,
...props ...props
}: React.ComponentProps<typeof AccordionPrimitive.Trigger>) { }: React.ComponentProps<typeof AccordionPrimitive.Trigger> & {
/** replaces the default chevron (e.g. the FAQ's teal plus disc) */
indicator?: React.ReactNode
}) {
return ( return (
<AccordionPrimitive.Header className="flex"> <AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger <AccordionPrimitive.Trigger
@@ -39,7 +43,9 @@ function AccordionTrigger({
{...props} {...props}
> >
{children} {children}
<ChevronDownIcon className="pointer-events-none size-4 shrink-0 translate-y-0.5 text-muted-foreground transition-transform duration-200" /> {indicator ?? (
<ChevronDownIcon className="pointer-events-none size-4 shrink-0 translate-y-0.5 text-muted-foreground transition-transform duration-200" />
)}
</AccordionPrimitive.Trigger> </AccordionPrimitive.Trigger>
</AccordionPrimitive.Header> </AccordionPrimitive.Header>
) )
@@ -13,6 +13,7 @@ import {
} from "@/components/gebos/comparison-table" } from "@/components/gebos/comparison-table"
import { Section, SectionHeading } from "@/components/gebos/section" import { Section, SectionHeading } from "@/components/gebos/section"
import { CtaBanner } from "@/components/gebos/cta-banner" import { CtaBanner } from "@/components/gebos/cta-banner"
import { FaqAccordion, type FaqItem } from "@/components/gebos/faq-accordion"
import { StepRail } from "@/components/gebos/process-steps" import { StepRail } from "@/components/gebos/process-steps"
import { import {
CloudAutomationIcon, CloudAutomationIcon,
@@ -194,6 +195,34 @@ const VARIANT_ROWS: ComparisonRow[] = [
}, },
] ]
const FAQS: FaqItem[] = [
{
question: "Kann ich die Geräte wirklich selbst installieren?",
answer:
"Ja. Die Technik kommt vorkonfiguriert und objektbezogen bei Ihnen an. Montiert wird von Ihnen, Ihrem Handwerker oder wenn Sie die Komplettlösung wählen von GebOS. Eine aufwendige Softwarekonfiguration vor Ort ist in keinem Fall erforderlich.",
},
{
question: "Was kostet GebOS für mein Gebäude?",
answer:
"Der Konfigurator gibt Ihnen mit wenigen Angaben zu Gebäude, Wohnungen und gewünschten Leistungen eine erste Preisindikation. Den genauen Preis erhalten Sie, sobald die tatsächlichen Gebäudedaten ergänzt sind.",
},
{
question: "Wer übernimmt Ablesung, Übertragung und Auswertung?",
answer:
"Nach der Montage läuft der Betrieb automatisch: GebOS erfasst die Verbrauchsdaten, überträgt sie und bereitet sie für die gebuchten Leistungen wie UVI und Heizkostenabrechnung auf.",
},
{
question: "Was sehe ich im GebOS Portal?",
answer:
"Ihre Gebäude, Wohnungen und Geräte an einer Stelle inklusive Status der Geräte und der laufenden Verbrauchswerte. Bewohner erhalten ihren eigenen Zugang zum Mieterportal.",
},
{
question: "Kann ich später weitere Gebäude oder Geräte ergänzen?",
answer:
"Ja. Weitere Objekte werden nach demselben standardisierten Prozess ergänzt. Bereits erfasste Gebäudedaten werden weiterverwendet und müssen nicht erneut eingegeben werden.",
},
]
export default function HauseigentuemerPage() { export default function HauseigentuemerPage() {
return ( return (
<AudiencePage <AudiencePage
@@ -334,6 +363,15 @@ export default function HauseigentuemerPage() {
rows={VARIANT_ROWS} rows={VARIANT_ROWS}
/> />
</Section> </Section>
<Section className="pt-0 sm:pt-0 lg:pt-0">
<SectionHeading
align="center"
eyebrow="Häufige Fragen"
title="Was Hauseigentümer am häufigsten fragen"
/>
<FaqAccordion className="mt-10" items={FAQS} />
</Section>
</AudiencePage> </AudiencePage>
) )
} }
@@ -16,6 +16,7 @@ import { Card, CardContent } from "@/components/ui/card"
import { Separator } from "@/components/ui/separator" import { Separator } from "@/components/ui/separator"
import { Section, SectionHeading } from "@/components/gebos/section" import { Section, SectionHeading } from "@/components/gebos/section"
import { CtaBanner } from "@/components/gebos/cta-banner" import { CtaBanner } from "@/components/gebos/cta-banner"
import { FaqAccordion, type FaqItem } from "@/components/gebos/faq-accordion"
import { IconBadge } from "@/components/gebos/icon-tile" import { IconBadge } from "@/components/gebos/icon-tile"
import { NumberedList } from "@/components/gebos/process-steps" import { NumberedList } from "@/components/gebos/process-steps"
import { import {
@@ -130,6 +131,34 @@ const whyGebos = [
}, },
] ]
const faqs: FaqItem[] = [
{
question: "Wie viel Aufwand entsteht für unser Team?",
answer:
"Die Gebäude- und Wohnungsdaten werden bei der Auftragserstellung einmal erfasst und anschließend weiterverwendet. Es entfällt die aufwendige Gerätekonfiguration und die manuelle Anlage jedes einzelnen Gerätes.",
},
{
question: "Können unsere bestehenden Monteure installieren?",
answer:
"Ja. Eigene Hausmeister, technische Mitarbeiter oder bestehende Installationspartner können die Montage übernehmen. Eine spezielle GebOS-Zertifizierung ist dafür nicht erforderlich.",
},
{
question: "Sehen wir, wo der Rollout gerade steht?",
answer:
"Ja. Vom angelegten Auftrag über vorbereitete und versendete Hardware bis zu aktiven Geräten und eingeladenen Bewohnern lässt sich der Status je Gebäude nachvollziehen inklusive Hinweis auf Objekte mit Handlungsbedarf.",
},
{
question: "Lassen sich viele Objekte parallel ausrollen?",
answer:
"Der Prozess ist auf Portfolios ausgelegt: Standardisierte Abläufe und objektbezogen vorbereitete Hardware ermöglichen die parallele Umsetzung vieler Gebäude.",
},
{
question: "Müssen wir mit dem gesamten Bestand starten?",
answer:
"Nein. Sie können mit einzelnen Objekten beginnen und weitere Gebäude schrittweise nach demselben standardisierten Prozess ergänzen.",
},
]
/** Portfolio/rollout hero per asset kit 06: building, teal bars, consumption ring. */ /** Portfolio/rollout hero per asset kit 06: building, teal bars, consumption ring. */
function PortfolioHeroArt() { function PortfolioHeroArt() {
return ( return (
@@ -343,6 +372,15 @@ export default function HausverwaltungenPage() {
lead="GebOS soll nicht verlangen, dass eine Hausverwaltung zunächst eine eigene Messdienst-Infrastruktur aufbaut. Neue Gebäude können schrittweise ergänzt und nach demselben standardisierten Prozess ausgerollt werden." lead="GebOS soll nicht verlangen, dass eine Hausverwaltung zunächst eine eigene Messdienst-Infrastruktur aufbaut. Neue Gebäude können schrittweise ergänzt und nach demselben standardisierten Prozess ausgerollt werden."
/> />
</Section> </Section>
<Section className="pt-0 sm:pt-0 lg:pt-0">
<SectionHeading
align="center"
eyebrow="Häufige Fragen"
title="Was Hausverwaltungen am häufigsten fragen"
/>
<FaqAccordion className="mt-10" items={faqs} />
</Section>
</AudiencePage> </AudiencePage>
) )
} }
@@ -13,6 +13,7 @@ import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card" import { Card } from "@/components/ui/card"
import { Checklist } from "@/components/gebos/checklist" import { Checklist } from "@/components/gebos/checklist"
import { FaqAccordion, type FaqItem } from "@/components/gebos/faq-accordion"
import { IconBadge } from "@/components/gebos/icon-tile" import { IconBadge } from "@/components/gebos/icon-tile"
import { ProcessSteps } from "@/components/gebos/process-steps" import { ProcessSteps } from "@/components/gebos/process-steps"
import { Section, SectionHeading } from "@/components/gebos/section" import { Section, SectionHeading } from "@/components/gebos/section"
@@ -61,6 +62,34 @@ const SIE_UEBERNEHMEN = [
"Abschluss der Installation", "Abschluss der Installation",
] ]
const FAQS: FaqItem[] = [
{
question: "Muss ich ein Partnerprogramm abschließen?",
answer:
"Nein. Für die normale Installation von GebOS gibt es keine verpflichtende Partnerschaft, keine Mindestabnahme und keine verpflichtende Vertriebsschulung. Sie installieren, wenn ein Kunde es benötigt.",
},
{
question: "Brauche ich eine GebOS-Zertifizierung?",
answer:
"Nein. Vorausgesetzt wird Ihr Handwerk: die fachgerechte Montage der Messtechnik. Die digitale Einrichtung und der laufende Betrieb liegen bei GebOS.",
},
{
question: "Was ist bei der Lieferung schon vorbereitet?",
answer:
"Je nach Auftrag sind Gebäudestruktur, Nutzungseinheiten, Gerätezuordnung, Hardware, Kommunikationsinfrastruktur und Softwarestruktur bereits angelegt. Die Geräte kommen objektbezogen und installationsfertig an.",
},
{
question: "Muss ich vor Ort Software konfigurieren?",
answer:
"Nein. Auf der Baustelle bleibt im Wesentlichen die Montage. Eine komplizierte Softwareeinrichtung am Gebäude ist nicht vorgesehen.",
},
{
question: "Bleibt der Kunde mein Kunde?",
answer:
"Ja. Der Fachbetrieb bleibt der Fachbetrieb des Kunden. GebOS stellt das Produkt und die digitale Infrastruktur bereit die Kundenbeziehung bleibt bei Ihnen.",
},
]
export default function InstallateurePage() { export default function InstallateurePage() {
return ( return (
<AudiencePage <AudiencePage
@@ -309,7 +338,7 @@ export default function InstallateurePage() {
</Section> </Section>
{/* 6. Und wenn Sie regelmäßig GebOS installieren? */} {/* 6. Und wenn Sie regelmäßig GebOS installieren? */}
<Section className="pt-0 pb-10 sm:pt-0 sm:pb-12 lg:pt-0 lg:pb-14"> <Section className="pt-0 sm:pt-0 lg:pt-0">
<div className="mx-auto max-w-2xl text-center"> <div className="mx-auto max-w-2xl text-center">
<h2 className="text-2xl font-extrabold tracking-tight text-balance text-navy sm:text-3xl"> <h2 className="text-2xl font-extrabold tracking-tight text-balance text-navy sm:text-3xl">
Und wenn Sie regelmäßig GebOS installieren? Und wenn Sie regelmäßig GebOS installieren?
@@ -322,6 +351,16 @@ export default function InstallateurePage() {
</p> </p>
</div> </div>
</Section> </Section>
{/* 7. Häufige Fragen */}
<Section className="pt-0 pb-10 sm:pt-0 sm:pb-12 lg:pt-0 lg:pb-14">
<SectionHeading
align="center"
eyebrow="Häufige Fragen"
title="Was Fachbetriebe am häufigsten fragen"
/>
<FaqAccordion className="mt-10" items={FAQS} />
</Section>
</AudiencePage> </AudiencePage>
) )
} }
@@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card" import { Card, CardContent } from "@/components/ui/card"
import { Section, SectionHeading } from "@/components/gebos/section" import { Section, SectionHeading } from "@/components/gebos/section"
import { Checklist } from "@/components/gebos/checklist" import { Checklist } from "@/components/gebos/checklist"
import { FaqAccordion, type FaqItem } from "@/components/gebos/faq-accordion"
import { import {
AssetRender, AssetRender,
renders, renders,
@@ -79,6 +80,34 @@ const businessComponents = [
const stackFlow = ["Ihre Kunden", "Ihr Messdienst", "GebOS Plattform"] const stackFlow = ["Ihre Kunden", "Ihr Messdienst", "GebOS Plattform"]
const faqs: FaqItem[] = [
{
question: "Können wir GebOS unter unserer eigenen Marke anbieten?",
answer:
"Ja. GebOS ist als technische Grundlage für Dienstleistungen unter Ihrer Marke vorgesehen mit eigenem Branding, mandantenfähiger Verwaltung und Endkundenportalen in Ihrem Auftritt.",
},
{
question: "Behalten wir die Kundenbeziehung?",
answer:
"Ja. Sie betreuen Ihre Kunden und verkaufen Ihre Dienstleistungen. GebOS stellt Hardware, Datenerfassung, Software und Betrieb im Hintergrund bereit.",
},
{
question: "Wie integrieren wir GebOS in unsere Systeme?",
answer:
"Über offene Schnittstellen: APIs und Datenexporte für Verbrauchsdaten, Geräte- und Objektstrukturen. Damit lässt sich GebOS an bestehende Abrechnungs- und Verwaltungssysteme anbinden.",
},
{
question: "Wer betreibt die Plattform technisch?",
answer:
"GebOS einschließlich Kommunikationsinfrastruktur, Gateways, Datenerfassung, Geräteverwaltung und Monitoring. Sie müssen dafür keine eigene Hardware- und Softwareentwicklung aufbauen.",
},
{
question: "Wie werden die Konditionen festgelegt?",
answer:
"Individuell beziehungsweise mengenabhängig mit anderen Geschäftsmodellen, Mengenstaffeln und gegebenenfalls White-Label-Konditionen als im öffentlichen Preiskonfigurator. Die konkreten Konditionen werden nicht öffentlich dargestellt.",
},
]
/** Platform hero per asset kit 07: laptop dashboard, glass cubes, cloud support. */ /** Platform hero per asset kit 07: laptop dashboard, glass cubes, cloud support. */
function PlatformHeroArt() { function PlatformHeroArt() {
return ( return (
@@ -266,6 +295,15 @@ export default function MessdienstleisterPage() {
lead="GebOS soll insbesondere Messdienstleistern ermöglichen, neue digitale Dienstleistungen anzubieten, ohne dafür eine vollständige eigene Hardware- und Softwareentwicklung aufbauen zu müssen. Der Messdienstleister behält dabei seine Kundenbeziehung und sein eigenes Geschäftsmodell." lead="GebOS soll insbesondere Messdienstleistern ermöglichen, neue digitale Dienstleistungen anzubieten, ohne dafür eine vollständige eigene Hardware- und Softwareentwicklung aufbauen zu müssen. Der Messdienstleister behält dabei seine Kundenbeziehung und sein eigenes Geschäftsmodell."
/> />
</Section> </Section>
<Section className="pt-0 sm:pt-0 lg:pt-0">
<SectionHeading
align="center"
eyebrow="Häufige Fragen"
title="Was Messdienstleister am häufigsten fragen"
/>
<FaqAccordion className="mt-10" items={faqs} />
</Section>
</AudiencePage> </AudiencePage>
) )
} }
+165 -41
View File
@@ -1,53 +1,71 @@
import { Link } from "react-router-dom" import { Link } from "react-router-dom"
import { ArrowRight, Calculator } from "lucide-react" import {
ArrowRight,
Calculator,
ChevronRight,
Lock,
Puzzle,
ShieldCheck,
Users,
} from "lucide-react"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { Card, CardContent } from "@/components/ui/card" import { Card, CardContent } from "@/components/ui/card"
import { Container, Section, SectionHeading } from "@/components/gebos/section" import { Container, Section, SectionHeading } from "@/components/gebos/section"
import { PageHero } from "@/components/gebos/page-hero" import { PageHero } from "@/components/gebos/page-hero"
import { HeroArt } from "@/components/gebos/hero-art" import { HeroArt } from "@/components/gebos/hero-art"
import { FeatureTile } from "@/components/gebos/icon-tile" import { InfoStrip } from "@/components/gebos/info-strip"
import { ProcessSteps, NumberedList } from "@/components/gebos/process-steps" import { ProcessSteps, NumberedList } from "@/components/gebos/process-steps"
import { Checklist } from "@/components/gebos/checklist" import { Checklist } from "@/components/gebos/checklist"
import { CtaBanner } from "@/components/gebos/cta-banner" import { CtaBanner } from "@/components/gebos/cta-banner"
import { FaqAccordion, type FaqItem } from "@/components/gebos/faq-accordion"
import { NoticeArt, NoticeBanner } from "@/components/gebos/notice-banner"
import { GermanyIcon } from "@/components/gebos/line-icons"
import { TrustPanel, TrustShieldArt } from "@/components/gebos/trust-panel"
import { import {
AssetRender, AssetRender,
icons, icons,
renders, renders,
vectors, vectors,
type RenderAsset,
} from "@/components/gebos/site-assets" } from "@/components/gebos/site-assets"
const features = [ function StripIcon({ render }: { render: RenderAsset }) {
return (
<AssetRender
render={render}
alt=""
className="flex size-14 items-center justify-center"
imgClassName="w-14"
/>
)
}
const infoStripItems = [
{ {
media: <img src={icons.uvi} alt="" className="size-14" loading="lazy" />, media: <StripIcon render={renders.uviGlassPane} />,
title: "Unterjährighe Verbrauchsinformation (UVI)", eyebrow: "UVI",
// sub: "Monatliche Verbrauchsinformation", title: "Monatliche UVI",
}, },
{ {
media: ( media: <StripIcon render={renders.glassMoney} />,
<img src={icons.billing} alt="" className="size-14" loading="lazy" /> eyebrow: "Abrechnung",
),
title: "Heizkosten\u00adabrechnung", title: "Heizkosten\u00adabrechnung",
// sub: "Jährlich korrekt abrechnen",
}, },
{ {
media: ( media: <StripIcon render={renders.glassSmokeDetector} />,
<img src={icons.smokeAlarm} alt="" className="size-14" loading="lazy" /> eyebrow: "Sicherheit",
),
title: "Rauchwarnmelder", title: "Rauchwarnmelder",
// sub: "Sicher überwacht, zentral verwaltet",
}, },
{ {
media: <img src={icons.radio} alt="" className="size-14" loading="lazy" />, media: <StripIcon render={renders.glassGateway} />,
title: "Fernauslesbare Messtechnik", eyebrow: "Technik",
// sub: "Automatisch erfasst, sicher übertragen", title: "Fernablesbare Messtechnik",
}, },
{ {
media: ( media: <StripIcon render={renders.glassLaptop} />,
<img src={icons.portal} alt="" className="size-14" loading="lazy" /> eyebrow: "Software",
), title: "Portal & Verwaltung",
title: "Portale & Verwaltung",
// sub: "Alle Daten und Geräte im Blick",
}, },
] ]
@@ -105,11 +123,6 @@ function StepMedia({ src }: { src: string }) {
} }
const processSteps = [ const processSteps = [
{
media: <StepMedia src={vectors.uviBadge} />,
title: "Gebäude erfassen",
description: "Daten zum Gebäude und den Wohnungen erfassen.",
},
{ {
media: ( media: (
<AssetRender <AssetRender
@@ -118,13 +131,14 @@ const processSteps = [
imgClassName="w-16" imgClassName="w-16"
/> />
), ),
title: "Gerätebedarf bestimmen", title: "Gebäude anlegen & bestellen",
description: "GebOS ermittelt die benötigte Ausstattung automatisch.", description:
"In nur 30 Minuten konfigurieren und bestellen.",
}, },
{ {
media: <StepMedia src={vectors.packageBox} />, media: <StepMedia src={vectors.packageBox} />,
title: "Vorkonfiguriert liefern", title: "Vorkonfiguriert liefern",
description: "Hardware wird für Ihr Objekt vorbereitet geliefert.", description: "Geräte werden für Ihr Objekt vorbereitet geliefert.",
}, },
{ {
media: ( media: (
@@ -137,9 +151,20 @@ const processSteps = [
title: "Geräte montieren", title: "Geräte montieren",
description: "Montage vor Ort ohne aufwendige Softwarekonfiguration.", description: "Montage vor Ort ohne aufwendige Softwarekonfiguration.",
}, },
{
media: (
<AssetRender
render={renders.laptopDashboard}
className="flex size-16 items-center justify-center"
imgClassName="w-16"
/>
),
title: "Bewohner einladen",
description: "Laden Sie die Bewohner in das GebOS Portal ein.",
},
{ {
media: <StepMedia src={vectors.wirelessOrb} />, media: <StepMedia src={vectors.wirelessOrb} />,
title: "Automatisch betreiben", title: "Automatischer Betrieb",
description: "GebOS übernimmt Daten, Services und laufenden Betrieb.", description: "GebOS übernimmt Daten, Services und laufenden Betrieb.",
}, },
] ]
@@ -300,6 +325,58 @@ const priceFlow = [
{ title: "Genauer Preis" }, { title: "Genauer Preis" },
] ]
const faqs: FaqItem[] = [
{
question: "Muss ich GebOS selbst installieren?",
answer:
"Nein. Je nach Ausstattung können geeignete Arbeiten selbst bzw. durch einen vorhandenen Fachbetrieb durchgeführt werden. Alternativ kann die Installation als Komplettleistung beauftragt werden.",
},
{
question:
"Kann mein bestehender Heizungs- oder Sanitärbetrieb die Geräte montieren?",
answer:
"In vielen Fällen kann ein vorhandener geeigneter Fachbetrieb eingesetzt werden. Welche Arbeiten erforderlich sind, hängt von der jeweiligen Messtechnik und Einbausituation ab.",
},
{
question: "Muss vor Ort jedes Gerät einzeln in GebOS eingerichtet werden?",
answer:
"Die Geräte werden soweit vorgesehen bereits vor dem Versand dem Gebäude und den geplanten Einheiten zugeordnet. Dadurch reduziert sich die Softwareeinrichtung vor Ort deutlich.",
},
{
question: "Wie genau ist die erste Preisschätzung?",
answer:
"Die erste Schätzung basiert auf wenigen Gebäudedaten und Annahmen zur benötigten Ausstattung. Die vollständige Gebäudekonfiguration bestimmt anschließend die konkreten Geräte und den finalen Preis.",
},
{
question: "Kann ich nur UVI buchen?",
answer:
"Ja. UVI und Rauchwarnmelder sind einzeln oder gemeinsam wählbar. Das UVI-Paket umfasst die dafür benötigte Messtechnik und die zugehörigen digitalen Dienstleistungen; die Heizkostenabrechnung baut auf denselben erfassten Verbrauchsdaten auf. Rauchwarnmelder können auch später ergänzt werden die bereits erfasste Gebäudestruktur wird dabei weiterverwendet.",
},
{
question: "Kann ich bestehende Messtechnik weiterverwenden?",
answer:
"Das hängt von den vorhandenen Geräten ab: Voraussetzung ist, dass sie fernauslesbar sind und auf geeigneten Standards basieren dann lassen sich unterschiedliche Zähler- und Gerätetypen einbinden. Vorhandene Messtechnik wird deshalb bei der vollständigen Konfiguration je Gebäude und Einheit erfasst und geprüft; nicht fernablesbare Verbrauchserfassung muss ohnehin bis zum 31.12.2026 nachgerüstet oder ersetzt werden. Was übernommen werden kann, wirkt sich auf die Geräteliste und damit auf den finalen Preis aus.",
},
{
question: "Was passiert bei einem Nutzerwechsel?",
answer:
"Der Wechsel wird für die betroffene Einheit im GebOS Portal hinterlegt. Weil die Geräte fernauslesbar sind, werden die Werte zum Stichtag aus der laufenden Datenerfassung übernommen ein Ablesetermin vor Ort ist dafür in der Regel nicht erforderlich. Die Verbräuche werden anschließend den jeweiligen Nutzungszeiträumen zugeordnet, der neue Bewohner erhält seinen Portalzugang, der bisherige verliert ihn. An der Messtechnik selbst ändert sich nichts.",
},
{
question: "Funktioniert GebOS auch für mehrere Gebäude?",
answer:
"Ja. Gebäude werden einzeln strukturiert, können aber gemeinsam innerhalb eines Portfolios verwaltet werden.",
},
]
const trustProperties = [
{ icon: <GermanyIcon />, label: "Hosting in Deutschland" },
{ icon: <ShieldCheck />, label: "AES-geschützte Messdaten" },
{ icon: <Lock />, label: "TLS-verschlüsselte Übertragung" },
{ icon: <Users />, label: "Rollenbasierte Zugriffsrechte" },
{ icon: <Puzzle />, label: "Interoperable Messtechnik" },
]
export default function HomePage() { export default function HomePage() {
return ( return (
<> <>
@@ -322,19 +399,40 @@ export default function HomePage() {
</Button> </Button>
</> </>
} }
note="Wenige Angaben · unverbindliche Kostenschätzung"
visual={<HeroArt />} visual={<HeroArt />}
/> />
<Container> <Container>
<Section className="pt-0"> <InfoStrip
<div className="rounded-2xl border border-border bg-card p-6 shadow-card sm:p-8"> items={infoStripItems}
<div className="grid grid-cols-2 gap-x-4 gap-y-8 sm:grid-cols-3 lg:grid-cols-5"> className="-mt-6 mb-12 sm:-mt-8 sm:mb-14 lg:-mt-10"
{features.map((feature) => ( />
<FeatureTile key={feature.title} {...feature} />
))} <NoticeBanner
</div> title="Nachrüstung bis 31.12.2026 erforderlich"
</div> source={{
</Section> cite: "§ 5 HeizkostenV",
href: "https://www.gesetze-im-internet.de/heizkostenv/__5.html",
}}
visual={<NoticeArt className="max-lg:hidden" />}
action={
<Button
asChild
variant="link"
className="text-brand-800 hover:text-navy decoration-brand-500 hover:decoration-brand-800 h-auto p-0 text-[15px] font-bold underline decoration-2 underline-offset-[6px]"
>
<Link to="/wissen">
Mehr erfahren
<ChevronRight />
</Link>
</Button>
}
>
Bestehende nicht fernablesbare Verbrauchserfassung muss grundsätzlich
bis zum 31.12.2026 nachgerüstet oder ersetzt werden. GebOS verbindet
die neue Messtechnik direkt mit UVI und Abrechnung.
</NoticeBanner>
<Section> <Section>
<SectionHeading <SectionHeading
@@ -418,7 +516,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="/loesungen"> <Link to="/produkte">
Mehr erfahren Mehr erfahren
<ArrowRight /> <ArrowRight />
</Link> </Link>
@@ -547,6 +645,32 @@ export default function HomePage() {
</div> </div>
</Section> </Section>
<Section>
<TrustPanel
title="Technik, Datenschutz & Sicherheit"
items={trustProperties}
visual={<TrustShieldArt />}
action={
<Button asChild variant="link" className="h-auto p-0 font-bold">
<Link to="/technik-sicherheit">
Mehr zu Technik & Sicherheit
<ArrowRight />
</Link>
</Button>
}
/>
</Section>
<Section>
<SectionHeading
align="center"
eyebrow="Häufige Fragen"
title="Fragen zu GebOS"
lead="Von Installation und Ausstattung bis zu Preis und laufendem Betrieb."
/>
<FaqAccordion className="mt-10" items={faqs} />
</Section>
<Section> <Section>
<CtaBanner <CtaBanner
icon={<Calculator />} icon={<Calculator />}
+54 -14
View File
@@ -6,6 +6,7 @@ import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { Card } from "@/components/ui/card" import { Card } from "@/components/ui/card"
import { CtaBanner } from "@/components/gebos/cta-banner" import { CtaBanner } from "@/components/gebos/cta-banner"
import { FaqAccordion, type FaqItem } from "@/components/gebos/faq-accordion"
import { ProcessSteps } from "@/components/gebos/process-steps" import { ProcessSteps } from "@/components/gebos/process-steps"
import { Container, Section, SectionHeading } from "@/components/gebos/section" import { Container, Section, SectionHeading } from "@/components/gebos/section"
import { PageBreadcrumb } from "@/components/gebos/page-breadcrumb" import { PageBreadcrumb } from "@/components/gebos/page-breadcrumb"
@@ -25,22 +26,13 @@ const steps = [
{ {
media: ( media: (
<StepMedia> <StepMedia>
<img src={vectors.uviBadge} alt="" className="w-16" /> <AssetRender render={renders.building} imgClassName="w-12" />
<img src={vectors.uviBadge} alt="" className="w-9" />
</StepMedia> </StepMedia>
), ),
title: "Gebäude erfassen", title: "Gebäude anlegen und bestellen",
description: description:
"Bei der Konfiguration werden Gebäude, Wohnungen und die benötigten Leistungen erfasst.", "Bei der Konfiguration werden Gebäude, Wohnungen und die benötigten Leistungen erfasst. Auf Basis dieser Daten wird automatisch ermittelt, welche Messtechnik, Rauchwarnmelder und weiteren Komponenten benötigt werden.",
},
{
media: (
<StepMedia>
<AssetRender render={renders.building} imgClassName="w-16" />
</StepMedia>
),
title: "Gerätebedarf bestimmen",
description:
"Auf Basis der Gebäudedaten wird ermittelt, welche Messtechnik, Rauchwarnmelder und weiteren Komponenten benötigt werden.",
}, },
{ {
media: ( media: (
@@ -70,13 +62,22 @@ const steps = [
</> </>
), ),
}, },
{
media: (
<StepMedia>
<AssetRender render={renders.laptopDashboard} imgClassName="w-16" />
</StepMedia>
),
title: "Bewohner einladen",
description: "Laden Sie die Bewohner in das GebOS Portal ein.",
},
{ {
media: ( media: (
<StepMedia> <StepMedia>
<img src={vectors.wirelessOrb} alt="" className="w-16" /> <img src={vectors.wirelessOrb} alt="" className="w-16" />
</StepMedia> </StepMedia>
), ),
title: "Automatisch betreiben", title: "Automatischer Betrieb",
description: description:
"Nach der Installation übernimmt GebOS die laufende Datenerfassung und die gebuchten digitalen Dienstleistungen.", "Nach der Installation übernimmt GebOS die laufende Datenerfassung und die gebuchten digitalen Dienstleistungen.",
}, },
@@ -96,6 +97,34 @@ const journey = [
"automatischer laufender Betrieb", "automatischer laufender Betrieb",
] ]
const faqs: FaqItem[] = [
{
question: "Wo fange ich an?",
answer:
"Im Konfigurator. Dort erfassen Sie Gebäude, Wohnungen und die benötigten Leistungen und erhalten eine erste Preisindikation. Aus denselben Daten wird anschließend der Auftrag aufgebaut.",
},
{
question: "Wie verbindlich ist der Preis aus dem Konfigurator?",
answer:
"Er ist eine Preisschätzung auf Basis Ihrer Angaben. Den genauen Preis erhalten Sie, sobald die tatsächlichen Gebäudedaten ergänzt und geprüft sind erst danach wird der Auftrag erteilt.",
},
{
question: "Muss ich meine Gebäudedaten mehrfach erfassen?",
answer:
"Nein. Die einmal eingegebenen Gebäudedaten werden durch den gesamten Prozess weiterverwendet: von der Preisschätzung über die Gerätevorbereitung bis in den laufenden Betrieb.",
},
{
question: "Wer montiert die Geräte?",
answer:
"Das entscheiden Sie: Sie selbst, Ihr Fachbetrieb oder GebOS als Komplettlösung. Die Hardware kommt objektbezogen vorkonfiguriert an, eine aufwendige Softwarekonfiguration vor Ort entfällt.",
},
{
question: "Was passiert nach der Installation?",
answer:
"GebOS übernimmt die laufende Datenerfassung und die gebuchten digitalen Dienstleistungen. Bewohner werden ins Portal eingeladen, der Betrieb läuft danach automatisch.",
},
]
export default function HowItWorksPage() { export default function HowItWorksPage() {
return ( return (
<> <>
@@ -159,6 +188,17 @@ export default function HowItWorksPage() {
</div> </div>
</Section> </Section>
</Container> </Container>
<Container>
<Section className="pt-0">
<SectionHeading
align="center"
eyebrow="Häufige Fragen"
title="Fragen zum Ablauf"
lead="Von der ersten Preisindikation bis zum laufenden Betrieb die häufigsten Fragen zum GebOS-Prozess."
/>
<FaqAccordion className="mt-10" items={faqs} />
</Section>
</Container>
<Container> <Container>
<Section className="pt-0"> <Section className="pt-0">
<CtaBanner <CtaBanner
@@ -8,7 +8,7 @@ import { Container, Section, SectionHeading } from "@/components/gebos/section"
import { PageBreadcrumb } from "@/components/gebos/page-breadcrumb" import { PageBreadcrumb } from "@/components/gebos/page-breadcrumb"
import { AssetRender, renders } from "@/components/gebos/site-assets" import { AssetRender, renders } from "@/components/gebos/site-assets"
const solutions = [ const products = [
{ {
title: "UVI", title: "UVI",
sub: "Monatliche Verbrauchsinformation", sub: "Monatliche Verbrauchsinformation",
@@ -36,7 +36,7 @@ const solutions = [
}, },
] ]
export default function SolutionsPage() { export default function ProductsPage() {
return ( return (
<> <>
<div className="bg-hero-glow"> <div className="bg-hero-glow">
@@ -46,15 +46,15 @@ export default function SolutionsPage() {
className="mb-4" className="mb-4"
items={[ items={[
{ label: "Startseite", to: "/" }, { label: "Startseite", to: "/" },
{ label: "Lösungen" }, { label: "Produkte" },
]} ]}
/> />
<SectionHeading <SectionHeading
title="Unsere Lösungen" title="Unsere Produkte"
lead="Hier finden sie eine Übersicht unserer Produkt Lösungen." 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">
{solutions.map((s) => ( {products.map((s) => (
<Card key={s.title} className="gap-0 p-6 sm:p-7"> <Card key={s.title} 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} {s.title}
@@ -71,7 +71,7 @@ export default function SolutionsPage() {
className="mt-3 self-start px-0" className="mt-3 self-start px-0"
asChild asChild
> >
<Link to="/loesungen"> <Link to="/produkte">
Mehr erfahren Mehr erfahren
<ArrowRight /> <ArrowRight />
</Link> </Link>
@@ -95,7 +95,7 @@ export default function SolutionsPage() {
</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="/loesungen"> <Link to="/produkte">
Mehr erfahren Mehr erfahren
<ArrowRight /> <ArrowRight />
</Link> </Link>
+26
View File
@@ -193,6 +193,32 @@
); );
} }
/* teal wash bleeding in from the left edge of a notice card: brand-300 at the
top turning into brand-800 at the base, masked out towards the middle so it
dissolves into the card surface, and thinned again over the last third of the
drop so small cards keep their footer copy readable. Hand-written, so the
stops interpolate in srgb like the tokens. */
@utility bg-notice-wash {
background-image: linear-gradient(
176deg,
var(--brand-300) 0%,
var(--brand-400) 16%,
var(--brand-500) 46%,
var(--brand-700) 82%,
var(--brand-800) 100%
);
mask-image:
linear-gradient(
92deg,
#000 0%,
rgb(0 0 0 / 0.74) 26%,
rgb(0 0 0 / 0.24) 60%,
transparent 92%
),
linear-gradient(180deg, #000 0%, #000 64%, rgb(0 0 0 / 0.5) 100%);
mask-composite: intersect;
}
/* dotted connector line used by process steppers */ /* dotted connector line used by process steppers */
@utility border-dotted-brand { @utility border-dotted-brand {
border-color: var(--brand-300); border-color: var(--brand-300);
+57
View File
@@ -4,3 +4,60 @@ For example on the configuration page it would be cool to have an indicator sayi
Also this self serve model should be explained more prominently on the home page and haseigentuemer page. Also this self serve model should be explained more prominently on the home page and haseigentuemer page.
Also to capture the european market as early as possible, we should build in translation for all european languages and english. Also to capture the european market as early as possible, we should build in translation for all european languages and english.
Can we use the same house renderer adapted throughout the site to "explain" some concepts?
Like A Querschnitt with sensors, smoke detectors and the gateway?
Perhaps even make small animations out of it.
13. Trust and compliance section
This should be relatively restrained.
Eyebrow:
TECHNIK & ANFORDERUNGEN
Für den Messbetrieb gebaut.
Supporting text:
GebOS verbindet die Anforderungen moderner Messdienstleistungen mit einer klaren technischen und organisatorischen Struktur.
HeizkostenV
UVI nach den Anforderungen des §6a HeizkostenV
Fernablesbarkeit
Fernauslesbare Verbrauchserfassung für die Anforderungen der HeizkostenV
Interoperabilität
Where demonstrably applicable to the supplied equipment:
Interoperable fernablesbare Messtechnik gemäß den Anforderungen des §5 HeizkostenV
§5 also contains interoperability and state-of-the-art requirements for remotely readable equipment installed from 1 December 2022 onward.
Expand:
Alle Geräte sind OMS fähig, die AES Schlüssel aller Geräte können jederzeit abgefragt werden
Datenschutz & Sicherheit
Instead of:
„Höchste Datensicherheit“
state actual properties when known, for example:
Hosting-Standort: Hosted in Falkenstein Deutschland
Verschlüsselung: (not sure I guess we should mention the OMS AES encrytption as well as SSL for sensor data and website data).
Aufbewahrung: Nur deutsche Server und Services.
Rollen- und Rechtekonzept: Umformulieren: Benutzer können im Portal nur ihre Daten sehen, nicht die der Vormieter etc.
CTA:
Technik & Sicherheit →
+23 -7
View File
@@ -21,6 +21,20 @@ Die beiden CTAs bedienen unterschiedliche Besucher:
--- ---
## Hinweis direkt unter dem Hero
# Nachrüstung bis 31.12.2026 erforderlich
Bestehende nicht fernablesbare Verbrauchserfassung muss grundsätzlich bis zum 31.12.2026 nachgerüstet oder ersetzt werden. GebOS verbindet die neue Messtechnik direkt mit UVI und Abrechnung.
Quelle: § 5 HeizkostenV https://www.gesetze-im-internet.de/heizkostenv/__5.html
**[Mehr erfahren]**
→ Führt auf die Wissensseite.
---
# Alles, was für den laufenden Messbetrieb benötigt wird # Alles, was für den laufenden Messbetrieb benötigt wird
Gebos verbindet Messtechnik, Datenerfassung und Software zu einem durchgängigen System. Gebos verbindet Messtechnik, Datenerfassung und Software zu einem durchgängigen System.
@@ -59,27 +73,29 @@ Gebäude, Wohnungen, Geräte und Verbrauchsdaten werden zentral verwaltet. Bewoh
Gebos verbindet Bestellung, Konfiguration und Installation miteinander. Gebos verbindet Bestellung, Konfiguration und Installation miteinander.
### 1. Gebäude erfassen ### 1. Gebäude anlegen und bestellen in nur 30 Minuten
Bei der Konfiguration werden Gebäude, Wohnungen und die benötigten Leistungen erfasst. Bei der Konfiguration werden Gebäude, Wohnungen und die benötigten Leistungen erfasst.
### 2. Gerätebedarf bestimmen Auf Basis dieser Daten wird automatisch ermittelt, welche Messtechnik, Rauchwarnmelder und weiteren Komponenten benötigt werden.
Auf Basis der Gebäudedaten wird ermittelt, welche Messtechnik, Rauchwarnmelder und weiteren Komponenten benötigt werden. ### 2. Vorkonfiguriert liefern
### 3. Vorkonfiguriert liefern
Gebäude, Wohnungen und Geräte werden bereits vorbereitet. Gebäude, Wohnungen und Geräte werden bereits vorbereitet.
Die Hardware wird objektbezogen und installationsfertig geliefert. Die Hardware wird objektbezogen und installationsfertig geliefert.
### 4. Geräte montieren ### 3. Geräte montieren
Die Geräte müssen vor Ort im Wesentlichen nur noch fachgerecht montiert werden. Die Geräte müssen vor Ort im Wesentlichen nur noch fachgerecht montiert werden.
**Keine aufwendige Softwarekonfiguration bei der Installation.** **Keine aufwendige Softwarekonfiguration bei der Installation.**
### 5. Automatisch betreiben ### 4. Bewohner einladen
Laden Sie die Bewohner in das GebOS Portal ein.
### 5. Automatischer Betrieb
Nach der Installation übernimmt Gebos die laufende Datenerfassung und die gebuchten digitalen Dienstleistungen. Nach der Installation übernimmt Gebos die laufende Datenerfassung und die gebuchten digitalen Dienstleistungen.