feat: landing page overhaul with pilot framing and compliance-safe copy
This commit is contained in:
parent
2d7773dbca
commit
a8e312a4f8
9 changed files with 537 additions and 448 deletions
12
CHANGELOG.md
12
CHANGELOG.md
|
|
@ -5,6 +5,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
### Added
|
||||||
|
- Landing page: status-quo problem block, 5-step process with quote,
|
||||||
|
portal overview with admin areas, AI platform parts, exception tiers,
|
||||||
|
quantified benefits, integrations, use-case examples, pilot-program
|
||||||
|
section, expanded FAQ (8 items incl. KI data usage, price model,
|
||||||
|
responsibility).
|
||||||
|
### Changed
|
||||||
|
- Landing page copy overhauled after 3 reviewer rounds (church-admin
|
||||||
|
compliance wording: "entwickelt nach DSG-EKD/DSGVO" + AVV/TOMs,
|
||||||
|
human release responsibility, honest pilot framing instead of
|
||||||
|
invented testimonials; CTA now "Pilotgespräch vereinbaren").
|
||||||
|
- New photo `pilot-meeting.webp` (consultation scene, self-hosted).
|
||||||
|
|
||||||
## [0.1.0] - 2026-09-13
|
## [0.1.0] - 2026-09-13
|
||||||
### Added
|
### Added
|
||||||
|
|
|
||||||
263
PLAN_NEXT.md
263
PLAN_NEXT.md
|
|
@ -1,263 +0,0 @@
|
||||||
# Continuation plan — kirche-einfach next steps (written after Phase 0)
|
|
||||||
|
|
||||||
> For a future agent restoring this session (e.g. after a container rebuild).
|
|
||||||
> Read this file + `AGENTS.md` + `README.md` first. All code/comments in
|
|
||||||
> English; only user-visible website strings are German.
|
|
||||||
|
|
||||||
## 0. Where we are
|
|
||||||
|
|
||||||
**Phase 0 (this commit) is done** — Docker/devcontainer reproducibility:
|
|
||||||
|
|
||||||
* `langflow` image pinned via `${LANGFLOW_VERSION:-1.12.0}` (was `:latest`
|
|
||||||
with nightly Watchtower drift). `Dockerfile.lfx` pins `lfx~=<same>`
|
|
||||||
(`ARG LFX_VERSION=1.12.0`). Backend test dep pinned `lfx~=1.12.0`.
|
|
||||||
Reason: official LFX contract — `major.minor` of Langflow and LFX must
|
|
||||||
match, patches are independent.
|
|
||||||
* `langflow` mounts `backend/components` (`LANGFLOW_COMPONENTS_PATH`) and
|
|
||||||
`backend/flows` (`LANGFLOW_LOAD_FLOWS_PATH`), both `:ro`. Files stay
|
|
||||||
source of truth; the API watcher (`app/flows/sync.py`) remains the ONLY
|
|
||||||
sync path — never add a second watcher.
|
|
||||||
* Backend venv moved to `/opt/venv` (image-local, `UV_PROJECT_ENVIRONMENT`),
|
|
||||||
managed Python to `/opt/python` (`UV_PYTHON_INSTALL_DIR`), both `a+rX`.
|
|
||||||
Removed `kirche-venv` + `kirche-node-modules` named volumes (they shadowed
|
|
||||||
image content with empty volumes on first start = split-brain).
|
|
||||||
`backend/entrypoint.sh` uses `/opt/venv/bin/*`, ensures `data/` exists and
|
|
||||||
is `vscode`-owned. `frontend/entrypoint.sh` runs `npm ci` when
|
|
||||||
`node_modules/.bin/vite` is missing (covers fresh prod checkouts).
|
|
||||||
* `devcontainer.json`: opencode session persistence via named volumes
|
|
||||||
`kirche-opencode-share → ~/.local/share/opencode` (sessions DB) and
|
|
||||||
`kirche-opencode-config → ~/.config/opencode`; interpreter path is now
|
|
||||||
`/opt/venv/bin/python`. `setup.sh` chowns first, then runs `uv sync` as
|
|
||||||
`vscode` (was: root `uv sync` into `/root`-backed venv → `Permission
|
|
||||||
denied` for vscode; `backend/data` root-owned → DB unwritable).
|
|
||||||
* Stale local `backend/.venv/` (bind-mount leftover) can be deleted manually
|
|
||||||
after rebuild: `rm -rf backend/.venv`. It is gitignored; nothing references
|
|
||||||
it anymore.
|
|
||||||
|
|
||||||
**Verified in this session:** `uv run pytest` green (after local venv repair,
|
|
||||||
see §5). NOT verifiable here (no node/docker in this sandbox): `npm run
|
|
||||||
build`, `docker compose config`, container rebuild. CI covers all three
|
|
||||||
(`.forgejo/workflows/`). After rebuilding the devcontainer, re-run: `uv run
|
|
||||||
pytest` (attached api, from `backend/`), `docker compose exec frontend npm
|
|
||||||
run build`, `docker compose config --quiet`.
|
|
||||||
|
|
||||||
**Still open:** nothing from the original list — Phases 1–4 are implemented
|
|
||||||
(see §0.2). Remaining eyeball checks: `kirche` category in the Langflow
|
|
||||||
palette, RJSF form UX in a browser, magic-link mail with real SMTP.
|
|
||||||
|
|
||||||
## 0.1 Follow-up fixes (commits after Phase 0)
|
|
||||||
|
|
||||||
`fix: devcontainer build and setup on fresh clones` + `feat: magic-link
|
|
||||||
auth` + the LFX/stack repair commit:
|
|
||||||
|
|
||||||
* `docker-outside-of-docker` needs `"moby": false` (`python:3.12-slim` is
|
|
||||||
Debian trixie, no moby-cli in its repos). Removed the manual
|
|
||||||
`/var/run/docker.sock` bind (breaks Windows, feature handles it).
|
|
||||||
* `setup.sh` used `su vscode -c` (drops env incl. `UV_PROJECT_ENVIRONMENT`
|
|
||||||
→ stray `backend/.venv`, blind sync). Now `sudo -u vscode -E` helper.
|
|
||||||
Missing `.env` is provisioned from `.env.example`, not fatal.
|
|
||||||
* Compose `env_file` is `required: false` everywhere (fresh clones, CI).
|
|
||||||
Images install `curl git ca-certificates`; `.dockerignore` files keep
|
|
||||||
host `node_modules/.venv/data` out of build contexts.
|
|
||||||
* `settings.database_url` default is now a real `sqlite:///` URL (bare
|
|
||||||
path crashed `create_engine`/alembic; api could never boot uncconfigured).
|
|
||||||
* `Dockerfile.lfx` CMD dropped the positional `/flows`: `lfx>=1.12.1`
|
|
||||||
exits on an empty flow dir (restart-loop → whole stack `unhealthy`).
|
|
||||||
`lfx` service blanks `LANGFLOW_API_KEY` (server prefers it over
|
|
||||||
`LFX_API_KEY`, so backend deploys 401'd and `/flows` stayed empty).
|
|
||||||
* Both entrypoints run via explicit `sh` (git tracks them 100644; the
|
|
||||||
bind-mount shadows image perms, breaking Windows checkouts).
|
|
||||||
* Verified live: full `compose up` green (api/frontend/langflow/lfx),
|
|
||||||
startup sync deploys `backend/flows` to LFX, `uv run pytest` 60 green.
|
|
||||||
Before Reopen in Container: `docker compose down` any manual stack
|
|
||||||
(same project name → port clash).
|
|
||||||
## 0.2 Phases 1–4 (done, with deviations)
|
|
||||||
|
|
||||||
* Phase 1 auth as planned (`app/auth/*`, migration `a3f1c9e2b4d7`).
|
|
||||||
Additionally fixed: `settings.database_url` default is a real
|
|
||||||
`sqlite:///` URL (bare path crashed `create_engine`/alembic).
|
|
||||||
* Phase 2: `CustomInputForm` has the 10 planned inputs **plus one hidden
|
|
||||||
`form_values: MessageTextInput (show=False)` plumbing input**. Without
|
|
||||||
it, submitted web-form values never reached the run (LFX routes run
|
|
||||||
input to entry nodes; scalar fields are not connectable). Fixture flow
|
|
||||||
`intake_form.json`: `ChatInput → form.form_values`,
|
|
||||||
`form.form_data → ChatOutput`; `collect()` merges the envelope
|
|
||||||
(`{"payload": {...}}` unwrapped like the other components) over field
|
|
||||||
defaults. Verified live: run with values returns them in the output.
|
|
||||||
* Phase 2 generator caveat: lfx 1.12.x `to_frontend_node()` **cannot
|
|
||||||
re-instantiate `MultiselectInput`** (emits `list`, constructor forbids
|
|
||||||
extras — their own builtin `SearXNGToolComponent` fails identically).
|
|
||||||
`kirche_node()` therefore replicates the pre-validation dict build and
|
|
||||||
appends the `code` field by hand. If a future lfx fixes this, simplify
|
|
||||||
back to plain `to_frontend_node()`.
|
|
||||||
* Phase 3 as planned (`input_schema.py`, `FlowVersion.input_schema`,
|
|
||||||
migration `b7e2d4a1c9f3`, `GET input-schema`). Deviations: schema is
|
|
||||||
cached in `store.create_version` (single choke point: sync, import, API
|
|
||||||
all covered); parser skips `show: false` fields; enum-mismatched
|
|
||||||
defaults omitted; endpoint maps `InvalidError` → 404 too
|
|
||||||
(`get_latest_deployed_version` raises it, not `NotFoundError`).
|
|
||||||
* Phase 4 as planned (`react-router-dom@7`, `@rjsf/core` +
|
|
||||||
`@rjsf/validator-ajv8` (v6), English routes, German labels,
|
|
||||||
`credentials: include`, old tab shell dropped). `FlowsTab.tsx` deleted;
|
|
||||||
`ApprovalsTab`/`AdminTab` reused inside Tasks/Admin pages. Verified:
|
|
||||||
`npm run build` green, routes + `/api` proxy return 200, served schema
|
|
||||||
validates under JSON-Schema Draft 7.
|
|
||||||
|
|
||||||
## 0.3 Frontend design pass (Mantine)
|
|
||||||
|
|
||||||
Plain-HTML UI replaced with a coherent Mantine v9 design system:
|
|
||||||
`@mantine/core` + `@mantine/hooks` + `@rjsf/mantine` (v6, matches core)
|
|
||||||
+ `@tabler/icons-react`. `src/theme.ts` (kirchenblau primary, system
|
|
||||||
fonts only, serif display headings), `AppShell` header/nav, per-page
|
|
||||||
`Card`/`Table`/`Alert`/`Badge` via shared `StatusBadge`, dark mode via
|
|
||||||
localStorage manager + toggle + inline `ColorSchemeScript` equivalent.
|
|
||||||
RJSF swap was import-only; SSR smoke test proved all 10 intake labels +
|
|
||||||
textarea/password/checkbox/slider widgets render. `npm run build` green.
|
|
||||||
|
|
||||||
## 0.4 Landing page (conversion structure + screenshot automation)
|
|
||||||
|
|
||||||
StartPage rebuilt along the 8-block anatomy (hero with outcome headline,
|
|
||||||
fact bar, 3 benefits, real product screenshot, 3 steps, trust list, FAQ,
|
|
||||||
final CTA, footer mini) — one CTA everywhere (`/access`), honest copy
|
|
||||||
(no fake testimonials/logos/numbers; pricing section omitted, model open).
|
|
||||||
Centralized hosting in Germany per operator decision (not self-hosted).
|
|
||||||
`frontend/scripts/capture.mjs` + `npm run screenshots` (Playwright/Chromium
|
|
||||||
in the frontend container) regenerates `public/screenshots/*.png`; the
|
|
||||||
workflows shot auto-selects `intake_form` to show the generated RJSF form.
|
|
||||||
Vite dev `server.watch` polling enabled (Windows bind-mount events often
|
|
||||||
never reach the Linux watcher → stale modules otherwise).
|
|
||||||
|
|
||||||
## 0.5 Email-first login (`POST /api/auth/magic/request`)
|
|
||||||
|
|
||||||
`/login` without `?token=` shows an email form (+ "Hier anfragen" link).
|
|
||||||
Allowed = approved user row or `ADMIN_EMAILS` (admin auto-created on first
|
|
||||||
request); anything else → 403 and the frontend forwards to
|
|
||||||
`/access?email=…` (prefilled). Rate-limited (5/hour), token by mail only,
|
|
||||||
delivery failures never 500 (see §0.4 hardening).
|
|
||||||
|
|
||||||
## 0.6 Polish round (bell-tower favicon, lfx-clean flows, HTML mail)
|
|
||||||
|
|
||||||
* Favicon simplified to a stylized bell tower (spire + shaft + bell-arch
|
|
||||||
knockout); PNGs regenerated via `npm run icons`.
|
|
||||||
* `lfx validate --level 4` now green for ALL canonical flows + regression
|
|
||||||
test (`test_lfx_validate.py`). Root cause: required form fields have no
|
|
||||||
edge (values arrive via the form_values envelope). Fix: generator writes
|
|
||||||
a single-space sentinel value for required template fields; the form
|
|
||||||
parser treats whitespace-only values as "no default". L3
|
|
||||||
Message-vs-str warnings are known lfx-validator noise (MessageTextInput
|
|
||||||
accepts Message at runtime).
|
|
||||||
* New-input authoring docs: `backend/components/kirche/README.md`.
|
|
||||||
* Magic-link + access-notification mails are branded multipart
|
|
||||||
(text + HTML): kirchenblau header, serif headline, real button link,
|
|
||||||
German copy, email-safe tables/inline styles, no external assets.
|
|
||||||
|
|
||||||
## 1. Locked decisions (do not re-ask)
|
|
||||||
|
|
||||||
* Auth: SMTP magic-link, 15-min single-use tokens (`secrets.token_urlsafe`,
|
|
||||||
store `sha256`, `used_at` set before session creation), session = opaque id
|
|
||||||
in **DB table**, cookie `HttpOnly; Secure; SameSite=Lax; Path=/`.
|
|
||||||
SMTP creds already in `.env` (`SMTP_HOST/PORT/USERNAME/PASSWORD`,
|
|
||||||
`MAIL_FROM`, `CONTACT_TO`); dev fallback logs the link when SMTP unset.
|
|
||||||
* `FileInput` is a **stub** for now (string reference only); real upload later.
|
|
||||||
* Frontend: `react-router-dom` + RJSF (`@rjsf/core` + `@rjsf/validator-ajv8`).
|
|
||||||
RJSF renders backend-supplied `{json_schema, ui_schema}`; server
|
|
||||||
re-validates (client validation is UX only).
|
|
||||||
* **Routes are pure English** (user correction — no German slugs anywhere,
|
|
||||||
including URLs):
|
|
||||||
`/`, `/access` (was once sketched as `/zugang` — do NOT use),
|
|
||||||
`/login?token=`, `/app/workflows`, `/app/tasks`, `/app/admin`.
|
|
||||||
Visible labels/nav are German (`Start`, `Zugang`, `Workflows`, `Aufgaben`,
|
|
||||||
`Verwaltung`); code, props, comments, routes English.
|
|
||||||
* Langflow preload + mount (done in Phase 0). Custom-component lib in
|
|
||||||
`backend/components/kirche/` **must stay GUI-editable**: category folder +
|
|
||||||
`__init__.py` + `Component` subclasses using `lfx.io` inputs only
|
|
||||||
(stdlib + `lfx`, so the LFX runtime can execute them).
|
|
||||||
* Watchtower: `langflow` label removed (pinned images must not auto-update).
|
|
||||||
Version bumps = deliberate `LANGFLOW_VERSION` + `LFX_VERSION` change, then
|
|
||||||
`lfx upgrade --upgrade-flow=safe` check over `backend/flows/*.json`.
|
|
||||||
|
|
||||||
## 2. Phase 1 — Backend auth (magic-link + RBAC)
|
|
||||||
|
|
||||||
Files: `backend/app/models/user.py` (`User`, `AccessRequest`,
|
|
||||||
`MagicLinkToken`, `Session`), `backend/app/auth/{schemas,service,mail,api,
|
|
||||||
deps}.py`, wire router in `app/main.py`, settings in `app/settings.py`
|
|
||||||
(`frontend_url, admin_emails, contact_to, mail_from, smtp_*`), ONE new
|
|
||||||
Alembic revision (never edit applied migrations).
|
|
||||||
|
|
||||||
Endpoints:
|
|
||||||
|
|
||||||
* `POST /api/access/requests` public, rate-limited: `{name,email,reason}`
|
|
||||||
→ row `pending` + notify `CONTACT_TO`.
|
|
||||||
* `POST /api/access/requests/{id}/approve|reject` admin: approve →
|
|
||||||
upsert `User` + emit magic-link + SMTP send.
|
|
||||||
* `POST /api/auth/magic/consume {token}` → session cookie; `GET
|
|
||||||
/api/auth/me`; `POST /api/auth/logout` (delete session row).
|
|
||||||
* `POST /api/auth/admin-bootstrap`: only when user table empty AND email in
|
|
||||||
`ADMIN_EMAILS`.
|
|
||||||
* Tenant from session (`user.tenant_id`); keep old `tenant_id` body param as
|
|
||||||
deprecated fallback. `require_admin` dependency; keep API handlers thin
|
|
||||||
(DB only via `app/flows/store.py` + new `app/auth/*` service funcs).
|
|
||||||
|
|
||||||
Tests (all in `backend/tests/`): request→approve→consume→me→logout happy
|
|
||||||
path; token expiry boundary (15 min); token reuse → 400; admin-only → 403;
|
|
||||||
bootstrap-once; cookie flags (`HttpOnly`, `SameSite`, `Path`); assert no
|
|
||||||
token/secret in logs.
|
|
||||||
|
|
||||||
## 3. Phase 2 — `CustomInput*` library (GUI-editable)
|
|
||||||
|
|
||||||
* `backend/components/kirche/__init__.py` +
|
|
||||||
`backend/components/kirche/form_inputs.py`:
|
|
||||||
`class CustomInputForm(Component)` with inputs `StrInput`,
|
|
||||||
`MultilineInput`, `SecretStrInput`, `IntInput`, `FloatInput`, `BoolInput`,
|
|
||||||
`DropdownInput`, `MultiselectInput`, `SliderInput`, `FileInput` (stub) +
|
|
||||||
`Output`. Category dir `kirche/` = appears in Langflow GUI.
|
|
||||||
* Regenerate/add fixture flow using it via
|
|
||||||
`backend/scripts/make_flow_fixtures.py`; keep `simple_rule.json` as the
|
|
||||||
textarea-fallback case. Commit flow JSON (source of truth).
|
|
||||||
|
|
||||||
## 4. Phase 3 — Dynamic-form parsing (no second watcher)
|
|
||||||
|
|
||||||
* New `backend/app/flows/input_schema.py`:
|
|
||||||
`extract_form_schema(langflow_json) -> {json_schema, ui_schema}`.
|
|
||||||
Find nodes with `type`/`display_name` starting `CustomInput`; iterate
|
|
||||||
`template` minus `code`; map `_input_type` → JSON Schema type/widget
|
|
||||||
(`StrInput`/`MessageTextInput`→string, `MultilineInput`→string+textarea,
|
|
||||||
`SecretStrInput`→string+password, `IntInput`→integer,
|
|
||||||
`FloatInput`→number, `BoolInput`→boolean, `DropdownInput`→string+enum,
|
|
||||||
`MultiselectInput`→array+enum, `SliderInput`→number+range,
|
|
||||||
`FileInput`→string stub); `display_name/info/value/options/required/
|
|
||||||
advanced` → `title/description/default/enum/required/uiSchema`.
|
|
||||||
No such node → `{fallback: "textarea"}`.
|
|
||||||
* Called from existing `sync_from_directory` (cache per version in new
|
|
||||||
nullable `input_schema` JSON column via Alembic) + new `GET
|
|
||||||
/api/flows/{id}/input-schema → {flow_id, version_no, json_schema,
|
|
||||||
ui_schema}` (latest **deployed** version; 404 when none).
|
|
||||||
* Tests: per-type mapping table, fallback, sync persistence, endpoint 404.
|
|
||||||
|
|
||||||
## 5. Phase 4 — Frontend (router + RJSF, German UI, English routes)
|
|
||||||
|
|
||||||
Deps: `react-router-dom`, `@rjsf/core`, `@rjsf/validator-ajv8`.
|
|
||||||
Routes (English, see §1): `/`, `/access`, `/login`, `/app/workflows`
|
|
||||||
(flow select → `GET input-schema` → RJSF `<Form>` → `POST run`),
|
|
||||||
`/app/tasks` (own runs + approval states), `/app/admin` (admin-only:
|
|
||||||
access-request queue, ALL tasks incl. open human-intervention with
|
|
||||||
approve/reject + note, import/sync/deploy). `fetch` with
|
|
||||||
`credentials: "include"`. Refactor `FlowsTab/ApprovalsTab/AdminTab` into
|
|
||||||
route components; drop old tab shell. Verify with `npm run build`.
|
|
||||||
|
|
||||||
## 6. Operational notes for the next agent
|
|
||||||
|
|
||||||
* Commands: backend from `backend/`: `uv run pytest`,
|
|
||||||
`uv run alembic upgrade head`, `uv run python scripts/sync_flows.py
|
|
||||||
--sync`; frontend via `docker compose exec frontend npm run build`.
|
|
||||||
* Local venv repair (this sandbox had a root-backed broken `.venv`):
|
|
||||||
`rm -rf backend/.venv && uv sync` (network needed for `uv python
|
|
||||||
install`). In rebuilt containers this is unnecessary (`/opt/venv`).
|
|
||||||
* `.env` (gitignored) still lacks `FRONTEND_URL`/`ADMIN_EMAILS` from the
|
|
||||||
`.env.example` diff — copy them over locally when starting Phase 1. Never
|
|
||||||
commit `.env`.
|
|
||||||
* Flow JSON that fails LFX after a version bump: run
|
|
||||||
`lfx upgrade --upgrade-flow=safe` on the file, review diff, commit.
|
|
||||||
* Definition of done per `AGENTS.md`: new behavior has tests, `uv run
|
|
||||||
pytest` green, `npm run build` green when frontend changed, schema changes
|
|
||||||
as new Alembic revisions.
|
|
||||||
|
|
@ -17,3 +17,4 @@ Dutch-language documents.
|
||||||
| church-pews.webp | Bright empty pews, checkerboard| https://www.pexels.com/photo/church-pews-7566207/ |
|
| church-pews.webp | Bright empty pews, checkerboard| https://www.pexels.com/photo/church-pews-7566207/ |
|
||||||
| paperwork-desk.webp | Desk with stamps and forms | https://www.pexels.com/photo/desk-with-documents-and-stamps-18687845/ |
|
| paperwork-desk.webp | Desk with stamps and forms | https://www.pexels.com/photo/desk-with-documents-and-stamps-18687845/ |
|
||||||
| team-laptop.webp | Team collaborating at laptop | https://www.pexels.com/photo/a-team-looking-at-a-laptop-7793702/ |
|
| team-laptop.webp | Team collaborating at laptop | https://www.pexels.com/photo/a-team-looking-at-a-laptop-7793702/ |
|
||||||
|
| pilot-meeting.webp | Consultation meeting, notes | https://www.pexels.com/photo/two-people-shaking-hands-5668858/ |
|
||||||
|
|
|
||||||
BIN
frontend/public/photos/pilot-meeting.webp
Normal file
BIN
frontend/public/photos/pilot-meeting.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
BIN
frontend/public/screenshots/hero-dark.png
Normal file
BIN
frontend/public/screenshots/hero-dark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 894 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 898 KiB After Width: | Height: | Size: 957 KiB |
BIN
frontend/public/screenshots/workflows-dark.png
Normal file
BIN
frontend/public/screenshots/workflows-dark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 44 KiB |
|
|
@ -50,6 +50,10 @@ PHOTOS = {
|
||||||
pexels(7793702),
|
pexels(7793702),
|
||||||
"https://www.pexels.com/photo/a-team-looking-at-a-laptop-7793702/",
|
"https://www.pexels.com/photo/a-team-looking-at-a-laptop-7793702/",
|
||||||
),
|
),
|
||||||
|
"pilot-meeting.webp": (
|
||||||
|
pexels(5668858),
|
||||||
|
"https://www.pexels.com/photo/two-people-shaking-hands-5668858/",
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import {
|
||||||
Group,
|
Group,
|
||||||
Image,
|
Image,
|
||||||
List,
|
List,
|
||||||
|
SimpleGrid,
|
||||||
Stack,
|
Stack,
|
||||||
Text,
|
Text,
|
||||||
ThemeIcon,
|
ThemeIcon,
|
||||||
|
|
@ -17,69 +18,210 @@ import {
|
||||||
Title,
|
Title,
|
||||||
} from "@mantine/core";
|
} from "@mantine/core";
|
||||||
import {
|
import {
|
||||||
|
IconArrowRight,
|
||||||
|
IconBook2,
|
||||||
IconCheck,
|
IconCheck,
|
||||||
IconClipboardCheck,
|
IconClipboardCheck,
|
||||||
|
IconCoins,
|
||||||
|
IconEye,
|
||||||
IconFileText,
|
IconFileText,
|
||||||
IconInbox,
|
IconInbox,
|
||||||
IconKey,
|
IconLink,
|
||||||
IconLock,
|
IconLock,
|
||||||
|
IconPigMoney,
|
||||||
|
IconPlug,
|
||||||
|
IconScale,
|
||||||
IconServer,
|
IconServer,
|
||||||
IconSparkles,
|
|
||||||
IconUserCheck,
|
IconUserCheck,
|
||||||
|
IconUsers,
|
||||||
|
IconWand,
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import { useAuth } from "../auth";
|
import { useAuth } from "../auth";
|
||||||
|
|
||||||
const CTA_LABEL = "Zugang anfragen";
|
const CTA_LABEL = "Pilotgespräch vereinbaren";
|
||||||
|
const CTA_TO = "/access";
|
||||||
|
|
||||||
const facts = [
|
const facts = [
|
||||||
{ icon: <IconServer size={20} />, text: "Gehostet in Deutschland" },
|
{ icon: <IconServer size={18} />, text: "Gehostet in Deutschland" },
|
||||||
{ icon: <IconKey size={20} />, text: "Login ohne Passwort" },
|
{
|
||||||
{ icon: <IconClipboardCheck size={20} />, text: "Jeder Schritt protokolliert" },
|
icon: <IconLink size={18} />,
|
||||||
|
text: "Anmeldung per einmaligem Link — ohne Passwort",
|
||||||
|
},
|
||||||
|
{ icon: <IconClipboardCheck size={18} />, text: "Jeder Schritt protokolliert" },
|
||||||
];
|
];
|
||||||
|
|
||||||
const benefits = [
|
const contrasts = [
|
||||||
{
|
{
|
||||||
icon: <IconFileText size={28} />,
|
before: "Papiervorgänge und Durchschläge",
|
||||||
title: "In eigenen Worten",
|
after: "Ein Vorgang, ein Ort, ein Stand",
|
||||||
text: "Mitarbeitende beschreiben Anliegen wie gewohnt — das System führt in verständlichen Schritten durch den Vorgang. Schluss mit Stempeln und Durchschlägen.",
|
|
||||||
photo: "/photos/paperwork-desk.webp",
|
|
||||||
alt: "Schreibtisch mit Stempeln und Formularen — so sieht es heute oft aus",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <IconSparkles size={28} />,
|
before: "E-Mail-Ketten zur Freigabe",
|
||||||
title: "Regeln automatisch geprüft",
|
after: "Freigaben mit allen Fakten auf einen Blick",
|
||||||
text: "Schwellen, Zuständigkeiten und Abläufe prüft die Software selbst. Routine läuft durch, ohne dass jemand klicken muss.",
|
|
||||||
photo: "/photos/team-laptop.webp",
|
|
||||||
alt: "Team arbeitet gemeinsam am Laptop",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: <IconUserCheck size={28} />,
|
before: "Wissen in Köpfen und Ordnern",
|
||||||
title: "Mensch nur wo nötig",
|
after: "Antworten mit Quellen, für alle abrufbar",
|
||||||
text: "Echte Entscheidungen landen mit allen Fakten bei der zuständigen Person — Freigabe oder Ablehnung inklusive Notiz. Für alles andere bleibt Zeit.",
|
|
||||||
photo: "/photos/church-pews.webp",
|
|
||||||
alt: "Ruhiger Kirchenraum mit leeren Bänken",
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const steps = [
|
const steps = [
|
||||||
{
|
{
|
||||||
title: "Zugang anfragen & anmelden",
|
title: "Anliegen stellen",
|
||||||
text: "Anfrage stellen, per E-Mail-Link einloggen — ganz ohne Passwort.",
|
text: "Sie beschreiben Ihr Anliegen in eigenen Worten.",
|
||||||
|
quote:
|
||||||
|
"Wir wollen das Dach unseres Gemeindehauses für 120.000 € sanieren.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Formular ausfüllen & starten",
|
title: "Verstehen & Beratung — KI-gestützt",
|
||||||
text: "Vorgang wählen, Angaben machen, starten. Der Ablauf folgt automatisch.",
|
text: "Die Wissens-KI erklärt, was zu beachten ist und welche Schritte erforderlich sind — mit Quellenangabe aus Kirchenrecht, Rundschreiben und Dienstanweisungen. Ihre Fachverantwortung bleibt unberührt: Die Beratung unterstützt, sie ersetzt keine rechtliche Prüfung im Einzelfall.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Ergebnis & Protokoll",
|
title: "Prozess starten",
|
||||||
text: "Antwort erhalten, Freigaben erteilen — jeder Schritt bleibt nachvollziehbar.",
|
text: "Der Prozessassistent führt Sie Schritt für Schritt, stellt Fragen und sammelt alle notwendigen Informationen.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Prüfen & automatisieren",
|
||||||
|
text: "Dokumente werden ausgelesen, Regeln nach dokumentierten Kriterien geprüft, Zuständigkeiten ermittelt — der Vorgang wird bearbeitet.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Entscheiden & abschließen",
|
||||||
|
text: "Regelkonforme Vorgänge werden automatisch zur Freigabe vorgemerkt. Bei Auffälligkeiten prüft ein Sachbearbeiter. Jede Entscheidung ist nachvollziehbar protokolliert.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const portalAreas = [
|
||||||
|
"Bauen & Immobilien",
|
||||||
|
"Personal",
|
||||||
|
"Haushalt & Finanzen",
|
||||||
|
"Beschaffung & Vergabe",
|
||||||
|
"Zuschüsse & Fördermittel",
|
||||||
|
"Gremien & Beschlüsse",
|
||||||
|
"Recht & Genehmigungen",
|
||||||
|
"Wissen & Vorschriften",
|
||||||
|
];
|
||||||
|
|
||||||
|
const platformParts = [
|
||||||
|
{
|
||||||
|
icon: <IconBook2 size={28} />,
|
||||||
|
title: "Wissens-KI",
|
||||||
|
text: "Antworten mit Quellen aus Kirchenrecht, Rundschreiben, Dienstanweisungen und FAQ.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <IconWand size={28} />,
|
||||||
|
title: "Prozessassistent",
|
||||||
|
text: "Versteht Ihr Anliegen und führt Sie Schritt für Schritt durch den Prozess.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <IconFileText size={28} />,
|
||||||
|
title: "Dokumenten-KI",
|
||||||
|
text: "Liest, versteht und extrahiert Daten aus PDFs, E-Mails und Belegen.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <IconScale size={28} />,
|
||||||
|
title: "Regelengine",
|
||||||
|
text: "Prüft automatisch Regeln, Schwellenwerte, Zuständigkeiten und Vollständigkeit — nach dokumentierten Kriterien.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <IconClipboardCheck size={28} />,
|
||||||
|
title: "Workflow & Entscheidung",
|
||||||
|
text: "Standardfall: regelbasiert bis zur Freigabevormerkung. Sonderfall: gezielte menschliche Prüfung.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const tiers = [
|
||||||
|
{
|
||||||
|
color: "green",
|
||||||
|
title: "Standardfall",
|
||||||
|
text: "Alle Regeln erfüllt — die Prüfung läuft automatisch nach dokumentierten Regeln bis zur Freigabevormerkung.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: "yellow",
|
||||||
|
title: "Auffälliger Fall",
|
||||||
|
text: "Kleine Abweichungen oder Unklarheiten — gezielte Prüfung durch den Sachbearbeiter, nur die offenen Punkte.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
color: "red",
|
||||||
|
title: "Risiko-/Sonderfall",
|
||||||
|
text: "Rechtlich oder finanziell kritisch — vollständige Fachprüfung mit allen Fakten auf einen Blick.",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const benefits = [
|
||||||
|
{
|
||||||
|
icon: <IconCoins size={28} />,
|
||||||
|
title: "Zeit sparen",
|
||||||
|
text: "Routinevorgänge laufen nach Regeln durch: bis zu 70 % weniger Bearbeitungszeit. Im Pilotprojekt messen wir Ihre tatsächliche Zeitersparnis — versprochen wird, was messbar ist.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <IconUsers size={28} />,
|
||||||
|
title: "Mitarbeitende entlasten",
|
||||||
|
text: "Weniger manuelle Prüfungen, mehr Zeit für Menschen und Ausnahmen.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <IconScale size={28} />,
|
||||||
|
title: "Rechtssicher & nachvollziehbar",
|
||||||
|
text: "Jede Entscheidung basiert auf dokumentierten Regeln und ist protokolliert.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <IconBook2 size={28} />,
|
||||||
|
title: "Wissen nutzen",
|
||||||
|
text: "Zentralisiertes Wissen macht Vorschriften, Zuständigkeiten und Prozesse für alle zugänglich.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <IconPigMoney size={28} />,
|
||||||
|
title: "Steuern & planen",
|
||||||
|
text: "Aktuelle Übersichten für Leitung und Verwaltung — bessere Entscheidungen.",
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
const [timeBenefit, ...otherBenefits] = benefits;
|
||||||
|
|
||||||
|
const integrations = [
|
||||||
|
"Finanzbuchhaltung",
|
||||||
|
"Personalwesen",
|
||||||
|
"DMS / Archiv",
|
||||||
|
"Kirchenbuch / Meldewesen",
|
||||||
|
"Microsoft 365",
|
||||||
|
"Weitere Fachverfahren",
|
||||||
|
];
|
||||||
|
|
||||||
|
const examples = [
|
||||||
|
"Bauvorhaben bis 200.000 € prüfen & genehmigen",
|
||||||
|
"Personaleinstellungen prüfen & bescheiden",
|
||||||
|
"Haushaltsentwurf erstellen & plausibilisieren",
|
||||||
|
"Zuschussanträge prüfen & bescheiden",
|
||||||
|
"Beschaffungen bis zur Vergabe freigeben",
|
||||||
|
"Ihr eigener Prozess: Gemeinsam bilden wir Ihr Prozessmodell ab.",
|
||||||
|
];
|
||||||
|
|
||||||
|
const trust = [
|
||||||
|
{
|
||||||
|
icon: <IconLock size={28} />,
|
||||||
|
title: "Datenschutz & Sicherheit",
|
||||||
|
text: "Entwickelt nach den Anforderungen von DSG-EKD und DSGVO: mit Auftragsverarbeitungsvertrag (AVV), technisch-organisatorischen Maßnahmen (TOMs) und Verarbeitungsübersicht. Verschlüsselte Verbindungen, Backups, Betrieb auf Servern in Deutschland.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <IconUserCheck size={28} />,
|
||||||
|
title: "Mensch im Mittelpunkt",
|
||||||
|
text: "KI unterstützt, der Mensch entscheidet. Jederzeit eingreifbar.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <IconEye size={28} />,
|
||||||
|
title: "Transparenz",
|
||||||
|
text: "Jede Antwort mit Quellenangabe. Jede Prüfung ist erklärbar und nachvollziehbar.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
icon: <IconPlug size={28} />,
|
||||||
|
title: "Flexibel & integrierbar",
|
||||||
|
text: "Offene Schnittstellen. Anbindung an bestehende Systeme.",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const faqs = [
|
const faqs = [
|
||||||
{
|
{
|
||||||
q: "Was kostet Kirche Einfach?",
|
q: "Was kostet Kirche Einfach?",
|
||||||
a: "Das Preismodell steht noch nicht fest. Stellen Sie eine Zugangsfrage — wir melden uns mit allen Details.",
|
a: "Das Angebot richtet sich nach Gemeindegröße und Anzahl der abgebildeten Vorgänge. Pilotgemeinden erhalten eine Sonderkondition. Im persönlichen Gespräch erhalten Sie ein verbindliches Angebot — und wir rechnen gemeinsam nach: Wie viele Stunden pro Woche sparen Ihre Mitarbeitenden ein, und was machen sie mit der gewonnenen Zeit?",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
q: "Was muss bei uns installiert werden?",
|
q: "Was muss bei uns installiert werden?",
|
||||||
|
|
@ -87,47 +229,88 @@ const faqs = [
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
q: "Wo liegen unsere Daten?",
|
q: "Wo liegen unsere Daten?",
|
||||||
a: "Auf Servern in Deutschland. Es gibt keine externen Aufrufe an Drittdienste.",
|
a: "Betrieb und Verarbeitung auf Servern in Deutschland. Keine Nutzung Ihrer Daten zum Training von KI-Modellen; der rechtliche Rahmen ist ein Auftragsverarbeitungsvertrag mit uns als Anbieter.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: "Werden unsere Daten zum KI-Training verwendet?",
|
||||||
|
a: "Nein. Ihre Daten werden nicht zum Training von KI-Modellen verwendet. Verarbeitung ausschließlich nach Auftragsverarbeitungsvertrag (AVV); Verarbeitungsstandorte in Deutschland.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: "Wer verantwortet Kirchenrecht-Auskünfte der KI?",
|
||||||
|
a: "Die Wissens-KI nennt zu jeder Antwort die Quelle (z. B. Rundschreiben, Dienstanweisung). Den Wissensbestand pflegen und aktualisieren wir gemeinsam mit Ihnen. Die Verbindlichkeit im Einzelfall bleibt in Ihrer Fachverantwortung.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
q: "Wer sieht welche Daten?",
|
q: "Wer sieht welche Daten?",
|
||||||
a: "Jede Gemeinde arbeitet in ihrem eigenen Mandanten. Innerhalb davon steuern Rollen (Mitglied, Verwaltung), wer was sieht und entscheidet.",
|
a: "Jede Gemeinde arbeitet in ihrem eigenen, getrennten Bereich. Innerhalb davon steuern Rollen (Mitglied, Verwaltung), wer was sieht und entscheidet.",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
q: "Brauchen wir Passwörter?",
|
q: "Brauchen wir Passwörter?",
|
||||||
a: "Nein. Die Anmeldung läuft über einen persönlichen Link per E-Mail — gültig für eine Sitzung, nichts zu merken, nichts zu vergessen.",
|
a: "Nein. Die Anmeldung läuft über einen persönlichen Link per E-Mail. Der Link ist sitzungsgebunden und nur kurze Zeit gültig — auch bei geteilten Postfächern im Pfarrbüro bleibt jeder Zugriff einer Sitzung zugeordnet und nachvollziehbar.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: "Stimmt die 70-%-Angabe für uns?",
|
||||||
|
a: "Zielgröße für Routinevorgänge. Im Pilotprojekt messen wir Ihre tatsächliche Zeitersparnis gemeinsam.",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const pilotPoints = [
|
||||||
|
"Gemeinsame Ersteinrichtung Ihrer Vorgänge",
|
||||||
|
"Schulung für Verwaltung und Sachbearbeitung",
|
||||||
|
"Persönlicher Ansprechpartner während der gesamten Pilotphase",
|
||||||
|
];
|
||||||
|
|
||||||
|
function SectionTitle({ children }: { children: string }) {
|
||||||
|
return (
|
||||||
|
<Title order={2} mb="xs" ta="center">
|
||||||
|
{children}
|
||||||
|
</Title>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function StartPage() {
|
export default function StartPage() {
|
||||||
const { me } = useAuth();
|
const { me } = useAuth();
|
||||||
return (
|
return (
|
||||||
<Stack gap="xl">
|
<Stack gap="xl">
|
||||||
{/* Hero: text left, parish photo right */}
|
{/* Hero: text left, parish photo right */}
|
||||||
<Card withBorder padding="xl" radius="lg">
|
<Card withBorder padding="xl" radius="lg">
|
||||||
<Flex
|
<Flex direction={{ base: "column", md: "row" }} gap="xl" align="center">
|
||||||
direction={{ base: "column", md: "row" }}
|
|
||||||
gap="xl"
|
|
||||||
align="center"
|
|
||||||
>
|
|
||||||
<Box style={{ flex: 7 }}>
|
<Box style={{ flex: 7 }}>
|
||||||
<Badge mb="sm">Für Pfarrbüros und Kirchengemeinden</Badge>
|
<Badge mb="sm">
|
||||||
|
Für Pfarrbüros, Kirchengemeinden und Kirchenverwaltungen
|
||||||
|
</Badge>
|
||||||
<Title order={1} mb="sm">
|
<Title order={1} mb="sm">
|
||||||
Weniger Verwaltung. Mehr Zeit für Menschen.
|
Kirche Einfach. Verwaltung. Einfach. Sicher.
|
||||||
</Title>
|
</Title>
|
||||||
<Text size="lg" c="dimmed" mb="lg">
|
<Text size="lg" c="dimmed" mb="xs">
|
||||||
Kirche Einfach führt Mitarbeitende in eigenen Worten durch
|
Die intelligente Verwaltungsplattform für Ihre Gemeinde. Statt
|
||||||
Vorgänge, prüft Regeln automatisch und fragt Menschen nur, wo
|
Formularen, E-Mail-Ketten und Excel-Listen: Anliegen in eigenen
|
||||||
Urteil nötig ist — lückenlos protokolliert.
|
Worten beschreiben — die Plattform führt durch den Prozess,
|
||||||
|
prüft Regeln nach dokumentierten Kriterien und dokumentiert
|
||||||
|
jeden Schritt.
|
||||||
|
</Text>
|
||||||
|
<Text size="lg" fw={600} mb="lg" c="kirchenblau.7">
|
||||||
|
Fragen. Verstehen. Prüfen. Genehmigen. Erledigt.
|
||||||
</Text>
|
</Text>
|
||||||
<Group>
|
<Group>
|
||||||
<Button component={Link} to="/access" size="lg">
|
<Button component={Link} to={CTA_TO} size="lg">
|
||||||
{CTA_LABEL}
|
{CTA_LABEL}
|
||||||
</Button>
|
</Button>
|
||||||
<Button component={Link} to="/login" size="lg" variant="light">
|
<Button component={Link} to="/login" size="lg" variant="light">
|
||||||
Zum Login
|
Zur Anmeldung
|
||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
<Group gap="lg" mt="lg" wrap="wrap">
|
||||||
|
{facts.map((f) => (
|
||||||
|
<Group key={f.text} gap="xs">
|
||||||
|
<ThemeIcon variant="light" size="sm">
|
||||||
|
{f.icon}
|
||||||
|
</ThemeIcon>
|
||||||
|
<Text size="sm" fw={500}>
|
||||||
|
{f.text}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
|
))}
|
||||||
|
</Group>
|
||||||
{me && (
|
{me && (
|
||||||
<Text mt="md">
|
<Text mt="md">
|
||||||
Angemeldet als <strong>{me.email}</strong> ({me.tenant_id}).
|
Angemeldet als <strong>{me.email}</strong> ({me.tenant_id}).
|
||||||
|
|
@ -140,34 +323,272 @@ export default function StartPage() {
|
||||||
alt="Steinerner Kirchturm unter blauem Himmel"
|
alt="Steinerner Kirchturm unter blauem Himmel"
|
||||||
radius="lg"
|
radius="lg"
|
||||||
className="photo"
|
className="photo"
|
||||||
h={380}
|
h={420}
|
||||||
fit="cover"
|
fit="cover"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Facts */}
|
{/* Status quo problem */}
|
||||||
<Group gap="lg">
|
<Card withBorder padding="xl" radius="lg">
|
||||||
{facts.map((f) => (
|
<Flex direction={{ base: "column", md: "row" }} gap="xl" align="center">
|
||||||
<Group key={f.text} gap="xs">
|
<Box style={{ flex: 7 }}>
|
||||||
<ThemeIcon variant="light" size="lg">
|
<Title order={2} mb="md">
|
||||||
{f.icon}
|
Verwaltung, die Zeit frisst.
|
||||||
|
</Title>
|
||||||
|
<Stack gap="sm">
|
||||||
|
{contrasts.map((c) => (
|
||||||
|
<Group key={c.before} gap="md" wrap="nowrap">
|
||||||
|
<Text size="sm" c="dimmed" style={{ flex: 5 }}>
|
||||||
|
{c.before}
|
||||||
|
</Text>
|
||||||
|
<ThemeIcon size="sm" variant="transparent" color="teal">
|
||||||
|
<IconArrowRight size={16} />
|
||||||
</ThemeIcon>
|
</ThemeIcon>
|
||||||
<Text size="sm" fw={500}>
|
<Text size="sm" fw={500} style={{ flex: 6 }}>
|
||||||
{f.text}
|
{c.after}
|
||||||
</Text>
|
</Text>
|
||||||
</Group>
|
</Group>
|
||||||
))}
|
))}
|
||||||
</Group>
|
</Stack>
|
||||||
|
</Box>
|
||||||
{/* Benefits: alternating photo/text rows */}
|
|
||||||
{benefits.map((b, i) => {
|
|
||||||
const photo = (
|
|
||||||
<Box style={{ flex: 5 }} w={{ base: "100%", md: "auto" }}>
|
<Box style={{ flex: 5 }} w={{ base: "100%", md: "auto" }}>
|
||||||
<Image
|
<Image
|
||||||
src={b.photo}
|
src="/photos/paperwork-desk.webp"
|
||||||
alt={b.alt}
|
alt="Schreibtisch mit Stempeln und Formularen — so sieht es heute oft aus"
|
||||||
|
radius="lg"
|
||||||
|
className="photo"
|
||||||
|
loading="lazy"
|
||||||
|
h={280}
|
||||||
|
fit="cover"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* 5-step process */}
|
||||||
|
<div>
|
||||||
|
<SectionTitle>So einfach funktioniert es</SectionTitle>
|
||||||
|
<Timeline bulletSize={32} lineWidth={2} mb="md">
|
||||||
|
{steps.map((s, i) => (
|
||||||
|
<Timeline.Item key={s.title} bullet={i + 1} title={s.title}>
|
||||||
|
<Text size="sm" c="dimmed" mb={s.quote ? "xs" : undefined}>
|
||||||
|
{s.text}
|
||||||
|
</Text>
|
||||||
|
{s.quote && (
|
||||||
|
<Card
|
||||||
|
withBorder
|
||||||
|
padding="sm"
|
||||||
|
radius="md"
|
||||||
|
bg="var(--mantine-color-kirchenblau-light)"
|
||||||
|
>
|
||||||
|
<Text size="sm" style={{ fontStyle: "italic" }}>
|
||||||
|
„{s.quote}“
|
||||||
|
</Text>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</Timeline.Item>
|
||||||
|
))}
|
||||||
|
</Timeline>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Portal */}
|
||||||
|
<Card withBorder padding="xl" radius="lg">
|
||||||
|
<Title order={2} mb="xs" ta="center">
|
||||||
|
Ein Portal — alle Verwaltungsprozesse.
|
||||||
|
</Title>
|
||||||
|
<Text c="dimmed" mb="md" ta="center">
|
||||||
|
Von Bauvorhaben bis Zuschussantrag: alle Vorgänge an einem Ort —
|
||||||
|
übersichtlich, protokolliert, immer verfügbar.
|
||||||
|
</Text>
|
||||||
|
<Group gap="xs" justify="center" mb="lg" wrap="wrap">
|
||||||
|
{portalAreas.map((a) => (
|
||||||
|
<Badge key={a} variant="light" size="lg">
|
||||||
|
{a}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</Group>
|
||||||
|
<Image
|
||||||
|
src="/screenshots/workflows-light.png"
|
||||||
|
alt="Portal von Kirche Einfach mit Vorgangsformular"
|
||||||
|
radius="md"
|
||||||
|
className="photo"
|
||||||
|
loading="lazy"
|
||||||
|
bd="1px solid var(--mantine-color-default-border)"
|
||||||
|
/>
|
||||||
|
<Text size="sm" c="dimmed" ta="center" mt="sm">
|
||||||
|
Eingabefeld im Portal: „Was möchten Sie erledigen?“
|
||||||
|
</Text>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Platform parts */}
|
||||||
|
<div>
|
||||||
|
<SectionTitle>Fünf Bausteine, die zusammenarbeiten</SectionTitle>
|
||||||
|
<Text c="dimmed" ta="center" mb="lg">
|
||||||
|
KI-gestützt, aber nicht KI-Alleingang — jede Prüfung nach
|
||||||
|
dokumentierten Regeln, jede Entscheidung mit Verantwortung beim
|
||||||
|
Menschen.
|
||||||
|
</Text>
|
||||||
|
<SimpleGrid cols={{ base: 1, xs: 2, md: 3 }} spacing="lg">
|
||||||
|
{platformParts.map((p) => (
|
||||||
|
<Card key={p.title} withBorder padding="lg" radius="lg">
|
||||||
|
<ThemeIcon size={44} radius="md" variant="light" mb="sm">
|
||||||
|
{p.icon}
|
||||||
|
</ThemeIcon>
|
||||||
|
<Title order={4} mb="xs">
|
||||||
|
{p.title}
|
||||||
|
</Title>
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
{p.text}
|
||||||
|
</Text>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</SimpleGrid>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Exception tiers */}
|
||||||
|
<div>
|
||||||
|
<SectionTitle>Geprüft wird, was geprüft werden muss</SectionTitle>
|
||||||
|
<Text c="dimmed" ta="center" mb="lg">
|
||||||
|
Ausnahmeverwaltung statt Vollprüfung — die Freigabe bleibt immer beim
|
||||||
|
Menschen.
|
||||||
|
</Text>
|
||||||
|
<SimpleGrid cols={{ base: 1, md: 3 }} spacing="lg">
|
||||||
|
{tiers.map((t) => (
|
||||||
|
<Card key={t.title} withBorder padding="lg" radius="lg">
|
||||||
|
<Badge color={t.color} variant="light" size="lg" mb="sm">
|
||||||
|
{t.title}
|
||||||
|
</Badge>
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
{t.text}
|
||||||
|
</Text>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</SimpleGrid>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Benefits */}
|
||||||
|
<div>
|
||||||
|
<SectionTitle>Mehr Zeit für das Wesentliche</SectionTitle>
|
||||||
|
<Card withBorder padding="xl" radius="lg" mb="lg">
|
||||||
|
<Flex direction={{ base: "column", sm: "row" }} gap="xl" align="center">
|
||||||
|
<Title order={1} c="kirchenblau.7" style={{ flex: 3 }} ta="center">
|
||||||
|
70 %
|
||||||
|
</Title>
|
||||||
|
<Box style={{ flex: 8 }}>
|
||||||
|
<Title order={3} mb="xs">
|
||||||
|
{timeBenefit.title}
|
||||||
|
</Title>
|
||||||
|
<Text c="dimmed">{timeBenefit.text}</Text>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
|
</Card>
|
||||||
|
<SimpleGrid cols={{ base: 1, xs: 2 }} spacing="lg">
|
||||||
|
{otherBenefits.map((b) => (
|
||||||
|
<Card key={b.title} withBorder padding="lg" radius="lg">
|
||||||
|
<ThemeIcon size={40} radius="md" variant="light" mb="sm">
|
||||||
|
{b.icon}
|
||||||
|
</ThemeIcon>
|
||||||
|
<Title order={4} mb="xs">
|
||||||
|
{b.title}
|
||||||
|
</Title>
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
{b.text}
|
||||||
|
</Text>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</SimpleGrid>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Integrations */}
|
||||||
|
<Card withBorder padding="xl" radius="lg">
|
||||||
|
<Title order={2} mb="xs" ta="center">
|
||||||
|
Passt zu Ihrer bestehenden IT
|
||||||
|
</Title>
|
||||||
|
<Text c="dimmed" ta="center" mb="lg">
|
||||||
|
Offene Schnittstellen, Anbindung an bewährte Fachverfahren.
|
||||||
|
</Text>
|
||||||
|
<Group gap="xs" justify="center" wrap="wrap">
|
||||||
|
{integrations.map((i) => (
|
||||||
|
<Badge key={i} variant="outline" size="lg">
|
||||||
|
{i}
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</Group>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Examples */}
|
||||||
|
<div>
|
||||||
|
<SectionTitle>Typische Vorgänge — ab Tag eins einsatzbereit</SectionTitle>
|
||||||
|
<SimpleGrid cols={{ base: 1, md: 2 }} spacing="sm">
|
||||||
|
{examples.map((e) => (
|
||||||
|
<Group key={e} gap="sm" wrap="nowrap" align="flex-start">
|
||||||
|
<ThemeIcon size="sm" variant="light" mt={4}>
|
||||||
|
<IconCheck size={14} />
|
||||||
|
</ThemeIcon>
|
||||||
|
<Text size="sm">{e}</Text>
|
||||||
|
</Group>
|
||||||
|
))}
|
||||||
|
</SimpleGrid>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Trust */}
|
||||||
|
<div>
|
||||||
|
<SectionTitle>Darauf können Sie sich verlassen</SectionTitle>
|
||||||
|
<SimpleGrid cols={{ base: 1, xs: 2 }} spacing="lg">
|
||||||
|
{trust.map((t) => (
|
||||||
|
<Card key={t.title} withBorder padding="lg" radius="lg">
|
||||||
|
<ThemeIcon size={40} radius="md" variant="light" mb="sm">
|
||||||
|
{t.icon}
|
||||||
|
</ThemeIcon>
|
||||||
|
<Title order={4} mb="xs">
|
||||||
|
{t.title}
|
||||||
|
</Title>
|
||||||
|
<Text size="sm" c="dimmed">
|
||||||
|
{t.text}
|
||||||
|
</Text>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</SimpleGrid>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Pilot program */}
|
||||||
|
<Card withBorder padding="xl" radius="lg">
|
||||||
|
<Flex direction={{ base: "column", md: "row" }} gap="xl" align="center">
|
||||||
|
<Box style={{ flex: 7 }}>
|
||||||
|
<Title order={2} mb="xs">
|
||||||
|
Jetzt Pilotgemeinde werden
|
||||||
|
</Title>
|
||||||
|
<Text c="dimmed" mb="md">
|
||||||
|
Wir bringen Kirche Einfach gemeinsam mit Pilotgemeinden in den
|
||||||
|
Betrieb — mit Einrichtung, Schulung und persönlicher Betreuung.
|
||||||
|
Als Pilotgemeinde gestalten Sie Prozesse und Regelwerk aktiv
|
||||||
|
mit; Ihre tatsächliche Zeitersparnis messen wir von Tag eins an
|
||||||
|
mit.
|
||||||
|
</Text>
|
||||||
|
<List
|
||||||
|
spacing="xs"
|
||||||
|
size="sm"
|
||||||
|
icon={
|
||||||
|
<ThemeIcon size={20} radius="xl" variant="light">
|
||||||
|
<IconCheck size={12} />
|
||||||
|
</ThemeIcon>
|
||||||
|
}
|
||||||
|
mb="md"
|
||||||
|
>
|
||||||
|
{pilotPoints.map((p) => (
|
||||||
|
<List.Item key={p}>{p}</List.Item>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
<Button component={Link} to={CTA_TO} size="lg">
|
||||||
|
{CTA_LABEL}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Box style={{ flex: 5 }} w={{ base: "100%", md: "auto" }}>
|
||||||
|
<Image
|
||||||
|
src="/photos/pilot-meeting.webp"
|
||||||
|
alt="Persönliches Beratungsgespräch mit Notizen am Tisch"
|
||||||
radius="lg"
|
radius="lg"
|
||||||
className="photo"
|
className="photo"
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
|
|
@ -175,116 +596,12 @@ export default function StartPage() {
|
||||||
fit="cover"
|
fit="cover"
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
|
||||||
const text = (
|
|
||||||
<Box style={{ flex: 7 }}>
|
|
||||||
<ThemeIcon size={48} radius="md" variant="light" mb="sm">
|
|
||||||
{b.icon}
|
|
||||||
</ThemeIcon>
|
|
||||||
<Title order={3} mb="xs">
|
|
||||||
{b.title}
|
|
||||||
</Title>
|
|
||||||
<Text c="dimmed">{b.text}</Text>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<Flex
|
|
||||||
key={b.title}
|
|
||||||
direction={{ base: "column", md: "row" }}
|
|
||||||
gap="xl"
|
|
||||||
align="center"
|
|
||||||
>
|
|
||||||
{i % 2 === 0 ? (
|
|
||||||
<>
|
|
||||||
{photo}
|
|
||||||
{text}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{text}
|
|
||||||
{photo}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Flex>
|
</Flex>
|
||||||
);
|
|
||||||
})}
|
|
||||||
|
|
||||||
{/* Product visual */}
|
|
||||||
<Card withBorder padding="lg" radius="lg">
|
|
||||||
<Title order={3} mb="xs">
|
|
||||||
So sieht die Arbeit aus
|
|
||||||
</Title>
|
|
||||||
<Text size="sm" c="dimmed" mb="md">
|
|
||||||
Formulare entstehen automatisch aus den Vorgängen — hier das
|
|
||||||
Aufnahmeformular mit allen Feldtypen.
|
|
||||||
</Text>
|
|
||||||
<Image
|
|
||||||
src="/screenshots/workflows-light.png"
|
|
||||||
alt="Workflow-Formular in Kirche Einfach"
|
|
||||||
radius="md"
|
|
||||||
className="photo"
|
|
||||||
loading="lazy"
|
|
||||||
bd="1px solid var(--mantine-color-default-border)"
|
|
||||||
/>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Steps */}
|
|
||||||
<Card withBorder padding="lg" radius="lg">
|
|
||||||
<Title order={3} mb="md">
|
|
||||||
In drei Schritten dabei
|
|
||||||
</Title>
|
|
||||||
<Timeline bulletSize={28}>
|
|
||||||
{steps.map((s, i) => (
|
|
||||||
<Timeline.Item
|
|
||||||
key={s.title}
|
|
||||||
bullet={i + 1}
|
|
||||||
title={s.title}
|
|
||||||
>
|
|
||||||
<Text size="sm" c="dimmed">
|
|
||||||
{s.text}
|
|
||||||
</Text>
|
|
||||||
</Timeline.Item>
|
|
||||||
))}
|
|
||||||
</Timeline>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
{/* Trust */}
|
|
||||||
<Card withBorder padding="lg" radius="lg">
|
|
||||||
<Title order={3} mb="md">
|
|
||||||
Darauf können Sie sich verlassen
|
|
||||||
</Title>
|
|
||||||
<List
|
|
||||||
spacing="sm"
|
|
||||||
icon={
|
|
||||||
<ThemeIcon size={24} radius="xl" variant="light">
|
|
||||||
<IconCheck size={14} />
|
|
||||||
</ThemeIcon>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<List.Item>
|
|
||||||
<strong>Datenhoheit:</strong> Betrieb auf Servern in Deutschland,
|
|
||||||
ohne Aufrufe an Drittdienste.
|
|
||||||
</List.Item>
|
|
||||||
<List.Item>
|
|
||||||
<strong>Klare Zuständigkeit:</strong> Rollen und Mandanten trennen,
|
|
||||||
wer sieht und wer entscheidet.
|
|
||||||
</List.Item>
|
|
||||||
<List.Item>
|
|
||||||
<strong>Sicher ohne Passwort:</strong> Einmal-Links per E-Mail
|
|
||||||
statt vergessener Kennwörter.
|
|
||||||
</List.Item>
|
|
||||||
<List.Item>
|
|
||||||
<strong>Nachvollziehbar:</strong> Läufe, Entscheidungen und Notizen
|
|
||||||
bleiben protokolliert.
|
|
||||||
</List.Item>
|
|
||||||
</List>
|
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* FAQ */}
|
{/* FAQ */}
|
||||||
<div>
|
<div>
|
||||||
<Title order={3} mb="md">
|
<SectionTitle>Häufige Fragen</SectionTitle>
|
||||||
Häufige Fragen
|
|
||||||
</Title>
|
|
||||||
<Accordion variant="separated">
|
<Accordion variant="separated">
|
||||||
{faqs.map((f) => (
|
{faqs.map((f) => (
|
||||||
<Accordion.Item key={f.q} value={f.q}>
|
<Accordion.Item key={f.q} value={f.q}>
|
||||||
|
|
@ -299,17 +616,32 @@ export default function StartPage() {
|
||||||
|
|
||||||
{/* Final CTA */}
|
{/* Final CTA */}
|
||||||
<Card withBorder padding="xl" radius="lg">
|
<Card withBorder padding="xl" radius="lg">
|
||||||
|
<Flex direction={{ base: "column", md: "row" }} gap="xl" align="center">
|
||||||
|
<Box style={{ flex: 7 }}>
|
||||||
<Group gap="sm" mb="xs">
|
<Group gap="sm" mb="xs">
|
||||||
<IconInbox size={28} />
|
<IconInbox size={28} />
|
||||||
<Title order={3}>Bereit für weniger Papierkram?</Title>
|
<Title order={2}>Mehr Zeit für das Wesentliche.</Title>
|
||||||
</Group>
|
</Group>
|
||||||
<Text c="dimmed" mb="md">
|
<Text c="dimmed" mb="md">
|
||||||
Stellen Sie eine Zugangsfrage — wir melden uns mit den nächsten
|
Vereinbaren Sie ein Pilotgespräch — wir melden uns persönlich
|
||||||
Schritten.
|
mit den nächsten Schritten.
|
||||||
</Text>
|
</Text>
|
||||||
<Button component={Link} to="/access" size="lg">
|
<Button component={Link} to={CTA_TO} size="lg">
|
||||||
{CTA_LABEL}
|
{CTA_LABEL}
|
||||||
</Button>
|
</Button>
|
||||||
|
</Box>
|
||||||
|
<Box style={{ flex: 5 }} w={{ base: "100%", md: "auto" }}>
|
||||||
|
<Image
|
||||||
|
src="/photos/church-pews.webp"
|
||||||
|
alt="Ruhiger Kirchenraum mit leeren Bänken"
|
||||||
|
radius="lg"
|
||||||
|
className="photo"
|
||||||
|
loading="lazy"
|
||||||
|
h={260}
|
||||||
|
fit="cover"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Flex>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Footer mini (Kontakt/Impressum liefert der Betreiber) */}
|
{/* Footer mini (Kontakt/Impressum liefert der Betreiber) */}
|
||||||
|
|
@ -317,11 +649,14 @@ export default function StartPage() {
|
||||||
<Text size="xs" c="dimmed">
|
<Text size="xs" c="dimmed">
|
||||||
Kirche Einfach
|
Kirche Einfach
|
||||||
</Text>
|
</Text>
|
||||||
<Anchor component={Link} to="/access" size="xs">
|
<Anchor component={Link} to={CTA_TO} size="xs">
|
||||||
Zugang
|
Pilotgespräch
|
||||||
</Anchor>
|
</Anchor>
|
||||||
<Anchor component={Link} to="/login" size="xs">
|
<Anchor component={Link} to="/login" size="xs">
|
||||||
Login
|
Anmeldung
|
||||||
|
</Anchor>
|
||||||
|
<Anchor component={Link} to={CTA_TO} size="xs">
|
||||||
|
Kontakt
|
||||||
</Anchor>
|
</Anchor>
|
||||||
<Text size="xs" c="dimmed">
|
<Text size="xs" c="dimmed">
|
||||||
{/* TODO Betreiber: Kontakt und Impressum ergänzen */}
|
{/* TODO Betreiber: Kontakt und Impressum ergänzen */}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue