save
This commit is contained in:
Generated
+29
@@ -16,6 +16,7 @@
|
|||||||
"@radix-ui/react-select": "^2.2.5",
|
"@radix-ui/react-select": "^2.2.5",
|
||||||
"@radix-ui/react-separator": "^1.1.7",
|
"@radix-ui/react-separator": "^1.1.7",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
|
"@radix-ui/react-switch": "^1.3.7",
|
||||||
"@radix-ui/react-tabs": "^1.1.12",
|
"@radix-ui/react-tabs": "^1.1.12",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
@@ -1471,6 +1472,34 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@radix-ui/react-switch": {
|
||||||
|
"version": "1.3.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.3.7.tgz",
|
||||||
|
"integrity": "sha512-48tB/4dn2UVLBCYhTu9AuR63IHl73l/qLbLgxd86noTUor4/K4LFDAcYjK+isP5313qxaFpjPVogE7+Y0/V3Kw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/primitive": "1.1.7",
|
||||||
|
"@radix-ui/react-compose-refs": "1.1.5",
|
||||||
|
"@radix-ui/react-context": "1.2.2",
|
||||||
|
"@radix-ui/react-primitive": "2.1.10",
|
||||||
|
"@radix-ui/react-use-controllable-state": "1.2.6",
|
||||||
|
"@radix-ui/react-use-size": "1.1.4"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "*",
|
||||||
|
"@types/react-dom": "*",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@radix-ui/react-tabs": {
|
"node_modules/@radix-ui/react-tabs": {
|
||||||
"version": "1.1.21",
|
"version": "1.1.21",
|
||||||
"resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.21.tgz",
|
"resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.21.tgz",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
"@radix-ui/react-select": "^2.2.5",
|
"@radix-ui/react-select": "^2.2.5",
|
||||||
"@radix-ui/react-separator": "^1.1.7",
|
"@radix-ui/react-separator": "^1.1.7",
|
||||||
"@radix-ui/react-slot": "^1.2.3",
|
"@radix-ui/react-slot": "^1.2.3",
|
||||||
|
"@radix-ui/react-switch": "^1.3.7",
|
||||||
"@radix-ui/react-tabs": "^1.1.12",
|
"@radix-ui/react-tabs": "^1.1.12",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import consumptionRing from "@/assets/gebos/consumption-ring.svg"
|
|||||||
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 tealBars from "@/assets/gebos/teal-bars.svg"
|
import tealBars from "@/assets/gebos/teal-bars.svg"
|
||||||
import uviBadge from "@/assets/gebos/uvi-badge.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:
|
||||||
@@ -30,6 +31,11 @@ function HeroArt({ className }: { className?: string }) {
|
|||||||
alt=""
|
alt=""
|
||||||
className="absolute top-[12%] -right-[5%] z-0 w-[46%]"
|
className="absolute top-[12%] -right-[5%] z-0 w-[46%]"
|
||||||
/>
|
/>
|
||||||
|
<AssetRender
|
||||||
|
render={renders.barChart}
|
||||||
|
alt=""
|
||||||
|
className="absolute top-[52%] left-[10%] w-[16%] scale-200"
|
||||||
|
/>
|
||||||
<picture>
|
<picture>
|
||||||
<source srcSet={buildingAvif} type="image/avif" />
|
<source srcSet={buildingAvif} type="image/avif" />
|
||||||
<img
|
<img
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ function SiteFooter() {
|
|||||||
<footer className="glass-navy inset-shadow-glass-slab shadow-glass-slab rounded-3xl text-white">
|
<footer className="glass-navy inset-shadow-glass-slab shadow-glass-slab rounded-3xl text-white">
|
||||||
<Container className="grid gap-10 py-14 sm:grid-cols-2 lg:grid-cols-[1.2fr_1fr_1fr_1fr]">
|
<Container className="grid gap-10 py-14 sm:grid-cols-2 lg:grid-cols-[1.2fr_1fr_1fr_1fr]">
|
||||||
<div>
|
<div>
|
||||||
<Logo inverse />
|
<Logo inverse mark="3d" markClassName="size-9" />
|
||||||
<p className="mt-4 max-w-xs text-sm leading-relaxed text-white/60">
|
<p className="mt-4 max-w-xs text-sm leading-relaxed text-white/60">
|
||||||
Messdienstleistungen einfach gemacht – von der vorkonfigurierten
|
Messdienstleistungen einfach gemacht – von der vorkonfigurierten
|
||||||
Hardware bis zum laufenden Betrieb.
|
Hardware bis zum laufenden Betrieb.
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import * as React from "react"
|
||||||
|
import * as SwitchPrimitive from "@radix-ui/react-switch"
|
||||||
|
import { CheckIcon } from "lucide-react"
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* thumb travels on the site's spring easing and carries the checkbox tick, so
|
||||||
|
* the state reads without relying on colour alone.
|
||||||
|
*/
|
||||||
|
function Switch({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof SwitchPrimitive.Root>) {
|
||||||
|
return (
|
||||||
|
<SwitchPrimitive.Root
|
||||||
|
data-slot="switch"
|
||||||
|
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",
|
||||||
|
"focus-visible:ring-[3px] focus-visible:ring-ring/40",
|
||||||
|
"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
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<SwitchPrimitive.Thumb
|
||||||
|
data-slot="switch-thumb"
|
||||||
|
className={cn(
|
||||||
|
"pointer-events-none 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)]",
|
||||||
|
"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"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<CheckIcon
|
||||||
|
aria-hidden="true"
|
||||||
|
className="size-3.5 stroke-[3] text-primary opacity-0 transition-opacity duration-200 group-data-[state=checked]:opacity-100 motion-reduce:transition-none"
|
||||||
|
/>
|
||||||
|
</SwitchPrimitive.Thumb>
|
||||||
|
</SwitchPrimitive.Root>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Switch }
|
||||||
@@ -35,7 +35,7 @@ function HomeownerArt() {
|
|||||||
<div className="relative mx-auto aspect-[1.28] w-full max-w-[560px]">
|
<div className="relative mx-auto aspect-[1.28] w-full max-w-[560px]">
|
||||||
<AssetRender
|
<AssetRender
|
||||||
render={renders.buildingIso}
|
render={renders.buildingIso}
|
||||||
className="absolute top-[4%] right-0 w-[74%] z-50"
|
className="absolute top-[4%] right-0 w-[74%]"
|
||||||
imgClassName="w-full"
|
imgClassName="w-full"
|
||||||
loading="eager"
|
loading="eager"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { Input } from "@/components/ui/input"
|
|||||||
import { Label } from "@/components/ui/label"
|
import { Label } from "@/components/ui/label"
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||||
import { Separator } from "@/components/ui/separator"
|
import { Separator } from "@/components/ui/separator"
|
||||||
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"
|
import { Switch } from "@/components/ui/switch"
|
||||||
import { Checklist } from "@/components/gebos/checklist"
|
import { Checklist } from "@/components/gebos/checklist"
|
||||||
import { IconBadge } from "@/components/gebos/icon-tile"
|
import { IconBadge } from "@/components/gebos/icon-tile"
|
||||||
import { Container, Section, SectionHeading } from "@/components/gebos/section"
|
import { Container, Section, SectionHeading } from "@/components/gebos/section"
|
||||||
@@ -160,13 +160,12 @@ function ServiceOption({
|
|||||||
* "Nicht enthalten" instead of merely dimmed.
|
* "Nicht enthalten" instead of merely dimmed.
|
||||||
*/
|
*/
|
||||||
function PackageCard({
|
function PackageCard({
|
||||||
assets,
|
render,
|
||||||
title,
|
title,
|
||||||
items,
|
items,
|
||||||
included,
|
included,
|
||||||
}: {
|
}: {
|
||||||
/** drawn side by side in the header, first one leading */
|
render: RenderAsset
|
||||||
assets: RenderAsset[]
|
|
||||||
title: string
|
title: string
|
||||||
items: string[]
|
items: string[]
|
||||||
included: boolean
|
included: boolean
|
||||||
@@ -179,17 +178,12 @@ function PackageCard({
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="flex flex-wrap items-center gap-x-4 gap-y-3">
|
<div className="flex flex-wrap items-center gap-x-4 gap-y-3">
|
||||||
<div className="flex shrink-0 items-center gap-1">
|
<AssetRender
|
||||||
{assets.map((asset) => (
|
render={render}
|
||||||
<AssetRender
|
alt=""
|
||||||
key={asset.webp}
|
className="flex size-20 shrink-0 items-center justify-center"
|
||||||
render={asset}
|
imgClassName={cn("w-20", !included && "opacity-40 grayscale")}
|
||||||
alt=""
|
/>
|
||||||
className="flex size-20 items-center justify-center"
|
|
||||||
imgClassName={cn("w-20", !included && "opacity-40 grayscale")}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<h3
|
<h3
|
||||||
className={cn(
|
className={cn(
|
||||||
"text-xl font-extrabold tracking-tight",
|
"text-xl font-extrabold tracking-tight",
|
||||||
@@ -292,10 +286,6 @@ export default function ConfiguratorPage() {
|
|||||||
const rwmRatePerUnit = install ? RWM_ONE_TIME_INSTALLED : RWM_ONE_TIME_SELF
|
const rwmRatePerUnit = install ? RWM_ONE_TIME_INSTALLED : RWM_ONE_TIME_SELF
|
||||||
/* RWM hardware is billed once per Wohnung – the estimate is the sum. */
|
/* RWM hardware is billed once per Wohnung – the estimate is the sum. */
|
||||||
const rwmOneTimeTotal = rwm ? rwmRatePerUnit * totalUnits : 0
|
const rwmOneTimeTotal = rwm ? rwmRatePerUnit * totalUnits : 0
|
||||||
/* the UVI package shows the heat distribution it has to measure */
|
|
||||||
const heatAsset = buildings.some((b) => b.heat === "fussbodenheizung")
|
|
||||||
? renders.floorHeating
|
|
||||||
: renders.radiator
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container>
|
<Container>
|
||||||
@@ -333,22 +323,34 @@ export default function ConfiguratorPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<Separator className="my-6" />
|
<Separator className="my-6" />
|
||||||
<p className="text-sm font-bold text-navy">Installation</p>
|
<div
|
||||||
<Tabs
|
className={cn(
|
||||||
className="mt-3"
|
"flex items-start justify-between gap-4 rounded-xl border p-4 transition-colors",
|
||||||
value={install ? "mit" : "ohne"}
|
install
|
||||||
onValueChange={(v) => setInstall(v === "mit")}
|
? "border-primary bg-brand-50/60 shadow-pill"
|
||||||
|
: "border-border bg-card"
|
||||||
|
)}
|
||||||
>
|
>
|
||||||
<TabsList>
|
<div className="min-w-0">
|
||||||
<TabsTrigger value="mit">Mit Installation</TabsTrigger>
|
<Label
|
||||||
<TabsTrigger value="ohne">Ohne Installation</TabsTrigger>
|
htmlFor="installation"
|
||||||
</TabsList>
|
className="cursor-pointer text-sm font-bold text-navy"
|
||||||
</Tabs>
|
>
|
||||||
<p className="mt-3 text-sm text-muted-foreground">
|
Installation durch GebOS
|
||||||
{install
|
</Label>
|
||||||
? "GebOS organisiert die Montage – Geräte- und Monatspreise enthalten die Installation."
|
<p className="mt-1 text-sm text-muted-foreground">
|
||||||
: "Sie selbst, Ihr Hausmeister oder Ihr Fachbetrieb montiert die Geräte."}
|
{install
|
||||||
</p>
|
? "GebOS organisiert die Montage – Geräte- und Monatspreise enthalten die Installation."
|
||||||
|
: "Sie selbst, Ihr Hausmeister oder Ihr Fachbetrieb montiert die Geräte."}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
id="installation"
|
||||||
|
checked={install}
|
||||||
|
onCheckedChange={setInstall}
|
||||||
|
className="mt-0.5"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</StepCard>
|
</StepCard>
|
||||||
|
|
||||||
<StepCard step={2} title="Gebäude angeben">
|
<StepCard step={2} title="Gebäude angeben">
|
||||||
@@ -563,7 +565,7 @@ export default function ConfiguratorPage() {
|
|||||||
/>
|
/>
|
||||||
<div className="mt-8 grid gap-6 lg:grid-cols-2">
|
<div className="mt-8 grid gap-6 lg:grid-cols-2">
|
||||||
<PackageCard
|
<PackageCard
|
||||||
assets={[renders.gateway, heatAsset]}
|
render={renders.gateway}
|
||||||
title="UVI"
|
title="UVI"
|
||||||
included={uvi}
|
included={uvi}
|
||||||
items={[
|
items={[
|
||||||
@@ -575,7 +577,7 @@ export default function ConfiguratorPage() {
|
|||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<PackageCard
|
<PackageCard
|
||||||
assets={[renders.smokeAlarm]}
|
render={renders.smokeAlarm}
|
||||||
title="Rauchwarnmelder"
|
title="Rauchwarnmelder"
|
||||||
included={rwm}
|
included={rwm}
|
||||||
items={[
|
items={[
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ export default function SolutionsPage() {
|
|||||||
/>
|
/>
|
||||||
<SectionHeading
|
<SectionHeading
|
||||||
title="Unsere Lösungen"
|
title="Unsere Lösungen"
|
||||||
lead="Drei sauber getrennte Leistungen. Perfekt zusammenspielend."
|
lead="Hier finden sie eine Übersicht unserer Produkt Lösungen."
|
||||||
/>
|
/>
|
||||||
<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) => (
|
{solutions.map((s) => (
|
||||||
|
|||||||
@@ -133,6 +133,10 @@
|
|||||||
0 1px 2px rgb(19 41 60 / 0.05), 0 10px 22px -10px var(--glass-bloom);
|
0 1px 2px rgb(19 41 60 / 0.05), 0 10px 22px -10px var(--glass-bloom);
|
||||||
--shadow-glass-lg:
|
--shadow-glass-lg:
|
||||||
0 2px 4px rgb(19 41 60 / 0.06), 0 18px 34px -12px var(--glass-bloom);
|
0 2px 4px rgb(19 41 60 / 0.06), 0 18px 34px -12px var(--glass-bloom);
|
||||||
|
/* the same optics at control scale, for pills the size of a switch track */
|
||||||
|
--inset-shadow-glass-pill:
|
||||||
|
inset 0 1px 0 0 var(--glass-rim), inset 0 0 0 1px var(--glass-ring),
|
||||||
|
inset 0 -4px 6px -4px var(--glass-caustic);
|
||||||
/* the same optics at slab scale, for surfaces the size of the footer */
|
/* the same optics at slab scale, for surfaces the size of the footer */
|
||||||
--inset-shadow-glass-slab:
|
--inset-shadow-glass-slab:
|
||||||
inset 0 2px 0 0 var(--glass-rim), inset 0 0 0 1px var(--glass-ring),
|
inset 0 2px 0 0 var(--glass-rim), inset 0 0 0 1px var(--glass-ring),
|
||||||
|
|||||||
Reference in New Issue
Block a user