save
|
After Width: | Height: | Size: 140 KiB |
|
After Width: | Height: | Size: 168 KiB |
|
After Width: | Height: | Size: 141 KiB |
|
After Width: | Height: | Size: 178 KiB |
|
After Width: | Height: | Size: 148 KiB |
|
After Width: | Height: | Size: 182 KiB |
|
After Width: | Height: | Size: 190 KiB |
|
After Width: | Height: | Size: 239 KiB |
|
After Width: | Height: | Size: 146 KiB |
|
After Width: | Height: | Size: 164 KiB |
|
After Width: | Height: | Size: 195 KiB |
|
After Width: | Height: | Size: 181 KiB |
|
After Width: | Height: | Size: 41 KiB |
|
After Width: | Height: | Size: 51 KiB |
|
After Width: | Height: | Size: 92 KiB |
|
After Width: | Height: | Size: 108 KiB |
@@ -1,13 +1,6 @@
|
|||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
import buildingAvif from "@/assets/gebos/building.avif"
|
import { AssetRender, renders } from "@/components/gebos/site-assets"
|
||||||
import buildingWebp from "@/assets/gebos/building.webp"
|
|
||||||
import consumptionRing from "@/assets/gebos/consumption-ring.svg"
|
|
||||||
import smokeAlarmAvif from "@/assets/gebos/smoke-alarm.avif"
|
|
||||||
import smokeAlarmWebp from "@/assets/gebos/smoke-alarm.webp"
|
|
||||||
import tealBars from "@/assets/gebos/teal-bars.svg"
|
|
||||||
import uviBadge from "@/assetAs/gebos/uvi-badge.svg"
|
|
||||||
import { AssetRender, renders } from ""
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Homepage hero composition from the GebOS visual asset kit:
|
* Homepage hero composition from the GebOS visual asset kit:
|
||||||
@@ -26,41 +19,15 @@ function HeroArt({ className }: { className?: string }) {
|
|||||||
aria-label="GebOS Produktvisualisierung"
|
aria-label="GebOS Produktvisualisierung"
|
||||||
role="img"
|
role="img"
|
||||||
>
|
>
|
||||||
<img
|
|
||||||
src={tealBars}
|
|
||||||
alt=""
|
|
||||||
className="absolute top-[12%] -right-[5%] z-0 w-[46%]"
|
|
||||||
/>
|
|
||||||
<AssetRender
|
<AssetRender
|
||||||
render={renders.barChart}
|
render={renders.barChart}
|
||||||
alt=""
|
alt=""
|
||||||
className="absolute top-[52%] left-[10%] w-[16%] scale-200"
|
className="absolute bottom-[0%] right-[10%] w-[16%] scale-200 z-10"
|
||||||
/>
|
/>
|
||||||
<picture>
|
<AssetRender
|
||||||
<source srcSet={buildingAvif} type="image/avif" />
|
render={renders.buildingAlpha}
|
||||||
<img
|
className="absolute top-[2%] left-[2%] z-1 h-[90%] w-[96%]"
|
||||||
src={buildingWebp}
|
imgClassName="h-full w-full object-contain"
|
||||||
alt="Modernes Mehrfamilienhaus"
|
|
||||||
className="absolute top-[2%] left-[2%] z-1 h-[90%] w-[96%] object-contain mix-blend-multiply"
|
|
||||||
/>
|
|
||||||
</picture>
|
|
||||||
<picture>
|
|
||||||
<source srcSet={smokeAlarmAvif} type="image/avif" />
|
|
||||||
<img
|
|
||||||
src={smokeAlarmWebp}
|
|
||||||
alt="Rauchwarnmelder"
|
|
||||||
className="absolute top-[4%] -left-[2%] z-3 w-[22%] rounded-full mix-blend-multiply"
|
|
||||||
/>
|
|
||||||
</picture>
|
|
||||||
<img
|
|
||||||
src={uviBadge}
|
|
||||||
alt=""
|
|
||||||
className="absolute bottom-[7%] left-[2%] z-4 w-[15%]"
|
|
||||||
/>
|
|
||||||
<img
|
|
||||||
src={consumptionRing}
|
|
||||||
alt=""
|
|
||||||
className="absolute -bottom-[2%] -right-[2%] z-5 w-[46%]"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -34,13 +34,25 @@ import glassBadge2Avif from "@/assets/gebos/glass-badge-2.avif"
|
|||||||
import glassBadge2Webp from "@/assets/gebos/glass-badge-2.webp"
|
import glassBadge2Webp from "@/assets/gebos/glass-badge-2.webp"
|
||||||
import glassBadge3Avif from "@/assets/gebos/glass-badge-3.avif"
|
import glassBadge3Avif from "@/assets/gebos/glass-badge-3.avif"
|
||||||
import glassBadge3Webp from "@/assets/gebos/glass-badge-3.webp"
|
import glassBadge3Webp from "@/assets/gebos/glass-badge-3.webp"
|
||||||
|
import glassBadge4Avif from "@/assets/gebos/glass-badge-4.avif"
|
||||||
|
import glassBadge4Webp from "@/assets/gebos/glass-badge-4.webp"
|
||||||
|
import glassBadge5Avif from "@/assets/gebos/glass-badge-5.avif"
|
||||||
|
import glassBadge5Webp from "@/assets/gebos/glass-badge-5.webp"
|
||||||
|
import glassBadge6Avif from "@/assets/gebos/glass-badge-6.avif"
|
||||||
|
import glassBadge6Webp from "@/assets/gebos/glass-badge-6.webp"
|
||||||
import glassCubes from "@/assets/gebos/glass-cubes.svg"
|
import glassCubes from "@/assets/gebos/glass-cubes.svg"
|
||||||
import glassCubesStackAvif from "@/assets/gebos/glass-cubes-stack.avif"
|
import glassCubesStackAvif from "@/assets/gebos/glass-cubes-stack.avif"
|
||||||
import glassCubesStackWebp from "@/assets/gebos/glass-cubes-stack.webp"
|
import glassCubesStackWebp from "@/assets/gebos/glass-cubes-stack.webp"
|
||||||
|
import glassDonutShadowAvif from "@/assets/gebos/glass-donut-shadow.avif"
|
||||||
|
import glassDonutShadowWebp from "@/assets/gebos/glass-donut-shadow.webp"
|
||||||
|
import glassDonutAvif from "@/assets/gebos/glass-donut.avif"
|
||||||
|
import glassDonutWebp from "@/assets/gebos/glass-donut.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 housesSmallIsoAvif from "@/assets/gebos/houses-small-isometric.avif"
|
||||||
|
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"
|
||||||
import iconPortal from "@/assets/gebos/icon-portal.svg"
|
import iconPortal from "@/assets/gebos/icon-portal.svg"
|
||||||
import iconRadio from "@/assets/gebos/icon-radio.svg"
|
import iconRadio from "@/assets/gebos/icon-radio.svg"
|
||||||
@@ -61,8 +73,12 @@ import shippingBoxClosedAvif from "@/assets/gebos/shipping-box-closed.avif"
|
|||||||
import shippingBoxClosedWebp from "@/assets/gebos/shipping-box-closed.webp"
|
import shippingBoxClosedWebp from "@/assets/gebos/shipping-box-closed.webp"
|
||||||
import shippingBoxMetersAvif from "@/assets/gebos/shipping-box-meters.avif"
|
import shippingBoxMetersAvif from "@/assets/gebos/shipping-box-meters.avif"
|
||||||
import shippingBoxMetersWebp from "@/assets/gebos/shipping-box-meters.webp"
|
import shippingBoxMetersWebp from "@/assets/gebos/shipping-box-meters.webp"
|
||||||
|
import smokeAlarmAlphaAvif from "@/assets/gebos/smoke-alarm-alpha.avif"
|
||||||
|
import smokeAlarmAlphaWebp from "@/assets/gebos/smoke-alarm-alpha.webp"
|
||||||
import smokeAlarmAvif from "@/assets/gebos/smoke-alarm.avif"
|
import smokeAlarmAvif from "@/assets/gebos/smoke-alarm.avif"
|
||||||
import smokeAlarmWebp from "@/assets/gebos/smoke-alarm.webp"
|
import smokeAlarmWebp from "@/assets/gebos/smoke-alarm.webp"
|
||||||
|
import sprinterAvif from "@/assets/gebos/sprinter.avif"
|
||||||
|
import sprinterWebp from "@/assets/gebos/sprinter.webp"
|
||||||
import tealBars from "@/assets/gebos/teal-bars.svg"
|
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"
|
||||||
@@ -165,6 +181,13 @@ const renders = {
|
|||||||
alt: "Modernes Mehrfamilienhaus",
|
alt: "Modernes Mehrfamilienhaus",
|
||||||
matte: "alpha",
|
matte: "alpha",
|
||||||
},
|
},
|
||||||
|
/** same subject as `smokeAlarm`, but with genuine alpha – safe on any surface */
|
||||||
|
smokeAlarmAlpha: {
|
||||||
|
avif: smokeAlarmAlphaAvif,
|
||||||
|
webp: smokeAlarmAlphaWebp,
|
||||||
|
alt: "Rauchwarnmelder",
|
||||||
|
matte: "alpha",
|
||||||
|
},
|
||||||
/** isometric corner view of the same building type, genuine alpha */
|
/** isometric corner view of the same building type, genuine alpha */
|
||||||
buildingIso: {
|
buildingIso: {
|
||||||
avif: buildingIsoAvif,
|
avif: buildingIsoAvif,
|
||||||
@@ -179,6 +202,13 @@ const renders = {
|
|||||||
alt: "Einfamilienhaus, isometrisch",
|
alt: "Einfamilienhaus, isometrisch",
|
||||||
matte: "alpha",
|
matte: "alpha",
|
||||||
},
|
},
|
||||||
|
/** cluster of small apartment blocks, isometric, genuine alpha */
|
||||||
|
housesSmallIso: {
|
||||||
|
avif: housesSmallIsoAvif,
|
||||||
|
webp: housesSmallIsoWebp,
|
||||||
|
alt: "Mehrere Mehrfamilienhäuser, isometrisch",
|
||||||
|
matte: "alpha",
|
||||||
|
},
|
||||||
/** full hero scene: building, gateway, consumption bars & ring on a glass platform */
|
/** full hero scene: building, gateway, consumption bars & ring on a glass platform */
|
||||||
fullHeroSection: {
|
fullHeroSection: {
|
||||||
avif: fullHeroSectionAvif,
|
avif: fullHeroSectionAvif,
|
||||||
@@ -211,6 +241,18 @@ const renders = {
|
|||||||
alt: "",
|
alt: "",
|
||||||
matte: "alpha",
|
matte: "alpha",
|
||||||
},
|
},
|
||||||
|
glassDonut: {
|
||||||
|
avif: glassDonutAvif,
|
||||||
|
webp: glassDonutWebp,
|
||||||
|
alt: "",
|
||||||
|
matte: "alpha",
|
||||||
|
},
|
||||||
|
glassDonutShadow: {
|
||||||
|
avif: glassDonutShadowAvif,
|
||||||
|
webp: glassDonutShadowWebp,
|
||||||
|
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,
|
||||||
@@ -232,6 +274,27 @@ const renders = {
|
|||||||
alt: "",
|
alt: "",
|
||||||
matte: "alpha",
|
matte: "alpha",
|
||||||
},
|
},
|
||||||
|
/** glossy numbered badge "4" – process-step numeral, genuine alpha */
|
||||||
|
glassBadge4: {
|
||||||
|
avif: glassBadge4Avif,
|
||||||
|
webp: glassBadge4Webp,
|
||||||
|
alt: "",
|
||||||
|
matte: "alpha",
|
||||||
|
},
|
||||||
|
/** glossy numbered badge "5" – process-step numeral, genuine alpha */
|
||||||
|
glassBadge5: {
|
||||||
|
avif: glassBadge5Avif,
|
||||||
|
webp: glassBadge5Webp,
|
||||||
|
alt: "",
|
||||||
|
matte: "alpha",
|
||||||
|
},
|
||||||
|
/** glossy numbered badge "6" – process-step numeral, genuine alpha */
|
||||||
|
glassBadge6: {
|
||||||
|
avif: glassBadge6Avif,
|
||||||
|
webp: glassBadge6Webp,
|
||||||
|
alt: "",
|
||||||
|
matte: "alpha",
|
||||||
|
},
|
||||||
cubesRound: {
|
cubesRound: {
|
||||||
avif: cubesRoundAvif,
|
avif: cubesRoundAvif,
|
||||||
webp: cubesRoundWebp,
|
webp: cubesRoundWebp,
|
||||||
@@ -262,6 +325,12 @@ const renders = {
|
|||||||
alt: "Fußbodenheizungsrohre",
|
alt: "Fußbodenheizungsrohre",
|
||||||
matte: "alpha",
|
matte: "alpha",
|
||||||
},
|
},
|
||||||
|
sprinter: {
|
||||||
|
avif: sprinterAvif,
|
||||||
|
webp: sprinterWebp,
|
||||||
|
alt: "Servicefahrzeug",
|
||||||
|
matte: "alpha",
|
||||||
|
},
|
||||||
workerTablet: {
|
workerTablet: {
|
||||||
avif: workerTabletAvif,
|
avif: workerTabletAvif,
|
||||||
webp: workerTabletWebp,
|
webp: workerTabletWebp,
|
||||||
|
|||||||
@@ -31,14 +31,16 @@ 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: "/loesungen", label: "Lösungen" },
|
{ to: "/loesungen", label: "Lösungen" },
|
||||||
{ to: "/so-funktionierts", label: "So funktioniert’s" },
|
{ to: "/so-funktionierts", label: "So funktioniert’s" },
|
||||||
{ to: "/fuer-wen", label: "Für wen?", menu: AUDIENCES },
|
{ to: "/fuer-wen", label: "Für wen?", menu: AUDIENCES },
|
||||||
{ to: "/konfigurator", label: "Konfigurator" },
|
{ to: "/konfigurator", label: "Konfigurator" },
|
||||||
{ to: "/unternehmen", label: "Unternehmen" },
|
|
||||||
{ to: "/kontakt", label: "Kontakt" },
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
/** "/" is a prefix of every route, so only the root may match exactly. */
|
||||||
|
const isExact = (to: string) => to === "/"
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Only colour changes between states: the highlight itself is one shared pill
|
* Only colour changes between states: the highlight itself is one shared pill
|
||||||
* that slides, so a weight change here would reflow the row it has to land on.
|
* that slides, so a weight change here would reflow the row it has to land on.
|
||||||
@@ -147,9 +149,9 @@ function AudienceMenu({ item, active }: { item: NavItem; active: boolean }) {
|
|||||||
|
|
||||||
function DesktopNav() {
|
function DesktopNav() {
|
||||||
const { pathname } = useLocation()
|
const { pathname } = useLocation()
|
||||||
/** mirrors NavLink's default (non-`end`) matching, but for the whole row at once */
|
/** mirrors each NavLink's own matching, but for the whole row at once */
|
||||||
const activeKey = NAV_ITEMS.find((item) =>
|
const activeKey = NAV_ITEMS.find((item) =>
|
||||||
matchPath({ path: item.to, end: false }, pathname)
|
matchPath({ path: item.to, end: isExact(item.to) }, pathname)
|
||||||
)?.to
|
)?.to
|
||||||
const { navRef, pill } = useActivePill(activeKey)
|
const { navRef, pill } = useActivePill(activeKey)
|
||||||
|
|
||||||
@@ -173,6 +175,7 @@ function DesktopNav() {
|
|||||||
<NavLink
|
<NavLink
|
||||||
key={item.to}
|
key={item.to}
|
||||||
to={item.to}
|
to={item.to}
|
||||||
|
end={isExact(item.to)}
|
||||||
data-nav={item.to}
|
data-nav={item.to}
|
||||||
className={navItemClass(item.to === activeKey)}
|
className={navItemClass(item.to === activeKey)}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import { cn } from "@/lib/utils"
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* On/off switch. The lit track is the same teal glass as a primary button, at
|
* On/off switch. The lit track is the same teal glass as a primary button, at
|
||||||
* pill scale (`inset-shadow-glass-pill`); off it is a recessed navy pane. The
|
* pill scale (`inset-shadow-glass-pill`); underneath sits a recessed navy pane.
|
||||||
* thumb travels on the site's spring easing and carries the checkbox tick, so
|
* The thumb travels on the site's spring easing and carries the checkbox tick,
|
||||||
* the state reads without relying on colour alone.
|
* so the state reads without relying on colour alone.
|
||||||
*/
|
*/
|
||||||
function Switch({
|
function Switch({
|
||||||
className,
|
className,
|
||||||
@@ -18,19 +18,28 @@ function Switch({
|
|||||||
<SwitchPrimitive.Root
|
<SwitchPrimitive.Root
|
||||||
data-slot="switch"
|
data-slot="switch"
|
||||||
className={cn(
|
className={cn(
|
||||||
"group peer inline-flex h-7 w-12 shrink-0 cursor-pointer items-center rounded-full p-0.5 transition-colors outline-none",
|
"group peer relative inline-flex h-7 w-12 shrink-0 cursor-pointer items-center rounded-full p-0.5 outline-none",
|
||||||
|
"inset-shadow-xs bg-navy/20 transition-colors duration-300 motion-reduce:transition-none",
|
||||||
|
"data-[state=unchecked]:hover:bg-navy/30 data-[state=checked]:bg-brand-600",
|
||||||
"focus-visible:ring-[3px] focus-visible:ring-ring/40",
|
"focus-visible:ring-[3px] focus-visible:ring-ring/40",
|
||||||
"disabled:cursor-not-allowed disabled:opacity-50",
|
"disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
"data-[state=unchecked]:inset-shadow-xs data-[state=unchecked]:bg-navy/20 data-[state=unchecked]:hover:bg-navy/30",
|
|
||||||
"data-[state=checked]:glass-teal data-[state=checked]:inset-shadow-glass-pill data-[state=checked]:shadow-glass data-[state=checked]:bg-brand-600 data-[state=checked]:hover:glass-lit",
|
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
|
{/*
|
||||||
|
* The teal glass rides its own layer: gradients cannot interpolate from
|
||||||
|
* `none`, so switching it on the track itself would snap. Fading the
|
||||||
|
* layer carries the gradient, rim and cast bloom across together.
|
||||||
|
*/}
|
||||||
|
<span
|
||||||
|
aria-hidden="true"
|
||||||
|
className="glass-teal inset-shadow-glass-pill shadow-glass pointer-events-none absolute inset-0 rounded-full opacity-0 transition-opacity duration-300 group-hover:glass-lit group-data-[state=checked]:opacity-100 motion-reduce:transition-none"
|
||||||
|
/>
|
||||||
<SwitchPrimitive.Thumb
|
<SwitchPrimitive.Thumb
|
||||||
data-slot="switch-thumb"
|
data-slot="switch-thumb"
|
||||||
className={cn(
|
className={cn(
|
||||||
"pointer-events-none flex size-6 items-center justify-center rounded-full bg-card ring-1 ring-navy/10",
|
"pointer-events-none relative flex size-6 items-center justify-center rounded-full bg-card ring-1 ring-navy/10",
|
||||||
"shadow-[0_1px_2px_rgb(19_41_60/0.2)]",
|
"shadow-[0_1px_2px_rgb(19_41_60/0.2)]",
|
||||||
"transition-transform duration-300 ease-[cubic-bezier(0.34,1.16,0.42,1)] motion-reduce:transition-none",
|
"transition-transform duration-300 ease-[cubic-bezier(0.34,1.16,0.42,1)] motion-reduce:transition-none",
|
||||||
"data-[state=unchecked]:translate-x-0 data-[state=checked]:translate-x-5"
|
"data-[state=unchecked]:translate-x-0 data-[state=checked]:translate-x-5"
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ export default function HauseigentuemerPage() {
|
|||||||
}
|
}
|
||||||
action={
|
action={
|
||||||
<Button asChild size="lg">
|
<Button asChild size="lg">
|
||||||
<Link to="/konfigurator">Preis berechnen</Link>
|
<Link to="/konfigurator?installation=0">Preis berechnen</Link>
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@@ -296,7 +296,7 @@ export default function HauseigentuemerPage() {
|
|||||||
visual={<FullServiceArt />}
|
visual={<FullServiceArt />}
|
||||||
action={
|
action={
|
||||||
<Button asChild size="lg" variant="outline">
|
<Button asChild size="lg" variant="outline">
|
||||||
<Link to="/kontakt">Komplettlösung anfragen</Link>
|
<Link to="/konfigurator?installation=1">Preis berechnen</Link>
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import * as React from "react"
|
import * as React from "react"
|
||||||
|
import { useSearchParams } from "react-router-dom"
|
||||||
import { Ban, Check, CircleCheck, Euro, Plus, X } from "lucide-react"
|
import { Ban, Check, CircleCheck, Euro, Plus, X } from "lucide-react"
|
||||||
|
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
@@ -40,9 +41,13 @@ const HEAT_OPTIONS: { value: Heat; label: string }[] = [
|
|||||||
{ value: "unbekannt", label: "Weiß ich nicht" },
|
{ value: "unbekannt", label: "Weiß ich nicht" },
|
||||||
]
|
]
|
||||||
|
|
||||||
const INITIAL_BUILDINGS: Building[] = [
|
const INITIAL_BUILDING: Building = {
|
||||||
{ id: 1, adresse: "", heat: "heizkoerper", wohnungen: 12, zimmer: 3 },
|
id: 1,
|
||||||
]
|
adresse: "",
|
||||||
|
heat: "heizkoerper",
|
||||||
|
wohnungen: 12,
|
||||||
|
zimmer: 3,
|
||||||
|
}
|
||||||
|
|
||||||
/** Grundpreise je Wohnung / Monat – Selbstmontage, wie in der Klarpreis-Liste. */
|
/** Grundpreise je Wohnung / Monat – Selbstmontage, wie in der Klarpreis-Liste. */
|
||||||
const UVI_MONTHLY = 10.5
|
const UVI_MONTHLY = 10.5
|
||||||
@@ -81,6 +86,74 @@ function settle(value: number | "", min: number) {
|
|||||||
return value === "" || value < min ? min : value
|
return value === "" || value < min ? min : value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deep-link preset. Pages elsewhere on the site link into the configurator with
|
||||||
|
* parts of the form already answered, e.g. `/konfigurator?installation=0` for
|
||||||
|
* the self-install route. Supported params: `installation`, `uvi`, `rwm`,
|
||||||
|
* `adresse`, `heizung`, `wohnungen`, `zimmer`. Missing or unreadable values
|
||||||
|
* keep the page defaults; numbers below the field minimum settle at it, just
|
||||||
|
* as they do when typed into the form.
|
||||||
|
*/
|
||||||
|
type Preset = {
|
||||||
|
uvi?: boolean
|
||||||
|
rwm?: boolean
|
||||||
|
install?: boolean
|
||||||
|
/** Seeds the single building the configurator starts with. */
|
||||||
|
building: Partial<Building>
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A bare flag (`?installation`) counts as "on" so short links stay readable. */
|
||||||
|
function presetFlag(raw: string | null): boolean | undefined {
|
||||||
|
if (raw === null) return undefined
|
||||||
|
switch (raw.trim().toLowerCase()) {
|
||||||
|
case "":
|
||||||
|
case "1":
|
||||||
|
case "true":
|
||||||
|
case "ja":
|
||||||
|
return true
|
||||||
|
case "0":
|
||||||
|
case "false":
|
||||||
|
case "nein":
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function presetNumber(
|
||||||
|
raw: string | null,
|
||||||
|
parse: (raw: string) => number | ""
|
||||||
|
): number | undefined {
|
||||||
|
if (raw === null) return undefined
|
||||||
|
const value = parse(raw)
|
||||||
|
return value === "" ? undefined : settle(value, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
function presetHeat(raw: string | null): Heat | undefined {
|
||||||
|
return HEAT_OPTIONS.some((opt) => opt.value === raw)
|
||||||
|
? (raw as Heat)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
function readPreset(params: URLSearchParams): Preset {
|
||||||
|
const building: Partial<Building> = {}
|
||||||
|
const adresse = params.get("adresse")
|
||||||
|
if (adresse !== null) building.adresse = adresse
|
||||||
|
const heat = presetHeat(params.get("heizung"))
|
||||||
|
if (heat !== undefined) building.heat = heat
|
||||||
|
const wohnungen = presetNumber(params.get("wohnungen"), parseCount)
|
||||||
|
if (wohnungen !== undefined) building.wohnungen = wohnungen
|
||||||
|
const zimmer = presetNumber(params.get("zimmer"), parseAmount)
|
||||||
|
if (zimmer !== undefined) building.zimmer = zimmer
|
||||||
|
|
||||||
|
return {
|
||||||
|
uvi: presetFlag(params.get("uvi")),
|
||||||
|
rwm: presetFlag(params.get("rwm")),
|
||||||
|
install: presetFlag(params.get("installation")),
|
||||||
|
building,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** glossy numerals for the three configurator steps */
|
/** glossy numerals for the three configurator steps */
|
||||||
type Step = 1 | 2 | 3
|
type Step = 1 | 2 | 3
|
||||||
|
|
||||||
@@ -254,12 +327,32 @@ function PriceRow({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function ConfiguratorPage() {
|
export default function ConfiguratorPage() {
|
||||||
const [uvi, setUvi] = React.useState(true)
|
const [searchParams] = useSearchParams()
|
||||||
const [rwm, setRwm] = React.useState(true)
|
const preset = React.useMemo(() => readPreset(searchParams), [searchParams])
|
||||||
const [buildings, setBuildings] = React.useState<Building[]>(INITIAL_BUILDINGS)
|
|
||||||
const [install, setInstall] = React.useState(true)
|
const [uvi, setUvi] = React.useState(preset.uvi ?? true)
|
||||||
|
const [rwm, setRwm] = React.useState(preset.rwm ?? true)
|
||||||
|
const [buildings, setBuildings] = React.useState<Building[]>(() => [
|
||||||
|
{ ...INITIAL_BUILDING, ...preset.building },
|
||||||
|
])
|
||||||
|
const [install, setInstall] = React.useState(preset.install ?? true)
|
||||||
const [email, setEmail] = React.useState("")
|
const [email, setEmail] = React.useState("")
|
||||||
const nextId = React.useRef(INITIAL_BUILDINGS.length + 1)
|
const nextId = React.useRef(INITIAL_BUILDING.id + 1)
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Following a second preset link while the page stays mounted re-seeds the
|
||||||
|
* form; the mount-time preset is already applied by the initialisers above.
|
||||||
|
*/
|
||||||
|
const appliedPreset = React.useRef(preset)
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (appliedPreset.current === preset) return
|
||||||
|
appliedPreset.current = preset
|
||||||
|
setUvi(preset.uvi ?? true)
|
||||||
|
setRwm(preset.rwm ?? true)
|
||||||
|
setInstall(preset.install ?? true)
|
||||||
|
setBuildings([{ ...INITIAL_BUILDING, ...preset.building }])
|
||||||
|
nextId.current = INITIAL_BUILDING.id + 1
|
||||||
|
}, [preset])
|
||||||
|
|
||||||
const updateBuilding = (id: number, patch: Partial<Building>) => {
|
const updateBuilding = (id: number, patch: Partial<Building>) => {
|
||||||
setBuildings((prev) => prev.map((b) => (b.id === id ? { ...b, ...patch } : b)))
|
setBuildings((prev) => prev.map((b) => (b.id === id ? { ...b, ...patch } : b)))
|
||||||
|
|||||||
@@ -1,19 +1,12 @@
|
|||||||
import { Link } from "react-router-dom"
|
import { Link } from "react-router-dom"
|
||||||
import {
|
import { ArrowRight, Calculator } from "lucide-react"
|
||||||
ArrowRight,
|
|
||||||
Building2,
|
|
||||||
Calculator,
|
|
||||||
HardHat,
|
|
||||||
House,
|
|
||||||
Network,
|
|
||||||
} 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, IconBadge } from "@/components/gebos/icon-tile"
|
import { FeatureTile } from "@/components/gebos/icon-tile"
|
||||||
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"
|
||||||
@@ -27,34 +20,34 @@ import {
|
|||||||
const features = [
|
const features = [
|
||||||
{
|
{
|
||||||
media: <img src={icons.uvi} alt="" className="size-14" loading="lazy" />,
|
media: <img src={icons.uvi} alt="" className="size-14" loading="lazy" />,
|
||||||
title: "UVI",
|
title: "Unterjährighe Verbrauchsinformation (UVI)",
|
||||||
sub: "Monatliche Verbrauchsinformation",
|
// sub: "Monatliche Verbrauchsinformation",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
media: (
|
media: (
|
||||||
<img src={icons.billing} alt="" className="size-14" loading="lazy" />
|
<img src={icons.billing} alt="" className="size-14" loading="lazy" />
|
||||||
),
|
),
|
||||||
title: "Heizkosten\u00adabrechnung",
|
title: "Heizkosten\u00adabrechnung",
|
||||||
sub: "Jährlich korrekt abrechnen",
|
// sub: "Jährlich korrekt abrechnen",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
media: (
|
media: (
|
||||||
<img src={icons.smokeAlarm} alt="" className="size-14" loading="lazy" />
|
<img src={icons.smokeAlarm} alt="" className="size-14" loading="lazy" />
|
||||||
),
|
),
|
||||||
title: "Rauchwarnmelder",
|
title: "Rauchwarnmelder",
|
||||||
sub: "Sicher überwacht, zentral verwaltet",
|
// sub: "Sicher überwacht, zentral verwaltet",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
media: <img src={icons.radio} alt="" className="size-14" loading="lazy" />,
|
media: <img src={icons.radio} alt="" className="size-14" loading="lazy" />,
|
||||||
title: "Fernauslesbare Messtechnik",
|
title: "Fernauslesbare Messtechnik",
|
||||||
sub: "Automatisch erfasst, sicher übertragen",
|
// sub: "Automatisch erfasst, sicher übertragen",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
media: (
|
media: (
|
||||||
<img src={icons.portal} alt="" className="size-14" loading="lazy" />
|
<img src={icons.portal} alt="" className="size-14" loading="lazy" />
|
||||||
),
|
),
|
||||||
title: "Portale & Verwaltung",
|
title: "Portale & Verwaltung",
|
||||||
sub: "Alle Daten und Geräte im Blick",
|
// sub: "Alle Daten und Geräte im Blick",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -153,22 +146,18 @@ const processSteps = [
|
|||||||
|
|
||||||
const lessEffort = [
|
const lessEffort = [
|
||||||
{
|
{
|
||||||
vector: vectors.packageBox,
|
|
||||||
title: "Vorkonfigurierte Hardware",
|
title: "Vorkonfigurierte Hardware",
|
||||||
text: "Geräte werden passend zum jeweiligen Gebäude und zur vorgesehenen Nutzung vorbereitet.",
|
text: "Geräte werden passend zum jeweiligen Gebäude und zur vorgesehenen Nutzung vorbereitet.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
vector: vectors.checkOrb,
|
|
||||||
title: "Keine aufwendige Einrichtung auf der Baustelle",
|
title: "Keine aufwendige Einrichtung auf der Baustelle",
|
||||||
text: "Der Installateur kann sich auf die fachgerechte Montage konzentrieren.",
|
text: "Der Installateur kann sich auf die fachgerechte Montage konzentrieren.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
vector: vectors.checkOrb,
|
|
||||||
title: "Bestehende Fachbetriebe nutzen",
|
title: "Bestehende Fachbetriebe nutzen",
|
||||||
text: "Kein spezieller GebOS-Partner erforderlich – eigene Hausmeister, Heizungsbauer oder bestehende Installationspartner können weiterhin eingesetzt werden.",
|
text: "Kein spezieller GebOS-Partner erforderlich – eigene Hausmeister, Heizungsbauer oder bestehende Installationspartner können weiterhin eingesetzt werden.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
vector: vectors.checkOrb,
|
|
||||||
title: "Weniger doppelte Dateneingabe",
|
title: "Weniger doppelte Dateneingabe",
|
||||||
text: "Informationen aus Bestellung und Konfiguration werden für den späteren Betrieb weiterverwendet.",
|
text: "Informationen aus Bestellung und Konfiguration werden für den späteren Betrieb weiterverwendet.",
|
||||||
},
|
},
|
||||||
@@ -203,7 +192,14 @@ const systemTasks = [
|
|||||||
|
|
||||||
const audiences = [
|
const audiences = [
|
||||||
{
|
{
|
||||||
icon: <House />,
|
media: (
|
||||||
|
<AssetRender
|
||||||
|
render={renders.houseSmallIso}
|
||||||
|
alt=""
|
||||||
|
className="flex size-14 shrink-0 items-center justify-center"
|
||||||
|
imgClassName="w-14"
|
||||||
|
/>
|
||||||
|
),
|
||||||
name: "Hauseigentümer",
|
name: "Hauseigentümer",
|
||||||
headline: "Messdienstleistungen für Ihr Mehrfamilienhaus",
|
headline: "Messdienstleistungen für Ihr Mehrfamilienhaus",
|
||||||
text: "Installieren Sie vorkonfigurierte Technik selbst beziehungsweise mit Ihrem bestehenden Fachbetrieb – oder überlassen Sie GebOS die komplette Umsetzung.",
|
text: "Installieren Sie vorkonfigurierte Technik selbst beziehungsweise mit Ihrem bestehenden Fachbetrieb – oder überlassen Sie GebOS die komplette Umsetzung.",
|
||||||
@@ -216,7 +212,14 @@ const audiences = [
|
|||||||
to: "/fuer-wen/hauseigentuemer",
|
to: "/fuer-wen/hauseigentuemer",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <Building2 />,
|
media: (
|
||||||
|
<AssetRender
|
||||||
|
render={renders.housesSmallIso}
|
||||||
|
alt=""
|
||||||
|
className="flex size-14 shrink-0 items-center justify-center"
|
||||||
|
imgClassName="w-14"
|
||||||
|
/>
|
||||||
|
),
|
||||||
name: "Hausverwaltungen",
|
name: "Hausverwaltungen",
|
||||||
headline: "Viele Gebäude. Ein einfacher Rollout.",
|
headline: "Viele Gebäude. Ein einfacher Rollout.",
|
||||||
text: "Erfassen Sie Ihre Objekte einmal, erhalten Sie vorkonfigurierte Hardware und nutzen Sie bestehende Mitarbeiter oder Installationspartner für die Montage.",
|
text: "Erfassen Sie Ihre Objekte einmal, erhalten Sie vorkonfigurierte Hardware und nutzen Sie bestehende Mitarbeiter oder Installationspartner für die Montage.",
|
||||||
@@ -229,7 +232,14 @@ const audiences = [
|
|||||||
to: "/fuer-wen/hausverwaltungen",
|
to: "/fuer-wen/hausverwaltungen",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <Network />,
|
media: (
|
||||||
|
<AssetRender
|
||||||
|
render={renders.sprinter}
|
||||||
|
alt=""
|
||||||
|
className="flex size-14 shrink-0 items-center justify-center"
|
||||||
|
imgClassName="w-14"
|
||||||
|
/>
|
||||||
|
),
|
||||||
name: "Messdienstleister",
|
name: "Messdienstleister",
|
||||||
headline: "Ihre Messdienstleistung. Unsere Infrastruktur.",
|
headline: "Ihre Messdienstleistung. Unsere Infrastruktur.",
|
||||||
text: "Nutzen Sie GebOS als technische Grundlage für Ihre eigenen Dienstleistungen – mit Hardware, Plattform, Gerätemanagement und optionalem White Label.",
|
text: "Nutzen Sie GebOS als technische Grundlage für Ihre eigenen Dienstleistungen – mit Hardware, Plattform, Gerätemanagement und optionalem White Label.",
|
||||||
@@ -238,7 +248,14 @@ const audiences = [
|
|||||||
to: "/fuer-wen/messdienstleister",
|
to: "/fuer-wen/messdienstleister",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <HardHat />,
|
media: (
|
||||||
|
<AssetRender
|
||||||
|
render={renders.workerTablet}
|
||||||
|
alt=""
|
||||||
|
className="flex size-14 shrink-0 items-center justify-center"
|
||||||
|
imgClassName="w-14"
|
||||||
|
/>
|
||||||
|
),
|
||||||
name: "Installateure & Heizungsbauer",
|
name: "Installateure & Heizungsbauer",
|
||||||
headline: "Bestellen. Montieren. Fertig.",
|
headline: "Bestellen. Montieren. Fertig.",
|
||||||
text: "Setzen Sie GebOS bei Ihren bestehenden Kunden ein, ohne zunächst ein Partnerprogramm durchlaufen zu müssen.",
|
text: "Setzen Sie GebOS bei Ihren bestehenden Kunden ein, ohne zunächst ein Partnerprogramm durchlaufen zu müssen.",
|
||||||
@@ -294,7 +311,7 @@ export default function HomePage() {
|
|||||||
<span className="text-primary">einfach</span> gemacht.
|
<span className="text-primary">einfach</span> gemacht.
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
lead="Unterjährige Verbrauchsinformation (UVI), Heizkostenabrechnung und Rauchwarnmelder – von der vorkonfigurierten Hardware bis zum laufenden Betrieb."
|
lead="Unterjährige Verbrauchsinformation (UVI), Heizkostenabrechnung und Rauchwarnmelder – mit vorkonfigurierter Hardware."
|
||||||
actions={
|
actions={
|
||||||
<>
|
<>
|
||||||
<Button asChild size="lg">
|
<Button asChild size="lg">
|
||||||
@@ -313,7 +330,7 @@ export default function HomePage() {
|
|||||||
<div className="rounded-2xl border border-border bg-card p-6 shadow-card sm:p-8">
|
<div className="rounded-2xl border border-border bg-card p-6 shadow-card sm:p-8">
|
||||||
<div className="grid grid-cols-2 gap-x-4 gap-y-8 sm:grid-cols-3 lg:grid-cols-5">
|
<div className="grid grid-cols-2 gap-x-4 gap-y-8 sm:grid-cols-3 lg:grid-cols-5">
|
||||||
{features.map((feature) => (
|
{features.map((feature) => (
|
||||||
<FeatureTile key={feature.sub} {...feature} />
|
<FeatureTile key={feature.title} {...feature} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -351,14 +368,6 @@ export default function HomePage() {
|
|||||||
lead="Von der Bestellung bis zum laufenden Betrieb – GebOS verbindet Bestellung, Konfiguration und Installation miteinander."
|
lead="Von der Bestellung bis zum laufenden Betrieb – GebOS verbindet Bestellung, Konfiguration und Installation miteinander."
|
||||||
/>
|
/>
|
||||||
<ProcessSteps steps={processSteps} className="mt-12" />
|
<ProcessSteps steps={processSteps} className="mt-12" />
|
||||||
<p className="mt-10 text-center text-base font-semibold italic text-navy">
|
|
||||||
„Bestellung, Konfiguration und Installation greifen ineinander.“
|
|
||||||
</p>
|
|
||||||
<div className="mt-6 flex justify-center">
|
|
||||||
<Button asChild size="lg">
|
|
||||||
<Link to="/konfigurator">Preis für mein Gebäude berechnen</Link>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section>
|
<Section>
|
||||||
@@ -372,12 +381,6 @@ export default function HomePage() {
|
|||||||
{lessEffort.map((item) => (
|
{lessEffort.map((item) => (
|
||||||
<Card key={item.title} className="p-6 gap-0">
|
<Card key={item.title} className="p-6 gap-0">
|
||||||
<CardContent className="flex h-full flex-col items-start gap-3 p-0">
|
<CardContent className="flex h-full flex-col items-start gap-3 p-0">
|
||||||
<img
|
|
||||||
src={item.vector}
|
|
||||||
alt=""
|
|
||||||
className="size-12"
|
|
||||||
loading="lazy"
|
|
||||||
/>
|
|
||||||
<h3 className="text-base font-bold text-navy">
|
<h3 className="text-base font-bold text-navy">
|
||||||
{item.title}
|
{item.title}
|
||||||
</h3>
|
</h3>
|
||||||
@@ -460,16 +463,15 @@ export default function HomePage() {
|
|||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<Section>
|
<Section>
|
||||||
|
<div className="grid items-start gap-10 lg:grid-cols-2 lg:gap-14">
|
||||||
|
<div>
|
||||||
<SectionHeading
|
<SectionHeading
|
||||||
eyebrow="Preis"
|
eyebrow="Preis"
|
||||||
title="Was kostet GebOS?"
|
title="Was kostet GebOS?"
|
||||||
lead="Mit dem GebOS-Konfigurator erhalten Sie bereits mit wenigen Angaben eine erste Preisindikation."
|
lead="Mit dem GebOS-Konfigurator erhalten Sie schnell einen ersten Preis und können sofort bestellen."
|
||||||
/>
|
/>
|
||||||
<div className="mt-10 grid items-start gap-10 lg:grid-cols-2 lg:gap-14">
|
<p className="mt-10 text-sm leading-relaxed text-muted-foreground">
|
||||||
<div>
|
Für einen ersten Preis brauchen wir Grundlegende Informationen:
|
||||||
<p className="text-sm leading-relaxed text-muted-foreground">
|
|
||||||
Dafür werden zunächst nur grundlegende Informationen benötigt,
|
|
||||||
beispielsweise:
|
|
||||||
</p>
|
</p>
|
||||||
<Checklist
|
<Checklist
|
||||||
className="mt-4"
|
className="mt-4"
|
||||||
@@ -485,15 +487,6 @@ export default function HomePage() {
|
|||||||
Auf Basis dieser Angaben schätzt GebOS die voraussichtlich
|
Auf Basis dieser Angaben schätzt GebOS die voraussichtlich
|
||||||
benötigte Geräteanzahl und berechnet einen ungefähren Preis.
|
benötigte Geräteanzahl und berechnet einen ungefähren Preis.
|
||||||
</p>
|
</p>
|
||||||
<h3 className="mt-8 text-lg font-extrabold tracking-tight text-navy">
|
|
||||||
Erst schätzen. Dann genau konfigurieren.
|
|
||||||
</h3>
|
|
||||||
<p className="mt-2 text-sm leading-relaxed text-muted-foreground">
|
|
||||||
Der Schnellkonfigurator arbeitet bewusst mit Annahmen. Für
|
|
||||||
einen genauen Preis können Sie die Konfiguration anschließend
|
|
||||||
speichern und Ihre Gebäude detailliert erfassen. Die bereits
|
|
||||||
eingegebenen Daten werden übernommen.
|
|
||||||
</p>
|
|
||||||
<Button asChild size="lg" className="mt-6">
|
<Button asChild size="lg" className="mt-6">
|
||||||
<Link to="/konfigurator">Preis berechnen</Link>
|
<Link to="/konfigurator">Preis berechnen</Link>
|
||||||
</Button>
|
</Button>
|
||||||
@@ -536,9 +529,7 @@ export default function HomePage() {
|
|||||||
<Card key={audience.to} className="p-6 gap-0 sm:p-7">
|
<Card key={audience.to} className="p-6 gap-0 sm:p-7">
|
||||||
<CardContent className="flex h-full flex-col items-start gap-3 p-0">
|
<CardContent className="flex h-full flex-col items-start gap-3 p-0">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<IconBadge variant="soft" shape="squircle" size="lg">
|
{audience.media}
|
||||||
{audience.icon}
|
|
||||||
</IconBadge>
|
|
||||||
<span className="text-xs font-bold tracking-wide text-primary uppercase">
|
<span className="text-xs font-bold tracking-wide text-primary uppercase">
|
||||||
{audience.name}
|
{audience.name}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -117,9 +117,6 @@ export default function HowItWorksPage() {
|
|||||||
<Card className="mt-10 gap-0 p-8 sm:mt-12 sm:p-10">
|
<Card className="mt-10 gap-0 p-8 sm:mt-12 sm:p-10">
|
||||||
<ProcessSteps steps={steps} />
|
<ProcessSteps steps={steps} />
|
||||||
</Card>
|
</Card>
|
||||||
<p className="mx-auto mt-10 max-w-xl text-center text-sm font-medium text-muted-foreground italic">
|
|
||||||
„Bestellung, Konfiguration und Installation greifen ineinander.“
|
|
||||||
</p>
|
|
||||||
</Section>
|
</Section>
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,11 +6,7 @@ import { Card } from "@/components/ui/card"
|
|||||||
import { CtaBanner } from "@/components/gebos/cta-banner"
|
import { CtaBanner } from "@/components/gebos/cta-banner"
|
||||||
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"
|
||||||
import {
|
import { AssetRender, renders } from "@/components/gebos/site-assets"
|
||||||
AssetRender,
|
|
||||||
renders,
|
|
||||||
vectors,
|
|
||||||
} from "@/components/gebos/site-assets"
|
|
||||||
|
|
||||||
const solutions = [
|
const solutions = [
|
||||||
{
|
{
|
||||||
@@ -21,11 +17,6 @@ const solutions = [
|
|||||||
illustration: (
|
illustration: (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<AssetRender render={renders.gateway} imgClassName="w-28" />
|
<AssetRender render={renders.gateway} imgClassName="w-28" />
|
||||||
<img
|
|
||||||
src={vectors.glassCubes}
|
|
||||||
alt=""
|
|
||||||
className="absolute -top-3 -right-8 w-14"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -34,7 +25,7 @@ const solutions = [
|
|||||||
sub: "Verbrauchsdaten korrekt abrechnen",
|
sub: "Verbrauchsdaten korrekt abrechnen",
|
||||||
description:
|
description:
|
||||||
"Verbrauchsdaten strukturiert erfassen und für die jährliche Abrechnung nutzen.",
|
"Verbrauchsdaten strukturiert erfassen und für die jährliche Abrechnung nutzen.",
|
||||||
illustration: <img src={vectors.consumptionRing} alt="" className="w-28" />,
|
illustration: <AssetRender render={renders.glassDonutShadow} alt="" className="w-28" />,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Rauchwarnmelder",
|
title: "Rauchwarnmelder",
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
It is important that we highlight the self serve model.
|
||||||
|
Meaning a customer can enter and register hist building and then righ away order.
|
||||||
|
For example on the configuration page it would be cool to have an indicator saying like "Von Gebäude-erfassung zur fertigen Bestellung in nur 15 Minuten".
|
||||||
|
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.
|
||||||