Initial commit: Rapport Website (Hugo + Hextra)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-05-26 11:52:03 +02:00
commit e007bdd4e7
480 changed files with 41697 additions and 0 deletions
+6
View File
@@ -0,0 +1,6 @@
# Hugo
.hugo_build.lock
resources/_gen/
# macOS
.DS_Store
+756
View File
@@ -0,0 +1,756 @@
/* ─────────────────────────────────────────────────────────────
RAPPORT — Theme-Overrides für Hextra
Warmes Sand/Tan auf hellem Grund (Architektur-Büro-Ästhetik)
───────────────────────────────────────────────────────────── */
@import url('https://fonts.googleapis.com/css2?family=Archivo+Black&family=Inter:wght@300;400;500;600;700&family=Playfair+Display:ital,wght@0,400;0,700;1,400&display=swap');
/* Krungthep — RAPPORT-Display-Font (Mac-System-Font lokal gebundelt) */
@font-face {
font-family: 'Krungthep';
src: url('/fonts/Krungthep.ttf') format('truetype');
font-weight: 400;
font-style: normal;
font-display: swap;
}
/* — Primary-HSL: Tan #b07848 — */
:root {
--primary-hue: 26deg;
--primary-saturation: 42%;
--primary-lightness: 49%;
/* RAPPORT-Palette (Light) */
--rapport-bg: #f7f5f2;
--rapport-surface: #ffffff;
--rapport-surface2: #fbf9f5;
--rapport-dark: #1a1a18;
--rapport-dark2: #2d2d28;
--rapport-accent: #b07848;
--rapport-accent-2: #9a673b;
--rapport-accent-3: #7d5430;
--rapport-text: #1a1a18;
--rapport-text-2: #555550;
--rapport-text-3: #888880;
--rapport-text-4: #aaaaaa;
--rapport-border: #e8e3dc;
--rapport-border-2: #d8d2ca;
}
.dark {
--primary-hue: 26deg;
--primary-saturation: 42%;
--primary-lightness: 56%;
--color-dark: var(--rapport-dark);
}
/* — Body & Backgrounds — */
body {
background: var(--rapport-bg);
color: var(--rapport-text);
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif;
}
.dark body {
background: #181614;
color: #ece9e3;
}
::selection {
background: rgba(176, 120, 72, 0.25);
color: var(--rapport-dark);
}
.dark ::selection {
background: rgba(176, 120, 72, 0.35);
color: #fff;
}
/* — Typografie — Headings serifig, Body monospaced — */
.hextra-toc,
.content,
.prose {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif;
}
h1, h2, h3, h4,
.hextra-home h1, .hextra-home h2, .hextra-home h3,
.hx\:text-2xl, .hx\:text-3xl, .hx\:text-4xl, .hx\:text-5xl, .hx\:text-6xl {
font-family: 'Playfair Display', Georgia, serif !important;
font-weight: 700 !important;
letter-spacing: -0.01em;
}
/* Navbar-Logo: RAPPORT in Krungthep/Archivo Black — nur navbar! */
.hextra-navbar-title,
nav .hextra-navbar-title,
nav .hextra-max-navbar-width .hx\:font-bold {
font-family: Krungthep, 'Archivo Black', sans-serif !important;
letter-spacing: -0.02em !important;
font-weight: 900 !important;
}
/* — Navbar mit hellem Hintergrund & dezenter Border — */
.nav-container {
background: rgba(247, 245, 242, 0.85) !important;
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-bottom: 1px solid var(--rapport-border) !important;
}
.dark .nav-container {
background: rgba(24, 22, 20, 0.85) !important;
border-bottom: 1px solid #2d2926 !important;
}
.nav-container-blur,
.dark .nav-container-blur {
background: transparent !important;
}
/* — Sidebar — */
aside.sidebar-container,
.sidebar-container {
background: var(--rapport-bg) !important;
border-right: 1px solid var(--rapport-border);
}
.sidebar-container a {
color: var(--rapport-text-2);
}
.sidebar-container a:hover {
color: var(--rapport-accent);
background: var(--rapport-surface);
}
.sidebar-active-item,
.sidebar-container .sidebar-active-item {
background: rgba(176, 120, 72, 0.10) !important;
color: var(--rapport-accent) !important;
border-color: rgba(176, 120, 72, 0.20) !important;
}
.dark aside.sidebar-container,
.dark .sidebar-container {
background: #181614 !important;
border-right: 1px solid #2d2926;
}
/* — Links — */
a {
transition: color 0.15s;
}
.content a,
.prose a {
color: var(--rapport-accent);
text-decoration: none;
}
.content a:hover,
.prose a:hover {
color: var(--rapport-accent-2);
text-decoration: underline;
}
/* Hextra-Card-Links (<cards>-Shortcode) sollen NIE unterstrichen werden */
.hextra-card,
.hextra-card:hover,
.hextra-card:focus,
.hextra-card:active,
.hextra-card *,
.hextra-card:hover * {
text-decoration: none !important;
}
/* Card-Hover-Effekt sauber: nur Border + leichte Schatten, kein Underline */
a.hextra-card:hover,
a.hextra-card:focus {
outline: none;
text-decoration: none !important;
}
/* — RAPPORT Hero-Buttons — eigene Pills, nicht von Hextra abhängig — */
.rapport-hero-actions {
display: flex;
gap: 18px;
align-items: center;
justify-content: center;
flex-wrap: wrap;
margin-top: 8px;
margin-bottom: 8px;
}
.rapport-btn {
display: inline-flex;
align-items: center;
gap: 9px;
border-radius: 999px;
padding: 14px 30px;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif;
font-size: 12px;
font-weight: 500;
letter-spacing: 0.07em;
text-transform: uppercase;
text-decoration: none !important;
cursor: pointer;
transition: transform 0.18s ease, box-shadow 0.18s ease, background 0.18s ease, border-color 0.18s ease;
user-select: none;
white-space: nowrap;
}
/* Primary (Download) — Weiss mit Tiefe */
.rapport-btn-primary {
background: #ffffff;
color: var(--rapport-text) !important;
border: 1px solid var(--rapport-border);
box-shadow:
0 1px 0 rgba(255,255,255,0.9) inset,
0 2px 4px rgba(0,0,0,0.04),
0 8px 20px rgba(0,0,0,0.10),
0 16px 40px rgba(0,0,0,0.08);
}
.rapport-btn-primary:hover {
background: #ffffff;
border-color: var(--rapport-border-2);
color: var(--rapport-accent-2) !important;
transform: translateY(-2px);
box-shadow:
0 1px 0 rgba(255,255,255,0.9) inset,
0 4px 8px rgba(0,0,0,0.06),
0 14px 28px rgba(0,0,0,0.12),
0 24px 56px rgba(176,120,72,0.18);
}
.rapport-btn-primary:active {
transform: translateY(0);
box-shadow:
0 1px 0 rgba(255,255,255,0.9) inset,
0 2px 4px rgba(0,0,0,0.06),
0 4px 12px rgba(0,0,0,0.08);
}
/* Secondary (Quellcode) — Outline */
.rapport-btn-secondary {
background: transparent;
color: var(--rapport-text-2) !important;
border: 1.5px solid var(--rapport-border-2);
box-shadow: 0 2px 6px rgba(0,0,0,0.03);
}
.rapport-btn-secondary:hover {
background: rgba(255,255,255,0.5);
border-color: var(--rapport-text-3);
color: var(--rapport-text) !important;
transform: translateY(-2px);
box-shadow:
0 6px 14px rgba(0,0,0,0.06),
0 12px 28px rgba(0,0,0,0.04);
}
.rapport-btn-secondary:active {
transform: translateY(0);
}
/* Dark-Mode-Varianten */
.dark .rapport-btn-primary {
background: #2a2722;
color: #ece9e3 !important;
border-color: #3a3530;
box-shadow:
0 1px 0 rgba(255,255,255,0.04) inset,
0 2px 4px rgba(0,0,0,0.30),
0 10px 24px rgba(0,0,0,0.45);
}
.dark .rapport-btn-primary:hover {
background: #322e28;
color: var(--rapport-accent) !important;
box-shadow:
0 1px 0 rgba(255,255,255,0.04) inset,
0 4px 8px rgba(0,0,0,0.40),
0 16px 32px rgba(0,0,0,0.50),
0 24px 56px rgba(176,120,72,0.22);
}
.dark .rapport-btn-secondary {
border-color: #3a3530;
color: #b8b2a8 !important;
}
.dark .rapport-btn-secondary:hover {
background: rgba(255,255,255,0.04);
border-color: #6a6258;
color: #ece9e3 !important;
}
/* — Hero-Badge — */
.hextra-badge {
background: var(--rapport-surface) !important;
border: 1px solid var(--rapport-border) !important;
border-radius: 20px !important;
padding: 5px 14px !important;
font-size: 10px !important;
letter-spacing: 0.12em !important;
color: var(--rapport-text-4) !important;
text-transform: uppercase !important;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06), 0 1px 3px rgba(0, 0, 0, 0.04);
}
/* — Feature-Cards — */
.hextra-feature-card {
background: var(--rapport-surface) !important;
border: 1px solid var(--rapport-border) !important;
border-radius: 14px !important;
transition: box-shadow 0.2s, transform 0.2s, border-color 0.2s !important;
}
.hextra-feature-card:hover {
border-color: var(--rapport-border-2) !important;
transform: translateY(-2px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.09), 0 2px 8px rgba(0, 0, 0, 0.05) !important;
}
.hextra-feature-card h3 {
font-family: 'Playfair Display', serif !important;
color: var(--rapport-text) !important;
font-weight: 700 !important;
}
.hextra-feature-card p {
color: var(--rapport-text-2) !important;
font-size: 12px !important;
line-height: 1.8 !important;
}
.dark .hextra-feature-card {
background: #211e1a !important;
border-color: #2d2926 !important;
}
.dark .hextra-feature-card h3 {
color: #ece9e3 !important;
}
.dark .hextra-feature-card p {
color: #b8b2a8 !important;
}
/* — Generic Cards (Hextra <cards> shortcode) — */
.hextra-card {
background: var(--rapport-surface);
border: 1px solid var(--rapport-border);
border-radius: 12px;
transition: border-color 0.2s, transform 0.2s;
}
.hextra-card:hover {
border-color: var(--rapport-accent-2);
transform: translateY(-1px);
}
/* — Code-Blocks — */
.hextra-code-block {
background: #ffffff !important;
border: 1px solid var(--rapport-border) !important;
border-radius: 12px !important;
overflow: hidden;
}
.hextra-code-block pre,
.hextra-code-block .highlight,
.hextra-code-block .chroma {
background: transparent !important;
border: none !important;
border-radius: 0 !important;
margin: 0 !important;
}
pre {
background: var(--rapport-surface2);
border: none;
}
code, pre, pre code, kbd, samp, tt {
font-family: ui-monospace, 'SF Mono', Menlo, Monaco, Consolas, 'Liberation Mono', monospace !important;
}
code {
color: var(--rapport-accent-3) !important;
background: rgba(176, 120, 72, 0.10) !important;
padding: 2px 6px;
border-radius: 4px;
font-size: 0.9em;
}
pre code {
color: var(--rapport-text) !important;
background: transparent !important;
padding: 0;
font-size: 0.9em;
}
.dark .hextra-code-block {
background: #1f1c19 !important;
border-color: #2d2926 !important;
}
.dark .hextra-code-block pre,
.dark .hextra-code-block .highlight,
.dark .hextra-code-block .chroma {
background: transparent !important;
border: none !important;
}
.dark code {
color: var(--rapport-accent) !important;
background: rgba(176, 120, 72, 0.15) !important;
}
.dark pre code {
color: #ece9e3 !important;
}
/* — Callouts — */
.hextra-callout {
background: var(--rapport-surface) !important;
border-color: var(--rapport-border-2) !important;
}
/* — Footer — */
.hextra-footer,
footer {
background: var(--rapport-surface2);
border-top: 1px solid var(--rapport-border);
color: var(--rapport-text-3);
}
.hextra-footer a {
color: var(--rapport-text-2);
}
.hextra-footer a:hover {
color: var(--rapport-accent);
}
.dark .hextra-footer,
.dark footer {
background: #15130f;
border-top: 1px solid #2d2926;
color: #888880;
}
/* — TOC — */
.hextra-toc a {
color: var(--rapport-text-3);
}
.hextra-toc a:hover,
.hextra-toc .active {
color: var(--rapport-accent) !important;
}
/* Hextra-Sticky-Bottom-Fades (TOC "Nach oben" + Sidebar-Footer) — */
/* der hardcoded weisse Fade passt nicht zum cremefarbenen RAPPORT-Bg */
.hextra-toc div:has(> #backToTop),
[data-toggle-animation] {
background: var(--rapport-bg) !important;
box-shadow: none !important;
border-top-color: var(--rapport-border) !important;
}
.dark .hextra-toc div:has(> #backToTop),
.dark [data-toggle-animation] {
background: #181614 !important;
border-top-color: #2d2926 !important;
box-shadow: none !important;
}
/* — Search — */
.hextra-search-wrapper input {
background: var(--rapport-surface) !important;
border: 1px solid var(--rapport-border) !important;
color: var(--rapport-text) !important;
}
.hextra-search-wrapper input:focus {
border-color: var(--rapport-accent-2) !important;
}
/* — Tabellen — */
table {
border-color: var(--rapport-border) !important;
}
thead {
background: var(--rapport-surface) !important;
}
th, td {
border-color: var(--rapport-border) !important;
}
/* ─────────────────────────────────────────────────────────────
RAPPORT-LOGO-KARTE (Hero) — Dark Card auf hellem Grund
───────────────────────────────────────────────────────────── */
.rapport-logo-card {
background: var(--rapport-dark);
border: 1px solid var(--rapport-dark2);
border-radius: 999px;
padding: 28px 64px 26px;
display: inline-block;
box-shadow: 6px 0 20px rgba(0, 0, 0, 0.18), 0 6px 16px rgba(0, 0, 0, 0.12);
text-align: center;
margin: 0 auto 32px;
}
.rapport-logo-text {
font-family: 'Krungthep', 'Archivo Black', sans-serif !important;
font-size: 48px;
letter-spacing: -0.02em;
color: #f0ede8;
line-height: 0.95;
font-weight: 400;
}
.rapport-logo-sub {
font-size: 9px;
letter-spacing: 0.15em;
color: #f0ede8;
text-transform: uppercase;
margin-top: 8px;
font-weight: 500;
}
/* — Hero-Meta-Pillen — */
.rapport-meta {
display: flex;
gap: 6px;
align-items: center;
justify-content: center;
flex-wrap: wrap;
margin-top: 32px;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif;
}
.rapport-meta-item {
font-size: 10px;
letter-spacing: 0.08em;
color: var(--rapport-text-4);
text-transform: uppercase;
padding: 0 10px;
border-right: 1px solid var(--rapport-border);
}
.rapport-meta-item:last-child {
border-right: none;
}
/* — Status-Badges (in Arbeit / Geplant / Stabil / Neu) — */
/* Einheitlich: weisse Pill mit dunklem Text — wie Download-Button */
.rapport-status,
.rapport-status.active,
.rapport-status.planned,
.rapport-status.stable,
.rapport-status.new {
display: inline-block;
font-size: 10px;
letter-spacing: 0.12em;
text-transform: uppercase;
padding: 5px 14px;
border-radius: 999px;
margin-bottom: 12px;
font-weight: 600;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif;
background: #ffffff;
color: var(--rapport-text);
border: 1px solid var(--rapport-border);
box-shadow:
0 1px 0 rgba(255,255,255,0.9) inset,
0 1px 2px rgba(0,0,0,0.04),
0 3px 8px rgba(0,0,0,0.06);
}
.dark .rapport-status,
.dark .rapport-status.active,
.dark .rapport-status.planned,
.dark .rapport-status.stable,
.dark .rapport-status.new {
background: #2a2722;
color: #ece9e3;
border-color: #3a3530;
box-shadow:
0 1px 0 rgba(255,255,255,0.04) inset,
0 1px 2px rgba(0,0,0,0.30),
0 4px 10px rgba(0,0,0,0.35);
}
/* — Stack-Bar (am Footer) — */
.rapport-stack-bar {
padding: 20px 0;
border-top: 1px solid var(--rapport-border);
display: flex;
align-items: center;
gap: 20px;
flex-wrap: wrap;
margin-top: 32px;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif;
}
.rapport-stack-label {
font-size: 10px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--rapport-text-4);
flex-shrink: 0;
}
.rapport-stack-items {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.rapport-stack-item {
font-size: 10px;
letter-spacing: 0.06em;
color: var(--rapport-text-3);
background: var(--rapport-surface);
border: 1px solid var(--rapport-border);
border-radius: 6px;
padding: 4px 10px;
}
/* — Reduce default content max-width slightly for monospace lines — */
.content article {
max-width: 100%;
}
/* ─────────────────────────────────────────────────────────────
HOME-HERO — zentriertes Layout wie auf der alten Website
───────────────────────────────────────────────────────────── */
/* Inhalt der home Page zentrieren */
.hextra-home {
align-items: center !important;
text-align: center;
max-width: 100%;
}
/* Subtitle zentriert, schmaler max-width, Playfair Display */
.hextra-home > p,
.hextra-home .not-prose.hx\:text-xl {
font-family: 'Playfair Display', serif !important;
font-size: clamp(16px, 2.2vw, 24px) !important;
font-weight: 400 !important;
color: var(--rapport-text-3) !important;
max-width: 560px !important;
margin-left: auto !important;
margin-right: auto !important;
line-height: 1.55 !important;
text-align: center;
}
/* Buttons immer in einer zentrierten Zeile */
.hextra-home > div.hx\:flex,
.hextra-home > .hx\:flex {
justify-content: center;
align-items: center;
}
/* Hero-Button matches RAPPORT design (dark on cream) */
.hextra-home .hextra-hero-button,
a.hextra-hero-button {
background: var(--rapport-dark);
color: #f0ede8 !important;
border-radius: 20px;
padding: 13px 28px;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif !important;
font-size: 11px !important;
font-weight: 500 !important;
letter-spacing: 0.07em !important;
text-transform: uppercase !important;
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.20);
transition: all 0.18s;
display: inline-flex;
align-items: center;
gap: 9px;
}
/* Feature-Grid linksbündig (Text in Cards) */
.hextra-home .hextra-feature-grid,
.hextra-home .rapport-stack-bar {
text-align: left;
width: 100%;
}
.hextra-home .hextra-feature-card * {
text-align: left;
}
.rapport-stack-bar {
justify-content: flex-start;
text-align: left;
}
/* Section-Heading & Eyebrow vor dem Feature-Grid zentrieren */
.hextra-home h2,
.hextra-home > div > h2 {
text-align: center;
margin-left: auto;
margin-right: auto;
}
/* RAPPORT-Logo bleibt zentriert */
.hextra-home .rapport-logo-card {
margin-left: auto;
margin-right: auto;
display: block;
}
/* Hero-Badge zentrieren */
.hextra-home .hextra-badge,
.hextra-home > p:has(.hextra-badge) {
margin-left: auto;
margin-right: auto;
display: inline-flex !important;
}
/* Hero-Background-Glow — sanfter Tan-Halo */
body:has(.hextra-home)::before {
content: "";
position: fixed;
top: 5%;
left: 50%;
transform: translateX(-50%);
width: 720px;
height: 720px;
background: radial-gradient(circle, rgba(176, 120, 72, 0.08) 0%, transparent 60%);
pointer-events: none;
z-index: 0;
}
.dark body:has(.hextra-home)::before {
background: radial-gradient(circle, rgba(176, 120, 72, 0.12) 0%, transparent 60%);
}
/* Navbar-Logo — RAPPORT-Schriftzug in Krungthep, etwas grösser */
.hextra-navbar-title,
nav a[href="/"] span,
nav [class*="font-bold"] {
font-family: 'Krungthep', 'Archivo Black', sans-serif !important;
letter-spacing: -0.02em !important;
font-weight: 400 !important;
font-size: 23px !important;
line-height: 1 !important;
}
/* RAPPORT-Meta-Pillen zentriert */
.rapport-meta {
justify-content: center;
text-align: center;
}
+107
View File
@@ -0,0 +1,107 @@
---
title: RAPPORT
layout: hextra-home
toc: false
---
{{< hextra/hero-badge >}}
<div class="hx:w-2 hx:h-2 hx:rounded-full" style="background: #b07848; box-shadow: 0 0 8px rgba(176,120,72,0.55);"></div>
<span>Pre-Release 0.8.3 · Aktiv in Entwicklung</span>
{{< /hextra/hero-badge >}}
<div class="hx:mt-8 hx:mb-6">
<div class="rapport-logo-card">
<div class="rapport-logo-text">RAPPORT</div>
<div class="rapport-logo-sub">Studio Administration</div>
</div>
</div>
<div class="hx:mb-12">
{{< hextra/hero-subtitle >}}
Die Studio Management Software für Architekturbüros mit offenem Quellcode — Zeiterfassung, Rechnungen, Offerten, Projekte, Mitarbeiter und QR-Einzahlungsscheine in einer App.
{{< /hextra/hero-subtitle >}}
</div>
<div class="rapport-hero-actions">
<a href="https://git.kgva.ch/karim/RAPPORT/releases/download/0.8.3/RAPPORT%20PRE-RELEASE_0.8.3_aarch64.dmg" target="_blank" rel="noreferrer" class="rapport-btn rapport-btn-primary">Download · 0.8.3 (macOS)</a>
<a href="https://git.kgva.ch/karim/RAPPORT" class="rapport-btn rapport-btn-secondary">Quellcode ↗</a>
</div>
<div class="rapport-meta hx:mb-12">
<span class="rapport-meta-item">AGPL-3.0</span>
<span class="rapport-meta-item">Tauri + React</span>
<span class="rapport-meta-item">macOS (später Linux &amp; Windows)</span>
<span class="rapport-meta-item">Lokal / Selfhosting</span>
</div>
<div class="hx:mt-12 hx:mb-8 hx:text-center">
<p style="font-size: 10px; letter-spacing: 0.2em; text-transform: uppercase; color: var(--rapport-accent); margin-bottom: 12px;">ZIEL</p>
<h2 style="font-family: 'Playfair Display', serif; font-size: clamp(26px, 3.5vw, 40px); font-weight: 700; line-height: 1.2; margin: 0 auto 16px; max-width: 580px;">Freie Studio Managementsoftware</h2>
<p style="font-size: 13px; line-height: 1.8; color: var(--rapport-text-3); max-width: 620px; margin: 0 auto;">Rapport wurde für eigene Zwecke konzipiert — aus dem Drang heraus, möglichst viel Open-Source-Software in einem Architekturbüro zu verwenden. Die Strukturen folgen der SIA 102 (Phasen, Honorar), die Daten bleiben lokal.</p>
</div>
{{< hextra/feature-grid >}}
{{< hextra/feature-card
title="Zeiterfassung"
subtitle="Tages- und Wochenraster mit Drag &amp; Drop. Auswertungen pro Mitarbeiter und Projekt. Ferienverwaltung mit Prorata und Jahresabschluss."
style="background: radial-gradient(ellipse at 50% 80%,rgba(176,120,72,0.10),hsla(0,0%,100%,0));"
>}}
{{< hextra/feature-card
title="Rechnungen & Offerten"
subtitle="QR-Einzahlungsscheine, SIA-Phasen, Akonto-, Teil- und Schlussrechnungen. Offerten sind in Projekte und Rechnungen konvertierbar. PDF-Export."
style="background: radial-gradient(ellipse at 50% 80%,rgba(176,120,72,0.08),hsla(0,0%,100%,0));"
>}}
{{< hextra/feature-card
title="Projekt- & Kundenverwaltung"
subtitle="Projekte nach SIA 102 mit Budget, Phasen und Beteiligten. Erstellung aus einer Offerte mit Verknüpfung zu Zeiterfassung und Rechnungen."
style="background: radial-gradient(ellipse at 50% 80%,rgba(176,120,72,0.12),hsla(0,0%,100%,0));"
>}}
{{< hextra/feature-card
title="Mitarbeiter"
subtitle="Ferienverwaltung, interne Stunden / Absenzen und Lohnabrechnung. Jahresabschluss mit Überstundenausgleich."
>}}
{{< hextra/feature-card
title="Spesen & Bürobudget"
subtitle="Spesenerfassung mit Belegupload. Jahresbudget mit Einnahmen und Ausgaben. Internes Rechnungswesen."
>}}
{{< hextra/feature-card
title="Protokolle & Lieferscheine"
subtitle="Einfache Erstellung von Sitzungsprotokollen mit Beschlüssen und Aufgaben. Briefe und Lieferscheine im gleichen Erscheinungsbild."
>}}
{{< hextra/feature-card
title="Auto-Updater"
subtitle="Rapport prüft beim Start automatisch auf neue Versionen und installiert Updates signiert über Tauri. Einzelne Versionen können übersprungen werden."
>}}
{{< hextra/feature-card
title="System-Tray"
subtitle="Schnellzugriff über die Menüleiste mit Hide-on-Close. Beim Schliessen läuft Rapport im Hintergrund weiter, Cmd+Q beendet die App vollständig."
>}}
{{< hextra/feature-card
title="Rapport Server"
subtitle="Selfhost-Stack für Studios mit mehreren Personen — Postgres, Auth, Realtime-Sync, Storage. Alles in einem Docker-Compose. Eigene Daten, eigene Domain, eigene Kontrolle."
icon="sparkles"
link="/server/"
style="background: radial-gradient(ellipse at 50% 80%,rgba(176,120,72,0.10),hsla(0,0%,100%,0));"
>}}
{{< /hextra/feature-grid >}}
<div class="rapport-stack-bar">
<span class="rapport-stack-label">Aufgebaut auf</span>
<div class="rapport-stack-items">
<span class="rapport-stack-item">Tauri 2</span>
<span class="rapport-stack-item">React 19</span>
<span class="rapport-stack-item">Vite</span>
<span class="rapport-stack-item">SIA 102</span>
<span class="rapport-stack-item">AGPL-3.0</span>
</div>
</div>
+35
View File
@@ -0,0 +1,35 @@
---
title: Dokumentation
linkTitle: Dokumentation
weight: 1
---
Vollständige Anleitung zu RAPPORT — von der Installation über den täglichen Arbeitsablauf bis zur Cloud-Variante und Eigen-Builds.
## Erste Schritte
{{< cards >}}
{{< card link="erste-schritte" title="Quick-Start" subtitle="In sechs Schritten von Null zur ersten Rechnung." >}}
{{< card link="installation" title="Installation" subtitle="macOS, Gatekeeper, Signatur, geplante Plattformen." >}}
{{< card link="einrichtung" title="Einrichtung" subtitle="Bürodaten, Mitarbeiter, Kunden, Projekte initial anlegen." >}}
{{< /cards >}}
## Im Alltag
{{< cards >}}
{{< card link="arbeitsablauf" title="Typischer Arbeitsablauf" subtitle="Kunde → Offerte → Projekt → Zeit → Rechnung." >}}
{{< card link="datenhaltung" title="Datenhaltung & Backup" subtitle="Wo die Daten liegen, wie du sie sicherst und wiederherstellst." >}}
{{< card link="troubleshooting" title="Troubleshooting" subtitle="App startet nicht, Daten weg, Update hängt." >}}
{{< /cards >}}
## Für Fortgeschrittene
{{< cards >}}
{{< card link="web-modus" title="Web-Modus (Multi-User)" subtitle="Rapport im Browser via Supabase — für Studios mit mehreren Nutzern." >}}
{{< card link="entwicklung" title="Entwicklung & Build" subtitle="Aus dem Quellcode kompilieren, beitragen, eigenes Release." >}}
{{< card link="changelog" title="Changelog" subtitle="Versionsgeschichte und Breaking Changes." >}}
{{< /cards >}}
## Hilfe & Support
Bei Bugs oder weiteren Fragen → [Issue auf Gitea](https://git.kgva.ch/karim/RAPPORT/issues). Siehe auch die [FAQ](../faq) für häufige Fragen.
+129
View File
@@ -0,0 +1,129 @@
---
title: Typischer Arbeitsablauf
linkTitle: Arbeitsablauf
weight: 4
toc: true
---
Vom Erstkontakt mit dem Kunden bis zur Schlussrechnung — der typische Weg eines Projekts durch Rapport.
## Übersicht
```text
Kunde → Offerte → Projekt → Zeit → Akonto → Schluss
anlegen erstellen (aus Offerte) erfassen -Rechnung -Rechnung
QR-Schein
```
## 1 · Kunde anlegen
**Kunden → Neu** — siehe [Einrichtung § 3](../einrichtung#3--kunden).
Falls der Kunde später bestellt, lassen sich alle gesammelten Daten (Adresse, Honorartyp) direkt übernehmen.
## 2 · Offerte erstellen
**Offerten → Neu**
| Feld | Inhalt |
|---|---|
| **Kunde** | aus Kundendatenbank wählen |
| **Bezeichnung** | Projekttitel (z. B. "Einfamilienhaus Müller, Bern") |
| **Honorartyp** | Stundensatz / SIA 102 / Pauschal |
| **Bauschätzwert** | bei SIA — Bruttowert in CHF |
| **Phasen** | bei SIA — anzuklickende Phasen |
| **Positionen** | bei Stundensatz/Pauschal — Position, Beschreibung, Betrag |
Mit *PDF exportieren* — fertige Offerte für den Kunden.
### Offerten-Status
| Status | Bedeutung |
|---|---|
| **Entwurf** | noch nicht versandt |
| **Versandt** | beim Kunden, wartet auf Antwort |
| **Angenommen** | Kunde hat zugesagt — bereit zur Konvertierung |
| **Abgelehnt** | Kunde hat abgelehnt — Archiv |
## 3 · Offerte → Projekt
Bei Status **"Angenommen"**: Knopf **"In Projekt konvertieren"**.
Das erzeugt:
- Ein neues Projekt mit den Daten der Offerte (Kunde, Bezeichnung, Honorar, Phasen)
- Eine Verknüpfung zwischen Offerte und Projekt
- Die Offerte selbst bleibt im Archiv erhalten
Siehe auch [Projekt-Feature](../../features/projekte).
## 4 · Zeit erfassen
**Zeit → Mitarbeiter wählen → Woche navigieren**
- **Klick** auf einen Halbstunden-Slot → Eintrag erstellen
- **Drag** über mehrere Slots → längerer Eintrag
- **Projekt zuweisen** (Pflichtfeld) — aus aktiven Projekten
- **SIA-Phase** zuweisen (optional, für detaillierte Auswertung)
### Spezialfälle
- **Ferien** — eigener "Projekt"-Typ "Ferien", in der Auswertung separat
- **Krankheit/Absenz** — eigener "Projekt"-Typ, ebenfalls separat
- **Interne Stunden** — z. B. Verwaltung, Marketing, IT — eigene interne Projekte
## 5 · Akonto-Rechnung
Während der Projektlaufzeit — typisch nach jeder abgeschlossenen SIA-Phase oder monatlich.
**Rechnungen → Neu → Akonto**
| Feld | Inhalt |
|---|---|
| **Projekt** | aus aktiven Projekten |
| **Bezug** | "Akonto für Phase 31 — Vorprojekt" |
| **Betrag** | Stundensatz × Stunden, oder Phasenanteil × Bausumme |
| **Fälligkeit** | i. d. R. 30 Tage |
Rapport zieht automatisch die geleisteten Stunden aus der Zeiterfassung — die kannst du als Basis nehmen oder überschreiben.
PDF inkl. **QR-Einzahlungsschein** — siehe [Rechnungen-Feature](../../features/rechnungen).
## 6 · Schlussrechnung
Nach Projektabschluss — Differenz aus Gesamthonorar minus aller Akonto-Beträge.
**Rechnungen → Neu → Schlussrechnung**
Rapport rechnet die bisherigen Akonto-Rechnungen automatisch ab:
```text
Gesamthonorar (SIA / Pauschal / Stundensatz)
Akonto-Rechnung 1
Akonto-Rechnung 2
Akonto-Rechnung 3
= Schlussrechnung
```
## 7 · Projektabschluss
Im Projekt → Status auf **"Abgeschlossen"**.
Das Projekt bleibt für historische Auswertungen sichtbar, taucht aber nicht mehr in der Zeiterfassungs-Auswahl auf.
## Auswertungen
Wöchentlich / monatlich / am Jahresende:
- **Zeit → Auswertungen** — Stunden pro Mitarbeiter, pro Projekt, pro Phase
- **Rechnungen → Übersicht** — offene Beträge, bezahlt, Mahnungen
- **Buchhaltung → Erfolgsrechnung** — Einnahmen vs. Ausgaben
- **Mitarbeiter → Lohnabrechnung** — monatlich
## Tipps aus dem Alltag
- **Zeit jeden Tag erfassen** statt rückwirkend — sonst gehen Details verloren
- **Akonto regelmässig** statt einmal am Schluss — Liquidität
- **Backups** vor Jahresabschluss — siehe [Datenhaltung](../datenhaltung)
- **Briefbogen-Logo** in hoher Auflösung — sieht im PDF besser aus
+114
View File
@@ -0,0 +1,114 @@
---
title: Changelog
linkTitle: Changelog
weight: 9
toc: true
---
Versionsgeschichte von RAPPORT. Aktuelle Releases: [Gitea](https://git.kgva.ch/karim/RAPPORT/releases).
## 0.8.3 — Aktuelle Version <span class="rapport-status new">Aktuell</span>
Veröffentlicht am 2026-05-24.
**Neu / Verbessert**
- Diverse Verbesserungen und Bugfixes (Details werden im [Release auf Gitea](https://git.kgva.ch/karim/RAPPORT/releases/tag/0.8.3) gepflegt)
**Bekannte Einschränkungen**
- Builds sind Tauri-signiert, aber noch nicht Apple-notarisiert — siehe [Installation § Gatekeeper](../installation#3--erster-start-macos-gatekeeper)
- Linux- und Windows-Builds noch nicht verfügbar
## 0.8.00.8.2 — Patch-Releases
Patch-Reihe mit kleineren Verbesserungen und Bugfixes. Details siehe [Releases auf Gitea](https://git.kgva.ch/karim/RAPPORT/releases).
## 0.7.0 — Auto-Updater & System-Tray
**Neu**
- **Auto-Updater** — Rapport prüft beim Start auf neue Versionen und installiert Updates signiert über Tauri. Einzelne Versionen können übersprungen werden. ([Doku](../../features/auto-updater))
- **System-Tray** — Schnellzugriff über die Menüleiste mit Hide-on-Close. Beim Schliessen läuft Rapport im Hintergrund weiter, Cmd+Q beendet die App vollständig. ([Doku](../../features/system-tray))
- **Quick-Open** der letzten 5 Projekte direkt aus dem Tray-Menü
**Verbessert**
- Schnellerer App-Start durch lazy-geladene Views
- Klarere Statusbadges in der Projekt-Übersicht
## 0.6.x — Spesen & Buchhaltung
**Neu**
- Spesenerfassung mit Beleg-Upload (Base64 in localStorage)
- Jahresbudget mit Einnahmen-/Ausgaben-Übersicht
- Vereinfachte Erfolgsrechnung pro Geschäftsjahr
**Verbessert**
- Lohnabrechnung integriert Spesen-Erstattungen
- CSV-Export aus der Zeiterfassung
## 0.5.x — Mitarbeiter & Lohn
**Neu**
- Mitarbeiterverwaltung mit Pensum, Stundensatz, Ferienanspruch
- Lohnabrechnung mit AHV/IV/EO, ALV, BVG, NBU
- Jahresabschluss mit Überstundenausgleich
- Ferien-Prorata bei Eintritt unter Jahr
## 0.4.x — Projekte & SIA 102
**Neu**
- Projektverwaltung nach SIA 102
- Vorgeschlagene Phasen-Anteile am Gesamthonorar
- Bauschätzwert-basiertes Honorar
**Verbessert**
- Zeit-Auswertung pro SIA-Phase
- Akonto- und Schlussrechnungen mit automatischer Differenzberechnung
## 0.3.x — Rechnungen & QR
**Neu**
- Rechnungsmodul mit QR-Einzahlungsschein (via `swissqrbill`)
- Akonto-, Teil- und Schlussrechnungen
- PDF-Export mit Bürobriefbogen
- Stundensatz-Rechnungen ziehen direkt aus der Zeiterfassung
## 0.2.x — Zeiterfassung
**Neu**
- Wochenraster mit Halbstunden-Slots
- Drag & Drop zur Slot-Erfassung
- Projekt-Zuweisung pro Eintrag
- Auswertungen pro Mitarbeiter und Projekt
## 0.1.x — Initial
**Neu**
- Erste Setup-Routine (Bürodaten, Mitarbeiter, Kunden)
- Briefe und Lieferscheine
- Tauri-2-Bundle für macOS
---
## Roadmap
Geplant — keine konkreten Termine:
- **Linux-Build** (Tauri 2 unterstützt es, Bedarf nötig)
- **Windows-Build** (analog)
- **Cloud-Modus** Stable (Supabase) — derzeit in [Web-Modus](../web-modus) verfügbar, aber experimentell
- **Französische / Italienische Übersetzung**
- **PostgreSQL-Migration** aus localStorage (Knopf in der App)
- **Mobile App** (iOS Companion zur Zeiterfassung) — offen
Wünsche oder Prioritäten → [Issue auf Gitea](https://git.kgva.ch/karim/RAPPORT/issues).
+148
View File
@@ -0,0 +1,148 @@
---
title: Datenhaltung & Backup
linkTitle: Datenhaltung
weight: 5
toc: true
---
Wo Rapport seine Daten speichert, wie du sie sicherst und wiederherstellst.
> **Diese Seite beschreibt die Desktop-App (Single-User).** Wer im Team arbeitet und Rapport gegen einen [Rapport Server](../../server/) betreibt, sichert stattdessen die Postgres-Datenbank — siehe [Rapport Server § Backup](../../server/#backup).
## Speicherort (Desktop-App)
Die Desktop-App speichert **alles lokal** — keine Cloud, kein Server.
### macOS
```text
~/Library/Application Support/com.rapport.app/
```
Dort liegt eine einzelne **`localStorage`**-Datenbank des WebView, in der **alle** Rapport-Daten als JSON unter dem Key `studio_data_v1` gespeichert sind:
- Bürodaten, Logo, IBAN
- Mitarbeiter, Kunden, Projekte, Offerten
- Zeit-Einträge, Rechnungen
- Spesen, Lohnabrechnungen, Protokolle
- App-Einstellungen
> **Konsequenz:** Wer den Application-Support-Ordner kopiert, hat ein vollständiges Backup. Wer ihn löscht, verliert alle Daten.
### Warum localStorage?
In der Desktop-App ist Rapport eine **Single-User-Anwendung**. localStorage ist dafür:
- **Schnell** — keine Datenbank-Roundtrips
- **Einfach** — keine Migration nötig, JSON-Schema im Code
- **Portabel** — eine Datei → ein Backup
Für **Multi-User-Betrieb** existiert [Rapport Server](../../server/) — Postgres + Auth + Realtime in einem Docker-Compose. Wechsel zwischen Desktop- und Server-Modus erfolgt im Login-Bildschirm der App.
## Backup-Strategien
### A · Einfach (manuell)
Den ganzen Ordner kopieren:
```bash
cp -R "~/Library/Application Support/com.rapport.app" \
"~/Documents/Rapport-Backup-$(date +%Y%m%d)"
```
→ Auf USB-Stick, externen Datenträger oder in die Cloud.
### B · Time Machine
Wenn Time Machine läuft, ist der Ordner automatisch dabei. Versionierung inbegriffen.
> **Einschränkung:** Time Machine sichert nur lokal/USB. Für off-site-Backup separat sorgen.
### C · Cron-Job (täglich automatisch)
```bash
# In ~/Library/LaunchAgents/com.rapport.backup.plist hinterlegen
# oder als crontab-Eintrag:
0 22 * * * rsync -a "$HOME/Library/Application Support/com.rapport.app/" \
"$HOME/Backups/rapport/$(date +\%Y\%m\%d)/"
```
### D · iCloud Drive (off-site)
Application-Support liegt **nicht** automatisch in iCloud. Wer das will:
```bash
# Symlink anlegen
mkdir -p "~/iCloud Drive/Rapport"
ln -s "~/Library/Application Support/com.rapport.app" "~/iCloud Drive/Rapport/data"
```
> **Achtung:** iCloud-Sync mit aktiver App kann zu **Race-Conditions** führen. Besser den Sync zeitversetzt (z. B. nachts via Cron).
## Wiederherstellung
### Vollständig (Rapport komplett tot)
1. Rapport beenden (Cmd+Q, nicht nur Fenster zu)
2. Aktuellen Ordner umbenennen (falls noch da):
```bash
mv "~/Library/Application Support/com.rapport.app" \
"~/Library/Application Support/com.rapport.app.bak"
```
3. Backup-Ordner zurück kopieren:
```bash
cp -R "~/Documents/Rapport-Backup-20260523" \
"~/Library/Application Support/com.rapport.app"
```
4. Rapport starten
### Selektiv (nur einzelne Daten)
Da alle Daten in **einem JSON unter `studio_data_v1`** liegen, ist selektive Wiederherstellung **manuell**:
1. Backup-`localStorage`-Datei öffnen (WebKit-Format → mit Tool wie [WebKit Storage Inspector] lesen, oder via Rapport DevTools)
2. Gewünschte Felder in die aktuelle Instanz übertragen
In der Praxis: meistens lohnt sich die **vollständige** Wiederherstellung mehr.
## Export-Funktionen (in der App)
Aus Rapport selbst:
| Was | Wo | Format |
|---|---|---|
| **Zeit-Auswertung** | Zeit → Export | CSV |
| **Rechnung** | Rechnung → PDF | PDF (inkl. QR) |
| **Offerte** | Offerte → PDF | PDF |
| **Lohnabrechnung** | Mitarbeiter → PDF | PDF |
| **Jahres-Buchhaltung** | Buchhaltung → Export | CSV |
Die Exports sind für externe Verwendung (Buchhalter, Treuhänder, Archiv) gedacht — kein Full-Backup.
## Schema-Migrationen
Bei Updates kann sich das Datenformat ändern. Rapport hat einen **Migrations-Mechanismus**: beim Start prüft die App, ob das gespeicherte Format dem aktuellen entspricht, und migriert es automatisch.
Code: [src/storage/migrations.js](https://git.kgva.ch/karim/RAPPORT/src/branch/main/APP/src/storage/migrations.js).
> **Empfehlung:** Vor jedem grösseren Update ein Backup machen — Migrationen sind getestet, aber 100%-Sicherheit gibt es nicht.
## Was wird **nicht** gespeichert?
- **WebView-Cache** — `~/Library/Caches/com.rapport.app/` und `~/Library/WebKit/com.rapport.app/` sind sicher zu löschen (UI-Caches, regenerieren sich)
- **App-Updates** — werden bei Bedarf erneut runtergeladen
- **Logs** — `~/Library/Logs/com.rapport.app/` (geplant, derzeit nicht geschrieben)
## Datenmenge
Typische Grössen pro Bürojahr:
| Inhalt | Grösse |
|---|---|
| Logo (PNG/SVG) | 50 KB 1 MB |
| 1 Jahr Zeiterfassung (1 MA) | ~ 80 KB |
| 1 Jahr Zeiterfassung (5 MA) | ~ 400 KB |
| 50 Projekte mit je 5 Rechnungen | ~ 800 KB |
| **Total typisches Solo-Büro / Jahr** | **~ 12 MB** |
localStorage hat Limits (i. d. R. ~10 MB pro Origin). Für Solo-Büros reicht das problemlos für viele Jahre. Wer das Limit erreicht oder im Team arbeitet → [Rapport Server](../../server/).
+111
View File
@@ -0,0 +1,111 @@
---
title: Einrichtung
linkTitle: Einrichtung
weight: 3
toc: true
---
Nach der [Installation](../installation): Bürodaten, Mitarbeiter, Kunden und Projekte initial anlegen.
## Reihenfolge
Die Reihenfolge ist wichtig — jede Stufe baut auf der vorherigen auf:
```text
1. Bürodaten → 2. Mitarbeiter → 3. Kunden → 4. Projekte
▼ ▼ ▼ ▼
Briefbogen, Zeiterfassung, Adressen, Zeiterfassung,
QR-Schein, Lohn Rechnungen Rechnungen
Login
```
## 1 · Bürodaten
**Einstellungen → Bürodaten**
| Feld | Beschreibung | Verwendung |
|---|---|---|
| **Name** | Bürobezeichnung | Briefbogen, Login-Screen |
| **Adresse** | Strasse, PLZ, Ort | Briefbogen, QR-Rechnung (Empfänger) |
| **Telefon, E-Mail** | Kontaktdaten | Briefbogen, Rechnung-Footer |
| **IBAN** | CH-IBAN (Format `CH XX XXXX …`) | **QR-Einzahlungsschein** — Pflicht |
| **Logo** | PNG/SVG-Upload | Briefbogen, Rechnung, Brief |
| **MwSt.-Nr.** | optional | Rechnung-Footer |
> **Tipp:** Das Logo wird hochauflösend gespeichert (Base64 im localStorage). Bei sehr grossen Dateien (>1 MB) vorher in Vorschau verkleinern.
## 2 · Mitarbeiter
**Einstellungen → Mitarbeiter → Neu**
| Feld | Beschreibung |
|---|---|
| **Name, Vorname** | wird in Zeiterfassung & Lohn verwendet |
| **Initialen** | Kürzel für Auswertungen (z. B. "KGE") |
| **Eintrittsdatum** | für **Prorata** der Ferien |
| **Pensum (%)** | 100 = Vollzeit |
| **Stundensatz (CHF)** | für Stundensatz-Rechnungen |
| **Ferienanspruch (Tage/Jahr)** | i. d. R. 2530 |
| **Lohn (brutto, monatlich)** | optional, für Lohnabrechnung |
Mindestens **ein Mitarbeiter** (z. B. der Inhaber selbst) muss angelegt sein, sonst lässt sich keine Zeit erfassen.
### Sozialabzüge (optional)
In Einstellungen → Lohn:
| Abzug | Standardwert (CH) |
|---|---|
| AHV / IV / EO | 5,3 % |
| ALV | 1,1 % |
| BVG (Pensionskasse) | variabel — je Mitarbeiter |
| NBU | je nach Versicherung |
Die Standardsätze sind hinterlegt, können aber überschrieben werden.
## 3 · Kunden
**Kunden → Neu**
| Feld | Beschreibung |
|---|---|
| **Typ** | Privatperson / Firma |
| **Anrede, Name** | für Brief / Rechnung |
| **Adresse** | Strasse, PLZ, Ort, Land |
| **Ansprechperson** | bei Firmen |
| **Telefon, E-Mail** | Kontakt |
| **Honorartyp Default** | Stundensatz / SIA / Pauschal |
| **Stundensatz** | falls vom Bürostandard abweichend |
| **MwSt.-pflichtig** | ja/nein |
## 4 · Projekte
**Projekte → Neu**
| Feld | Beschreibung |
|---|---|
| **Projekt-Nr.** | freie Form, oder generiert (`2026-001`) |
| **Bezeichnung** | Kurztitel |
| **Standort** | Adresse |
| **Kunde** | aus Kundendatenbank |
| **Bauschätzwert** | für SIA-Phasen-Honorar |
| **SIA-Phasen** | Vorprojekt / Bauprojekt / … — alle anwählbar |
| **Honorartyp** | Stundensatz / SIA 102 / Pauschal |
| **Status** | aktiv / pausiert / abgeschlossen |
### SIA-102-Phasen
Wenn als Honorartyp **SIA 102** gewählt ist, werden die Phasen-Anteile am Gesamthonorar vorgeschlagen — siehe [Projekt-Feature](../../features/projekte#sia-102).
## Checkliste
Nach diesen vier Schritten ist Rapport einsatzbereit:
- [ ] Bürodaten inkl. IBAN erfasst
- [ ] Mindestens ein Mitarbeiter angelegt
- [ ] Erster Kunde angelegt
- [ ] Erstes Projekt angelegt
- [ ] Eine Test-Zeitbuchung erfasst — wird das Projekt korrekt zugewiesen?
- [ ] Eine Test-Rechnung erstellt — kommt der QR-Schein sauber raus?
Wenn alles funktioniert: [Typischer Arbeitsablauf](../arbeitsablauf).
+146
View File
@@ -0,0 +1,146 @@
---
title: Entwicklung & Build
linkTitle: Entwicklung
weight: 7
toc: true
---
Aus dem Quellcode kompilieren, beitragen, eigenes Release erzeugen.
## Voraussetzungen
| Tool | Version |
|---|---|
| **Node.js** | ≥ 20 (für Vite 8) |
| **npm** | ≥ 10 |
| **Rust toolchain** | ≥ 1.77.2 (via `rustup`) |
| **Plattform-Tools** | siehe [Tauri Prerequisites](https://v2.tauri.app/start/prerequisites/) |
Plattform-spezifisch:
- **macOS:** Xcode Command Line Tools (`xcode-select --install`)
- **Windows:** Microsoft C++ Build Tools + WebView2
- **Linux:** `webkit2gtk-4.1`, `librsvg2-dev`, `libayatana-appindicator3-dev`, `build-essential`
## Setup
```bash
git clone https://git.kgva.ch/karim/RAPPORT.git
cd RAPPORT/APP
npm install
```
## Entwicklung
### Web-Modus (HMR, schnellster Loop)
```bash
npm run dev # http://localhost:3000
```
- Hot-Module-Replacement
- Schnellster Iteration-Loop für UI-Arbeit
- Datenpersistierung: Browser-localStorage
### Native Window (Tauri-Fenster mit Desktop-Integration)
```bash
npx tauri dev
```
- Echtes Tauri-Fenster
- System-Tray, Updater, native APIs verfügbar
- Erster Start dauert lange (Rust-Compile)
## Architektur in einem Absatz
> RAPPORT ist eine monolithische SPA: ein React-Root in [App.jsx](https://git.kgva.ch/karim/RAPPORT/src/branch/main/APP/src/App.jsx) hält den **gesamten** App-State in einem `useState({...})`, persistiert ihn synchron in `localStorage` unter `studio_data_v1`, und übergibt ihn als Props an lazy-geladene Views. **Kein Routing-Framework**, **kein State-Library**, **kein TypeScript**, **kein CSS-Framework**. Der **Rust-Teil** ist 109 Zeilen und macht nur drei Dinge: System-Tray, Window-Hide-on-Close, Plugin-Registrierung (Updater, Process, Log). **Keine** `#[tauri::command]` — Frontend ↔ Backend kommuniziert nur über das Event `rapport:navigate` (Tray → Frontend).
Detaillierte Karte: [ARCHITECTURE.md](https://git.kgva.ch/karim/RAPPORT/src/branch/main/APP/ARCHITECTURE.md).
## Verzeichnis-Karte
```text
APP/
├── src/ React 19 (kein TS, nur .jsx)
│ ├── App.jsx Root: State, Navigation, Sidebar, Modals
│ ├── constants.js STORAGE_KEY, NAV_ITEMS, defaultData, SIA-Phasen
│ ├── utils.js Business-Logik: Kalkulation, QR-Bill, CSV, Lohn
│ ├── storage/adapter.js LocalStorageAdapter (Phase 1), SupabaseAdapter (Phase 2)
│ ├── storage/migrations.js Schema-Migrationen
│ ├── views/ 20 Top-Level-Screens, lazy-geladen
│ ├── components/UI.jsx StatusBadge, Modal, FormField, …
│ ├── components/Update* Auto-Update-UI
│ ├── print/ PrintComponents.jsx — alle Druckansichten
│ └── utils/updater.js @tauri-apps/plugin-updater Wrapper
├── supabase/migrations/ Postgres-Schema für Cloud-Variante
├── src-tauri/ Rust-Backend (Tauri 2.10.3)
│ ├── src/lib.rs ~103 Z. — Tray, Window-Events, Plugins
│ ├── tauri.conf.json Updater-URL, Public-Key, CSP, Bundle-Targets
│ └── capabilities/ Tauri Permissions
├── scripts/release.sh Build + Sign + latest.json erzeugen
├── latest.json Updater-Manifest
└── deploy/ Docker-Compose für Web-Modus
```
## Konventionen
- **JavaScript** statt TypeScript — bewusste Entscheidung für Solo-Dev-Velocity
- **Inline-Styles** statt CSS-Framework
- **kein Routing-Framework** — `view`-State in App.jsx triggert Komponente
- **JSON-Schema implizit** — definiert durch `defaultData` in `constants.js`
- **Migrationen** als reine Funktionen in [storage/migrations.js](https://git.kgva.ch/karim/RAPPORT/src/branch/main/APP/src/storage/migrations.js)
## Build
### Desktop (Tauri DMG)
```bash
npm run build # erst Vite-Build (dist/)
npx tauri build # dann Tauri-Bundle (DMG)
```
Output: `src-tauri/target/release/bundle/dmg/Rapport_<version>_aarch64.dmg`
### Web (statisches Bundle)
```bash
npm run build # Output: dist/ (~500 KB)
```
Für Hosting auf einem Static-Server (z. B. Nginx, Caddy, GitHub Pages). Siehe [Web-Modus](../web-modus).
## Release-Workflow
```bash
# 1 · Version bumpen in package.json + Cargo.toml + tauri.conf.json
./scripts/release.sh 0.7.1
# 2 · Build mit Signatur
TAURI_SIGNING_PRIVATE_KEY=$(cat ~/.tauri/rapport-key) \
TAURI_SIGNING_PRIVATE_KEY_PASSWORD=\
npx tauri build
# 3 · latest.json aktualisieren mit neuer Signatur
# 4 · DMG + latest.json auf Gitea Releases hochladen
```
## Beitragen
[Issues & Pull Requests](https://git.kgva.ch/karim/RAPPORT) sind willkommen. Wertvoll sind:
- **Bug-Reports** mit Reproduktionsschritten
- **Workflow-Verbesserungen** aus dem realen Büroalltag
- **Vorlagen** (Briefe, Protokolle, Lieferscheine) für andere Büros
- **Übersetzungen / Internationalisierung** (FR, IT)
- **Linux-/Windows-Builds** und plattformspezifische Fixes
Vor grösseren Änderungen → Issue zum Diskutieren.
## Lizenz
**GNU AGPL-3.0-or-later** — eigene Builds erlaubt, Modifikationen müssen unter derselben Lizenz veröffentlicht werden, wenn die Software als Service angeboten wird.
+62
View File
@@ -0,0 +1,62 @@
---
title: Erste Schritte
linkTitle: Erste Schritte
weight: 1
toc: true
---
Von der Installation bis zur ersten Rechnung — in sechs Schritten.
## 01 · Installation
DMG von [Gitea Releases](https://git.kgva.ch/karim/RAPPORT/releases) herunterladen. Rapport in den **Programme-Ordner** ziehen. Beim ersten Start: *Systemeinstellungen → Datenschutz & Sicherheit* öffnen und Rapport zulassen.
Die Pre-Release-Builds sind **signiert über Tauri**, aber (noch) nicht über die Apple-Notarisierung gegangen — daher der manuelle Freigabe-Schritt.
## 02 · Einrichtung
In den **Einstellungen** hinterlegen:
- **Bürodaten** — Name, Adresse, IBAN, Logo
- **Mitarbeiter** — Namen, Pensum, Stundensatz, Ferienanspruch
- **Kunden** — Adresse, Ansprechperson, Honorartyp
- **Projekte** — SIA-102-Phasen, Budget, Beteiligte
Danach ist die Zeiterfassung bereit.
## 03 · Zeiterfassung
Im Modul **Zeit**:
1. Mitarbeiter wählen
2. Woche navigieren
3. Stunden per **Klick** oder **Drag** erfassen
4. Jedem Eintrag ein Projekt zuweisen
Auswertungen pro Mitarbeiter und pro Projekt sind unter *Zeit → Auswertungen* abrufbar. Halbe Tage und Mehrfacheinträge pro Slot werden unterstützt.
## 04 · Rechnungen
Aus einer **Offerte** oder **direkt** erstellen:
- SIA-Phasen, Stundensatz oder Pauschal wählen
- Akonto-, Teil- oder Schlussrechnung
- Mit *PDF exportieren* wird die fertige Rechnung inkl. **QR-Einzahlungsschein** generiert
Offerten lassen sich nahtlos in Projekte und Rechnungen konvertieren.
## 05 · Backup
In der Desktop-App liegen alle Daten als **JSON** im Applikationsordner:
```text
~/Library/Application Support/com.rapport.app/
```
Für ein Backup reicht es, diesen Ordner zu kopieren — z. B. auf einen externen Datenträger oder in die Cloud. Empfohlen: regelmässig (wöchentlich oder via Time Machine). Im Server-Modus läuft das Backup über Postgres-Dumps — siehe [Rapport Server § Backup](../../server/#backup).
## 06 · Probleme melden
Ein [Issue auf Gitea](https://git.kgva.ch/karim/RAPPORT/issues) erstellen — mit kurzer Beschreibung, was passiert ist. **Screenshots helfen.** Bitte die Rapport-Version (links unten in der App) angeben.
> **Tipp:** Wenn die App nicht mehr startet, hilft oft, den Cache-Ordner zu sichern und neu zu starten. Die JSON-Daten selbst bleiben unverändert.
+94
View File
@@ -0,0 +1,94 @@
---
title: Installation
linkTitle: Installation
weight: 2
toc: true
---
Schritt-für-Schritt-Anleitung für die Installation der Desktop-App.
## Voraussetzungen
| Plattform | Status | Versionen |
|---|---|---|
| **macOS** Apple Silicon (M1 M4) | ✅ Unterstützt | macOS 12+ |
| **macOS** Intel | ⚠ Build auf Anfrage | macOS 12+ |
| **Linux** | 🕐 Geplant | — |
| **Windows** | 🕐 Geplant | — |
Eine Portierung auf Linux und Windows ist mit Tauri 2 möglich. [Issue erstellen](https://git.kgva.ch/karim/RAPPORT/issues/new), wenn du eine Plattform brauchst.
## 1 · Download
Aktueller Build: [Downloads-Seite](../../downloads) oder direkt [Releases auf Gitea](https://git.kgva.ch/karim/RAPPORT/releases).
| Datei | Plattform |
|---|---|
| `RAPPORT_<version>_aarch64.dmg` | macOS Apple Silicon |
| `RAPPORT_<version>_x86_64.dmg` | macOS Intel (auf Anfrage) |
## 2 · DMG öffnen & installieren
1. DMG doppelklicken
2. Rapport.app in den **Applications**-Ordner ziehen
3. DMG auswerfen
## 3 · Erster Start (macOS Gatekeeper)
Beim ersten Start verweigert macOS den Start, weil die Pre-Release-Builds **Tauri-signiert**, aber (noch) **nicht Apple-notarisiert** sind.
### Lösung
1. **Systemeinstellungen → Datenschutz & Sicherheit** öffnen
2. Bis ganz nach unten scrollen — es erscheint:
> "Rapport" wurde blockiert, da es nicht von einem identifizierten Entwickler stammt.
3. Auf **"Trotzdem öffnen"** klicken
4. Bestätigen
Ab dem zweiten Start läuft Rapport ohne Rückfragen.
### Alternative (Terminal)
Falls der GUI-Weg nicht funktioniert:
```bash
xattr -d com.apple.quarantine /Applications/Rapport.app
```
Das entfernt das Quarantäne-Flag und macOS akzeptiert den Start.
## 4 · Erstes Setup
Beim ersten Start zeigt Rapport den **Setup-Bildschirm**. Hier werden die Stammdaten erfasst — siehe [Einrichtung](../einrichtung).
## 5 · Update-Strategie
Ab Version 0.7.0 prüft Rapport beim Start automatisch auf neue Versionen (siehe [Auto-Updater](../../features/auto-updater)). Updates können in den Einstellungen deaktiviert werden.
Manuelle Updates: einfach das neuere DMG installieren — die Daten bleiben erhalten, da sie unabhängig von der App liegen (siehe [Datenhaltung](../datenhaltung)).
## Deinstallation
```bash
# App entfernen
rm -rf /Applications/Rapport.app
# Daten entfernen (optional!)
rm -rf "~/Library/Application Support/com.rapport.app"
# Caches
rm -rf "~/Library/Caches/com.rapport.app"
rm -rf "~/Library/WebKit/com.rapport.app"
```
> **Achtung:** Schritt 2 löscht **alle Rapport-Daten unwiederbringlich**. Vorher Backup machen — siehe [Datenhaltung](../datenhaltung).
## Bekannte Probleme
| Problem | Lösung |
|---|---|
| App lässt sich nicht öffnen trotz Freigabe | Terminal-Variante mit `xattr` |
| "Rapport is damaged" | DMG erneut von Gitea ziehen (Browser-Cache hat evtl. Müll) |
| Schwarzer Bildschirm beim Start | `~/Library/WebKit/com.rapport.app` löschen, neu starten |
Mehr unter [Troubleshooting](../troubleshooting).
+203
View File
@@ -0,0 +1,203 @@
---
title: Troubleshooting
linkTitle: Troubleshooting
weight: 8
toc: true
---
Typische Probleme und Lösungen. Wenn dein Problem nicht dabei ist → [Issue auf Gitea](https://git.kgva.ch/karim/RAPPORT/issues).
## App startet nicht
### "Rapport ist beschädigt" beim ersten Start
**Ursache:** macOS Gatekeeper blockt unsignierte/nicht-notarisierte Apps.
**Lösung:** siehe [Installation § 3](../installation#3--erster-start-macos-gatekeeper). Kurz:
```bash
xattr -d com.apple.quarantine /Applications/Rapport.app
```
### App startet, zeigt aber schwarzen Bildschirm
**Ursache:** WebView-Cache korrupt.
**Lösung:**
```bash
rm -rf "~/Library/Caches/com.rapport.app"
rm -rf "~/Library/WebKit/com.rapport.app"
```
App neu starten. Daten gehen dabei **nicht** verloren (liegen in `Application Support`, nicht im Cache).
### App stürzt sofort beim Start ab
**Ursache:** wahrscheinlich beschädigte JSON-Daten in `studio_data_v1`.
**Diagnose:**
```bash
# Daten ansehen (DevTools-Output)
open "~/Library/Application Support/com.rapport.app"
```
**Lösung:**
1. Backup wiederherstellen (siehe [Datenhaltung](../datenhaltung#wiederherstellung))
2. **Oder** als letzter Ausweg: Daten zurücksetzen
```bash
mv "~/Library/Application Support/com.rapport.app" \
"~/Library/Application Support/com.rapport.app.bad"
```
App neu starten → erstellt frische Daten. Anschliessend Setup-Screen.
## Daten weg
### Nach einem App-Update fehlen Einträge
**Ursache:** mögliche fehlgeschlagene Migration.
**Sofortmassnahme:**
1. Rapport **beenden** (Cmd+Q, nicht nur Fenster zu)
2. **Aktuelles Datenverzeichnis sichern**:
```bash
cp -R "~/Library/Application Support/com.rapport.app" \
"~/Documents/Rapport-Notfall-$(date +%Y%m%d-%H%M)"
```
3. [Issue erstellen](https://git.kgva.ch/karim/RAPPORT/issues/new) mit:
- Version vor dem Update (falls bekannt)
- Version nach dem Update
- Was fehlt
- Optional: gesicherter Datenordner (via Pastebin oder verschlüsselt zugesandt)
### localStorage voll
**Symptom:** Rapport schreibt Fehler-Toast "Speicher voll" beim Sichern.
**Ursache:** macOS WebView limitiert localStorage auf ~10 MB pro Origin.
**Lösung:**
- Sehr grosse Logos durch kleinere ersetzen (Bürodaten → Logo)
- Belege (Spesen) selektiv löschen oder als externe Datei archivieren
- Auf [Web-Modus](../web-modus) wechseln (Postgres ohne praktisches Limit)
## Updates
### Auto-Update findet nichts
**Diagnose:**
```bash
curl -s https://git.kgva.ch/karim/RAPPORT/raw/branch/main/APP/latest.json
```
→ sollte JSON liefern. Wenn nicht: Server-/Netzwerkproblem.
### Update lädt, lässt sich aber nicht installieren
**Ursache:** Signaturprüfung scheitert (Public-Key in App ≠ Signatur in `latest.json`).
**Lösung:** Manuelles Update — DMG direkt von [Releases](https://git.kgva.ch/karim/RAPPORT/releases) laden und installieren. Daten bleiben erhalten.
### "Diese Version überspringen" rückgängig machen
In **Einstellungen → Updates** → *Übersprungene Versionen zurücksetzen*. Beim nächsten Start wird die Version wieder angeboten.
## PDF / QR-Schein
### QR-Schein hat falsche Daten
**Diagnose-Checkliste:**
- [ ] IBAN korrekt? (CH, 21 Zeichen, keine Leerzeichen)
- [ ] Empfänger-Adresse vollständig? (PLZ und Ort beide gefüllt)
- [ ] Schuldner-Adresse vollständig?
- [ ] Betrag > 0?
- [ ] Referenz nicht zu lang? (max 27 Zeichen)
QR-Bibliothek: [`swissqrbill`](https://github.com/schoero/SwissQRBill) — bei merkwürdigen Fehlern dort nachschauen.
### PDF-Export ist leer / weisses Blatt
**Ursache:** Print-View hat keine Daten (möglicherweise Race-Condition beim Laden).
**Lösung:** Rechnung schliessen, erneut öffnen, dann PDF.
### PDF-Schrift sieht falsch aus
**Ursache:** Web-Schrift nicht geladen, Fallback greift.
**Lösung:** Vor dem Drucken warten, bis das Vorschau-Bild komplett geladen ist (35 Sek).
## System-Tray
### Tray-Icon erscheint nicht
**Plattform-Hinweis:** Tray-Icons unter macOS sind bei extrem voller Menüleiste oder unter "Bartender"/"Hidden Bar" eventuell unsichtbar.
**Diagnose:**
```bash
ps aux | grep -i rapport
```
→ wenn Prozess läuft, aber kein Icon: in den Tray-Manager-Apps prüfen.
**Konfiguration:** Einstellungen → System-Tray → *Tray-Icon ausblenden* (aus → Icon erzwingen).
### Tray-Menü reagiert nicht / hängt
**Lösung:** App via *Activity Monitor* hart beenden und neu starten. Daten gehen nicht verloren (alle Schreibungen sind synchron in localStorage).
## Web-Modus
### Login-Screen zeigt keine Server-URL
**Ursache:** `.env.production` enthielt nicht den richtigen `VITE_SUPABASE_URL` zur Build-Zeit.
**Lösung:** `.env.production` prüfen, dann `npm run build` neu, Container restart.
### Realtime-Updates kommen nicht an
**Ursache:** Websocket-Support fehlt im Reverse Proxy.
**Lösung:** In Nginx Proxy Manager für `api.*` Websocket Support aktivieren.
Siehe [Web-Modus § Troubleshooting](../web-modus#troubleshooting).
## Debug-Informationen sammeln
Bei einem Issue helfen folgende Infos:
```bash
# Rapport-Version
defaults read /Applications/Rapport.app/Contents/Info.plist CFBundleShortVersionString
# macOS-Version
sw_vers
# Architektur
uname -m
# Datenverzeichnis-Grösse
du -sh "~/Library/Application Support/com.rapport.app"
# Cache-Verzeichnis-Grösse
du -sh "~/Library/Caches/com.rapport.app"
```
→ Bei Issue mit anhängen.
## Wenn nichts hilft
[Neues Issue auf Gitea](https://git.kgva.ch/karim/RAPPORT/issues/new) mit:
- Was du gemacht hast
- Was passiert ist
- Was du erwartet hättest
- Screenshots (auch von DevTools-Konsole falls möglich)
- Rapport-Version und macOS-Version
+53
View File
@@ -0,0 +1,53 @@
---
title: Web-Modus (Multi-User)
linkTitle: Web-Modus
weight: 6
toc: true
---
> **Hinweis:** Der frühere Supabase-basierte Web-Modus wurde durch **[Rapport Server](../../server/)** abgelöst — den vollständigen Selfhost-Stack mit eigenem Postgres, Auth, Realtime und Storage. Keine externe Cloud-Abhängigkeit mehr.
>
> Diese Seite bleibt als Referenz erhalten, der **empfohlene Weg** für Multi-User-Setups ist **[Rapport Server](../../server/)**.
## Wann brauchst du das?
| Anwendungsfall | Empfehlung |
|---|---|
| **Solo-Büro, ein Mac** | Desktop-App — siehe [Installation](../installation) |
| **25 Personen, gleicher Standort** | [Rapport Server](../../server/) auf einem Mac Mini im LAN |
| **Verteiltes Team / Home-Office** | [Rapport Server](../../server/) mit SSL + Reverse Proxy |
| **Hosted Backend (eigener VPS)** | [Rapport Server](../../server/) auf Linux-VPS |
## Architektur (Kurzfassung)
```text
┌────────────┐ HTTPS ┌──────────────┐ SQL ┌────────────┐
│ Browser │ ──────────────│ nginx │ ─────────────│ Postgres │
│ / Desktop │ │ (Frontend) │ │ + GoTrue │
└────────────┘ └──────────────┘ │ + REST │
│ + Realtime │
│ + Storage │
└────────────┘
```
- **Frontend:** dieselbe React-App, aber Vite-Build statt Tauri (`npm run build`)
- **Backend:** Postgres-Stack ([Rapport Server](../../server/))
- **Auth:** E-Mail / Passwort über GoTrue
- **Storage:** Belege, Logos in Object-Storage
## Setup
Alle Setup-Schritte (Repo klonen, `.env` erstellen, Migrations syncen, Docker-Compose starten, Reverse-Proxy konfigurieren) sind in **[Rapport Server](../../server/)** dokumentiert.
## Migration Desktop → Cloud
Wer mit der Desktop-App startet und später auf den Web-Modus wechseln möchte:
- **Aktuell:** manueller Export aus Rapport (CSV/PDF) und manuelles Wiederanlegen im neuen Setup
- **Geplant:** *"localStorage → Postgres"*-Import-Knopf direkt in der App
Status: [Issue auf Gitea](https://git.kgva.ch/karim/RAPPORT/issues).
## Troubleshooting
Siehe [Rapport Server § Troubleshooting](../../server/) oder [allgemeine Troubleshooting-Seite](../troubleshooting).
+99
View File
@@ -0,0 +1,99 @@
---
title: Downloads
linkTitle: Downloads
weight: 3
toc: true
---
Aktuelle Builds von Rapport. Quellcode und ältere Versionen auf [Gitea](https://git.kgva.ch/karim/RAPPORT/releases).
Rapport besteht aus zwei Komponenten:
| Komponente | Für wen | Aktuelle Version |
|---|---|---|
| **Desktop-App** | Solo-Büro, lokale Datenhaltung | 0.8.3 |
| **Rapport Server** | Team / Multi-User / Selfhost | 0.1.0 |
---
## Desktop-App — Pre-Release 0.8.3
<span class="rapport-status new">Aktuelle Version</span>
**Neuerungen** — siehe [Changelog](../docs/changelog#083) für Details.
### macOS
| Architektur | Download |
|---|---|
| **Apple Silicon (M1M4)** | [RAPPORT_0.8.3_aarch64.dmg](https://git.kgva.ch/karim/RAPPORT/releases/download/0.8.3/RAPPORT%20PRE-RELEASE_0.8.3_aarch64.dmg) |
| **Intel (x86_64)** | [auf Anfrage](https://git.kgva.ch/karim/RAPPORT/issues/new) |
> **Erstinstallation:** *Systemeinstellungen → Datenschutz & Sicherheit* öffnen und Rapport zulassen. Die Builds sind über Tauri signiert, aber (noch) nicht Apple-notarisiert.
### Linux & Windows
**Geplant.** Rapport basiert auf Tauri 2 — eine Portierung ist möglich, sobald der Bedarf besteht. [Issue erstellen](https://git.kgva.ch/karim/RAPPORT/issues/new), wenn du eine Plattform brauchst.
---
## Rapport Server — 0.1.0
<span class="rapport-status new">Erstes Release</span>
Selfhost-Backend für Multi-User-Setups — Postgres, Auth, Realtime-Sync, Storage. Details und Setup-Anleitung auf der [Server-Seite](../server).
### Server-App (macOS)
GUI zum Starten, Stoppen und Verwalten der Server-Instanz auf einem Mac (Mac Mini, Studio-Rechner). Bündelt den Docker-Compose-Stack.
| Architektur | Download |
|---|---|
| **Apple Silicon (M1M4)** | [RAPPORT_SERVER_0.1.0_aarch64.dmg](https://git.kgva.ch/karim/rapport-server/releases/download/0.1.0/RAPPORT%20SERVER_0.1.0_aarch64.dmg) |
| **Intel (x86_64)** | [auf Anfrage](https://git.kgva.ch/karim/rapport-server/issues/new) |
> **Voraussetzung:** [Colima](https://github.com/abiosoft/colima) oder Docker Desktop muss installiert sein. Die App verwaltet den Container-Stack darüber.
### Docker (Linux / VPS / Headless)
Für Linux-Server, NAS oder VPS — der reine Docker-Compose-Stack ohne GUI.
```bash
git clone https://git.kgva.ch/karim/rapport-server.git
cd rapport-server
git checkout 0.1.0
cp .env.example .env
# .env editieren (POSTGRES_PASSWORD, JWT_SECRET, SITE_URL)
docker compose up -d
```
Vollständige Anleitung mit `.env`-Variablen, Reverse-Proxy und Backup → [Server-Seite](../server).
Container-Images werden von Docker Hub / Gitea-Registry gezogen — `docker compose pull` aktualisiert auf die neuesten Tags.
---
## Quellcode
**Desktop-App** — Tauri / React:
```bash
git clone https://git.kgva.ch/karim/RAPPORT.git
cd RAPPORT/APP
npm install
npm run tauri dev
```
Voraussetzungen: Node 20+, Rust (stable), Xcode-CLI (macOS).
**Rapport Server**:
```bash
git clone https://git.kgva.ch/karim/rapport-server.git
```
Beide Repos auf [Gitea](https://git.kgva.ch/karim) — Issues und PRs willkommen.
## Auto-Update
Seit 0.7.0 prüft die Desktop-App beim Start automatisch auf neue Versionen — siehe [Auto-Updater](../features/auto-updater). Für Rapport Server: `git pull && docker compose pull && docker compose up -d`.
+65
View File
@@ -0,0 +1,65 @@
---
title: FAQ
linkTitle: FAQ
weight: 4
toc: true
---
Häufige Fragen zu RAPPORT. Bei Bugs oder weiteren Fragen → [Issue auf Gitea](https://git.kgva.ch/karim/RAPPORT/issues).
## Für wen ist Rapport gedacht?
Für **kleine Architekturbüros in der Schweiz**. Die Strukturen folgen der SIA 102 (Phasen, Honorar). Rapport wurde für den internen Gebrauch entwickelt und wird mit diesem Projekt öffentlich geteilt.
## Ist Rapport kostenlos?
**Ja, vollständig.** Quellcode unter GNU AGPL-3.0-or-later. Keine versteckten Kosten, keine Telemetrie. Die Daten bleiben lokal auf deinem Computer bzw. in deiner Instanz. Du hast die komplette Kontrolle über deine Daten.
## Welche Systeme werden unterstützt?
Aktuell nur **macOS** (Intel & Apple Silicon). Rapport basiert auf Tauri — eine Portierung auf **Linux** und **Windows** ist möglich, sobald der Bedarf seitens Community besteht.
## Wo werden die Daten gespeichert?
Rapport unterstützt **zwei Modi**, beide selbst-gehostet:
- **Desktop-App (Single-User)** — alle Daten liegen lokal auf deinem Mac (`localStorage` im Applikationsordner). Kein Server nötig, kein Cloud-Account, keine Telemetrie.
- **Server-Modus (Multi-User)** — Daten in einer eigenen PostgreSQL-Datenbank über [Rapport Server](../server). Mehrere Personen, Realtime-Sync, eigene Domain. Wechsel direkt im Login-Bildschirm der App.
In beiden Fällen bleibt die Kontrolle über die Daten bei dir.
## Ist die Software stabil genug für den Betrieb?
**Noch nicht.** Aktuell befindet sich Rapport in aktiver Entwicklung (Pre-Release **0.8.3**). Funktionen können sich ändern, Bugs sind möglich. Regelmässige Backups sind empfohlen. Testen und Feedback geben ist erwünscht.
## Was ist mit dem QR-Einzahlungsschein?
Rapport erzeugt **Schweizer QR-Rechnungen** nach Norm — direkt eingebettet in das PDF der Rechnung. IBAN, Bürodaten und Empfänger werden aus den Einstellungen bzw. den Kundendaten gezogen. Akonto-, Teil- und Schlussrechnungen werden unterstützt.
## Wie funktioniert die Zeiterfassung?
Tages- und Wochenraster mit **Drag & Drop**. Jeder Eintrag wird einem Projekt zugewiesen. Auswertungen pro Mitarbeiter und Projekt sind unter *Zeit* abrufbar. Ferienverwaltung mit Prorata und Jahresabschluss mit Überstundenausgleich.
## Kann ich zum Projekt beitragen?
**Ja.** Issues und Pull Requests sind willkommen auf [Gitea](https://git.kgva.ch/karim/RAPPORT). Rapport ist kein Framework — konkrete Verbesserungen für den **Büroalltag** sind am wertvollsten:
- Bug-Reports mit Reproduktionsschritten
- Workflow-Verbesserungen aus dem realen Büroalltag
- Vorlagen (Briefe, Protokolle, Lieferscheine) für andere Büros
- Übersetzungen / Internationalisierung
## Wie erhalte ich Hilfe bei einem Problem?
Ein [Issue auf Gitea](https://git.kgva.ch/karim/RAPPORT/issues) ist der beste Weg. Bitte beschreibe, was du gemacht hast und was passiert ist. Screenshots helfen. Die Rapport-Version (links unten in der App) bitte angeben.
| Kanal | Verwendung |
|--------------------|-------------------------------------------|
| **Gitea Issues** | Bugs, Feature-Wünsche, allgemeine Fragen |
| **gabrielevarano.ch** | Entwickler-Kontakt |
## Warum Open Source?
Bürowissen sollte nicht in proprietären Tools eingesperrt sein. Studio-Management-Workflows sind in der Branche gut etabliert — ein Tool, das die Schweizer Konventionen (SIA 102, QR-Rechnung) sauber umsetzt, gehört allen.
**AGPL-3.0** stellt sicher, dass Verbesserungen wieder ins Projekt fliessen.
+20
View File
@@ -0,0 +1,20 @@
---
title: Features
linkTitle: Features
weight: 2
---
Die Bausteine von RAPPORT — Studio-Management für Schweizer Architekturbüros.
## Module
{{< cards >}}
{{< card link="zeiterfassung" title="Zeiterfassung" subtitle="Tages- & Wochenraster mit Drag & Drop." >}}
{{< card link="rechnungen" title="Rechnungen & Offerten" subtitle="QR-Einzahlungsscheine, SIA-Phasen, Akonto." >}}
{{< card link="projekte" title="Projekt- & Kundenverwaltung" subtitle="SIA 102, Budget, Phasen, Beteiligte." >}}
{{< card link="mitarbeiter" title="Mitarbeiter" subtitle="Ferien, Absenzen, Lohnabrechnung." >}}
{{< card link="spesen" title="Spesen & Bürobudget" subtitle="Belegupload, Jahresbudget, Internes." >}}
{{< card link="protokolle" title="Protokolle & Lieferscheine" subtitle="Sitzungsprotokolle, Briefe, Lieferscheine." >}}
{{< card link="auto-updater" title="Auto-Updater" subtitle="Signierte Updates via Tauri." >}}
{{< card link="system-tray" title="System-Tray" subtitle="Hide-on-Close, Quick-Open." >}}
{{< /cards >}}
+49
View File
@@ -0,0 +1,49 @@
---
title: Auto-Updater
linkTitle: Auto-Updater
weight: 7
toc: true
---
<span class="rapport-status new">Neu in 0.7.0</span>
**Rapport prüft beim Start automatisch auf neue Versionen** und installiert Updates signiert über Tauri. Einzelne Versionen können übersprungen werden.
## Funktionsweise
Beim App-Start:
1. Abfrage gegen `https://git.kgva.ch/karim/RAPPORT/releases/latest.json`
2. Versionsvergleich mit lokaler `version` im Tauri-Bundle
3. Bei neuer Version → Update-Dialog
4. Bei Bestätigung → Download + Signaturprüfung + Installation + Neustart
## Sicherheit
- Updates werden mit dem **Tauri-Updater-Schlüssel** signiert
- Manipulierte Downloads werden abgelehnt
- Quellcode und Build sind reproduzierbar (Gitea CI, geplant)
## Optionen
- **Update installieren** — Download & Neustart
- **Diese Version überspringen** — überspringt nur diese eine Version
- **Später erinnern** — beim nächsten Start erneut fragen
Updates können in den Einstellungen komplett deaktiviert werden.
## Latest-Endpoint
```json
{
"version": "0.8.3",
"notes": "Rapport 0.8.3",
"pub_date": "2026-05-24T00:00:00Z",
"platforms": {
"darwin-aarch64": {
"signature": "…",
"url": "https://git.kgva.ch/karim/RAPPORT/releases/download/0.8.3/RAPPORT%20PRE-RELEASE.app.tar.gz"
}
}
}
```
+52
View File
@@ -0,0 +1,52 @@
---
title: Mitarbeiter
linkTitle: Mitarbeiter
weight: 4
toc: true
---
<span class="rapport-status active">In Arbeit</span>
**Ferienverwaltung, interne Stunden / Absenzen und Lohnabrechnung.** Jahresabschluss mit Überstundenausgleich.
## Stammdaten
Pro Mitarbeiter:
- Name, Eintrittsdatum, Pensum (%)
- Stundensatz (intern, für Rechnungen)
- Ferienanspruch (Tage / Jahr)
- Lohn (monatlich, brutto)
## Ferienverwaltung
- **Prorata-Berechnung** bei Eintritt unter Jahr
- **Ferien-Saldo** in Tagen (live)
- **Halbtage** unterstützt
- **Übertrag** ins Folgejahr oder Auszahlung
## Absenzen
Krankheit, Militär, Mutterschaft, unbezahlter Urlaub — getrennt erfasst, mit Auswertung pro Mitarbeiter.
## Lohnabrechnung
Monatliche Abrechnung mit:
- Grundlohn (basierend auf Pensum)
- Überstunden-Vergütung
- Spesen-Erstattung
- Sozialabzüge (AHV, ALV, Pensionskasse)
PDF-Export pro Mitarbeiter.
## Jahresabschluss
- Ferien-Restguthaben übertragen oder auszahlen
- Überstunden ausgleichen oder vergüten
- Lohnausweis vorbereiten (Export)
## Verwandte Module
- [Zeiterfassung](../zeiterfassung) — Pensum-Soll vs. Stunden-Ist
- [Spesen](../spesen) — Spesen-Erstattung in der Lohnabrechnung
+53
View File
@@ -0,0 +1,53 @@
---
title: Projekt- & Kundenverwaltung
linkTitle: Projekte
weight: 3
toc: true
---
<span class="rapport-status active">In Arbeit</span>
**Projekte nach SIA 102** mit Budget, Phasen und Beteiligten. Erstellung aus einer Offerte mit Verknüpfung zu Zeiterfassung und Rechnungen.
## Projektstruktur
Jedes Projekt besitzt:
- **Stammdaten** — Nummer, Bezeichnung, Standort, Bauschätzwert
- **Kunde** — verknüpft mit Kundendatenbank
- **Beteiligte** — Bauleitung, Fachplaner, Behörden
- **Phasen** — SIA 102 (Vorprojekt, Bauprojekt, Ausschreibung, …)
- **Budget** — Gesamthonorar, pro Phase aufgeteilt
## SIA 102
Standard-Phasenverteilung wird vorgeschlagen, kann pro Projekt überschrieben werden.
| Phase | Anteil (Standard) |
|---|---|
| 31 — Vorprojekt | 9 % |
| 32 — Bauprojekt | 21 % |
| 33 — Bewilligung | 3 % |
| 41 — Ausschreibung | 18 % |
| 51 — Ausführung | 38 % |
| 52 — Inbetriebnahme | 6 % |
| 53 — Abschluss | 5 % |
## Kundendatenbank
- Adresse, Ansprechperson, Telefon, E-Mail
- Honorartyp (Stundensatz / SIA / Pauschal)
- Verknüpfung zu allen Projekten und Rechnungen des Kunden
## Auswertung
Pro Projekt:
- Geleistete Stunden vs. Budget
- Honorar-Saldo (verrechnet / Akonto / offen)
- Phasen-Fortschritt
## Verwandte Module
- [Rechnungen](../rechnungen) — Offerte → Projekt
- [Zeiterfassung](../zeiterfassung) — Stunden-Auswertung pro Phase
+44
View File
@@ -0,0 +1,44 @@
---
title: Protokolle & Lieferscheine
linkTitle: Protokolle
weight: 6
toc: true
---
<span class="rapport-status active">In Arbeit</span>
**Einfache Erstellung von Sitzungsprotokollen** mit Beschlüssen und Aufgaben. Briefe und Lieferscheine im gleichen Erscheinungsbild.
## Sitzungsprotokolle
Pro Sitzung:
- Datum, Ort, Teilnehmer (aus Beteiligten-Liste)
- **Traktanden** als nummerierte Liste
- Pro Traktandum: Beschluss, Aufgabe, Verantwortlich, Frist
- Anhänge
PDF-Export mit Bürobriefbogen.
## Briefe
Brief-Editor mit:
- Empfänger aus Kundendatenbank
- Bezugszeile, Anrede, Text, Grussformel
- Briefbogen-Vorlage mit Logo
- PDF-Export
## Lieferscheine
Pro Lieferung:
- Empfänger, Datum, Bezug
- Positionen (Plan-Nummer, Bezeichnung, Anzahl, Massstab)
- Unterschriftenfeld
Konsistentes Erscheinungsbild über alle Dokumenttypen — eine Briefbogen-Vorlage, mehrere Verwendungen.
## Verwandte Module
- [Projekte](../projekte) — Beteiligte als Empfänger
+44
View File
@@ -0,0 +1,44 @@
---
title: Rechnungen & Offerten
linkTitle: Rechnungen
weight: 2
toc: true
---
<span class="rapport-status active">In Arbeit</span>
**QR-Einzahlungsscheine, SIA-Phasen, Akonto-, Teil- und Schlussrechnungen.** Offerten sind in Projekte und Rechnungen konvertierbar. PDF-Export.
## Workflow
1. **Offerte** erstellen — auf Basis SIA 102 oder pauschal
2. Kunde nimmt an → **konvertieren in Projekt + Rechnung**
3. **Akonto-Rechnungen** während der Projektlaufzeit
4. **Schlussrechnung** mit Differenz zum bisher Akonto-bezahlten
## QR-Einzahlungsschein
Schweizer **QR-Rechnung** nach Norm — direkt eingebettet in die PDF.
Ausgelesen aus:
- **Bürodaten** — IBAN, Empfänger-Adresse
- **Kundendaten** — Schuldner-Adresse
- **Rechnungs-Daten** — Betrag, Referenz, Zusatzinformation
## Honorarmodelle
| Modell | Berechnung | Verwendung |
|---|---|---|
| **Stundensatz** | Aus Zeiterfassung × Mitarbeiter-Stundensatz | Kleinaufträge, Beratung |
| **SIA-Phasen** | Bauschätzwert × Honorarsatz × Phasenanteil | Reguläre Architektur-Aufträge |
| **Pauschal** | Fester Betrag | Auf Wunsch des Kunden |
## PDF-Export
Druckfertige Rechnung inkl. QR-Schein. Layout aus dem Büro-Briefbogen (mit Logo). Mehrsprachig DE/FR/IT (geplant).
## Verwandte Module
- [Projekte](../projekte) — Honorarstruktur stammt aus dem Projekt
- [Zeiterfassung](../zeiterfassung) — Stundensatz-Rechnungen
+40
View File
@@ -0,0 +1,40 @@
---
title: Spesen & Bürobudget
linkTitle: Spesen
weight: 5
toc: true
---
<span class="rapport-status active">In Arbeit</span>
**Spesenerfassung mit Belegupload.** Jahresbudget mit Einnahmen und Ausgaben. Internes Rechnungswesen.
## Spesenerfassung
Pro Mitarbeiter:
- Datum, Betrag, Kategorie
- **Beleg-Upload** (PDF, JPG, PNG)
- Projekt-Zuordnung (optional)
- Status (offen / erstattet)
Kategorien: Reise, Verpflegung, Material, Telefon, Sonstiges.
## Jahresbudget
Übersicht über:
- **Einnahmen** — Rechnungsbeträge, sortiert nach Eingang
- **Ausgaben** — Spesen, Bürokosten, Löhne, Sozialabzüge
- **Saldo** pro Monat / Quartal / Jahr
## Auswertung
- Einnahmen pro Kunde / Projekt
- Ausgaben pro Kategorie / Mitarbeiter
- Erfolgsrechnung pro Geschäftsjahr (vereinfacht)
## Verwandte Module
- [Mitarbeiter](../mitarbeiter) — Spesen-Erstattung in der Lohnabrechnung
- [Rechnungen](../rechnungen) — Einnahmen-Quelle
+40
View File
@@ -0,0 +1,40 @@
---
title: System-Tray
linkTitle: System-Tray
weight: 8
toc: true
---
<span class="rapport-status new">Neu in 0.7.0</span>
**Schnellzugriff über die Menüleiste mit Hide-on-Close.** Beim Schliessen läuft Rapport im Hintergrund weiter — Cmd+Q beendet die App vollständig.
## Verhalten
| Aktion | Verhalten |
|---|---|
| **Fenster schliessen** (⌘W oder rotes X) | App läuft im Tray weiter |
| **Cmd+Q** | App wird vollständig beendet |
| **Klick auf Tray-Icon** | Fenster nach vorne, oder zeigen |
| **Rechtsklick auf Tray-Icon** | Menü mit Schnellzugriffen |
## Tray-Menü
- **Rapport zeigen** — Fenster nach vorne
- **Neue Zeiterfassung** — direkt im Zeit-Modul
- **Neue Rechnung** — direkt im Rechnungs-Modul
- **Letzte Projekte** — Quick-Open der letzten 5 Projekte
- **Einstellungen**
- **Rapport beenden**
## Konfiguration
In den Einstellungen:
- **Beim Systemstart starten** (Login-Item) — Standard: aus
- **Beim Schliessen beenden** statt ins Tray — Standard: aus
- **Tray-Icon ausblenden** — App läuft, aber kein Menüleisten-Icon
## Verwandte Module
- [Auto-Updater](../auto-updater) — prüft Updates im Hintergrund
+45
View File
@@ -0,0 +1,45 @@
---
title: Zeiterfassung
linkTitle: Zeiterfassung
weight: 1
toc: true
---
<span class="rapport-status active">In Arbeit</span>
**Tages- und Wochenraster** mit Drag & Drop. Auswertungen pro Mitarbeiter und Projekt. Ferienverwaltung mit Prorata und Jahresabschluss.
## Konzept
Die Zeiterfassung ist das **Kernmodul** von RAPPORT — alle anderen Module (Rechnungen, Auswertungen, Lohnabrechnung) greifen auf die hier erfassten Stunden zu.
## Eingabe
- **Wochenraster** mit den 5 (oder 7) Arbeitstagen
- **Halbstunden-Slots** von 06:00 bis 22:00
- **Klick** oder **Drag** über mehrere Slots
- Jeder Eintrag wird einem **Projekt** zugewiesen (Pflichtfeld)
- Mehrfacheinträge pro Slot möglich (z. B. parallele Telefonate)
## Auswertungen
Pro Mitarbeiter und pro Projekt:
- Geleistete Stunden vs. Soll-Pensum
- Ferienanspruch / -saldo (mit Prorata bei Eintritt unter Jahr)
- Überstunden-Saldo
- Stundenaufschlüsselung nach SIA-Phase pro Projekt
## Jahresabschluss
Am Jahresende:
- Ferien-Restguthaben übertragen oder auszahlen
- Überstunden ausgleichen oder vergüten
- Neues Jahr automatisch initialisieren
## Verwandte Module
- [Rechnungen](../rechnungen) — Stundensatz-Rechnungen ziehen direkt aus der Zeiterfassung
- [Projekte](../projekte) — Stunden-Auswertung pro SIA-Phase
- [Mitarbeiter](../mitarbeiter) — Pensum, Ferienanspruch
+104
View File
@@ -0,0 +1,104 @@
---
title: Lizenz & Mitwirkende
linkTitle: Lizenz
weight: 99
toc: true
---
Lizenz von RAPPORT, Zugehörigkeit zu OpenBureau und Attribution der verwendeten Open-Source-Software.
## RAPPORT
**RAPPORT** — Studio Management Software für Architekturbüros.
Quellcode: [git.kgva.ch/karim/RAPPORT](https://git.kgva.ch/karim/RAPPORT)
Autor: [Karim Gabriele Varano](https://gabrielevarano.ch)
### Lizenz
Lizenziert unter **GNU Affero General Public License v3.0 oder höher** ([AGPL-3.0-or-later](https://www.gnu.org/licenses/agpl-3.0.html)).
> Dies bedeutet im Kern:
>
> - **Freie Nutzung** für jeden Zweck (privat, kommerziell, behördlich)
> - **Freier Zugang** zum Quellcode
> - **Freie Modifikation** und Weitergabe — unter derselben Lizenz
> - **Netzwerk-Nutzung gilt als Weitergabe** — wer eine modifizierte Version als Service anbietet, muss den Quellcode offenlegen
Eigene Builds, eigene Forks, eigene Anpassungen — alles erlaubt, solange Modifikationen wieder unter AGPL-3.0 veröffentlicht werden.
## OpenBureau
RAPPORT ist Teil der **OpenBureau**-Initiative — einer Sammlung freier Software-Werkzeuge für den Architektur-Büro-Alltag.
Die Idee: Bürowissen — Workflows, Konventionen, Vorlagen — sollte nicht in proprietären Tools eingesperrt sein. OpenBureau-Tools setzen die in der Branche etablierten Konventionen (SIA-Normen, QR-Rechnung, Plan-Verwaltung) sauber um und stehen unter freien Lizenzen zur Verfügung.
| Tool | Zweck | Status |
|---|---|---|
| **RAPPORT** | Studio-Management Desktop-App (Zeit, Rechnungen, Projekte) | Pre-Release |
| **Rapport Server** | Selfhost-Stack (Postgres + Auth + Realtime + Storage) | Pre-Release |
| **DOSSIER** | Rhino-8-Plugin für Plan- und Dokumentationsausgabe | Pre-Release |
Mehr unter [gabrielevarano.ch](https://gabrielevarano.ch).
## Verwendete Open-Source-Software
RAPPORT baut auf freier Software auf. Diese Liste nennt die wichtigsten Komponenten und ihre jeweiligen Lizenzen.
### Frontend
| Software | Verwendung | Lizenz |
|---|---|---|
| [React 19](https://react.dev/) | UI-Bibliothek | MIT |
| [Vite](https://vite.dev/) | Build-Tool, Dev-Server | MIT |
| [swissqrbill](https://github.com/schoero/SwissQRBill) | QR-Einzahlungsscheine | MIT |
### Desktop-Wrapper
| Software | Verwendung | Lizenz |
|---|---|---|
| [Tauri 2](https://v2.tauri.app/) | Cross-Platform Desktop-Wrapper | MIT / Apache-2.0 |
| [WebKit](https://webkit.org/) | WebView-Engine (macOS) | LGPL-2.1 / BSD |
### Rapport Server (Selfhost-Stack)
| Software | Verwendung | Lizenz |
|---|---|---|
| [PostgreSQL](https://www.postgresql.org/) | Datenbank | PostgreSQL-Lizenz |
| [GoTrue](https://github.com/supabase/gotrue) | Authentication-Service | MIT |
| [PostgREST](https://postgrest.org/) | REST-API über Postgres-Schema | MIT |
| [Realtime](https://github.com/supabase/realtime) | WebSocket-Sync | Apache-2.0 |
| [Storage](https://github.com/supabase/storage-api) | Object-Storage für Belege & Logos | Apache-2.0 |
| [Kong](https://konghq.com/) | API-Gateway | Apache-2.0 |
| [nginx](https://nginx.org/) | Frontend-Webserver | BSD-2-Clause |
| [Docker Compose](https://docs.docker.com/compose/) | Orchestrierung | Apache-2.0 |
### Diese Website
| Software | Verwendung | Lizenz |
|---|---|---|
| [Hugo](https://gohugo.io/) | Static-Site-Generator | Apache-2.0 |
| [Hextra](https://imfing.github.io/hextra/) | Hugo-Theme (von Xin) | MIT |
| [Inter](https://rsms.me/inter/) | UI-Schrift (Rasmus Andersson) | SIL OFL 1.1 |
| [Playfair Display](https://fonts.google.com/specimen/Playfair+Display) | Headings | SIL OFL 1.1 |
Alle Logos und Marken-Namen gehören ihren jeweiligen Eigentümern.
## Lizenz-Volltexte
Die vollständigen Lizenztexte:
- [AGPL-3.0](https://www.gnu.org/licenses/agpl-3.0.html) — RAPPORT
- [MIT](https://opensource.org/licenses/MIT) — React, Vite, Tauri, Hextra, swissqrbill
- [Apache-2.0](https://opensource.org/licenses/Apache-2.0) — Tauri, Hugo, Supabase
- [SIL OFL 1.1](https://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=OFL) — Inter, Playfair Display
## Mitwirkende
RAPPORT wird derzeit als Ein-Personen-Projekt von [Karim Gabriele Varano](https://gabrielevarano.ch) entwickelt. Beiträge — Code, Issues, Übersetzungen, Vorlagen — sind willkommen.
Wer beitragen möchte → [Gitea Issues](https://git.kgva.ch/karim/RAPPORT/issues).
## Kontakt
Fragen zur Lizenz, kommerzieller Einsatz, Spezialfälle: [Issue auf Gitea](https://git.kgva.ch/karim/RAPPORT/issues) oder via [gabrielevarano.ch](https://gabrielevarano.ch).
+227
View File
@@ -0,0 +1,227 @@
---
title: Rapport Server
linkTitle: Server
weight: 4
toc: true
---
<span class="rapport-status new">Self-Hosting</span>
**Rapport Server** — der vollständige Selfhost-Stack für Rapport. Eigene Daten, eigene Domain, eigener Server. Komplett Open-Source, Docker-Compose, AGPL-3.0.
## Wann brauchst du Rapport Server?
| Szenario | Lösung |
|---|---|
| **Ein Mensch, ein Mac** | Desktop-App reicht — [Installation](../docs/installation) |
| **Mehrere Personen im Studio** | Rapport Server auf einem Mac Mini oder Linux-Server |
| **Verteiltes Team, Home-Office, Mobile-Zugriff** | Rapport Server mit Reverse-Proxy + SSL |
| **Cloud-Hosting bei einem Anbieter** | Rapport Server auf VPS/Hetzner/etc. |
> Die Desktop-App speichert lokal als JSON. Rapport Server bringt **Postgres + Multi-User + Realtime-Sync** — für alle, die zu zweit oder im Team arbeiten.
## Architektur
Rapport Server bündelt sechs Open-Source-Komponenten zu einem stimmigen Stack:
```text
┌──────────────────────────────────────────────────────┐
│ Browser / Desktop-App │
└──────────────┬───────────────────────┬───────────────┘
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ nginx │ │ Kong │ ← API-Gateway
│ (app) │ │ │
└──────────┘ └──────────┘
┌───────────────────┼─────────────────────┐
▼ ▼ ▼
┌────────┐ ┌──────────┐ ┌─────────┐
│ GoTrue │ │PostgREST │ │ Realtime│
│ (Auth) │ │ (API) │ │ (WS) │
└────────┘ └──────────┘ └─────────┘
│ │ │
└───────────────────┼─────────────────────┘
┌─────────────────┐
│ PostgreSQL │
└─────────────────┘
┌─────────────────┐
│ Storage │ ← Belege, Logos
└─────────────────┘
```
| Komponente | Funktion |
|---|---|
| **PostgreSQL** | Datenbank — alle Rapport-Daten |
| **GoTrue** | Authentication — Email/Passwort, JWT-Tokens |
| **PostgREST** | REST-API direkt aus dem Postgres-Schema |
| **Realtime** | WebSocket-Sync für Live-Updates |
| **Storage** | Object-Storage für Belege & Logos |
| **Kong** | API-Gateway, routet alle Calls |
| **app** (nginx) | Liefert das Rapport-Frontend (React-Build) |
Alles in **einem Docker-Compose**. Keine Cloud-Abhängigkeit, keine Telemetrie.
## Voraussetzungen
| OS | Container-Runtime |
|---|---|
| **Linux** (Ubuntu 22.04+, Debian 12+, …) | Docker Engine + Compose v2 |
| **macOS** (Mac Mini etc.) | [Colima](https://github.com/abiosoft/colima) + Docker CLI — voll OSS |
> Auf macOS funktionieren auch OrbStack oder Docker Desktop, beide sind aber proprietär. **Colima** ist die OSS-Alternative und für Selfhost vollkommen ausreichend.
**Plus:** ein DNS-Name für TLS (z. B. `rapport.studio.ch`), und optional ein Reverse-Proxy wie [Nginx Proxy Manager](https://nginxproxymanager.com/) oder [Caddy](https://caddyserver.com/).
## Setup in 5 Schritten
### 1 · Repo klonen
```bash
git clone https://git.kgva.ch/karim/rapport-server.git
cd rapport-server
```
### 2 · `.env` erstellen
```bash
cp .env.example .env
```
In `.env` müssen mindestens diese Werte ersetzt werden:
| Variable | Was |
|---|---|
| `POSTGRES_PASSWORD` | DB-Passwort (≥ 32 Zeichen, zufällig) |
| `JWT_SECRET` | JWT-Signatur (≥ 32 Zeichen, zufällig) |
| `ANON_KEY` / `SERVICE_ROLE_KEY` | aus JWT-Secret abgeleitet |
| `SITE_URL` | Public-URL der Instanz (z. B. `https://app.rapport.studio.ch`) |
Zufallswerte generieren:
```bash
openssl rand -hex 32 # für POSTGRES_PASSWORD und JWT_SECRET
node scripts/generate-keys.mjs # ANON_KEY + SERVICE_ROLE_KEY aus JWT_SECRET
```
### 3 · Migrations holen
Die SQL-Migrations stammen aus dem App-Repo. Einmal initial holen:
```bash
./scripts/sync-migrations.sh
```
Bei späteren Rapport-Updates erneut ausführen, dann `docker compose down && docker compose up -d`.
### 4 · Stack starten
```bash
docker compose up -d
```
Erststart dauert ~1 Minute (Postgres initialisiert sich, Migrations laufen, Container starten).
### 5 · Health-Check
```bash
docker compose ps
```
Alle Container sollten `healthy` zeigen. Frontend ist dann erreichbar unter `http://localhost:8080`.
## Reverse-Proxy + HTTPS
Für Production-Setups mit eigener Domain — **Caddy** ist die einfachste Variante (config-as-code, Let's-Encrypt automatisch):
```caddy
app.rapport.studio.ch {
reverse_proxy localhost:8080
}
api.rapport.studio.ch {
reverse_proxy localhost:8000
}
```
In `.env` dann:
```env
SITE_URL=https://app.rapport.studio.ch
API_EXTERNAL_URL=https://api.rapport.studio.ch
```
Alternativ über **Nginx Proxy Manager** mit Web-UI — siehe [Troubleshooting](../docs/troubleshooting).
## Updates
```bash
git pull
./scripts/sync-migrations.sh # falls neue SQL-Migrationen
docker compose pull # neueste Container-Versionen
docker compose up -d # neu starten
```
Daten bleiben erhalten — das Volume `postgres-data` wird nicht angetastet.
## Backup
### Postgres-Dump
```bash
docker compose exec -T db pg_dumpall -U postgres > backup-$(date +%Y%m%d).sql
```
Empfohlen: per `cron` täglich + auf externe Disk / S3 / Backblaze sichern.
### Storage (Belege, Logos)
```bash
docker compose exec storage tar -czf - /var/lib/storage > storage-$(date +%Y%m%d).tar.gz
```
### Restore
```bash
docker compose down
docker volume rm rapport-server_postgres-data
docker compose up -d db
sleep 10
cat backup-YYYYMMDD.sql | docker compose exec -T db psql -U postgres
docker compose up -d
```
## Bezug Rapport ↔ Rapport Server
| Komponente | Wo | Lizenz |
|---|---|---|
| **Rapport Desktop-App** | [git.kgva.ch/karim/RAPPORT](https://git.kgva.ch/karim/RAPPORT) | AGPL-3.0 |
| **Rapport Server** | [git.kgva.ch/karim/rapport-server](https://git.kgva.ch/karim/rapport-server) | AGPL-3.0 |
Die Desktop-App kann **wahlweise** im Lokal-Modus (JSON, kein Server nötig) oder im Cloud-Modus (gegen Rapport Server) betrieben werden. Wechsel erfolgt im Login-Bildschirm der App.
## Häufige Fragen
### Brauche ich Supabase Cloud?
**Nein.** Rapport Server ist die Selfhost-Variante derselben Komponenten (Postgres + GoTrue + PostgREST + Realtime + Storage), aber komplett auf eigener Infrastruktur. Kein Supabase-Account, keine externe Abhängigkeit.
### Kann ich von der Desktop-App auf Rapport Server migrieren?
Im Login der Desktop-App auf **Cloud** wechseln, Server-URL eingeben, Account erstellen, Daten manuell wieder anlegen (Bürodaten, Mitarbeiter, Kunden). Ein direkter localStorage → Postgres-Import ist [geplant](https://git.kgva.ch/karim/RAPPORT/issues).
### Was kostet das?
Nichts — Rapport Server ist Open-Source (AGPL-3.0). Du brauchst nur einen Server (Mac Mini, NAS, VPS) und ggf. eine Domain.
### Wieviel Server-Resourcen?
Ein Mac Mini M1 (8 GB RAM) reicht problemlos für ein 5-Personen-Studio. Linux-VPS mit 2 GB RAM + 2 CPU reicht auch.
## Quellcode
[git.kgva.ch/karim/rapport-server](https://git.kgva.ch/karim/rapport-server) — Issues, Pull Requests und Forks willkommen.
+103
View File
@@ -0,0 +1,103 @@
# RAPPORT — Hugo site configuration
baseURL: "https://rapport.gabrielevarano.ch/"
title: "RAPPORT"
theme: "hextra"
enableRobotsTXT: true
hasCJKLanguage: false
outputs:
home: [html]
page: [html]
section: [html, rss]
defaultContentLanguage: de
languages:
de:
label: Deutsch
weight: 1
title: RAPPORT
module:
hugoVersion:
extended: true
min: "0.146.0"
markup:
highlight:
noClasses: false
goldmark:
renderer:
unsafe: true
enableInlineShortcodes: true
menu:
main:
- identifier: documentation
name: Dokumentation
pageRef: /docs
weight: 1
- identifier: features
name: Features
pageRef: /features
weight: 2
- identifier: downloads
name: Downloads
pageRef: /downloads
weight: 3
- identifier: server
name: Server
pageRef: /server
weight: 4
- identifier: faq
name: FAQ
pageRef: /faq
weight: 5
- name: Search
weight: 6
params:
type: search
- name: Gitea
weight: 7
url: "https://git.kgva.ch/karim/RAPPORT"
params:
icon: github
params:
description: "Freie Studio Management Software für Architekturbüros — Zeiterfassung, Rechnungen, Projekte, QR-Einzahlungsscheine. Tauri + React. AGPL-3.0."
externalLinkDecoration: true
navbar:
displayTitle: true
displayLogo: false
width: normal
theme:
default: light
displayToggle: true
footer:
enable: true
displayCopyright: true
displayPoweredBy: false
width: normal
page:
width: normal
displayUpdatedDate: false
search:
enable: true
type: flexsearch
flexsearch:
index: content
tokenize: forward
editURL:
enable: false
toc:
displayTags: false
+1
View File
@@ -0,0 +1 @@
copyright: '© 2026 [RAPPORT](https://git.kgva.ch/karim/RAPPORT) · [AGPL-3.0](/lizenz/) · Teil von [OpenBureau](/lizenz/#openbureau)'
+10
View File
@@ -0,0 +1,10 @@
{{ define "main" }}
<div class='hx:mx-auto hx:flex hextra-max-page-width'>
{{ partial "sidebar.html" (dict "context" . "disableSidebar" true) }}
<main id="content" class="hx:w-full hx:break-words hx:min-h-[calc(100vh-var(--navbar-height))] hx:min-w-0 hx:pb-8 hx:pt-8 hx:md:pt-12 hx:pl-[max(env(safe-area-inset-left),1.5rem)] hx:pr-[max(env(safe-area-inset-left),1.5rem)]">
<div class="hx:flex hx:flex-col hx:items-center hx:text-center hextra-home">
{{ .Content }}
</div>
</main>
</div>
{{ end }}
+1
View File
@@ -0,0 +1 @@
<!doctype html><html lang=de dir=ltr><body style='font-family:system-ui,segoe ui,Roboto,Helvetica,Arial,sans-serif,apple color emoji,segoe ui emoji;height:100vh;text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center'><main id=content><style>body{color:#000;background:#fff;margin:0}.hextra-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media(prefers-color-scheme:dark){body{color:#fff;background:#000}.hextra-error-h1{border-right:1px solid rgba(255,255,255,.3)}}</style><h1 class=hextra-error-h1 style='display:inline-block;margin:0 20px 0 0;padding-right:23px;font-size:24px;font-weight:500;vertical-align:top;line-height:49px;font-feature-settings:"rlig" 1,"calt" 1,"ss01" 1,"ss06" 1!important'>404</h1><div style=display:inline-block;text-align:left><h2 style=font-size:14px;font-weight:400;line-height:49px;margin:0>This page could not be found.</h2></div></main></body></html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>RAPPORT Categories</title><link>https://rapport.gabrielevarano.ch/categories/</link><description>Recent content in Categories on RAPPORT</description><generator>Hugo -- gohugo.io</generator><language>de</language><atom:link href="https://rapport.gabrielevarano.ch/categories/index.xml" rel="self" type="application/rss+xml"/></channel></rss>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+756
View File
@@ -0,0 +1,756 @@
/* ─────────────────────────────────────────────────────────────
RAPPORT — Theme-Overrides für Hextra
Warmes Sand/Tan auf hellem Grund (Architektur-Büro-Ästhetik)
───────────────────────────────────────────────────────────── */
@import url('https://fonts.googleapis.com/css2?family=Archivo+Black&family=Inter:wght@300;400;500;600;700&family=Playfair+Display:ital,wght@0,400;0,700;1,400&display=swap');
/* Krungthep — RAPPORT-Display-Font (Mac-System-Font lokal gebundelt) */
@font-face {
font-family: 'Krungthep';
src: url('/fonts/Krungthep.ttf') format('truetype');
font-weight: 400;
font-style: normal;
font-display: swap;
}
/* — Primary-HSL: Tan #b07848 — */
:root {
--primary-hue: 26deg;
--primary-saturation: 42%;
--primary-lightness: 49%;
/* RAPPORT-Palette (Light) */
--rapport-bg: #f7f5f2;
--rapport-surface: #ffffff;
--rapport-surface2: #fbf9f5;
--rapport-dark: #1a1a18;
--rapport-dark2: #2d2d28;
--rapport-accent: #b07848;
--rapport-accent-2: #9a673b;
--rapport-accent-3: #7d5430;
--rapport-text: #1a1a18;
--rapport-text-2: #555550;
--rapport-text-3: #888880;
--rapport-text-4: #aaaaaa;
--rapport-border: #e8e3dc;
--rapport-border-2: #d8d2ca;
}
.dark {
--primary-hue: 26deg;
--primary-saturation: 42%;
--primary-lightness: 56%;
--color-dark: var(--rapport-dark);
}
/* — Body & Backgrounds — */
body {
background: var(--rapport-bg);
color: var(--rapport-text);
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif;
}
.dark body {
background: #181614;
color: #ece9e3;
}
::selection {
background: rgba(176, 120, 72, 0.25);
color: var(--rapport-dark);
}
.dark ::selection {
background: rgba(176, 120, 72, 0.35);
color: #fff;
}
/* — Typografie — Headings serifig, Body monospaced — */
.hextra-toc,
.content,
.prose {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif;
}
h1, h2, h3, h4,
.hextra-home h1, .hextra-home h2, .hextra-home h3,
.hx\:text-2xl, .hx\:text-3xl, .hx\:text-4xl, .hx\:text-5xl, .hx\:text-6xl {
font-family: 'Playfair Display', Georgia, serif !important;
font-weight: 700 !important;
letter-spacing: -0.01em;
}
/* Navbar-Logo: RAPPORT in Krungthep/Archivo Black — nur navbar! */
.hextra-navbar-title,
nav .hextra-navbar-title,
nav .hextra-max-navbar-width .hx\:font-bold {
font-family: Krungthep, 'Archivo Black', sans-serif !important;
letter-spacing: -0.02em !important;
font-weight: 900 !important;
}
/* — Navbar mit hellem Hintergrund & dezenter Border — */
.nav-container {
background: rgba(247, 245, 242, 0.85) !important;
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border-bottom: 1px solid var(--rapport-border) !important;
}
.dark .nav-container {
background: rgba(24, 22, 20, 0.85) !important;
border-bottom: 1px solid #2d2926 !important;
}
.nav-container-blur,
.dark .nav-container-blur {
background: transparent !important;
}
/* — Sidebar — */
aside.sidebar-container,
.sidebar-container {
background: var(--rapport-bg) !important;
border-right: 1px solid var(--rapport-border);
}
.sidebar-container a {
color: var(--rapport-text-2);
}
.sidebar-container a:hover {
color: var(--rapport-accent);
background: var(--rapport-surface);
}
.sidebar-active-item,
.sidebar-container .sidebar-active-item {
background: rgba(176, 120, 72, 0.10) !important;
color: var(--rapport-accent) !important;
border-color: rgba(176, 120, 72, 0.20) !important;
}
.dark aside.sidebar-container,
.dark .sidebar-container {
background: #181614 !important;
border-right: 1px solid #2d2926;
}
/* — Links — */
a {
transition: color 0.15s;
}
.content a,
.prose a {
color: var(--rapport-accent);
text-decoration: none;
}
.content a:hover,
.prose a:hover {
color: var(--rapport-accent-2);
text-decoration: underline;
}
/* Hextra-Card-Links (<cards>-Shortcode) sollen NIE unterstrichen werden */
.hextra-card,
.hextra-card:hover,
.hextra-card:focus,
.hextra-card:active,
.hextra-card *,
.hextra-card:hover * {
text-decoration: none !important;
}
/* Card-Hover-Effekt sauber: nur Border + leichte Schatten, kein Underline */
a.hextra-card:hover,
a.hextra-card:focus {
outline: none;
text-decoration: none !important;
}
/* — RAPPORT Hero-Buttons — eigene Pills, nicht von Hextra abhängig — */
.rapport-hero-actions {
display: flex;
gap: 18px;
align-items: center;
justify-content: center;
flex-wrap: wrap;
margin-top: 8px;
margin-bottom: 8px;
}
.rapport-btn {
display: inline-flex;
align-items: center;
gap: 9px;
border-radius: 999px;
padding: 14px 30px;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif;
font-size: 12px;
font-weight: 500;
letter-spacing: 0.07em;
text-transform: uppercase;
text-decoration: none !important;
cursor: pointer;
transition: transform 0.18s ease, box-shadow 0.18s ease, background 0.18s ease, border-color 0.18s ease;
user-select: none;
white-space: nowrap;
}
/* Primary (Download) — Weiss mit Tiefe */
.rapport-btn-primary {
background: #ffffff;
color: var(--rapport-text) !important;
border: 1px solid var(--rapport-border);
box-shadow:
0 1px 0 rgba(255,255,255,0.9) inset,
0 2px 4px rgba(0,0,0,0.04),
0 8px 20px rgba(0,0,0,0.10),
0 16px 40px rgba(0,0,0,0.08);
}
.rapport-btn-primary:hover {
background: #ffffff;
border-color: var(--rapport-border-2);
color: var(--rapport-accent-2) !important;
transform: translateY(-2px);
box-shadow:
0 1px 0 rgba(255,255,255,0.9) inset,
0 4px 8px rgba(0,0,0,0.06),
0 14px 28px rgba(0,0,0,0.12),
0 24px 56px rgba(176,120,72,0.18);
}
.rapport-btn-primary:active {
transform: translateY(0);
box-shadow:
0 1px 0 rgba(255,255,255,0.9) inset,
0 2px 4px rgba(0,0,0,0.06),
0 4px 12px rgba(0,0,0,0.08);
}
/* Secondary (Quellcode) — Outline */
.rapport-btn-secondary {
background: transparent;
color: var(--rapport-text-2) !important;
border: 1.5px solid var(--rapport-border-2);
box-shadow: 0 2px 6px rgba(0,0,0,0.03);
}
.rapport-btn-secondary:hover {
background: rgba(255,255,255,0.5);
border-color: var(--rapport-text-3);
color: var(--rapport-text) !important;
transform: translateY(-2px);
box-shadow:
0 6px 14px rgba(0,0,0,0.06),
0 12px 28px rgba(0,0,0,0.04);
}
.rapport-btn-secondary:active {
transform: translateY(0);
}
/* Dark-Mode-Varianten */
.dark .rapport-btn-primary {
background: #2a2722;
color: #ece9e3 !important;
border-color: #3a3530;
box-shadow:
0 1px 0 rgba(255,255,255,0.04) inset,
0 2px 4px rgba(0,0,0,0.30),
0 10px 24px rgba(0,0,0,0.45);
}
.dark .rapport-btn-primary:hover {
background: #322e28;
color: var(--rapport-accent) !important;
box-shadow:
0 1px 0 rgba(255,255,255,0.04) inset,
0 4px 8px rgba(0,0,0,0.40),
0 16px 32px rgba(0,0,0,0.50),
0 24px 56px rgba(176,120,72,0.22);
}
.dark .rapport-btn-secondary {
border-color: #3a3530;
color: #b8b2a8 !important;
}
.dark .rapport-btn-secondary:hover {
background: rgba(255,255,255,0.04);
border-color: #6a6258;
color: #ece9e3 !important;
}
/* — Hero-Badge — */
.hextra-badge {
background: var(--rapport-surface) !important;
border: 1px solid var(--rapport-border) !important;
border-radius: 20px !important;
padding: 5px 14px !important;
font-size: 10px !important;
letter-spacing: 0.12em !important;
color: var(--rapport-text-4) !important;
text-transform: uppercase !important;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif !important;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06), 0 1px 3px rgba(0, 0, 0, 0.04);
}
/* — Feature-Cards — */
.hextra-feature-card {
background: var(--rapport-surface) !important;
border: 1px solid var(--rapport-border) !important;
border-radius: 14px !important;
transition: box-shadow 0.2s, transform 0.2s, border-color 0.2s !important;
}
.hextra-feature-card:hover {
border-color: var(--rapport-border-2) !important;
transform: translateY(-2px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.09), 0 2px 8px rgba(0, 0, 0, 0.05) !important;
}
.hextra-feature-card h3 {
font-family: 'Playfair Display', serif !important;
color: var(--rapport-text) !important;
font-weight: 700 !important;
}
.hextra-feature-card p {
color: var(--rapport-text-2) !important;
font-size: 12px !important;
line-height: 1.8 !important;
}
.dark .hextra-feature-card {
background: #211e1a !important;
border-color: #2d2926 !important;
}
.dark .hextra-feature-card h3 {
color: #ece9e3 !important;
}
.dark .hextra-feature-card p {
color: #b8b2a8 !important;
}
/* — Generic Cards (Hextra <cards> shortcode) — */
.hextra-card {
background: var(--rapport-surface);
border: 1px solid var(--rapport-border);
border-radius: 12px;
transition: border-color 0.2s, transform 0.2s;
}
.hextra-card:hover {
border-color: var(--rapport-accent-2);
transform: translateY(-1px);
}
/* — Code-Blocks — */
.hextra-code-block {
background: #ffffff !important;
border: 1px solid var(--rapport-border) !important;
border-radius: 12px !important;
overflow: hidden;
}
.hextra-code-block pre,
.hextra-code-block .highlight,
.hextra-code-block .chroma {
background: transparent !important;
border: none !important;
border-radius: 0 !important;
margin: 0 !important;
}
pre {
background: var(--rapport-surface2);
border: none;
}
code, pre, pre code, kbd, samp, tt {
font-family: ui-monospace, 'SF Mono', Menlo, Monaco, Consolas, 'Liberation Mono', monospace !important;
}
code {
color: var(--rapport-accent-3) !important;
background: rgba(176, 120, 72, 0.10) !important;
padding: 2px 6px;
border-radius: 4px;
font-size: 0.9em;
}
pre code {
color: var(--rapport-text) !important;
background: transparent !important;
padding: 0;
font-size: 0.9em;
}
.dark .hextra-code-block {
background: #1f1c19 !important;
border-color: #2d2926 !important;
}
.dark .hextra-code-block pre,
.dark .hextra-code-block .highlight,
.dark .hextra-code-block .chroma {
background: transparent !important;
border: none !important;
}
.dark code {
color: var(--rapport-accent) !important;
background: rgba(176, 120, 72, 0.15) !important;
}
.dark pre code {
color: #ece9e3 !important;
}
/* — Callouts — */
.hextra-callout {
background: var(--rapport-surface) !important;
border-color: var(--rapport-border-2) !important;
}
/* — Footer — */
.hextra-footer,
footer {
background: var(--rapport-surface2);
border-top: 1px solid var(--rapport-border);
color: var(--rapport-text-3);
}
.hextra-footer a {
color: var(--rapport-text-2);
}
.hextra-footer a:hover {
color: var(--rapport-accent);
}
.dark .hextra-footer,
.dark footer {
background: #15130f;
border-top: 1px solid #2d2926;
color: #888880;
}
/* — TOC — */
.hextra-toc a {
color: var(--rapport-text-3);
}
.hextra-toc a:hover,
.hextra-toc .active {
color: var(--rapport-accent) !important;
}
/* Hextra-Sticky-Bottom-Fades (TOC "Nach oben" + Sidebar-Footer) — */
/* der hardcoded weisse Fade passt nicht zum cremefarbenen RAPPORT-Bg */
.hextra-toc div:has(> #backToTop),
[data-toggle-animation] {
background: var(--rapport-bg) !important;
box-shadow: none !important;
border-top-color: var(--rapport-border) !important;
}
.dark .hextra-toc div:has(> #backToTop),
.dark [data-toggle-animation] {
background: #181614 !important;
border-top-color: #2d2926 !important;
box-shadow: none !important;
}
/* — Search — */
.hextra-search-wrapper input {
background: var(--rapport-surface) !important;
border: 1px solid var(--rapport-border) !important;
color: var(--rapport-text) !important;
}
.hextra-search-wrapper input:focus {
border-color: var(--rapport-accent-2) !important;
}
/* — Tabellen — */
table {
border-color: var(--rapport-border) !important;
}
thead {
background: var(--rapport-surface) !important;
}
th, td {
border-color: var(--rapport-border) !important;
}
/* ─────────────────────────────────────────────────────────────
RAPPORT-LOGO-KARTE (Hero) — Dark Card auf hellem Grund
───────────────────────────────────────────────────────────── */
.rapport-logo-card {
background: var(--rapport-dark);
border: 1px solid var(--rapport-dark2);
border-radius: 999px;
padding: 28px 64px 26px;
display: inline-block;
box-shadow: 6px 0 20px rgba(0, 0, 0, 0.18), 0 6px 16px rgba(0, 0, 0, 0.12);
text-align: center;
margin: 0 auto 32px;
}
.rapport-logo-text {
font-family: 'Krungthep', 'Archivo Black', sans-serif !important;
font-size: 48px;
letter-spacing: -0.02em;
color: #f0ede8;
line-height: 0.95;
font-weight: 400;
}
.rapport-logo-sub {
font-size: 9px;
letter-spacing: 0.15em;
color: #f0ede8;
text-transform: uppercase;
margin-top: 8px;
font-weight: 500;
}
/* — Hero-Meta-Pillen — */
.rapport-meta {
display: flex;
gap: 6px;
align-items: center;
justify-content: center;
flex-wrap: wrap;
margin-top: 32px;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif;
}
.rapport-meta-item {
font-size: 10px;
letter-spacing: 0.08em;
color: var(--rapport-text-4);
text-transform: uppercase;
padding: 0 10px;
border-right: 1px solid var(--rapport-border);
}
.rapport-meta-item:last-child {
border-right: none;
}
/* — Status-Badges (in Arbeit / Geplant / Stabil / Neu) — */
/* Einheitlich: weisse Pill mit dunklem Text — wie Download-Button */
.rapport-status,
.rapport-status.active,
.rapport-status.planned,
.rapport-status.stable,
.rapport-status.new {
display: inline-block;
font-size: 10px;
letter-spacing: 0.12em;
text-transform: uppercase;
padding: 5px 14px;
border-radius: 999px;
margin-bottom: 12px;
font-weight: 600;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif;
background: #ffffff;
color: var(--rapport-text);
border: 1px solid var(--rapport-border);
box-shadow:
0 1px 0 rgba(255,255,255,0.9) inset,
0 1px 2px rgba(0,0,0,0.04),
0 3px 8px rgba(0,0,0,0.06);
}
.dark .rapport-status,
.dark .rapport-status.active,
.dark .rapport-status.planned,
.dark .rapport-status.stable,
.dark .rapport-status.new {
background: #2a2722;
color: #ece9e3;
border-color: #3a3530;
box-shadow:
0 1px 0 rgba(255,255,255,0.04) inset,
0 1px 2px rgba(0,0,0,0.30),
0 4px 10px rgba(0,0,0,0.35);
}
/* — Stack-Bar (am Footer) — */
.rapport-stack-bar {
padding: 20px 0;
border-top: 1px solid var(--rapport-border);
display: flex;
align-items: center;
gap: 20px;
flex-wrap: wrap;
margin-top: 32px;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif;
}
.rapport-stack-label {
font-size: 10px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--rapport-text-4);
flex-shrink: 0;
}
.rapport-stack-items {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.rapport-stack-item {
font-size: 10px;
letter-spacing: 0.06em;
color: var(--rapport-text-3);
background: var(--rapport-surface);
border: 1px solid var(--rapport-border);
border-radius: 6px;
padding: 4px 10px;
}
/* — Reduce default content max-width slightly for monospace lines — */
.content article {
max-width: 100%;
}
/* ─────────────────────────────────────────────────────────────
HOME-HERO — zentriertes Layout wie auf der alten Website
───────────────────────────────────────────────────────────── */
/* Inhalt der home Page zentrieren */
.hextra-home {
align-items: center !important;
text-align: center;
max-width: 100%;
}
/* Subtitle zentriert, schmaler max-width, Playfair Display */
.hextra-home > p,
.hextra-home .not-prose.hx\:text-xl {
font-family: 'Playfair Display', serif !important;
font-size: clamp(16px, 2.2vw, 24px) !important;
font-weight: 400 !important;
color: var(--rapport-text-3) !important;
max-width: 560px !important;
margin-left: auto !important;
margin-right: auto !important;
line-height: 1.55 !important;
text-align: center;
}
/* Buttons immer in einer zentrierten Zeile */
.hextra-home > div.hx\:flex,
.hextra-home > .hx\:flex {
justify-content: center;
align-items: center;
}
/* Hero-Button matches RAPPORT design (dark on cream) */
.hextra-home .hextra-hero-button,
a.hextra-hero-button {
background: var(--rapport-dark);
color: #f0ede8 !important;
border-radius: 20px;
padding: 13px 28px;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Helvetica Neue', sans-serif !important;
font-size: 11px !important;
font-weight: 500 !important;
letter-spacing: 0.07em !important;
text-transform: uppercase !important;
box-shadow: 0 4px 14px rgba(0, 0, 0, 0.20);
transition: all 0.18s;
display: inline-flex;
align-items: center;
gap: 9px;
}
/* Feature-Grid linksbündig (Text in Cards) */
.hextra-home .hextra-feature-grid,
.hextra-home .rapport-stack-bar {
text-align: left;
width: 100%;
}
.hextra-home .hextra-feature-card * {
text-align: left;
}
.rapport-stack-bar {
justify-content: flex-start;
text-align: left;
}
/* Section-Heading & Eyebrow vor dem Feature-Grid zentrieren */
.hextra-home h2,
.hextra-home > div > h2 {
text-align: center;
margin-left: auto;
margin-right: auto;
}
/* RAPPORT-Logo bleibt zentriert */
.hextra-home .rapport-logo-card {
margin-left: auto;
margin-right: auto;
display: block;
}
/* Hero-Badge zentrieren */
.hextra-home .hextra-badge,
.hextra-home > p:has(.hextra-badge) {
margin-left: auto;
margin-right: auto;
display: inline-flex !important;
}
/* Hero-Background-Glow — sanfter Tan-Halo */
body:has(.hextra-home)::before {
content: "";
position: fixed;
top: 5%;
left: 50%;
transform: translateX(-50%);
width: 720px;
height: 720px;
background: radial-gradient(circle, rgba(176, 120, 72, 0.08) 0%, transparent 60%);
pointer-events: none;
z-index: 0;
}
.dark body:has(.hextra-home)::before {
background: radial-gradient(circle, rgba(176, 120, 72, 0.12) 0%, transparent 60%);
}
/* Navbar-Logo — RAPPORT-Schriftzug in Krungthep, etwas grösser */
.hextra-navbar-title,
nav a[href="/"] span,
nav [class*="font-bold"] {
font-family: 'Krungthep', 'Archivo Black', sans-serif !important;
letter-spacing: -0.02em !important;
font-weight: 400 !important;
font-size: 23px !important;
line-height: 1 !important;
}
/* RAPPORT-Meta-Pillen zentriert */
.rapport-meta {
justify-content: center;
text-align: center;
}
+26
View File
@@ -0,0 +1,26 @@
/* Hugo template to derive CSS variables from site and page parameters */
/* Do not remove the following comment. It is used by Hugo to render CSS variables.*/
:root {
--hextra-max-page-width: 80rem;
--hextra-max-content-width: 72rem;
--hextra-max-navbar-width: 80rem;
--hextra-max-footer-width: 80rem;
}
.hextra-max-page-width {
max-width: var(--hextra-max-page-width);
}
.hextra-max-content-width {
max-width: var(--hextra-max-content-width);
}
.hextra-max-navbar-width {
max-width: var(--hextra-max-navbar-width);
}
.hextra-max-footer-width {
max-width: var(--hextra-max-footer-width);
}
File diff suppressed because one or more lines are too long
+490
View File
@@ -0,0 +1,490 @@
// Search functionality using FlexSearch.
// Change shortcut key to cmd+k on Mac, iPad or iPhone.
document.addEventListener("DOMContentLoaded", function () {
if (/iPad|iPhone|Macintosh/.test(navigator.userAgent)) {
// select the kbd element under the .hextra-search-wrapper class
const keys = document.querySelectorAll(".hextra-search-wrapper kbd");
keys.forEach(key => {
key.innerHTML = '<span class="hx:text-xs">⌘</span>K';
});
}
});
// Render the search data as JSON.
//
//
//
//
(function () {
const searchDataURL = '/de.search-data.json';
const resultsFoundTemplate = '%d Ergebnisse gefunden';
const inputElements = document.querySelectorAll('.hextra-search-input');
for (const el of inputElements) {
el.addEventListener('focus', init);
el.addEventListener('keyup', search);
el.addEventListener('keydown', handleKeyDown);
el.addEventListener('input', handleInputChange);
}
const shortcutElements = document.querySelectorAll('.hextra-search-wrapper kbd');
function setShortcutElementsOpacity(opacity) {
shortcutElements.forEach(el => {
el.style.opacity = opacity;
});
}
function handleInputChange(e) {
const opacity = e.target.value.length > 0 ? 0 : 100;
setShortcutElementsOpacity(opacity);
}
// Get the search wrapper, input, and results elements.
function getActiveSearchElement() {
const inputs = Array.from(document.querySelectorAll('.hextra-search-wrapper')).filter(el => el.clientHeight > 0);
if (inputs.length === 1) {
return {
wrapper: inputs[0],
inputElement: inputs[0].querySelector('.hextra-search-input'),
resultsElement: inputs[0].querySelector('.hextra-search-results')
};
}
return undefined;
}
const INPUTS = ['input', 'select', 'button', 'textarea']
// Focus the search input when pressing ctrl+k/cmd+k or /.
document.addEventListener('keydown', function (e) {
const { inputElement } = getActiveSearchElement();
if (!inputElement) return;
const activeElement = document.activeElement;
const tagName = activeElement && activeElement.tagName;
if (
inputElement === activeElement ||
!tagName ||
INPUTS.includes(tagName) ||
(activeElement && activeElement.isContentEditable))
return;
if (
e.key === '/' ||
(e.key === 'k' &&
(e.metaKey /* for Mac */ || /* for non-Mac */ e.ctrlKey))
) {
e.preventDefault();
inputElement.focus();
} else if (e.key === 'Escape' && inputElement.value) {
inputElement.blur();
}
});
// Dismiss the search results when clicking outside the search box.
document.addEventListener('mousedown', function (e) {
const { inputElement, resultsElement } = getActiveSearchElement();
if (!inputElement || !resultsElement) return;
if (
e.target !== inputElement &&
e.target !== resultsElement &&
!resultsElement.contains(e.target)
) {
setShortcutElementsOpacity(100);
hideSearchResults();
}
});
// Get the currently active result and its index.
function getActiveResult() {
const { resultsElement } = getActiveSearchElement();
if (!resultsElement) return { result: undefined, index: -1 };
const result = resultsElement.querySelector('.hextra-search-active');
if (!result) return { result: undefined, index: -1 };
const index = parseInt(result.dataset.index, 10);
return { result, index };
}
// Set the active result by index.
function setActiveResult(index) {
const { resultsElement } = getActiveSearchElement();
if (!resultsElement) return;
const { result: activeResult } = getActiveResult();
activeResult && activeResult.classList.remove('hextra-search-active');
const result = resultsElement.querySelector(`[data-index="${index}"]`);
if (result) {
result.classList.add('hextra-search-active');
result.focus();
}
}
// Get the number of search results from the DOM.
function getResultsLength() {
const { resultsElement } = getActiveSearchElement();
if (!resultsElement) return 0;
return resultsElement.dataset.count;
}
// Finish the search by hiding the results and clearing the input.
function finishSearch() {
const { inputElement } = getActiveSearchElement();
if (!inputElement) return;
hideSearchResults();
inputElement.value = '';
inputElement.blur();
}
function hideSearchResults() {
const { resultsElement } = getActiveSearchElement();
if (!resultsElement) return;
resultsElement.classList.add('hx:hidden');
}
// Handle keyboard events.
function handleKeyDown(e) {
const { inputElement } = getActiveSearchElement();
if (!inputElement) return;
const resultsLength = getResultsLength();
const { result: activeResult, index: activeIndex } = getActiveResult();
switch (e.key) {
case 'ArrowUp':
e.preventDefault();
if (activeIndex > 0) setActiveResult(activeIndex - 1);
break;
case 'ArrowDown':
e.preventDefault();
if (activeIndex + 1 < resultsLength) setActiveResult(activeIndex + 1);
break;
case 'Enter':
e.preventDefault();
if (activeResult) {
activeResult.click();
}
finishSearch();
case 'Escape':
e.preventDefault();
hideSearchResults();
// Clear the input when pressing escape
inputElement.value = '';
inputElement.dispatchEvent(new Event('input'));
// Remove focus from the input
inputElement.blur();
break;
}
}
// Initializes the search.
function init(e) {
e.target.removeEventListener('focus', init);
if (!(window.pageIndex && window.sectionIndex)) {
preloadIndex();
}
}
/**
* Preloads the search index by fetching data and adding it to the FlexSearch index.
* @returns {Promise<void>} A promise that resolves when the index is preloaded.
*/
async function preloadIndex() {
const tokenize = 'forward';
// https://github.com/TryGhost/Ghost/pull/21148
const regex = new RegExp(
`[\u{4E00}-\u{9FFF}\u{3040}-\u{309F}\u{30A0}-\u{30FF}\u{AC00}-\u{D7A3}\u{3400}-\u{4DBF}\u{20000}-\u{2A6DF}\u{2A700}-\u{2B73F}\u{2B740}-\u{2B81F}\u{2B820}-\u{2CEAF}\u{2CEB0}-\u{2EBEF}\u{30000}-\u{3134F}\u{31350}-\u{323AF}\u{2EBF0}-\u{2EE5F}\u{F900}-\u{FAFF}\u{2F800}-\u{2FA1F}]|[0-9A-Za-zа\u00C0-\u017F\u0400-\u04FF\u0600-\u06FF\u0980-\u09FF\u1E00-\u1EFF\u0590-\u05FF]+`,
'mug'
);
const encode = (str) => { return ('' + str).toLowerCase().match(regex) ?? []; }
window.pageIndex = new FlexSearch.Document({
tokenize,
encode,
cache: 100,
document: {
id: 'id',
store: ['title', 'crumb'],
index: "content"
}
});
window.sectionIndex = new FlexSearch.Document({
tokenize,
encode,
cache: 100,
document: {
id: 'id',
store: ['title', 'content', 'url', 'display', 'crumb'],
index: "content",
tag: [{
field: "pageId"
}]
}
});
const resp = await fetch(searchDataURL);
const data = await resp.json();
let pageId = 0;
for (const route in data) {
let pageContent = '';
++pageId;
const urlParts = route.split('/').filter(x => x != "" && !x.startsWith('#'));
let crumb = '';
let searchUrl = '/';
for (let i = 0; i < urlParts.length; i++) {
const urlPart = urlParts[i];
searchUrl += urlPart + '/'
const crumbData = data[searchUrl];
if (!crumbData) {
console.debug('Excluded page', searchUrl, '- will not be included for search result breadcrumb for', route);
continue;
}
let title = data[searchUrl].title;
if (title == "_index") {
title = urlPart.split("-").map(x => x).join(" ");
}
crumb += title;
if (i < urlParts.length - 1) {
crumb += ' > ';
}
}
for (const heading in data[route].data) {
const [hash, text] = heading.split('#');
const url = route.trimEnd('/') + (hash ? '#' + hash : '');
const title = text || data[route].title;
const content = data[route].data[heading] || '';
const paragraphs = content.split('\n').filter(Boolean);
sectionIndex.add({
id: url,
url,
title,
crumb,
pageId: `page_${pageId}`,
content: title,
...(paragraphs[0] && { display: paragraphs[0] })
});
for (let i = 0; i < paragraphs.length; i++) {
sectionIndex.add({
id: `${url}_${i}`,
url,
title,
crumb,
pageId: `page_${pageId}`,
content: paragraphs[i]
});
}
pageContent += ` ${title} ${content}`;
}
window.pageIndex.add({
id: pageId,
title: data[route].title,
crumb,
content: pageContent
});
}
}
/**
* Performs a search based on the provided query and displays the results.
* @param {Event} e - The event object.
*/
function search(e) {
const query = e.target.value;
if (!e.target.value) {
hideSearchResults();
return;
}
const { resultsElement } = getActiveSearchElement();
while (resultsElement.firstChild) {
resultsElement.removeChild(resultsElement.firstChild);
}
resultsElement.classList.remove('hx:hidden');
// Configurable search limits with sensible defaults
const maxPageResults = parseInt('20', 10);
const maxSectionResults = parseInt('10', 10);
const pageResults = window.pageIndex.search(query, maxPageResults, { enrich: true, suggest: true })[0]?.result || [];
const results = [];
const pageTitleMatches = {};
for (let i = 0; i < pageResults.length; i++) {
const result = pageResults[i];
pageTitleMatches[i] = 0;
const sectionResults = window.sectionIndex.search(query,
{ enrich: true, suggest: true, tag: { 'pageId': `page_${result.id}` } })[0]?.result || [];
let isFirstItemOfPage = true
const occurred = {}
const nResults = Math.min(sectionResults.length, maxSectionResults);
for (let j = 0; j < nResults; j++) {
const { doc } = sectionResults[j]
const isMatchingTitle = doc.display !== undefined
if (isMatchingTitle) {
pageTitleMatches[i]++
}
const { url, title } = doc
const content = doc.display || doc.content
if (occurred[url + '@' + content]) continue
occurred[url + '@' + content] = true
results.push({
_page_rk: i,
_section_rk: j,
route: url,
prefix: isFirstItemOfPage ? result.doc.crumb : undefined,
children: { title, content }
})
isFirstItemOfPage = false
}
}
const sortedResults = results
.sort((a, b) => {
// Sort by number of matches in the title.
if (a._page_rk === b._page_rk) {
return a._section_rk - b._section_rk
}
if (pageTitleMatches[a._page_rk] !== pageTitleMatches[b._page_rk]) {
return pageTitleMatches[b._page_rk] - pageTitleMatches[a._page_rk]
}
return a._page_rk - b._page_rk
})
.map(res => ({
id: `${res._page_rk}_${res._section_rk}`,
route: res.route,
prefix: res.prefix,
children: res.children
}));
displayResults(sortedResults, query);
}
/**
* Displays the search results on the page.
*
* @param {Array} results - The array of search results.
* @param {string} query - The search query.
*/
function displayResults(results, query) {
const { resultsElement } = getActiveSearchElement();
if (!resultsElement) return;
if (!results.length) {
resultsElement.innerHTML = `<span class="hextra-search-no-result">Keine Ergebnisse gefunden.</span>`;
// Announce no results to screen readers
const wrapper = resultsElement.closest('.hextra-search-wrapper');
const statusEl = wrapper ? wrapper.querySelector('.hextra-search-status') : null;
if (statusEl) {
statusEl.textContent = 'Keine Ergebnisse gefunden.';
}
return;
}
// Append text with highlighted matches using safe text nodes.
function appendHighlightedText(container, text, query) {
if (!text) return;
if (!query) {
container.textContent = text;
return;
}
const escapedQuery = query.replace(/[-\\^$*+?.()|[\]{}]/g, '\\$&');
if (!escapedQuery) {
container.textContent = text;
return;
}
const regex = new RegExp(escapedQuery, 'gi');
let lastIndex = 0;
let match;
while ((match = regex.exec(text)) !== null) {
if (match.index > lastIndex) {
container.appendChild(document.createTextNode(text.slice(lastIndex, match.index)));
}
const span = document.createElement('span');
span.className = 'hextra-search-match';
span.textContent = match[0];
container.appendChild(span);
lastIndex = match.index + match[0].length;
}
if (lastIndex < text.length) {
container.appendChild(document.createTextNode(text.slice(lastIndex)));
}
}
function handleMouseMove(e) {
const target = e.target.closest('a');
if (target) {
const active = resultsElement.querySelector('a.hextra-search-active');
if (active) {
active.classList.remove('hextra-search-active');
}
target.classList.add('hextra-search-active');
}
}
const fragment = document.createDocumentFragment();
for (let i = 0; i < results.length; i++) {
const result = results[i];
if (result.prefix) {
const prefix = document.createElement('div');
prefix.className = 'hextra-search-prefix';
prefix.textContent = result.prefix;
fragment.appendChild(prefix);
}
const li = document.createElement('li');
const link = document.createElement('a');
link.dataset.index = i;
link.href = result.route;
if (i === 0) {
link.classList.add('hextra-search-active');
}
const title = document.createElement('div');
title.className = 'hextra-search-title';
appendHighlightedText(title, result.children.title, query);
link.appendChild(title);
if (result.children.content) {
const excerpt = document.createElement('div');
excerpt.className = 'hextra-search-excerpt';
appendHighlightedText(excerpt, result.children.content, query);
link.appendChild(excerpt);
}
li.appendChild(link);
li.addEventListener('mousemove', handleMouseMove);
li.addEventListener('keydown', handleKeyDown);
link.addEventListener('click', finishSearch);
fragment.appendChild(li);
}
resultsElement.appendChild(fragment);
resultsElement.dataset.count = results.length;
// Announce results count to screen readers
const wrapper = resultsElement.closest('.hextra-search-wrapper');
const statusEl = wrapper ? wrapper.querySelector('.hextra-search-status') : null;
if (statusEl) {
statusEl.textContent = results.length > 0
? resultsFoundTemplate.replace('%d', results.length.toString())
: 'Keine Ergebnisse gefunden.';
}
}
})();
@@ -0,0 +1,2 @@
document.addEventListener("DOMContentLoaded",function(){if(/iPad|iPhone|Macintosh/.test(navigator.userAgent)){const e=document.querySelectorAll(".hextra-search-wrapper kbd");e.forEach(e=>{e.innerHTML='<span class="hx:text-xs">⌘</span>K'})}}),function(){const f="/de.search-data.json",h="%d Ergebnisse gefunden",c=document.querySelectorAll(".hextra-search-input");for(const e of c)e.addEventListener("focus",o),e.addEventListener("keyup",g),e.addEventListener("keydown",i),e.addEventListener("input",m);const u=document.querySelectorAll(".hextra-search-wrapper kbd");function n(e){u.forEach(t=>{t.style.opacity=e})}function m(e){const t=e.target.value.length>0?0:100;n(t)}function e(){const e=Array.from(document.querySelectorAll(".hextra-search-wrapper")).filter(e=>e.clientHeight>0);return e.length===1?{wrapper:e[0],inputElement:e[0].querySelector(".hextra-search-input"),resultsElement:e[0].querySelector(".hextra-search-results")}:0[0]}const l=["input","select","button","textarea"];document.addEventListener("keydown",function(t){const{inputElement:n}=e();if(!n)return;const s=document.activeElement,o=s&&s.tagName;if(n===s||!o||l.includes(o)||s&&s.isContentEditable)return;t.key==="/"||t.key==="k"&&(t.metaKey||t.ctrlKey)?(t.preventDefault(),n.focus()):t.key==="Escape"&&n.value&&n.blur()}),document.addEventListener("mousedown",function(s){const{inputElement:i,resultsElement:o}=e();if(!i||!o)return;s.target!==i&&s.target!==o&&!o.contains(s.target)&&(n(100),t())});function s(){const{resultsElement:n}=e();if(!n)return{result:0[0],index:-1};const t=n.querySelector(".hextra-search-active");if(!t)return{result:0[0],index:-1};const s=parseInt(t.dataset.index,10);return{result:t,index:s}}function r(t){const{resultsElement:o}=e();if(!o)return;const{result:i}=s();i&&i.classList.remove("hextra-search-active");const n=o.querySelector(`[data-index="${t}"]`);n&&(n.classList.add("hextra-search-active"),n.focus())}function d(){const{resultsElement:t}=e();return t?t.dataset.count:0}function a(){const{inputElement:n}=e();if(!n)return;t(),n.value="",n.blur()}function t(){const{resultsElement:t}=e();if(!t)return;t.classList.add("hx:hidden")}function i(n){const{inputElement:o}=e();if(!o)return;const{result:c,index:i}=s(),l=d();switch(n.key){case"ArrowUp":n.preventDefault(),i>0&&r(i-1);break;case"ArrowDown":n.preventDefault(),i+1<l&&r(i+1);break;case"Enter":n.preventDefault(),c&&c.click(),a();case"Escape":n.preventDefault(),t(),o.value="",o.dispatchEvent(new Event("input")),o.blur();break}}function o(e){e.target.removeEventListener("focus",o),window.pageIndex&&window.sectionIndex||p()}async function p(){const n="forward",o=new RegExp(`[一-鿿぀-ゟ゠-ヿ가-힣㐀-䶿𠀀-𪛟𪜀-𫜿𫝀-𫠟𫠠-𬺯𬺰-𮯯𰀀-𱍏𱍐-𲎯𮯰-𮹟豈-﫿丽-𯨟]|[0-9A-Za-zа-яÀ-ſЀ-ӿ؀-ۿঀ-৿Ḁ-ỿ֐-׿]+`,"mug"),s=e=>(""+e).toLowerCase().match(o)??[];window.pageIndex=new FlexSearch.Document({tokenize:n,encode:s,cache:100,document:{id:"id",store:["title","crumb"],index:"content"}}),window.sectionIndex=new FlexSearch.Document({tokenize:n,encode:s,cache:100,document:{id:"id",store:["title","content","url","display","crumb"],index:"content",tag:[{field:"pageId"}]}});const i=await fetch(f),e=await i.json();let t=0;for(const n in e){let a="";++t;const i=n.split("/").filter(e=>e!=""&&!e.startsWith("#"));let s="",o="/";for(let t=0;t<i.length;t++){const r=i[t];o+=r+"/";const c=e[o];if(!c){console.debug("Excluded page",o,"- will not be included for search result breadcrumb for",n);continue}let a=e[o].title;a=="_index"&&(a=r.split("-").map(e=>e).join(" ")),s+=a,t<i.length-1&&(s+=" > ")}for(const c in e[n].data){const[l,u]=c.split("#"),o=n.trimEnd("/")+(l?"#"+l:""),i=u||e[n].title,d=e[n].data[c]||"",r=d.split(`
`).filter(Boolean);sectionIndex.add({id:o,url:o,title:i,crumb:s,pageId:`page_${t}`,content:i,...r[0]&&{display:r[0]}});for(let e=0;e<r.length;e++)sectionIndex.add({id:`${o}_${e}`,url:o,title:i,crumb:s,pageId:`page_${t}`,content:r[e]});a+=` ${i} ${d}`}window.pageIndex.add({id:t,title:e[n].title,crumb:s,content:a})}}function g(n){const i=n.target.value;if(!n.target.value){t();return}const{resultsElement:o}=e();for(;o.firstChild;)o.removeChild(o.firstChild);o.classList.remove("hx:hidden");const c=parseInt("20",10),l=parseInt("10",10),a=window.pageIndex.search(i,c,{enrich:!0,suggest:!0})[0]?.result||[],r=[],s={};for(let e=0;e<a.length;e++){const t=a[e];s[e]=0;const n=window.sectionIndex.search(i,{enrich:!0,suggest:!0,tag:{pageId:`page_${t.id}`}})[0]?.result||[];let o=!0;const c={},d=Math.min(n.length,l);for(let i=0;i<d;i++){const{doc:a}=n[i],h=a.display!==0[0];h&&s[e]++;const{url:l,title:m}=a,u=a.display||a.content;if(c[l+"@"+u])continue;c[l+"@"+u]=!0,r.push({_page_rk:e,_section_rk:i,route:l,prefix:o?t.doc.crumb:0[0],children:{title:m,content:u}}),o=!1}}const d=r.sort((e,t)=>e._page_rk===t._page_rk?e._section_rk-t._section_rk:s[e._page_rk]!==s[t._page_rk]?s[t._page_rk]-s[e._page_rk]:e._page_rk-t._page_rk).map(e=>({id:`${e._page_rk}_${e._section_rk}`,route:e.route,prefix:e.prefix,children:e.children}));v(d,i)}function v(t,n){const{resultsElement:s}=e();if(!s)return;if(!t.length){s.innerHTML=`<span class="hextra-search-no-result">Keine Ergebnisse gefunden.</span>`;const e=s.closest(".hextra-search-wrapper"),t=e?e.querySelector(".hextra-search-status"):null;t&&(t.textContent="Keine Ergebnisse gefunden.");return}function r(e,t,n){if(!t)return;if(!n){e.textContent=t;return}const i=n.replace(/[-\\^$*+?.()|[\]{}]/g,"\\$&");if(!i){e.textContent=t;return}const a=new RegExp(i,"gi");let o=0,s;for(;(s=a.exec(t))!==null;){s.index>o&&e.appendChild(document.createTextNode(t.slice(o,s.index)));const n=document.createElement("span");n.className="hextra-search-match",n.textContent=s[0],e.appendChild(n),o=s.index+s[0].length}o<t.length&&e.appendChild(document.createTextNode(t.slice(o)))}function d(e){const t=e.target.closest("a");if(t){const e=s.querySelector("a.hextra-search-active");e&&e.classList.remove("hextra-search-active"),t.classList.add("hextra-search-active")}}const o=document.createDocumentFragment();for(let c=0;c<t.length;c++){const s=t[c];if(s.prefix){const e=document.createElement("div");e.className="hextra-search-prefix",e.textContent=s.prefix,o.appendChild(e)}const l=document.createElement("li"),e=document.createElement("a");e.dataset.index=c,e.href=s.route,c===0&&e.classList.add("hextra-search-active");const u=document.createElement("div");if(u.className="hextra-search-title",r(u,s.children.title,n),e.appendChild(u),s.children.content){const t=document.createElement("div");t.className="hextra-search-excerpt",r(t,s.children.content,n),e.appendChild(t)}l.appendChild(e),l.addEventListener("mousemove",d),l.addEventListener("keydown",i),e.addEventListener("click",a),o.appendChild(l)}s.appendChild(o),s.dataset.count=t.length;const c=s.closest(".hextra-search-wrapper"),l=c?c.querySelector(".hextra-search-status"):null;l&&(l.textContent=t.length>0?h.replace("%d",t.length.toString()):"Keine Ergebnisse gefunden.")}}()
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>RAPPORT Downloads</title><link>https://rapport.gabrielevarano.ch/downloads/</link><description>Recent content in Downloads on RAPPORT</description><generator>Hugo -- gohugo.io</generator><language>de</language><atom:link href="https://rapport.gabrielevarano.ch/downloads/index.xml" rel="self" type="application/rss+xml"/></channel></rss>
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>RAPPORT FAQ</title><link>https://rapport.gabrielevarano.ch/faq/</link><description>Recent content in FAQ on RAPPORT</description><generator>Hugo -- gohugo.io</generator><language>de</language><atom:link href="https://rapport.gabrielevarano.ch/faq/index.xml" rel="self" type="application/rss+xml"/></channel></rss>
Binary file not shown.

After

Width:  |  Height:  |  Size: 340 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 753 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

+13
View File
@@ -0,0 +1,13 @@
<svg
viewBox="0 0 180 180"
fill="#1E1E1E"
width="180"
height="180"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="m 105.50024,22.224647 c -9.59169,-5.537563 -21.40871,-5.537563 -31.000093,0 L 39.054693,42.689119 C 29.463353,48.226675 23.55484,58.460531 23.55484,69.535642 v 40.928918 c 0,11.07542 5.908513,21.3092 15.499853,26.84652 l 35.445453,20.46446 c 9.591313,5.53732 21.408404,5.53732 31.000094,0 l 35.44507,-20.46446 c 9.59131,-5.53732 15.49985,-15.7711 15.49985,-26.84652 V 69.535642 c 0,-11.075111 -5.90854,-21.308967 -15.49985,-26.846523 z M 34.112797,85.737639 c -1.384445,2.397827 -1.384445,5.352099 0,7.749927 l 24.781554,42.922974 c 1.38437,2.39783 3.942853,3.87496 6.711592,3.87496 h 49.563107 c 2.76905,0 5.3273,-1.47713 6.71144,-3.87496 l 24.78194,-42.922974 c 1.38414,-2.397828 1.38414,-5.3521 0,-7.749927 L 121.88049,42.814746 c -1.38414,-2.397828 -3.94239,-3.874964 -6.71144,-3.874964 H 65.605944 c -2.768739,0 -5.327223,1.477059 -6.711592,3.874964 z"
style="stroke-width:0.774993" />
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+349
View File
@@ -0,0 +1,349 @@
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>RAPPORT Features</title><link>https://rapport.gabrielevarano.ch/features/</link><description>Recent content in Features on RAPPORT</description><generator>Hugo -- gohugo.io</generator><language>de</language><atom:link href="https://rapport.gabrielevarano.ch/features/index.xml" rel="self" type="application/rss+xml"/><item><title>Zeiterfassung</title><link>https://rapport.gabrielevarano.ch/features/zeiterfassung/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://rapport.gabrielevarano.ch/features/zeiterfassung/</guid><description>
&lt;p&gt;&lt;span class="rapport-status active"&gt;In Arbeit&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tages- und Wochenraster&lt;/strong&gt; mit Drag &amp;amp; Drop. Auswertungen pro Mitarbeiter und Projekt. Ferienverwaltung mit Prorata und Jahresabschluss.&lt;/p&gt;
&lt;h2&gt;Konzept&lt;span class="hx:absolute hx:-mt-20" id="konzept"&gt;&lt;/span&gt;
&lt;a href="#konzept" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Die Zeiterfassung ist das &lt;strong&gt;Kernmodul&lt;/strong&gt; von RAPPORT — alle anderen Module (Rechnungen, Auswertungen, Lohnabrechnung) greifen auf die hier erfassten Stunden zu.&lt;/p&gt;
&lt;h2&gt;Eingabe&lt;span class="hx:absolute hx:-mt-20" id="eingabe"&gt;&lt;/span&gt;
&lt;a href="#eingabe" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Wochenraster&lt;/strong&gt; mit den 5 (oder 7) Arbeitstagen&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Halbstunden-Slots&lt;/strong&gt; von 06:00 bis 22:00&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Klick&lt;/strong&gt; oder &lt;strong&gt;Drag&lt;/strong&gt; über mehrere Slots&lt;/li&gt;
&lt;li&gt;Jeder Eintrag wird einem &lt;strong&gt;Projekt&lt;/strong&gt; zugewiesen (Pflichtfeld)&lt;/li&gt;
&lt;li&gt;Mehrfacheinträge pro Slot möglich (z. B. parallele Telefonate)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Auswertungen&lt;span class="hx:absolute hx:-mt-20" id="auswertungen"&gt;&lt;/span&gt;
&lt;a href="#auswertungen" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Pro Mitarbeiter und pro Projekt:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Geleistete Stunden vs. Soll-Pensum&lt;/li&gt;
&lt;li&gt;Ferienanspruch / -saldo (mit Prorata bei Eintritt unter Jahr)&lt;/li&gt;
&lt;li&gt;Überstunden-Saldo&lt;/li&gt;
&lt;li&gt;Stundenaufschlüsselung nach SIA-Phase pro Projekt&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Jahresabschluss&lt;span class="hx:absolute hx:-mt-20" id="jahresabschluss"&gt;&lt;/span&gt;
&lt;a href="#jahresabschluss" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Am Jahresende:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ferien-Restguthaben übertragen oder auszahlen&lt;/li&gt;
&lt;li&gt;Überstunden ausgleichen oder vergüten&lt;/li&gt;
&lt;li&gt;Neues Jahr automatisch initialisieren&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Verwandte Module&lt;span class="hx:absolute hx:-mt-20" id="verwandte-module"&gt;&lt;/span&gt;
&lt;a href="#verwandte-module" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="../rechnungen"&gt;Rechnungen&lt;/a&gt; — Stundensatz-Rechnungen ziehen direkt aus der Zeiterfassung&lt;/li&gt;
&lt;li&gt;&lt;a href="../projekte"&gt;Projekte&lt;/a&gt; — Stunden-Auswertung pro SIA-Phase&lt;/li&gt;
&lt;li&gt;&lt;a href="../mitarbeiter"&gt;Mitarbeiter&lt;/a&gt; — Pensum, Ferienanspruch&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Rechnungen &amp; Offerten</title><link>https://rapport.gabrielevarano.ch/features/rechnungen/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://rapport.gabrielevarano.ch/features/rechnungen/</guid><description>
&lt;p&gt;&lt;span class="rapport-status active"&gt;In Arbeit&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;QR-Einzahlungsscheine, SIA-Phasen, Akonto-, Teil- und Schlussrechnungen.&lt;/strong&gt; Offerten sind in Projekte und Rechnungen konvertierbar. PDF-Export.&lt;/p&gt;
&lt;h2&gt;Workflow&lt;span class="hx:absolute hx:-mt-20" id="workflow"&gt;&lt;/span&gt;
&lt;a href="#workflow" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Offerte&lt;/strong&gt; erstellen — auf Basis SIA 102 oder pauschal&lt;/li&gt;
&lt;li&gt;Kunde nimmt an → &lt;strong&gt;konvertieren in Projekt + Rechnung&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Akonto-Rechnungen&lt;/strong&gt; während der Projektlaufzeit&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Schlussrechnung&lt;/strong&gt; mit Differenz zum bisher Akonto-bezahlten&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;QR-Einzahlungsschein&lt;span class="hx:absolute hx:-mt-20" id="qr-einzahlungsschein"&gt;&lt;/span&gt;
&lt;a href="#qr-einzahlungsschein" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Schweizer &lt;strong&gt;QR-Rechnung&lt;/strong&gt; nach Norm — direkt eingebettet in die PDF.&lt;/p&gt;
&lt;p&gt;Ausgelesen aus:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bürodaten&lt;/strong&gt; — IBAN, Empfänger-Adresse&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kundendaten&lt;/strong&gt; — Schuldner-Adresse&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rechnungs-Daten&lt;/strong&gt; — Betrag, Referenz, Zusatzinformation&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Honorarmodelle&lt;span class="hx:absolute hx:-mt-20" id="honorarmodelle"&gt;&lt;/span&gt;
&lt;a href="#honorarmodelle" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Modell&lt;/th&gt;
&lt;th&gt;Berechnung&lt;/th&gt;
&lt;th&gt;Verwendung&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Stundensatz&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Aus Zeiterfassung × Mitarbeiter-Stundensatz&lt;/td&gt;
&lt;td&gt;Kleinaufträge, Beratung&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;SIA-Phasen&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bauschätzwert × Honorarsatz × Phasenanteil&lt;/td&gt;
&lt;td&gt;Reguläre Architektur-Aufträge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pauschal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fester Betrag&lt;/td&gt;
&lt;td&gt;Auf Wunsch des Kunden&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;PDF-Export&lt;span class="hx:absolute hx:-mt-20" id="pdf-export"&gt;&lt;/span&gt;
&lt;a href="#pdf-export" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Druckfertige Rechnung inkl. QR-Schein. Layout aus dem Büro-Briefbogen (mit Logo). Mehrsprachig DE/FR/IT (geplant).&lt;/p&gt;
&lt;h2&gt;Verwandte Module&lt;span class="hx:absolute hx:-mt-20" id="verwandte-module"&gt;&lt;/span&gt;
&lt;a href="#verwandte-module" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="../projekte"&gt;Projekte&lt;/a&gt; — Honorarstruktur stammt aus dem Projekt&lt;/li&gt;
&lt;li&gt;&lt;a href="../zeiterfassung"&gt;Zeiterfassung&lt;/a&gt; — Stundensatz-Rechnungen&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Projekt- &amp; Kundenverwaltung</title><link>https://rapport.gabrielevarano.ch/features/projekte/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://rapport.gabrielevarano.ch/features/projekte/</guid><description>
&lt;p&gt;&lt;span class="rapport-status active"&gt;In Arbeit&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Projekte nach SIA 102&lt;/strong&gt; mit Budget, Phasen und Beteiligten. Erstellung aus einer Offerte mit Verknüpfung zu Zeiterfassung und Rechnungen.&lt;/p&gt;
&lt;h2&gt;Projektstruktur&lt;span class="hx:absolute hx:-mt-20" id="projektstruktur"&gt;&lt;/span&gt;
&lt;a href="#projektstruktur" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Jedes Projekt besitzt:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Stammdaten&lt;/strong&gt; — Nummer, Bezeichnung, Standort, Bauschätzwert&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kunde&lt;/strong&gt; — verknüpft mit Kundendatenbank&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Beteiligte&lt;/strong&gt; — Bauleitung, Fachplaner, Behörden&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Phasen&lt;/strong&gt; — SIA 102 (Vorprojekt, Bauprojekt, Ausschreibung, …)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Budget&lt;/strong&gt; — Gesamthonorar, pro Phase aufgeteilt&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;SIA 102&lt;span class="hx:absolute hx:-mt-20" id="sia-102"&gt;&lt;/span&gt;
&lt;a href="#sia-102" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Standard-Phasenverteilung wird vorgeschlagen, kann pro Projekt überschrieben werden.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;Anteil (Standard)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;31 — Vorprojekt&lt;/td&gt;
&lt;td&gt;9 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32 — Bauprojekt&lt;/td&gt;
&lt;td&gt;21 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;33 — Bewilligung&lt;/td&gt;
&lt;td&gt;3 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;41 — Ausschreibung&lt;/td&gt;
&lt;td&gt;18 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;51 — Ausführung&lt;/td&gt;
&lt;td&gt;38 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;52 — Inbetriebnahme&lt;/td&gt;
&lt;td&gt;6 %&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;53 — Abschluss&lt;/td&gt;
&lt;td&gt;5 %&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Kundendatenbank&lt;span class="hx:absolute hx:-mt-20" id="kundendatenbank"&gt;&lt;/span&gt;
&lt;a href="#kundendatenbank" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Adresse, Ansprechperson, Telefon, E-Mail&lt;/li&gt;
&lt;li&gt;Honorartyp (Stundensatz / SIA / Pauschal)&lt;/li&gt;
&lt;li&gt;Verknüpfung zu allen Projekten und Rechnungen des Kunden&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Auswertung&lt;span class="hx:absolute hx:-mt-20" id="auswertung"&gt;&lt;/span&gt;
&lt;a href="#auswertung" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Pro Projekt:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Geleistete Stunden vs. Budget&lt;/li&gt;
&lt;li&gt;Honorar-Saldo (verrechnet / Akonto / offen)&lt;/li&gt;
&lt;li&gt;Phasen-Fortschritt&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Verwandte Module&lt;span class="hx:absolute hx:-mt-20" id="verwandte-module"&gt;&lt;/span&gt;
&lt;a href="#verwandte-module" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="../rechnungen"&gt;Rechnungen&lt;/a&gt; — Offerte → Projekt&lt;/li&gt;
&lt;li&gt;&lt;a href="../zeiterfassung"&gt;Zeiterfassung&lt;/a&gt; — Stunden-Auswertung pro Phase&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Mitarbeiter</title><link>https://rapport.gabrielevarano.ch/features/mitarbeiter/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://rapport.gabrielevarano.ch/features/mitarbeiter/</guid><description>
&lt;p&gt;&lt;span class="rapport-status active"&gt;In Arbeit&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ferienverwaltung, interne Stunden / Absenzen und Lohnabrechnung.&lt;/strong&gt; Jahresabschluss mit Überstundenausgleich.&lt;/p&gt;
&lt;h2&gt;Stammdaten&lt;span class="hx:absolute hx:-mt-20" id="stammdaten"&gt;&lt;/span&gt;
&lt;a href="#stammdaten" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Pro Mitarbeiter:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Name, Eintrittsdatum, Pensum (%)&lt;/li&gt;
&lt;li&gt;Stundensatz (intern, für Rechnungen)&lt;/li&gt;
&lt;li&gt;Ferienanspruch (Tage / Jahr)&lt;/li&gt;
&lt;li&gt;Lohn (monatlich, brutto)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Ferienverwaltung&lt;span class="hx:absolute hx:-mt-20" id="ferienverwaltung"&gt;&lt;/span&gt;
&lt;a href="#ferienverwaltung" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Prorata-Berechnung&lt;/strong&gt; bei Eintritt unter Jahr&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ferien-Saldo&lt;/strong&gt; in Tagen (live)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Halbtage&lt;/strong&gt; unterstützt&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Übertrag&lt;/strong&gt; ins Folgejahr oder Auszahlung&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Absenzen&lt;span class="hx:absolute hx:-mt-20" id="absenzen"&gt;&lt;/span&gt;
&lt;a href="#absenzen" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Krankheit, Militär, Mutterschaft, unbezahlter Urlaub — getrennt erfasst, mit Auswertung pro Mitarbeiter.&lt;/p&gt;
&lt;h2&gt;Lohnabrechnung&lt;span class="hx:absolute hx:-mt-20" id="lohnabrechnung"&gt;&lt;/span&gt;
&lt;a href="#lohnabrechnung" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Monatliche Abrechnung mit:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Grundlohn (basierend auf Pensum)&lt;/li&gt;
&lt;li&gt;Überstunden-Vergütung&lt;/li&gt;
&lt;li&gt;Spesen-Erstattung&lt;/li&gt;
&lt;li&gt;Sozialabzüge (AHV, ALV, Pensionskasse)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PDF-Export pro Mitarbeiter.&lt;/p&gt;
&lt;h2&gt;Jahresabschluss&lt;span class="hx:absolute hx:-mt-20" id="jahresabschluss"&gt;&lt;/span&gt;
&lt;a href="#jahresabschluss" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Ferien-Restguthaben übertragen oder auszahlen&lt;/li&gt;
&lt;li&gt;Überstunden ausgleichen oder vergüten&lt;/li&gt;
&lt;li&gt;Lohnausweis vorbereiten (Export)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Verwandte Module&lt;span class="hx:absolute hx:-mt-20" id="verwandte-module"&gt;&lt;/span&gt;
&lt;a href="#verwandte-module" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="../zeiterfassung"&gt;Zeiterfassung&lt;/a&gt; — Pensum-Soll vs. Stunden-Ist&lt;/li&gt;
&lt;li&gt;&lt;a href="../spesen"&gt;Spesen&lt;/a&gt; — Spesen-Erstattung in der Lohnabrechnung&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Spesen &amp; Bürobudget</title><link>https://rapport.gabrielevarano.ch/features/spesen/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://rapport.gabrielevarano.ch/features/spesen/</guid><description>
&lt;p&gt;&lt;span class="rapport-status active"&gt;In Arbeit&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Spesenerfassung mit Belegupload.&lt;/strong&gt; Jahresbudget mit Einnahmen und Ausgaben. Internes Rechnungswesen.&lt;/p&gt;
&lt;h2&gt;Spesenerfassung&lt;span class="hx:absolute hx:-mt-20" id="spesenerfassung"&gt;&lt;/span&gt;
&lt;a href="#spesenerfassung" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Pro Mitarbeiter:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Datum, Betrag, Kategorie&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Beleg-Upload&lt;/strong&gt; (PDF, JPG, PNG)&lt;/li&gt;
&lt;li&gt;Projekt-Zuordnung (optional)&lt;/li&gt;
&lt;li&gt;Status (offen / erstattet)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Kategorien: Reise, Verpflegung, Material, Telefon, Sonstiges.&lt;/p&gt;
&lt;h2&gt;Jahresbudget&lt;span class="hx:absolute hx:-mt-20" id="jahresbudget"&gt;&lt;/span&gt;
&lt;a href="#jahresbudget" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Übersicht über:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Einnahmen&lt;/strong&gt; — Rechnungsbeträge, sortiert nach Eingang&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ausgaben&lt;/strong&gt; — Spesen, Bürokosten, Löhne, Sozialabzüge&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Saldo&lt;/strong&gt; pro Monat / Quartal / Jahr&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Auswertung&lt;span class="hx:absolute hx:-mt-20" id="auswertung"&gt;&lt;/span&gt;
&lt;a href="#auswertung" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Einnahmen pro Kunde / Projekt&lt;/li&gt;
&lt;li&gt;Ausgaben pro Kategorie / Mitarbeiter&lt;/li&gt;
&lt;li&gt;Erfolgsrechnung pro Geschäftsjahr (vereinfacht)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Verwandte Module&lt;span class="hx:absolute hx:-mt-20" id="verwandte-module"&gt;&lt;/span&gt;
&lt;a href="#verwandte-module" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="../mitarbeiter"&gt;Mitarbeiter&lt;/a&gt; — Spesen-Erstattung in der Lohnabrechnung&lt;/li&gt;
&lt;li&gt;&lt;a href="../rechnungen"&gt;Rechnungen&lt;/a&gt; — Einnahmen-Quelle&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Protokolle &amp; Lieferscheine</title><link>https://rapport.gabrielevarano.ch/features/protokolle/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://rapport.gabrielevarano.ch/features/protokolle/</guid><description>
&lt;p&gt;&lt;span class="rapport-status active"&gt;In Arbeit&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Einfache Erstellung von Sitzungsprotokollen&lt;/strong&gt; mit Beschlüssen und Aufgaben. Briefe und Lieferscheine im gleichen Erscheinungsbild.&lt;/p&gt;
&lt;h2&gt;Sitzungsprotokolle&lt;span class="hx:absolute hx:-mt-20" id="sitzungsprotokolle"&gt;&lt;/span&gt;
&lt;a href="#sitzungsprotokolle" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Pro Sitzung:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Datum, Ort, Teilnehmer (aus Beteiligten-Liste)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Traktanden&lt;/strong&gt; als nummerierte Liste&lt;/li&gt;
&lt;li&gt;Pro Traktandum: Beschluss, Aufgabe, Verantwortlich, Frist&lt;/li&gt;
&lt;li&gt;Anhänge&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PDF-Export mit Bürobriefbogen.&lt;/p&gt;
&lt;h2&gt;Briefe&lt;span class="hx:absolute hx:-mt-20" id="briefe"&gt;&lt;/span&gt;
&lt;a href="#briefe" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Brief-Editor mit:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Empfänger aus Kundendatenbank&lt;/li&gt;
&lt;li&gt;Bezugszeile, Anrede, Text, Grussformel&lt;/li&gt;
&lt;li&gt;Briefbogen-Vorlage mit Logo&lt;/li&gt;
&lt;li&gt;PDF-Export&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Lieferscheine&lt;span class="hx:absolute hx:-mt-20" id="lieferscheine"&gt;&lt;/span&gt;
&lt;a href="#lieferscheine" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Pro Lieferung:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Empfänger, Datum, Bezug&lt;/li&gt;
&lt;li&gt;Positionen (Plan-Nummer, Bezeichnung, Anzahl, Massstab)&lt;/li&gt;
&lt;li&gt;Unterschriftenfeld&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Konsistentes Erscheinungsbild über alle Dokumenttypen — eine Briefbogen-Vorlage, mehrere Verwendungen.&lt;/p&gt;
&lt;h2&gt;Verwandte Module&lt;span class="hx:absolute hx:-mt-20" id="verwandte-module"&gt;&lt;/span&gt;
&lt;a href="#verwandte-module" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="../projekte"&gt;Projekte&lt;/a&gt; — Beteiligte als Empfänger&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Auto-Updater</title><link>https://rapport.gabrielevarano.ch/features/auto-updater/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://rapport.gabrielevarano.ch/features/auto-updater/</guid><description>
&lt;p&gt;&lt;span class="rapport-status new"&gt;Neu in 0.7.0&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Rapport prüft beim Start automatisch auf neue Versionen&lt;/strong&gt; und installiert Updates signiert über Tauri. Einzelne Versionen können übersprungen werden.&lt;/p&gt;
&lt;h2&gt;Funktionsweise&lt;span class="hx:absolute hx:-mt-20" id="funktionsweise"&gt;&lt;/span&gt;
&lt;a href="#funktionsweise" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;Beim App-Start:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Abfrage gegen &lt;code&gt;https://git.kgva.ch/karim/RAPPORT/releases/latest.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Versionsvergleich mit lokaler &lt;code&gt;version&lt;/code&gt; im Tauri-Bundle&lt;/li&gt;
&lt;li&gt;Bei neuer Version → Update-Dialog&lt;/li&gt;
&lt;li&gt;Bei Bestätigung → Download + Signaturprüfung + Installation + Neustart&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Sicherheit&lt;span class="hx:absolute hx:-mt-20" id="sicherheit"&gt;&lt;/span&gt;
&lt;a href="#sicherheit" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Updates werden mit dem &lt;strong&gt;Tauri-Updater-Schlüssel&lt;/strong&gt; signiert&lt;/li&gt;
&lt;li&gt;Manipulierte Downloads werden abgelehnt&lt;/li&gt;
&lt;li&gt;Quellcode und Build sind reproduzierbar (Gitea CI, geplant)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Optionen&lt;span class="hx:absolute hx:-mt-20" id="optionen"&gt;&lt;/span&gt;
&lt;a href="#optionen" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Update installieren&lt;/strong&gt; — Download &amp;amp; Neustart&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Diese Version überspringen&lt;/strong&gt; — überspringt nur diese eine Version&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Später erinnern&lt;/strong&gt; — beim nächsten Start erneut fragen&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Updates können in den Einstellungen komplett deaktiviert werden.&lt;/p&gt;
&lt;h2&gt;Latest-Endpoint&lt;span class="hx:absolute hx:-mt-20" id="latest-endpoint"&gt;&lt;/span&gt;
&lt;a href="#latest-endpoint" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code"&gt;
&lt;div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;0.8.3&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;notes&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Rapport 0.8.3&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;pub_date&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2026-05-24T00:00:00Z&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;platforms&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;darwin-aarch64&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;signature&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;…&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;url&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://git.kgva.ch/karim/RAPPORT/releases/download/0.8.3/RAPPORT%20PRE-RELEASE.app.tar.gz&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="hextra-code-copy-btn-container hx:opacity-0 hx:transition hx:group-hover/code:opacity-100 hx:flex hx:gap-1 hx:absolute hx:m-[11px] hx:right-0 hx:top-0"&gt;
&lt;button
class="hextra-code-copy-btn hx:group/copybtn hx:cursor-pointer hx:transition-all hx:active:opacity-50 hx:bg-primary-700/5 hx:border hx:border-black/5 hx:text-gray-600 hx:hover:text-gray-900 hx:rounded-md hx:p-1.5 hx:dark:bg-primary-300/10 hx:dark:border-white/10 hx:dark:text-gray-400 hx:dark:hover:text-gray-50"
title="Code kopieren"
aria-label="Code kopieren"
data-copied-label="Kopiert!"
&gt;
&lt;div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"&gt;&lt;/div&gt;
&lt;/button&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>System-Tray</title><link>https://rapport.gabrielevarano.ch/features/system-tray/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://rapport.gabrielevarano.ch/features/system-tray/</guid><description>
&lt;p&gt;&lt;span class="rapport-status new"&gt;Neu in 0.7.0&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Schnellzugriff über die Menüleiste mit Hide-on-Close.&lt;/strong&gt; Beim Schliessen läuft Rapport im Hintergrund weiter — Cmd+Q beendet die App vollständig.&lt;/p&gt;
&lt;h2&gt;Verhalten&lt;span class="hx:absolute hx:-mt-20" id="verhalten"&gt;&lt;/span&gt;
&lt;a href="#verhalten" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Aktion&lt;/th&gt;
&lt;th&gt;Verhalten&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fenster schliessen&lt;/strong&gt; (⌘W oder rotes X)&lt;/td&gt;
&lt;td&gt;App läuft im Tray weiter&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cmd+Q&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;App wird vollständig beendet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Klick auf Tray-Icon&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fenster nach vorne, oder zeigen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rechtsklick auf Tray-Icon&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Menü mit Schnellzugriffen&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;Tray-Menü&lt;span class="hx:absolute hx:-mt-20" id="tray-menü"&gt;&lt;/span&gt;
&lt;a href="#tray-men%c3%bc" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Rapport zeigen&lt;/strong&gt; — Fenster nach vorne&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Neue Zeiterfassung&lt;/strong&gt; — direkt im Zeit-Modul&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Neue Rechnung&lt;/strong&gt; — direkt im Rechnungs-Modul&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Letzte Projekte&lt;/strong&gt; — Quick-Open der letzten 5 Projekte&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Einstellungen&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rapport beenden&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Konfiguration&lt;span class="hx:absolute hx:-mt-20" id="konfiguration"&gt;&lt;/span&gt;
&lt;a href="#konfiguration" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;p&gt;In den Einstellungen:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Beim Systemstart starten&lt;/strong&gt; (Login-Item) — Standard: aus&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Beim Schliessen beenden&lt;/strong&gt; statt ins Tray — Standard: aus&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tray-Icon ausblenden&lt;/strong&gt; — App läuft, aber kein Menüleisten-Icon&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Verwandte Module&lt;span class="hx:absolute hx:-mt-20" id="verwandte-module"&gt;&lt;/span&gt;
&lt;a href="#verwandte-module" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"&gt;&lt;/a&gt;&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a href="../auto-updater"&gt;Auto-Updater&lt;/a&gt; — prüft Updates im Hintergrund&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
+3
View File
@@ -0,0 +1,3 @@
<svg viewBox="0 0 180 180" xmlns="http://www.w3.org/2000/svg" fill="white">
<path fill-rule="evenodd" clip-rule="evenodd" d="m 105.50024,22.224647 c -9.59169,-5.537563 -21.40871,-5.537563 -31.000093,0 L 39.054693,42.689119 C 29.463353,48.226675 23.55484,58.460531 23.55484,69.535642 v 40.928918 c 0,11.07542 5.908513,21.3092 15.499853,26.84652 l 35.445453,20.46446 c 9.591313,5.53732 21.408404,5.53732 31.000094,0 l 35.44507,-20.46446 c 9.59131,-5.53732 15.49985,-15.7711 15.49985,-26.84652 V 69.535642 c 0,-11.075111 -5.90854,-21.308967 -15.49985,-26.846523 z M 34.112797,85.737639 c -1.384445,2.397827 -1.384445,5.352099 0,7.749927 l 24.781554,42.922974 c 1.38437,2.39783 3.942853,3.87496 6.711592,3.87496 h 49.563107 c 2.76905,0 5.3273,-1.47713 6.71144,-3.87496 l 24.78194,-42.922974 c 1.38414,-2.397828 1.38414,-5.3521 0,-7.749927 L 121.88049,42.814746 c -1.38414,-2.397828 -3.94239,-3.874964 -6.71144,-3.874964 H 65.605944 c -2.768739,0 -5.327223,1.477059 -6.711592,3.874964 z" style="stroke-width:0.774993" />
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

+3
View File
@@ -0,0 +1,3 @@
<svg viewBox="0 0 180 180" xmlns="http://www.w3.org/2000/svg" fill="currentColor">
<path fill-rule="evenodd" clip-rule="evenodd" d="m 105.50024,22.224647 c -9.59169,-5.537563 -21.40871,-5.537563 -31.000093,0 L 39.054693,42.689119 C 29.463353,48.226675 23.55484,58.460531 23.55484,69.535642 v 40.928918 c 0,11.07542 5.908513,21.3092 15.499853,26.84652 l 35.445453,20.46446 c 9.591313,5.53732 21.408404,5.53732 31.000094,0 l 35.44507,-20.46446 c 9.59131,-5.53732 15.49985,-15.7711 15.49985,-26.84652 V 69.535642 c 0,-11.075111 -5.90854,-21.308967 -15.49985,-26.846523 z M 34.112797,85.737639 c -1.384445,2.397827 -1.384445,5.352099 0,7.749927 l 24.781554,42.922974 c 1.38437,2.39783 3.942853,3.87496 6.711592,3.87496 h 49.563107 c 2.76905,0 5.3273,-1.47713 6.71144,-3.87496 l 24.78194,-42.922974 c 1.38414,-2.397828 1.38414,-5.3521 0,-7.749927 L 121.88049,42.814746 c -1.38414,-2.397828 -3.94239,-3.874964 -6.71144,-3.874964 H 65.605944 c -2.768739,0 -5.327223,1.477059 -6.711592,3.874964 z" style="stroke-width:0.774993" />
</svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

+41
View File
File diff suppressed because one or more lines are too long
@@ -0,0 +1,96 @@
/**!
* FlexSearch.js v0.8.143 (Bundle)
* Author and Copyright: Thomas Wilkerling
* Licence: Apache-2.0
* Hosted by Nextapps GmbH
* https://github.com/nextapps-de/flexsearch
*/
(function _f(self){'use strict';if(typeof module!=='undefined')self=module;else if(typeof process !== 'undefined')self=process;self._factory=_f;var t;function z(a,c,b){const e=typeof b,d=typeof a;if("undefined"!==e){if("undefined"!==d){if(b){if("function"===d&&e===d)return function(h){return a(b(h))};c=a.constructor;if(c===b.constructor){if(c===Array)return b.concat(a);if(c===Map){var f=new Map(b);for(var g of a)f.set(g[0],g[1]);return f}if(c===Set){g=new Set(b);for(f of a.values())g.add(f);return g}}}return a}return b}return"undefined"===d?c:a}function B(){return Object.create(null)}function E(a){return"string"===typeof a}
function I(a){return"object"===typeof a}function aa(a){const c=[];for(const b of a.keys())c.push(b);return c}function ba(a,c){if(E(c))a=a[c];else for(let b=0;a&&b<c.length;b++)a=a[c[b]];return a}function ca(a){let c=0;for(let b=0,e;b<a.length;b++)(e=a[b])&&c<e.length&&(c=e.length);return c};const da=/[^\p{L}\p{N}]+/u,ea=/(\d{3})/g,fa=/(\D)(\d{3})/g,ha=/(\d{3})(\D)/g,ia=/[\u0300-\u036f]/g;function J(a={}){if(!this||this.constructor!==J)return new J(...arguments);if(arguments.length)for(a=0;a<arguments.length;a++)this.assign(arguments[a]);else this.assign(a)}t=J.prototype;
t.assign=function(a){this.normalize=z(a.normalize,!0,this.normalize);let c=a.include,b=c||a.exclude||a.split,e;if(b||""===b){if("object"===typeof b&&b.constructor!==RegExp){let d="";e=!c;c||(d+="\\p{Z}");b.letter&&(d+="\\p{L}");b.number&&(d+="\\p{N}",e=!!c);b.symbol&&(d+="\\p{S}");b.punctuation&&(d+="\\p{P}");b.control&&(d+="\\p{C}");if(b=b.char)d+="object"===typeof b?b.join(""):b;try{this.split=new RegExp("["+(c?"^":"")+d+"]+","u")}catch(f){this.split=/\s+/}}else this.split=b,e=!1===b||2>"a1a".split(b).length;
this.numeric=z(a.numeric,e)}else{try{this.split=z(this.split,da)}catch(d){this.split=/\s+/}this.numeric=z(a.numeric,z(this.numeric,!0))}this.prepare=z(a.prepare,null,this.prepare);this.finalize=z(a.finalize,null,this.finalize);b=a.filter;this.filter="function"===typeof b?b:z(b&&new Set(b),null,this.filter);this.dedupe=z(a.dedupe,!1,this.dedupe);this.matcher=z((b=a.matcher)&&new Map(b),null,this.matcher);this.mapper=z((b=a.mapper)&&new Map(b),null,this.mapper);this.stemmer=z((b=a.stemmer)&&new Map(b),
null,this.stemmer);this.replacer=z(a.replacer,null,this.replacer);this.minlength=z(a.minlength,1,this.minlength);this.maxlength=z(a.maxlength,0,this.maxlength);this.rtl=z(a.rtl,!1,this.rtl);if(this.cache=b=z(a.cache,!0,this.cache))this.H=null,this.S="number"===typeof b?b:2E5,this.B=new Map,this.G=new Map,this.L=this.K=128;this.h="";this.M=null;this.A="";this.N=null;if(this.matcher)for(const d of this.matcher.keys())this.h+=(this.h?"|":"")+d;if(this.stemmer)for(const d of this.stemmer.keys())this.A+=
(this.A?"|":"")+d;return this};t.addStemmer=function(a,c){this.stemmer||(this.stemmer=new Map);this.stemmer.set(a,c);this.A+=(this.A?"|":"")+a;this.N=null;this.cache&&K(this);return this};t.addFilter=function(a){"function"===typeof a?this.filter=a:(this.filter||(this.filter=new Set),this.filter.add(a));this.cache&&K(this);return this};
t.addMapper=function(a,c){if("object"===typeof a)return this.addReplacer(a,c);if(1<a.length)return this.addMatcher(a,c);this.mapper||(this.mapper=new Map);this.mapper.set(a,c);this.cache&&K(this);return this};t.addMatcher=function(a,c){if("object"===typeof a)return this.addReplacer(a,c);if(2>a.length&&(this.dedupe||this.mapper))return this.addMapper(a,c);this.matcher||(this.matcher=new Map);this.matcher.set(a,c);this.h+=(this.h?"|":"")+a;this.M=null;this.cache&&K(this);return this};
t.addReplacer=function(a,c){if("string"===typeof a)return this.addMatcher(a,c);this.replacer||(this.replacer=[]);this.replacer.push(a,c);this.cache&&K(this);return this};
t.encode=function(a){if(this.cache&&a.length<=this.K)if(this.H){if(this.B.has(a))return this.B.get(a)}else this.H=setTimeout(K,50,this);this.normalize&&("function"===typeof this.normalize?a=this.normalize(a):a=ia?a.normalize("NFKD").replace(ia,"").toLowerCase():a.toLowerCase());this.prepare&&(a=this.prepare(a));this.numeric&&3<a.length&&(a=a.replace(fa,"$1 $2").replace(ha,"$1 $2").replace(ea,"$1 "));const c=!(this.dedupe||this.mapper||this.filter||this.matcher||this.stemmer||this.replacer);let b=
[],e=this.split||""===this.split?a.split(this.split):a;for(let f=0,g,h;f<e.length;f++)if((g=h=e[f])&&!(g.length<this.minlength))if(c)b.push(g);else if(!this.filter||("function"===typeof this.filter?this.filter(g):!this.filter.has(g))){if(this.cache&&g.length<=this.L)if(this.H){var d=this.G.get(g);if(d||""===d){d&&b.push(d);continue}}else this.H=setTimeout(K,50,this);this.stemmer&&2<g.length&&(this.N||(this.N=new RegExp("(?!^)("+this.A+")$")),d=g,g=g.replace(this.N,k=>this.stemmer.get(k)),d!==g&&this.filter&&
g.length>=this.minlength&&("function"===typeof this.filter?!this.filter(g):this.filter.has(g))&&(g=""));if(g&&(this.mapper||this.dedupe&&1<g.length)){d="";for(let k=0,l="",n,m;k<g.length;k++)n=g.charAt(k),n===l&&this.dedupe||((m=this.mapper&&this.mapper.get(n))||""===m?m===l&&this.dedupe||!(l=m)||(d+=m):d+=l=n);g=d}this.matcher&&1<g.length&&(this.M||(this.M=new RegExp("("+this.h+")","g")),g=g.replace(this.M,k=>this.matcher.get(k)));if(g&&this.replacer)for(d=0;g&&d<this.replacer.length;d+=2)g=g.replace(this.replacer[d],
this.replacer[d+1]);this.cache&&h.length<=this.L&&(this.G.set(h,g),this.G.size>this.S&&(this.G.clear(),this.L=this.L/1.1|0));g&&b.push(g)}this.finalize&&(b=this.finalize(b)||b);this.cache&&a.length<=this.K&&(this.B.set(a,b),this.B.size>this.S&&(this.B.clear(),this.K=this.K/1.1|0));return b};function K(a){a.H=null;a.B.clear();a.G.clear()};let M,ja;async function ka(a){a=a.data;var c=a.task;const b=a.id;let e=a.args;switch(c){case "init":ja=a.options||{};(c=a.factory)?(Function("return "+c)()(self),M=new self.FlexSearch.Index(ja),delete self.FlexSearch):M=new N(ja);postMessage({id:b});break;default:let d;"export"===c&&(e[1]?(e[0]=ja.export,e[2]=0,e[3]=1):e=null);"import"===c?e[0]&&(a=await ja.import.call(M,e[0]),M.import(e[0],a)):(d=e&&M[c].apply(M,e))&&d.then&&(d=await d);postMessage("search"===c?{id:b,msg:d}:{id:b})}};function la(a){ma.call(a,"add");ma.call(a,"append");ma.call(a,"search");ma.call(a,"update");ma.call(a,"remove")}let na,oa,pa;function qa(){na=pa=0}
function ma(a){this[a+"Async"]=function(){const c=arguments;var b=c[c.length-1];let e;"function"===typeof b&&(e=b,delete c[c.length-1]);na?pa||(pa=Date.now()-oa>=this.priority*this.priority*3):(na=setTimeout(qa,0),oa=Date.now());if(pa){const f=this;return new Promise(g=>{setTimeout(function(){g(f[a+"Async"].apply(f,c))},0)})}const d=this[a].apply(this,c);b=d.then?d:new Promise(f=>f(d));e&&b.then(e);return b}};let O=0;
function P(a={}){function c(g){function h(k){k=k.data||k;const l=k.id,n=l&&d.h[l];n&&(n(k.msg),delete d.h[l])}this.worker=g;this.h=B();if(this.worker){e?this.worker.on("message",h):this.worker.onmessage=h;if(a.config)return new Promise(function(k){d.h[++O]=function(){k(d);1E9<O&&(O=0)};d.worker.postMessage({id:O,task:"init",factory:b,options:a})});this.worker.postMessage({task:"init",factory:b,options:a});this.priority=a.priority||4;return this}}if(!this||this.constructor!==P)return new P(a);let b=
"undefined"!==typeof self?self._factory:"undefined"!==typeof window?window._factory:null;b&&(b=b.toString());const e="undefined"===typeof window,d=this,f=ra(b,e,a.worker);return f.then?f.then(function(g){return c.call(d,g)}):c.call(this,f)}Q("add");Q("append");Q("search");Q("update");Q("remove");Q("clear");Q("export");Q("import");la(P.prototype);
function Q(a){P.prototype[a]=function(){const c=this,b=[].slice.call(arguments);var e=b[b.length-1];let d;"function"===typeof e&&(d=e,b.pop());e=new Promise(function(f){"export"===a&&"function"===typeof b[0]&&(b[0]=null);c.h[++O]=f;c.worker.postMessage({task:a,id:O,args:b})});return d?(e.then(d),this):e}}
function ra(a,c,b){return c?"undefined"!==typeof module?new(require("worker_threads")["Worker"])(__dirname+"/node/node.js"):import("worker_threads").then(function(worker){return new worker["Worker"]((1,eval)("import.meta.dirname")+"/node/node.mjs")}):a?new window.Worker(URL.createObjectURL(new Blob(["onmessage="+ka.toString()],{type:"text/javascript"}))):new window.Worker("string"===typeof b?b:(0,eval)("import.meta.url").replace("/worker.js","/worker/worker.js").replace("flexsearch.bundle.module.min.js",
"module/worker/worker.js"),{type:"module"})};function sa(a,c=0){let b=[],e=[];c&&(c=25E4/c*5E3|0);for(const d of a.entries())e.push(d),e.length===c&&(b.push(e),e=[]);e.length&&b.push(e);return b}function ta(a,c){c||(c=new Map);for(let b=0,e;b<a.length;b++)e=a[b],c.set(e[0],e[1]);return c}function ua(a,c=0){let b=[],e=[];c&&(c=25E4/c*1E3|0);for(const d of a.entries())e.push([d[0],sa(d[1])[0]]),e.length===c&&(b.push(e),e=[]);e.length&&b.push(e);return b}
function va(a,c){c||(c=new Map);for(let b=0,e,d;b<a.length;b++)e=a[b],d=c.get(e[0]),c.set(e[0],ta(e[1],d));return c}function wa(a){let c=[],b=[];for(const e of a.keys())b.push(e),25E4===b.length&&(c.push(b),b=[]);b.length&&c.push(b);return c}function xa(a,c){c||(c=new Set);for(let b=0;b<a.length;b++)c.add(a[b]);return c}
function ya(a,c,b,e,d,f,g=0){const h=e&&e.constructor===Array;var k=h?e.shift():e;if(!k)return this.export(a,c,d,f+1);if((k=a((c?c+".":"")+(g+1)+"."+b,JSON.stringify(k)))&&k.then){const l=this;return k.then(function(){return ya.call(l,a,c,b,h?e:null,d,f,g+1)})}return ya.call(this,a,c,b,h?e:null,d,f,g+1)}
function za(a,c){let b="";for(const e of a.entries()){a=e[0];const d=e[1];let f="";for(let g=0,h;g<d.length;g++){h=d[g]||[""];let k="";for(let l=0;l<h.length;l++)k+=(k?",":"")+("string"===c?'"'+h[l]+'"':h[l]);k="["+k+"]";f+=(f?",":"")+k}f='["'+a+'",['+f+"]]";b+=(b?",":"")+f}return b};function Aa(a,c,b,e){let d=[];for(let f=0,g;f<a.index.length;f++)if(g=a.index[f],c>=g.length)c-=g.length;else{c=g[e?"splice":"slice"](c,b);const h=c.length;if(h&&(d=d.length?d.concat(c):c,b-=h,e&&(a.length-=h),!b))break;c=0}return d}
function R(a){if(!this||this.constructor!==R)return new R(a);this.index=a?[a]:[];this.length=a?a.length:0;const c=this;return new Proxy([],{get(b,e){if("length"===e)return c.length;if("push"===e)return function(d){c.index[c.index.length-1].push(d);c.length++};if("pop"===e)return function(){if(c.length)return c.length--,c.index[c.index.length-1].pop()};if("indexOf"===e)return function(d){let f=0;for(let g=0,h,k;g<c.index.length;g++){h=c.index[g];k=h.indexOf(d);if(0<=k)return f+k;f+=h.length}return-1};
if("includes"===e)return function(d){for(let f=0;f<c.index.length;f++)if(c.index[f].includes(d))return!0;return!1};if("slice"===e)return function(d,f){return Aa(c,d||0,f||c.length,!1)};if("splice"===e)return function(d,f){return Aa(c,d||0,f||c.length,!0)};if("constructor"===e)return Array;if("symbol"!==typeof e)return(b=c.index[e/2**31|0])&&b[e]},set(b,e,d){b=e/2**31|0;(c.index[b]||(c.index[b]=[]))[e]=d;c.length++;return!0}})}R.prototype.clear=function(){this.index.length=0};
R.prototype.destroy=function(){this.proxy=this.index=null};R.prototype.push=function(){};function S(a=8){if(!this||this.constructor!==S)return new S(a);this.index=B();this.h=[];this.size=0;32<a?(this.B=Ba,this.A=BigInt(a)):(this.B=Ca,this.A=a)}S.prototype.get=function(a){const c=this.index[this.B(a)];return c&&c.get(a)};S.prototype.set=function(a,c){var b=this.B(a);let e=this.index[b];e?(b=e.size,e.set(a,c),(b-=e.size)&&this.size++):(this.index[b]=e=new Map([[a,c]]),this.h.push(e),this.size++)};
function T(a=8){if(!this||this.constructor!==T)return new T(a);this.index=B();this.h=[];this.size=0;32<a?(this.B=Ba,this.A=BigInt(a)):(this.B=Ca,this.A=a)}T.prototype.add=function(a){var c=this.B(a);let b=this.index[c];b?(c=b.size,b.add(a),(c-=b.size)&&this.size++):(this.index[c]=b=new Set([a]),this.h.push(b),this.size++)};t=S.prototype;t.has=T.prototype.has=function(a){const c=this.index[this.B(a)];return c&&c.has(a)};
t.delete=T.prototype.delete=function(a){const c=this.index[this.B(a)];c&&c.delete(a)&&this.size--};t.clear=T.prototype.clear=function(){this.index=B();this.h=[];this.size=0};t.values=T.prototype.values=function*(){for(let a=0;a<this.h.length;a++)for(let c of this.h[a].values())yield c};t.keys=T.prototype.keys=function*(){for(let a=0;a<this.h.length;a++)for(let c of this.h[a].keys())yield c};t.entries=T.prototype.entries=function*(){for(let a=0;a<this.h.length;a++)for(let c of this.h[a].entries())yield c};
function Ca(a){let c=2**this.A-1;if("number"==typeof a)return a&c;let b=0,e=this.A+1;for(let d=0;d<a.length;d++)b=(b*e^a.charCodeAt(d))&c;return 32===this.A?b+2**31:b}function Ba(a){let c=BigInt(2)**this.A-BigInt(1);var b=typeof a;if("bigint"===b)return a&c;if("number"===b)return BigInt(a)&c;b=BigInt(0);let e=this.A+BigInt(1);for(let d=0;d<a.length;d++)b=(b*e^BigInt(a.charCodeAt(d)))&c;return b};U.prototype.add=function(a,c,b){I(a)&&(c=a,a=ba(c,this.key));if(c&&(a||0===a)){if(!b&&this.reg.has(a))return this.update(a,c);for(let h=0,k;h<this.field.length;h++){k=this.D[h];var e=this.index.get(this.field[h]);if("function"===typeof k){var d=k(c);d&&e.add(a,d,!1,!0)}else if(d=k.I,!d||d(c))k.constructor===String?k=[""+k]:E(k)&&(k=[k]),Da(c,k,this.J,0,e,a,k[0],b)}if(this.tag)for(e=0;e<this.F.length;e++){var f=this.F[e];d=this.tag.get(this.R[e]);let h=B();if("function"===typeof f){if(f=f(c),!f)continue}else{var g=
f.I;if(g&&!g(c))continue;f.constructor===String&&(f=""+f);f=ba(c,f)}if(d&&f){E(f)&&(f=[f]);for(let k=0,l,n;k<f.length;k++)if(l=f[k],!h[l]&&(h[l]=1,(g=d.get(l))?n=g:d.set(l,n=[]),!b||!n.includes(a))){if(n.length===2**31-1){g=new R(n);if(this.fastupdate)for(let m of this.reg.values())m.includes(n)&&(m[m.indexOf(n)]=g);d.set(l,n=g)}n.push(a);this.fastupdate&&((g=this.reg.get(a))?g.push(n):this.reg.set(a,[n]))}}}if(this.store&&(!b||!this.store.has(a))){let h;if(this.C){h=B();for(let k=0,l;k<this.C.length;k++){l=
this.C[k];if((b=l.I)&&!b(c))continue;let n;if("function"===typeof l){n=l(c);if(!n)continue;l=[l.V]}else if(E(l)||l.constructor===String){h[l]=c[l];continue}Ea(c,h,l,0,l[0],n)}}this.store.set(a,h||c)}this.worker&&(this.fastupdate||this.reg.add(a))}return this};function Ea(a,c,b,e,d,f){a=a[d];if(e===b.length-1)c[d]=f||a;else if(a)if(a.constructor===Array)for(c=c[d]=Array(a.length),d=0;d<a.length;d++)Ea(a,c,b,e,d);else c=c[d]||(c[d]=B()),d=b[++e],Ea(a,c,b,e,d)}
function Da(a,c,b,e,d,f,g,h){if(a=a[g])if(e===c.length-1){if(a.constructor===Array){if(b[e]){for(c=0;c<a.length;c++)d.add(f,a[c],!0,!0);return}a=a.join(" ")}d.add(f,a,h,!0)}else if(a.constructor===Array)for(g=0;g<a.length;g++)Da(a,c,b,e,d,f,g,h);else g=c[++e],Da(a,c,b,e,d,f,g,h);else d.db&&d.remove(f)};function Fa(a,c,b,e,d,f,g){const h=a.length;let k=[],l,n;l=B();for(let m=0,q,p,r,u;m<c;m++)for(let v=0;v<h;v++)if(r=a[v],m<r.length&&(q=r[m]))for(let w=0;w<q.length;w++){p=q[w];(n=l[p])?l[p]++:(n=0,l[p]=1);u=k[n]||(k[n]=[]);if(!g){let y=m+(v||!d?0:f||0);u=u[y]||(u[y]=[])}u.push(p);if(g&&b&&n===h-1&&u.length-e===b)return u}if(a=k.length)if(d)k=1<k.length?Ga(k,b,e,g,f):(k=k[0]).length>b||e?k.slice(e,b+e):k;else{if(a<h)return[];k=k[a-1];if(b||e)if(g){if(k.length>b||e)k=k.slice(e,b+e)}else{d=[];for(let m=
0,q;m<k.length;m++)if(q=k[m],q.length>e)e-=q.length;else{if(q.length>b||e)q=q.slice(e,b+e),b-=q.length,e&&(e-=q.length);d.push(q);if(!b)break}k=1<d.length?[].concat.apply([],d):d[0]}}return k}
function Ga(a,c,b,e,d){const f=[],g=B();let h;var k=a.length;let l;if(e)for(d=k-1;0<=d;d--){if(l=(e=a[d])&&e.length)for(k=0;k<l;k++)if(h=e[k],!g[h])if(g[h]=1,b)b--;else if(f.push(h),f.length===c)return f}else for(let n=k-1,m,q=0;0<=n;n--){m=a[n];for(let p=0;p<m.length;p++)if(l=(e=m[p])&&e.length)for(let r=0;r<l;r++)if(h=e[r],!g[h])if(g[h]=1,b)b--;else{let u=(p+(n<k-1?d||0:0))/(n+1)|0;(f[u]||(f[u]=[])).push(h);if(++q===c)return f}}return f}
function Ha(a,c,b){const e=B(),d=[];for(let f=0,g;f<c.length;f++){g=c[f];for(let h=0;h<g.length;h++)e[g[h]]=1}if(b)for(let f=0,g;f<a.length;f++)g=a[f],e[g]&&(d.push(g),e[g]=0);else for(let f=0,g,h;f<a.result.length;f++)for(g=a.result[f],c=0;c<g.length;c++)h=g[c],e[h]&&((d[f]||(d[f]=[])).push(h),e[h]=0);return d};function Ia(a,c,b,e){if(!a.length)return a;if(1===a.length)return a=a[0],a=b||a.length>c?c?a.slice(b,b+c):a.slice(b):a,e?V.call(this,a):a;let d=[];for(let f=0,g,h;f<a.length;f++)if((g=a[f])&&(h=g.length)){if(b){if(b>=h){b-=h;continue}b<h&&(g=c?g.slice(b,b+c):g.slice(b),h=g.length,b=0)}h>c&&(g=g.slice(0,c),h=c);if(!d.length&&h>=c)return e?V.call(this,g):g;d.push(g);c-=h;if(!c)break}d=1<d.length?[].concat.apply([],d):d[0];return e?V.call(this,d):d};function Ja(a,c,b){var e=b[0];if(e.then)return Promise.all(b).then(function(n){return a[c].apply(a,n)});if(e[0]&&e[0].index)return a[c].apply(a,e);e=[];let d=[],f=0,g=0,h,k,l;for(let n=0,m;n<b.length;n++)if(m=b[n]){let q;if(m.constructor===W)q=m.result;else if(m.constructor===Array)q=m;else if(f=m.limit||0,g=m.offset||0,l=m.suggest,k=m.resolve,h=m.enrich&&k,m.index)m.resolve=!1,q=m.index.search(m).result,m.resolve=k;else if(m.and)q=a.and(m.and);else if(m.or)q=a.or(m.or);else if(m.xor)q=a.xor(m.xor);
else if(m.not)q=a.not(m.not);else continue;if(q.then)d.push(q);else if(q.length)e[n]=q;else if(!l&&("and"===c||"xor"===c)){e=[];break}}return{O:e,P:d,limit:f,offset:g,enrich:h,resolve:k,suggest:l}};W.prototype.or=function(){const {O:a,P:c,limit:b,offset:e,enrich:d,resolve:f}=Ja(this,"or",arguments);return Ka.call(this,a,c,b,e,d,f)};function Ka(a,c,b,e,d,f){if(c.length){const g=this;return Promise.all(c).then(function(h){a=[];for(let k=0,l;k<h.length;k++)(l=h[k]).length&&(a[k]=l);return Ka.call(g,a,[],b,e,d,f)})}a.length&&(this.result.length&&a.push(this.result),2>a.length?this.result=a[0]:(this.result=Ga(a,b,e,!1,this.h),e=0));return f?this.resolve(b,e,d):this};W.prototype.and=function(){let a=this.result.length,c,b,e,d;if(!a){const f=arguments[0];f&&(a=!!f.suggest,d=f.resolve,c=f.limit,b=f.offset,e=f.enrich&&d)}if(a){const {O:f,P:g,limit:h,offset:k,enrich:l,resolve:n,suggest:m}=Ja(this,"and",arguments);return La.call(this,f,g,h,k,l,n,m)}return d?this.resolve(c,b,e):this};
function La(a,c,b,e,d,f,g){if(c.length){const h=this;return Promise.all(c).then(function(k){a=[];for(let l=0,n;l<k.length;l++)(n=k[l]).length&&(a[l]=n);return La.call(h,a,[],b,e,d,f,g)})}if(a.length)if(this.result.length&&a.unshift(this.result),2>a.length)this.result=a[0];else{if(c=ca(a))return this.result=Fa(a,c,b,e,g,this.h,f),f?d?V.call(this.index,this.result):this.result:this;this.result=[]}else g||(this.result=a);return f?this.resolve(b,e,d):this};W.prototype.xor=function(){const {O:a,P:c,limit:b,offset:e,enrich:d,resolve:f,suggest:g}=Ja(this,"xor",arguments);return Ma.call(this,a,c,b,e,d,f,g)};
function Ma(a,c,b,e,d,f,g){if(c.length){const h=this;return Promise.all(c).then(function(k){a=[];for(let l=0,n;l<k.length;l++)(n=k[l]).length&&(a[l]=n);return Ma.call(h,a,[],b,e,d,f,g)})}if(a.length)if(this.result.length&&a.unshift(this.result),2>a.length)this.result=a[0];else return this.result=Na.call(this,a,b,e,f,this.h),f?d?V.call(this.index,this.result):this.result:this;else g||(this.result=a);return f?this.resolve(b,e,d):this}
function Na(a,c,b,e,d){const f=[],g=B();let h=0;for(let k=0,l;k<a.length;k++)if(l=a[k]){h<l.length&&(h=l.length);for(let n=0,m;n<l.length;n++)if(m=l[n])for(let q=0,p;q<m.length;q++)p=m[q],g[p]=g[p]?2:1}for(let k=0,l,n=0;k<h;k++)for(let m=0,q;m<a.length;m++)if(q=a[m])if(l=q[k])for(let p=0,r;p<l.length;p++)if(r=l[p],1===g[r])if(b)b--;else if(e){if(f.push(r),f.length===c)return f}else{const u=k+(m?d:0);f[u]||(f[u]=[]);f[u].push(r);if(++n===c)return f}return f};W.prototype.not=function(){const {O:a,P:c,limit:b,offset:e,enrich:d,resolve:f,suggest:g}=Ja(this,"not",arguments);return Oa.call(this,a,c,b,e,d,f,g)};function Oa(a,c,b,e,d,f,g){if(c.length){const h=this;return Promise.all(c).then(function(k){a=[];for(let l=0,n;l<k.length;l++)(n=k[l]).length&&(a[l]=n);return Oa.call(h,a,[],b,e,d,f,g)})}if(a.length&&this.result.length)this.result=Pa.call(this,a,b,e,f);else if(f)return this.resolve(b,e,d);return f?d?V.call(this.index,this.result):this.result:this}
function Pa(a,c,b,e){const d=[];a=new Set(a.flat().flat());for(let f=0,g,h=0;f<this.result.length;f++)if(g=this.result[f])for(let k=0,l;k<g.length;k++)if(l=g[k],!a.has(l))if(b)b--;else if(e){if(d.push(l),d.length===c)return d}else if(d[f]||(d[f]=[]),d[f].push(l),++h===c)return d;return d};function W(a){if(!this||this.constructor!==W)return new W(a);if(a&&a.index)return a.resolve=!1,this.index=a.index,this.h=a.boost||0,this.result=a.index.search(a).result,this;this.index=null;this.result=a||[];this.h=0}W.prototype.limit=function(a){if(this.result.length){const c=[];for(let b=0,e;b<this.result.length;b++)if(e=this.result[b])if(e.length<=a){if(c[b]=e,a-=e.length,!a)break}else{c[b]=e.slice(0,a);break}this.result=c}return this};
W.prototype.offset=function(a){if(this.result.length){const c=[];for(let b=0,e;b<this.result.length;b++)if(e=this.result[b])e.length<=a?a-=e.length:(c[b]=e.slice(a),a=0);this.result=c}return this};W.prototype.boost=function(a){this.h+=a;return this};W.prototype.resolve=function(a,c,b){const e=this.result,d=this.index;this.result=this.index=null;return e.length?("object"===typeof a&&(b=a.enrich,c=a.offset,a=a.limit),Ia.call(d,e,a||100,c,b)):e};B();U.prototype.search=function(a,c,b,e){b||(!c&&I(a)?(b=a,a=""):I(c)&&(b=c,c=0));let d=[];var f=[],g;let h;let k,l;let n=0;var m=!0;let q;if(b){b.constructor===Array&&(b={index:b});a=b.query||a;var p=b.pluck;h=b.merge;k=p||b.field||(k=b.index)&&(k.index?null:k);l=this.tag&&b.tag;var r=b.suggest;m=!1!==b.resolve;m||p||!(k=k||this.field)||(E(k)?p=k:(k.constructor===Array&&1===k.length&&(k=k[0]),p=k.field||k.index));q=(g=this.store&&b.enrich&&m)&&b.highlight;c=b.limit||c;var u=b.offset||0;c||(c=100);if(l&&
(!this.db||!e)){l.constructor!==Array&&(l=[l]);var v=[];for(let A=0,x;A<l.length;A++)if(x=l[A],x.field&&x.tag){var w=x.tag;if(w.constructor===Array)for(var y=0;y<w.length;y++)v.push(x.field,w[y]);else v.push(x.field,w)}else{w=Object.keys(x);for(let D=0,G,C;D<w.length;D++)if(G=w[D],C=x[G],C.constructor===Array)for(y=0;y<C.length;y++)v.push(G,C[y]);else v.push(G,C)}l=v;if(!a){m=[];if(v.length)for(f=0;f<v.length;f+=2){if(this.db){p=this.index.get(v[f]);if(!p)continue;m.push(p=p.db.tag(v[f+1],c,u,g))}else p=
Qa.call(this,v[f],v[f+1],c,u,g);d.push({field:v[f],tag:v[f+1],result:p})}return m.length?Promise.all(m).then(function(A){for(let x=0;x<A.length;x++)d[x].result=A[x];return d}):d}}k&&k.constructor!==Array&&(k=[k])}k||(k=this.field);v=!e&&(this.worker||this.db)&&[];let F;for(let A=0,x,D,G;A<k.length;A++){D=k[A];if(this.db&&this.tag&&!this.D[A])continue;let C;E(D)||(C=D,D=C.field,a=C.query||a,c=C.limit||c,u=C.offset||u,r=C.suggest||r,g=this.store&&(C.enrich||g));if(e)x=e[A];else if(w=C||b,y=this.index.get(D),
l&&(this.db&&(w.tag=l,F=y.db.support_tag_search,w.field=k),F||(w.enrich=!1)),v){v[A]=y.search(a,c,w);w&&g&&(w.enrich=g);continue}else x=y.search(a,c,w),w&&g&&(w.enrich=g);G=x&&(m?x.length:x.result.length);if(l&&G){w=[];y=0;if(this.db&&e){if(!F)for(let H=k.length;H<e.length;H++){let L=e[H];if(L&&L.length)y++,w.push(L);else if(!r)return m?d:new W(d)}}else for(let H=0,L,sb;H<l.length;H+=2){L=this.tag.get(l[H]);if(!L)if(r)continue;else return m?d:new W(d);if(sb=(L=L&&L.get(l[H+1]))&&L.length)y++,w.push(L);
else if(!r)return m?d:new W(d)}if(y){x=Ha(x,w,m);G=x.length;if(!G&&!r)return m?x:new W(x);y--}}if(G)f[n]=D,d.push(x),n++;else if(1===k.length)return m?d:new W(d)}if(v){if(this.db&&l&&l.length&&!F)for(g=0;g<l.length;g+=2){f=this.index.get(l[g]);if(!f)if(r)continue;else return m?d:new W(d);v.push(f.db.tag(l[g+1],c,u,!1))}const A=this;return Promise.all(v).then(function(x){return x.length?A.search(a,c,b,x):x})}if(!n)return m?d:new W(d);if(p&&(!g||!this.store))return d[0];v=[];for(u=0;u<f.length;u++){r=
d[u];g&&r.length&&"undefined"===typeof r[0].doc&&(this.db?v.push(r=this.index.get(this.field[0]).db.enrich(r)):r=V.call(this,r));if(p)return m?r:new W(r);d[u]={field:f[u],result:r}}if(g&&this.db&&v.length){const A=this;return Promise.all(v).then(function(x){for(let D=0;D<x.length;D++)d[D].result=x[D];return h?Ra(d,c):q?Sa(d,a,A.index,A.field,A.D,q):d})}return h?Ra(d,c):q?Sa(d,a,this.index,this.field,this.D,q):d};
function Sa(a,c,b,e,d,f){let g,h,k;for(let n=0,m,q,p,r;n<a.length;n++){let u=a[n].result;m=a[n].field;p=b.get(m);q=p.encoder;k=p.tokenize;r=d[e.indexOf(m)];q!==g&&(g=q,h=g.encode(c));for(let v=0;v<u.length;v++){let w="";var l=ba(u[v].doc,r);let y=g.encode(l);l=l.split(g.split);for(let F=0,A,x;F<y.length;F++){A=y[F];x=l[F];if(!A||!x)continue;let D;for(let G=0,C;G<h.length;G++)if(C=h[G],"strict"===k){if(A===C){w+=(w?" ":"")+f.replace("$1",x);D=!0;break}}else{const H=A.indexOf(C);if(-1<H){w+=(w?" ":
"")+x.substring(0,H)+f.replace("$1",x.substring(H,C.length))+x.substring(H+C.length);D=!0;break}}D||(w+=(w?" ":"")+l[F])}u[v].highlight=w}}return a}function Ra(a,c){const b=[],e=B();for(let d=0,f,g;d<a.length;d++){f=a[d];g=f.result;for(let h=0,k,l,n;h<g.length;h++)if(l=g[h],"object"!==typeof l&&(l={id:l}),k=l.id,n=e[k])n.push(f.field);else{if(b.length===c)return b;l.field=e[k]=[f.field];b.push(l)}}return b}
function Qa(a,c,b,e,d){a=this.tag.get(a);if(!a)return[];if((c=(a=a&&a.get(c))&&a.length-e)&&0<c){if(c>b||e)a=a.slice(e,e+b);d&&(a=V.call(this,a));return a}}function V(a){if(!this||!this.store)return a;const c=Array(a.length);for(let b=0,e;b<a.length;b++)e=a[b],c[b]={id:e,doc:this.store.get(e)};return c};function U(a){if(!this||this.constructor!==U)return new U(a);const c=a.document||a.doc||a;let b,e;this.D=[];this.field=[];this.J=[];this.key=(b=c.key||c.id)&&Ta(b,this.J)||"id";(e=a.keystore||0)&&(this.keystore=e);this.fastupdate=!!a.fastupdate;this.reg=!this.fastupdate||a.worker||a.db?e?new T(e):new Set:e?new S(e):new Map;this.C=(b=c.store||null)&&b&&!0!==b&&[];this.store=b&&(e?new S(e):new Map);this.cache=(b=a.cache||null)&&new X(b);a.cache=!1;this.worker=a.worker;this.priority=a.priority||4;this.index=
Ua.call(this,a,c);this.tag=null;if(b=c.tag)if("string"===typeof b&&(b=[b]),b.length){this.tag=new Map;this.F=[];this.R=[];for(let d=0,f,g;d<b.length;d++){f=b[d];g=f.field||f;if(!g)throw Error("The tag field from the document descriptor is undefined.");f.custom?this.F[d]=f.custom:(this.F[d]=Ta(g,this.J),f.filter&&("string"===typeof this.F[d]&&(this.F[d]=new String(this.F[d])),this.F[d].I=f.filter));this.R[d]=g;this.tag.set(g,new Map)}}if(this.worker){this.fastupdate=!1;a=[];for(const d of this.index.values())d.then&&
a.push(d);if(a.length){const d=this;return Promise.all(a).then(function(f){let g=0;for(const h of d.index.entries()){const k=h[0];h[1].then&&d.index.set(k,f[g++])}return d})}}else a.db&&(this.fastupdate=!1,this.mount(a.db))}t=U.prototype;
t.mount=function(a){let c=this.field;if(this.tag)for(let d=0,f;d<this.R.length;d++){f=this.R[d];var b=void 0;this.index.set(f,b=new N({},this.reg));c===this.field&&(c=c.slice(0));c.push(f);b.tag=this.tag.get(f)}b=[];const e={db:a.db,type:a.type,fastupdate:a.fastupdate};for(let d=0,f,g;d<c.length;d++){e.field=g=c[d];f=this.index.get(g);const h=new a.constructor(a.id,e);h.id=a.id;b[d]=h.mount(f);f.document=!0;d?f.bypass=!0:f.store=this.store}this.db=!0;return Promise.all(b)};
t.commit=async function(a,c){const b=[];for(const e of this.index.values())b.push(e.commit(a,c));await Promise.all(b);this.reg.clear()};t.destroy=function(){const a=[];for(const c of this.index.values())a.push(c.destroy());return Promise.all(a)};
function Ua(a,c){const b=new Map;let e=c.index||c.field||c;E(e)&&(e=[e]);for(let d=0,f,g;d<e.length;d++){f=e[d];E(f)||(g=f,f=f.field);g=I(g)?Object.assign({},a,g):a;if(this.worker){const h=new P(g);b.set(f,h)}this.worker||b.set(f,new N(g,this.reg));g.custom?this.D[d]=g.custom:(this.D[d]=Ta(f,this.J),g.filter&&("string"===typeof this.D[d]&&(this.D[d]=new String(this.D[d])),this.D[d].I=g.filter));this.field[d]=f}if(this.C){a=c.store;E(a)&&(a=[a]);for(let d=0,f,g;d<a.length;d++)f=a[d],g=f.field||f,f.custom?
(this.C[d]=f.custom,f.custom.V=g):(this.C[d]=Ta(g,this.J),f.filter&&("string"===typeof this.C[d]&&(this.C[d]=new String(this.C[d])),this.C[d].I=f.filter))}return b}function Ta(a,c){const b=a.split(":");let e=0;for(let d=0;d<b.length;d++)a=b[d],"]"===a[a.length-1]&&(a=a.substring(0,a.length-2))&&(c[e]=!0),a&&(b[e++]=a);e<b.length&&(b.length=e);return 1<e?b:b[0]}t.append=function(a,c){return this.add(a,c,!0)};t.update=function(a,c){return this.remove(a).add(a,c)};
t.remove=function(a){I(a)&&(a=ba(a,this.key));for(var c of this.index.values())c.remove(a,!0);if(this.reg.has(a)){if(this.tag&&!this.fastupdate)for(let b of this.tag.values())for(let e of b){c=e[0];const d=e[1],f=d.indexOf(a);-1<f&&(1<d.length?d.splice(f,1):b.delete(c))}this.store&&this.store.delete(a);this.reg.delete(a)}this.cache&&this.cache.remove(a);return this};
t.clear=function(){const a=[];for(const c of this.index.values()){const b=c.clear();b.then&&a.push(b)}if(this.tag)for(const c of this.tag.values())c.clear();this.store&&this.store.clear();this.cache&&this.cache.clear();return a.length?Promise.all(a):this};t.contain=function(a){return this.db?this.index.get(this.field[0]).db.has(a):this.reg.has(a)};t.cleanup=function(){for(const a of this.index.values())a.cleanup();return this};
t.get=function(a){return this.db?this.index.get(this.field[0]).db.enrich(a).then(function(c){return c[0]&&c[0].doc}):this.store.get(a)};t.set=function(a,c){this.store.set(a,c);return this};t.searchCache=Va;
t.export=function(a,c,b=0,e=0){if(b<this.field.length){const g=this.field[b];if((c=this.index.get(g).export(a,g,b,e=1))&&c.then){const h=this;return c.then(function(){return h.export(a,g,b+1)})}return this.export(a,g,b+1)}let d,f;switch(e){case 0:d="reg";f=wa(this.reg);c=null;break;case 1:d="tag";f=this.tag&&ua(this.tag,this.reg.size);c=null;break;case 2:d="doc";f=this.store&&sa(this.store);c=null;break;default:return}return ya.call(this,a,c,d,f,b,e)};
t.import=function(a,c){var b=a.split(".");"json"===b[b.length-1]&&b.pop();const e=2<b.length?b[0]:"";b=2<b.length?b[2]:b[1];if(this.worker&&e)return this.index.get(e).import(a);if(c){"string"===typeof c&&(c=JSON.parse(c));if(e)return this.index.get(e).import(b,c);switch(b){case "reg":this.fastupdate=!1;this.reg=xa(c,this.reg);for(let d=0,f;d<this.field.length;d++)f=this.index.get(this.field[d]),f.fastupdate=!1,f.reg=this.reg;if(this.worker){c=[];for(const d of this.index.values())c.push(d.import(a));
return Promise.all(c)}break;case "tag":this.tag=va(c,this.tag);break;case "doc":this.store=ta(c,this.store)}}};la(U.prototype);function Va(a,c,b){a=("object"===typeof a?""+a.query:a).toLowerCase();this.cache||(this.cache=new X);let e=this.cache.get(a);if(!e){e=this.search(a,c,b);if(e.then){const d=this;e.then(function(f){d.cache.set(a,f);return f})}this.cache.set(a,e)}return e}function X(a){this.limit=a&&!0!==a?a:1E3;this.cache=new Map;this.h=""}X.prototype.set=function(a,c){this.cache.set(this.h=a,c);this.cache.size>this.limit&&this.cache.delete(this.cache.keys().next().value)};
X.prototype.get=function(a){const c=this.cache.get(a);c&&this.h!==a&&(this.cache.delete(a),this.cache.set(this.h=a,c));return c};X.prototype.remove=function(a){for(const c of this.cache){const b=c[0];c[1].includes(a)&&this.cache.delete(b)}};X.prototype.clear=function(){this.cache.clear();this.h=""};const Wa={normalize:!1,numeric:!1,split:/\s+/};const Xa={normalize:!0};const Ya={normalize:!0,dedupe:!0};const Za=new Map([["b","p"],["v","f"],["w","f"],["z","s"],["x","s"],["d","t"],["n","m"],["c","k"],["g","k"],["j","k"],["q","k"],["i","e"],["y","e"],["u","o"]]);const $a=new Map([["ae","a"],["oe","o"],["sh","s"],["kh","k"],["th","t"],["ph","f"],["pf","f"]]),ab=[/([^aeo])h(.)/g,"$1$2",/([aeo])h([^aeo]|$)/g,"$1$2",/(.)\1+/g,"$1"];const bb={a:"",e:"",i:"",o:"",u:"",y:"",b:1,f:1,p:1,v:1,c:2,g:2,j:2,k:2,q:2,s:2,x:2,z:2,"\u00df":2,d:3,t:3,l:4,m:5,n:5,r:6};var cb={X:Wa,W:Xa,Y:Ya,LatinBalance:{normalize:!0,dedupe:!0,mapper:Za},LatinAdvanced:{normalize:!0,dedupe:!0,mapper:Za,matcher:$a,replacer:ab},LatinExtra:{normalize:!0,dedupe:!0,mapper:Za,replacer:ab.concat([/(?!^)[aeo]/g,""]),matcher:$a},LatinSoundex:{normalize:!0,dedupe:!1,include:{letter:!0},finalize:function(a){for(let b=0;b<a.length;b++){var c=a[b];let e=c.charAt(0),d=bb[e];for(let f=1,g;f<c.length&&(g=c.charAt(f),"h"===g||"w"===g||!(g=bb[g])||g===d||(e+=g,d=g,4!==e.length));f++);a[b]=e}}},LatinExact:Wa,
LatinDefault:Xa,LatinSimple:Ya};const db={memory:{resolution:1},performance:{resolution:3,fastupdate:!0,context:{depth:1,resolution:1}},match:{tokenize:"forward"},score:{resolution:9,context:{depth:2,resolution:3}}};N.prototype.add=function(a,c,b,e){if(c&&(a||0===a)){if(!e&&!b&&this.reg.has(a))return this.update(a,c);c=this.encoder.encode(c);if(e=c.length){const l=B(),n=B(),m=this.depth,q=this.resolution;for(let p=0;p<e;p++){let r=c[this.rtl?e-1-p:p];var d=r.length;if(d&&(m||!n[r])){var f=this.score?this.score(c,r,p,null,0):eb(q,e,p),g="";switch(this.tokenize){case "full":if(2<d){for(let u=0,v;u<d;u++)for(f=d;f>u;f--){g=r.substring(u,f);v=this.rtl?d-1-u:u;var h=this.score?this.score(c,r,p,g,v):eb(q,e,p,d,v);
fb(this,n,g,h,a,b)}break}case "bidirectional":case "reverse":if(1<d){for(h=d-1;0<h;h--){g=r[this.rtl?d-1-h:h]+g;var k=this.score?this.score(c,r,p,g,h):eb(q,e,p,d,h);fb(this,n,g,k,a,b)}g=""}case "forward":if(1<d){for(h=0;h<d;h++)g+=r[this.rtl?d-1-h:h],fb(this,n,g,f,a,b);break}default:if(fb(this,n,r,f,a,b),m&&1<e&&p<e-1)for(d=B(),g=this.U,f=r,h=Math.min(m+1,this.rtl?p+1:e-p),d[f]=1,k=1;k<h;k++)if((r=c[this.rtl?e-1-p-k:p+k])&&!d[r]){d[r]=1;const u=this.score?this.score(c,f,p,r,k-1):eb(g+(e/2>g?0:1),
e,p,h-1,k-1),v=this.bidirectional&&r>f;fb(this,l,v?f:r,u,a,b,v?r:f)}}}}this.fastupdate||this.reg.add(a)}else c=""}this.db&&(c||this.commit_task.push({del:a}),this.T&&gb(this));return this};
function fb(a,c,b,e,d,f,g){let h=g?a.ctx:a.map,k;if(!c[b]||g&&!(k=c[b])[g])if(g?(c=k||(c[b]=B()),c[g]=1,(k=h.get(g))?h=k:h.set(g,h=new Map)):c[b]=1,(k=h.get(b))?h=k:h.set(b,h=k=[]),h=h[e]||(h[e]=[]),!f||!h.includes(d)){if(h.length===2**31-1){c=new R(h);if(a.fastupdate)for(let l of a.reg.values())l.includes(h)&&(l[l.indexOf(h)]=c);k[e]=h=c}h.push(d);a.fastupdate&&((e=a.reg.get(d))?e.push(h):a.reg.set(d,[h]))}}
function eb(a,c,b,e,d){return b&&1<a?c+(e||0)<=a?b+(d||0):(a-1)/(c+(e||0))*(b+(d||0))+1|0:0};N.prototype.search=function(a,c,b){b||(!c&&I(a)?(b=a,a=""):I(c)&&(b=c,c=0));let e=[],d,f,g,h=0,k,l,n,m,q;b?(a=b.query||a,c=b.limit||c,h=b.offset||0,f=b.context,g=b.suggest,q=(k=!1!==b.resolve)&&b.enrich,n=b.boost,m=b.resolution,l=this.db&&b.tag):k=this.resolve;let p=this.encoder.encode(a);d=p.length;c=c||(k?100:0);if(1===d)return hb.call(this,p[0],"",c,h,k,q,l);f=this.depth&&!1!==f;if(2===d&&f&&!g)return hb.call(this,p[0],p[1],c,h,k,q,l);let r=B(),u=0,v;1<d&&f&&(v=p[0],u=1);m||0===m||(m=v?this.U:
this.resolution);if(this.db){if(this.db.search&&(a=this.db.search(this,p,c,h,g,k,q,l),!1!==a))return a;const w=this;return async function(){for(let y,F;u<d;u++){if((F=p[u])&&!r[F]){r[F]=1;y=await ib(w,F,v,0,0,!1,!1);if(y=jb(y,e,g,m)){e=y;break}v&&(g&&y&&e.length||(v=F))}g&&v&&u===d-1&&!e.length&&(m=w.resolution,v="",u=-1,r=B())}return kb(e,m,c,h,g,n,k)}()}for(let w,y;u<d;u++){if((y=p[u])&&!r[y]){r[y]=1;w=ib(this,y,v,0,0,!1,!1);if(w=jb(w,e,g,m)){e=w;break}v&&(g&&w&&e.length||(v=y))}g&&v&&u===d-1&&
!e.length&&(m=this.resolution,v="",u=-1,r=B())}return kb(e,m,c,h,g,n,k)};function kb(a,c,b,e,d,f,g){let h=a.length,k=a;if(1<h)k=Fa(a,c,b,e,d,f,g);else if(1===h)return g?Ia.call(null,a[0],b,e):new W(a[0]);return g?k:new W(k)}function hb(a,c,b,e,d,f,g){a=ib(this,a,c,b,e,d,f,g);return this.db?a.then(function(h){return d?h||[]:new W(h)}):a&&a.length?d?Ia.call(this,a,b,e):new W(a):d?[]:new W}
function jb(a,c,b,e){let d=[];if(a&&a.length){if(a.length<=e){c.push(a);return}for(let f=0,g;f<e;f++)if(g=a[f])d[f]=g;if(d.length){c.push(d);return}}if(!b)return d}function ib(a,c,b,e,d,f,g,h){let k;b&&(k=a.bidirectional&&c>b)&&(k=b,b=c,c=k);if(a.db)return a.db.get(c,b,e,d,f,g,h);a=b?(a=a.ctx.get(b))&&a.get(c):a.map.get(c);return a};N.prototype.remove=function(a,c){const b=this.reg.size&&(this.fastupdate?this.reg.get(a):this.reg.has(a));if(b){if(this.fastupdate)for(let e=0,d;e<b.length;e++){if(d=b[e])if(2>d.length)d.pop();else{const f=d.indexOf(a);f===b.length-1?d.pop():d.splice(f,1)}}else lb(this.map,a),this.depth&&lb(this.ctx,a);c||this.reg.delete(a)}this.db&&(this.commit_task.push({del:a}),this.T&&gb(this));this.cache&&this.cache.remove(a);return this};
function lb(a,c){let b=0;if(a.constructor===Array)for(let e=0,d,f;e<a.length;e++){if((d=a[e])&&d.length)if(f=d.indexOf(c),0<=f){1<d.length?(d.splice(f,1),b++):delete a[e];break}else b++}else for(let e of a.entries()){const d=e[0],f=lb(e[1],c);f?b+=f:a.delete(d)}return b};function N(a,c){if(!this||this.constructor!==N)return new N(a);if(a){var b=E(a)?a:a.preset;b&&(a=Object.assign({},db[b],a))}else a={};b=a.context;const e=!0===b?{depth:1}:b||{},d=E(a.encoder)?cb[a.encoder]:a.encode||a.encoder||Xa;this.encoder=d.encode?d:"object"===typeof d?new J(d):{encode:d};this.resolution=a.resolution||9;this.tokenize=b=(b=a.tokenize)&&"default"!==b&&"exact"!==b&&b||"strict";this.depth="strict"===b&&e.depth||0;this.bidirectional=!1!==e.bidirectional;this.fastupdate=!!a.fastupdate;
this.score=a.score||null;(b=a.keystore||0)&&(this.keystore=b);this.map=b?new S(b):new Map;this.ctx=b?new S(b):new Map;this.reg=c||(this.fastupdate?b?new S(b):new Map:b?new T(b):new Set);this.U=e.resolution||3;this.rtl=d.rtl||a.rtl||!1;this.cache=(b=a.cache||null)&&new X(b);this.resolve=!1!==a.resolve;if(b=a.db)this.db=this.mount(b);this.T=!1!==a.commit;this.commit_task=[];this.commit_timer=null;this.priority=a.priority||4}t=N.prototype;
t.mount=function(a){this.commit_timer&&(clearTimeout(this.commit_timer),this.commit_timer=null);return a.mount(this)};t.commit=function(a,c){this.commit_timer&&(clearTimeout(this.commit_timer),this.commit_timer=null);return this.db.commit(this,a,c)};t.destroy=function(){this.commit_timer&&(clearTimeout(this.commit_timer),this.commit_timer=null);return this.db.destroy()};function gb(a){a.commit_timer||(a.commit_timer=setTimeout(function(){a.commit_timer=null;a.db.commit(a,void 0,void 0)},1))}
t.clear=function(){this.map.clear();this.ctx.clear();this.reg.clear();this.cache&&this.cache.clear();this.db&&(this.commit_timer&&clearTimeout(this.commit_timer),this.commit_timer=null,this.commit_task=[{clear:!0}]);return this};t.append=function(a,c){return this.add(a,c,!0)};t.contain=function(a){return this.db?this.db.has(a):this.reg.has(a)};t.update=function(a,c){const b=this,e=this.remove(a);return e&&e.then?e.then(()=>b.add(a,c)):this.add(a,c)};
function mb(a){let c=0;if(a.constructor===Array)for(let b=0,e;b<a.length;b++)(e=a[b])&&(c+=e.length);else for(const b of a.entries()){const e=b[0],d=mb(b[1]);d?c+=d:a.delete(e)}return c}t.cleanup=function(){if(!this.fastupdate)return this;mb(this.map);this.depth&&mb(this.ctx);return this};t.searchCache=Va;
t.export=function(a,c,b=0,e=0){let d,f;switch(e){case 0:d="reg";f=wa(this.reg);break;case 1:d="cfg";f=null;break;case 2:d="map";f=sa(this.map,this.reg.size);break;case 3:d="ctx";f=ua(this.ctx,this.reg.size);break;default:return}return ya.call(this,a,c,d,f,b,e)};
t.import=function(a,c){if(c)switch("string"===typeof c&&(c=JSON.parse(c)),a=a.split("."),"json"===a[a.length-1]&&a.pop(),3===a.length&&a.shift(),a=1<a.length?a[1]:a[0],a){case "reg":this.fastupdate=!1;this.reg=xa(c,this.reg);break;case "map":this.map=ta(c,this.map);break;case "ctx":this.ctx=va(c,this.ctx)}};
t.serialize=function(a=!0){let c="",b="",e="";if(this.reg.size){let f;for(var d of this.reg.keys())f||(f=typeof d),c+=(c?",":"")+("string"===f?'"'+d+'"':d);c="index.reg=new Set(["+c+"]);";b=za(this.map,f);b="index.map=new Map(["+b+"]);";for(const g of this.ctx.entries()){d=g[0];let h=za(g[1],f);h="new Map(["+h+"])";h='["'+d+'",'+h+"]";e+=(e?",":"")+h}e="index.ctx=new Map(["+e+"]);"}return a?"function inject(index){"+c+b+e+"}":c+b+e};la(N.prototype);const nb="undefined"!==typeof window&&(window.indexedDB||window.mozIndexedDB||window.webkitIndexedDB||window.msIndexedDB),ob=["map","ctx","tag","reg","cfg"],Y=B();
function pb(a,c={}){if(!this)return new pb(a,c);"object"===typeof a&&(c=a,a=a.name);a||console.info("Default storage space was used, because a name was not passed.");this.id="flexsearch"+(a?":"+a.toLowerCase().replace(/[^a-z0-9_\-]/g,""):"");this.field=c.field?c.field.toLowerCase().replace(/[^a-z0-9_\-]/g,""):"";this.type=c.type;this.fastupdate=this.support_tag_search=!1;this.db=null;this.h={}}t=pb.prototype;t.mount=function(a){if(!a.encoder)return a.mount(this);a.db=this;return this.open()};
t.open=function(){if(this.db)return this.db;let a=this;navigator.storage&&navigator.storage.persist();Y[a.id]||(Y[a.id]=[]);Y[a.id].push(a.field);const c=nb.open(a.id,1);c.onupgradeneeded=function(){const b=a.db=this.result;for(let e=0,d;e<ob.length;e++){d=ob[e];for(let f=0,g;f<Y[a.id].length;f++)g=Y[a.id][f],b.objectStoreNames.contains(d+("reg"!==d?g?":"+g:"":""))||b.createObjectStore(d+("reg"!==d?g?":"+g:"":""))}};return a.db=Z(c,function(b){a.db=b;a.db.onversionchange=function(){a.close()}})};
t.close=function(){this.db&&this.db.close();this.db=null};t.destroy=function(){const a=nb.deleteDatabase(this.id);return Z(a)};t.clear=function(){const a=[];for(let b=0,e;b<ob.length;b++){e=ob[b];for(let d=0,f;d<Y[this.id].length;d++)f=Y[this.id][d],a.push(e+("reg"!==e?f?":"+f:"":""))}const c=this.db.transaction(a,"readwrite");for(let b=0;b<a.length;b++)c.objectStore(a[b]).clear();return Z(c)};
t.get=function(a,c,b=0,e=0,d=!0,f=!1){a=this.db.transaction((c?"ctx":"map")+(this.field?":"+this.field:""),"readonly").objectStore((c?"ctx":"map")+(this.field?":"+this.field:"")).get(c?c+":"+a:a);const g=this;return Z(a).then(function(h){let k=[];if(!h||!h.length)return k;if(d){if(!b&&!e&&1===h.length)return h[0];for(let l=0,n;l<h.length;l++)if((n=h[l])&&n.length){if(e>=n.length){e-=n.length;continue}const m=b?e+Math.min(n.length-e,b):n.length;for(let q=e;q<m;q++)k.push(n[q]);e=0;if(k.length===b)break}return f?
g.enrich(k):k}return h})};t.tag=function(a,c=0,b=0,e=!1){a=this.db.transaction("tag"+(this.field?":"+this.field:""),"readonly").objectStore("tag"+(this.field?":"+this.field:"")).get(a);const d=this;return Z(a).then(function(f){if(!f||!f.length||b>=f.length)return[];if(!c&&!b)return f;f=f.slice(b,b+c);return e?d.enrich(f):f})};
t.enrich=function(a){"object"!==typeof a&&(a=[a]);const c=this.db.transaction("reg","readonly").objectStore("reg"),b=[];for(let e=0;e<a.length;e++)b[e]=Z(c.get(a[e]));return Promise.all(b).then(function(e){for(let d=0;d<e.length;d++)e[d]={id:a[d],doc:e[d]?JSON.parse(e[d]):null};return e})};t.has=function(a){a=this.db.transaction("reg","readonly").objectStore("reg").getKey(a);return Z(a).then(function(c){return!!c})};t.search=null;t.info=function(){};
t.transaction=function(a,c,b){a+="reg"!==a?this.field?":"+this.field:"":"";let e=this.h[a+":"+c];if(e)return b.call(this,e);let d=this.db.transaction(a,c);this.h[a+":"+c]=e=d.objectStore(a);const f=b.call(this,e);this.h[a+":"+c]=null;return Z(d).finally(function(){d=e=null;return f})};
t.commit=async function(a,c,b){if(c)await this.clear(),a.commit_task=[];else{let e=a.commit_task;a.commit_task=[];for(let d=0,f;d<e.length;d++)if(f=e[d],f.clear){await this.clear();c=!0;break}else e[d]=f.del;c||(b||(e=e.concat(aa(a.reg))),e.length&&await this.remove(e))}a.reg.size&&(await this.transaction("map","readwrite",function(e){for(const d of a.map){const f=d[0],g=d[1];g.length&&(c?e.put(g,f):e.get(f).onsuccess=function(){let h=this.result;var k;if(h&&h.length){const l=Math.max(h.length,g.length);
for(let n=0,m,q;n<l;n++)if((q=g[n])&&q.length){if((m=h[n])&&m.length)for(k=0;k<q.length;k++)m.push(q[k]);else h[n]=q;k=1}}else h=g,k=1;k&&e.put(h,f)})}}),await this.transaction("ctx","readwrite",function(e){for(const d of a.ctx){const f=d[0],g=d[1];for(const h of g){const k=h[0],l=h[1];l.length&&(c?e.put(l,f+":"+k):e.get(f+":"+k).onsuccess=function(){let n=this.result;var m;if(n&&n.length){const q=Math.max(n.length,l.length);for(let p=0,r,u;p<q;p++)if((u=l[p])&&u.length){if((r=n[p])&&r.length)for(m=
0;m<u.length;m++)r.push(u[m]);else n[p]=u;m=1}}else n=l,m=1;m&&e.put(n,f+":"+k)})}}}),a.store?await this.transaction("reg","readwrite",function(e){for(const d of a.store){const f=d[0],g=d[1];e.put("object"===typeof g?JSON.stringify(g):1,f)}}):a.bypass||await this.transaction("reg","readwrite",function(e){for(const d of a.reg.keys())e.put(1,d)}),a.tag&&await this.transaction("tag","readwrite",function(e){for(const d of a.tag){const f=d[0],g=d[1];g.length&&(e.get(f).onsuccess=function(){let h=this.result;
h=h&&h.length?h.concat(g):g;e.put(h,f)})}}),a.map.clear(),a.ctx.clear(),a.tag&&a.tag.clear(),a.store&&a.store.clear(),a.document||a.reg.clear())};function qb(a,c,b){const e=a.value;let d,f=0;for(let g=0,h;g<e.length;g++){if(h=b?e:e[g]){for(let k=0,l,n;k<c.length;k++)if(n=c[k],l=h.indexOf(n),0<=l)if(d=1,1<h.length)h.splice(l,1);else{e[g]=[];break}f+=h.length}if(b)break}f?d&&a.update(e):a.delete();a.continue()}
t.remove=function(a){"object"!==typeof a&&(a=[a]);return Promise.all([this.transaction("map","readwrite",function(c){c.openCursor().onsuccess=function(){const b=this.result;b&&qb(b,a)}}),this.transaction("ctx","readwrite",function(c){c.openCursor().onsuccess=function(){const b=this.result;b&&qb(b,a)}}),this.transaction("tag","readwrite",function(c){c.openCursor().onsuccess=function(){const b=this.result;b&&qb(b,a,!0)}}),this.transaction("reg","readwrite",function(c){for(let b=0;b<a.length;b++)c.delete(a[b])})])};
function Z(a,c){return new Promise((b,e)=>{a.onsuccess=a.oncomplete=function(){c&&c(this.result);c=null;b(this.result)};a.onerror=a.onblocked=e;a=null})};const rb={Index:N,Charset:cb,Encoder:J,Document:U,Worker:P,Resolver:W,IndexedDB:pb,Language:{}},tb="undefined"!==typeof self?self:"undefined"!==typeof global?global:self;let ub;(ub=tb.define)&&ub.amd?ub([],function(){return rb}):"object"===typeof tb.exports?tb.exports=rb:tb.FlexSearch=rb;}(this||self));
+22
View File
@@ -0,0 +1,22 @@
// The section must not be in the banner.js (body) file because it can create a quick flash.
if (localStorage.getItem('banner-closed')) {
document.documentElement.style.setProperty("--hextra-banner-height", "0px");
document.documentElement.classList.add("hextra-banner-hidden");
}
;
// The section must not be in the theme.js (body) file because it can create a quick flash (switch between light and dark).
function setTheme(theme) {
document.documentElement.classList.remove("light", "dark");
if (theme !== "light" && theme !== "dark") {
theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
}
document.documentElement.classList.add(theme);
document.documentElement.style.colorScheme = theme;
}
setTheme("color-theme" in localStorage ? localStorage.getItem("color-theme") : 'light')
@@ -0,0 +1 @@
localStorage.getItem("banner-closed")&&(document.documentElement.style.setProperty("--hextra-banner-height","0px"),document.documentElement.classList.add("hextra-banner-hidden"));function setTheme(e){document.documentElement.classList.remove("light","dark"),e!=="light"&&e!=="dark"&&(e=window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"),document.documentElement.classList.add(e),document.documentElement.style.colorScheme=e}setTheme("color-theme"in localStorage?localStorage.getItem("color-theme"):"light")
+1082
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>RAPPORT Lizenz &amp; Mitwirkende</title><link>https://rapport.gabrielevarano.ch/lizenz/</link><description>Recent content in Lizenz &amp; Mitwirkende on RAPPORT</description><generator>Hugo -- gohugo.io</generator><language>de</language><atom:link href="https://rapport.gabrielevarano.ch/lizenz/index.xml" rel="self" type="application/rss+xml"/></channel></rss>
+1
View File
@@ -0,0 +1 @@
User-agent: *
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>RAPPORT Rapport Server</title><link>https://rapport.gabrielevarano.ch/server/</link><description>Recent content in Rapport Server on RAPPORT</description><generator>Hugo -- gohugo.io</generator><language>de</language><atom:link href="https://rapport.gabrielevarano.ch/server/index.xml" rel="self" type="application/rss+xml"/></channel></rss>
+20
View File
@@ -0,0 +1,20 @@
{
"name": "Hextra",
"short_name": "Hextra",
"start_url": "index.html",
"icons": [
{
"src": "android-chrome-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "android-chrome-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#000000",
"background_color": "#000000",
"display": "standalone"
}
+1
View File
@@ -0,0 +1 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml"><url><loc>https://rapport.gabrielevarano.ch/docs/</loc></url><url><loc>https://rapport.gabrielevarano.ch/docs/erste-schritte/</loc></url><url><loc>https://rapport.gabrielevarano.ch/features/zeiterfassung/</loc></url><url><loc>https://rapport.gabrielevarano.ch/features/</loc></url><url><loc>https://rapport.gabrielevarano.ch/docs/installation/</loc></url><url><loc>https://rapport.gabrielevarano.ch/features/rechnungen/</loc></url><url><loc>https://rapport.gabrielevarano.ch/downloads/</loc></url><url><loc>https://rapport.gabrielevarano.ch/docs/einrichtung/</loc></url><url><loc>https://rapport.gabrielevarano.ch/features/projekte/</loc></url><url><loc>https://rapport.gabrielevarano.ch/docs/arbeitsablauf/</loc></url><url><loc>https://rapport.gabrielevarano.ch/faq/</loc></url><url><loc>https://rapport.gabrielevarano.ch/features/mitarbeiter/</loc></url><url><loc>https://rapport.gabrielevarano.ch/server/</loc></url><url><loc>https://rapport.gabrielevarano.ch/docs/datenhaltung/</loc></url><url><loc>https://rapport.gabrielevarano.ch/features/spesen/</loc></url><url><loc>https://rapport.gabrielevarano.ch/features/protokolle/</loc></url><url><loc>https://rapport.gabrielevarano.ch/docs/web-modus/</loc></url><url><loc>https://rapport.gabrielevarano.ch/features/auto-updater/</loc></url><url><loc>https://rapport.gabrielevarano.ch/docs/entwicklung/</loc></url><url><loc>https://rapport.gabrielevarano.ch/features/system-tray/</loc></url><url><loc>https://rapport.gabrielevarano.ch/docs/troubleshooting/</loc></url><url><loc>https://rapport.gabrielevarano.ch/docs/changelog/</loc></url><url><loc>https://rapport.gabrielevarano.ch/lizenz/</loc></url><url><loc>https://rapport.gabrielevarano.ch/categories/</loc></url><url><loc>https://rapport.gabrielevarano.ch/</loc></url><url><loc>https://rapport.gabrielevarano.ch/tags/</loc></url></urlset>
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>RAPPORT Tags</title><link>https://rapport.gabrielevarano.ch/tags/</link><description>Recent content in Tags on RAPPORT</description><generator>Hugo -- gohugo.io</generator><language>de</language><atom:link href="https://rapport.gabrielevarano.ch/tags/index.xml" rel="self" type="application/rss+xml"/></channel></rss>
Binary file not shown.
@@ -0,0 +1,17 @@
{
"image": "mcr.microsoft.com/devcontainers/go:1",
"features": {
"ghcr.io/devcontainers/features/hugo:1": {
"extended": true,
"version": "0.161.1"
},
"ghcr.io/devcontainers/features/node:1": {}
},
"customizations": {
"vscode": {
"extensions": ["mhutchie.git-graph", "esbenp.prettier-vscode", "tamasfe.even-better-toml", "budparr.language-hugo-vscode"]
}
},
"postCreateCommand": "npm install",
"forwardPorts": [1313]
}
+3
View File
@@ -0,0 +1,3 @@
# Mark generated files so they are collapsed by default in GitHub diffs
assets/css/compiled/main.css linguist-generated=true
docs/hugo_stats.json linguist-generated=true
+9
View File
@@ -0,0 +1,9 @@
node_modules/
public/
resources/
.hugo_build.lock
# Playwright
playwright-report/
test-results/
+21
View File
@@ -0,0 +1,21 @@
{
"plugins": [
"prettier-plugin-go-template"
],
"goTemplateBracketSpacing": true,
"htmlWhitespaceSensitivity": "css",
"printWidth": 200,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "es5",
"overrides": [
{
"files": [
"*.html"
],
"options": {
"parser": "go-template"
}
}
]
}
+4
View File
@@ -0,0 +1,4 @@
{
"editor.tabSize": 2,
"markdown.extension.toc.levels": "2..6"
}
+193
View File
@@ -0,0 +1,193 @@
# AGENTS.md
This file provides guidance to AI coding agents when working with code in this repository.
## Project Overview
Hextra is a modern, responsive Hugo theme designed for creating documentation websites, technical blogs, and static sites. Built with Tailwind CSS, it offers features like full-text search, dark mode, multi-language support, and extensive customization options.
## Development Commands
### Initial Setup
When working in a new worktree or fresh clone without `node_modules`, run `npm install` first to install dependencies:
```bash
npm install
```
### Development Server
```bash
# Start development server with theme reloading (recommended for theme development)
npm run dev:theme
```
### Building
```bash
# Build the example site
npm run build
# Build CSS assets only
npm run build:css
```
## Architecture Overview
### Hugo Theme Structure
- **Base Layout**: `layouts/baseof.html` wraps all pages
- **Specialized Layouts**: `layouts/docs/`, `layouts/blog/`, `layouts/hextra-home.html`
- **Partials**: Reusable components in `layouts/_partials/`
- Core UI: `navbar.html`, `sidebar.html`, `footer.html`, `breadcrumb.html`, `toc.html`
- Utilities: `layouts/_partials/utils/` for helper functions
- Custom overrides: `layouts/_partials/custom/` for user customizations
- **Shortcodes**: Custom Markdown extensions in `layouts/_shortcodes/`
- **Render Hooks**: Custom Markdown rendering in `layouts/_markup/` for codeblocks, headings, images, and links
### Asset Organization
```
assets/
├── css/
│ ├── styles.css # Main stylesheet (Tailwind entry point)
│ ├── compiled/main.css # Built CSS output (generated)
│ ├── components/ # Component-specific styles
│ ├── chroma/ # Syntax highlighting themes
│ └── custom.css # User customization entry point
└── js/
├── core/ # Core JS components
└── flexsearch.js # Search functionality
```
### Key Components
- **Search**: FlexSearch-powered full-text search (`assets/js/flexsearch.js`)
- **Navigation**: Responsive navbar and auto-generated sidebar
- **Theme Toggle**: Dark/light mode switching
- **Internationalization**: 20+ language support in `i18n/`
### Content Features
- **Shortcodes**: `callout`, `card`, `cards`, `tabs`, `tab`, `details`, `steps`, `filetree`, `jupyter`, `badge`, `icon`, `pdf`, `include`, `asciinema`, `term`
- **Code Features**: Syntax highlighting (Chroma), copy buttons, line numbers via render hooks
- **SEO**: Open Graph, Twitter Cards, structured data
- **Performance**: Minimal JavaScript, optimized CSS with Tailwind
## Development Workflow
### Example Site Development
The `docs/` directory serves as both documentation and testing ground:
- Test new features here before releasing
- Configuration examples in `docs/hugo.yaml` showing multi-language setup
- Content examples demonstrate all theme capabilities
- Run from docs with: `hugo server --themesDir=../..`
### CSS Development Workflow
- Source: `assets/css/styles.css` (main stylesheet)
- Build process: Tailwind CSS → PostCSS → `assets/css/compiled/main.css`
- Component styles organized in `assets/css/components/`
- Chroma syntax highlighting themes in `assets/css/chroma/`
- CSS compilation requires Node.js dependencies (PostCSS, Tailwind CSS v4+)
#### Rebuilding CSS after template changes
Tailwind CSS relies on `docs/hugo_stats.json` to know which HTML tags, classes, and IDs are actually used in the built site, so it can tree-shake unused styles. When you modify layouts, partials, or shortcodes you must **regenerate `hugo_stats.json` first**, then rebuild the CSS:
1. **Generate `docs/hugo_stats.json`** — Run Hugo with the `dev.toml` config (which sets `build.buildStats.enable = true`):
```bash
# Using npm (starts a dev server that writes hugo_stats.json on every rebuild):
npm run dev:theme
# Or a one-shot build using the raw Hugo command:
hugo --config=hugo.yaml,../dev.toml --themesDir=../.. --source=docs
```
2. **Build the CSS** — With an up-to-date `hugo_stats.json` in place, compile the stylesheet:
```bash
npm run build:css
```
> **Why two steps?** `dev.toml` mounts `docs/hugo_stats.json` into the Hugo asset pipeline (`assets/notwatching/hugo_stats.json`) and configures a cache-buster so that changes to the stats file trigger a CSS recompile during `dev:theme`. When running outside the dev server you need to perform these steps manually in order.
### Customization Points
- Custom partials: `layouts/_partials/custom/`
- Custom CSS: `assets/css/custom.css`
- Site-specific overrides: Copy any layout to your site's `layouts/` directory
## Configuration & Requirements
### Theme Requirements
- Hugo minimum version: 0.146.0 (extended version required - see `theme.toml`)
- Go 1.20+ (as specified in `go.mod`)
- Node.js for CSS compilation (PostCSS, Tailwind CSS v4+)
### Key Configuration Files
- `docs/hugo.yaml` - Example Hugo configuration with multi-language setup
- `postcss.config.mjs` - PostCSS configuration for CSS processing
- `package.json` - Node.js dependencies and build scripts
### Development Environment
- Default Hugo development server: Port 1313
- Development server runs with `--disableFastRender -D` for better development experience
- Theme development uses `--logLevel=debug` for detailed logging
### Multi-language Support
- Configure languages in `hugo.yaml` (supports 20+ languages including RTL)
- Translation files in `i18n/` directory (e.g., `en.yaml`, `fa.yaml`, `ja.yaml`, `zh-cn.yaml`)
- Example supports English, Persian (RTL), Japanese, and Simplified Chinese
## Theme Development Guidelines
### Hugo Theme Conventions
- Theme files in this repository override Hugo defaults
- Follow Hugo's theme development guidelines for compatibility
- Maintain backward compatibility with existing configurations
### JavaScript & Performance
- All JavaScript components are designed to have minimal footprint
- Core JS components in `assets/js/core/`: `theme.js`, `nav-menu.js`, `code-copy.js`, `sidebar.js`, `tabs.js`, etc.
- FlexSearch powers offline full-text search (`assets/js/flexsearch.js`)
### CSS Architecture
- Uses Tailwind CSS v4+ with PostCSS processing
- Component-based CSS organization in `assets/css/components/`
- Compiled output goes to `assets/css/compiled/main.css`
- Prettier formatting for Go templates and code consistency
### Accessibility (WCAG Compliance)
All new features and UI changes must follow the [Web Content Accessibility Guidelines (WCAG) 2.2](https://www.w3.org/TR/WCAG22/) at the **AA** conformance level. Key requirements:
- **Semantic HTML**: Use appropriate elements (`<nav>`, `<main>`, `<aside>`, `<button>`, `<ul>`, etc.) instead of generic `<div>`/`<span>` where applicable.
- **ARIA attributes**: Add `aria-label`, `aria-expanded`, `aria-controls`, `aria-current`, `role`, and other ARIA attributes to interactive components (menus, toggles, dropdowns, modals) so screen readers can interpret them.
- **Keyboard navigation**: All interactive elements must be reachable and operable via keyboard (`Tab`, `Enter`, `Escape`, arrow keys). Manage focus appropriately when opening/closing menus, modals, and drawers.
- **Focus indicators**: Never remove visible focus outlines. Use the existing `hextra-focus` utility or equivalent visible focus ring styles.
- **Color contrast**: Text and interactive elements must meet WCAG AA contrast ratios (4.5:1 for normal text, 3:1 for large text). Verify in both light and dark modes.
- **Images and icons**: Decorative SVGs/icons should have `aria-hidden="true"`. Meaningful images need descriptive `alt` text.
- **Skip links and landmarks**: Preserve existing skip-navigation links and ARIA landmark roles (`role="navigation"`, `role="search"`, etc.).
- **Live regions**: Use `aria-live` for dynamic content updates (e.g., search results, status messages) so assistive technology announces changes.
- **Form controls**: Associate `<label>` elements with inputs. Provide accessible names for buttons that contain only icons.
When introducing a new component or modifying an existing one, verify it works with keyboard-only navigation and review the rendered HTML for proper semantics and ARIA usage.
### Testing & Quality Assurance
- Test all changes in `docs/` before releasing
- Use `npm run dev:theme` for theme development with hot reloading
- Format code with `npx prettier --write .` before committing
- Verify multi-language functionality across supported languages
+193
View File
@@ -0,0 +1,193 @@
# AGENTS.md
This file provides guidance to AI coding agents when working with code in this repository.
## Project Overview
Hextra is a modern, responsive Hugo theme designed for creating documentation websites, technical blogs, and static sites. Built with Tailwind CSS, it offers features like full-text search, dark mode, multi-language support, and extensive customization options.
## Development Commands
### Initial Setup
When working in a new worktree or fresh clone without `node_modules`, run `npm install` first to install dependencies:
```bash
npm install
```
### Development Server
```bash
# Start development server with theme reloading (recommended for theme development)
npm run dev:theme
```
### Building
```bash
# Build the example site
npm run build
# Build CSS assets only
npm run build:css
```
## Architecture Overview
### Hugo Theme Structure
- **Base Layout**: `layouts/baseof.html` wraps all pages
- **Specialized Layouts**: `layouts/docs/`, `layouts/blog/`, `layouts/hextra-home.html`
- **Partials**: Reusable components in `layouts/_partials/`
- Core UI: `navbar.html`, `sidebar.html`, `footer.html`, `breadcrumb.html`, `toc.html`
- Utilities: `layouts/_partials/utils/` for helper functions
- Custom overrides: `layouts/_partials/custom/` for user customizations
- **Shortcodes**: Custom Markdown extensions in `layouts/_shortcodes/`
- **Render Hooks**: Custom Markdown rendering in `layouts/_markup/` for codeblocks, headings, images, and links
### Asset Organization
```
assets/
├── css/
│ ├── styles.css # Main stylesheet (Tailwind entry point)
│ ├── compiled/main.css # Built CSS output (generated)
│ ├── components/ # Component-specific styles
│ ├── chroma/ # Syntax highlighting themes
│ └── custom.css # User customization entry point
└── js/
├── core/ # Core JS components
└── flexsearch.js # Search functionality
```
### Key Components
- **Search**: FlexSearch-powered full-text search (`assets/js/flexsearch.js`)
- **Navigation**: Responsive navbar and auto-generated sidebar
- **Theme Toggle**: Dark/light mode switching
- **Internationalization**: 20+ language support in `i18n/`
### Content Features
- **Shortcodes**: `callout`, `card`, `cards`, `tabs`, `tab`, `details`, `steps`, `filetree`, `jupyter`, `badge`, `icon`, `pdf`, `include`, `asciinema`, `term`
- **Code Features**: Syntax highlighting (Chroma), copy buttons, line numbers via render hooks
- **SEO**: Open Graph, Twitter Cards, structured data
- **Performance**: Minimal JavaScript, optimized CSS with Tailwind
## Development Workflow
### Example Site Development
The `docs/` directory serves as both documentation and testing ground:
- Test new features here before releasing
- Configuration examples in `docs/hugo.yaml` showing multi-language setup
- Content examples demonstrate all theme capabilities
- Run from docs with: `hugo server --themesDir=../..`
### CSS Development Workflow
- Source: `assets/css/styles.css` (main stylesheet)
- Build process: Tailwind CSS → PostCSS → `assets/css/compiled/main.css`
- Component styles organized in `assets/css/components/`
- Chroma syntax highlighting themes in `assets/css/chroma/`
- CSS compilation requires Node.js dependencies (PostCSS, Tailwind CSS v4+)
#### Rebuilding CSS after template changes
Tailwind CSS relies on `docs/hugo_stats.json` to know which HTML tags, classes, and IDs are actually used in the built site, so it can tree-shake unused styles. When you modify layouts, partials, or shortcodes you must **regenerate `hugo_stats.json` first**, then rebuild the CSS:
1. **Generate `docs/hugo_stats.json`** — Run Hugo with the `dev.toml` config (which sets `build.buildStats.enable = true`):
```bash
# Using npm (starts a dev server that writes hugo_stats.json on every rebuild):
npm run dev:theme
# Or a one-shot build using the raw Hugo command:
hugo --config=hugo.yaml,../dev.toml --themesDir=../.. --source=docs
```
2. **Build the CSS** — With an up-to-date `hugo_stats.json` in place, compile the stylesheet:
```bash
npm run build:css
```
> **Why two steps?** `dev.toml` mounts `docs/hugo_stats.json` into the Hugo asset pipeline (`assets/notwatching/hugo_stats.json`) and configures a cache-buster so that changes to the stats file trigger a CSS recompile during `dev:theme`. When running outside the dev server you need to perform these steps manually in order.
### Customization Points
- Custom partials: `layouts/_partials/custom/`
- Custom CSS: `assets/css/custom.css`
- Site-specific overrides: Copy any layout to your site's `layouts/` directory
## Configuration & Requirements
### Theme Requirements
- Hugo minimum version: 0.146.0 (extended version required - see `theme.toml`)
- Go 1.20+ (as specified in `go.mod`)
- Node.js for CSS compilation (PostCSS, Tailwind CSS v4+)
### Key Configuration Files
- `docs/hugo.yaml` - Example Hugo configuration with multi-language setup
- `postcss.config.mjs` - PostCSS configuration for CSS processing
- `package.json` - Node.js dependencies and build scripts
### Development Environment
- Default Hugo development server: Port 1313
- Development server runs with `--disableFastRender -D` for better development experience
- Theme development uses `--logLevel=debug` for detailed logging
### Multi-language Support
- Configure languages in `hugo.yaml` (supports 20+ languages including RTL)
- Translation files in `i18n/` directory (e.g., `en.yaml`, `fa.yaml`, `ja.yaml`, `zh-cn.yaml`)
- Example supports English, Persian (RTL), Japanese, and Simplified Chinese
## Theme Development Guidelines
### Hugo Theme Conventions
- Theme files in this repository override Hugo defaults
- Follow Hugo's theme development guidelines for compatibility
- Maintain backward compatibility with existing configurations
### JavaScript & Performance
- All JavaScript components are designed to have minimal footprint
- Core JS components in `assets/js/core/`: `theme.js`, `nav-menu.js`, `code-copy.js`, `sidebar.js`, `tabs.js`, etc.
- FlexSearch powers offline full-text search (`assets/js/flexsearch.js`)
### CSS Architecture
- Uses Tailwind CSS v4+ with PostCSS processing
- Component-based CSS organization in `assets/css/components/`
- Compiled output goes to `assets/css/compiled/main.css`
- Prettier formatting for Go templates and code consistency
### Accessibility (WCAG Compliance)
All new features and UI changes must follow the [Web Content Accessibility Guidelines (WCAG) 2.2](https://www.w3.org/TR/WCAG22/) at the **AA** conformance level. Key requirements:
- **Semantic HTML**: Use appropriate elements (`<nav>`, `<main>`, `<aside>`, `<button>`, `<ul>`, etc.) instead of generic `<div>`/`<span>` where applicable.
- **ARIA attributes**: Add `aria-label`, `aria-expanded`, `aria-controls`, `aria-current`, `role`, and other ARIA attributes to interactive components (menus, toggles, dropdowns, modals) so screen readers can interpret them.
- **Keyboard navigation**: All interactive elements must be reachable and operable via keyboard (`Tab`, `Enter`, `Escape`, arrow keys). Manage focus appropriately when opening/closing menus, modals, and drawers.
- **Focus indicators**: Never remove visible focus outlines. Use the existing `hextra-focus` utility or equivalent visible focus ring styles.
- **Color contrast**: Text and interactive elements must meet WCAG AA contrast ratios (4.5:1 for normal text, 3:1 for large text). Verify in both light and dark modes.
- **Images and icons**: Decorative SVGs/icons should have `aria-hidden="true"`. Meaningful images need descriptive `alt` text.
- **Skip links and landmarks**: Preserve existing skip-navigation links and ARIA landmark roles (`role="navigation"`, `role="search"`, etc.).
- **Live regions**: Use `aria-live` for dynamic content updates (e.g., search results, status messages) so assistive technology announces changes.
- **Form controls**: Associate `<label>` elements with inputs. Provide accessible names for buttons that contain only icons.
When introducing a new component or modifying an existing one, verify it works with keyboard-only navigation and review the rendered HTML for proper semantics and ARIA usage.
### Testing & Quality Assurance
- Test all changes in `docs/` before releasing
- Use `npm run dev:theme` for theme development with hot reloading
- Format code with `npx prettier --write .` before committing
- Verify multi-language functionality across supported languages
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Xin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+49
View File
@@ -0,0 +1,49 @@
<div align="center">
<h1 align="center">هگزترا</h1>
<sup align="center"><a href="README.md">English</a> | <a href="README.zh-cn.md">简体中文</a> <a href="README.fa.md">فارسی</a></sup>
<p align="center">تم هیوگو مدرن، پاسخگو و دارای امکانات کامل برای ایجاد وب‌سایت‌های استاتیک زیبا.</p>
نسخه‌ی نمایشی → [imfing.github.io/hextra](https://imfing.github.io/hextra/fa)
</div>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/5097752/263550533-c18343ca-3848-4230-b5c0-ee989d7916da.png">
<img alt="Hextra" src="https://user-images.githubusercontent.com/5097752/263550528-663599f9-17a1-4686-b5c4-3da233b5034d.png">
</picture>
<div align="right">
<a href="https://github.com/imfing/hextra/actions/workflows/pages.yml"><img alt="GitHub Actions Status" src="https://github.com/imfing/hextra/actions/workflows/pages.yml/badge.svg"></a> <a href="https://app.netlify.com/sites/hugo-hextra/deploys"><img alt="Netlify Status" src="https://api.netlify.com/api/v1/badges/61d6e55a-2447-487e-b59f-c9537e5df175/deploy-status"></a>
</div>
## ویژگی‌ها
- **طراحی زیبا** - با الهام از Nextra، هگزترا از Tailwind CSS برای ارائه یک طراحی مدرن که سایت شما را برجسته می‌کند، استفاده می‌کند.
- **طراحی واکنش‌گرا و حالت تیره** - در تمام دستگاه‌ها، از تلفن همراه، تبلت تا دسکتاپ، عالی به نظر می‌رسد. حالت تیره نیز برای انطباق با شرایط مختلف روشنایی پشتیبانی می‌شود.
- **سریع و سبک** - طراحی شده توسط Hugo، یک ایجادکننده سایت استاتیک سریع مثل رعد و برق که در یک فایل باینری قرار گرفته است، هگزترا ردپای خود را به حداقل می‌رساند. برای استفاده از آن به جاوااسکریپت یا Node.js نیازی ندارید.
- **جستجوی متن کامل** - جستجوی متن کاملا آفلاین داخلی طراحی شده توسط FlexSearch، بدون نیاز به پیکربندی اضافی.
- **امکانات کامل** - برای بهتر کردن محتوای شما مارک‌داون، برجسته‌کردن سینتکس، فرمول‌های ریاضی LaTeX، نمودارها و عناصر Shortcodeها را شامل میشه. فهرست مطالب، بردکرامب، صفحه‌بندی، پیمایش نوار کناری و موارد دیگر همه به صورت خودکار تولید می‌شوند.
- **چند زبانه و سئو آماده** - سایت‌های چند زبانه با حالت چند زبانه Hugo راحت ساخته می‌شوند. پشتیبانی خارج از جعبه برای برچسب‌های سئو، Open Graph و کارت‌های توییتر گنجانده شده است.
- **پشتیبانی از دسترس‌پذیری** - اجزای تعاملی از نشانه‌گذاری معنایی، رفتار سازگار با صفحه‌کلید و بررسی‌های خودکار دسترس‌پذیری استفاده می‌کنند تا رابط کاربری در گردش‌کارهای رایج فناوری‌های کمکی قابل استفاده بماند.
## شروع کنید
### شروع سریع از طریق Template
استفاده از [Hextra Starter Template](https://github.com/imfing/hextra-starter-template) ساده‌ترین روش برای راه‌اندازی سریع یک وب‌سایت جدید با تم هگزترا است. با کلیک بر روی دکمه "Use this template" در بالای صفحه مخزن شروع کنید.
مخزن تم همچنین شامل یک [گردش کار گیت‌هاب Actions](https://docs.github.com/en/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site#publishing-with-a-custom-github-actions-workflow) رای به‌کاراندازی وب‌سایت شما در گیت‌هاب Pages است.
<img alt="Hextra Starter Template" src="https://user-images.githubusercontent.com/5097752/263551418-c403b9a9-a76c-47a6-8466-513d772ef0b7.jpg" width=600/>
### استفاده
برای اطلاعات بیشتر به بخش [مستندات](https://imfing.github.io/hextra/fa/docs) مراجعه کنید.
## مشارکت کردن
از مشارکت افراد جدید استقبال می‌کنیم.
برای شروع، [راهنمای مشارکت](.github/CONTRIBUTING.md) را بررسی کنید.
## مجوز
[مجوز MIT](./LICENSE)

Some files were not shown because too many files have changed in this diff Show More