@@ -1,48 +0,0 @@
|
||||
name: Deploy Static Site
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
cache: npm
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Build static site
|
||||
run: npm run build
|
||||
|
||||
- name: Setup SSH key
|
||||
run: |
|
||||
mkdir -p ~/.ssh
|
||||
echo "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id_ed25519
|
||||
chmod 600 ~/.ssh/id_ed25519
|
||||
|
||||
- name: Deploy static files to server
|
||||
env:
|
||||
DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
|
||||
REMOTE_DIR: /home/deploy/projects/ge-bos/static
|
||||
run: |
|
||||
SSH="ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new"
|
||||
# Recreate the target dir so removed files don't linger (rsync --delete equivalent)
|
||||
$SSH deploy@"$DEPLOY_HOST" "rm -rf '$REMOTE_DIR' && mkdir -p '$REMOTE_DIR'"
|
||||
# Stream the build output over ssh and extract it on the server
|
||||
tar -czf - -C out . | $SSH deploy@"$DEPLOY_HOST" "tar -xzf - -C '$REMOTE_DIR'"
|
||||
|
||||
- name: Run deployment script
|
||||
run: |
|
||||
ssh -i ~/.ssh/id_ed25519 -o StrictHostKeyChecking=accept-new \
|
||||
deploy@${{ secrets.DEPLOY_HOST }} \
|
||||
"cd /home/deploy/projects/ge-bos && ./deploy.sh"
|
||||
|
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 165 KiB After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 168 KiB After Width: | Height: | Size: 41 KiB |
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 178 KiB After Width: | Height: | Size: 45 KiB |
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 182 KiB After Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 35 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 36 KiB |
@@ -2,6 +2,7 @@ import * as React from "react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { IconBadge } from "@/components/gebos/icon-tile"
|
||||
import { GlassNumber } from "@/components/gebos/site-assets"
|
||||
|
||||
export interface ProcessStep {
|
||||
icon?: React.ReactNode
|
||||
@@ -13,7 +14,7 @@ export interface ProcessStep {
|
||||
|
||||
/**
|
||||
* Numbered process row ("So funktioniert GebOS", steps 1–5):
|
||||
* numbered chips joined by a dotted teal line, device icon, title, copy.
|
||||
* glass numerals joined by a dotted teal line, device icon, title, copy.
|
||||
*/
|
||||
function ProcessSteps({
|
||||
steps,
|
||||
@@ -32,16 +33,14 @@ function ProcessSteps({
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="absolute inset-x-[10%] top-3.5 hidden border-t-2 border-dotted-brand lg:block"
|
||||
className="absolute inset-x-[10%] top-[17px] hidden border-t-2 border-dotted-brand lg:block"
|
||||
/>
|
||||
{steps.map((step, i) => (
|
||||
<li
|
||||
key={i}
|
||||
className="relative flex flex-col items-center gap-3 text-center"
|
||||
>
|
||||
<span className="z-10 flex size-7 items-center justify-center rounded-full bg-card text-xs font-bold text-primary shadow-pill ring-2 ring-brand-300">
|
||||
{i + 1}
|
||||
</span>
|
||||
<GlassNumber n={i + 1} className="z-10 size-9" />
|
||||
{step.media ?? (
|
||||
<IconBadge variant="outline" shape="squircle" size="xl">
|
||||
{step.icon}
|
||||
@@ -73,14 +72,12 @@ function NumberedList({
|
||||
<ol className={cn("relative flex flex-col gap-6", className)}>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="absolute top-2 bottom-2 left-3.5 border-l-2 border-dotted-brand"
|
||||
className="absolute top-2 bottom-2 left-[17px] border-l-2 border-dotted-brand"
|
||||
/>
|
||||
{steps.map((step, i) => (
|
||||
<li key={i} className="relative flex items-start gap-4">
|
||||
<span className="z-10 flex size-7 shrink-0 items-center justify-center rounded-full bg-primary text-xs font-bold text-primary-foreground shadow-pill">
|
||||
{i + 1}
|
||||
</span>
|
||||
<div className="pt-0.5">
|
||||
<GlassNumber n={i + 1} className="z-10 size-9" />
|
||||
<div className="flex min-h-9 flex-col justify-center">
|
||||
<h3 className="text-sm font-bold text-navy">{step.title}</h3>
|
||||
{step.description ? (
|
||||
<p className="mt-0.5 text-sm leading-relaxed text-muted-foreground">
|
||||
@@ -95,7 +92,7 @@ function NumberedList({
|
||||
}
|
||||
|
||||
/**
|
||||
* Step rail on a card (Selbstinstallation, steps 1–5): numbered rings threaded
|
||||
* Step rail on a card (Selbstinstallation, steps 1–5): glass numerals threaded
|
||||
* on a solid teal rail, hairline-divided columns beneath, each an asset render
|
||||
* over title and copy. Each column draws its own half of the rail, so the run
|
||||
* ends at the first and last ring whatever the step count. Rail and dividers
|
||||
@@ -120,16 +117,14 @@ function StepRail({
|
||||
<li key={i} className="relative flex flex-col items-center">
|
||||
<span
|
||||
aria-hidden="true"
|
||||
/* rail sits on the ring's centre line (size-9 ring → 18px) */
|
||||
/* rail sits on the badge's centre line (size-9 badge → 18px) */
|
||||
className={cn(
|
||||
"absolute top-[17px] hidden h-0.5 bg-brand-300 lg:block",
|
||||
i === 0 ? "left-1/2" : "left-0",
|
||||
i === steps.length - 1 ? "right-1/2" : "right-0"
|
||||
)}
|
||||
/>
|
||||
<span className="z-10 flex size-9 items-center justify-center rounded-full bg-card text-sm font-bold text-navy ring-2 ring-brand-300">
|
||||
{i + 1}
|
||||
</span>
|
||||
<GlassNumber n={i + 1} className="z-10 size-9" />
|
||||
<div
|
||||
className={cn(
|
||||
"mt-5 flex w-full flex-1 flex-col items-center px-2 text-center",
|
||||
|
||||
@@ -447,4 +447,56 @@ function AssetRender({
|
||||
)
|
||||
}
|
||||
|
||||
export { AssetRender, icons, renders, vectors }
|
||||
/** glossy glass numerals, index 0 = "1" */
|
||||
const glassNumbers = [
|
||||
renders.glassBadge1,
|
||||
renders.glassBadge2,
|
||||
renders.glassBadge3,
|
||||
renders.glassBadge4,
|
||||
renders.glassBadge5,
|
||||
renders.glassBadge6,
|
||||
]
|
||||
|
||||
/**
|
||||
* Glass numeral for a step in an ordered list. Decorative on purpose: the
|
||||
* enclosing <ol> already carries the ordinal, so the badge is not announced a
|
||||
* second time. Only 1–6 exist as renders; a longer list falls back to the
|
||||
* numeral on a teal disc rather than losing its number.
|
||||
*/
|
||||
function GlassNumber({
|
||||
n,
|
||||
className,
|
||||
loading = "lazy",
|
||||
}: {
|
||||
n: number
|
||||
/** sizes the badge – pass a `size-*` utility */
|
||||
className?: string
|
||||
loading?: "lazy" | "eager"
|
||||
}) {
|
||||
const render = glassNumbers[n - 1]
|
||||
|
||||
if (!render) {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"flex shrink-0 items-center justify-center rounded-full bg-primary text-xs font-bold text-primary-foreground shadow-pill",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{n}
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<AssetRender
|
||||
render={render}
|
||||
alt=""
|
||||
className={cn("flex shrink-0 items-center justify-center", className)}
|
||||
imgClassName="size-full"
|
||||
loading={loading}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { AssetRender, GlassNumber, icons, renders, vectors }
|
||||
|
||||
@@ -17,6 +17,7 @@ import { Container, Section, SectionHeading } from "@/components/gebos/section"
|
||||
import { PageBreadcrumb } from "@/components/gebos/page-breadcrumb"
|
||||
import {
|
||||
AssetRender,
|
||||
GlassNumber,
|
||||
renders,
|
||||
type RenderAsset,
|
||||
} from "@/components/gebos/site-assets"
|
||||
@@ -154,15 +155,9 @@ function readPreset(params: URLSearchParams): Preset {
|
||||
}
|
||||
}
|
||||
|
||||
/** glossy numerals for the three configurator steps */
|
||||
/** the three configurator steps */
|
||||
type Step = 1 | 2 | 3
|
||||
|
||||
const STEP_BADGES: Record<Step, RenderAsset> = {
|
||||
1: renders.glassBadge1,
|
||||
2: renders.glassBadge2,
|
||||
3: renders.glassBadge3,
|
||||
}
|
||||
|
||||
function StepCard({
|
||||
step,
|
||||
title,
|
||||
@@ -175,12 +170,7 @@ function StepCard({
|
||||
return (
|
||||
<Card className="gap-0 p-6 sm:p-7">
|
||||
<div className="flex items-center gap-3">
|
||||
<AssetRender
|
||||
render={STEP_BADGES[step]}
|
||||
className="flex size-11 shrink-0 items-center justify-center"
|
||||
imgClassName="w-11"
|
||||
loading="eager"
|
||||
/>
|
||||
<GlassNumber n={step} className="size-11" loading="eager" />
|
||||
<h3 className="text-lg font-extrabold tracking-tight text-navy">
|
||||
<span className="sr-only">Schritt {step}: </span>
|
||||
{title}
|
||||
|
||||
@@ -494,13 +494,7 @@ export default function HomePage() {
|
||||
<div>
|
||||
<Card className="p-6 gap-0 sm:p-8">
|
||||
<CardContent className="p-0">
|
||||
<div className="flex items-center gap-4">
|
||||
<img
|
||||
src={vectors.euroOrb}
|
||||
alt=""
|
||||
className="size-14"
|
||||
loading="lazy"
|
||||
/>
|
||||
<div>
|
||||
<h3 className="text-base font-bold text-navy">
|
||||
In fünf Schritten zum genauen Preis
|
||||
</h3>
|
||||
|
||||