Initial commit: Rapport Website (Hugo + Hextra)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@@ -0,0 +1,6 @@
|
|||||||
|
# Hugo
|
||||||
|
.hugo_build.lock
|
||||||
|
resources/_gen/
|
||||||
|
|
||||||
|
# macOS
|
||||||
|
.DS_Store
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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 & 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 & 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>
|
||||||
@@ -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.
|
||||||
@@ -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
|
||||||
@@ -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.0–0.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).
|
||||||
@@ -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** | **~ 1–2 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/).
|
||||||
@@ -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. 25–30 |
|
||||||
|
| **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).
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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).
|
||||||
@@ -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 (3–5 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
|
||||||
@@ -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) |
|
||||||
|
| **2–5 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).
|
||||||
@@ -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 (M1–M4)** | [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 (M1–M4)** | [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`.
|
||||||
@@ -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.
|
||||||
@@ -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 >}}
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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).
|
||||||
@@ -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.
|
||||||
@@ -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
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
copyright: '© 2026 [RAPPORT](https://git.kgva.ch/karim/RAPPORT) · [AGPL-3.0](/lizenz/) · Teil von [OpenBureau](/lizenz/#openbureau)'
|
||||||
@@ -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 }}
|
||||||
@@ -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>
|
||||||
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 27 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
@@ -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>
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
@@ -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.")}}()
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
|
After Width: | Height: | Size: 340 B |
|
After Width: | Height: | Size: 753 B |
|
After Width: | Height: | Size: 15 KiB |
@@ -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 |
@@ -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>
|
||||||
|
<p><span class="rapport-status active">In Arbeit</span></p>
|
||||||
|
<p><strong>Tages- und Wochenraster</strong> mit Drag &amp; Drop. Auswertungen pro Mitarbeiter und Projekt. Ferienverwaltung mit Prorata und Jahresabschluss.</p>
|
||||||
|
<h2>Konzept<span class="hx:absolute hx:-mt-20" id="konzept"></span>
|
||||||
|
<a href="#konzept" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><p>Die Zeiterfassung ist das <strong>Kernmodul</strong> von RAPPORT — alle anderen Module (Rechnungen, Auswertungen, Lohnabrechnung) greifen auf die hier erfassten Stunden zu.</p>
|
||||||
|
<h2>Eingabe<span class="hx:absolute hx:-mt-20" id="eingabe"></span>
|
||||||
|
<a href="#eingabe" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><ul>
|
||||||
|
<li><strong>Wochenraster</strong> mit den 5 (oder 7) Arbeitstagen</li>
|
||||||
|
<li><strong>Halbstunden-Slots</strong> von 06:00 bis 22:00</li>
|
||||||
|
<li><strong>Klick</strong> oder <strong>Drag</strong> über mehrere Slots</li>
|
||||||
|
<li>Jeder Eintrag wird einem <strong>Projekt</strong> zugewiesen (Pflichtfeld)</li>
|
||||||
|
<li>Mehrfacheinträge pro Slot möglich (z. B. parallele Telefonate)</li>
|
||||||
|
</ul>
|
||||||
|
<h2>Auswertungen<span class="hx:absolute hx:-mt-20" id="auswertungen"></span>
|
||||||
|
<a href="#auswertungen" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><p>Pro Mitarbeiter und pro Projekt:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Geleistete Stunden vs. Soll-Pensum</li>
|
||||||
|
<li>Ferienanspruch / -saldo (mit Prorata bei Eintritt unter Jahr)</li>
|
||||||
|
<li>Überstunden-Saldo</li>
|
||||||
|
<li>Stundenaufschlüsselung nach SIA-Phase pro Projekt</li>
|
||||||
|
</ul>
|
||||||
|
<h2>Jahresabschluss<span class="hx:absolute hx:-mt-20" id="jahresabschluss"></span>
|
||||||
|
<a href="#jahresabschluss" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><p>Am Jahresende:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Ferien-Restguthaben übertragen oder auszahlen</li>
|
||||||
|
<li>Überstunden ausgleichen oder vergüten</li>
|
||||||
|
<li>Neues Jahr automatisch initialisieren</li>
|
||||||
|
</ul>
|
||||||
|
<h2>Verwandte Module<span class="hx:absolute hx:-mt-20" id="verwandte-module"></span>
|
||||||
|
<a href="#verwandte-module" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><ul>
|
||||||
|
<li><a href="../rechnungen">Rechnungen</a> — Stundensatz-Rechnungen ziehen direkt aus der Zeiterfassung</li>
|
||||||
|
<li><a href="../projekte">Projekte</a> — Stunden-Auswertung pro SIA-Phase</li>
|
||||||
|
<li><a href="../mitarbeiter">Mitarbeiter</a> — Pensum, Ferienanspruch</li>
|
||||||
|
</ul></description></item><item><title>Rechnungen & 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>
|
||||||
|
<p><span class="rapport-status active">In Arbeit</span></p>
|
||||||
|
<p><strong>QR-Einzahlungsscheine, SIA-Phasen, Akonto-, Teil- und Schlussrechnungen.</strong> Offerten sind in Projekte und Rechnungen konvertierbar. PDF-Export.</p>
|
||||||
|
<h2>Workflow<span class="hx:absolute hx:-mt-20" id="workflow"></span>
|
||||||
|
<a href="#workflow" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><ol>
|
||||||
|
<li><strong>Offerte</strong> erstellen — auf Basis SIA 102 oder pauschal</li>
|
||||||
|
<li>Kunde nimmt an → <strong>konvertieren in Projekt + Rechnung</strong></li>
|
||||||
|
<li><strong>Akonto-Rechnungen</strong> während der Projektlaufzeit</li>
|
||||||
|
<li><strong>Schlussrechnung</strong> mit Differenz zum bisher Akonto-bezahlten</li>
|
||||||
|
</ol>
|
||||||
|
<h2>QR-Einzahlungsschein<span class="hx:absolute hx:-mt-20" id="qr-einzahlungsschein"></span>
|
||||||
|
<a href="#qr-einzahlungsschein" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><p>Schweizer <strong>QR-Rechnung</strong> nach Norm — direkt eingebettet in die PDF.</p>
|
||||||
|
<p>Ausgelesen aus:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Bürodaten</strong> — IBAN, Empfänger-Adresse</li>
|
||||||
|
<li><strong>Kundendaten</strong> — Schuldner-Adresse</li>
|
||||||
|
<li><strong>Rechnungs-Daten</strong> — Betrag, Referenz, Zusatzinformation</li>
|
||||||
|
</ul>
|
||||||
|
<h2>Honorarmodelle<span class="hx:absolute hx:-mt-20" id="honorarmodelle"></span>
|
||||||
|
<a href="#honorarmodelle" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Modell</th>
|
||||||
|
<th>Berechnung</th>
|
||||||
|
<th>Verwendung</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Stundensatz</strong></td>
|
||||||
|
<td>Aus Zeiterfassung × Mitarbeiter-Stundensatz</td>
|
||||||
|
<td>Kleinaufträge, Beratung</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>SIA-Phasen</strong></td>
|
||||||
|
<td>Bauschätzwert × Honorarsatz × Phasenanteil</td>
|
||||||
|
<td>Reguläre Architektur-Aufträge</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Pauschal</strong></td>
|
||||||
|
<td>Fester Betrag</td>
|
||||||
|
<td>Auf Wunsch des Kunden</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<h2>PDF-Export<span class="hx:absolute hx:-mt-20" id="pdf-export"></span>
|
||||||
|
<a href="#pdf-export" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><p>Druckfertige Rechnung inkl. QR-Schein. Layout aus dem Büro-Briefbogen (mit Logo). Mehrsprachig DE/FR/IT (geplant).</p>
|
||||||
|
<h2>Verwandte Module<span class="hx:absolute hx:-mt-20" id="verwandte-module"></span>
|
||||||
|
<a href="#verwandte-module" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><ul>
|
||||||
|
<li><a href="../projekte">Projekte</a> — Honorarstruktur stammt aus dem Projekt</li>
|
||||||
|
<li><a href="../zeiterfassung">Zeiterfassung</a> — Stundensatz-Rechnungen</li>
|
||||||
|
</ul></description></item><item><title>Projekt- & 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>
|
||||||
|
<p><span class="rapport-status active">In Arbeit</span></p>
|
||||||
|
<p><strong>Projekte nach SIA 102</strong> mit Budget, Phasen und Beteiligten. Erstellung aus einer Offerte mit Verknüpfung zu Zeiterfassung und Rechnungen.</p>
|
||||||
|
<h2>Projektstruktur<span class="hx:absolute hx:-mt-20" id="projektstruktur"></span>
|
||||||
|
<a href="#projektstruktur" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><p>Jedes Projekt besitzt:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Stammdaten</strong> — Nummer, Bezeichnung, Standort, Bauschätzwert</li>
|
||||||
|
<li><strong>Kunde</strong> — verknüpft mit Kundendatenbank</li>
|
||||||
|
<li><strong>Beteiligte</strong> — Bauleitung, Fachplaner, Behörden</li>
|
||||||
|
<li><strong>Phasen</strong> — SIA 102 (Vorprojekt, Bauprojekt, Ausschreibung, …)</li>
|
||||||
|
<li><strong>Budget</strong> — Gesamthonorar, pro Phase aufgeteilt</li>
|
||||||
|
</ul>
|
||||||
|
<h2>SIA 102<span class="hx:absolute hx:-mt-20" id="sia-102"></span>
|
||||||
|
<a href="#sia-102" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><p>Standard-Phasenverteilung wird vorgeschlagen, kann pro Projekt überschrieben werden.</p>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Phase</th>
|
||||||
|
<th>Anteil (Standard)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>31 — Vorprojekt</td>
|
||||||
|
<td>9 %</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>32 — Bauprojekt</td>
|
||||||
|
<td>21 %</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>33 — Bewilligung</td>
|
||||||
|
<td>3 %</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>41 — Ausschreibung</td>
|
||||||
|
<td>18 %</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>51 — Ausführung</td>
|
||||||
|
<td>38 %</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>52 — Inbetriebnahme</td>
|
||||||
|
<td>6 %</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>53 — Abschluss</td>
|
||||||
|
<td>5 %</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<h2>Kundendatenbank<span class="hx:absolute hx:-mt-20" id="kundendatenbank"></span>
|
||||||
|
<a href="#kundendatenbank" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><ul>
|
||||||
|
<li>Adresse, Ansprechperson, Telefon, E-Mail</li>
|
||||||
|
<li>Honorartyp (Stundensatz / SIA / Pauschal)</li>
|
||||||
|
<li>Verknüpfung zu allen Projekten und Rechnungen des Kunden</li>
|
||||||
|
</ul>
|
||||||
|
<h2>Auswertung<span class="hx:absolute hx:-mt-20" id="auswertung"></span>
|
||||||
|
<a href="#auswertung" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><p>Pro Projekt:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Geleistete Stunden vs. Budget</li>
|
||||||
|
<li>Honorar-Saldo (verrechnet / Akonto / offen)</li>
|
||||||
|
<li>Phasen-Fortschritt</li>
|
||||||
|
</ul>
|
||||||
|
<h2>Verwandte Module<span class="hx:absolute hx:-mt-20" id="verwandte-module"></span>
|
||||||
|
<a href="#verwandte-module" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><ul>
|
||||||
|
<li><a href="../rechnungen">Rechnungen</a> — Offerte → Projekt</li>
|
||||||
|
<li><a href="../zeiterfassung">Zeiterfassung</a> — Stunden-Auswertung pro Phase</li>
|
||||||
|
</ul></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>
|
||||||
|
<p><span class="rapport-status active">In Arbeit</span></p>
|
||||||
|
<p><strong>Ferienverwaltung, interne Stunden / Absenzen und Lohnabrechnung.</strong> Jahresabschluss mit Überstundenausgleich.</p>
|
||||||
|
<h2>Stammdaten<span class="hx:absolute hx:-mt-20" id="stammdaten"></span>
|
||||||
|
<a href="#stammdaten" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><p>Pro Mitarbeiter:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Name, Eintrittsdatum, Pensum (%)</li>
|
||||||
|
<li>Stundensatz (intern, für Rechnungen)</li>
|
||||||
|
<li>Ferienanspruch (Tage / Jahr)</li>
|
||||||
|
<li>Lohn (monatlich, brutto)</li>
|
||||||
|
</ul>
|
||||||
|
<h2>Ferienverwaltung<span class="hx:absolute hx:-mt-20" id="ferienverwaltung"></span>
|
||||||
|
<a href="#ferienverwaltung" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><ul>
|
||||||
|
<li><strong>Prorata-Berechnung</strong> bei Eintritt unter Jahr</li>
|
||||||
|
<li><strong>Ferien-Saldo</strong> in Tagen (live)</li>
|
||||||
|
<li><strong>Halbtage</strong> unterstützt</li>
|
||||||
|
<li><strong>Übertrag</strong> ins Folgejahr oder Auszahlung</li>
|
||||||
|
</ul>
|
||||||
|
<h2>Absenzen<span class="hx:absolute hx:-mt-20" id="absenzen"></span>
|
||||||
|
<a href="#absenzen" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><p>Krankheit, Militär, Mutterschaft, unbezahlter Urlaub — getrennt erfasst, mit Auswertung pro Mitarbeiter.</p>
|
||||||
|
<h2>Lohnabrechnung<span class="hx:absolute hx:-mt-20" id="lohnabrechnung"></span>
|
||||||
|
<a href="#lohnabrechnung" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><p>Monatliche Abrechnung mit:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Grundlohn (basierend auf Pensum)</li>
|
||||||
|
<li>Überstunden-Vergütung</li>
|
||||||
|
<li>Spesen-Erstattung</li>
|
||||||
|
<li>Sozialabzüge (AHV, ALV, Pensionskasse)</li>
|
||||||
|
</ul>
|
||||||
|
<p>PDF-Export pro Mitarbeiter.</p>
|
||||||
|
<h2>Jahresabschluss<span class="hx:absolute hx:-mt-20" id="jahresabschluss"></span>
|
||||||
|
<a href="#jahresabschluss" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><ul>
|
||||||
|
<li>Ferien-Restguthaben übertragen oder auszahlen</li>
|
||||||
|
<li>Überstunden ausgleichen oder vergüten</li>
|
||||||
|
<li>Lohnausweis vorbereiten (Export)</li>
|
||||||
|
</ul>
|
||||||
|
<h2>Verwandte Module<span class="hx:absolute hx:-mt-20" id="verwandte-module"></span>
|
||||||
|
<a href="#verwandte-module" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><ul>
|
||||||
|
<li><a href="../zeiterfassung">Zeiterfassung</a> — Pensum-Soll vs. Stunden-Ist</li>
|
||||||
|
<li><a href="../spesen">Spesen</a> — Spesen-Erstattung in der Lohnabrechnung</li>
|
||||||
|
</ul></description></item><item><title>Spesen & 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>
|
||||||
|
<p><span class="rapport-status active">In Arbeit</span></p>
|
||||||
|
<p><strong>Spesenerfassung mit Belegupload.</strong> Jahresbudget mit Einnahmen und Ausgaben. Internes Rechnungswesen.</p>
|
||||||
|
<h2>Spesenerfassung<span class="hx:absolute hx:-mt-20" id="spesenerfassung"></span>
|
||||||
|
<a href="#spesenerfassung" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><p>Pro Mitarbeiter:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Datum, Betrag, Kategorie</li>
|
||||||
|
<li><strong>Beleg-Upload</strong> (PDF, JPG, PNG)</li>
|
||||||
|
<li>Projekt-Zuordnung (optional)</li>
|
||||||
|
<li>Status (offen / erstattet)</li>
|
||||||
|
</ul>
|
||||||
|
<p>Kategorien: Reise, Verpflegung, Material, Telefon, Sonstiges.</p>
|
||||||
|
<h2>Jahresbudget<span class="hx:absolute hx:-mt-20" id="jahresbudget"></span>
|
||||||
|
<a href="#jahresbudget" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><p>Übersicht über:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Einnahmen</strong> — Rechnungsbeträge, sortiert nach Eingang</li>
|
||||||
|
<li><strong>Ausgaben</strong> — Spesen, Bürokosten, Löhne, Sozialabzüge</li>
|
||||||
|
<li><strong>Saldo</strong> pro Monat / Quartal / Jahr</li>
|
||||||
|
</ul>
|
||||||
|
<h2>Auswertung<span class="hx:absolute hx:-mt-20" id="auswertung"></span>
|
||||||
|
<a href="#auswertung" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><ul>
|
||||||
|
<li>Einnahmen pro Kunde / Projekt</li>
|
||||||
|
<li>Ausgaben pro Kategorie / Mitarbeiter</li>
|
||||||
|
<li>Erfolgsrechnung pro Geschäftsjahr (vereinfacht)</li>
|
||||||
|
</ul>
|
||||||
|
<h2>Verwandte Module<span class="hx:absolute hx:-mt-20" id="verwandte-module"></span>
|
||||||
|
<a href="#verwandte-module" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><ul>
|
||||||
|
<li><a href="../mitarbeiter">Mitarbeiter</a> — Spesen-Erstattung in der Lohnabrechnung</li>
|
||||||
|
<li><a href="../rechnungen">Rechnungen</a> — Einnahmen-Quelle</li>
|
||||||
|
</ul></description></item><item><title>Protokolle & 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>
|
||||||
|
<p><span class="rapport-status active">In Arbeit</span></p>
|
||||||
|
<p><strong>Einfache Erstellung von Sitzungsprotokollen</strong> mit Beschlüssen und Aufgaben. Briefe und Lieferscheine im gleichen Erscheinungsbild.</p>
|
||||||
|
<h2>Sitzungsprotokolle<span class="hx:absolute hx:-mt-20" id="sitzungsprotokolle"></span>
|
||||||
|
<a href="#sitzungsprotokolle" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><p>Pro Sitzung:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Datum, Ort, Teilnehmer (aus Beteiligten-Liste)</li>
|
||||||
|
<li><strong>Traktanden</strong> als nummerierte Liste</li>
|
||||||
|
<li>Pro Traktandum: Beschluss, Aufgabe, Verantwortlich, Frist</li>
|
||||||
|
<li>Anhänge</li>
|
||||||
|
</ul>
|
||||||
|
<p>PDF-Export mit Bürobriefbogen.</p>
|
||||||
|
<h2>Briefe<span class="hx:absolute hx:-mt-20" id="briefe"></span>
|
||||||
|
<a href="#briefe" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><p>Brief-Editor mit:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Empfänger aus Kundendatenbank</li>
|
||||||
|
<li>Bezugszeile, Anrede, Text, Grussformel</li>
|
||||||
|
<li>Briefbogen-Vorlage mit Logo</li>
|
||||||
|
<li>PDF-Export</li>
|
||||||
|
</ul>
|
||||||
|
<h2>Lieferscheine<span class="hx:absolute hx:-mt-20" id="lieferscheine"></span>
|
||||||
|
<a href="#lieferscheine" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><p>Pro Lieferung:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Empfänger, Datum, Bezug</li>
|
||||||
|
<li>Positionen (Plan-Nummer, Bezeichnung, Anzahl, Massstab)</li>
|
||||||
|
<li>Unterschriftenfeld</li>
|
||||||
|
</ul>
|
||||||
|
<p>Konsistentes Erscheinungsbild über alle Dokumenttypen — eine Briefbogen-Vorlage, mehrere Verwendungen.</p>
|
||||||
|
<h2>Verwandte Module<span class="hx:absolute hx:-mt-20" id="verwandte-module"></span>
|
||||||
|
<a href="#verwandte-module" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><ul>
|
||||||
|
<li><a href="../projekte">Projekte</a> — Beteiligte als Empfänger</li>
|
||||||
|
</ul></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>
|
||||||
|
<p><span class="rapport-status new">Neu in 0.7.0</span></p>
|
||||||
|
<p><strong>Rapport prüft beim Start automatisch auf neue Versionen</strong> und installiert Updates signiert über Tauri. Einzelne Versionen können übersprungen werden.</p>
|
||||||
|
<h2>Funktionsweise<span class="hx:absolute hx:-mt-20" id="funktionsweise"></span>
|
||||||
|
<a href="#funktionsweise" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><p>Beim App-Start:</p>
|
||||||
|
<ol>
|
||||||
|
<li>Abfrage gegen <code>https://git.kgva.ch/karim/RAPPORT/releases/latest.json</code></li>
|
||||||
|
<li>Versionsvergleich mit lokaler <code>version</code> im Tauri-Bundle</li>
|
||||||
|
<li>Bei neuer Version → Update-Dialog</li>
|
||||||
|
<li>Bei Bestätigung → Download + Signaturprüfung + Installation + Neustart</li>
|
||||||
|
</ol>
|
||||||
|
<h2>Sicherheit<span class="hx:absolute hx:-mt-20" id="sicherheit"></span>
|
||||||
|
<a href="#sicherheit" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><ul>
|
||||||
|
<li>Updates werden mit dem <strong>Tauri-Updater-Schlüssel</strong> signiert</li>
|
||||||
|
<li>Manipulierte Downloads werden abgelehnt</li>
|
||||||
|
<li>Quellcode und Build sind reproduzierbar (Gitea CI, geplant)</li>
|
||||||
|
</ul>
|
||||||
|
<h2>Optionen<span class="hx:absolute hx:-mt-20" id="optionen"></span>
|
||||||
|
<a href="#optionen" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><ul>
|
||||||
|
<li><strong>Update installieren</strong> — Download &amp; Neustart</li>
|
||||||
|
<li><strong>Diese Version überspringen</strong> — überspringt nur diese eine Version</li>
|
||||||
|
<li><strong>Später erinnern</strong> — beim nächsten Start erneut fragen</li>
|
||||||
|
</ul>
|
||||||
|
<p>Updates können in den Einstellungen komplett deaktiviert werden.</p>
|
||||||
|
<h2>Latest-Endpoint<span class="hx:absolute hx:-mt-20" id="latest-endpoint"></span>
|
||||||
|
<a href="#latest-endpoint" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><div class="hextra-code-block hx:relative hx:mt-6 hx:first:mt-0 hx:group/code">
|
||||||
|
<div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="p">{</span>
|
||||||
|
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;version&#34;</span><span class="p">:</span> <span class="s2">&#34;0.8.3&#34;</span><span class="p">,</span>
|
||||||
|
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;notes&#34;</span><span class="p">:</span> <span class="s2">&#34;Rapport 0.8.3&#34;</span><span class="p">,</span>
|
||||||
|
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;pub_date&#34;</span><span class="p">:</span> <span class="s2">&#34;2026-05-24T00:00:00Z&#34;</span><span class="p">,</span>
|
||||||
|
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;platforms&#34;</span><span class="p">:</span> <span class="p">{</span>
|
||||||
|
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;darwin-aarch64&#34;</span><span class="p">:</span> <span class="p">{</span>
|
||||||
|
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;signature&#34;</span><span class="p">:</span> <span class="s2">&#34;…&#34;</span><span class="p">,</span>
|
||||||
|
</span></span><span class="line"><span class="cl"> <span class="nt">&#34;url&#34;</span><span class="p">:</span> <span class="s2">&#34;https://git.kgva.ch/karim/RAPPORT/releases/download/0.8.3/RAPPORT%20PRE-RELEASE.app.tar.gz&#34;</span>
|
||||||
|
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
|
||||||
|
</span></span><span class="line"><span class="cl"> <span class="p">}</span>
|
||||||
|
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div><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">
|
||||||
|
<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!"
|
||||||
|
>
|
||||||
|
<div class="hextra-copy-icon hx:group-[.copied]/copybtn:hidden hx:pointer-events-none hx:h-4 hx:w-4"></div>
|
||||||
|
<div class="hextra-success-icon hx:hidden hx:group-[.copied]/copybtn:block hx:pointer-events-none hx:h-4 hx:w-4"></div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div></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>
|
||||||
|
<p><span class="rapport-status new">Neu in 0.7.0</span></p>
|
||||||
|
<p><strong>Schnellzugriff über die Menüleiste mit Hide-on-Close.</strong> Beim Schliessen läuft Rapport im Hintergrund weiter — Cmd+Q beendet die App vollständig.</p>
|
||||||
|
<h2>Verhalten<span class="hx:absolute hx:-mt-20" id="verhalten"></span>
|
||||||
|
<a href="#verhalten" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Aktion</th>
|
||||||
|
<th>Verhalten</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Fenster schliessen</strong> (⌘W oder rotes X)</td>
|
||||||
|
<td>App läuft im Tray weiter</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Cmd+Q</strong></td>
|
||||||
|
<td>App wird vollständig beendet</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Klick auf Tray-Icon</strong></td>
|
||||||
|
<td>Fenster nach vorne, oder zeigen</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Rechtsklick auf Tray-Icon</strong></td>
|
||||||
|
<td>Menü mit Schnellzugriffen</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<h2>Tray-Menü<span class="hx:absolute hx:-mt-20" id="tray-menü"></span>
|
||||||
|
<a href="#tray-men%c3%bc" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><ul>
|
||||||
|
<li><strong>Rapport zeigen</strong> — Fenster nach vorne</li>
|
||||||
|
<li><strong>Neue Zeiterfassung</strong> — direkt im Zeit-Modul</li>
|
||||||
|
<li><strong>Neue Rechnung</strong> — direkt im Rechnungs-Modul</li>
|
||||||
|
<li><strong>Letzte Projekte</strong> — Quick-Open der letzten 5 Projekte</li>
|
||||||
|
<li><strong>Einstellungen</strong></li>
|
||||||
|
<li><strong>Rapport beenden</strong></li>
|
||||||
|
</ul>
|
||||||
|
<h2>Konfiguration<span class="hx:absolute hx:-mt-20" id="konfiguration"></span>
|
||||||
|
<a href="#konfiguration" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><p>In den Einstellungen:</p>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Beim Systemstart starten</strong> (Login-Item) — Standard: aus</li>
|
||||||
|
<li><strong>Beim Schliessen beenden</strong> statt ins Tray — Standard: aus</li>
|
||||||
|
<li><strong>Tray-Icon ausblenden</strong> — App läuft, aber kein Menüleisten-Icon</li>
|
||||||
|
</ul>
|
||||||
|
<h2>Verwandte Module<span class="hx:absolute hx:-mt-20" id="verwandte-module"></span>
|
||||||
|
<a href="#verwandte-module" class="subheading-anchor" aria-label="Permalink für diesen Abschnitt"></a></h2><ul>
|
||||||
|
<li><a href="../auto-updater">Auto-Updater</a> — prüft Updates im Hintergrund</li>
|
||||||
|
</ul></description></item></channel></rss>
|
||||||
@@ -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 |
@@ -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 |
@@ -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));
|
||||||
@@ -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")
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>RAPPORT – Lizenz & Mitwirkende</title><link>https://rapport.gabrielevarano.ch/lizenz/</link><description>Recent content in Lizenz & 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>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
User-agent: *
|
||||||
@@ -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>
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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]
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
node_modules/
|
||||||
|
public/
|
||||||
|
resources/
|
||||||
|
|
||||||
|
.hugo_build.lock
|
||||||
|
|
||||||
|
# Playwright
|
||||||
|
playwright-report/
|
||||||
|
test-results/
|
||||||
@@ -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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"editor.tabSize": 2,
|
||||||
|
"markdown.extension.toc.levels": "2..6"
|
||||||
|
}
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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.
|
||||||
@@ -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)
|
||||||