diff --git a/Bamort_detailed_project_documentation.md b/Bamort_detailed_project_documentation.md
new file mode 100644
index 0000000..9217368
--- /dev/null
+++ b/Bamort_detailed_project_documentation.md
@@ -0,0 +1,1087 @@
+# Bamort — Detailed Project Documentation
+
+> Version: 0.2.4 (backend) / 0.2.3 (frontend)
+> Generated: 2026-03-13
+> Purpose: Comprehensive architecture, component, and business-logic reference
+
+---
+
+## Table of Contents
+
+1. [Project Purpose and Domain](#1-project-purpose-and-domain)
+2. [High-Level Architecture](#2-high-level-architecture)
+3. [Technology Stack](#3-technology-stack)
+4. [Backend Architecture](#4-backend-architecture)
+ - [Entry Point and Startup Sequence](#41-entry-point-and-startup-sequence)
+ - [Router and Middleware](#42-router-and-middleware)
+ - [Authentication System](#43-authentication-system)
+ - [Module Breakdown (Functional View)](#44-module-breakdown-functional-view)
+5. [Frontend Architecture](#5-frontend-architecture)
+ - [Application Bootstrap](#51-application-bootstrap)
+ - [Router and Navigation Guards](#52-router-and-navigation-guards)
+ - [State Management (Pinia Stores)](#53-state-management-pinia-stores)
+ - [Views and Components (Functional View)](#54-views-and-components-functional-view)
+ - [API Communication Layer](#55-api-communication-layer)
+6. [Core Business Logic](#6-core-business-logic)
+ - [Character Data Model](#61-character-data-model)
+ - [Derived Values Calculation](#62-derived-values-calculation)
+ - [Skill and Spell Learning System](#63-skill-and-spell-learning-system)
+ - [Character Creation Wizard](#64-character-creation-wizard)
+ - [PDF Export Pipeline](#65-pdf-export-pipeline)
+ - [Audit Log](#66-audit-log)
+7. [Data Access and Persistence](#7-data-access-and-persistence)
+8. [Module Interaction Map](#8-module-interaction-map)
+9. [Complete API Route Reference](#9-complete-api-route-reference)
+10. [Infrastructure and Deployment](#10-infrastructure-and-deployment)
+11. [User Roles and Access Control](#11-user-roles-and-access-control)
+12. [Internationalisation (i18n)](#12-internationalisation-i18n)
+13. [Known Limitations and Security Notes](#13-known-limitations-and-security-notes)
+
+---
+
+## 1. Project Purpose and Domain
+
+**Bamort** (BaMoRT = *Ba*sic Mo*R*phing *T*ool) is a web-based character management system for the **Midgard** tabletop role-playing game (M5 system). It is designed as a replacement for the older MOAM desktop application.
+
+**Core user-facing features:**
+- Create, manage, and view RPG characters (stats, skills, spells, equipment)
+- Guide new users through a multi-step character creation wizard following Midgard M5 rules
+- Manage skill and spell learning progression with rule-enforced costs (EP, TE, LE, gold)
+- Track experience points (EP), practice points (PP), and wealth (gold/silver/copper)
+- Export characters as PDF character sheets via HTML templates
+- Import/export characters as JSON or CSV for interoperability with other tools
+- Share characters between users (read-only or read/write)
+- Maintain game-system master data (canonical skill, spell, and equipment tables)
+
+**Business domain vocabulary:**
+| Term | Meaning |
+|------|---------|
+| Char / Character | A player or NPC character in the game |
+| Grad | Character level/grade |
+| EP | Experience points — spent to improve skills and spells |
+| TE | Training entries — attempts needed to improve a skill |
+| LE | Learning entries — prerequisite step counts for spells |
+| PP | Practice points — earned in combat/use, reduce learning costs |
+| Fertigkeit | Skill (e.g., Schwimmen, Klettern) |
+| Waffenfertigkeit | Weapon skill (subtype of Fertigkeit) |
+| Zauber | Spell (magical ability) |
+| Eigenschaft | Attribute (St=Strength, Gs=Dexterity, Gw=Agility, Ko=Constitution, In=Intelligence, Zt=Magical Talent, Au=Appearance, Wk=Willpower, Pa=Parry) |
+| Rasse | Race (Mensch, Zwerg, Elf, etc.) |
+| Typ | Character class code (Kr=Knight, Ma=Mage, Hx=Witch, etc.) |
+| Lp / Ap / B | Life Points / Action Points / Load capacity |
+| Bennies (Gg/Gp/Sg) | Luck tokens used in gameplay |
+| Vermoegen | Wealth in gold/silver/copper pieces |
+| Abwehr | Derived defense value |
+| GSMaster | Game-system master data (canonical tables for skills, spells, equipment) |
+| M5 | Midgard 5th edition rule set (the game system code used) |
+
+---
+
+## 2. High-Level Architecture
+
+```
+┌─────────────────────────────────────────────────────────────────┐
+│ User (Browser) │
+└──────────────────────────┬──────────────────────────────────────┘
+ │ HTTP / HTTPS
+┌──────────────────────────▼──────────────────────────────────────┐
+│ Vue 3 SPA (bamort-frontend) │
+│ Vite dev server (port 5173) / Nginx (port 80) │
+│ - Vue Router (client-side navigation) │
+│ - Pinia (state management) │
+│ - vue-i18n (DE/EN translations) │
+│ - Axios (API calls to backend) │
+└──────────────────────────┬──────────────────────────────────────┘
+ │ REST API calls to VITE_API_URL
+ │ Bearer token in Authorization header
+┌──────────────────────────▼──────────────────────────────────────┐
+│ Go / Gin REST API (bamort-backend) │
+│ Port 8180 │
+│ - Public routes: /register /login /password-reset/* │
+│ - Protected routes: /api/* (auth middleware enforced) │
+│ - Public info routes: /api/public/* │
+└──────────────────────────┬──────────────────────────────────────┘
+ │ GORM
+┌──────────────────────────▼──────────────────────────────────────┐
+│ MariaDB 11.4 │
+│ (bamort-mariadb, port 3306) │
+│ Production: not exposed to host network │
+│ Dev: accessible at localhost:3306 + phpMyAdmin at :8081 │
+└─────────────────────────────────────────────────────────────────┘
+```
+
+The frontend communicates **directly** with the backend API via the `VITE_API_URL` environment variable — there is no Nginx reverse-proxy layer between them in production (TLS termination is the responsibility of an external proxy like Traefik).
+
+---
+
+## 3. Technology Stack
+
+| Layer | Technology | Version | Purpose |
+|-------|-----------|---------|---------|
+| Backend language | Go | 1.25 | API server, business logic |
+| Backend framework | Gin | latest | HTTP routing, middleware |
+| ORM | GORM | latest | Database access, migrations |
+| Database | MariaDB | 11.4 | Primary persistent storage |
+| Test DB | SQLite | (via CGO) | Isolated per-test snapshot |
+| PDF rendering | chromedp + Chromium | latest | HTML→PDF via headless browser |
+| PDF merging | pdfcpu | latest | Multi-page PDF assembly |
+| Live reload (dev) | Air | latest | Backend hot-reload during development |
+| Frontend framework | Vue 3 | ^3.5.13 | UI (Options API) |
+| Frontend build | Vite | ^6.0.1 | Dev server + production bundler |
+| State management | Pinia | ^2.3.0 | Reactive global stores |
+| HTTP client | Axios | ^1.7.9 | API communication with auth interceptors |
+| i18n | vue-i18n | ^11.0.0 | German/English UI localisation |
+| Frontend server | Nginx (prod) / Vite (dev) | alpine / 6.x | Static file serving + SPA routing |
+| Container runtime | Docker + Docker Compose | — | Isolated service deployment |
+
+---
+
+## 4. Backend Architecture
+
+### 4.1 Entry Point and Startup Sequence
+
+**File:** `backend/cmd/main.go`
+
+The `main()` function orchestrates startup in this order:
+
+1. **Load configuration** — reads `.env` / `.env.local` then environment variable overrides via `config.Cfg` (auto-loaded in `config.init()`)
+2. **Configure logger** — sets log level (`DEBUG`, `INFO`, `WARN`, `ERROR`) and debug mode based on config
+3. **Set Gin mode** — `gin.ReleaseMode` in production, `gin.DebugMode` otherwise
+4. **Connect database** — calls `database.ConnectDatabase()` which selects MySQL or SQLite based on `DATABASE_TYPE` and `ENVIRONMENT`
+5. **Initialize PDF templates** — copies default templates from `/app/default_templates` to `cfg.TemplatesDir` if needed via `pdfrender.InitializeTemplates()`
+6. **Set up Gin engine** — calls `router.SetupGin(r)` for CORS config
+7. **Register routes** — each module calls `RegisterRoutes(protected)` on the protected `/api` group
+8. **Register public routes** — pdfrender and appsystem register unauthenticated endpoints
+9. **Start HTTP server** — `r.Run(cfg.GetServerAddress())`
+
+### 4.2 Router and Middleware
+
+**File:** `backend/router/routes.go` and `backend/router/setup.go`
+
+**Route structure:**
+```
+POST /register → user.RegisterUser
+POST /login → user.LoginUser
+POST /password-reset/request → user.RequestPasswordReset
+GET /password-reset/validate/:token → user.ValidateResetToken
+POST /password-reset/reset → user.ResetPassword
+
+/api/* ← ALL require valid Bearer token via user.AuthMiddleware()
+├── /api/public/version → appsystem (no auth check inside group, separate route)
+├── /api/public/systeminfo → appsystem
+├── /api/user/* → user module
+├── /api/users/* → user module (admin-only subset)
+├── /api/characters/* → character module
+├── /api/maintenance/* → gsmaster (read) + maintenance (maintainer-only write)
+├── /api/importer/* → importer module
+├── /api/pdf/* → pdfrender module
+├── /api/transfer/* → transfer module
+└── /api/appsystem/* → appsystem module
+```
+
+**CORS:** Configured in `router/setup.go` with allowed origins including `localhost:5173` (dev), `localhost:8180`, and configurable `BASE_URL` (production frontend).
+
+### 4.3 Authentication System
+
+**Token generation** (`user/handlers.go` → `GenerateToken`):
+- Hashes `username + createdAt` with MD5
+- Embeds the user's ID in an predictable position within the hex string
+- Token is stored client-side in `localStorage`
+
+**Token validation** (`user/handlers.go` → `CheckToken`):
+- Extracts the Base64/hex-encoded user ID from position `len("Bearer ")` onward in the `Authorization` header
+- Loads the user from the database by that ID
+- **No cryptographic signature verification is performed** — this is a design limitation (see Section 13)
+
+**AuthMiddleware:**
+- Calls `CheckToken`, sets `userID`, `username`, `user` in the Gin context
+- Returns `401 Unauthorized` if token is absent or user not found
+
+**Password hashing:** MD5 (`crypto/md5`). The bcrypt implementation is present but commented out.
+
+### 4.4 Module Breakdown (Functional View)
+
+Each module follows the same structure: `handlers.go` (business logic + HTTP responses), `routes.go` (route registration), and `*_test.go` (tests).
+
+---
+
+#### `user/` — User Account Management
+
+**Business function:** Registration, login, profile management, password reset via email, and role-based access control.
+
+**Key responsibilities:**
+- `RegisterUser` — creates a new user with MD5-hashed password and default `standard` role
+- `LoginUser` — validates credentials, returns a token
+- `AuthMiddleware` — protects all `/api/*` routes; injects user context
+- `RequireAdmin()` / `RequireMaintainer()` — role-check middleware used on specific sub-routes
+- Profile endpoints: `GET/PUT /api/user/profile`, display name, email, preferred language, password change
+- Admin user management: `GET/PUT/DELETE /api/users/:id`
+- Password reset flow: request → email with token → validate token → set new password (uses `mail/` module for SMTP)
+
+**Roles:**
+| Role | Permissions |
+|------|------------|
+| `standard` | Own characters + shared characters |
+| `maintainer` | + Write access to GSMaster data and maintenance endpoints |
+| `admin` | + User management (list, change role, delete, reset password) |
+
+---
+
+#### `character/` — Character Management (Core Module)
+
+**Business function:** The heart of the application. Full CRUD for characters plus all RPG game-logic operations.
+
+**Sub-responsibilities:**
+
+| Sub-area | Files | Business purpose |
+|----------|-------|-----------------|
+| CRUD | `handlers.go`, `database.go` | Create / read / update / delete characters and their sub-entities (attributes, skills, spells, equipment) |
+| Derived values | `derived_values_calculator.go` | Calculate secondary stats (Abwehr, Zaubern, Raufen, body/mind resistance) from primary attributes using M5 formulas |
+| Derived values (dice) | `derived_values_calculator.go` | Calculate dice-dependent stats during character creation (Lp_max, Ap_max, B_max, PA, Wk) requiring die-roll input from frontend |
+| Learning cost engine | `lerncost_handler.go` | Compute EP/TE/LE/gold cost to learn a new skill/spell or improve an existing one, considering class, category, difficulty, practice points, and reward bonuses |
+| Skill actions | `handlers.go` | Add/remove/edit skills and weapon skills inline |
+| Learn skill | Separate handler | Learn a new skill (deducts resources, creates audit log entries) |
+| Improve skill | `ImproveSkill` handler | Increase an existing skill value (validates costs, writes audit trail) |
+| Learn spell | `LearnSpell` handler | Add a new spell to the character |
+| Practice points | `practice_points_handler.go` | Track PP per skill category; PP reduce TE costs in learning |
+| Experience & wealth | `handlers.go` | Update EP/gold separately from full character update |
+| Character sharing | `share_handlers.go` | Grant read or write access to specific other users |
+| Character creation wizard | Multiple handlers | Multi-step session-based creation: basic info → attributes → derived rolling → skills → finalize |
+| Creation rules | `creation_rules.go` | M5-specific tables for special abilities (W100 roll → bonus/malus), starting attribute bonuses by race/class |
+| Audit log | `audit_log.go`, `audit_log_handlers.go` | Immutable log of EP/gold/stat changes with reason codes |
+| Image | `image_handler.go` | Upload and store Base64 character portrait |
+| Ownership guard | `ownership_guard_test.go` | Ensures only the owner (or share-granted user) can modify |
+| System info | `system_information_handlers.go` | Returns available skill categories for UI dropdowns |
+
+---
+
+#### `gsmaster/` — Game-System Master Data
+
+**Business function:** Canonical reference tables for the M5 game system. Skills, weapon skills, spells, equipment, weapons, and containers as defined by the Midgard rules. Users with the `maintainer` role can edit these tables.
+
+**Read (all authenticated users):**
+- `GET /api/maintenance/skills` — list all skills
+- `GET /api/maintenance/spells` — list all spells
+- `GET /api/maintenance/weapons` — list all weapons
+- `GET /api/maintenance/equipment` — general equipment
+- `GET /api/maintenance/*-enhanced` — enhanced views with additional metadata (learning costs, categories, etc.)
+
+**Write (maintainer only):**
+- POST/PUT/DELETE on all the above categories
+
+**Learning cost integration:** `gsmaster` package provides lookup helpers used by the `character` module to calculate learning costs. Skills carry `Category`, `Difficulty`, and `InnateSkill` properties that drive cost lookup logic.
+
+---
+
+#### `equipment/` — Character Equipment Management
+
+**Business function:** CRUD operations specifically for the equipment attached to a character — weapons (`EqWaffe`), containers/bags (`EqContainer`), transportation (`EqContainer` with `IsTransportation=true`), and general gear (`EqAusruestung`).
+
+**Why separate from `character/`?** Equipment has its own handlers/routes to keep the character module focused on core stats and skills. Equipment can also reference master data items from `gsmaster/`.
+
+---
+
+#### `pdfrender/` — PDF Character Sheet Export
+
+**Business function:** Generate printable PDF character sheets by rendering HTML templates with character data using a Chromium headless browser, then merging the resulting per-page PDFs into a single file.
+
+**Flow:**
+1. `ExportCharacterToPDF` receives a character ID and template name
+2. Loads the character from the database (fully preloaded)
+3. For each template page (page1_stats.html, page2_play.html, etc.):
+ - Injects character data into the HTML template using Go template engine
+ - Renders it to PDF via `chromedp` (headless Chromium)
+ - Checks `` comment in template to detect overflow
+ - Generates continuation pages (page1.2_stats.html) for overflow
+4. Merges all page PDFs with `pdfcpu` into one file
+5. Saves to `cfg.ExportTempDir` and returns the filename
+6. Frontend opens a download URL in a new window
+
+**Templates:** Located in `backend/templates/Default_A4_Quer/`. The `InitializeTemplates` function copies the default template to the runtime directory on first run.
+
+**Routes:**
+- `GET /api/pdf/export/:id` — export, returns `{"filename": "..."}`
+- `GET /api/pdf/file/:filename` — download the generated PDF (public route, no auth required, filename-based security)
+- `GET /api/pdf/templates` — list available templates
+- `POST /api/pdf/cleanup` — clean up old generated PDF files
+
+---
+
+#### `importer/` — VTT / CSV Import and Export
+
+**Business function:** Import characters from Virtual Tabletop (VTT) JSON format or CSV files. Export characters to VTT JSON or CSV. Export/import spell lists via CSV.
+
+**Routes:**
+```
+POST /api/importer/upload — Upload VTT JSON + optional CSV
+POST /api/importer/spells/csv — Import spell data from CSV
+GET /api/importer/export/vtt/:id — Export character as VTT JSON (API response)
+GET /api/importer/export/vtt/:id/file — Export character as VTT JSON (file download)
+GET /api/importer/export/csv/:id — Export character as CSV
+GET /api/importer/export/spells/csv — Export all spells as CSV
+```
+
+---
+
+#### `transfer/` — JSON Backup and Restore
+
+**Business function:** Portable character backup/restore in a Bamort-native JSON format. Also supports full-database export/import (admin/migration use case).
+
+**Routes:**
+```
+GET /api/transfer/export/:id — Export character as JSON
+GET /api/transfer/download/:id — Download character JSON as file
+POST /api/transfer/import — Import character from JSON
+POST /api/transfer/database/export — Export entire database as JSON
+POST /api/transfer/database/import — Import full database from JSON
+```
+
+---
+
+#### `maintenance/` — Admin and Database Operations
+
+**Business function:** Maintainer-only admin operations: database consistency checks, re-connect/reload env, data migration (SQLite→MariaDB), and managing auxiliary master data not covered by `gsmaster/`.
+
+**Key operations:**
+- `GET /api/maintenance/setupcheck` — validate database schema integrity
+- `GET /api/maintenance/setupcheck-dev` — extended dev-mode diagnostics
+- `GET /api/maintenance/reconndb` — reconnect database (useful after connection interruption)
+- `GET /api/maintenance/reloadenv` — reload environment variables at runtime
+- `POST /api/maintenance/transfer-sqlite-to-mariadb` — one-time data migration
+- CRUD for beliefs (`gsm-believes`), game systems, literature sources, misc lookups, skill improvement cost tables
+
+---
+
+#### `appsystem/` — System Information
+
+**Business function:** Exposes version and system health information.
+
+**Routes:**
+```
+GET /api/public/version — { version, gitCommit }
+GET /api/public/systeminfo — { version, gitCommit, userCount, charCount, dbVersion }
+```
+
+These are reachable without authentication (used by `LandingView` and `SystemInfoView` to display status without requiring login).
+
+---
+
+#### `gamesystem/` — Game System Records
+
+**Business function:** Manages `GameSystem` records in the database (e.g., M5 = Midgard 5th edition). Used when creating characters and as a FK reference in master data. Initial seed data creates the M5 game system if it doesn't exist.
+
+---
+
+#### `logger/` — Application Logging
+
+**Business function:** Custom structured logger with levels (DEBUG/INFO/WARN/ERROR). Used throughout all packages. Debug mode controlled by config.
+
+---
+
+#### `mail/` — Email Delivery
+
+**Business function:** SMTP client used exclusively for password-reset emails. Supports TLS (port 465) and STARTTLS (port 587). Configured via `MAIL_HOST`, `MAIL_PORT`, `MAIL_USERNAME`, `MAIL_PASSWORD`, `MAIL_FROM` env vars.
+
+---
+
+## 5. Frontend Architecture
+
+### 5.1 Application Bootstrap
+
+**`src/main.js`** registers plugins in order:
+
+1. **Pinia** — state store (must be first)
+2. **Vue Router** — client-side routing
+3. **vue-i18n** — i18n instance created and exported from `stores/languageStore.js`
+4. **UtilsPlugin** — installs global utilities on all components: `$formatDate`, `$formatDateTime`, `$formatRelativeDate`, `$safeValue`, `$capitalize`, `$rollDie`, `$rollDice`, `$rollDiceWithSum`, `$rollNotation`, `$randomBetween`, `$randomChoice`, `$shuffleArray`
+
+**`App.vue`** — root component:
+- Shows `
` only when `localStorage.getItem('token')` is truthy
+- Applies `.full-width` CSS class on `` for non-logged-in pages (centers login/register forms)
+- Reacts to `storage` and `auth-changed` window events to update `loggedIn` state reactively
+
+### 5.2 Router and Navigation Guards
+
+**File:** `src/router/index.js`
+
+Uses `createWebHistory` (HTML5 pushstate — no hash URLs). Unauthenticated landing/auth pages are statically imported; all other views are lazy-loaded (code-split) for performance.
+
+**Navigation guard logic (beforeEach):**
+1. If route requires auth and user is not logged in → redirect to `/login`
+2. If route requires admin role → lazy-load `userStore`, fetch profile if not loaded, check `isAdmin` getter → redirect to `/dashboard` if not admin
+3. Otherwise → allow navigation
+
+`isLoggedIn()` from `utils/auth.js` simply checks for a token in `localStorage`.
+
+### 5.3 State Management (Pinia Stores)
+
+**`userStore` (`src/stores/userStore.js`)**
+
+Central user identity state. Used by `Menu.vue`, `CharacterDetails.vue`, `UserManagementView.vue`, and the router guard.
+
+| State | Purpose |
+|-------|---------|
+| `currentUser` | Logged-in user object `{ id, username, display_name, email, role, preferred_language }` |
+| `isLoading` | Loading flag for profile fetch |
+
+| Getter | Returns |
+|--------|---------|
+| `isAuthenticated` | `true` if `currentUser` is not null |
+| `userRole` | `currentUser.role` or `'standard'` |
+| `isAdmin` | `role === 'admin'` |
+| `isMaintainer` | `role === 'maintainer'` or `'admin'` |
+| `isStandardUser` | any logged-in user |
+
+Action `fetchCurrentUser()` calls `GET /api/user/profile`, hydrates `currentUser`, and also sets the UI language to the user's preferred language.
+
+---
+
+**`languageStore` (`src/stores/languageStore.js`)**
+
+Holds current language (`de` or `en`). Unusually, the `i18n` instance itself is **created and exported from this file**, then imported in `main.js`. This coupling means language changes can be made from anywhere that imports `i18n`.
+
+---
+
+**`characterCreationStore` (`src/stores/characterCreation.js`)**
+
+In-memory wizard state for multi-step character creation. Tracks current step (1–5), and partial data for each step. The actual server-side session persistence is handled directly by `CharacterCreation.vue` via API calls; the Pinia store mirrors this state in memory.
+
+### 5.4 Views and Components (Functional View)
+
+#### Public Pages (no auth required)
+
+| Component | Route | Purpose |
+|-----------|-------|---------|
+| `LandingView` | `/` | Landing page; polls `GET /api/public/version` every 5s (up to 24 retries) to show backend status; disables login button until backend responds |
+| `LoginView` → `LoginForm` | `/login` | Login form; on success stores token in `localStorage`, triggers `auth-changed` event |
+| `RegisterView` → `RegisterForm` | `/register` | Registration form |
+| `ForgotPasswordView` → `ForgotPasswordForm` | `/forgot-password` | Sends password-reset email request |
+| `ResetPasswordView` → `ResetPasswordForm` | `/reset-password` | Accepts reset token from URL, sets new password |
+| `HelpView` | `/help` | FAQ page built from i18n keys dynamically |
+| `SponsorsView` | `/sponsors` | Static credits/sponsor page |
+| `SystemInfoView` | `/system-info` | Shows system versions and live backend stats |
+
+#### Authenticated Pages
+
+| Component | Route | Purpose |
+|-----------|-------|---------|
+| `DashboardView` → `CharacterList` | `/dashboard` | Lists own and shared characters; shows active creation sessions; "Create New Character" button starts a new wizard session |
+| `UserProfileView` | `/profile` | Self-service: change display name, language, email, password |
+| `UserManagementView` | `/users` | Admin-only: list all users, change roles, delete users, reset passwords |
+| `MaintenanceView` → `Maintenance` | `/maintenance` | Maintainer tools: database checks, master data management |
+| `FileUploadPage` | `/upload` | Import VTT JSON + optional CSV |
+| `AusruestungView` → `AusruestungList` | `/ausruestung/:characterId` | Standalone equipment list (legacy view) |
+| `CharacterDetails` | `/character/:id` | Main character hub (see below) |
+| `CharacterCreation` | `/character/create/:sessionId` | Multi-step character creation wizard |
+
+#### CharacterDetails Component — Central Hub
+
+`CharacterDetails.vue` is the most complex component. It:
+1. Fetches full character data from `GET /api/characters/:id`
+2. Determines `isOwner` by comparing `character.user_id` vs `userStore.currentUser.id`
+3. Renders a **dynamic sub-component** (``) driven by a submenu
+4. Passes `character` and `isOwner` as props to every sub-view
+5. Listens for `@character-updated` events from sub-views to trigger a full data reload
+
+**Sub-views (tabs):**
+
+| Component | Business purpose |
+|-----------|----------------|
+| `DatasheetView` | Character biography, attributes (Au/Gs/Gw/In/Ko/St/Wk/Zt/PA), derived stats (Lp/Ap/B, Abwehr, Zaubern, Raufen, Resistenz), bennies, wealth. Inline double-click editing for owners |
+| `SkillView` | Skills grouped by category. Learning mode (toggle 🎓) reveals resources (EP, Gold) and action buttons: learn new skill, improve existing, add manually |
+| `WeaponView` | Weapon skills and equipped weapons |
+| `SpellView` | Spell list with learn/improve actions |
+| `EquipmentView` | Containers, general equipment, transported items |
+| `ExperianceView` | Experience and wealth tracking; audit log access |
+| `DeleteCharView` | Confirmation UI for deleting the character (owner only) |
+| `AuditLogView` | Read-only history of EP/gold changes |
+
+**Overlay dialogs accessible from CharacterDetails:**
+- `ExportDialog` — multi-format export: choose PDF template, VTT JSON, or BaMoRT JSON and trigger download
+- `VisibilityDialog` — toggle character between public/private (owner only)
+
+#### Learning Dialogs
+
+`SkillLearnDialog.vue`, `SkillImproveDialog.vue`, `SpellLearnDialog.vue` are modal dialogs that:
+1. Call `POST /api/characters/lerncost-new` with skill name, current level, type, and action
+2. Display the computed cost breakdown (EP, TE, LE, gold, PP reduction)
+3. Allow the user to confirm → backend executes the learning/improvement and deducts resources
+
+### 5.5 API Communication Layer
+
+**File:** `src/utils/api.js`
+
+A pre-configured Axios instance:
+- `baseURL`: `import.meta.env.VITE_API_URL || 'https://bamort-api.trokan.de'`
+- **Request interceptor:** Injects `Authorization: Bearer ` from `localStorage` automatically
+- **Response interceptor:** On `401`, removes the stale token from `localStorage` and logs a warning (but does not redirect — the nav guard handles that on next navigation)
+
+All components and stores import this `API` instance. Raw `axios` (without the interceptor) is used only in `LandingView` and `SystemInfoView` for unauthenticated public API calls.
+
+---
+
+## 6. Core Business Logic
+
+### 6.1 Character Data Model
+
+The character data model maps closely to the **Midgard M5 character sheet**:
+
+```
+Char (root entity, table: char_chars)
+├── Eigenschaften[] — 9 primary attributes (Au, Gs, Gw, In, Ko, St, Wk, Zt, PA)
+├── Lp — Life points (current + max)
+├── Ap — Action points (current + max)
+├── B — Load/burden capacity (current + max)
+├── Merkmale — Physical description (eye/hair color, height, etc.)
+├── Erfahrungsschatz — EP (experience points) + ES (experience treasure/milestone)
+├── Bennies — Luck tokens: Gg (lucky coin), Gp (lucky penny), Sg (lucky stroke)
+├── Vermoegen — Wealth: Goldstuecke, Silberstuecke, Kupferstuecke
+├── Fertigkeiten[] — Skills (Fertigkeit), each with value, base, bonus, PP, category
+├── Waffenfertigkeiten[] — Weapon skills (subtype with additional combat fields)
+├── Zauber[] — Spells with description, bonus, source reference
+├── Waffen[] — Equipped weapons (EqWaffe) with attack/defense bonuses
+├── Behaeltnisse[] — Bags/containers (EqContainer)
+├── Transportmittel[] — Vehicles (EqContainer with IsTransportation=true)
+├── Ausruestung[] — General equipment items
+└── Spezialisierung — JSON array of specialization strings
+```
+
+**`FeChar`** (Frontend Character) is the API response type: it embeds `Char` plus computed fields:
+- `Git` (poison tolerance = 30 + Ko/2)
+- `CategorizedSkills` — map grouping skills by category for `SkillView` rendering
+- `InnateSkills` — innate/racial skills separated from learned ones
+
+### 6.2 Derived Values Calculation
+
+**File:** `backend/character/derived_values_calculator.go`
+
+The backend implements the M5 formula set. Key derivations:
+
+| Derived value | Formula |
+|--------------|---------|
+| AusdauerBonus | Ko/10 + St/20 |
+| SchadensBonus | St/20 + Gs/30 − 3 |
+| AngriffsBonus | Gs/20 + Gw/30 − 3 |
+| AbwehrBonus | Gw/20 + Gs/30 − 3 |
+| ZauberBonus | In/10 + Zt/20 − 3 |
+| ResistenzBonusKoerper | Ko/10 + Wk/20 − 3 |
+| ResistenzBonusGeist | In/10 + Wk/15 − 3 |
+| Abwehr | (grade-based base) + AbwehrBonus + PA (parry) |
+| Zaubern | (class-based base) + ZauberBonus |
+| Raufen | (class-based base at Grad 1) |
+
+Dice-dependent values (rolled during character creation) include: `lp_max`, `ap_max`, `b_max`, `pa` (parry), `wk` (willpower). The frontend rolls dice and sends the results to `POST /api/characters/create-session/:id/derived` where the server validates and stores them.
+
+The frontend component in `CharacterCreation.vue` exposes the dice-roll UI and calls `GET /api/characters/calculate-static-fields` and `POST /api/characters/calculate-rolled-field` to get the values computed by the backend rather than computing them in JavaScript.
+
+### 6.3 Skill and Spell Learning System
+
+**File:** `backend/character/lerncost_handler.go`
+
+The learning cost engine computes what it costs (EP + TE, or EP + LE for spells) to learn a new skill/spell or improve an existing one. This is the most complex piece of business logic in the application.
+
+**Input:**
+```json
+{
+ "name": "Klettern",
+ "type": "skill",
+ "action": "improve",
+ "current_level": 10,
+ "target_level": 12,
+ "use_pp": 2,
+ "reward": { "type": "half_ep_improvement" }
+}
+```
+
+**Processing steps:**
+1. Look up the character's class abbreviation (`Typ`)
+2. Normalize the skill/spell name (trim, lowercase for lookup)
+3. Find the skill in `gsmaster` master data to get `Category` and `Difficulty`
+4. Query learning cost tables:
+ - For **skills**: `ClassCategoryEPCost` (EP per TE by class+category) + `SkillImprovementCost` (TE per level step) + `SkillCategoryDifficulty` (base LE)
+ - For **spells**: `ClassSpellSchoolEPCost` (EP per LE by class+spell school) + `SpellLevelLECost` (LE by spell level)
+ - For **weapon skills**: separate weapon skill cost tables
+5. Apply **practice points (PP)** reduction: each PP reduces required TE by 1 (capped at max PP available for category)
+6. Apply **reward bonuses** if specified:
+ - `free_learning` — zero cost
+ - `free_spell_learning` — zero cost for spells
+ - `half_ep_improvement` — halve EP cost when improving
+ - `gold_for_ep` — substitute up to half the EP with 10 GS each
+7. Return full cost breakdown plus `CanAfford` flag comparing costs to character's current EP/gold
+
+For **improve** action, costs are computed **per level step** from `current_level+1` to `target_level` and aggregated: `MultiLevelCostResponse` contains both per-step and total costs.
+
+The `ImproveSkill` and `LearnSkill` handlers execute the actual resource deduction and write audit log entries.
+
+### 6.4 Character Creation Wizard
+
+**Backend:** `backend/character/` (multiple handlers)
+**Frontend:** `frontend/src/components/CharacterCreation.vue` and `CharacterCreation/` sub-components
+**Session storage:** `models.CharacterCreationSession` (table: `char_creation_sessions`)
+
+The wizard is a **server-side session** model. A session stores partial character data as JSON blobs per step. Sessions expire (stored `ExpiresAt`).
+
+**Steps:**
+
+| Step | Name | Backend endpoint | Data stored |
+|------|------|-----------------|-------------|
+| 1 | Basic Info | `PUT .../basic` | Name, race, class, origin, social class, faith, gender, handedness |
+| 2 | Attributes | `PUT .../attributes` | Primary attributes (rolled client-side, validated server-side) |
+| 3 | Derived Values | `PUT .../derived` | Dice-rolled derived stats (LP/AP/B/PA/Wk) |
+| 4 | Skills | `PUT .../skills` | Selected starting skills with initial values |
+| 5 | Finalize | `POST .../finalize` | Creates the final `Char` record from all session data |
+
+**Available-skills-for-creation endpoint** (`POST /api/characters/available-skills-creation`):
+Returns all learnable skills for a specific class, grouped by category, with starting LP (Learning Points) that determine how many skills the character can learn at creation. LP are class+category specific (`ClassCategoryLearningPoints` table).
+
+**Special abilities** (`creation_rules.go`):
+M5 allows rolling a W100 to gain a random special ability at character creation. The `GetSpecialAbilityByRoll(roll)` function maps dice outcomes to named bonuses (e.g., roll 56–60 → "Gute Reflexe+9"). This is pure backend logic; the frontend passes the rolled value as part of the creation session data, and the server calls this function when computing derived values.
+
+**Dashboard integration:** `CharacterList.vue` shows in-progress sessions via `CharacterCreationSessions.vue`. Users can resume or delete draft sessions.
+
+### 6.5 PDF Export Pipeline
+
+**Flow (backend):**
+```
+ExportCharacterToPDF (pdfrender/handlers.go)
+ ↓
+LoadCharacterFromDB (full preload)
+ ↓
+For each template page (page1_stats.html, page2_play.html, ...):
+ → Execute Go text/template with character data
+ → chromedp: launch Chromium, navigate to data URL, print to PDF
+ → Check overflow marker
+ → Generate continuation pages if needed (page1.2_stats.html)
+ ↓
+pdfcpu.MergeFiles → single merged PDF
+ ↓
+Save to cfg.ExportTempDir
+ ↓
+Return { filename: "CharName_20231225_143045.pdf" }
+```
+
+**Flow (frontend):**
+```
+ExportDialog.vue
+ ↓
+GET /api/pdf/templates → populate template selector
+ ↓
+User selects format (PDF / VTT / BaMoRT) + template
+ ↓
+For PDF format:
+ GET /api/pdf/export/:id?template=Default_A4_Quer
+ → Response: { filename: "..." }
+ → window.open(baseURL + /api/pdf/file/, '_blank')
+
+For VTT format:
+ GET /api/importer/export/vtt/:id/file (blob) → browser download link
+
+For BaMoRT format:
+ GET /api/transfer/download/:id (blob) → browser download link
+```
+
+**Note:** For PDF, `window.open` is called after the async API response, making it subject to popup blockers.
+
+### 6.6 Audit Log
+
+**Files:** `backend/character/audit_log.go`, `audit_log_handlers.go`
+
+Every change to EP, gold, or significant character values writes an immutable `AuditLogEntry` record:
+
+| Field | Purpose |
+|-------|---------|
+| `CharacterID` | Which character |
+| `FieldName` | What changed (e.g., `"experience_points"`, `"goldstücke"`) |
+| `OldValue` / `NewValue` | Before and after |
+| `Difference` | `NewValue - OldValue` |
+| `Reason` | Typed constant: `manual`, `skill_learning`, `skill_improvement`, `spell_learning`, `equipment`, `reward`, `correction`, `import` |
+| `UserID` | Who made the change |
+| `Notes` | Free-text context |
+| `Timestamp` | Auto-set by GORM |
+
+API endpoints:
+- `GET /api/characters/:id/audit-log` — all entries or filtered by `?field=experience_points`
+- `GET /api/characters/:id/audit-log/stats` — aggregated statistics
+
+---
+
+## 7. Data Access and Persistence
+
+### Database Connection
+
+- **Production/development:** MariaDB 11.4 via GORM MySQL driver. DSN from `DATABASE_URL` env var.
+- **Tests:** SQLite (via CGO). `testutils.SetupTestDB()` copies `testdata/prepared_test_data.db` to a temp dir per test run, ensuring isolation.
+- **Global variable:** `database.DB *gorm.DB` — shared across all packages by import.
+
+### Schema Migrations
+
+`models.MigrateStructure(db)` is called at startup and runs GORM `AutoMigrate` for all entity types in dependency order:
+1. `game_systems`
+2. GSMaster tables (skills, spells, equipment, weapons, containers, believes, misc lookups)
+3. Character tables (chars, attributes, skills, spells, equipment sub-tables)
+4. Equipment tables
+5. Skill learning cost tables
+6. Learning/cost relation tables
+
+Migration history is tracked in `schema_version` and `migration_history` tables.
+
+### Test Data
+
+`testdata/prepared_test_data.db` is a pre-populated SQLite snapshot containing real test characters, including **character ID 18 ("Fanjo Vetrani")** which is used as the primary test fixture throughout all backend tests.
+
+### Custom Types
+
+`StringArray` is a custom GORM type that serializes `[]string` as JSON into a TEXT column:
+```go
+type StringArray []string
+// Stored as: ["specialization1","specialization2"]
+```
+Used by `Char.Spezialisierung`.
+
+---
+
+## 8. Module Interaction Map
+
+```
+┌─────────────────────────────────────────────────────────────────────┐
+│ Frontend (Vue 3) │
+│ │
+│ CharacterDetails ──────→ SkillView ──────→ SkillLearnDialog │
+│ │ │ │
+│ │ SpellView ──────→ SpellLearnDialog │
+│ │ │ │
+│ └──────→ ExportDialog │ │
+│ │ │ │
+└─────────────────────┼──────────────────────────────┼────────────────┘
+ │ REST API │ REST API
+ ┌─────────────▼──────────────┐ ┌───────────▼───────────────┐
+ │ pdfrender module │ │ character module │
+ │ ExportCharacterToPDF │ │ GetLernCostNewSystem │
+ │ chromedp → Chromium │ │ LearnSkill / ImproveSkill │
+ │ pdfcpu merge │ │ Audit log writes │
+ └─────────────────────────── ┘ └───────────────────────────┘
+ │
+ ┌────────────▼──────────────┐
+ │ gsmaster module │
+ │ Skill/Spell lookup │
+ │ Cost table queries │
+ └───────────────────────────┘
+ │
+ ┌────────────▼──────────────┐
+ │ models package │
+ │ GORM entity definitions │
+ │ LearnCost, Char, etc. │
+ └───────────────────────────┘
+ │
+ ┌────────────▼──────────────┐
+ │ database.DB │
+ │ MariaDB (prod/dev) │
+ │ SQLite (test) │
+ └───────────────────────────┘
+```
+
+**Cross-module dependencies (backend):**
+- `character` depends on `gsmaster` (skill/spell lookup for cost calculations)
+- `character` depends on `models` (all entity types)
+- `character` depends on `database` (direct DB access)
+- `gsmaster` depends on `models` and `database`
+- `pdfrender` depends on `models` and `database` (loads full character)
+- `maintenance` depends on `gsmaster`, `models`, `database`
+- `user` is depended on by all modules via `AuthMiddleware` import into `router`
+
+---
+
+## 9. Complete API Route Reference
+
+### Public Routes (no authentication)
+```
+POST /register
+POST /login
+POST /password-reset/request
+GET /password-reset/validate/:token
+POST /password-reset/reset
+GET /api/public/version
+GET /api/public/systeminfo
+GET /api/pdf/file/:filename
+```
+
+### User Routes (authenticated)
+```
+GET /api/user/profile
+PUT /api/user/display-name
+PUT /api/user/language
+PUT /api/user/email
+PUT /api/user/password
+```
+
+### App System Routes
+```
+GET /api/version — version info (authenticated)
+GET /api/systeminfo — system info (authenticated)
+GET /api/public/version — version info (public, no auth)
+GET /api/public/systeminfo — system info (public, no auth)
+```
+
+### Admin User Management (admin role required)
+```
+GET /api/users
+GET /api/users/:id
+PUT /api/users/:id/role
+DELETE /api/users/:id
+PUT /api/users/:id/password
+```
+
+### Character Routes
+```
+GET /api/characters — list own + shared characters
+POST /api/characters — create character
+GET /api/characters/:id — get full character (FeChar)
+PUT /api/characters/:id — update character
+PATCH /api/characters/:id — partial update
+DELETE /api/characters/:id — delete (owner only)
+PUT /api/characters/:id/image — update portrait
+GET /api/characters/:id/datasheet-options — dropdowns for editing
+
+GET /api/characters/:id/shares — list share grants
+PUT /api/characters/:id/shares — update share grants
+GET /api/characters/:id/available-users — users available for sharing
+
+GET /api/characters/:id/experience-wealth — EP + wealth snapshot
+PUT /api/characters/:id/experience — update EP
+PUT /api/characters/:id/wealth — update wealth
+
+GET /api/characters/:id/audit-log — change history
+GET /api/characters/:id/audit-log/stats — audit statistics
+
+POST /api/characters/lerncost-new — compute learning costs
+POST /api/characters/lerncost — alias for lerncost-new
+POST /api/characters/improve-skill-new — execute skill improvement
+POST /api/characters/improve-skill — alias
+POST /api/characters/:id/learn-skill-new — execute skill learning
+POST /api/characters/:id/learn-skill — alias
+POST /api/characters/:id/learn-spell-new — execute spell learning
+POST /api/characters/:id/learn-spell — alias
+
+POST /api/characters/available-skills-new — available skills with costs
+POST /api/characters/available-skills — alias
+POST /api/characters/available-skills-creation — skills for creation wizard
+POST /api/characters/available-spells-creation — spells for creation wizard
+POST /api/characters/available-spells-new — available spells with costs
+POST /api/characters/available-spells — alias
+GET /api/characters/spell-details — spell detail lookup
+GET /api/characters/:id/reward-types — available reward types
+
+GET /api/characters/:id/practice-points — PP by category
+PUT /api/characters/:id/practice-points — update PP
+POST /api/characters/:id/practice-points/add — add PP
+POST /api/characters/:id/practice-points/use — consume PP
+
+GET /api/characters/skill-categories — static skill category list
+
+GET /api/characters/create-sessions — list wizard sessions
+POST /api/characters/create-session — start new session
+GET /api/characters/create-session/:id — get session data
+PUT /api/characters/create-session/:id/basic — save step 1
+PUT /api/characters/create-session/:id/attributes — save step 2
+PUT /api/characters/create-session/:id/derived — save step 3
+PUT /api/characters/create-session/:id/skills — save step 4
+POST /api/characters/create-session/:id/finalize — create character from session
+DELETE /api/characters/create-session/:id — discard session
+
+GET /api/characters/races — available races list
+GET /api/characters/classes — available character classes
+GET /api/characters/classes/learning-points — LP per class+category for creation
+GET /api/characters/origins — available origins
+GET /api/characters/beliefs — belief/faith search
+
+POST /api/characters/calculate-static-fields — derived values (no dice)
+POST /api/characters/calculate-rolled-field — derived value requiring dice roll
+```
+
+### GSMaster Routes (read: all authenticated; write: maintainer+)
+```
+GET /api/maintenance — all master data
+GET /api/maintenance/skills
+GET /api/maintenance/skills-enhanced
+GET /api/maintenance/skills/:id
+GET /api/maintenance/skills-enhanced/:id
+GET /api/maintenance/weaponskills[/*]
+GET /api/maintenance/spells[/*]
+GET /api/maintenance/equipment[/*]
+GET /api/maintenance/weapons[/*]
+POST/PUT/DELETE above (maintainer only)
+```
+
+### Maintenance Routes (maintainer role required)
+```
+GET /api/maintenance/gsm-believes
+PUT /api/maintenance/gsm-believes/:id
+GET /api/maintenance/game-systems
+PUT /api/maintenance/game-systems/:id
+GET /api/maintenance/gsm-lit-sources
+PUT /api/maintenance/gsm-lit-sources/:id
+GET /api/maintenance/gsm-misc
+PUT /api/maintenance/gsm-misc/:id
+GET /api/maintenance/skill-improvement-cost2
+PUT /api/maintenance/skill-improvement-cost2/:id
+GET /api/maintenance/setupcheck
+GET /api/maintenance/setupcheck-dev
+GET /api/maintenance/mktestdata
+GET /api/maintenance/reconndb
+GET /api/maintenance/reloadenv
+POST /api/maintenance/transfer-sqlite-to-mariadb
+```
+
+### PDF Export Routes
+```
+GET /api/pdf/templates — list available templates
+GET /api/pdf/export/:id — export character to PDF
+POST /api/pdf/cleanup — clean up old generated PDF files
+GET /api/pdf/file/:filename — download generated PDF (public, no auth)
+```
+
+### Importer Routes
+```
+POST /api/importer/upload — import VTT JSON + CSV
+POST /api/importer/spells/csv — import spell CSV
+GET /api/importer/export/vtt/:id — export as VTT JSON
+GET /api/importer/export/vtt/:id/file — download VTT JSON file
+GET /api/importer/export/csv/:id — export as CSV
+GET /api/importer/export/spells/csv — export all spells as CSV
+```
+
+### Transfer Routes
+```
+GET /api/transfer/export/:id — export character JSON
+GET /api/transfer/download/:id — download character JSON file
+POST /api/transfer/import — import character JSON
+POST /api/transfer/database/export — export full database
+POST /api/transfer/database/import — import full database
+```
+
+---
+
+## 10. Infrastructure and Deployment
+
+### Development Environment
+
+```
+docker/docker-compose.dev.yml
+ bamort-backend-dev (port 8180) — Go source mounted + Air live-reload
+ bamort-frontend-dev (port 5173) — Vue source mounted + Vite HMR
+ bamort-mariadb-dev (port 3306) — MariaDB with health-check
+ bamort-phpmyadmin-dev (port 8081) — Database GUI
+```
+
+- Full source code is volume-mounted into each container.
+- Backend uses `air -c .air.toml` for hot-reload on `.go` file changes.
+- Frontend uses Vite's native HMR for instant module replacement.
+- Database data persists in `docker/bamort-db-dev/` on the host.
+- Database is initialized from `docker/init-db/*.sql` on first container creation only.
+
+### Production Environment
+
+```
+docker/docker-compose.yml
+ bamort-backend (port 8182→8180) — compiled Go binary, no source mount
+ bamort-frontend (port 8181→80) — Nginx serving pre-built Vue bundle
+ bamort-mariadb (not exposed) — MariaDB accessible only within Docker network
+```
+
+- Backend image: multi-stage build. Stage 1 (golang:1.25-alpine) compiles the binary. Stage 2 (alpine:3.23) installs Chromium and copies only the binary + templates.
+- Frontend image: multi-stage. Stage 1 (node:22-alpine) runs `npm run build` with `VITE_*` build args baked in. Stage 2 (nginx:alpine) serves the static bundle.
+- **`VITE_API_URL`** is a **compile-time** build argument — runtime env vars cannot change it after image build.
+- Database port is intentionally not exposed in production.
+- TLS termination is expected at an external reverse proxy (e.g., Traefik).
+- Templates are volume-mounted: `./templates:/app/templates`.
+
+### Starting/Stopping
+
+```bash
+# Development
+cd /data/dev/bamort
+./docker/start-dev.sh # builds + starts all dev containers
+./docker/stop-dev.sh # stops and removes dev containers
+
+# Production
+./docker/start-prd.sh # builds new images while old containers run, then switches
+./docker/stop-prd.sh # stops production containers
+```
+
+---
+
+## 11. User Roles and Access Control
+
+| Capability | standard | maintainer | admin |
+|-----------|----------|-----------|-------|
+| Register / login | ✓ | ✓ | ✓ |
+| View own characters | ✓ | ✓ | ✓ |
+| View public/shared characters | ✓ | ✓ | ✓ |
+| Create/edit/delete own characters | ✓ | ✓ | ✓ |
+| Learn/improve skills and spells | ✓ | ✓ | ✓ |
+| Export characters to PDF | ✓ | ✓ | ✓ |
+| Import/export characters (JSON/CSV) | ✓ | ✓ | ✓ |
+| Read GSMaster data | ✓ | ✓ | ✓ |
+| Write GSMaster data | ✗ | ✓ | ✓ |
+| Maintenance operations | ✗ | ✓ | ✓ |
+| List all users | ✗ | ✗ | ✓ |
+| Change user roles | ✗ | ✗ | ✓ |
+| Delete users | ✗ | ✗ | ✓ |
+| Reset any user's password | ✗ | ✗ | ✓ |
+
+**Enforcement mechanisms:**
+- Backend: `user.AuthMiddleware()` on all `/api/*` routes
+- Backend: `user.RequireMaintainer()` middleware on maintenance write routes
+- Backend: `user.RequireAdmin()` middleware on `/api/users/*` routes
+- Backend: `checkCharacterOwnership()` inside character handlers (returns 403 if user doesn't own the character)
+- Frontend: router navigation guard redirects non-admins away from `/users`
+- Frontend: `isOwner` prop controls visibility of edit/delete buttons in `CharacterDetails`
+
+---
+
+## 12. Internationalisation (i18n)
+
+**Implementation:** `vue-i18n` (v11, composition-mode `legacy: false`)
+
+**Locales:** `src/locales/de/` and `src/locales/en/` — JS module files (not JSON) exporting translation objects. Both must be updated when adding new UI strings.
+
+**Default language:** `de` (German)
+
+**Language selection precedence:**
+1. User's stored preference in `localStorage.language`
+2. User's `preferred_language` field from their profile (set on `fetchCurrentUser()`)
+3. Fallback to `de`
+
+**Language change flow:**
+- User changes language in `UserProfileView` → `PUT /api/user/language`
+- Frontend immediately sets `this.$i18n.locale` and updates `localStorage.language`
+- On next login, `userStore.fetchCurrentUser()` sets locale from profile
+
+**Usage in components:**
+```vue
+{{ $t('skill.name') }}
+this.$t('error.notFound')
+this.$te('help.faq3.question')
+```
+
+---
+
+## 13. Known Limitations and Security Notes
+
+### Authentication Security
+
+> **Important:** The current token scheme has cryptographic weaknesses.
+
+1. **MD5 token generation** — MD5 is not a cryptographic authentication scheme. The token is derived predictably from `username + createdAt`, without a secret key. An attacker with knowledge of when a user registered could forge tokens.
+2. **No token signature verification** — `CheckToken` extracts the user ID from the token string and loads the user from the database; it does not verify that the token was actually issued by the server.
+3. **MD5 password hashing** — Passwords are hashed with MD5 (not bcrypt/argon2). MD5 is unsuitable for password storage; it is fast enough for brute-force attacks and not salted.
+4. **Token scope** — Tokens do not expire. Once issued, a token is valid indefinitely.
+
+The bcrypt implementation is commented out in `user/handlers.go` and `user/model.go` — migration to bcrypt for future versions is anticipated but not yet active.
+
+### Other Notes
+
+- `backend/startserver.sh` contains hardcoded development credentials — this file should not be committed to public repositories.
+- The PDF download endpoint (`/api/pdf/file/:filename`) is unauthenticated by design (to allow direct browser downloads) but relies on filename unpredictability for security.
+- `FileUploadPage.vue` manually adds an `Authorization` header that is already added by the Axios interceptor — this is a redundancy, not a security issue.
+- The production `DATABASE_URL` in `docker-compose.yml` is constructed inline without fallback defaults — missing env vars will produce an empty credentials DSN.
diff --git a/backend_findings.md b/backend_findings.md
new file mode 100644
index 0000000..c253157
--- /dev/null
+++ b/backend_findings.md
@@ -0,0 +1,1362 @@
+# BaMoRT Backend — Comprehensive Architecture & Code Findings
+
+> Generated: 2026-03-13
+> Source: `/home/de31a2/bamort/backend`
+> Module: `bamort` (Go 1.24, toolchain 1.24.4)
+
+---
+
+## Table of Contents
+
+1. [Module Structure Overview](#1-module-structure-overview)
+2. [Entry Point — cmd/main.go](#2-entry-point--cmdmaingo)
+3. [Router & Middleware Setup](#3-router--middleware-setup)
+4. [Authentication & Authorization](#4-authentication--authorization)
+5. [Configuration](#5-configuration)
+6. [Database Layer](#6-database-layer)
+7. [Models (GORM Entities)](#7-models-gorm-entities)
+8. [Module-by-Module Analysis](#8-module-by-module-analysis)
+ - [user/](#81-user)
+ - [character/](#82-character)
+ - [equipment/](#83-equipment)
+ - [gsmaster/](#84-gsmaster)
+ - [gamesystem/](#85-gamesystem)
+ - [maintenance/](#86-maintenance)
+ - [pdfrender/](#87-pdfrender)
+ - [importer/](#88-importer)
+ - [transfer/](#89-transfer)
+ - [appsystem/](#810-appsystem)
+ - [logger/](#811-logger)
+ - [mail/](#812-mail)
+ - [router/](#813-router)
+ - [config/](#814-config)
+ - [database/](#815-database)
+ - [testutils/](#816-testutils)
+ - [api/](#817-api)
+9. [Complete API Route Listing](#9-complete-api-route-listing)
+10. [Testing Approach](#10-testing-approach)
+11. [Key Dependencies](#11-key-dependencies)
+12. [Security Observations](#12-security-observations)
+13. [Cross-Module Relationships Diagram](#13-cross-module-relationships-diagram)
+
+---
+
+## 1. Module Structure Overview
+
+```
+backend/
+├── cmd/ # Entry point (main.go)
+├── config/ # App configuration loading from env/.env
+├── database/ # DB connection, migrations, test helpers
+├── models/ # All GORM entities and migration orchestration
+├── router/ # Gin engine setup, CORS, base route group + JWT guard
+├── user/ # User accounts, auth, roles, password reset
+├── character/ # Core character CRUD, skills, spells, learning system
+├── equipment/ # Character equipment (weapons, containers, gear)
+├── gsmaster/ # Game-system master data (skills, spells, weapons, learning costs)
+├── gamesystem/ # Game system record management and initial seed data
+├── maintenance/ # Admin/maintainer ops – DB checks, migrations, scratchpad endpoints
+├── pdfrender/ # HTML→PDF export using chromedp + pdfcpu merge
+├── importer/ # VTT-JSON / CSV import and export of characters/spells
+├── transfer/ # JSON-based character and full-database import/export
+├── appsystem/ # Version and system-info endpoints
+├── logger/ # Custom leveled logger (DEBUG/INFO/WARN/ERROR)
+├── mail/ # SMTP email client (password reset emails)
+├── api/ # Integration tests spanning multiple modules
+├── testutils/ # Shared test environment setup helpers
+├── testdata/ # Prepared SQLite snapshot used by all tests
+├── templates/ # PDF HTML templates (Default_A4_Quer/)
+└── scripts/ # Shell scripts (e.g., SQLite→MariaDB transfer)
+```
+
+Each domain module follows the pattern:
+
+```
+module/
+ handlers.go HTTP handler functions (Gin controllers)
+ routes.go RegisterRoutes(r *gin.RouterGroup)
+ *_test.go Tests using setupTestEnvironment(t)
+```
+
+---
+
+## 2. Entry Point — cmd/main.go
+
+**File:** `cmd/main.go`
+
+### Startup Sequence
+
+```go
+func main() {
+ cfg := config.Cfg // 1. Load config (auto-loaded in init())
+
+ logger.SetDebugMode(cfg.DebugMode) // 2. Configure logger
+ logger.SetMinLogLevel(...)
+
+ if cfg.IsProduction() {
+ gin.SetMode(gin.ReleaseMode) // 3. Set Gin mode
+ }
+
+ database.ConnectDatabase() // 4. Connect to database
+
+ pdfrender.InitializeTemplates(...) // 5. Sync PDF templates
+
+ r := gin.Default()
+ router.SetupGin(r) // 6. CORS middleware
+
+ protected := router.BaseRouterGrp(r) // 7. Public auth routes + JWT-protected group
+
+ // 8. Register module routes
+ user.RegisterRoutes(protected)
+ gsmaster.RegisterRoutes(protected)
+ character.RegisterRoutes(protected)
+ equipment.RegisterRoutes(protected)
+ maintenance.RegisterRoutes(protected)
+ importer.RegisterRoutes(protected)
+ pdfrender.RegisterRoutes(protected)
+ transfer.RegisterRoutes(protected)
+ appsystem.RegisterRoutes(protected)
+
+ // 9. Public routes (no auth)
+ pdfrender.RegisterPublicRoutes(r)
+ appsystem.RegisterPublicRoutes(r)
+
+ r.Run(cfg.GetServerAddress()) // 10. Start HTTP server
+}
+```
+
+### Swagger Annotations
+The file contains `@title BaMoRT API`, `@version 1`, `@host localhost:8180` – indicates Swagger/OpenAPI doc generation is intended.
+
+---
+
+## 3. Router & Middleware Setup
+
+### CORS (`router/setup.go`)
+
+```go
+func SetupGin(r *gin.Engine) {
+ allowedOrigins := []string{
+ config.Cfg.FrontendURL,
+ "http://localhost:5173",
+ "http://192.168.0.48:5173",
+ "http://192.168.0.36:5173",
+ "https://bamort.trokan.de",
+ }
+ r.Use(cors.New(cors.Config{
+ AllowOrigins: allowedOrigins,
+ AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
+ AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
+ ExposeHeaders: []string{"Content-Length"},
+ AllowCredentials: true,
+ MaxAge: 12 * 3600,
+ }))
+}
+```
+
+### Route Groups (`router/routes.go`)
+
+**Public (unauthenticated) routes:**
+
+| Method | Path | Handler |
+|--------|------|---------|
+| POST | `/register` | `user.RegisterUser` |
+| POST | `/login` | `user.LoginUser` |
+| POST | `/password-reset/request` | `user.RequestPasswordReset` |
+| GET | `/password-reset/validate/:token` | `user.ValidateResetToken` |
+| POST | `/password-reset/reset` | `user.ResetPassword` |
+
+**Protected group:** `/api` — guarded by `user.AuthMiddleware()`.
+
+---
+
+## 4. Authentication & Authorization
+
+### Token Scheme
+
+The project uses a **custom MD5-based token**, not JWT.
+
+**Token Generation (`user/handlers.go`):**
+```go
+func GenerateToken(u *User) string {
+ tx := md5.Sum([]byte(u.Username + u.CreatedAt.String()))
+ hashString := hex.EncodeToString(tx[:])
+ // Insert ".{userID}:" at position 7
+ pos := 7
+ idm := "." + fmt.Sprintf("%d", u.UserID) + ":"
+ token := hashString[:pos] + idm + hashString[pos:]
+ return token
+}
+```
+
+The token embeds the user ID in a predictable location. Clients send it as the `Authorization` header value (prefixed `Bearer `).
+
+**Token Validation (`CheckToken`):**
+1. Extracts user ID from position `7 + len("Bearer ")` in the `Authorization` header.
+2. Loads the user from the database by that ID.
+3. Returns the user or `nil`.
+
+**No cryptographic signature is verified.** Any token with a valid user ID at the expected position is accepted.
+
+### `AuthMiddleware()` (`user/handlers.go`)
+
+Sets `userID`, `username`, and `user` in the Gin context for downstream handlers.
+
+### Password Hashing
+
+Passwords are hashed with **MD5** (not bcrypt):
+```go
+hashedPassword := md5.Sum([]byte(user.PasswordHash))
+user.PasswordHash = hex.EncodeToString(hashedPassword[:])
+```
+The bcrypt implementation is commented out.
+
+### Roles (`user/model.go`)
+
+| Constant | Value | Description |
+|----------|-------|-------------|
+| `RoleStandardUser` | `"standard"` | Default for new registrations |
+| `RoleMaintainer` | `"maintainer"` | Can manage master data |
+| `RoleAdmin` | `"admin"` | Can manage users |
+
+Role-checking middleware:
+- `RequireAdmin()` → used on `/api/users/*` admin endpoints
+- `RequireMaintainer()` → used on maintenance/gsmaster write endpoints
+
+---
+
+## 5. Configuration
+
+**File:** `config/config.go`
+
+### `Config` Struct Fields
+
+| Field | Env Var(s) | Default | Description |
+|-------|-----------|---------|-------------|
+| `ServerPort` | `API_PORT`, `SERVER_PORT` | `"8180"` | HTTP listen port |
+| `DatabaseURL` | `DATABASE_URL` | `""` | DSN string for DB driver |
+| `DatabaseType` | `DATABASE_TYPE` | `"mysql"` | `mysql` or `sqlite` |
+| `DebugMode` | `DEBUG` | `false` | Enables verbose logging |
+| `LogLevel` | `LOG_LEVEL` | `"INFO"` | `DEBUG`, `INFO`, `WARN`, `ERROR` |
+| `Environment` | `ENVIRONMENT`, `GO_ENV` | `"production"` | `production`, `development`, `test` |
+| `DevTesting` | `DEVTESTING` | `"no"` | `"yes"` redirects to SQLite test DB |
+| `FrontendURL` | `BASE_URL` | `"http://localhost:5173"` | CORS allowed origin |
+| `TemplatesDir` | `TEMPLATES_DIR` | `"./templates"` | PDF template directory |
+| `ExportTempDir` | `EXPORT_TEMP_DIR` | `"./xporttemp"` | Temporary PDF output directory |
+| `MailHost` | `MAIL_HOST` | `""` | SMTP server hostname |
+| `MailPort` | `MAIL_PORT` | `465` | SMTP port (465=TLS, 587=STARTTLS) |
+| `MailUsername` | `MAIL_USERNAME` | `""` | SMTP credentials |
+| `MailPassword` | `MAIL_PASSWORD` | `""` | SMTP credentials |
+| `MailFrom` | `MAIL_FROM` | (falls back to username) | Sender email address |
+
+### Loading Logic
+
+1. Reads `.env` and `.env.local` files from the working directory.
+2. Overrides with env vars.
+3. `ENVIRONMENT=development` automatically enables `DebugMode=true` and `LogLevel=DEBUG`.
+4. `ENVIRONMENT=test` or `DEVTESTING=yes` causes `ConnectDatabase()` to use the SQLite test snapshot.
+
+---
+
+## 6. Database Layer
+
+**Package:** `database/`
+
+### Connection (`database/config.go`)
+
+```go
+func ConnectDatabase() *gorm.DB {
+ if envIsTest || devTestingIsYes {
+ SetupTestDB() // Use prepared SQLite snapshot
+ } else {
+ ConnectDatabaseOrig() // MySQL or SQLite via DATABASE_URL
+ }
+ return DB
+}
+```
+
+Supported drivers: `mysql` (default), `sqlite`.
+
+### Global Variable
+
+`database.DB *gorm.DB` — all packages import this directly.
+
+### Test Database
+
+- **Source:** `testdata/prepared_test_data.db` (SQLite snapshot with real test data including character ID 18 "Fanjo Vetrani")
+- **Mechanism:** `SetupTestDB()` copies the snapshot to a `os.MkdirTemp` directory, opens it, and assigns it to `database.DB`.
+- Each test run gets a fresh isolated copy.
+
+### Migration Tables (`database/model.go`)
+
+| Table | Purpose |
+|-------|---------|
+| `schema_version` | Current schema version tracking |
+| `migration_history` | Log of applied migrations |
+
+### Migration Orchestration (`models/database.go`)
+
+`models.MigrateStructure(db)` calls in order:
+1. `gameSystemMigrateStructure` — `game_systems`
+2. `gsMasterMigrateStructure` — skills, spells, equipment, weapons, etc.
+3. `characterMigrateStructure` — characters, skills, spells, equipment
+4. `equipmentMigrateStructure`
+5. `skillsMigrateStructure` — learning cost tables
+6. `importerMigrateStructure` — (currently no-op)
+7. `learningMigrateStructure` — all learning/cost relation tables
+
+### `StringArray` Custom Type
+
+```go
+type StringArray []string
+// Stored as JSON in TEXT column (Value+Scan implementations)
+```
+
+Used for `Char.Spezialisierung` (character specializations).
+
+---
+
+## 7. Models (GORM Entities)
+
+### Base Types
+
+```go
+type BamortBase struct {
+ ID uint `gorm:"primaryKey"`
+ Name string
+}
+
+type BamortCharTrait struct {
+ BamortBase
+ CharacterID uint `gorm:"index"`
+ UserID uint `gorm:"index"`
+}
+
+type Magisch struct {
+ IstMagisch bool
+ Abw int
+ Ausgebrannt bool
+}
+```
+
+---
+
+### User Model (`user/model.go` → table: `users`)
+
+| Field | Type | Notes |
+|-------|------|-------|
+| `UserID` | uint PK | |
+| `Username` | string unique | |
+| `DisplayName` | string | Falls back to Username if empty |
+| `PasswordHash` | string | MD5 hex string |
+| `Email` | string unique | |
+| `Role` | string | `standard`, `maintainer`, `admin` |
+| `PreferredLanguage` | string | default `de` |
+| `ResetPwHash` | *string | Hidden from JSON serialization |
+| `ResetPwHashExpires` | *time.Time | 14 days validity |
+| `CreatedAt`, `UpdatedAt` | time.Time | |
+
+---
+
+### Character Model (`models/model_character.go` → table: `char_chars`)
+
+**Main struct `Char`:**
+
+| Field | Type | Notes |
+|-------|------|-------|
+| `ID` | uint PK | (via BamortBase) |
+| `Name` | string | Character name |
+| `GameSystem` | string | e.g. `"midgard"` |
+| `GameSystemId` | uint | FK to `game_systems` |
+| `UserID` | uint index | FK to `users` |
+| `User` | User | FK `UserID→users.user_id` CASCADE |
+| `Rasse` | string | Race (e.g. "Mensch", "Zwerg") |
+| `Typ` | string | Class code (e.g. "Kr", "Ma") |
+| `Alter` | int | Age |
+| `Anrede` | string | Title/salutation |
+| `Grad` | int | Character grade/level |
+| `Gender` | string | |
+| `SocialClass` | string | |
+| `Groesse` | int | Height (cm) |
+| `Gewicht` | int | Weight (kg) |
+| `Herkunft` | string | Origin/homeland |
+| `Glaube` | string | Faith |
+| `Hand` | string | Dominant hand |
+| `Public` | bool | Publicly visible |
+| `ResistenzKoerper` | int | Body resistance (derived) |
+| `ResistenzGeist` | int | Mental resistance (derived) |
+| `Abwehr` | int | Defense value (derived) |
+| `Zaubern` | int | Spell casting value (derived) |
+| `Raufen` | int | Brawling value (derived) |
+| `Lp` | Lp | Life points (has-one, CASCADE) |
+| `Ap` | Ap | Action points (has-one) |
+| `B` | B | Load/burden (has-one) |
+| `Merkmale` | Merkmale | Physical traits (has-one) |
+| `Eigenschaften` | []Eigenschaft | Attributes Au/Gs/Gw/In/Ko/St/Wk/Zt/PA |
+| `Fertigkeiten` | []SkFertigkeit | Skills (has-many) |
+| `Waffenfertigkeiten` | []SkWaffenfertigkeit | Weapon skills |
+| `Zauber` | []SkZauber | Spells |
+| `Spezialisierung` | StringArray | TEXT/JSON stored |
+| `Bennies` | Bennies | Gg/Gp/Sg |
+| `Vermoegen` | Vermoegen | GS/SS/KS wealth |
+| `Erfahrungsschatz` | Erfahrungsschatz | EP/ES experience |
+| `Waffen` | []EqWaffe | Weapons (has-many) |
+| `Behaeltnisse` | []EqContainer | Containers |
+| `Transportmittel` | []EqContainer | Transportation |
+| `Ausruestung` | []EqAusruestung | General equipment |
+| `Image` | string | Base64 or URL |
+
+**Helper types:**
+
+| Type | Table | Fields |
+|------|-------|--------|
+| `Eigenschaft` | (embedded in preload) | ID, CharacterID, UserID, Name, Value |
+| `Lp` | — | ID, CharacterID, Max, Value |
+| `Ap` | — | ID, CharacterID, Max, Value |
+| `B` | — | ID, CharacterID, Max, Value |
+| `Merkmale` | — | BamortCharTrait + Augenfarbe/Haarfarbe/Sonstige/Breite/Groesse |
+| `Erfahrungsschatz` | — | BamortCharTrait + ES, EP |
+| `Bennies` | — | BamortCharTrait + Gg, Gp, Sg |
+| `Vermoegen` | — | BamortCharTrait + Goldstuecke, Silberstuecke, Kupferstuecke |
+
+**`FeChar`** (frontend-facing):
+Embeds `Char` + `Git` (gift tolerance = 30+Ko/2) + `CategorizedSkills` map + `InnateSkills` slice.
+
+---
+
+### Skill Models (`models/model_char_skills.go`)
+
+| Type | Table | Key Fields |
+|------|-------|------------|
+| `SkFertigkeit` | `char_skills` | BamortCharTrait + Fertigkeitswert, BasisWert, Bonus, Pp, Category, Improvable, LearningCost |
+| `SkWaffenfertigkeit` | `char_weaponskills` | Embeds SkFertigkeit |
+| `SkAngeboreneFertigkeit` | (no own table) | Embeds SkFertigkeit |
+| `SkZauber` | `char_spells` | BamortCharTrait + Beschreibung, Bonus, Quelle |
+
+---
+
+### Equipment Models (`models/model_char_equipment.go`)
+
+| Type | Table | Key Fields |
+|------|-------|------------|
+| `EqAusruestung` | `equi_equipments` | BamortCharTrait + Magisch + Anzahl, ContainedIn, Bonus, Gewicht, Wert |
+| `EqWaffe` | `equi_weapons` | BamortCharTrait + Magisch + Abwb, Anb, Anzahl, Schb, NameFuerSpezialisierung |
+| `EqContainer` | `equi_containers` | BamortCharTrait + Magisch + IsTransportation, Tragkraft, Volumen, ExtID |
+
+---
+
+### GSMaster Models (`models/model_gsmaster.go`)
+
+| Type | Table | Key Fields |
+|------|-------|------------|
+| `Skill` | `gsm_skills` | ID, GameSystem/Id, Name, Initialwert, BasisWert, Bonuseigenschaft, Improvable, InnateSkill, Category, Difficulty |
+| `WeaponSkill` | (inherits Skill) | Embeds Skill |
+| `Spell` | (via gsmaster) | ID, Name, Stufe, AP, Art, Zauberdauer, Reichweite, Ursprung, Category, LearningCategory |
+| `Equipment` | (via gsmaster) | ID, Name, Gewicht, Wert, PersonalItem |
+| `Weapon` | (extends Equipment) | SkillRequired, Damage, RangeNear/Middle/Far |
+| `Container` | (extends Equipment) | Tragkraft, Volumen |
+| `Transportation` | (extends Container) | — |
+| `Believe` | (via gsmaster) | ID, GameSystem/Id, Name, Beschreibung, SourceID |
+| `MiscLookup` | `gsm_misc_lookups` | ID, GameSystem/Id, Key, Value |
+| `LookupList` | `gsm_lookup_lists` | Generic lookup |
+| `LearnCost` | (computed, no own table) | GameSystem, Stufe, LE, TE, Ep, Money, PP |
+
+---
+
+### Game System (`models/model_game_system.go` → table: `game_systems`)
+
+| Field | Notes |
+|-------|-------|
+| `Code` | Unique, e.g. `"M5"` |
+| `Name` | e.g. `"M-System"` |
+| `IsActive` | bool |
+
+Default game system: Code=`M5`, Name=`M-System`.
+
+---
+
+### Learning-Cost Models (`models/model_learning_costs.go`)
+
+| Type | Table | Purpose |
+|------|-------|---------|
+| `Source` | `gsm_lit_sources` | Rulebook/sourcebook references |
+| `CharacterClass` | `gsm_character_classes` | Class codes (Kr, Ma, Hx …) + source link |
+| `SkillCategory` | `learning_skill_categories` | Alltag, Freiland, Halbwelt, Kampf, Körper, Sozial, Unterwelt, Waffen, Wissen |
+| `SkillDifficulty` | `learning_skill_difficulties` | leicht, normal, schwer, sehr schwer |
+| `SpellSchool` | `learning_spell_schools` | Beherrschen, Bewegen, Erkennen, … |
+| `ClassCategoryEPCost` | `learning_class_category_ep_costs` | EP per TE by class+category |
+| `ClassSpellSchoolEPCost` | `learning_class_spell_school_ep_costs` | EP per LE by class+school |
+| `SpellLevelLECost` | `learning_spell_level_le_costs` | LE required by spell level |
+| `SkillCategoryDifficulty` | `learning_skill_category_difficulties` | LE cost for skill in category+difficulty |
+| `WeaponSkillCategoryDifficulty` | (separate table) | LE cost for weapon skills |
+| `SkillImprovementCost` | `learning_skill_improvement_costs` | TE to improve by current level |
+| `ClassCategoryLearningPoints` | — | Starting LP per class+category for creation |
+| `AuditLogEntry` | `audit_log_entries`(?) | EP/gold change audit trail |
+
+---
+
+### Character Creation Session (`models/model_character_creation.go`)
+
+| Type | Table | Purpose |
+|------|-------|---------|
+| `CharacterCreationSession` | `char_creation_sessions` | Multi-step wizard persistence |
+
+Fields: UserID, Name, Rasse, Typ, Herkunft, Stand, Glaube, Attributes (JSON), DerivedValues (JSON), Skills (JSON), Spells (JSON), SkillPoints (JSON), CurrentStep, ExpiresAt.
+
+---
+
+### Character Share (`models/model_character_share.go` → table: `char_shares`)
+
+| Field | Notes |
+|-------|-------|
+| `CharacterID` | FK to char_chars |
+| `UserID` | FK to users (recipient) |
+| `Permission` | `"read"` or `"write"` |
+
+---
+
+### Database Schema/Migration Models (`database/model.go`)
+
+| Type | Table |
+|------|-------|
+| `SchemaVersion` | `schema_version` |
+| `MigrationHistory` | `migration_history` |
+
+---
+
+## 8. Module-by-Module Analysis
+
+---
+
+### 8.1 `user/`
+
+**Purpose:** User account management, authentication, authorization, password reset.
+
+**Key Files:**
+- `model.go` — User struct, CRUD methods, reset-hash helpers
+- `handlers.go` — RegisterUser, LoginUser, CheckToken, AuthMiddleware, password reset
+- `admin_handlers.go` — ListUsers, GetUser, UpdateUserRole, ChangeUserPassword, DeleteUser
+- `middleware.go` — RequireRole, RequireAdmin, RequireMaintainer
+- `database.go` — `MigrateStructure` for `users` table
+- `routes.go` — Route registration
+
+**Business Logic:**
+- Registration creates users with MD5-hashed passwords and a default `standard` role.
+- Login returns a custom MD5 token containing the encoded user ID.
+- Password reset uses a 32-byte random hex hash (secure) with a 14-day expiry, sent via SMTP.
+- `AuthMiddleware` validates the token structure, loads the user from DB, injects `userID`/`username`/`user` into context.
+
+**HTTP Endpoints (protected unless noted):**
+
+| Method | Path | Handler | Auth level |
+|--------|------|---------|-----------|
+| POST | `/register` | `RegisterUser` | Public |
+| POST | `/login` | `LoginUser` | Public |
+| POST | `/password-reset/request` | `RequestPasswordReset` | Public |
+| GET | `/password-reset/validate/:token` | `ValidateResetToken` | Public |
+| POST | `/password-reset/reset` | `ResetPassword` | Public |
+| GET | `/api/user/profile` | `GetUserProfile` | Auth |
+| PUT | `/api/user/display-name` | `UpdateDisplayName` | Auth |
+| PUT | `/api/user/email` | `UpdateEmail` | Auth |
+| PUT | `/api/user/password` | `UpdatePassword` | Auth |
+| PUT | `/api/user/language` | `UpdateLanguage` | Auth |
+| GET | `/api/users` | `ListUsers` | Admin |
+| GET | `/api/users/:id` | `GetUser` | Admin (own profile also allowed) |
+| PUT | `/api/users/:id/role` | `UpdateUserRole` | Admin |
+| PUT | `/api/users/:id/password` | `ChangeUserPassword` | Admin |
+| DELETE | `/api/users/:id` | `DeleteUser` | Admin |
+
+---
+
+### 8.2 `character/`
+
+**Purpose:** Core character management — CRUD, skill/spell learning system, character creation wizard, audit logging, sharing, practice points, PDF-relevant data prep.
+
+**Key Files:**
+- `handlers.go` — ListCharacters, CreateCharacter, GetCharacter, UpdateCharacter, DeleteCharacter, ToFeChar, UpdateCharacterImage, GetDatasheetOptions
+- `lerncost_handler.go` — `GetLernCostNewSystem`, `ImproveSkill`, `LearnSkill`, `LearnSpell` — full learning economy
+- `derived_values_calculator.go` — `CalculateStaticFields`, `CalculateRolledField` — character creation math
+- `creation_rules.go` — `GetSpecialAbilityByRoll` — special ability table
+- `audit_log.go` — `CreateAuditLogEntry`, `GetAuditLogForCharacter/Field`
+- `share_handlers.go` — `GetCharacterShares`, `UpdateCharacterShares`, `GetAvailableUsersForSharing`
+- `practice_points_handler.go` — `GetPracticePoints`, `UpdatePracticePoints`, `AddPracticePoint`, `UsePracticePoint`
+- `skill_type_helper.go` — skill categorization helpers
+- `system_information_handlers.go` — `GetSkillCategoriesHandlerStatic`
+- `spell_utils.go` — spell availability and creation utilities
+- `database.go` — `SaveCharacterToDB`
+- `image_handler.go` — character image upload/handling
+
+**Business Logic:**
+- `ListCharacters`: Returns `{ self_owned: [], others: [] }` where `others` = public + shared.
+- `GetCharacter`: Returns `FeChar` (augmented view with categorized skills).
+- `toFeChar`: Adds bonus field from attributes, splits skills by category.
+- `checkCharacterOwnership`: Guards update/delete/share with `character.UserID == c.GetUint("userID")`.
+- **Learning System** (`GetLernCostNewSystem`): Calculates LE/TE/EP/Money cost for learning or improving a skill/spell/weapon. Considers: character class, skill category, difficulty, current level, practice points (PP) reductions, gold-for-EP conversion, reward types (free learning, half EP).
+- **Derived Values**: `CalculateStaticFieldsLogic` implements Midgard 5th edition formulae for Ausdauer-, Schadens-, Angriffs-, Abwehr-, Zauber-Bonus, Resistenz, Abwehr, Zaubern, Raufen.
+
+**HTTP Endpoints (`/api/characters/...`):**
+
+| Method | Path | Handler |
+|--------|------|---------|
+| GET | `/api/characters` | `ListCharacters` |
+| POST | `/api/characters` | `CreateCharacter` |
+| GET | `/api/characters/:id` | `GetCharacter` |
+| PUT/PATCH | `/api/characters/:id` | `UpdateCharacter` |
+| DELETE | `/api/characters/:id` | `DeleteCharacter` |
+| PUT | `/api/characters/:id/image` | `UpdateCharacterImage` |
+| GET | `/api/characters/:id/datasheet-options` | `GetDatasheetOptions` |
+| GET | `/api/characters/:id/shares` | `GetCharacterShares` |
+| PUT | `/api/characters/:id/shares` | `UpdateCharacterShares` |
+| GET | `/api/characters/:id/available-users` | `GetAvailableUsersForSharing` |
+| GET | `/api/characters/:id/experience-wealth` | `GetCharacterExperienceAndWealth` |
+| PUT | `/api/characters/:id/experience` | `UpdateCharacterExperience` |
+| PUT | `/api/characters/:id/wealth` | `UpdateCharacterWealth` |
+| GET | `/api/characters/:id/audit-log` | `GetCharacterAuditLog` |
+| GET | `/api/characters/:id/audit-log/stats` | `GetAuditLogStats` |
+| POST | `/api/characters/lerncost-new` | `GetLernCostNewSystem` |
+| POST | `/api/characters/lerncost` | `GetLernCostNewSystem` |
+| POST | `/api/characters/improve-skill-new` | `ImproveSkill` |
+| POST | `/api/characters/improve-skill` | `ImproveSkill` |
+| POST | `/api/characters/:id/learn-skill-new` | `LearnSkill` |
+| POST | `/api/characters/:id/learn-skill` | `LearnSkill` |
+| POST | `/api/characters/:id/learn-spell-new` | `LearnSpell` |
+| POST | `/api/characters/:id/learn-spell` | `LearnSpell` |
+| POST | `/api/characters/available-skills-new` | `GetAvailableSkillsNewSystem` |
+| POST | `/api/characters/available-skills` | `GetAvailableSkillsNewSystem` |
+| POST | `/api/characters/available-skills-creation` | `GetAvailableSkillsForCreation` |
+| POST | `/api/characters/available-spells-creation` | `GetAvailableSpellsForCreation` |
+| POST | `/api/characters/available-spells-new` | `GetAvailableSpellsNewSystem` |
+| POST | `/api/characters/available-spells` | `GetAvailableSpellsNewSystem` |
+| GET | `/api/characters/spell-details` | `GetSpellDetails` |
+| GET | `/api/characters/:id/reward-types` | `GetRewardTypesStatic` |
+| GET | `/api/characters/:id/practice-points` | `GetPracticePoints` |
+| PUT | `/api/characters/:id/practice-points` | `UpdatePracticePoints` |
+| POST | `/api/characters/:id/practice-points/add` | `AddPracticePoint` |
+| POST | `/api/characters/:id/practice-points/use` | `UsePracticePoint` |
+| GET | `/api/characters/skill-categories` | `GetSkillCategoriesHandlerStatic` |
+| GET | `/api/characters/create-sessions` | `ListCharacterSessions` |
+| POST | `/api/characters/create-session` | `CreateCharacterSession` |
+| GET | `/api/characters/create-session/:sessionId` | `GetCharacterSession` |
+| PUT | `/api/characters/create-session/:sessionId/basic` | `UpdateCharacterBasicInfo` |
+| PUT | `/api/characters/create-session/:sessionId/attributes` | `UpdateCharacterAttributes` |
+| PUT | `/api/characters/create-session/:sessionId/derived` | `UpdateCharacterDerivedValues` |
+| PUT | `/api/characters/create-session/:sessionId/skills` | `UpdateCharacterSkills` |
+| POST | `/api/characters/create-session/:sessionId/finalize` | `FinalizeCharacterCreation` |
+| DELETE | `/api/characters/create-session/:sessionId` | `DeleteCharacterSession` |
+| GET | `/api/characters/races` | `GetRaces` |
+| GET | `/api/characters/classes` | `GetCharacterClasses` |
+| GET | `/api/characters/classes/learning-points` | `GetCharacterClassLearningPoints` |
+| GET | `/api/characters/origins` | `GetOrigins` |
+| GET | `/api/characters/beliefs` | `SearchBeliefs` |
+| POST | `/api/characters/calculate-static-fields` | `CalculateStaticFields` |
+| POST | `/api/characters/calculate-rolled-field` | `CalculateRolledField` |
+
+---
+
+### 8.3 `equipment/`
+
+**Purpose:** CRUD for character-owned equipment items and weapons.
+
+**Key Files:** `handlers.go`, `routes.go`
+
+**Business Logic:**
+- `checkEquipmentOwnership`: Loads character by ID, compares `character.UserID` to logged-in user.
+- All operations (CRUD) filter/verify ownership before executing DB writes.
+
+**HTTP Endpoints:**
+
+| Method | Path | Handler |
+|--------|------|---------|
+| POST | `/api/equipment` | `CreateAusruestung` |
+| GET | `/api/equipment/character/:character_id` | `ListAusruestung` |
+| PUT | `/api/equipment/:ausruestung_id` | `UpdateAusruestung` |
+| DELETE | `/api/equipment/:ausruestung_id` | `DeleteAusruestung` |
+| POST | `/api/weapons` | `CreateWaffe` |
+| GET | `/api/weapons/character/:character_id` | `ListWaffen` |
+| PUT | `/api/weapons/:waffe_id` | `UpdateWaffe` |
+| DELETE | `/api/weapons/:waffe_id` | `DeleteWaffe` |
+
+---
+
+### 8.4 `gsmaster/`
+
+**Purpose:** Game-system master data management — the reference library for skills, weapon skills, spells, equipment, weapons, and learning costs.
+
+**Key Files:**
+- `handlers.go` — Generic get/add/update/delete using Go generics (`getMDItem[T]`, `getMDItems[T]`, etc.)
+- `routes.go` — Public read endpoints + maintainer-guarded write endpoints
+- `learning_costs.go` — `LearningCostsTable` with full EP/TE cost tables for all 15 character classes
+- `learning_costs_init.go` — `ValidateLearningCostsData`, `GetLearningCostsSummary`
+- `levelup.go` — `CalculateImprovementCost`, `CalculateSkillImprovementCost`
+- `game_system.go` — Game system lookup helper
+- `misclookup.go` — `GetMiscLookupByKey`, `GetMiscLookupByKeyForSystem` (races, faiths, origins, etc.)
+- `skill_enhanced_handlers.go`, `spell_enhanced_handlers.go`, `weapon_enhanced_handlers.go`, `weapon_skill_enhanced_handlers.go`, `equipment_enhanced_handlers.go` — Enhanced CRUD with source/difficulty enrichment
+
+**Business Logic:**
+- Learning costs are stored both as hardcoded Go maps (in `learning_costs.go`, 15 classes × 8-9 categories) and in the database (`ClassCategoryEPCost` tables).
+- `GetMiscLookupByKey("faiths")` merges entries from both `gsm_misc_lookups` and `gsm_believes` tables.
+- Generic handlers use interface constraints (`Creator`, `Saver`, `FirstIdGetter`, `Deleter`) via Go generics for DRY CRUD.
+
+**HTTP Endpoints (`/api/maintenance/...`):**
+
+| Method | Path | Handler | Auth Level |
+|--------|------|---------|-----------|
+| GET | `/api/maintenance` | `GetMasterData` | Auth |
+| GET | `/api/maintenance/skills` | `GetMDSkills` | Auth |
+| GET | `/api/maintenance/skills-enhanced` | `GetEnhancedMDSkills` | Auth |
+| GET | `/api/maintenance/skills/:id` | `GetMDSkill` | Auth |
+| GET | `/api/maintenance/skills-enhanced/:id` | `GetEnhancedMDSkill` | Auth |
+| GET | `/api/maintenance/weaponskills` | `GetMDWeaponSkills` | Auth |
+| GET | `/api/maintenance/weaponskills-enhanced` | `GetEnhancedMDWeaponSkills` | Auth |
+| GET | `/api/maintenance/weaponskills/:id` | `GetMDWeaponSkill` | Auth |
+| GET | `/api/maintenance/weaponskills-enhanced/:id` | `GetEnhancedMDWeaponSkill` | Auth |
+| GET | `/api/maintenance/spells` | `GetMDSpells` | Auth |
+| GET | `/api/maintenance/spells-enhanced` | `GetEnhancedMDSpells` | Auth |
+| GET | `/api/maintenance/spells/:id` | `GetMDSpell` | Auth |
+| GET | `/api/maintenance/spells-enhanced/:id` | `GetEnhancedMDSpell` | Auth |
+| GET | `/api/maintenance/equipment` | `GetMDEquipments` | Auth |
+| GET | `/api/maintenance/equipment-enhanced` | `GetEnhancedMDEquipment` | Auth |
+| GET | `/api/maintenance/equipment/:id` | `GetMDEquipment` | Auth |
+| GET | `/api/maintenance/equipment-enhanced/:id` | `GetEnhancedMDEquipmentItem` | Auth |
+| GET | `/api/maintenance/weapons` | `GetMDWeapons` | Auth |
+| GET | `/api/maintenance/weapons-enhanced` | `GetEnhancedMDWeapons` | Auth |
+| GET | `/api/maintenance/weapons/:id` | `GetMDWeapon` | Auth |
+| GET | `/api/maintenance/weapons-enhanced/:id` | `GetEnhancedMDWeapon` | Auth |
+| POST | `/api/maintenance/skills-enhanced` | `CreateEnhancedMDSkill` | Maintainer |
+| PUT | `/api/maintenance/skills/:id` | `UpdateMDSkill` | Maintainer |
+| PUT | `/api/maintenance/skills-enhanced/:id` | `UpdateEnhancedMDSkill` | Maintainer |
+| POST | `/api/maintenance/skills` | `AddSkill` | Maintainer |
+| DELETE | `/api/maintenance/skills/:id` | `DeleteMDSkill` | Maintainer |
+| PUT | `/api/maintenance/weaponskills/:id` | `UpdateMDWeaponSkill` | Maintainer |
+| PUT | `/api/maintenance/weaponskills-enhanced/:id` | `UpdateEnhancedMDWeaponSkill` | Maintainer |
+| POST | `/api/maintenance/weaponskills` | `AddWeaponSkill` | Maintainer |
+| DELETE | `/api/maintenance/weaponskills/:id` | `DeleteMDWeaponSkill` | Maintainer |
+| PUT | `/api/maintenance/spells/:id` | `UpdateMDSpell` | Maintainer |
+| PUT | `/api/maintenance/spells-enhanced/:id` | `UpdateEnhancedMDSpell` | Maintainer |
+| POST | `/api/maintenance/spells` | `AddSpell` | Maintainer |
+| DELETE | `/api/maintenance/spells/:id` | `DeleteMDSpell` | Maintainer |
+| PUT | `/api/maintenance/equipment/:id` | `UpdateMDEquipment` | Maintainer |
+| PUT | `/api/maintenance/equipment-enhanced/:id` | `UpdateEnhancedMDEquipmentItem` | Maintainer |
+| POST | `/api/maintenance/equipment` | `AddEquipment` | Maintainer |
+| DELETE | `/api/maintenance/equipment/:id` | `DeleteMDEquipment` | Maintainer |
+| PUT | `/api/maintenance/weapons/:id` | `UpdateMDWeapon` | Maintainer |
+| PUT | `/api/maintenance/weapons-enhanced/:id` | `UpdateEnhancedMDWeapon` | Maintainer |
+| POST | `/api/maintenance/weapons` | `AddWeapon` | Maintainer |
+| DELETE | `/api/maintenance/weapons/:id` | `DeleteMDWeapon` | Maintainer |
+
+---
+
+### 8.5 `gamesystem/`
+
+**Purpose:** Minimal package that handles migration of the `game_systems` table and seeds the initial Midgard 5 game system.
+
+**Key File:** `database.go`
+
+- `MigrateStructure(db)` — AutoMigrates `GameSystem`.
+- `MigrateDataIfNeeded(db)` — Creates `{Code:"M5", Name:"M-System"}` if no row with ID=1 exists.
+
+No HTTP endpoints registered.
+
+---
+
+### 8.6 `maintenance/`
+
+**Purpose:** Admin/maintainer tooling — DB health checks, data migrations, test data generation, SQLite→MariaDB transfer.
+
+**Key Files:**
+- `handlers.go` — SetupCheck, MakeTestdataFromLive, ReconnectDataBase, ReloadENV, TransferSQLiteToMariaDB, migrateAllStructures
+- `masterdata_handlers.go` — GetGameSystems, UpdateGameSystem, GetLitSources, UpdateLitSource, GetMisc, UpdateMisc, GetSkillImprovementCost2, UpdateSkillImprovementCost2
+- `believe_handlers.go` — GetBelieves, UpdateBelieve
+- `skill_migration.go` — Skill-related migration helpers
+
+**Business Logic:**
+- `migrateAllStructures` calls all module `MigrateStructure()` functions in correct dependency order.
+- `MakeTestdataFromLive` exports current live DB to a SQLite file for test snapshots.
+- `TransferSQLiteToMariaDB` copies records table-by-table from SQLite to MariaDB.
+- All endpoints require `RequireMaintainer()` or `RequireAdmin()` — no public access.
+
+**HTTP Endpoints (`/api/maintenance/...` — Maintainer required):**
+
+| Method | Path | Handler |
+|--------|------|---------|
+| GET | `/api/maintenance/gsm-believes` | `GetBelieves` |
+| PUT | `/api/maintenance/gsm-believes/:id` | `UpdateBelieve` |
+| GET | `/api/maintenance/game-systems` | `GetGameSystems` |
+| PUT | `/api/maintenance/game-systems/:id` | `UpdateGameSystem` |
+| GET | `/api/maintenance/gsm-lit-sources` | `GetLitSources` |
+| PUT | `/api/maintenance/gsm-lit-sources/:id` | `UpdateLitSource` |
+| GET | `/api/maintenance/gsm-misc` | `GetMisc` |
+| PUT | `/api/maintenance/gsm-misc/:id` | `UpdateMisc` |
+| GET | `/api/maintenance/skill-improvement-cost2` | `GetSkillImprovementCost2` |
+| PUT | `/api/maintenance/skill-improvement-cost2/:id` | `UpdateSkillImprovementCost2` |
+| GET | `/api/maintenance/setupcheck` | `SetupCheck` |
+| GET | `/api/maintenance/setupcheck-dev` | `SetupCheckDev` |
+| GET | `/api/maintenance/mktestdata` | `MakeTestdataFromLive` |
+| GET | `/api/maintenance/reconndb` | `ReconnectDataBase` |
+| GET | `/api/maintenance/reloadenv` | `ReloadENV` |
+| POST | `/api/maintenance/transfer-sqlite-to-mariadb` | `TransferSQLiteToMariaDB` |
+
+*(These overlap with the `gsmaster` maintenance prefix — both modules register under `/api/maintenance` with `gsmaster` first, then `maintenance` module adds its own routes. Both use `RequireMaintainer()` for writes.)*
+
+---
+
+### 8.7 `pdfrender/`
+
+**Purpose:** Generate multi-page PDF character sheets from HTML templates using headless Chromium + pdfcpu merge.
+
+**Key Files:**
+- `handlers.go` — `ListTemplates`, `ExportCharacterToPDF`, `CleanupExportTemp`, `GetPDFFile`
+- `init.go` — `InitializeTemplates` — copies bundled templates to `TemplatesDir` on startup
+- `chromedp.go` — `NewPDFRenderer`, `RenderHTMLToPDF` using chromedp
+- `mapper.go` — `MapCharacterToViewModel` — maps `Char` to `CharacterSheetViewModel`
+- `viewmodel.go` — All view model types (`CharacterSheetViewModel`, `CharacterInfo`, `AttributeValues`, `DerivedValueSet`, `SkillViewModel`, `WeaponViewModel`, `SpellViewModel`, `EquipmentViewModel`, `MagicItemViewModel`, `PageMeta`)
+- `render_with_continuation.go` — `RenderPageWithContinuations` — auto-generates overflow pages
+- `pagination.go` / `pagination_helper.go` — item distribution across pages
+- `fill_capacity.go` — reads `` comments from HTML templates
+- `template_parser.go` / `template_metadata.go` — template loading and Go `html/template` execution
+- `inline_resources.go` — inlines CSS/images into HTML before rendering
+- `file_management.go` — manages `xporttemp` directory
+
+**Business Logic:**
+1. On startup: sync `./default_templates` → `cfg.TemplatesDir`.
+2. `ExportCharacterToPDF`:
+ - Loads character, maps to view model.
+ - Renders pages 1–4 (`page_1.html` through `page_4.html`) with continuation logic.
+ - Each page checks `` to determine when to create a continuation page.
+ - All rendered PDFs are merged via `pdfcpu`.
+ - Saves to `xporttemp/` directory, returns `{"filename": "..."}`.
+3. `GetPDFFile`: public endpoint, serves files from `xporttemp/` by filename.
+
+**HTTP Endpoints:**
+
+| Method | Path | Handler | Auth |
+|--------|------|---------|------|
+| GET | `/api/pdf/templates` | `ListTemplates` | Auth |
+| GET | `/api/pdf/export/:id` | `ExportCharacterToPDF` | Auth |
+| POST | `/api/pdf/cleanup` | `CleanupExportTemp` | Auth |
+| GET | `/api/pdf/file/:filename` | `GetPDFFile` | Public |
+
+---
+
+### 8.8 `importer/`
+
+**Purpose:** Import characters from VTT JSON format or CSV; export characters as VTT JSON, CSV, or spell CSV.
+
+**Key Files:**
+- `handler.go` — `UploadFiles`, `ImportSpellCSVHandler`
+- `importer.go` — `ImportChar`, `CheckSkill`, VTT import logic
+- `importVTTJson.go` — `ImportVTTJSON` — main VTT import driver
+- `exporter.go` — `ExportCharToVTT`, `ExportCharacterToCSV`
+- `model.go` — `CharacterImport` struct (VTT format), sub-structs for Fertigkeiten/Spells/Equipment
+- `routes.go` — Route registration
+
+**Business Logic:**
+- `UploadFiles`: Accepts `file_vtt` (required) + `file_csv` (optional), validates extensions (.csv/.json), calls `ImportVTTJSON`.
+- `CheckSkill`: Looks up skill in `gsm_skills`, auto-creates if `autocreate=true`.
+- Source IDs are cached (`sourceIDCache`) to reduce DB queries during bulk import.
+- `ExportCharacterVTTFileHandler`: Returns character data as downloadable `.json` file.
+
+**HTTP Endpoints:**
+
+| Method | Path | Handler |
+|--------|------|---------|
+| POST | `/api/importer/upload` | `UploadFiles` |
+| POST | `/api/importer/spells/csv` | `ImportSpellCSVHandler` |
+| GET | `/api/importer/export/vtt/:id` | `ExportCharacterVTTHandler` |
+| GET | `/api/importer/export/vtt/:id/file` | `ExportCharacterVTTFileHandler` |
+| GET | `/api/importer/export/csv/:id` | `ExportCharacterCSVHandler` |
+| GET | `/api/importer/export/spells/csv` | `ExportSpellsCSVHandler` |
+
+---
+
+### 8.9 `transfer/`
+
+**Purpose:** JSON-serialized character import/export and full database backup/restore.
+
+**Key Files:**
+- `handlers.go` — HTTP handlers for all transfer operations
+- `exporter.go` — `ExportCharacter` — serializes a full `Char` + metadata to `CharacterExport`
+- `importer.go` — `ImportCharacter` — deserializes and recreates character in DB
+- `database.go` — `ExportDatabase`, `ImportDatabase` — full table serialization
+- `routes.go` — Route registration
+
+**Business Logic:**
+- `ExportCharacter` / `ImportCharacter`: Full round-trip JSON serialization of characters with all associations.
+- `ExportDatabase` / `ImportDatabase`: Table-level JSON snapshot used for backup/restore.
+- `DownloadCharacterHandler` sets `Content-Disposition: attachment` for browser download.
+
+**HTTP Endpoints:**
+
+| Method | Path | Handler |
+|--------|------|---------|
+| GET | `/api/transfer/export/:id` | `ExportCharacterHandler` |
+| GET | `/api/transfer/download/:id` | `DownloadCharacterHandler` |
+| POST | `/api/transfer/import` | `ImportCharacterHandler` |
+| POST | `/api/transfer/database/export` | `ExportDatabaseHandler` |
+| POST | `/api/transfer/database/import` | `ImportDatabaseHandler` |
+
+---
+
+### 8.10 `appsystem/`
+
+**Purpose:** Exposes application version and system info (user/character counts, DB version).
+
+**Key Files:**
+- `version.go` — `Version = "0.2.4"`, `GetInfo()`, `GetInfo2()`
+- `handlers.go` — `Versionsinfo`, `SystemInfo`
+- `routes.go` — Route registration (both protected and public)
+
+**HTTP Endpoints:**
+
+| Method | Path | Handler | Auth |
+|--------|------|---------|------|
+| GET | `/api/version` | `Versionsinfo` | Auth |
+| GET | `/api/systeminfo` | `SystemInfo` | Auth |
+| GET | `/api/public/version` | `Versionsinfo` | Public |
+| GET | `/api/public/systeminfo` | `SystemInfo` | Public |
+
+---
+
+### 8.11 `logger/`
+
+**Purpose:** Custom leveled logger with timestamp prefixing.
+
+**Key API:**
+
+```go
+logger.Debug(format, args...)
+logger.Info(format, args...)
+logger.Warn(format, args...)
+logger.Error(format, args...)
+logger.SetDebugMode(bool)
+logger.SetMinLogLevel(logger.DEBUG | INFO | WARN | ERROR)
+```
+
+- Outputs to `os.Stdout` with format: `[YYYY-MM-DD HH:MM:SS] LEVEL: message`
+- Debug messages are suppressed unless both `debugEnabled=true` AND `minLevel<=DEBUG`.
+- Configured once in `main()` from `config.Cfg`.
+
+---
+
+### 8.12 `mail/`
+
+**Purpose:** SMTP email client used exclusively for password-reset emails.
+
+**Key File:** `smtp.go`
+
+```go
+type Client struct {
+ host, username, password, from string
+ port int
+}
+
+func NewClient() *Client // reads from config.Cfg
+func (c *Client) Send(msg Message) error
+```
+
+- Port 465: Direct TLS (`tls.Dial`)
+- Port 587: STARTTLS (`smtp.Dial` + `TLSConfig`)
+- If `host == ""`: logs the email content instead of sending (fallback for unconfigured environments).
+
+---
+
+### 8.13 `router/`
+
+**Purpose:** Central Gin setup and base route group creation.
+
+**Files:**
+- `setup.go` — `SetupGin(r)` — CORS middleware
+- `routes.go` — `BaseRouterGrp(r)` — public auth routes + protected `/api` group
+- `routes_test.go` — router tests
+
+---
+
+### 8.14 `config/`
+
+**Purpose:** Application configuration loading and validation.
+
+**Key Functions:**
+- `init()` — auto-loads config on package import
+- `LoadConfig()` — reads `.env`/`.env.local`, then env vars
+- `loadEnvFile()` / `loadEnvFileContent()` — parse `KEY=VALUE` lines
+- `cfg.IsProduction() bool`
+- `cfg.GetServerAddress() string` — returns `":PORT"` format
+
+---
+
+### 8.15 `database/`
+
+**Purpose:** Database connection management, test helpers, and migration execution.
+
+**Key Functions:**
+- `ConnectDatabase()` — routes to `SetupTestDB()` or `ConnectDatabaseOrig()` based on env
+- `ConnectDatabaseOrig()` — opens MySQL or SQLite with GORM
+- `SetupTestDB(opts ...bool)` — copies snapshot `testdata/prepared_test_data.db` → temp dir, opens it
+- `MigrateStructure(db)` — migrates `schema_version` + `migration_history`
+- `MigrateDataIfNeeded(db)` — ensures schema version record exists
+- `GetDB()` — lazy init getter
+
+**Path Constants:**
+```go
+PreparedTestDB = filepath.Join(backendDir, "testdata", "prepared_test_data.db")
+TestDataDir = filepath.Join(backendDir, "maintenance", "testdata")
+```
+
+---
+
+### 8.16 `testutils/`
+
+**Purpose:** Shared test environment setup utility.
+
+```go
+func SetupTestEnvironment(t *testing.T)
+func SetupTestEnvironmentWithConfig(t *testing.T, envVars map[string]string)
+func EnsureTestEnvironment(t *testing.T)
+```
+
+- Sets `ENVIRONMENT=test` to redirect DB connection to SQLite test snapshot.
+- Registers `t.Cleanup()` to restore original env vars after each test.
+
+---
+
+### 8.17 `api/`
+
+**Purpose:** Integration tests that test multi-module interactions via full HTTP stack.
+
+Key test: `TestListCharacters` — spins up Gin with all essential routes, uses `database.SetupTestDB(true)`.
+
+---
+
+## 9. Complete API Route Listing
+
+### Public (No Authentication)
+
+| Method | Path | Module | Purpose |
+|--------|------|--------|---------|
+| POST | `/register` | user | Register a new user |
+| POST | `/login` | user | Login, get token |
+| POST | `/password-reset/request` | user | Request password reset email |
+| GET | `/password-reset/validate/:token` | user | Validate reset token |
+| POST | `/password-reset/reset` | user | Set new password |
+| GET | `/api/pdf/file/:filename` | pdfrender | Download generated PDF |
+| GET | `/api/public/version` | appsystem | App version (no auth) |
+| GET | `/api/public/systeminfo` | appsystem | System info (no auth) |
+
+### Protected — All Authenticated Users (`/api/...`)
+
+**User Management:**
+
+| Method | Path | Purpose |
+|--------|------|---------|
+| GET | `/api/user/profile` | Own profile |
+| PUT | `/api/user/display-name` | Update display name |
+| PUT | `/api/user/email` | Update email |
+| PUT | `/api/user/password` | Change password |
+| PUT | `/api/user/language` | Set preferred language |
+
+**Characters:**
+
+| Method | Path | Purpose |
+|--------|------|---------|
+| GET | `/api/characters` | List own + shared + public characters |
+| POST | `/api/characters` | Create character |
+| GET | `/api/characters/:id` | Get full character (FeChar) |
+| PUT/PATCH | `/api/characters/:id` | Update character |
+| DELETE | `/api/characters/:id` | Delete character |
+| PUT | `/api/characters/:id/image` | Upload character image |
+| GET | `/api/characters/:id/datasheet-options` | PDF datasheet options |
+| GET | `/api/characters/:id/shares` | List share recipients |
+| PUT | `/api/characters/:id/shares` | Update share recipients |
+| GET | `/api/characters/:id/available-users` | Users available to share with |
+| GET | `/api/characters/:id/experience-wealth` | Current EP + wealth |
+| PUT | `/api/characters/:id/experience` | Update experience points |
+| PUT | `/api/characters/:id/wealth` | Update wealth |
+| GET | `/api/characters/:id/audit-log` | Change history |
+| GET | `/api/characters/:id/audit-log/stats` | Change statistics |
+| POST | `/api/characters/lerncost` | Calculate learn/improve cost |
+| POST | `/api/characters/lerncost-new` | (Alias) Calculate learn/improve cost |
+| POST | `/api/characters/improve-skill` | Apply skill improvement |
+| POST | `/api/characters/improve-skill-new` | (Alias) Apply skill improvement |
+| POST | `/api/characters/:id/learn-skill` | Learn new skill |
+| POST | `/api/characters/:id/learn-skill-new` | (Alias) Learn new skill |
+| POST | `/api/characters/:id/learn-spell` | Learn new spell |
+| POST | `/api/characters/:id/learn-spell-new` | (Alias) Learn new spell |
+| POST | `/api/characters/available-skills` | Available skills to learn |
+| POST | `/api/characters/available-skills-new` | (Alias) |
+| POST | `/api/characters/available-skills-creation` | Skills for creation wizard |
+| POST | `/api/characters/available-spells` | Available spells to learn |
+| POST | `/api/characters/available-spells-new` | (Alias) |
+| POST | `/api/characters/available-spells-creation` | Spells for creation wizard |
+| GET | `/api/characters/spell-details` | Spell detail lookup |
+| GET | `/api/characters/:id/reward-types` | Reward options for this context |
+| GET | `/api/characters/:id/practice-points` | Current practice points |
+| PUT | `/api/characters/:id/practice-points` | Update practice points |
+| POST | `/api/characters/:id/practice-points/add` | Add a practice point |
+| POST | `/api/characters/:id/practice-points/use` | Use a practice point |
+| GET | `/api/characters/skill-categories` | Static skill category listing |
+| GET | `/api/characters/create-sessions` | List creation wizard sessions |
+| POST | `/api/characters/create-session` | Start creation wizard session |
+| GET | `/api/characters/create-session/:sessionId` | Get session data |
+| PUT | `/api/characters/create-session/:sessionId/basic` | Save basic info |
+| PUT | `/api/characters/create-session/:sessionId/attributes` | Save attributes |
+| PUT | `/api/characters/create-session/:sessionId/derived` | Save derived values |
+| PUT | `/api/characters/create-session/:sessionId/skills` | Save skills selection |
+| POST | `/api/characters/create-session/:sessionId/finalize` | Finalize creation |
+| DELETE | `/api/characters/create-session/:sessionId` | Discard session |
+| GET | `/api/characters/races` | Available races |
+| GET | `/api/characters/classes` | Available character classes |
+| GET | `/api/characters/classes/learning-points` | LP by class |
+| GET | `/api/characters/origins` | Available origins |
+| GET | `/api/characters/beliefs` | Faith search |
+| POST | `/api/characters/calculate-static-fields` | Calc derived values (no dice) |
+| POST | `/api/characters/calculate-rolled-field` | Calc derived value (with dice) |
+
+**Equipment:**
+
+| Method | Path | Purpose |
+|--------|------|---------|
+| POST | `/api/equipment` | Add equipment item |
+| GET | `/api/equipment/character/:character_id` | List equipment for character |
+| PUT | `/api/equipment/:ausruestung_id` | Update equipment |
+| DELETE | `/api/equipment/:ausruestung_id` | Delete equipment |
+| POST | `/api/weapons` | Add weapon |
+| GET | `/api/weapons/character/:character_id` | List weapons for character |
+| PUT | `/api/weapons/:waffe_id` | Update weapon |
+| DELETE | `/api/weapons/:waffe_id` | Delete weapon |
+
+**Master Data (read — all authenticated, write — maintainers only):**
+
+| Method | Path | Auth | Purpose |
+|--------|------|------|---------|
+| GET | `/api/maintenance` | Auth | All master data overview |
+| GET | `/api/maintenance/skills` | Auth | List skills |
+| GET | `/api/maintenance/skills/:id` | Auth | Get skill |
+| GET | `/api/maintenance/skills-enhanced` | Auth | List skills (rich) |
+| GET | `/api/maintenance/skills-enhanced/:id` | Auth | Get skill (rich) |
+| POST | `/api/maintenance/skills` | Maintainer | Create skill |
+| POST | `/api/maintenance/skills-enhanced` | Maintainer | Create skill (rich) |
+| PUT | `/api/maintenance/skills/:id` | Maintainer | Update skill |
+| PUT | `/api/maintenance/skills-enhanced/:id` | Maintainer | Update skill (rich) |
+| DELETE | `/api/maintenance/skills/:id` | Maintainer | Delete skill |
+| GET | `/api/maintenance/weaponskills[/:id]` | Auth | Weapon skill(s) |
+| GET | `/api/maintenance/weaponskills-enhanced[/:id]` | Auth | Weapon skill(s) (rich) |
+| POST/PUT/DELETE | `/api/maintenance/weaponskills[/:id]` | Maintainer | Weapon skill CRUD |
+| GET | `/api/maintenance/spells[/:id]` | Auth | Spell(s) |
+| GET | `/api/maintenance/spells-enhanced[/:id]` | Auth | Spell(s) (rich) |
+| POST/PUT/DELETE | `/api/maintenance/spells[/:id]` | Maintainer | Spell CRUD |
+| GET | `/api/maintenance/equipment[/:id]` | Auth | Equipment item(s) |
+| GET | `/api/maintenance/equipment-enhanced[/:id]` | Auth | Equipment (rich) |
+| POST/PUT/DELETE | `/api/maintenance/equipment[/:id]` | Maintainer | Equipment CRUD |
+| GET | `/api/maintenance/weapons[/:id]` | Auth | Master weapon(s) |
+| GET | `/api/maintenance/weapons-enhanced[/:id]` | Auth | Master weapon(s) (rich) |
+| POST/PUT/DELETE | `/api/maintenance/weapons[/:id]` | Maintainer | Weapon CRUD |
+
+**Maintenance Admin (Maintainer required):**
+
+| Method | Path | Purpose |
+|--------|------|---------|
+| GET | `/api/maintenance/gsm-believes` | List faith entries |
+| PUT | `/api/maintenance/gsm-believes/:id` | Update faith entry |
+| GET | `/api/maintenance/game-systems` | List game systems |
+| PUT | `/api/maintenance/game-systems/:id` | Update game system |
+| GET | `/api/maintenance/gsm-lit-sources` | List source books |
+| PUT | `/api/maintenance/gsm-lit-sources/:id` | Update source book |
+| GET | `/api/maintenance/gsm-misc` | MiscLookup entries |
+| PUT | `/api/maintenance/gsm-misc/:id` | Update misc entry |
+| GET/PUT | `/api/maintenance/skill-improvement-cost2[/:id]` | Improvement cost table |
+| GET | `/api/maintenance/setupcheck` | DB structure validation |
+| GET | `/api/maintenance/setupcheck-dev` | Extended setup check |
+| GET | `/api/maintenance/mktestdata` | Generate test snapshot from live DB |
+| GET | `/api/maintenance/reconndb` | Reconnect database |
+| GET | `/api/maintenance/reloadenv` | Reload .env config |
+| POST | `/api/maintenance/transfer-sqlite-to-mariadb` | Migrate data |
+
+**PDF:**
+
+| Method | Path | Auth | Purpose |
+|--------|------|------|---------|
+| GET | `/api/pdf/templates` | Auth | List available PDF templates |
+| GET | `/api/pdf/export/:id` | Auth | Export character to PDF |
+| POST | `/api/pdf/cleanup` | Auth | Clean up old PDFs |
+| GET | `/api/pdf/file/:filename` | Public | Download PDF file |
+
+**Import/Export:**
+
+| Method | Path | Purpose |
+|--------|------|---------|
+| POST | `/api/importer/upload` | Import character from VTT JSON |
+| POST | `/api/importer/spells/csv` | Import spells from CSV |
+| GET | `/api/importer/export/vtt/:id` | Export character as VTT JSON (response body) |
+| GET | `/api/importer/export/vtt/:id/file` | Export character as downloadable VTT JSON |
+| GET | `/api/importer/export/csv/:id` | Export character as CSV |
+| GET | `/api/importer/export/spells/csv` | Export all spells as CSV |
+| GET | `/api/transfer/export/:id` | Export character as BaMoRT JSON |
+| GET | `/api/transfer/download/:id` | Download character as JSON file |
+| POST | `/api/transfer/import` | Import character from BaMoRT JSON |
+| POST | `/api/transfer/database/export` | Full DB export to JSON |
+| POST | `/api/transfer/database/import` | Full DB import from JSON |
+
+**System:**
+
+| Method | Path | Auth | Purpose |
+|--------|------|------|---------|
+| GET | `/api/version` | Auth | App version + git commit |
+| GET | `/api/systeminfo` | Auth | Version + user/char counts + DB version |
+
+**Admin (Admin role required):**
+
+| Method | Path | Purpose |
+|--------|------|---------|
+| GET | `/api/users` | List all users |
+| GET | `/api/users/:id` | Get user by ID |
+| PUT | `/api/users/:id/role` | Change user role |
+| PUT | `/api/users/:id/password` | Change user password |
+| DELETE | `/api/users/:id` | Delete user |
+
+---
+
+## 10. Testing Approach
+
+### Test Environment Setup
+
+Every test file that touches the database must call:
+
+```go
+func setupTestEnvironment(t *testing.T) {
+ original := os.Getenv("ENVIRONMENT")
+ os.Setenv("ENVIRONMENT", "test")
+ t.Cleanup(func() { os.Setenv("ENVIRONMENT", original) })
+}
+```
+
+Or the shared utility:
+```go
+testutils.SetupTestEnvironment(t)
+```
+
+This causes `database.ConnectDatabase()` to open the SQLite test snapshot instead of MariaDB.
+
+### Test Database Mechanism
+
+1. `testdata/prepared_test_data.db` — a SQLite snapshot with real-world test data.
+2. `database.SetupTestDB(true)` copies this to a `os.MkdirTemp` location and opens it.
+3. Tests run against this isolated copy — no pollution of live data.
+4. Character ID 18 ("Fanjo Vetrani") is guaranteed to exist in the test DB.
+
+### Test Structure Conventions
+
+- All test files use `_test.go` suffix.
+- Test functions call `database.SetupTestDB(true)` or `testutils.SetupTestEnvironment(t)` first.
+- Integration tests use `testify/assert`.
+- Many module tests (`character_test.go`, `handlers_test.go`, etc.) use helper constructors from `test_data_helper.go`.
+- The `api/api_test.go` package runs HTTP tests using `httptest.NewRecorder()`.
+- `maintenance/copy_db_test.go` tests the DB snapshot refresh mechanism.
+- Test data helpers (`character/test_data_helper.go`) provide `CreateTestCharacter()`, etc.
+
+### Key Test Files by Module
+
+| Module | Test Files |
+|--------|-----------|
+| `api/` | `api_test.go` — Integration |
+| `character/` | `character_test.go`, `handlers_test.go`, `lerncost_handler_test.go`, `skill_update_test.go`, `learn_spell_test.go`, etc. |
+| `database/` | `database_test.go`, `testhelper_test.go` |
+| `gsmaster/` | `gsmaster_test.go`, `learning_costs_test.go`, `export_import_test.go` |
+| `importer/` | `importer_test.go`, `charimport_test.go`, `upload_test.go` |
+| `maintenance/` | `handlers_test.go`, `masterdata_handlers_test.go`, `copy_db_test.go` |
+| `models/` | `model_character_test.go`, `model_char_skills_test.go`, `model_learning_costs_test.go` |
+| `pdfrender/` | `chromedp_test.go`, `mapper_test.go`, `pagination_test.go`, `integration_test.go` |
+| `transfer/` | `database_test.go`, `exporter_test.go`, `importer_test.go` |
+| `user/` | `handlers_test.go`, `role_test.go` |
+
+---
+
+## 11. Key Dependencies
+
+| Dependency | Version | Purpose |
+|-----------|---------|---------|
+| `github.com/gin-gonic/gin` | v1.10.0 | HTTP framework |
+| `github.com/gin-contrib/cors` | v1.7.3 | CORS middleware |
+| `gorm.io/gorm` | v1.25.12 | ORM |
+| `gorm.io/driver/mysql` | v1.5.7 | MySQL/MariaDB driver |
+| `gorm.io/driver/sqlite` | v1.5.7 | SQLite driver (tests + dev) |
+| `github.com/chromedp/chromedp` | v0.14.2 | Headless Chrome for PDF rendering |
+| `github.com/pdfcpu/pdfcpu` | v0.11.1 | PDF merging |
+| `github.com/stretchr/testify` | v1.10.0 | Test assertions |
+
+---
+
+## 12. Security Observations
+
+### Critical Issues
+
+1. **Weak Token Authentication**: The custom token embeds the user ID in a predictable location. Any token with any valid user ID at position `7 + len("Bearer ")` is accepted. There is **no HMAC or digital signature**. An attacker can forge tokens.
+
+2. **MD5 Password Hashing**: Passwords are stored as MD5 hashes. MD5 is cryptographically broken for password storage; brute-force and rainbow-table attacks are trivial. Should be replaced with bcrypt, argon2id, or scrypt.
+
+3. **Token Leaks User ID**: The token format encodes the user ID in plaintext, revealing enumerable user IDs to clients.
+
+### Moderate Concerns
+
+4. **Debug Logging of Passwords**: `handlers.go` has `fmt.Printf("pwdh: %s", ...)` that prints password hashes to stdout on every login — a security logging issue.
+
+5. **Hardcoded Origins in CORS**: Local network IPs (`192.168.0.48`, `192.168.0.36`) are hardcoded — appropriate for development but should be env-driven for production.
+
+6. **Importer File Upload**: `UploadFiles` saves files to `./uploads/` relative path without sanitizing filenames for path traversal. The extension validation (`isValidFileType`) only checks the suffix.
+
+7. **SQL Injection via `fieldName`**: `GetMiscLookupByKeyForSystem` constructs `query.Order(orderBy)` from a user-supplied `order` parameter — however the value is mapped to a safe set of constants before use, so actual injection risk is mitigated.
+
+8. **No rate limiting**: Login and registration endpoints have no rate limiting, making them susceptible to brute-force attacks.
+
+### Notes
+
+- The bcrypt implementation is commented out in `handlers.go` with the MD5 hash in use — this is intentional but documents the known deficit.
+- Password reset tokens use `crypto/rand` (secure, 32 bytes), which is correct.
+- GORM parameterized queries protect most DB operations from SQL injection.
+
+---
+
+## 13. Cross-Module Relationships Diagram
+
+```
+cmd/main.go
+ ├── config ──────────────────────────────────── read by all modules
+ ├── logger ──────────────────────────────────── used by all modules
+ ├── database ─────────────────────────────────── .DB used by all models
+ │ └── testutils ────────────────────────── test env setup
+ ├── router
+ │ └── user (AuthMiddleware, role guards)
+ ├── user ─────────────────── handlers, model, auth
+ │ └── mail (SMTP for password reset)
+ ├── models ────────────────── GORM entities, MigrateStructure
+ │ ├── user.User (FK in Char)
+ │ └── database.DB (queries)
+ ├── character ─────────────── CRUD + learning + creation wizard
+ │ ├── models (Char, SkFertigkeit, SkZauber, etc.)
+ │ ├── gsmaster (LerncostRequest, skill lookups)
+ │ └── database.DB
+ ├── equipment ─────────────── Equipment/Weapon CRUD
+ │ ├── models (EqAusruestung, EqWaffe)
+ │ └── database.DB
+ ├── gsmaster ──────────────── Master data (skills/spells/weapons)
+ │ ├── models (Skill, Spell, Weapon, etc.)
+ │ ├── user (RequireMaintainer guard)
+ │ └── database.DB
+ ├── gamesystem ────────────── GameSystem seed/migration
+ │ └── models.GameSystem
+ ├── maintenance ───────────── Admin tooling, migration orchestration
+ │ ├── database, gamesystem, user, models (all MigrateStructure)
+ │ └── user (RequireMaintainer guard)
+ ├── pdfrender ─────────────── HTML→PDF pipeline
+ │ ├── models.Char (character data)
+ │ └── config (TemplatesDir, ExportTempDir)
+ ├── importer ──────────────── VTT/CSV import/export
+ │ └── models (Char, Skill, Spell)
+ ├── transfer ──────────────── JSON character + DB backup
+ │ └── models.Char
+ └── appsystem ─────────────── Version info
+ └── database.DB (count queries)
+```
+
+---
+
+*End of findings — /home/de31a2/bamort/backend_findings.md*
diff --git a/frontend_findings.md b/frontend_findings.md
new file mode 100644
index 0000000..9f68629
--- /dev/null
+++ b/frontend_findings.md
@@ -0,0 +1,1563 @@
+# BaMoRT Frontend — Comprehensive Code Analysis
+
+_Generated: 2026-03-13_
+
+---
+
+## Table of Contents
+
+1. [Project Setup](#1-project-setup)
+2. [Application Entry](#2-application-entry)
+3. [Router](#3-router)
+4. [Pinia Stores](#4-pinia-stores)
+5. [Views (src/views/)](#5-views-srcviews)
+6. [Components (src/components/)](#6-components-srccomponents)
+7. [API Layer](#7-api-layer)
+8. [i18n](#8-i18n)
+9. [Styling](#9-styling)
+10. [Business Logic in Frontend](#10-business-logic-in-frontend)
+11. [User Flows](#11-user-flows)
+12. [Component Hierarchy](#12-component-hierarchy)
+
+---
+
+## 1. Project Setup
+
+### `package.json`
+
+```
+name: bamort-frontend
+version: 0.2.3
+type: module
+```
+
+**Runtime dependencies:**
+| Package | Version | Purpose |
+|---|---|---|
+| `vue` | ^3.5.13 | Core framework (Options API) |
+| `vue-router` | ^4.5.0 | Client-side routing |
+| `pinia` | ^2.3.0 | State management |
+| `vue-i18n` | ^11.0.0 | Internationalisation (DE + EN) |
+| `axios` | ^1.7.9 | HTTP client |
+
+**Dev dependencies:**
+| Package | Version | Purpose |
+|---|---|---|
+| `vite` | ^6.0.1 | Build tool / dev server |
+| `@vitejs/plugin-vue` | ^5.2.1 | Vite Vue 3 plugin |
+| `vite-plugin-vue-devtools` | ^7.6.5 | Browser devtools integration |
+
+No testing framework, no TypeScript compiler (`.ts` files exist only as type declaration stubs).
+
+### `vite.config.js`
+
+```js
+export default defineConfig({
+ plugins: [vue(), vueDevTools()],
+ resolve: {
+ alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) }
+ },
+ server: {
+ host: ['bamort.trokan.de', '192.168.0.48', 'localhost', 'terra.local'],
+ }
+})
+```
+
+Key notes:
+- `@` alias maps to `src/`
+- Dev server explicitly bound to multiple hostnames (network-accessible)
+- Minification and source maps are commented out (disabled)
+- No Vite proxy configuration — all API calls go directly to the configured `VITE_API_URL`
+
+### `index.html`
+
+Single-page app root. Mounts `#app` and loads `src/main.js` as an ES module. Uses `favicon.png`.
+
+### `src/version.js`
+
+```js
+export const VERSION = '0.2.3'
+export const GIT_COMMIT = import.meta.env.VITE_GIT_COMMIT || 'unknown'
+```
+
+Exposes `getVersion()` and `getGitCommit()` consumed by `LandingView` and `SystemInfoView`.
+
+---
+
+## 2. Application Entry
+
+**`src/main.js`**
+
+```js
+import './assets/main.css'
+import { createApp } from "vue"
+import { createPinia } from 'pinia'
+import App from "./App.vue"
+import router from "./router"
+import { i18n } from './stores/languageStore'
+import UtilsPlugin from './utils/utilsPlugin'
+
+const app = createApp(App)
+app.use(createPinia())
+app.use(router)
+app.use(i18n)
+app.use(UtilsPlugin)
+app.mount("#app")
+```
+
+Order of registration:
+1. Pinia (state store)
+2. Vue Router
+3. vue-i18n instance (imported from `languageStore.js`, not created here)
+4. `UtilsPlugin` (global properties: date helpers + dice rollers)
+
+**`src/App.vue`**
+
+Root component. Shows `` only when the user is logged-in (detected via `localStorage.getItem('token')`). The main `` is wrapped in a `` that gets the CSS class `full-width` when the user is not logged in (centering forms on the page). Listens to `storage` and custom `auth-changed` window events to reactively update `loggedIn`.
+
+### `UtilsPlugin` (`src/utils/utilsPlugin.js`)
+
+Installs these global properties on the Vue app instance:
+
+| Property | Source | Description |
+|---|---|---|
+| `$formatDate` | `dateUtils.js` | Format ISO date to locale string |
+| `$formatDateTime` | `dateUtils.js` | Format ISO datetime |
+| `$formatRelativeDate` | `dateUtils.js` | Relative time (e.g. "2 hours ago") |
+| `$safeValue` | `dateUtils.js` | Return value or fallback |
+| `$capitalize` | `dateUtils.js` | Capitalize string |
+| `$rollDie` | `randomUtils.js` | Roll single die (1–N) |
+| `$rollDice` | `randomUtils.js` | Roll multiple dice → array |
+| `$rollDiceWithSum` | `randomUtils.js` | Roll + return sum |
+| `$rollNotation` | `randomUtils.js` | Parse RPG notation e.g. `"3d6+2"` |
+| `$randomBetween` | `randomUtils.js` | Random int in range |
+| `$randomChoice` | `randomUtils.js` | Pick random element from array |
+| `$shuffleArray` | `randomUtils.js` | Fisher-Yates shuffle |
+
+---
+
+## 3. Router
+
+**`src/router/index.js`** — `createWebHistory` (HTML5 mode, no hash).
+
+### Route Table
+
+| Path | Name | Component | Auth? | Admin? |
+|---|---|---|---|---|
+| `/` | `Landing` | `LandingView` | — | — |
+| `/login` | `Login` | `LoginView` | — | — |
+| `/register` | `Register` | `RegisterView` | — | — |
+| `/forgot-password` | `ForgotPassword` | `ForgotPasswordView` | — | — |
+| `/reset-password` | `ResetPassword` | `ResetPasswordView` | — | — |
+| `/dashboard` | `Dashboard` | `DashboardView` | ✓ | — |
+| `/profile` | `UserProfile` | `UserProfileView` | ✓ | — |
+| `/users` | `UserManagement` | `UserManagementView` | ✓ | ✓ |
+| `/ausruestung/:characterId` | `Ausruestung` | `AusruestungView` | ✓ | — |
+| `/maintenance` | `Maintenance` | `MaintenanceView` | ✓ | — |
+| `/upload` | `FileUpload` | `FileUploadPage` | ✓ | — |
+| `/sponsors` | `Sponsors` | `SponsorsView` | — | — |
+| `/help` | `Help` | `HelpView` | — | — |
+| `/system-info` | `SystemInfo` | `SystemInfoView` | — | — |
+| `/character/:id` | `CharacterDetails` | `CharacterDetails` | ✓ | — |
+| `/character/create/:sessionId` | `CharacterCreation` | `CharacterCreation` | ✓ | — |
+
+**Static imports** (loaded immediately — needed for unauthenticated pages): `LandingView`, `LoginView`, `RegisterView`, `ForgotPasswordView`, `ResetPasswordView`.
+
+**Lazy-loaded** (code-split into separate chunks on first access): all other views and both character components.
+
+Route params are passed as props for `CharacterDetails` (`id`) and `CharacterCreation` (`sessionId`).
+
+### Navigation Guard
+
+```js
+router.beforeEach(async (to, from, next) => {
+ if (to.meta.requiresAuth && !isLoggedIn()) {
+ next({ name: "Login" })
+ } else if (to.meta.requiresAdmin) {
+ const userStore = useUserStore()
+ if (!userStore.currentUser) await userStore.fetchCurrentUser()
+ if (!userStore.isAdmin) next({ name: "Dashboard" })
+ else next()
+ } else {
+ next()
+ }
+})
+```
+
+`isLoggedIn()` simply checks `localStorage.getItem("token")`. The admin check lazy-loads the user profile if it hasn't been fetched yet.
+
+---
+
+## 4. Pinia Stores
+
+### `userStore` (`src/stores/userStore.js`)
+
+```js
+defineStore('user', {
+ state: () => ({ currentUser: null, isLoading: false }),
+ getters: {
+ isAuthenticated: state => !!state.currentUser,
+ userRole: state => state.currentUser?.role || 'standard',
+ isAdmin: state => state.currentUser?.role === 'admin',
+ isMaintainer: state => state.currentUser?.role === 'maintainer'
+ || state.currentUser?.role === 'admin',
+ isStandardUser: state => !!state.currentUser
+ },
+ actions: {
+ async fetchCurrentUser() { /* GET /api/user/profile */ },
+ clearUser() { this.currentUser = null }
+ }
+})
+```
+
+- Fetches profile from `GET /api/user/profile`.
+- On successful fetch, also updates the i18n locale from `profile.preferred_language` and persists it to `localStorage`.
+- `currentUser` shape: `{ id, username, display_name, email, role, preferred_language }`.
+- Used by: `Menu.vue`, `CharacterDetails.vue`, `UserManagementView.vue`, router guard.
+
+### `languageStore` (`src/stores/languageStore.js`)
+
+```js
+export const i18n = createI18n({
+ legacy: false,
+ locale: localStorage.getItem('language') || 'de',
+ fallbackLocale: 'en',
+ messages: { de, en }
+})
+
+defineStore('language', {
+ state: () => ({ currentLanguage: localStorage.getItem('language') || 'de' }),
+ actions: {
+ setLanguage(lang) {
+ this.currentLanguage = lang
+ i18n.global.locale.value = lang
+ localStorage.setItem('language', lang)
+ }
+ }
+})
+```
+
+Unusually, the `i18n` instance is **created and exported from this file**, then imported in `main.js`. Default language: `de`.
+
+### `characterCreationStore` (`src/stores/characterCreation.js`)
+
+Manages the multi-step character wizard session state in-memory (backed by server-side session).
+
+**State:**
+
+```js
+{
+ sessionData: {
+ basic_info: null,
+ attributes: null,
+ derived_values: null,
+ skills: [],
+ skills_meta: { totalUsedPoints: 0, selectedCategory: null },
+ spells: [],
+ equipment: null
+ },
+ currentStep: 1, // 1–5
+ sessionId: null,
+ isLoading: false,
+ error: null
+}
+```
+
+**Getters:** `characterClass`, `characterStand`, `characterRace`, `hasSelectedSkills`, `totalSkillPoints`, `isValid` (checks basic_info + attributes + derived_values are all non-null).
+
+**Actions:** `initializeSession(sessionId?)`, `createNewSession()`, `loadSession(sessionId)` (TODO), `saveSession()` (TODO), `updateStepData(data)`, `goToStep(n)`, `nextStep()`, `previousStep()`, `clearSession()`.
+
+Note: `saveSession()` and `loadSession()` are marked TODO — the actual session persistence is handled directly by `CharacterCreation.vue` via the API, bypassing this store.
+
+### `counter` (`src/stores/counter.ts`)
+
+A boilerplate Pinia counter store from Vue scaffolding. Not used in the application.
+
+---
+
+## 5. Views (`src/views/`)
+
+Most views are thin wrappers that delegate to a single component.
+
+### `LandingView.vue`
+
+**Purpose:** Public landing page. Displays app logo, description, version info, and action links.
+
+**Data fetched:** `GET ${VITE_API_URL}/api/public/version` (unauthenticated, uses raw `axios` not the `API` utility). Retries every 5 s up to 24 times if the backend is unreachable.
+
+**Logic:**
+- `isBackendAvailable` computed: disables the login button until the backend responds.
+- Clears retry interval on `beforeUnmount`.
+- Shows frontend version from `version.js` (`0.2.3`) and backend version from API.
+
+**Components used:** None (self-contained).
+
+---
+
+### `LoginView.vue`
+
+Thin wrapper: renders ``.
+
+---
+
+### `RegisterView.vue`
+
+Thin wrapper: renders ``.
+
+---
+
+### `ForgotPasswordView.vue`
+
+Thin wrapper: renders ``.
+
+---
+
+### `ResetPasswordView.vue`
+
+Thin wrapper: renders ``.
+
+---
+
+### `DashboardView.vue`
+
+Thin wrapper: renders ``.
+
+---
+
+### `CharacterView.vue`
+
+Thin wrapper (legacy): passes `$route.params.characterId` to `` as `:characterId`. Route `/character/:id` uses `CharacterDetails` directly via lazy import; this view appears unused by the router.
+
+---
+
+### `AusruestungView.vue`
+
+Passes `$route.params.characterId` to ``. Legacy/simple standalone equipment list for a character — separate from the main character detail equipment tab.
+
+---
+
+### `MaintenanceView.vue`
+
+Thin wrapper: renders ``.
+
+---
+
+### `UserProfileView.vue`
+
+**Purpose:** Self-service profile management for the logged-in user.
+
+**API calls:**
+- `GET /api/user/profile` (on created)
+- `PUT /api/user/display-name` — change display name (max 30 chars)
+- `PUT /api/user/language` — change preferred language (updates i18n locale + localStorage)
+- `PUT /api/user/email` — change email
+- `PUT /api/user/password` — change password (requires current + new + confirm)
+
+**Validation:**
+- Display name: max 30 chars
+- Password: min 6 chars, must match confirmation
+- Email: must differ from current, type="email"
+- Duplicate email check via backend error message parsing
+
+**Sections rendered:** User info display, Change Display Name, Change Language, Change Email, Change Password.
+
+**After language update:** directly sets `this.$i18n.locale` and `localStorage.language`. Also updates `userStore.currentUser.display_name` after display name change.
+
+---
+
+### `UserManagementView.vue`
+
+**Purpose:** Admin panel for managing all users.
+
+**Guard:** Route has `requiresAdmin: true`.
+
+**API calls:**
+- `GET /api/users` — list all users
+- `PUT /api/users/:id/role` — change role (`standard` | `maintainer` | `admin`)
+- `DELETE /api/users/:id` — delete user
+- `PUT /api/users/:id/password` — admin password reset (no current password required)
+
+**Dialogs (modal overlays):**
+1. Role change dialog — select from standard/maintainer/admin
+2. Delete confirmation dialog
+3. Password change dialog (min 6 chars, must confirm)
+
+**Protection:** Buttons disabled for the current logged-in user's own row (cannot self-demote or self-delete). Detected via `currentUser.id === user.id` from `userStore`.
+
+---
+
+### `FileUploadPage.vue`
+
+**Purpose:** Import character data from VTT (Virtual Tabletop) JSON or CSV files.
+
+**API call:** `POST /api/importer/upload` with `multipart/form-data`.
+
+**Validation:** Checks MIME types (`application/json`, `text/csv`). VTT file is required; CSV is optional.
+
+**Note:** Manually adds `Authorization` header even though the API interceptor already does this — a redundancy.
+
+---
+
+### `HelpView.vue`
+
+Static content page. Dynamically loads FAQ items from i18n keys `help.faq1.question`/`.answer`, `help.faq2...`, etc., iterating until a key is not found using `$te()`.
+
+---
+
+### `SponsorsView.vue`
+
+Static content page listing contributors and special thanks. Links to GitHub and Ko-fi.
+
+---
+
+### `SystemInfoView.vue`
+
+**Purpose:** Displays system/technology information and live backend status.
+
+**API call:** `GET ${VITE_API_URL}/api/public/systeminfo` (uses raw `axios`). Returns `{ version, gitCommit, userCount, charCount, dbVersion }`.
+
+**Technology stack display:** Hardcoded lists of Vue 3, Vite, Go 1.25, Gin, GORM, MariaDB, JWT, chromedp, Docker.
+
+---
+
+## 6. Components (`src/components/`)
+
+### `Menu.vue`
+
+**Purpose:** Fixed top navigation bar (60px high, z-index 1000).
+
+**Authentication-dependent rendering:**
+- Always visible: Home, Info dropdown (Help, Sponsors, System Info)
+- Logged-in only: Characters dropdown (Dashboard, Import Data)
+- Maintainer/Admin only: Admin dropdown (Maintenance, User Management)
+- Not logged-in only: Register link
+- Right side (logged-in): User icon dropdown (Profile, Logout)
+
+**Hover dropdowns:** Implemented with `@mouseenter`/`@mouseleave` + 200 ms `setTimeout` delay to prevent premature closing.
+
+**Logout flow:** calls `logout()` from `auth.js` (removes token), clears `userStore`, dispatches `auth-changed` window event, navigates to `/`.
+
+**Auth change listener:** `window.addEventListener('auth-changed', ...)` — re-fetches user profile on login or clears on logout.
+
+**Props/emits:** None.
+
+---
+
+### `LoginForm.vue`
+
+**Purpose:** Login form.
+
+**API call:** `POST /login` with `{ username, password }`. On success: stores JWT in `localStorage`, calls `userStore.fetchCurrentUser()`, dispatches `auth-changed`, navigates to `/dashboard`.
+
+**Error handling:** Displays "Invalid credentials" on any error.
+
+**Note:** Hardcoded English labels — does not use `$t()` for the form labels (unlike most other components).
+
+---
+
+### `RegisterForm.vue`
+
+**Purpose:** Registration form.
+
+**Validation:** Client-side password confirmation match check before API call.
+
+**API call:** `POST /register` with `{ username, password, email }`.
+
+**i18n:** Fully translated via `auth.*` keys.
+
+---
+
+### `ForgotPasswordForm.vue`
+
+**Purpose:** Request a password reset email.
+
+**API call:** `POST /password-reset/request` with `{ email, redirect_url: window.location.origin }`.
+
+**UX:** Shows success state (hides form, shows info text) on success without redirecting. Shows error message on failure.
+
+---
+
+### `ResetPasswordForm.vue`
+
+Handles the password-reset token link (not examined in detail — delegates to ``).
+
+---
+
+### `CharacterList.vue`
+
+**Purpose:** Dashboard character listing with creation session management.
+
+**API calls:**
+- `GET /api/characters` → `{ self_owned: [...], others: [...] }`
+- `GET /api/characters/create-sessions` → `{ sessions: [...] }`
+- `POST /api/characters/create-session` → `{ session_id }` — starts new character wizard
+- `DELETE /api/characters/create-session/:sessionId`
+
+**Layout:**
+- "Create New Character" button (green dashed border)
+- `` sub-component (shows in-progress wizards)
+- Two column layout: owned characters | shared characters
+- Each character shows: name, race, class, grade, owner, public/private badge
+- Character names are `` to `/character/:id`
+
+**Props/emits:** None (standalone).
+
+**Sub-components:** `CharacterCreationSessions`.
+
+---
+
+### `CharacterCreationSessions.vue`
+
+**Purpose:** Shows active character creation sessions (drafts) on the dashboard.
+
+**Props:** `sessions: Array`
+
+**Emits:** `continue-session(sessionId)`, `delete-session(sessionId)`
+
+**Renders:** Grid of cards, one per session. Shows name, step progress (e.g. "Step 2/5"), race/class, last updated, expires. Clicking a card card navigates via `continue-session` emit.
+
+---
+
+### `CharacterDetails.vue`
+
+**Purpose:** Main character management hub. Fetches character data and dynamically renders sub-views through a tab-like submenu.
+
+**Props:** `id` (string, from route)
+
+**API calls:**
+- `GET /api/characters/:id` (on created + after any edit via `refreshCharacter`)
+
+**Computed:** `isOwner` — compares `character.user_id` with `userStore.currentUser.id`.
+
+**Sub-views rendered via `` with `character` and `isOwner` props:**
+1. `DatasheetView` (default)
+2. `SkillView`
+3. `WeaponView`
+4. `SpellView`
+5. `EquipmentView`
+6. `ExperianceView`
+7. `DeleteCharView`
+
+**Additional dialogs:**
+- 📄 Export button → `ExportDialog`
+- 🌐/🔒 Visibility button (owner only) → `VisibilityDialog`
+
+**Event handling:** `@character-updated` from sub-views triggers `refreshCharacter()` which reloads from API.
+
+---
+
+### `DatasheetView.vue`
+
+**Purpose:** Character stats and biographical information display with inline editing.
+
+**Props:** `character: Object` (required)
+
+**Emits:** `character-updated`
+
+**Sub-components:** `ImageUploadCropper`
+
+**Inline editing mechanism:** Double-click (`@dblclick`) on any displayed value activates an input/select in its place. On blur or Enter, `saveEdit(path)` / `saveProp(prop)` PATCHes the character via `PUT /api/characters/:id`. On Escape, `cancelEdit()` restores display.
+
+**Stat fields (left column stat box):**
+
+| Label key | Character path |
+|---|---|
+| `stats.beauty` | `eigenschaften.0.value` |
+| `stats.dexterity` | `eigenschaften.1.value` |
+| `stats.agility` | `eigenschaften.2.value` |
+| `stats.intelligence` | `eigenschaften.3.value` |
+| `stats.constitution` | `eigenschaften.4.value` |
+| `stats.charisma` | `eigenschaften.5.value` |
+| `stats.strength` | `eigenschaften.6.value` |
+| `stats.willpower` | `eigenschaften.7.value` |
+| `stats.spelltalent` | `eigenschaften.8.value` |
+| `stats.poisontolerance` | `git` |
+| `stats.movement` | `b.max` |
+| `stats.lifepoints` | `lp.max` |
+| `stats.staminapoints` | `ap.max` |
+| `stats.divinegrace` | `bennies.gg` |
+| `stats.fatesfavor` | `bennies.sg` |
+
+**Character info fields (editable):** name, typ (class), gender (select), grad, rasse (select), origin (select), social_class (select), spezialisierung (select), alter, hand (select: rechts/links/beidhändig), groesse, gewicht, merkmale.groesse, merkmale.breite, merkmale.augenfarbe, merkmale.haarfarbe, glaube (multi-select), merkmale.sonstige.
+
+**Select options:** loaded lazily via `GET /api/characters/:id/datasheet-options` (only when a select-type field is first double-clicked).
+
+**`glaube` (beliefs) field:** Multi-select stored as comma-separated string; displayed and edited as array.
+
+**Path traversal for saving:** `path.split('.')` reduces into nested character object, then `PUT /api/characters/:id` with full character payload.
+
+---
+
+### `SkillView.vue`
+
+**Purpose:** Display and manage character skills (Fertigkeiten + Waffenfertigkeiten) and innate skills.
+
+**Props:** `character: Object` (required), `isOwner: Boolean`
+
+**Emits:** `character-updated`
+
+**Sub-components:** `SkillImproveDialog`, `SkillLearnDialog`
+
+**Learning mode toggle:** 🎓 button visible to owner. When active:
+- Resources bar (EP + Gold) shown
+- Additional action buttons: 📚 Learn New, ➕ Add Skill
+- Each skill row shows ⬆️ Improve button
+
+**Table structure:**
+- Regular skills: grouped by category (`character.categorizedskills`) — name, EW (Fertigkeitswert), bonus, PP (Praxispunkte), note
+- Weapon skills: from `character.waffenfertigkeiten`
+- Innate skills: from `character.InnateSkills` (right column, name + value only)
+
+**PP (Praxispunkte) adjustment:** +/− buttons directly call `POST /api/characters/practice-points`.
+
+**Inline skill editing:** Double-click on name, fertigkeitswert, or bemerkung → inline input; saved via `PUT /api/characters/:skill_id/skill`.
+
+**Dialogs:**
+1. `SkillLearnDialog` — learn new skill (complex, see below)
+2. Improve Selection Dialog — simple modal; selects skill + PP count → `POST /api/characters/improve-skill`
+3. Add Skill Dialog — manually add skill by name/value → `POST /api/characters/:id/add-skill`
+4. `SkillImproveDialog` — detailed multi-level improvement dialog
+
+---
+
+### `SkillImproveDialog.vue`
+
+**Purpose:** Detailed skill improvement dialog supporting multiple reward types and level sequences.
+
+**Props:** `character: Object`, `skill: Object`, `isVisible: Boolean`, `learningType: 'improve'|'learn'|'spell'`
+
+**Emits:** `close`, `skill-updated`
+
+**Features:**
+- Loads available improvement levels + costs from `GET /api/characters/lerncost-newsystem` or similar
+- Loads reward types from API
+- Shows live resource tracking (EP remaining, Gold remaining, PP remaining)
+- Multi-level selection (click/deselect in sequence — must be consecutive)
+- Reward types: `default` (EP + Gold), `noGold` (EP only), `halveepnoGold` (half EP), `pp` (PP only), `mixed` (EP + PP)
+- Submits via `POST /api/characters/improve-skill-newsystem` or similar
+
+---
+
+### `SkillLearnDialog.vue`
+
+**Purpose:** Learn a brand-new skill (not yet on the character).
+
+**Features:**
+- Drag & drop (HTML5 native) or click-to-select skills from available list to "to learn" list
+- Category filter buttons (all categories shown as chips)
+- Search filter input
+- Sort by name or EP cost (ascending/descending toggle)
+- Reward type selector: Standard (EP + Gold) or Nur EP (no gold)
+- Live EP/Gold remaining display
+- Multi-skill batch learning (multiple skills can be queued)
+- Submits batch via `POST /api/characters/learn-skills` (or similar)
+
+---
+
+### `SpellView.vue`
+
+**Purpose:** Display character spells.
+
+**Props:** `character: Object`, `isOwner: Boolean`
+
+**Emits:** `character-updated`
+
+**Sub-components:** `SpellLearnDialog`
+
+**Table:** name, description, bonus, source.
+
+**Learning:** Owner sees 🎓 button → opens `SpellLearnDialog`.
+
+---
+
+### `SpellLearnDialog.vue`
+
+**Purpose:** Learn new spells with filtering, sorting, and multi-spell queuing.
+
+**Features:**
+- School (Schule) filter buttons
+- Search term filter
+- Sort by name, EP cost, Gold cost
+- Reward type selector (loads from API)
+- Available/selected two-column layout
+- Per-spell affordability check
+- Batch spell learning queue with total cost summary
+- Submits via `POST /api/characters/learn-spells` (or similar)
+
+---
+
+### `WeaponView.vue`
+
+**Purpose:** Display and manage character weapons.
+
+**Props:** `character: Object`, `isOwner: Boolean`
+
+**Emits:** `character-updated`
+
+**Table:** name (`*` suffix if magical), description, weight, value, amount, contained_in, bonus (anb/abwb).
+
+**Add weapon dialog:**
+- Loads master weapon list from `GET /api/maintenance/weapons` (or similar)
+- Text search filter
+- Select + set amount
+- Submits via `POST /api/equipment` (or similar weapon endpoint)
+
+**Edit weapon dialog:**
+- Inline form for: description, is_magical checkbox, amount, value, attack bonus (anb), defense bonus (abwb), damage bonus (schb)
+- Saves via `PUT /api/equipment/:id` (or similar)
+
+**Delete weapon:** `DELETE /api/equipment/:id` with confirm dialog using `$t('weapon.confirmDelete')` with `{name}` interpolation.
+
+---
+
+### `EquipmentView.vue`
+
+**Purpose:** Display and manage character non-weapon equipment.
+
+**Props:** `character: Object`, `isOwner: Boolean`
+
+**Emits:** `character-updated`
+
+**Table:** name, description, weight, value, amount, contained_in, bonus.
+
+**Add equipment dialog:**
+- Loads master equipment from `GET /api/maintenance/equipment`
+- Text search filter
+- Select + set amount
+- Submits:`POST /api/equipment` with `{ character_id, name, beschreibung, gewicht, wert, anzahl, bonus, beinhaltet_in, contained_in, container_type }`
+
+**Delete:** `DELETE /api/equipment/:id` with i18n confirm using `{name}` interpolation.
+
+---
+
+### `ExperianceView.vue`
+
+**Purpose:** Manage EP (experience points) and gold/wealth, with audit log.
+
+**Props:** `character: Object`, `isOwner: Boolean`
+
+**Sub-components:** `AuditLogView`
+
+**Computed:** `totalWealthInGS` — currency conversion:
+```js
+goldstücke + Math.floor(silberstücke / 10) + Math.floor(kupferstücke / 10)
+```
+(1 GS = 10 SS = 10 KS — Midgard RPG currency system)
+
+**EP management:** Add/remove EP inputs → `PUT /api/characters/:id/experience` or similar.
+
+**Gold management:** Add/remove gold inputs → `PUT /api/characters/:id/wealth` or similar.
+
+After both operations: calls `this.$refs.auditLog.loadAuditLog()` to refresh the audit log.
+
+---
+
+### `AuditLogView.vue`
+
+**Purpose:** Display change history (EP/gold transactions) for a character.
+
+**Props:** `character: Object`
+
+**API:** `GET /api/characters/:id/audit-log` with query params for field filter and date range.
+
+**Filters:**
+- Field: all / experience_points / gold / silver / copper
+- Date range: all time / today / this week / this month / custom (from–to date inputs)
+- Group by date toggle
+
+**Statistics section:** total_changes, ep_spent, ep_gained, gold_spent, gold_gained.
+
+**Entry display:** Shows old → new value with difference (+/−), reason, optional notes. Color-coded: green for positive, red for negative changes.
+
+---
+
+### `ExportDialog.vue`
+
+**Purpose:** Multi-format character export modal.
+
+**Props:** `characterId`, `showDialog: Boolean`
+
+**Emits:** `update:showDialog`, `export-success`
+
+**Computed:** `canExport` — false if no format selected, or PDF format but no template selected.
+
+**On created:** loads templates from `GET /api/pdf/templates`.
+
+**Export formats:**
+
+| Format | API Call | Delivery |
+|---|---|---|
+| PDF | `GET /api/pdf/export/:id?template=X&showUserName=true` → returns `{ filename }`, then opens `GET /api/pdf/file/:filename` in new tab | Browser window popup |
+| VTT | `GET /api/importer/export/vtt/:id/file` (blob) | Programmatic download link |
+| BaMoRT (JSON) | `GET /api/transfer/download/:id` (blob) | Programmatic download link |
+
+**PDF export note:** Gets filename from API first, then constructs full URL from `API.defaults.baseURL + /api/pdf/file/:filename`. Uses `window.open(url, '_blank')` synchronously (not before an async call) — avoids popup blockers per project convention.
+
+---
+
+### `VisibilityDialog.vue`
+
+**Purpose:** Change character visibility (private/public) and manage per-user sharing.
+
+**Props:** `characterId`, `currentVisibility: Boolean`, `showDialog: Boolean`
+
+**Emits:** `update:showDialog`, `visibility-updated(isPublic)`
+
+**Watches:** `showDialog` — on open, loads available users and current shares.
+
+**API calls:**
+- `GET /api/characters/:id/available-users` — users eligible to share with
+- `GET /api/characters/:id/shares` (implied) — currently shared users
+- `PUT /api/characters/:id/visibility` + share update
+
+**Features:** Radio buttons for private/public. Searchable user list (filter by display name, username, email). Two-panel: "Add Users" | "Currently Shared With" with remove buttons.
+
+---
+
+### `DeleteCharView.vue`
+
+**Purpose:** Delete character confirmation panel (inside character detail submenu).
+
+**Props:** `character: Object`, `isOwner: Boolean`
+
+**Emits:** `deleted`, `cancel`
+
+**Behavior:** Shows "not authorized" message for non-owners. For owners: confirmation text + "Yes, Delete" button → `DELETE /api/characters/:id` → navigates to `/dashboard`.
+
+---
+
+### `ImageUploadCropper.vue`
+
+**Purpose:** Upload and crop a character portrait image.
+
+**Props:** `characterId`
+
+**Emits:** `image-updated`
+
+**Features:**
+- File picker restricted to `image/*`
+- HTML5 Canvas cropping with mouse drag selection
+- Two crop shapes: rectangular or circular
+- Live preview canvas (400×400)
+- `POST /api/characters/:id/image` with FormData containing crop coordinates
+
+**Implementation:** Uses `FileReader` → `Image` → two `