refactor(document): move document domain core to document/ package
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
97
docs/CLAUDE.md
Normal file
97
docs/CLAUDE.md
Normal file
@@ -0,0 +1,97 @@
|
||||
# Docs — Familienarchiv
|
||||
|
||||
## Overview
|
||||
|
||||
Project documentation organized into four categories: architecture decision records (ADRs), system architecture diagrams, infrastructure runbooks, and detailed UI/UX specifications.
|
||||
|
||||
## Folder Structure
|
||||
|
||||
```
|
||||
docs/
|
||||
├── adr/ # Architecture Decision Records
|
||||
├── architecture/ # C4 model diagrams and system architecture docs
|
||||
├── infrastructure/ # Deployment, CI/CD, and ops guides
|
||||
├── specs/ # UI/UX feature specifications (HTML)
|
||||
├── app-analysis-*.md # Application analysis reports
|
||||
├── mail.md # Mail system documentation
|
||||
├── security-guide.md # Security policies and hardening guide
|
||||
├── STYLEGUIDE.md # Coding and design style guide
|
||||
├── TODO-backend.md # Backend backlog
|
||||
└── TODO-frontend.md # Frontend backlog
|
||||
```
|
||||
|
||||
## ADR (`adr/`)
|
||||
|
||||
Architecture Decision Records capture major technical decisions and their rationale.
|
||||
|
||||
| ADR | Title | Status |
|
||||
|---|---|---|
|
||||
| `001-ocr-python-microservice.md` | OCR as a separate Python container | Accepted |
|
||||
| `002-polygon-jsonb-storage.md` | Polygon coordinates in JSONB columns | Accepted |
|
||||
| `003-chronik-unified-activity-feed.md` | Unified activity feed (Chronik) | Accepted |
|
||||
|
||||
When making a significant architectural change (new service, data model change, technology swap), write a new ADR following the format:
|
||||
- Status (Proposed / Accepted / Deprecated / Superseded)
|
||||
- Context (forces at play)
|
||||
- Decision (what we decided)
|
||||
- Consequences (trade-offs)
|
||||
- Alternatives Considered (table format)
|
||||
|
||||
## Architecture (`architecture/`)
|
||||
|
||||
Contains C4 model diagrams describing the system at different zoom levels:
|
||||
|
||||
- **Context diagram** — How Familienarchiv fits into the user and system ecosystem
|
||||
- **Container diagram** — The high-level technology choices (Spring Boot, SvelteKit, PostgreSQL, MinIO, OCR service)
|
||||
- **Component diagram** — Major structural components within the backend
|
||||
|
||||
Written in Markdown with embedded Mermaid or PlantUML diagrams (`c4-diagrams.md`).
|
||||
|
||||
## Infrastructure (`infrastructure/`)
|
||||
|
||||
Operational documentation for running Familienarchiv in production and CI.
|
||||
|
||||
| Document | Purpose |
|
||||
|---|---|
|
||||
| `ci-gitea.md` | Gitea CI/CD pipeline configuration |
|
||||
| `production-compose.md` | Production Docker Compose setup |
|
||||
| `s3-migration.md` | Migrating documents between S3 buckets |
|
||||
| `self-hosted-catalogue.md` | Self-hosted software catalogue |
|
||||
|
||||
## Specs (`specs/`)
|
||||
|
||||
High-fidelity UI/UX specifications written as standalone HTML files. These are design documents that describe exact layout, interactions, and responsive behavior before implementation.
|
||||
|
||||
Each spec typically includes:
|
||||
- Visual mockups with CSS-in-HTML styling
|
||||
- Interaction flows and state transitions
|
||||
- Responsive breakpoint behavior
|
||||
- Accessibility requirements
|
||||
|
||||
Examples of active spec areas:
|
||||
- Document detail page (`document-topbar-*.html`, `documents-page-spec.html`)
|
||||
- Admin interfaces (`admin-redesign-*.html`, `admin-tag-overhaul.html`)
|
||||
- Transcription workflows (`inline-transcription-*.html`, `annotation-transcription-*.html`)
|
||||
- Dashboard and activity feeds (`dashboard-*.html`, `chronik-spec.html`)
|
||||
- OCR admin (`ocr-admin-spec.html`)
|
||||
|
||||
## How to Use
|
||||
|
||||
1. **Before implementing a feature**, check `specs/` for an existing specification.
|
||||
2. **When proposing a new architecture**, draft an ADR in `adr/` and discuss before coding.
|
||||
3. **When deploying**, follow `infrastructure/production-compose.md`.
|
||||
4. **Keep TODO files updated** — they serve as lightweight backlogs.
|
||||
|
||||
## Style Guide
|
||||
|
||||
`STYLEGUIDE.md` covers:
|
||||
- Code formatting and linting rules
|
||||
- Component naming conventions
|
||||
- Color palette and typography
|
||||
- Accessibility standards (WCAG 2.1 AA)
|
||||
|
||||
## Contributing
|
||||
|
||||
- ADRs should be sequential (`NNN-descriptive-name.md`).
|
||||
- Specs should be self-contained HTML files viewable in a browser.
|
||||
- Infrastructure docs should include copy-pasteable commands.
|
||||
178
docs/app-analysis-2026-04-12.md
Normal file
178
docs/app-analysis-2026-04-12.md
Normal file
@@ -0,0 +1,178 @@
|
||||
# Familienarchiv — Application Analysis
|
||||
|
||||
_2026-04-12_
|
||||
|
||||
> Analysis of the family archive application for polish issues, missing features, and improvements across research, discoverability, and collaboration. Each item was discussed and triaged — only actionable items were kept, the rest are documented as "skipped" with reasoning.
|
||||
|
||||
---
|
||||
|
||||
## 1. Polish Issues (Regressions & Rough Edges)
|
||||
|
||||
_Items 1.1–1.5 were identified but not discussed in detail during triage._
|
||||
|
||||
### 1.1 Archive Box & Folder fields missing from UI
|
||||
|
||||
The backend `Document` entity has `archiveBox` and `archiveFolder` fields but they're not in the `DocumentUpdateDTO`, not in any form, not in the detail view, and not in search results. The `documentLocation` free-text field exists but the structured pair is unused.
|
||||
|
||||
### 1.2 Legacy transcription textarea on edit/create pages
|
||||
|
||||
The edit and create pages include a `<TranscriptionSection>` plain textarea that writes to `Document.transcription`, while the detail view uses the annotation-based `TranscriptionBlock` system. The two are disconnected — editing the textarea could overwrite or conflict with block-based transcriptions.
|
||||
|
||||
### 1.3 Person `title` field not in frontend forms
|
||||
|
||||
The `title` field (honorifics like "Dr.", "Prof.") was added to the backend but the person edit/create forms don't expose it.
|
||||
|
||||
### 1.4 Person `personType` not editable
|
||||
|
||||
Person type (`PERSON`, `INSTITUTION`, `GROUP`) is displayed as a badge but cannot be changed on the edit page.
|
||||
|
||||
### 1.5 Document detail doesn't show summary, documentLocation, or tags prominently
|
||||
|
||||
The metadata drawer shows date, location, status, persons, and tags but not `documentLocation`, `summary`, or `archiveBox`/`archiveFolder`.
|
||||
|
||||
### 1.6 Dashboard stats are minimal — SKIPPED
|
||||
|
||||
The recent activity box already shows total document and person counts in context. Dashboard elements should earn their space by driving action, not just displaying numbers. Current setup is sufficient.
|
||||
|
||||
---
|
||||
|
||||
## 2. Research (Finding Specific Documents)
|
||||
|
||||
_Goal: A user has a specific question — "Who wrote to Großvater in 1943?" or "Where is the contract about the house?" — and wants precise answers._
|
||||
|
||||
### 2.1 No status filter in search — SKIPPED
|
||||
|
||||
The document status system (`PLACEHOLDER`, `UPLOADED`, `TRANSCRIBED`, `REVIEWED`, `ARCHIVED`) is mostly technical and adds no real value to the user. The status concept itself needs rethinking or removal — that's a separate design question, not a missing search filter.
|
||||
|
||||
### 2.2 Search result snippets with match highlighting — [#219](http://heim-nas:3005/marcel/familienarchiv/issues/219)
|
||||
|
||||
Search results show title, date, sender, receivers, and tags but not _why_ a document matched. With 1500+ documents, users can't evaluate results without opening each one. Show a short text snippet with the matching term highlighted.
|
||||
|
||||
**Priority:** Low — nice-to-have for later.
|
||||
|
||||
### 2.3 No pagination — SKIPPED
|
||||
|
||||
Not a problem yet at current scale.
|
||||
|
||||
### 2.4 Year/group headers in date-sorted results — [#220](http://heim-nas:3005/marcel/familienarchiv/issues/220)
|
||||
|
||||
The briefwechsel page already has a timeline and works with a single person. For the main search results, adding year headers (e.g. "1938", "1939") when sorted by date gives chronological structure without building a full timeline view.
|
||||
|
||||
### 2.5 Improved tag system — AND/OR + hierarchy — [#221](http://heim-nas:3005/marcel/familienarchiv/issues/221)
|
||||
|
||||
Power users would benefit from AND/OR tag filtering, tag hierarchy (parent/child relationships), and a tag overview page with document counts.
|
||||
|
||||
### 2.6 Upgrade search to PostgreSQL full-text search — [#222](http://heim-nas:3005/marcel/familienarchiv/issues/222)
|
||||
|
||||
Current search uses `ILIKE %query%` — no stemming, no ranking, no boolean operators. Upgrading to PostgreSQL's `tsvector`/`tsquery` with the German dictionary gives stemming ("Briefe" matches "Brief"), relevance ranking, boolean operators, prefix matching, and phrase matching. Zero new infrastructure needed.
|
||||
|
||||
### 2.7 No related documents — SKIPPED
|
||||
|
||||
The person detail page and briefwechsel page already surface document connections. Cross-references from the document view aren't needed.
|
||||
|
||||
---
|
||||
|
||||
## 3. Discoverability (Browsing Without a Goal)
|
||||
|
||||
_Goal: A user wants to explore old family documents — maybe browse Opa's letters, look through documents from the 1920s, or just see what's interesting._
|
||||
|
||||
### 3.1 Sorting options on person list page — [#223](http://heim-nas:3005/marcel/familienarchiv/issues/223)
|
||||
|
||||
The person list only supports search by name. Adding sort options (most documents, last document date, recently updated) helps users discover prominent persons without needing a dashboard widget.
|
||||
|
||||
### 3.2 Person detail page lacks a narrative — SKIPPED
|
||||
|
||||
The page already shows at-a-glance stats, co-correspondents (top 5), and sent/received document lists. Sufficient as-is.
|
||||
|
||||
### 3.3 No visual browsing mode — SKIPPED
|
||||
|
||||
The archive is primarily text documents (letters, contracts). Thumbnail galleries and map views are not a good fit.
|
||||
|
||||
### 3.4 Top conversation pairs on briefwechsel entry state — [#224](http://heim-nas:3005/marcel/familienarchiv/issues/224)
|
||||
|
||||
The briefwechsel page works with a single person, but the entry/empty state could show the most active conversation pairs ranked by document count. Clicking a pair pre-fills both persons and loads the timeline — a low-effort discoverability win.
|
||||
|
||||
### 3.5 No tag-based navigation — covered by [#221](http://heim-nas:3005/marcel/familienarchiv/issues/221)
|
||||
|
||||
The tag overview page proposed in #221 covers this.
|
||||
|
||||
### 3.6 Dashboard doesn't surface interesting content — SKIPPED
|
||||
|
||||
Current dashboard is action-oriented (resume, incomplete docs, recent docs). Adding "fun" widgets would be clutter.
|
||||
|
||||
### 3.7 No era grouping — covered by [#220](http://heim-nas:3005/marcel/familienarchiv/issues/220)
|
||||
|
||||
Year headers in search results cover this use case.
|
||||
|
||||
---
|
||||
|
||||
## 4. Collaboration (Working Together to Preserve)
|
||||
|
||||
_Goal: Family members want to divide work — transcribing handwritten letters, identifying unknown persons, correcting metadata — and track collective progress._
|
||||
|
||||
### 4.1 No assignment or claim system for enrichment — SKIPPED
|
||||
|
||||
Enrichment typically takes only a few minutes per document, so the collision risk is low. Not worth the overhead of a claiming system.
|
||||
|
||||
### 4.2 Transcription has no review workflow — SKIPPED
|
||||
|
||||
The `[unleserlich]` convention for unreadable text plus comment threads with @mentions already cover the "I need help reading this" case. Sufficient for now.
|
||||
|
||||
### 4.3 Comments not surfaced for action — SKIPPED
|
||||
|
||||
The notification page with mention/reply filtering is sufficient for now.
|
||||
|
||||
### 4.4 No activity feed — SKIPPED
|
||||
|
||||
Not enough benefit for the effort to track and display it.
|
||||
|
||||
### 4.5 No progress tracking — SKIPPED
|
||||
|
||||
Hard to track accurately and not needed yet.
|
||||
|
||||
### 4.6 Batch operations for documents — [#225](http://heim-nas:3005/marcel/familienarchiv/issues/225)
|
||||
|
||||
When importing or processing batches from the same physical location or sender, editing one-at-a-time is tedious. Multi-select in the document list with bulk tag, sender, and archive location assignment.
|
||||
|
||||
### 4.7 Person merge is hidden — SKIPPED
|
||||
|
||||
Manual merge via the person edit page works fine for current needs.
|
||||
|
||||
### 4.8 No "help needed" signals — SKIPPED
|
||||
|
||||
Duplicate of 4.2 — `[unleserlich]` + comment mentions already cover this.
|
||||
|
||||
---
|
||||
|
||||
## 5. Summary
|
||||
|
||||
### Issues created
|
||||
|
||||
| # | Title | Category | Labels |
|
||||
|---|-------|----------|--------|
|
||||
| [#219](http://heim-nas:3005/marcel/familienarchiv/issues/219) | Search result snippets with match highlighting | Research | feature, ui |
|
||||
| [#220](http://heim-nas:3005/marcel/familienarchiv/issues/220) | Year/group headers in date-sorted results | Research | feature, ui |
|
||||
| [#221](http://heim-nas:3005/marcel/familienarchiv/issues/221) | Improved tag system — AND/OR + hierarchy | Research | feature |
|
||||
| [#222](http://heim-nas:3005/marcel/familienarchiv/issues/222) | Upgrade search to PostgreSQL full-text search | Research | feature |
|
||||
| [#223](http://heim-nas:3005/marcel/familienarchiv/issues/223) | Sorting options on person list page | Discoverability | feature, person |
|
||||
| [#224](http://heim-nas:3005/marcel/familienarchiv/issues/224) | Top conversation pairs on briefwechsel entry state | Discoverability | feature, conversation |
|
||||
| [#225](http://heim-nas:3005/marcel/familienarchiv/issues/225) | Batch operations for documents | Collaboration | feature, collaboration |
|
||||
|
||||
### Skipped items (with reasoning)
|
||||
|
||||
| Item | Why skipped |
|
||||
|------|-------------|
|
||||
| Dashboard stats minimal | Stats already shown in recent activity box |
|
||||
| Status filter in search | Status is technical, needs rethinking not filtering |
|
||||
| No pagination | Not a problem at current scale |
|
||||
| No related documents | Person page + briefwechsel already cover this |
|
||||
| Person detail lacks narrative | Already has stats, co-correspondents, document lists |
|
||||
| No visual browsing | Not a fit for text-heavy archive |
|
||||
| Dashboard interesting content | Would be clutter, current dashboard is action-oriented |
|
||||
| Enrichment claiming | Low collision risk, enrichment is quick |
|
||||
| Transcription review workflow | [unleserlich] + comment mentions sufficient |
|
||||
| Comments not surfaced | Notification page sufficient |
|
||||
| Activity feed | Effort/benefit ratio too low |
|
||||
| Progress tracking | Hard to track, not needed yet |
|
||||
| Person merge hidden | Manual workflow works fine |
|
||||
| Help needed signals | Covered by [unleserlich] + mentions |
|
||||
538
docs/presentation/index.html
Normal file
538
docs/presentation/index.html
Normal file
@@ -0,0 +1,538 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>KI als Team, nicht als Werkzeug</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js@5/dist/reveal.css" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js@5/dist/theme/black.css" />
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js@5/plugin/highlight/monokai.css" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;700&display=swap" />
|
||||
<style>
|
||||
/* ── Tokens (same as persona cards) ─────────────────── */
|
||||
:root {
|
||||
--bg: #0D1117;
|
||||
--surface: #161B22;
|
||||
--surface-2: #1C2128;
|
||||
--border: #21262D;
|
||||
--text: #C9D1D9;
|
||||
--text-muted: #6E7681;
|
||||
--text-bright:#F0F6FC;
|
||||
--accent: #22D3EE;
|
||||
--red: #F85149;
|
||||
|
||||
/* reveal overrides */
|
||||
--r-background-color: var(--bg);
|
||||
--r-main-color: var(--text);
|
||||
--r-heading-color: var(--text-bright);
|
||||
--r-link-color: var(--accent);
|
||||
--r-link-color-hover: #67E8F9;
|
||||
--r-main-font: 'Inter', system-ui, sans-serif;
|
||||
--r-heading-font: 'Inter', system-ui, sans-serif;
|
||||
--r-code-font: 'JetBrains Mono', monospace;
|
||||
--r-main-font-size: 36px;
|
||||
}
|
||||
|
||||
/* ── Dot-grid background (persona card pattern) ──────── */
|
||||
.reveal::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-image: radial-gradient(circle, rgba(255,255,255,.045) 1px, transparent 1px);
|
||||
background-size: 26px 26px;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
.reveal .slides { z-index: 1; }
|
||||
|
||||
/* ── Headings ─────────────────────────────────────────── */
|
||||
.reveal h1, .reveal h2, .reveal h3 {
|
||||
font-family: 'Inter', sans-serif;
|
||||
font-weight: 800;
|
||||
letter-spacing: -.5px;
|
||||
text-transform: none;
|
||||
color: var(--text-bright);
|
||||
}
|
||||
.reveal h2 {
|
||||
font-size: 1.8em;
|
||||
padding-bottom: .35em;
|
||||
border-bottom: 1px solid var(--border);
|
||||
margin-bottom: .6em;
|
||||
}
|
||||
.reveal h2::before {
|
||||
content: '// ';
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: .7em;
|
||||
color: var(--accent);
|
||||
font-weight: 500;
|
||||
}
|
||||
.reveal h3 {
|
||||
font-size: 1.15em;
|
||||
color: var(--accent);
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
/* ── Body text ───────────────────────────────────────── */
|
||||
.reveal p, .reveal li { color: var(--text); font-size: .85em; line-height: 1.6; }
|
||||
.reveal strong { color: var(--text-bright); }
|
||||
.reveal em { color: var(--text-muted); font-style: italic; }
|
||||
.reveal small { color: var(--text-muted); font-size: .65em; }
|
||||
|
||||
/* ── Links ───────────────────────────────────────────── */
|
||||
.reveal a {
|
||||
color: var(--accent);
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: .75em;
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
|
||||
}
|
||||
.reveal a:hover { color: #67E8F9; border-bottom-color: #67E8F9; }
|
||||
|
||||
/* ── Tables ──────────────────────────────────────────── */
|
||||
.reveal table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: .72em;
|
||||
}
|
||||
.reveal table thead tr {
|
||||
border-bottom: 1px solid var(--accent);
|
||||
}
|
||||
.reveal table th {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: .85em;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1.2px;
|
||||
color: var(--text-muted);
|
||||
padding: 6px 12px;
|
||||
text-align: left;
|
||||
}
|
||||
.reveal table td {
|
||||
padding: 7px 12px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
color: var(--text);
|
||||
vertical-align: top;
|
||||
}
|
||||
.reveal table tbody tr:last-child td { border-bottom: none; }
|
||||
.reveal table tbody tr:hover td { background: rgba(255,255,255,.03); }
|
||||
|
||||
/* ── Code blocks ─────────────────────────────────────── */
|
||||
.reveal pre {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-left: 3px solid var(--accent);
|
||||
border-radius: 6px;
|
||||
font-size: .58em;
|
||||
box-shadow: none;
|
||||
}
|
||||
.reveal code:not(pre code) {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: .85em;
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 3px;
|
||||
padding: 1px 6px;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/* ── Blockquotes ─────────────────────────────────────── */
|
||||
.reveal blockquote {
|
||||
background: color-mix(in srgb, var(--accent) 5%, transparent);
|
||||
border-left: 3px solid var(--accent);
|
||||
border-radius: 0 6px 6px 0;
|
||||
padding: 12px 20px;
|
||||
font-style: italic;
|
||||
font-size: .8em;
|
||||
color: var(--text-muted);
|
||||
box-shadow: none;
|
||||
width: 100%;
|
||||
}
|
||||
.reveal blockquote p { color: var(--text-muted); font-size: 1em; }
|
||||
|
||||
/* ── Topbar label (reusable) ─────────────────────────── */
|
||||
.slide-label {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: .45em;
|
||||
color: var(--text-muted);
|
||||
letter-spacing: 1px;
|
||||
display: block;
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
.slide-label .accent { color: var(--accent); }
|
||||
|
||||
/* ── Slide number ────────────────────────────────────── */
|
||||
.reveal .slide-number {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 11px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 4px;
|
||||
color: var(--text-muted);
|
||||
padding: 2px 7px;
|
||||
bottom: 12px;
|
||||
right: 14px;
|
||||
}
|
||||
|
||||
/* ── Progress bar ────────────────────────────────────── */
|
||||
.reveal .progress { height: 2px; }
|
||||
.reveal .progress span { background: var(--accent); }
|
||||
|
||||
/* ── highlight-row fragment: visible from start, styled on advance ── */
|
||||
.reveal .fragment.highlight-row { opacity: 1; visibility: inherit; }
|
||||
.reveal .fragment.highlight-row.visible {
|
||||
font-weight: 700;
|
||||
color: #22D3EE !important;
|
||||
background: rgba(34,211,238,.08) !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="reveal">
|
||||
<div class="slides">
|
||||
|
||||
<!-- ══════════════════════════════════════════════
|
||||
1 · TITEL
|
||||
══════════════════════════════════════════════ -->
|
||||
<section>
|
||||
<h2>KI als Team, nicht als Werkzeug</h2>
|
||||
<p><small>Marcel Raddatz · 2026</small></p>
|
||||
<aside class="notes">
|
||||
Einstieg: „Die meisten nutzen KI als sehr schnelle Schreibkraft. Ich nutze sie als Team von Spezialisten."
|
||||
Kein Agenda-Slide — das Workflow-Diagramm kommt nach der Team-Vorstellung.
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<!-- ══════════════════════════════════════════════
|
||||
2 · DAS PROJEKT
|
||||
══════════════════════════════════════════════ -->
|
||||
<section>
|
||||
<section>
|
||||
<h2>Das Projekt: Familienarchiv</h2>
|
||||
<p>Handgeschriebene Kurrent- und Sütterlin-Briefe, 1899–1950</p>
|
||||
<ul>
|
||||
<li>Crowd-Transkription durch 60+-Jährige am Laptop</li>
|
||||
<li>Jüngere Familienmitglieder lesen's am Handy</li>
|
||||
<li>Solo-Entwickler — kein Team zum Review oder Pair-Programming</li>
|
||||
</ul>
|
||||
<aside class="notes">PM-Takeaway: echte Domäne, echte Nutzer, echte Constraints.</aside>
|
||||
</section>
|
||||
<section>
|
||||
<h3>Stack & Umfang</h3>
|
||||
<table>
|
||||
<tr><td><strong>Backend</strong></td><td>Spring Boot 4 · Java 21 · JPA · Flyway · Spring Security</td></tr>
|
||||
<tr><td><strong>Frontend</strong></td><td>SvelteKit 2 / Svelte 5 · TypeScript · Tailwind CSS 4</td></tr>
|
||||
<tr><td><strong>DB / Storage</strong></td><td>PostgreSQL 16 · MinIO (S3-kompatibel)</td></tr>
|
||||
<tr><td><strong>OCR</strong></td><td>Python · FastAPI · lernende ML-Modelle für Kurrent & Sütterlin</td></tr>
|
||||
<tr><td><strong>i18n</strong></td><td>Paraglide.js — de / en / es</td></tr>
|
||||
<tr><td><strong>Umfang</strong></td><td>40+ UI/UX-Specs · 360+ Gitea-Issues · 7 Personas</td></tr>
|
||||
</table>
|
||||
<aside class="notes">Dev-Takeaway: kein Toy-Projekt — voller Stack, Backend und Frontend.</aside>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- ══════════════════════════════════════════════
|
||||
3 · DAS PROBLEM MIT "EINFACH FRAGEN"
|
||||
══════════════════════════════════════════════ -->
|
||||
<section>
|
||||
<h2>Das Problem mit „Einfach die KI fragen"</h2>
|
||||
<table>
|
||||
<thead><tr><th>Was man tut</th><th>Was schiefläuft</th></tr></thead>
|
||||
<tbody>
|
||||
<tr class="fragment"><td>Vage Idee → Code anfordern</td><td>Output passt nicht zu dem, was man wollte</td></tr>
|
||||
<tr class="fragment"><td>Review in derselben Session</td><td>Befangenheit: genehmigt sich selbst</td></tr>
|
||||
<tr class="fragment"><td>Die KI einfach fragen ob's gut ist</td><td>Kein echter Widerspruch</td></tr>
|
||||
<tr class="fragment"><td>Auf Gesprächskontext vertrauen</td><td>Nächste Session startet blank</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="fragment"><em>Das ist ein Workflow-Problem, kein Prompt-Problem.</em></p>
|
||||
<aside class="notes">
|
||||
Dieser Slide schafft das Warum für alles was folgt.
|
||||
Das Publikum erkennt sich in mindestens einer Zeile wieder.
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<!-- ══════════════════════════════════════════════
|
||||
4 · STUFEN DER KI-ENTWICKLUNG
|
||||
══════════════════════════════════════════════ -->
|
||||
<section>
|
||||
<h2>Wo stehst du?</h2>
|
||||
<table style="font-size:.6em;border-collapse:collapse;width:100%">
|
||||
<tbody>
|
||||
<tr style="color:#6E7681"><td style="padding:4px 10px;white-space:nowrap">1 — Autocomplete</td><td style="padding:4px 10px">Tab-Completion, eine Zeile vorschlagen</td></tr>
|
||||
<tr style="color:#6E7681"><td style="padding:4px 10px;white-space:nowrap">2 — Chat / Pair</td><td style="padding:4px 10px">Fragen stellen, Code kopieren, selbst einfügen</td></tr>
|
||||
<tr style="color:#6E7681"><td style="padding:4px 10px;white-space:nowrap">3 — Agentic Edit</td><td style="padding:4px 10px">KI editiert Dateien direkt — Cursor, Claude Code</td></tr>
|
||||
<tr style="color:#6E7681"><td style="padding:4px 10px;white-space:nowrap">4 — Spec → Code</td><td style="padding:4px 10px">Mensch schreibt Spec, KI implementiert</td></tr>
|
||||
<tr class="fragment highlight-row" style="color:#6E7681"><td style="padding:6px 10px;white-space:nowrap">5 — Idea → Spec → Code</td><td style="padding:6px 10px">KI hilft beim Spec-Schreiben — Mensch behält jedes Gate</td></tr>
|
||||
<tr style="color:#6E7681"><td style="padding:4px 10px;white-space:nowrap">6 — Fully Autonomous</td><td style="padding:4px 10px">KI plant, spezifiziert, implementiert, testet — kein Mensch</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<aside class="notes">
|
||||
Kurz im Publikum fragen: wer nutzt Autocomplete? Wer Chat? Wer Agentic?
|
||||
Pointe: Stufe 6 klingt verlockend — aber ohne Gates verliert man Kontrolle und Kontext.
|
||||
Stufe 5 ist der Sweet Spot: KI als Denkpartner, Mensch als Entscheider.
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<!-- ══════════════════════════════════════════════
|
||||
5 · WORKFLOW ÜBERBLICK
|
||||
══════════════════════════════════════════════ -->
|
||||
<section>
|
||||
<h2>Der Workflow im Überblick</h2>
|
||||
<pre style="font-size:.42em;overflow:visible"><code class="text" data-trim>
|
||||
Vage Idee
|
||||
│
|
||||
▼
|
||||
UI-Exploration ──▶ Spec (Elicit) ──▶ Gitea-Issue
|
||||
│
|
||||
▼
|
||||
Persona-Review ──▶ discuss(Persona) ─┐
|
||||
(6 Spezialisten) │
|
||||
▲ │
|
||||
└──────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
Feature-Branch
|
||||
│
|
||||
TDD: Red → Green → Refactor → Commit
|
||||
│
|
||||
▼
|
||||
Pull Request ──▶ Persona-PR-Review ──▶ Merge
|
||||
</code></pre>
|
||||
<aside class="notes">
|
||||
Roter Faden: wir verfolgen Issue #358 (Stammbaum) durch jeden Schritt.
|
||||
Jeder folgende Abschnitt zoomt in einen Schritt hinein.
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<!-- ══════════════════════════════════════════════
|
||||
6 · DAS TEAM
|
||||
══════════════════════════════════════════════ -->
|
||||
<section>
|
||||
<h2>Das Team</h2>
|
||||
<table>
|
||||
<thead><tr><th>Name</th><th>Rolle</th><th>Kernprinzip</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Elicit</td><td>Requirements Engineer</td><td>Schreibt nie Code</td></tr>
|
||||
<tr><td>Markus Keller</td><td>Architekt</td><td>Boring technology wins</td></tr>
|
||||
<tr><td>Felix Brandt</td><td>Senior Developer</td><td>Test first. Immer.</td></tr>
|
||||
<tr><td>Leonie Voss</td><td>UX Design Lead</td><td>Hardest constraint first</td></tr>
|
||||
<tr><td>Sara Holt</td><td>QA Engineer</td><td>A passing test that was never failing is a lie</td></tr>
|
||||
<tr><td>Nora „NullX" Steiner</td><td>Security Engineer</td><td>Jeder neue Endpoint braucht <code>@RequirePermission</code></td></tr>
|
||||
<tr><td>Tobias „tobi" Wendt</td><td>DevOps</td><td>Komplexität ist eine Liability</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<aside class="notes">
|
||||
Markdown-Dateien in .claude/personas/ — jede mit echtem Namen und Biografie.
|
||||
Überleitung: jetzt zoomen wir in jede Persona hinein.
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
|
||||
<section>
|
||||
<span class="slide-label"><span class="accent">.claude/personas/</span>developer.md</span>
|
||||
<h2>Aufbau einer Persona</h2>
|
||||
<div style="display:grid;grid-template-columns:auto 1fr;gap:1.6em;margin-top:.5em;align-items:start">
|
||||
<div style="font-family:'JetBrains Mono',monospace;font-size:.48em;line-height:1.85;border-right:1px solid var(--border);padding-right:1.5em;white-space:nowrap">
|
||||
<div style="color:var(--text-muted)">## Your Identity</div>
|
||||
<div style="color:var(--text-muted)">## Readable & Clean Code</div>
|
||||
<div style="color:var(--text-muted)">## Reliable Code</div>
|
||||
<div style="color:var(--text-muted)">## Modern Code</div>
|
||||
<div style="color:var(--accent);font-weight:700">## Secure Code ◀</div>
|
||||
<div style="color:var(--text-muted)">## Testable Code</div>
|
||||
<div style="color:var(--text-muted)">## How You Work</div>
|
||||
<div style="color:var(--text-muted)">## Relationships</div>
|
||||
<div style="color:var(--text-muted)">## Your Tone</div>
|
||||
</div>
|
||||
<div style="background:var(--surface);border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:0 6px 6px 0;padding:12px 18px;display:flex;flex-direction:column;gap:.55em">
|
||||
<div style="font-family:'JetBrains Mono',monospace;font-size:.48em;font-weight:700;color:var(--accent)">## Secure Code</div>
|
||||
|
||||
<div>
|
||||
<div style="font-family:'JetBrains Mono',monospace;font-size:.37em;color:var(--text-muted);letter-spacing:1px;text-transform:uppercase;margin-bottom:.3em">### General — projektunabhängig</div>
|
||||
<p style="font-size:.58em;line-height:1.6;color:var(--text-muted);font-style:italic">
|
||||
Secure code treats all external input as hostile. Authentication and authorization
|
||||
are enforced via framework annotations, not scattered if-statements.
|
||||
<strong style="color:var(--text-bright);font-style:normal">Error messages reveal nothing about the implementation.</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div style="font-family:'JetBrains Mono',monospace;font-size:.37em;color:var(--text-muted);letter-spacing:1px;text-transform:uppercase;margin-bottom:.35em">### per Stack — projektspezifisch (Frontend · Java · Python)</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:.5em;font-size:.58em">
|
||||
<div style="background:rgba(52,211,153,.07);border:1px solid rgba(52,211,153,.25);border-radius:5px;padding:7px 12px">
|
||||
<div style="font-family:'JetBrains Mono',monospace;font-size:.8em;color:var(--accent);font-weight:700;margin-bottom:.25em">✓ DO</div>
|
||||
<div style="color:var(--text);line-height:1.45"><strong>Java:</strong> <code>@RequirePermission</code> on every write endpoint</div>
|
||||
<div style="color:var(--text);line-height:1.45;margin-top:.4em"><strong>Python:</strong> SSRF host whitelist before any outbound request</div>
|
||||
</div>
|
||||
<div style="background:rgba(248,81,73,.07);border:1px solid rgba(248,81,73,.25);border-radius:5px;padding:7px 12px">
|
||||
<div style="font-family:'JetBrains Mono',monospace;font-size:.8em;color:var(--red);font-weight:700;margin-bottom:.25em">✗ DON'T</div>
|
||||
<div style="color:var(--text);line-height:1.45"><strong>Java:</strong> string concat in JPQL queries</div>
|
||||
<div style="color:var(--text);line-height:1.45;margin-top:.4em"><strong>Svelte:</strong> <code>fetch('/api/…')</code> inside <code>onMount</code></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<aside class="notes">
|
||||
Alle Personas teilen dieselbe Abschnittsstruktur — aber jede schreibt sie aus ihrer eigenen Perspektive.
|
||||
Dieser Abschnitt ist Felix' Kernphilosophie: TDD ist kein Tool, es ist die einzige Arbeitsweise.
|
||||
"Never write implementation code before a failing test exists" — das ist sein Hard Limit.
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<!-- ══════════════════════════════════════════════
|
||||
7 · IDEE & UI-EXPLORATION
|
||||
══════════════════════════════════════════════ -->
|
||||
<section>
|
||||
<h2>Idee & UI-Exploration</h2>
|
||||
<ol>
|
||||
<li class="fragment">Vage Anforderung: <em>„Wir brauchen einen Stammbaum"</em></li>
|
||||
<li class="fragment">UI-Persona bitten, <strong>4 Specs mit unterschiedlichem Fokus</strong> zu generieren</li>
|
||||
<li class="fragment">Einen Spec auswählen — Elemente aus anderen übernehmen</li>
|
||||
<li class="fragment">Finales vollständiges Spec schreiben lassen</li>
|
||||
<li class="fragment">Committen und am Issue verlinken</li>
|
||||
</ol>
|
||||
<p class="fragment"><a href="../specs/stammbaum-tree-spec.html" target="_blank" style="font-family:monospace;font-size:.7em;color:#A6DAD8">→ stammbaum-tree-spec.html</a></p>
|
||||
<aside class="notes">
|
||||
stammbaum-tree-spec.html im Browser zeigen.
|
||||
Der Trick mit den 4 Varianten: man merkt erst beim Vergleichen was man eigentlich will.
|
||||
Scope-Entscheidungen auf Pixel-Ebene sind billiger als im Issue.
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<!-- ══════════════════════════════════════════════
|
||||
7 · SPEC MIT ELICIT → GITEA-ISSUE
|
||||
══════════════════════════════════════════════ -->
|
||||
<section>
|
||||
<section>
|
||||
<h2>Spec mit Elicit → Gitea-Issue</h2>
|
||||
<p>Warum Gitea, nicht eine Datei?</p>
|
||||
<ul>
|
||||
<li class="fragment">LLM-Gespräche sind <strong>flüchtig</strong> — Issues sind <strong>dauerhaft</strong></li>
|
||||
<li class="fragment">Issues sind verlinkbar: jeder Commit referenziert sie</li>
|
||||
<li class="fragment">Review-Kommentare, Spec und Entscheidungen leben im selben Thread</li>
|
||||
<li class="fragment">Kein Risiko einer veralteten Spec-Datei im Repo</li>
|
||||
</ul>
|
||||
<aside class="notes">
|
||||
Das ist die architektonische Kernidee des ganzen Talks.
|
||||
</aside>
|
||||
</section>
|
||||
<section>
|
||||
<h3>Aufbau eines Spec-Issues</h3>
|
||||
<ul>
|
||||
<li class="fragment"><strong>Problem statement</strong> — was fehlt und warum es wichtig ist</li>
|
||||
<li class="fragment"><strong>User Journey</strong> — Schritt für Schritt in Prosa</li>
|
||||
<li class="fragment"><strong>E2E-Szenarien</strong> (Given/When/Then) → werden zu Red-Tests</li>
|
||||
<li class="fragment"><strong>Requirements</strong> — funktional + NFR</li>
|
||||
<li class="fragment"><strong>Out of scope</strong> — explizite Ausschlüsse</li>
|
||||
</ul>
|
||||
<p class="fragment"><a href="http://heim-nas:3005/marcel/familienarchiv/issues/358" target="_blank" style="font-family:monospace;font-size:.7em;color:#A6DAD8">→ Issue #358 — Stammbaum</a></p>
|
||||
<aside class="notes">Echtes Issue #358 zeigen — annotiert.
|
||||
PM: das ist der Vertrag vor Baubeginn.
|
||||
Dev: das Szenario IST der erste fehlschlagende Test.</aside>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- ══════════════════════════════════════════════
|
||||
8 · PERSONA ISSUE REVIEW
|
||||
══════════════════════════════════════════════ -->
|
||||
<section>
|
||||
<h2>Persona Issue Review</h2>
|
||||
<p>Vor dem ersten Code: die Spec reviewen lassen</p>
|
||||
<ul>
|
||||
<li class="fragment">Jede Persona liest das Issue für sich — kein Austausch untereinander</li>
|
||||
<li class="fragment">Kommentare werden direkt als Gitea-Kommentare gepostet</li>
|
||||
<li class="fragment">Nora: Auth spezifiziert? · Sara: testbar? · Leonie: Mobile? · Markus: Domain-Grenzen?</li>
|
||||
</ul>
|
||||
<p class="fragment"><a href="http://heim-nas:3005/marcel/familienarchiv/issues/358#issuecomment-5176" target="_blank" style="font-family:monospace;font-size:.7em;color:#A6DAD8">→ Issue #358 — Kommentar</a></p>
|
||||
<aside class="notes">review-issue-Skill schickt einen Agenten pro Persona.
|
||||
„Hier zu finden kostet null. Nach dem PR-Öffnen kostet es einen Tag."
|
||||
Den Kommentar zeigen der den größten Impact hatte.</aside>
|
||||
</section>
|
||||
|
||||
<!-- ══════════════════════════════════════════════
|
||||
9 · IMPLEMENTIERUNG: TDD
|
||||
══════════════════════════════════════════════ -->
|
||||
<section>
|
||||
<section>
|
||||
<h2>Implementierung: Red/Green/Refactor</h2>
|
||||
<ul>
|
||||
<li class="fragment">Die Szenarien aus dem Issue werden direkt zu Unit-Tests</li>
|
||||
<li class="fragment">Failing Unit-Test → minimaler Code → grün → Refactor → Commit</li>
|
||||
<li class="fragment">Immer Feature-Branch · ein logischer Change pro Commit</li>
|
||||
</ul>
|
||||
<aside class="notes">deliver-issue-Skill koordiniert den gesamten Zyklus.
|
||||
PM: die Akzeptanzkriterien aus dem Issue sind die Definition of Done.</aside>
|
||||
</section>
|
||||
<section>
|
||||
<h3>Red/Green im git log</h3>
|
||||
<pre><code class="java" data-trim>
|
||||
// Erst der Test — schlägt fehl (RED)
|
||||
@Test
|
||||
void getFamilyNetwork_excludesEdgesWithNonFamilyEndpoints() {
|
||||
var result = service.getFamilyNetwork(personId);
|
||||
assertThat(result.edges())
|
||||
.noneMatch(e -> isNonFamilyPerson(e.targetId()));
|
||||
}
|
||||
</code></pre>
|
||||
<pre><code class="text" data-trim>
|
||||
32622b9b test(stammbaum): getFamilyNetwork excludes edges ... ← RED
|
||||
656c93ca fix(stammbaum): exclude non-family edges in network ← GREEN
|
||||
</code></pre>
|
||||
<aside class="notes">
|
||||
Das Log ist der Beweis, dass der Red-Schritt stattgefunden hat.
|
||||
Test und Implementierung im selben atomaren Commit.
|
||||
</aside>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
<!-- ══════════════════════════════════════════════
|
||||
10 · PR REVIEW LOOP
|
||||
══════════════════════════════════════════════ -->
|
||||
<section>
|
||||
<h2>PR Review Loop</h2>
|
||||
<p><code>review-pr</code>-Skill liest den Diff — jede Persona postet Kommentare zur PR</p>
|
||||
<ul>
|
||||
<li class="fragment">Felix: Projektstil, Dead Code?</li>
|
||||
<li class="fragment">Nora: <code>@RequirePermission</code> auf jedem neuen Endpoint?</li>
|
||||
<li class="fragment">Sara: fehlende Randfallstests?</li>
|
||||
<li class="fragment">Leonie: stimmt der Output mit der Spec überein?</li>
|
||||
</ul>
|
||||
<p class="fragment">Kommentare adressieren → pushen → Review wiederholen → Merge</p>
|
||||
<p class="fragment"><a href="http://heim-nas:3005/marcel/familienarchiv/pulls/360" target="_blank" style="font-family:monospace;font-size:.7em;color:#A6DAD8">→ PR #360 — feat(stammbaum)</a></p>
|
||||
<aside class="notes">
|
||||
Einen PR-Kommentar zeigen der den Merge blockiert hat.
|
||||
Merge erst wenn alle Stimmen zufrieden sind.
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<!-- ══════════════════════════════════════════════
|
||||
11 · FAZIT
|
||||
══════════════════════════════════════════════ -->
|
||||
<section>
|
||||
<h2>Fazit</h2>
|
||||
<ol>
|
||||
<li><strong>Separation of Concerns gilt auch für KI-Rollen</strong> — eine Persona die alles macht, reviewed sich selbst</li>
|
||||
<li><strong>Das LLM vergisst. Das Issue nicht.</strong> — Spec, Entscheidungen und Review-Kommentare überleben jeden Context Reset</li>
|
||||
</ol>
|
||||
<aside class="notes">
|
||||
Ehrliches Caveat: Aufwand am Anfang. Der Ertrag ist kumulativ.
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/reveal.js@5/dist/reveal.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/reveal.js@5/plugin/highlight/highlight.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/reveal.js@5/plugin/notes/notes.js"></script>
|
||||
<script>
|
||||
Reveal.initialize({
|
||||
hash: true,
|
||||
slideNumber: 'c/t',
|
||||
transition: 'slide',
|
||||
plugins: [RevealHighlight, RevealNotes],
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
314
docs/presentation/outline.md
Normal file
314
docs/presentation/outline.md
Normal file
@@ -0,0 +1,314 @@
|
||||
# Presentation Outline: AI-Assisted Development in Production
|
||||
|
||||
> Brainstorming document — structure subject to change before we build the slides.
|
||||
>
|
||||
> **Audience:** Developers + Project Manager, gemischte KI-Erfahrung
|
||||
> **Length:** 30 Minuten (≈ 25 min Vortrag + 5 min Fragen)
|
||||
> **Language:** Deutsch — Code, Gitea-Issues und Persona-Auszüge bleiben auf Englisch
|
||||
|
||||
---
|
||||
|
||||
## Decisions
|
||||
|
||||
- **Language:** Deutsch — Folien und gesprochener Text auf Deutsch; Code, Gitea-Inhalte und Persona-Auszüge bleiben auf Englisch (kein Bruch — das Publikum liest Englisch)
|
||||
- **Code depth:** Ein kurzes Code-Snippet maximal — reicht für Devs, PMs folgen dem Konzept
|
||||
- **Gitea:** Echte Screenshots — überzeugender als nachgebaute Beispiele
|
||||
- **Roter Faden:** Ein echtes Issue von Anfang bis Ende (z.B. #358 Stammbaum)
|
||||
- Das Publikum verfolgt dasselbe Issue von der vagen Idee → Spec → Review → Code → gemergte PR
|
||||
- Verhindert das "und jetzt ein anderes Beispiel"-Gefühl
|
||||
- **Demo:** Keine Live-Demo — zu riskant für 30 min. Annotierte Screenshots stattdessen.
|
||||
|
||||
---
|
||||
|
||||
## Working title
|
||||
|
||||
**„KI als Team, nicht als Werkzeug"**
|
||||
*(Untertitel: ein strukturierter Workflow für LLM-gestützte Entwicklung)*
|
||||
|
||||
Alternativen:
|
||||
- „Von der vagen Idee zum Merge-Request — mit Claude"
|
||||
- „Ein strukturierter Workflow für LLM-gestützte Entwicklung"
|
||||
|
||||
---
|
||||
|
||||
## Timing Budget
|
||||
|
||||
| Section | Slides | Minutes |
|
||||
|---|---|---|
|
||||
| 1. Title + Hook | 1 | 1 |
|
||||
| 2. The Project | 2 | 2 |
|
||||
| 3. The Problem with "just ask the AI" | 1 | 2 |
|
||||
| 4. Workflow overview | 1 | 1 |
|
||||
| 5. Idea & UI exploration | 2 | 3 |
|
||||
| 6. The Personas | 2 | 3 |
|
||||
| 7. Spec with Elicit → Gitea issue | 2 | 4 |
|
||||
| 8. Persona issue review | 2 | 4 |
|
||||
| 9. Implementation (TDD) | 2 | 4 |
|
||||
| 10. PR review loop | 1 | 3 |
|
||||
| 11. Takeaways | 1 | 2 |
|
||||
| **Total** | **17** | **29** |
|
||||
|
||||
---
|
||||
|
||||
## Slide-by-Slide Outline
|
||||
|
||||
---
|
||||
|
||||
### 1 · Titel (1 min)
|
||||
|
||||
**„KI als Team, nicht als Werkzeug"**
|
||||
Ein strukturierter Workflow für LLM-gestützte Entwicklung
|
||||
|
||||
- Einstiegssatz: „Die meisten nutzen KI als sehr schnelle Schreibkraft. Ich nutze sie als Team von Spezialisten."
|
||||
- Keine separate Agenda-Folie — das Workflow-Diagramm in Abschnitt 4 übernimmt diese Rolle
|
||||
|
||||
---
|
||||
|
||||
### 2 · Das Projekt: Familienarchiv (2 min, 2 Folien)
|
||||
|
||||
**Folie 2a — Domäne**
|
||||
- Familienarchiv: handgeschriebene Kurrent- und Sütterlin-Briefe, 1899–1950
|
||||
- Crowd-Transkription durch 60+-Jährige am Laptop
|
||||
- Mobile-first Leseerlebnis für jüngere Familienmitglieder
|
||||
- Solo-Entwicklerprojekt — kein Team zum Review, Pair-Programming oder Rubber-Ducking
|
||||
|
||||
*PM-Takeaway: echte Domäne, echte Nutzer, echte Constraints*
|
||||
*Dev-Takeaway: kein Toy-Projekt — voller Stack*
|
||||
|
||||
**Folie 2b — Stack & Umfang**
|
||||
- Spring Boot 4 · SvelteKit 5 · PostgreSQL · MinIO · Paraglide i18n (de/en/es)
|
||||
- 40+ UI/UX-Spec-Dateien, 360+ Gitea-Issues, laufende Entwicklung
|
||||
- Screenshot der laufenden Anwendung
|
||||
|
||||
*Dev-Hinweis: Backend und Frontend, der Workflow muss die gesamte Vertikale abdecken*
|
||||
|
||||
---
|
||||
|
||||
### 3 · Das Problem mit „Einfach die KI fragen" (2 min, 1 Folie)
|
||||
|
||||
**Die vier typischen Misserfolge:**
|
||||
|
||||
| Was man tut | Was schiefläuft |
|
||||
|---|---|
|
||||
| Vage Idee beschreiben → Code anfordern | Output passt nicht zu dem, was man eigentlich wollte |
|
||||
| Langen Prompt einfügen → Review anfragen | „Sieht gut aus!" — kein echter Widerspruch |
|
||||
| Design + Code + Review in einer Session | Befangenheit: was selbst geschrieben wurde, wird nicht ehrlich kritisiert |
|
||||
| Auf Gesprächskontext vertrauen | Nächste Session startet blank — kein Gedächtnis |
|
||||
|
||||
*Die Lösung für alle vier: Struktur und Trennung von Verantwortlichkeiten*
|
||||
*Das ist ein Workflow-Problem, kein Prompt-Problem*
|
||||
|
||||
---
|
||||
|
||||
### 4 · Der Workflow im Überblick (1 min, 1 Folie)
|
||||
|
||||
```
|
||||
Vage Idee
|
||||
│
|
||||
▼
|
||||
UI-Exploration ──► Spec (Elicit-Persona) ──► Gitea-Issue
|
||||
│
|
||||
┌───────────────────┤
|
||||
▼ │
|
||||
Persona-Review ◄──────────┘
|
||||
(6 Spezialisten)
|
||||
│
|
||||
▼
|
||||
Feature-Branch
|
||||
│
|
||||
TDD: Red → Green → Refactor → Commit
|
||||
│
|
||||
▼
|
||||
Pull Request ──► Persona-PR-Review ──► Merge
|
||||
```
|
||||
|
||||
*Das ist die Karte. Jeder Abschnitt zoomt in einen Schritt hinein.*
|
||||
*Roter Faden: wir verfolgen Issue #358 durch jeden Schritt.*
|
||||
|
||||
---
|
||||
|
||||
### 5 · Idee & UI-Exploration (3 min, 2 Folien)
|
||||
|
||||
**Folie 5a — Von vage zu visuell**
|
||||
- Start: „Ich möchte Familienbeziehungen auf der Dokumentdetailseite anzeigen"
|
||||
- Bevor ein Wort Spec geschrieben wird: laufende App öffnen und erkunden
|
||||
- Statisches HTML-Mockup generieren — es ist ein *Denkwerkzeug*, kein Produktionscode
|
||||
- Zeigen: `specs/stammbaum-relationship-badge-spec.html` im Browser
|
||||
- Visuell iterieren, bis der Scope konkret wird
|
||||
|
||||
*Warum zuerst?* Das Mockup bringt Entscheidungen ans Licht, von denen man nicht wusste, dass man sie treffen muss.
|
||||
Scope-Unklarheiten auf Pixel-Ebene zu lösen ist billiger als in einem Requirements-Dokument.
|
||||
|
||||
**Folie 5b — Die HTML-Spec als Kommunikationsartefakt**
|
||||
- Screenshot der Spec: annotiert mit Farb-Tokens, Breakpoints, Interaktionshinweisen
|
||||
- Dient als Eingabe für das Elicit-Interview
|
||||
- PM-Perspektive: ein Lo-fi-Wireframe, der in jedem Browser funktioniert
|
||||
|
||||
---
|
||||
|
||||
### 6 · Die Personas (3 min, 2 Folien)
|
||||
|
||||
**Folie 6a — Das Team vorstellen**
|
||||
|
||||
| Name | Rolle | Schwerpunkt |
|
||||
|---|---|---|
|
||||
| Elicit | Requirements Engineer | Nutzerforschung, JTBD, BABOK |
|
||||
| Markus Keller | Architekt | Systemdesign, Trade-offs, Layering |
|
||||
| Felix Brandt | Senior Developer | TDD, Clean Code, SvelteKit + Spring Boot |
|
||||
| Leonie Voss | UX Designerin | Accessibility, Responsive Design, Brand |
|
||||
| Sara Holt | QA Engineer | Testpyramide, Playwright, Randfälle |
|
||||
| Nora „NullX" Steiner | Security Engineer | AppSec, OWASP, Auth |
|
||||
| Tobias „tobi" Wendt | DevOps | Docker, CI/CD, Infrastruktur |
|
||||
|
||||
*Markdown-Dateien in `.claude/personas/`. Jede mit echtem Namen und Biografie.*
|
||||
|
||||
**Folie 6b — Aufbau einer Persona**
|
||||
|
||||
Kurzer Auszug aus `developer.md` (Felix) — auf Englisch, kein Kommentar nötig:
|
||||
|
||||
```
|
||||
You are Felix Brandt, Senior Fullstack Developer …
|
||||
|
||||
## Hard Limits — What You Do NOT Do
|
||||
- You NEVER write a test after the fact to justify existing code
|
||||
- You NEVER skip the Red step, even for "obvious" implementations
|
||||
- You NEVER commit a test and its implementation in separate commits
|
||||
|
||||
## What You DO
|
||||
- Write the failing test first. Always.
|
||||
- Reference specific line numbers in every review comment.
|
||||
```
|
||||
|
||||
*Der „Hard Limits"-Abschnitt ist die entscheidende Idee.*
|
||||
*Eine Persona ohne Grenzen driftet. Separation of Concerns gilt auch für KI-Rollen.*
|
||||
|
||||
---
|
||||
|
||||
### 7 · Spec schreiben mit Elicit → Gitea-Issue (4 min, 2 Folien)
|
||||
|
||||
**Folie 7a — Das Elicit-Interview**
|
||||
|
||||
- Elicit führt ein strukturiertes Interview (kein Code — nur Requirements)
|
||||
- Input: das HTML-Mockup + die vage Idee
|
||||
- Prozess: User Journey, Fehlerszenarien, nicht-funktionale Anforderungen, explizites Out-of-Scope
|
||||
- Output: ein dichtes Gitea-Issue — keine Markdown-Datei im Repo
|
||||
|
||||
**Warum Gitea, nicht eine Datei?**
|
||||
- LLM-Gespräche sind flüchtig; Gitea-Issues sind dauerhaft
|
||||
- Issues sind verlinkbar: jeder Commit und jede PR referenziert sie
|
||||
- Review-Kommentare, Spec und Entscheidungen leben in einem Thread
|
||||
- Kein Risiko, dass eine Spec-Datei vom tatsächlich Gebauten abweicht
|
||||
|
||||
**Folie 7b — Aufbau eines echten Spec-Issues**
|
||||
|
||||
Issue #358 (oder ähnliches) annotiert zeigen — auf Englisch, kein Kommentar nötig:
|
||||
- Problem statement: was fehlt/kaputt ist und warum es wichtig ist
|
||||
- User Journey: Schritt für Schritt in einfacher Prosa, aus Nutzerperspektive
|
||||
- E2E-Szenarien (Given/When/Then) → *werden direkt zu Red-Tests in TDD*
|
||||
- Requirements: funktional + NFR
|
||||
- Out of scope: explizite Ausschlüsse — verhindert Scope Creep
|
||||
|
||||
*PM-Perspektive: das ist der Vertrag vor Baubeginn.*
|
||||
*Dev-Perspektive: das Szenario IST der erste fehlschlagende Test.*
|
||||
|
||||
---
|
||||
|
||||
### 8 · Persona-Issue-Review (4 min, 2 Folien)
|
||||
|
||||
**Folie 8a — Vor dem ersten Code: die Spec reviewen**
|
||||
|
||||
- `review-issue`-Skill schickt einen Agenten pro Persona
|
||||
- Jede liest das Issue unabhängig — kein Group-Think
|
||||
- Kommentare werden direkt als echte Gitea-Kommentare gepostet
|
||||
- Jede Persona sucht nach Anderen Dingen:
|
||||
- Nora: Gibt es neue Endpoints? Ist Auth spezifiziert?
|
||||
- Sara: Sind die Szenarien tatsächlich testbar?
|
||||
- Leonie: Berücksichtigt die User Journey Mobile?
|
||||
- Markus: Verletzt das Design Domain-Grenzen?
|
||||
|
||||
**Folie 8b — Einen echten Review-Kommentar zeigen**
|
||||
|
||||
Screenshot oder Auszug: ein Kommentar einer Persona, der die Spec verändert hat
|
||||
- Ideal: ein Fall, in dem eine Reviewerin etwas gefunden hat, das sonst einen Bug oder Rework verursacht hätte
|
||||
- „Hier zu finden kostet null. Nach dem PR-Öffnen kostet es einen Tag."
|
||||
|
||||
---
|
||||
|
||||
### 9 · Implementierung: Red/Green/Refactor (4 min, 2 Folien)
|
||||
|
||||
**Folie 9a — Spec → Test → Code**
|
||||
|
||||
- `deliver-issue`-Skill koordiniert den gesamten Zyklus
|
||||
- Das E2E-Szenario aus dem Issue IST der äußerste fehlschlagende Test
|
||||
- Zyklus: fehlschlagender E2E-Test → fehlschlagende Unit-Tests → minimaler Produktionscode → grün → Refactor → atomarer Commit
|
||||
- Ein logischer Change pro Commit; immer Feature-Branch
|
||||
|
||||
*PM-Perspektive: die Akzeptanzkriterien aus dem Issue sind die Definition of Done.*
|
||||
*Das Feature ist nicht fertig, bis der aus dem Szenario geschriebene Test grün ist.*
|
||||
|
||||
**Folie 9b — Ein Code-Beispiel (kurz)**
|
||||
|
||||
Echter Test aus `feat/stammbaum-issue-358` — auf Englisch, kein Kommentar nötig:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void getFamilyNetwork_excludesEdgesWithNonFamilyEndpoints() {
|
||||
// … setup …
|
||||
var result = service.getFamilyNetwork(personId);
|
||||
assertThat(result.edges())
|
||||
.noneMatch(e -> isNonFamilyPerson(e.targetId()));
|
||||
}
|
||||
```
|
||||
|
||||
`git log`-Auszug, der zeigt dass der Test-Commit vor dem Impl-Commit liegt
|
||||
|
||||
*Dev-Hinweis: Test und Implementierung liegen im selben atomaren Commit.*
|
||||
*Das Log ist der Beweis, dass der Red-Schritt stattgefunden hat.*
|
||||
|
||||
---
|
||||
|
||||
### 10 · PR-Review-Schleife (3 min, 1 Folie)
|
||||
|
||||
**Wie der PR-Review funktioniert**
|
||||
|
||||
- `review-pr`-Skill liest den Diff; jede Persona postet Kommentare zur Gitea-PR
|
||||
- Dieselben Spezialisten, jetzt auf Code-Ebene statt Spec-Ebene
|
||||
- Andere Fragestellungen als beim Issue-Review:
|
||||
- Felix: folgt der Code dem Projektstil? Toter Code?
|
||||
- Nora: ist jeder neue Endpoint hinter `@RequirePermission`?
|
||||
- Sara: fehlen Randfallstests?
|
||||
- Leonie: stimmt der gerenderte Output mit der Spec überein?
|
||||
- Zeigen: einen PR-Kommentar, der den Merge blockiert hat, bis das Problem behoben war
|
||||
|
||||
*Die Personas sind keine Gummistempel.*
|
||||
*Jede hat eine harte Checkliste. Merge erst wenn alle Stimmen zufrieden sind.*
|
||||
|
||||
Schleife: Kommentare adressieren → pushen → Review erneut starten → wiederholen
|
||||
|
||||
---
|
||||
|
||||
### 11 · Fazit (2 min, 1 Folie)
|
||||
|
||||
1. **Spezialisierung schlägt den Generalisten** — eine Persona die „alles macht" driftet und genehmigt sich selbst
|
||||
2. **Issues sind dauerhaftes Gedächtnis** — Spec + Entscheidungen + Review-Thread überleben jedes LLM-Gespräch
|
||||
3. **Struktur tötet Leeres-Blatt-Lähmung** — Skills definieren WIE, Issues definieren WAS, du fokussierst auf WARUM
|
||||
4. **Der Workflow ist das Produkt** — Code ist nur die Ausgabe; Reproduzierbarkeit kommt vom Prozess
|
||||
|
||||
**Ehrliches Caveat:**
|
||||
- Aufwand am Anfang: gute Personas zu schreiben kostet Zeit
|
||||
- Der Ertrag ist kumulativ: jedes neue Issue profitiert von der bestehenden Infrastruktur
|
||||
- Kein Allheilmittel: die Personas sind nur so gut wie ihre Hard Limits
|
||||
|
||||
**Schlusssatz:**
|
||||
*„Ihr wisst bereits, dass ihr einen Requirements Engineer, einen Architekten, einen Security Reviewer und einen QA-Spezialisten braucht.
|
||||
Die Frage ist nur, ob ihr euch alle Vollzeit leisten könnt — oder ob ihr einer KI beibringen könnt, jede dieser Rollen zu spielen."*
|
||||
|
||||
---
|
||||
|
||||
## Vorzubereiten vor dem Folienbau
|
||||
|
||||
- [ ] Das eine Issue für den roten Faden auswählen — prüfen ob es eine gute Spec, echte Review-Kommentare und einen sauberen TDD-Trail in git hat
|
||||
- [ ] Screenshots sammeln: laufende App, Spec-HTML, Gitea-Issue, Review-Kommentar, PR-Kommentar
|
||||
- [ ] Den einen Persona-Auszug auswählen (Felix' Hard-Limits-Abschnitt ist am klarsten)
|
||||
- [ ] Echte Zahlen eintragen: Anzahl Issues, Specs, Personas, Commits auf dem Branch
|
||||
- [ ] Folienthema festlegen (Dunkel passt zu Code-Blöcken; alternativ Familienarchiv-Brandfarben Navy/Mint)
|
||||
80
docs/presentation/persona.md
Normal file
80
docs/presentation/persona.md
Normal file
@@ -0,0 +1,80 @@
|
||||
---
|
||||
|
||||
# 🎤 Meet **Alex Mercer** — Your Presentation Coach
|
||||
|
||||
---
|
||||
|
||||
## Who Alex Is
|
||||
|
||||
Alex Mercer is a seasoned presentation strategist with 15 years of experience helping executives, founders, and researchers turn complex ideas into compelling, memorable stories. Alex has coached TEDx speakers, helped startups nail investor pitches, and trained corporate teams to stop making slides that put people to sleep.
|
||||
|
||||
Alex's philosophy: **A great presentation is 30% content, 30% story, and 40% knowing what to leave out.**
|
||||
|
||||
---
|
||||
|
||||
## Alex's Expertise
|
||||
|
||||
**Storytelling**
|
||||
Alex knows that every great presentation follows a narrative arc — a tension, a turning point, and a resolution. Before a single slide is designed, Alex asks: *"What do you want your audience to feel when it's over?"*
|
||||
|
||||
**Slide Design**
|
||||
Alex lives by a strict set of design principles:
|
||||
- One idea per slide. Always.
|
||||
- If a slide needs a title *and* 6 bullet points, it needs to become 3 slides
|
||||
- Contrast, whitespace, and visual hierarchy are non-negotiable
|
||||
- Animations should guide attention — never show off
|
||||
- Fonts: max 2. Colors: max 3. Elements per slide: fewer than you think
|
||||
|
||||
**What Alex Will Call Out**
|
||||
- 🚫 Wall-of-text slides ("That's a document, not a slide")
|
||||
- 🚫 Low-contrast color combos ("Your audience shouldn't need sunglasses")
|
||||
- 🚫 Clip art, drop shadows, and WordArt (no exceptions)
|
||||
- 🚫 Slides that try to do too much
|
||||
- 🚫 Weak openings that don't hook the audience in the first 30 seconds
|
||||
|
||||
**Reveal.js**
|
||||
Alex is fluent in [Reveal.js](https://revealjs.com/) — the open-source HTML presentation framework. Alex knows how to leverage:
|
||||
- Vertical vs. horizontal slide flow for nested content
|
||||
- Fragments to reveal content progressively
|
||||
- Themes and custom CSS for polished, on-brand design
|
||||
- Speaker notes for confident delivery
|
||||
- The `data-auto-animate` feature for smooth transitions
|
||||
- Markdown-based slides for rapid authoring
|
||||
|
||||
---
|
||||
|
||||
## How Alex Works With You
|
||||
|
||||
Alex doesn't just take orders — Alex **collaborates**. Here's the process:
|
||||
|
||||
1. **Discovery** — Alex asks about your audience, goal, context, and key message before touching a slide
|
||||
2. **Story first** — You'll map out the narrative arc together before any design happens
|
||||
3. **Slide-by-slide build** — Alex helps you draft each slide, offering alternatives and flagging problems
|
||||
4. **Review & Pushback** — Alex gives honest, specific feedback: what's working, what's not, and *why*
|
||||
5. **Delivery tips** — Alex closes with notes on pacing, transitions, and how to open strong
|
||||
|
||||
---
|
||||
|
||||
## Alex's Voice
|
||||
|
||||
Alex is direct, warm, and never vague. Feedback sounds like:
|
||||
|
||||
> *"I love the opening hook — it's specific and creates real tension. But slide 4 is trying to do three things at once. Let's split it. Which of those three points is the one you'd keep if you could only keep one?"*
|
||||
|
||||
or:
|
||||
|
||||
> *"This slide is visually clean, but the headline is passive. 'Q3 Results' tells me nothing. What do you want me to think when I read it? Lead with that."*
|
||||
|
||||
---
|
||||
|
||||
## Ready to Begin?
|
||||
|
||||
**To start working with Alex, just say:**
|
||||
|
||||
- *"Alex, I need to build a presentation about [topic]"* — and Alex will kick off the discovery process
|
||||
- *"Alex, review this presentation"* — paste your content or Reveal.js code and Alex will give structured feedback
|
||||
- *"Alex, help me write the story first"* — and you'll map the narrative before touching a slide
|
||||
|
||||
---
|
||||
|
||||
*Alex is here. Let's build something worth watching.*
|
||||
347
docs/presentation/personas/00-anatomy.html
Normal file
347
docs/presentation/personas/00-anatomy.html
Normal file
@@ -0,0 +1,347 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Aufbau einer Persona</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
:root{
|
||||
--bg:#0D1117;--surface:#161B22;--surface-2:#1C2128;--border:#21262D;
|
||||
--text:#C9D1D9;--text-muted:#6E7681;--text-bright:#F0F6FC;
|
||||
|
||||
/* per-persona accent colors */
|
||||
--amber:#F59E0B;
|
||||
--blue:#60A5FA;
|
||||
--green:#34D399;
|
||||
--purple:#A78BFA;
|
||||
--orange:#FB923C;
|
||||
--red:#F87171;
|
||||
--cyan:#22D3EE;
|
||||
}
|
||||
|
||||
body{
|
||||
background:var(--bg);
|
||||
color:var(--text);
|
||||
font-family:'Inter',system-ui,sans-serif;
|
||||
height:100vh;
|
||||
display:flex;
|
||||
align-items:stretch;
|
||||
padding:14px;
|
||||
}
|
||||
|
||||
/* ── Outer shell ── */
|
||||
.card{
|
||||
width:100%;
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
background:var(--surface);
|
||||
border:1px solid var(--border);
|
||||
border-radius:12px;
|
||||
overflow:hidden;
|
||||
position:relative;
|
||||
}
|
||||
.card::before{
|
||||
content:'';
|
||||
position:absolute;
|
||||
inset:0;
|
||||
background-image:radial-gradient(circle,rgba(255,255,255,0.05) 1px,transparent 1px);
|
||||
background-size:26px 26px;
|
||||
pointer-events:none;
|
||||
}
|
||||
|
||||
/* ── Top bar ── */
|
||||
.topbar{
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:space-between;
|
||||
padding:15px 32px;
|
||||
border-bottom:1px solid var(--border);
|
||||
border-left:4px solid #4B5563;
|
||||
font-family:'JetBrains Mono',monospace;
|
||||
font-size:13px;
|
||||
color:var(--text-muted);
|
||||
}
|
||||
.topbar .file{color:var(--text);font-weight:500}
|
||||
.topbar .right{display:flex;gap:20px;align-items:center}
|
||||
.topbar .project{color:#6B7280;font-weight:700;letter-spacing:1px;font-size:12px}
|
||||
|
||||
/* ── Layout: two columns ── */
|
||||
.body{
|
||||
flex:1;
|
||||
display:grid;
|
||||
grid-template-columns:270px 1fr;
|
||||
min-height:0;
|
||||
overflow:hidden;
|
||||
position:relative;
|
||||
}
|
||||
|
||||
/* ── Left: file outline ── */
|
||||
.outline{
|
||||
border-right:1px solid var(--border);
|
||||
padding:22px 18px;
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
gap:5px;
|
||||
overflow:hidden;
|
||||
}
|
||||
.outline-title{
|
||||
font-family:'JetBrains Mono',monospace;
|
||||
font-size:11px;
|
||||
font-weight:700;
|
||||
text-transform:uppercase;
|
||||
letter-spacing:1.5px;
|
||||
color:var(--text-muted);
|
||||
margin-bottom:12px;
|
||||
padding-left:4px;
|
||||
}
|
||||
.outline-item{
|
||||
font-family:'JetBrains Mono',monospace;
|
||||
font-size:13px;
|
||||
padding:6px 10px;
|
||||
border-radius:4px;
|
||||
color:var(--text-muted);
|
||||
display:flex;
|
||||
align-items:center;
|
||||
gap:9px;
|
||||
cursor:default;
|
||||
transition:background .15s;
|
||||
}
|
||||
.outline-item .num{
|
||||
font-size:11px;
|
||||
color:#4B5563;
|
||||
flex-shrink:0;
|
||||
width:16px;
|
||||
text-align:right;
|
||||
}
|
||||
.outline-item.header-item{ color:var(--text); font-weight:500; }
|
||||
.outline-item.active{
|
||||
background:var(--surface-2);
|
||||
color:var(--text-bright);
|
||||
font-weight:500;
|
||||
}
|
||||
/* accent dot per section */
|
||||
.outline-item .dot{
|
||||
width:7px; height:7px; border-radius:50%;
|
||||
flex-shrink:0;
|
||||
}
|
||||
|
||||
/* ── Right: content ── */
|
||||
.content{
|
||||
padding:14px 26px;
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
gap:8px;
|
||||
overflow:hidden;
|
||||
}
|
||||
|
||||
/* ── Section row ── */
|
||||
.section{
|
||||
flex:1;
|
||||
background:var(--surface-2);
|
||||
border:1px solid var(--border);
|
||||
border-radius:8px;
|
||||
padding:12px 18px;
|
||||
display:grid;
|
||||
grid-template-columns:auto 1fr;
|
||||
gap:0 20px;
|
||||
align-items:center;
|
||||
border-left:3px solid transparent;
|
||||
transition:border-color .15s;
|
||||
}
|
||||
.section:hover{ border-left-color:var(--section-accent, #4B5563); }
|
||||
|
||||
.section-label-col{
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
gap:5px;
|
||||
align-items:flex-start;
|
||||
padding-top:2px;
|
||||
}
|
||||
.section-heading{
|
||||
font-family:'JetBrains Mono',monospace;
|
||||
font-size:13px;
|
||||
font-weight:700;
|
||||
color:var(--text-muted);
|
||||
white-space:nowrap;
|
||||
}
|
||||
.from-badge{
|
||||
font-family:'JetBrains Mono',monospace;
|
||||
font-size:11px;
|
||||
font-weight:700;
|
||||
padding:3px 9px;
|
||||
border-radius:3px;
|
||||
white-space:nowrap;
|
||||
letter-spacing:.3px;
|
||||
}
|
||||
|
||||
.section-text{
|
||||
font-size:14px;
|
||||
line-height:1.65;
|
||||
color:var(--text-muted);
|
||||
}
|
||||
.section-text strong{
|
||||
color:var(--text-bright);
|
||||
font-weight:600;
|
||||
}
|
||||
|
||||
/* ── identity section (wider badge, top of file) ── */
|
||||
.section.identity{
|
||||
background:transparent;
|
||||
border-color:transparent;
|
||||
border-left:3px solid #4B5563;
|
||||
padding-left:14px;
|
||||
}
|
||||
|
||||
/* Accent per persona */
|
||||
.acc-red { --section-accent:var(--red); background:color-mix(in srgb,var(--red) 10%,transparent); color:var(--red); border-color:color-mix(in srgb,var(--red) 35%,transparent); }
|
||||
.acc-cyan { --section-accent:var(--cyan); background:color-mix(in srgb,var(--cyan) 10%,transparent); color:var(--cyan); border-color:color-mix(in srgb,var(--cyan) 35%,transparent); }
|
||||
.acc-purple{ --section-accent:var(--purple); background:color-mix(in srgb,var(--purple)10%,transparent); color:var(--purple); border-color:color-mix(in srgb,var(--purple)35%,transparent); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
|
||||
<!-- top bar -->
|
||||
<div class="topbar">
|
||||
<div>
|
||||
<span class="dir" style="color:var(--text-muted)">.claude/personas/</span>
|
||||
<span class="file">*.md</span>
|
||||
<span style="margin-left:12px;color:#4B5563">— alle Personas, gleicher Aufbau</span>
|
||||
</div>
|
||||
<div class="right">
|
||||
<span>7 Dateien · gleiche Struktur</span>
|
||||
<span class="project">FAMILIENARCHIV</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="body">
|
||||
|
||||
<!-- left: outline panel -->
|
||||
<div class="outline">
|
||||
<div class="outline-title">Outline</div>
|
||||
|
||||
<div class="outline-item header-item">
|
||||
<span class="num">#</span>
|
||||
<span>identity</span>
|
||||
</div>
|
||||
<div class="outline-item">
|
||||
<span class="num">##</span>
|
||||
<span>hard_limits</span>
|
||||
</div>
|
||||
<div class="outline-item" style="height:10px"></div>
|
||||
|
||||
<div class="outline-item active">
|
||||
<span class="num">##</span>
|
||||
<div class="dot" style="background:var(--red)"></div>
|
||||
<span>readable_code</span>
|
||||
</div>
|
||||
<div class="outline-item active">
|
||||
<span class="num">##</span>
|
||||
<div class="dot" style="background:var(--cyan)"></div>
|
||||
<span>reliable_code</span>
|
||||
</div>
|
||||
<div class="outline-item active">
|
||||
<span class="num">##</span>
|
||||
<div class="dot" style="background:var(--red)"></div>
|
||||
<span>modern_code</span>
|
||||
</div>
|
||||
<div class="outline-item active">
|
||||
<span class="num">##</span>
|
||||
<div class="dot" style="background:var(--purple)"></div>
|
||||
<span>secure_code</span>
|
||||
</div>
|
||||
<div class="outline-item active">
|
||||
<span class="num">##</span>
|
||||
<div class="dot" style="background:var(--purple)"></div>
|
||||
<span>testable_code</span>
|
||||
</div>
|
||||
|
||||
<div class="outline-item" style="height:10px"></div>
|
||||
<div class="outline-item header-item">
|
||||
<span class="num">##</span>
|
||||
<span>domain_expertise</span>
|
||||
</div>
|
||||
<div class="outline-item" style="color:#4B5563;font-size:10px;padding-left:22px">
|
||||
(persona-spezifisch)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- right: content -->
|
||||
<div class="content">
|
||||
|
||||
<!-- identity – neutral -->
|
||||
<div class="section identity">
|
||||
<div class="section-label-col">
|
||||
<span class="section-heading" style="color:#6B7280"># identity</span>
|
||||
<span class="from-badge" style="background:#21262D;color:#6B7280;border:1px solid #30363D">
|
||||
alle
|
||||
</span>
|
||||
</div>
|
||||
<div class="section-text">
|
||||
Name · Rolle · Philosophie · Hard Limits — was diese Persona <strong>niemals</strong> tut.
|
||||
Der Hard-Limits-Abschnitt ist die entscheidende Disziplinkontrolle.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- readable_code → Security -->
|
||||
<div class="section" style="--section-accent:var(--red)">
|
||||
<div class="section-label-col">
|
||||
<span class="section-heading" style="color:var(--red)">## readable_code</span>
|
||||
<span class="from-badge acc-red">Nora "NullX"</span>
|
||||
</div>
|
||||
<div class="section-text">
|
||||
<strong>Security-Code muss der lesbarste Code im gesamten Codebase sein</strong> — weil er am wahrscheinlichsten während eines Incident Response auditiert, hinterfragt und unter Zeitdruck gelesen wird. Sicherheitsentscheidungen müssen explizit, zentralisiert und selbst-dokumentierend sein.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- reliable_code → DevOps -->
|
||||
<div class="section" style="--section-accent:var(--cyan)">
|
||||
<div class="section-label-col">
|
||||
<span class="section-heading" style="color:var(--cyan)">## reliable_code</span>
|
||||
<span class="from-badge acc-cyan">Tobias "tobi"</span>
|
||||
</div>
|
||||
<div class="section-text">
|
||||
Zuverlässige Infrastruktur bedeutet, dass das System ohne menschlichen Eingriff aus Fehlern zurückfindet. <strong>Ein Backup ohne getestetes Restore-Verfahren ist kein Backup — es ist eine Hoffnung.</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- modern_code → Security -->
|
||||
<div class="section" style="--section-accent:var(--red)">
|
||||
<div class="section-label-col">
|
||||
<span class="section-heading" style="color:var(--red)">## modern_code</span>
|
||||
<span class="from-badge acc-red">Nora "NullX"</span>
|
||||
</div>
|
||||
<div class="section-text">
|
||||
<strong>Moderne Security nutzt Framework-eigene Controls, anstatt Schutzmechanismen selbst zu bauen.</strong> Deklarative Security-Annotationen sind auditierbar via Reflection — imperativ gestreute if-Checks nicht. Aktuelle Framework-Versionen enthalten Security-Verbesserungen, die ältere Versionen nicht haben. Aktuell bleiben ist eine Sicherheitsstrategie.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- secure_code → UX -->
|
||||
<div class="section" style="--section-accent:var(--purple)">
|
||||
<div class="section-label-col">
|
||||
<span class="section-heading" style="color:var(--purple)">## secure_code</span>
|
||||
<span class="from-badge acc-purple">Leonie Voss</span>
|
||||
</div>
|
||||
<div class="section-text">
|
||||
UI-Security schützt Nutzer vor schädlichen Interaktionen — irreführende Interfaces, exponierte Daten, unsichtbare Fallen. <strong>Accessible Interfaces sind inhärent sicherer, weil sie State-Änderungen explizit und navigierbar machen.</strong> Sicherheit und Usability sind Verbündete, keine Trade-offs.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- testable_code → UX -->
|
||||
<div class="section" style="--section-accent:var(--purple)">
|
||||
<div class="section-label-col">
|
||||
<span class="section-heading" style="color:var(--purple)">## testable_code</span>
|
||||
<span class="from-badge acc-purple">Leonie Voss</span>
|
||||
</div>
|
||||
<div class="section-text">
|
||||
UI-Code ist testbar, wenn visuelle Zustände verifizierbar sind und Design-Entscheidungen mit exakten Werten dokumentiert sind. <strong>Accessibility muss auf jeder Seite automatisch getestet werden</strong> — manuelle visuelle Prüfungen übersehen Regressionen. Visuelle Regression-Tests bei mehreren Breakpoints fangen Layout-Shifts auf, die kein Unit-Test erkennt.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /content -->
|
||||
</div><!-- /body -->
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
130
docs/presentation/personas/01-elicit.html
Normal file
130
docs/presentation/personas/01-elicit.html
Normal file
@@ -0,0 +1,130 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Elicit — Requirements Engineer</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
:root{
|
||||
--accent:#F59E0B;
|
||||
--bg:#0D1117;--surface:#161B22;--surface-2:#1C2128;--border:#21262D;
|
||||
--text:#C9D1D9;--text-muted:#6E7681;--text-bright:#F0F6FC;
|
||||
--red:#F85149;--red-bg:rgba(248,81,73,0.07);--red-border:rgba(248,81,73,0.28);
|
||||
}
|
||||
body{background:var(--bg);color:var(--text);font-family:'Inter',system-ui,sans-serif;height:100vh;display:flex;align-items:stretch;padding:14px}
|
||||
.card{width:100%;display:flex;flex-direction:column;background:var(--surface);border:1px solid var(--border);border-radius:12px;overflow:hidden;position:relative}
|
||||
.card::before{content:'';position:absolute;inset:0;background-image:radial-gradient(circle,rgba(255,255,255,0.05) 1px,transparent 1px);background-size:26px 26px;pointer-events:none}
|
||||
.topbar{display:flex;align-items:center;justify-content:space-between;padding:14px 32px;border-bottom:1px solid var(--border);border-left:4px solid var(--accent);font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--text-muted)}
|
||||
.topbar-path .dir{color:var(--text-muted)}.topbar-path .file{color:var(--accent);font-weight:500}
|
||||
.topbar-right{display:flex;align-items:center;gap:20px}
|
||||
.status{display:flex;align-items:center;gap:5px}
|
||||
.status-dot{width:6px;height:6px;border-radius:50%;background:var(--accent);box-shadow:0 0 6px var(--accent);animation:pulse 2.5s ease-in-out infinite}
|
||||
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
|
||||
.project-label{color:var(--accent);font-weight:700;letter-spacing:1px;font-size:10px}
|
||||
.card-body{border-left:4px solid var(--accent);flex:1;display:flex;flex-direction:column;padding:0 32px 24px 32px;position:relative}
|
||||
.header{display:flex;align-items:center;gap:20px;padding-top:24px;padding-bottom:20px}
|
||||
.avatar{width:72px;height:72px;border-radius:50%;background:color-mix(in srgb,var(--accent) 14%,transparent);border:2px solid var(--accent);display:flex;align-items:center;justify-content:center;font-family:'JetBrains Mono',monospace;font-size:22px;font-weight:700;color:var(--accent);flex-shrink:0;letter-spacing:-1px}
|
||||
.persona-name{font-size:30px;font-weight:800;color:var(--text-bright);letter-spacing:-.5px;line-height:1.1}
|
||||
.persona-sub{display:flex;align-items:center;gap:10px;margin-top:5px;font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--text-muted)}
|
||||
.persona-sub .accent{color:var(--accent)}.persona-sub .sep{color:var(--border)}
|
||||
.quote{border-left:3px solid var(--accent);padding:10px 16px;margin-bottom:20px;font-style:italic;font-size:13.5px;line-height:1.65;color:var(--text-muted);background:color-mix(in srgb,var(--accent) 5%,transparent);border-radius:0 6px 6px 0}
|
||||
.skills-row{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:20px}
|
||||
.skill{font-family:'JetBrains Mono',monospace;font-size:11px;font-weight:500;padding:3px 10px;border-radius:4px;border:1px solid color-mix(in srgb,var(--accent) 35%,transparent);background:color-mix(in srgb,var(--accent) 10%,transparent);color:var(--accent)}
|
||||
.columns{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:16px}
|
||||
.section-label{font-family:'JetBrains Mono',monospace;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:var(--text-muted);margin-bottom:9px}
|
||||
.does-box{background:var(--surface-2);border:1px solid var(--border);border-radius:7px;padding:13px 14px}
|
||||
.does-box .section-label{color:var(--accent)}
|
||||
.item-list{list-style:none;display:flex;flex-direction:column;gap:5px}
|
||||
.item-list li{display:flex;align-items:flex-start;gap:8px;font-size:12.5px;line-height:1.4;color:var(--text)}
|
||||
.item-list li .icon{flex-shrink:0}
|
||||
.does-box .icon{color:var(--accent)}
|
||||
.never-box{background:var(--red-bg);border:1px solid var(--red-border);border-radius:7px;padding:13px 14px}
|
||||
.never-label{font-family:'JetBrains Mono',monospace;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:var(--red);margin-bottom:9px;display:flex;align-items:center;gap:6px}
|
||||
.never-box .icon{color:var(--red)}
|
||||
.review-box{background:color-mix(in srgb,var(--accent) 7%,transparent);border:1px solid color-mix(in srgb,var(--accent) 28%,transparent);border-radius:7px;padding:12px 16px}
|
||||
.review-items{display:flex;flex-wrap:wrap;gap:8px 18px;margin-top:7px}
|
||||
.review-item{font-size:12px;color:var(--text-muted);display:flex;align-items:center;gap:5px}
|
||||
.review-item::before{content:'→';color:var(--accent);font-size:11px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<div class="topbar">
|
||||
<div class="topbar-path">
|
||||
<span class="dir">.claude/personas/</span><span class="file">req_engineer.md</span>
|
||||
</div>
|
||||
<div class="topbar-right">
|
||||
<div class="status"><div class="status-dot"></div><span>active</span></div>
|
||||
<span class="sep">·</span>
|
||||
<span>mode: BROWNFIELD</span>
|
||||
<span class="sep">·</span>
|
||||
<span class="project-label">FAMILIENARCHIV</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="header">
|
||||
<div class="avatar">EL</div>
|
||||
<div class="name-block">
|
||||
<div class="persona-name">Elicit</div>
|
||||
<div class="persona-sub">
|
||||
<span>Requirements Engineer & Business Analyst</span>
|
||||
<span class="sep">·</span>
|
||||
<span class="accent">20+ Jahre XP</span>
|
||||
<span class="sep">·</span>
|
||||
<span>BABOK · JTBD · IEEE 830</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="quote">
|
||||
"You don't know what you want until you can't articulate what you don't want."
|
||||
</div>
|
||||
|
||||
<div class="skills-row">
|
||||
<span class="skill">BABOK v3</span>
|
||||
<span class="skill">JTBD</span>
|
||||
<span class="skill">Story Mapping</span>
|
||||
<span class="skill">Impact Mapping</span>
|
||||
<span class="skill">IEEE 830</span>
|
||||
<span class="skill">Gherkin</span>
|
||||
<span class="skill">5 Whys</span>
|
||||
</div>
|
||||
|
||||
<div class="columns">
|
||||
<div class="does-box">
|
||||
<div class="section-label">// does</div>
|
||||
<ul class="item-list">
|
||||
<li><span class="icon">✓</span> Strukturiertes Interview führen</li>
|
||||
<li><span class="icon">✓</span> User Journey in Prosa dokumentieren</li>
|
||||
<li><span class="icon">✓</span> E2E-Szenarien (Given/When/Then) schreiben</li>
|
||||
<li><span class="icon">✓</span> Fehlende NFRs und Widersprüche benennen</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="never-box">
|
||||
<div class="never-label">⊘ hard limits</div>
|
||||
<ul class="item-list">
|
||||
<li><span class="icon">✗</span> Produktionscode oder SQL schreiben</li>
|
||||
<li><span class="icon">✗</span> Architektur- oder Hosting-Entscheidungen treffen</li>
|
||||
<li><span class="icon">✗</span> Pixel-genaue Mockups oder Figma-Files liefern</li>
|
||||
<li><span class="icon">✗</span> Frameworks empfehlen (nur als Constraint framen)</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="review-box">
|
||||
<div class="section-label">// review focus</div>
|
||||
<div class="review-items">
|
||||
<span class="review-item">Fehlende User Journeys</span>
|
||||
<span class="review-item">Untestbare Requirements</span>
|
||||
<span class="review-item">Scope Creep</span>
|
||||
<span class="review-item">Fehlende NFRs</span>
|
||||
<span class="review-item">Kein expliziter Out-of-Scope</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
130
docs/presentation/personas/02-markus-keller.html
Normal file
130
docs/presentation/personas/02-markus-keller.html
Normal file
@@ -0,0 +1,130 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Markus Keller — Architect</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
:root{
|
||||
--accent:#60A5FA;
|
||||
--bg:#0D1117;--surface:#161B22;--surface-2:#1C2128;--border:#21262D;
|
||||
--text:#C9D1D9;--text-muted:#6E7681;--text-bright:#F0F6FC;
|
||||
--red:#F85149;--red-bg:rgba(248,81,73,0.07);--red-border:rgba(248,81,73,0.28);
|
||||
}
|
||||
body{background:var(--bg);color:var(--text);font-family:'Inter',system-ui,sans-serif;height:100vh;display:flex;align-items:stretch;padding:14px}
|
||||
.card{width:100%;display:flex;flex-direction:column;background:var(--surface);border:1px solid var(--border);border-radius:12px;overflow:hidden;position:relative}
|
||||
.card::before{content:'';position:absolute;inset:0;background-image:radial-gradient(circle,rgba(255,255,255,0.05) 1px,transparent 1px);background-size:26px 26px;pointer-events:none}
|
||||
.topbar{display:flex;align-items:center;justify-content:space-between;padding:14px 32px;border-bottom:1px solid var(--border);border-left:4px solid var(--accent);font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--text-muted)}
|
||||
.topbar-path .dir{color:var(--text-muted)}.topbar-path .file{color:var(--accent);font-weight:500}
|
||||
.topbar-right{display:flex;align-items:center;gap:20px}
|
||||
.status{display:flex;align-items:center;gap:5px}
|
||||
.status-dot{width:6px;height:6px;border-radius:50%;background:var(--accent);box-shadow:0 0 6px var(--accent);animation:pulse 2.5s ease-in-out infinite}
|
||||
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
|
||||
.project-label{color:var(--accent);font-weight:700;letter-spacing:1px;font-size:10px}
|
||||
.card-body{border-left:4px solid var(--accent);flex:1;display:flex;flex-direction:column;padding:0 32px 24px 32px;position:relative}
|
||||
.header{display:flex;align-items:center;gap:20px;padding-top:24px;padding-bottom:20px}
|
||||
.avatar{width:72px;height:72px;border-radius:50%;background:color-mix(in srgb,var(--accent) 14%,transparent);border:2px solid var(--accent);display:flex;align-items:center;justify-content:center;font-family:'JetBrains Mono',monospace;font-size:22px;font-weight:700;color:var(--accent);flex-shrink:0;letter-spacing:-1px}
|
||||
.persona-name{font-size:30px;font-weight:800;color:var(--text-bright);letter-spacing:-.5px;line-height:1.1}
|
||||
.persona-sub{display:flex;align-items:center;gap:10px;margin-top:5px;font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--text-muted)}
|
||||
.persona-sub .accent{color:var(--accent)}.persona-sub .sep{color:var(--border)}
|
||||
.quote{border-left:3px solid var(--accent);padding:10px 16px;margin-bottom:20px;font-style:italic;font-size:13.5px;line-height:1.65;color:var(--text-muted);background:color-mix(in srgb,var(--accent) 5%,transparent);border-radius:0 6px 6px 0}
|
||||
.skills-row{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:20px}
|
||||
.skill{font-family:'JetBrains Mono',monospace;font-size:11px;font-weight:500;padding:3px 10px;border-radius:4px;border:1px solid color-mix(in srgb,var(--accent) 35%,transparent);background:color-mix(in srgb,var(--accent) 10%,transparent);color:var(--accent)}
|
||||
.columns{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:16px}
|
||||
.section-label{font-family:'JetBrains Mono',monospace;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:var(--text-muted);margin-bottom:9px}
|
||||
.does-box{background:var(--surface-2);border:1px solid var(--border);border-radius:7px;padding:13px 14px}
|
||||
.does-box .section-label{color:var(--accent)}
|
||||
.item-list{list-style:none;display:flex;flex-direction:column;gap:5px}
|
||||
.item-list li{display:flex;align-items:flex-start;gap:8px;font-size:12.5px;line-height:1.4;color:var(--text)}
|
||||
.item-list li .icon{flex-shrink:0}
|
||||
.does-box .icon{color:var(--accent)}
|
||||
.never-box{background:var(--red-bg);border:1px solid var(--red-border);border-radius:7px;padding:13px 14px}
|
||||
.never-label{font-family:'JetBrains Mono',monospace;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:var(--red);margin-bottom:9px;display:flex;align-items:center;gap:6px}
|
||||
.never-box .icon{color:var(--red)}
|
||||
.review-box{background:color-mix(in srgb,var(--accent) 7%,transparent);border:1px solid color-mix(in srgb,var(--accent) 28%,transparent);border-radius:7px;padding:12px 16px}
|
||||
.review-items{display:flex;flex-wrap:wrap;gap:8px 18px;margin-top:7px}
|
||||
.review-item{font-size:12px;color:var(--text-muted);display:flex;align-items:center;gap:5px}
|
||||
.review-item::before{content:'→';color:var(--accent);font-size:11px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<div class="topbar">
|
||||
<div class="topbar-path">
|
||||
<span class="dir">.claude/personas/</span><span class="file">architect.md</span>
|
||||
</div>
|
||||
<div class="topbar-right">
|
||||
<div class="status"><div class="status-dot"></div><span>active</span></div>
|
||||
<span>·</span>
|
||||
<span>philosophy: boring-tech-wins</span>
|
||||
<span>·</span>
|
||||
<span class="project-label">FAMILIENARCHIV</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="header">
|
||||
<div class="avatar">MK</div>
|
||||
<div class="name-block">
|
||||
<div class="persona-name">Markus Keller</div>
|
||||
<div class="persona-sub">
|
||||
<span>Senior Application Architect</span>
|
||||
<span class="sep">·</span>
|
||||
<span class="accent">15+ Jahre XP</span>
|
||||
<span class="sep">·</span>
|
||||
<span>@mkeller</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="quote">
|
||||
"Boring technology wins. Every architecture decision is a trade-off you either make consciously — or regret later."
|
||||
</div>
|
||||
|
||||
<div class="skills-row">
|
||||
<span class="skill">C4 Model</span>
|
||||
<span class="skill">DDD</span>
|
||||
<span class="skill">SOLID</span>
|
||||
<span class="skill">ADRs</span>
|
||||
<span class="skill">Modular Monolith</span>
|
||||
<span class="skill">Spring Boot 4</span>
|
||||
<span class="skill">PostgreSQL</span>
|
||||
</div>
|
||||
|
||||
<div class="columns">
|
||||
<div class="does-box">
|
||||
<div class="section-label">// does</div>
|
||||
<ul class="item-list">
|
||||
<li><span class="icon">✓</span> ADRs vor jeder strukturellen Entscheidung schreiben</li>
|
||||
<li><span class="icon">✓</span> Domain-Grenzen definieren und durchsetzen</li>
|
||||
<li><span class="icon">✓</span> Trade-off-Analysen liefern (nicht nur Meinung)</li>
|
||||
<li><span class="icon">✓</span> Layering-Verletzungen mit konkreter Alternative benennen</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="never-box">
|
||||
<div class="never-label">⊘ hard limits</div>
|
||||
<ul class="item-list">
|
||||
<li><span class="icon">✗</span> Implementierungscode schreiben</li>
|
||||
<li><span class="icon">✗</span> Microservices ohne konkrete Begründung empfehlen</li>
|
||||
<li><span class="icon">✗</span> Test-Implementierungen oder UI-Pixel-Entscheidungen</li>
|
||||
<li><span class="icon">✗</span> „Best practice" als Begründung akzeptieren</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="review-box">
|
||||
<div class="section-label">// review focus</div>
|
||||
<div class="review-items">
|
||||
<span class="review-item">Layering-Verletzungen</span>
|
||||
<span class="review-item">Cross-Domain-Coupling</span>
|
||||
<span class="review-item">Fehlende ADRs</span>
|
||||
<span class="review-item">Premature Complexity</span>
|
||||
<span class="review-item">Service-Grenzen</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
131
docs/presentation/personas/03-felix-brandt.html
Normal file
131
docs/presentation/personas/03-felix-brandt.html
Normal file
@@ -0,0 +1,131 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Felix Brandt — Developer</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
:root{
|
||||
--accent:#34D399;
|
||||
--bg:#0D1117;--surface:#161B22;--surface-2:#1C2128;--border:#21262D;
|
||||
--text:#C9D1D9;--text-muted:#6E7681;--text-bright:#F0F6FC;
|
||||
--red:#F85149;--red-bg:rgba(248,81,73,0.07);--red-border:rgba(248,81,73,0.28);
|
||||
}
|
||||
body{background:var(--bg);color:var(--text);font-family:'Inter',system-ui,sans-serif;height:100vh;display:flex;align-items:stretch;padding:14px}
|
||||
.card{width:100%;display:flex;flex-direction:column;background:var(--surface);border:1px solid var(--border);border-radius:12px;overflow:hidden;position:relative}
|
||||
.card::before{content:'';position:absolute;inset:0;background-image:radial-gradient(circle,rgba(255,255,255,0.05) 1px,transparent 1px);background-size:26px 26px;pointer-events:none}
|
||||
.topbar{display:flex;align-items:center;justify-content:space-between;padding:18px 40px;border-bottom:1px solid var(--border);border-left:4px solid var(--accent);font-family:'JetBrains Mono',monospace;font-size:15px;color:var(--text-muted)}
|
||||
.topbar-path .dir{color:var(--text-muted)}.topbar-path .file{color:var(--accent);font-weight:500}
|
||||
.topbar-right{display:flex;align-items:center;gap:20px}
|
||||
.status{display:flex;align-items:center;gap:5px}
|
||||
.status-dot{width:6px;height:6px;border-radius:50%;background:var(--accent);box-shadow:0 0 6px var(--accent);animation:pulse 2.5s ease-in-out infinite}
|
||||
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
|
||||
.project-label{color:var(--accent);font-weight:700;letter-spacing:1px;font-size:14px}
|
||||
.card-body{border-left:4px solid var(--accent);flex:1;display:flex;flex-direction:column;padding:0 40px 28px 40px;position:relative}
|
||||
.header{display:flex;align-items:center;gap:24px;padding-top:28px;padding-bottom:22px}
|
||||
.avatar{width:96px;height:96px;border-radius:50%;background:color-mix(in srgb,var(--accent) 14%,transparent);border:2px solid var(--accent);display:flex;align-items:center;justify-content:center;font-family:'JetBrains Mono',monospace;font-size:30px;font-weight:700;color:var(--accent);flex-shrink:0;letter-spacing:-1px}
|
||||
.persona-name{font-size:42px;font-weight:800;color:var(--text-bright);letter-spacing:-.5px;line-height:1.1}
|
||||
.persona-sub{display:flex;align-items:center;gap:10px;margin-top:6px;font-family:'JetBrains Mono',monospace;font-size:16px;color:var(--text-muted)}
|
||||
.persona-sub .accent{color:var(--accent)}.persona-sub .sep{color:var(--border)}
|
||||
.quote{border-left:3px solid var(--accent);padding:14px 20px;margin-bottom:24px;font-style:italic;font-size:19px;line-height:1.65;color:var(--text-muted);background:color-mix(in srgb,var(--accent) 5%,transparent);border-radius:0 6px 6px 0}
|
||||
.skills-row{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:24px}
|
||||
.skill{font-family:'JetBrains Mono',monospace;font-size:15px;font-weight:500;padding:5px 14px;border-radius:4px;border:1px solid color-mix(in srgb,var(--accent) 35%,transparent);background:color-mix(in srgb,var(--accent) 10%,transparent);color:var(--accent)}
|
||||
.columns{display:grid;grid-template-columns:1fr 1fr;gap:18px;margin-bottom:18px}
|
||||
.section-label{font-family:'JetBrains Mono',monospace;font-size:13px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:var(--text-muted);margin-bottom:11px}
|
||||
.does-box{background:var(--surface-2);border:1px solid var(--border);border-radius:7px;padding:16px 18px}
|
||||
.does-box .section-label{color:var(--accent)}
|
||||
.item-list{list-style:none;display:flex;flex-direction:column;gap:7px}
|
||||
.item-list li{display:flex;align-items:flex-start;gap:10px;font-size:17px;line-height:1.4;color:var(--text)}
|
||||
.item-list li .icon{flex-shrink:0}
|
||||
.does-box .icon{color:var(--accent)}
|
||||
.never-box{background:var(--red-bg);border:1px solid var(--red-border);border-radius:7px;padding:16px 18px}
|
||||
.never-label{font-family:'JetBrains Mono',monospace;font-size:13px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:var(--red);margin-bottom:11px;display:flex;align-items:center;gap:6px}
|
||||
.never-box .icon{color:var(--red)}
|
||||
.review-box{background:color-mix(in srgb,var(--accent) 7%,transparent);border:1px solid color-mix(in srgb,var(--accent) 28%,transparent);border-radius:7px;padding:16px 20px}
|
||||
.review-items{display:flex;flex-wrap:wrap;gap:10px 22px;margin-top:9px}
|
||||
.review-item{font-size:16px;color:var(--text-muted);display:flex;align-items:center;gap:5px}
|
||||
.review-item::before{content:'→';color:var(--accent);font-size:14px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<div class="topbar">
|
||||
<div class="topbar-path">
|
||||
<span class="dir">.claude/personas/</span><span class="file">developer.md</span>
|
||||
</div>
|
||||
<div class="topbar-right">
|
||||
<div class="status"><div class="status-dot"></div><span>active</span></div>
|
||||
<span>·</span>
|
||||
<span>red_step: NEVER_SKIP</span>
|
||||
<span>·</span>
|
||||
<span class="project-label">FAMILIENARCHIV</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="header">
|
||||
<div class="avatar">FB</div>
|
||||
<div class="name-block">
|
||||
<div class="persona-name">Felix Brandt</div>
|
||||
<div class="persona-sub">
|
||||
<span>Senior Fullstack Developer</span>
|
||||
<span class="sep">·</span>
|
||||
<span class="accent">8+ Jahre XP</span>
|
||||
<span class="sep">·</span>
|
||||
<span>@felixbrandt</span>
|
||||
<span class="sep">·</span>
|
||||
<span>SvelteKit · Spring Boot · TDD</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="quote">
|
||||
"If the test was written after the code, it's not a test. It's documentation."
|
||||
</div>
|
||||
|
||||
<div class="skills-row">
|
||||
<span class="skill">TDD</span>
|
||||
<span class="skill">Spring Boot 4</span>
|
||||
<span class="skill">SvelteKit 5</span>
|
||||
<span class="skill">Clean Code</span>
|
||||
<span class="skill">SOLID</span>
|
||||
<span class="skill">Refactoring</span>
|
||||
<span class="skill">TypeScript</span>
|
||||
</div>
|
||||
|
||||
<div class="columns">
|
||||
<div class="does-box">
|
||||
<div class="section-label">// does</div>
|
||||
<ul class="item-list">
|
||||
<li><span class="icon">✓</span> Failing test schreiben — immer zuerst</li>
|
||||
<li><span class="icon">✓</span> Red/Green/Refactor-Zyklus konsequent einhalten</li>
|
||||
<li><span class="icon">✓</span> Test + Implementierung im selben atomaren Commit</li>
|
||||
<li><span class="icon">✓</span> Zeilennummern in jedem Review-Kommentar</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="never-box">
|
||||
<div class="never-label">⊘ hard limits</div>
|
||||
<ul class="item-list">
|
||||
<li><span class="icon">✗</span> Test nachträglich schreiben um Code zu rechtfertigen</li>
|
||||
<li><span class="icon">✗</span> Red-Schritt überspringen — auch bei „offensichtlichen" Fixes</li>
|
||||
<li><span class="icon">✗</span> Mehrere logische Changes in einem einzigen Commit bündeln</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="review-box">
|
||||
<div class="section-label">// review focus</div>
|
||||
<div class="review-items">
|
||||
<span class="review-item">TDD-Compliance</span>
|
||||
<span class="review-item">Naming & Abstraktionen</span>
|
||||
<span class="review-item">SOLID-Verletzungen</span>
|
||||
<span class="review-item">Dead Code</span>
|
||||
<span class="review-item">Commit-Struktur</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
129
docs/presentation/personas/04-leonie-voss.html
Normal file
129
docs/presentation/personas/04-leonie-voss.html
Normal file
@@ -0,0 +1,129 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Leonie Voss — UX Designer</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
:root{
|
||||
--accent:#A78BFA;
|
||||
--bg:#0D1117;--surface:#161B22;--surface-2:#1C2128;--border:#21262D;
|
||||
--text:#C9D1D9;--text-muted:#6E7681;--text-bright:#F0F6FC;
|
||||
--red:#F85149;--red-bg:rgba(248,81,73,0.07);--red-border:rgba(248,81,73,0.28);
|
||||
}
|
||||
body{background:var(--bg);color:var(--text);font-family:'Inter',system-ui,sans-serif;height:100vh;display:flex;align-items:stretch;padding:14px}
|
||||
.card{width:100%;display:flex;flex-direction:column;background:var(--surface);border:1px solid var(--border);border-radius:12px;overflow:hidden;position:relative}
|
||||
.card::before{content:'';position:absolute;inset:0;background-image:radial-gradient(circle,rgba(255,255,255,0.05) 1px,transparent 1px);background-size:26px 26px;pointer-events:none}
|
||||
.topbar{display:flex;align-items:center;justify-content:space-between;padding:14px 32px;border-bottom:1px solid var(--border);border-left:4px solid var(--accent);font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--text-muted)}
|
||||
.topbar-path .dir{color:var(--text-muted)}.topbar-path .file{color:var(--accent);font-weight:500}
|
||||
.topbar-right{display:flex;align-items:center;gap:20px}
|
||||
.status{display:flex;align-items:center;gap:5px}
|
||||
.status-dot{width:6px;height:6px;border-radius:50%;background:var(--accent);box-shadow:0 0 6px var(--accent);animation:pulse 2.5s ease-in-out infinite}
|
||||
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
|
||||
.project-label{color:var(--accent);font-weight:700;letter-spacing:1px;font-size:10px}
|
||||
.card-body{border-left:4px solid var(--accent);flex:1;display:flex;flex-direction:column;padding:0 32px 24px 32px;position:relative}
|
||||
.header{display:flex;align-items:center;gap:20px;padding-top:24px;padding-bottom:20px}
|
||||
.avatar{width:72px;height:72px;border-radius:50%;background:color-mix(in srgb,var(--accent) 14%,transparent);border:2px solid var(--accent);display:flex;align-items:center;justify-content:center;font-family:'JetBrains Mono',monospace;font-size:22px;font-weight:700;color:var(--accent);flex-shrink:0;letter-spacing:-1px}
|
||||
.persona-name{font-size:30px;font-weight:800;color:var(--text-bright);letter-spacing:-.5px;line-height:1.1}
|
||||
.persona-sub{display:flex;align-items:center;gap:10px;margin-top:5px;font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--text-muted)}
|
||||
.persona-sub .accent{color:var(--accent)}.persona-sub .sep{color:var(--border)}
|
||||
.quote{border-left:3px solid var(--accent);padding:10px 16px;margin-bottom:20px;font-style:italic;font-size:13.5px;line-height:1.65;color:var(--text-muted);background:color-mix(in srgb,var(--accent) 5%,transparent);border-radius:0 6px 6px 0}
|
||||
.skills-row{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:20px}
|
||||
.skill{font-family:'JetBrains Mono',monospace;font-size:11px;font-weight:500;padding:3px 10px;border-radius:4px;border:1px solid color-mix(in srgb,var(--accent) 35%,transparent);background:color-mix(in srgb,var(--accent) 10%,transparent);color:var(--accent)}
|
||||
.columns{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:16px}
|
||||
.section-label{font-family:'JetBrains Mono',monospace;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:var(--text-muted);margin-bottom:9px}
|
||||
.does-box{background:var(--surface-2);border:1px solid var(--border);border-radius:7px;padding:13px 14px}
|
||||
.does-box .section-label{color:var(--accent)}
|
||||
.item-list{list-style:none;display:flex;flex-direction:column;gap:5px}
|
||||
.item-list li{display:flex;align-items:flex-start;gap:8px;font-size:12.5px;line-height:1.4;color:var(--text)}
|
||||
.item-list li .icon{flex-shrink:0}
|
||||
.does-box .icon{color:var(--accent)}
|
||||
.never-box{background:var(--red-bg);border:1px solid var(--red-border);border-radius:7px;padding:13px 14px}
|
||||
.never-label{font-family:'JetBrains Mono',monospace;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:var(--red);margin-bottom:9px;display:flex;align-items:center;gap:6px}
|
||||
.never-box .icon{color:var(--red)}
|
||||
.review-box{background:color-mix(in srgb,var(--accent) 7%,transparent);border:1px solid color-mix(in srgb,var(--accent) 28%,transparent);border-radius:7px;padding:12px 16px}
|
||||
.review-items{display:flex;flex-wrap:wrap;gap:8px 18px;margin-top:7px}
|
||||
.review-item{font-size:12px;color:var(--text-muted);display:flex;align-items:center;gap:5px}
|
||||
.review-item::before{content:'→';color:var(--accent);font-size:11px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<div class="topbar">
|
||||
<div class="topbar-path">
|
||||
<span class="dir">.claude/personas/</span><span class="file">ui_expert.md</span>
|
||||
</div>
|
||||
<div class="topbar-right">
|
||||
<div class="status"><div class="status-dot"></div><span>active</span></div>
|
||||
<span>·</span>
|
||||
<span>a11y: WCAG_2.1_AA</span>
|
||||
<span>·</span>
|
||||
<span class="project-label">FAMILIENARCHIV</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="header">
|
||||
<div class="avatar">LV</div>
|
||||
<div class="name-block">
|
||||
<div class="persona-name">Leonie Voss</div>
|
||||
<div class="persona-sub">
|
||||
<span>UI/UX Design Lead & Accessibility Advocate</span>
|
||||
<span class="sep">·</span>
|
||||
<span class="accent">12+ Jahre XP</span>
|
||||
<span class="sep">·</span>
|
||||
<span>@leonievoss</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="quote">
|
||||
"Design for the hardest constraint first. If it works for a 67-year-old in bright sunlight, it works for everyone."
|
||||
</div>
|
||||
|
||||
<div class="skills-row">
|
||||
<span class="skill">WCAG 2.1 AA</span>
|
||||
<span class="skill">Tailwind CSS 4</span>
|
||||
<span class="skill">Figma</span>
|
||||
<span class="skill">Responsive Design</span>
|
||||
<span class="skill">Brand Systems</span>
|
||||
<span class="skill">ARIA</span>
|
||||
<span class="skill">Svelte 5</span>
|
||||
</div>
|
||||
|
||||
<div class="columns">
|
||||
<div class="does-box">
|
||||
<div class="section-label">// does</div>
|
||||
<ul class="item-list">
|
||||
<li><span class="icon">✓</span> Kontrast-Verhältnisse messen (min. 4.5:1 Text)</li>
|
||||
<li><span class="icon">✓</span> Touch-Targets auf ≥44px prüfen</li>
|
||||
<li><span class="icon">✓</span> Brand-Compliance (Navy/Mint/Sand) sicherstellen</li>
|
||||
<li><span class="icon">✓</span> Jeden Critique mit konkretem Fix liefern</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="never-box">
|
||||
<div class="never-label">⊘ hard limits</div>
|
||||
<ul class="item-list">
|
||||
<li><span class="icon">✗</span> Backend-Code oder Datenbank-Design</li>
|
||||
<li><span class="icon">✗</span> Performance-Profiling oder Infrastruktur</li>
|
||||
<li><span class="icon">✗</span> Accessibility-Mängel ohne Fix-Vorschlag benennen</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="review-box">
|
||||
<div class="section-label">// review focus</div>
|
||||
<div class="review-items">
|
||||
<span class="review-item">Kontrast-Verhältnisse</span>
|
||||
<span class="review-item">Touch Targets ≥44px</span>
|
||||
<span class="review-item">Keyboard Navigation</span>
|
||||
<span class="review-item">Responsive Breakpoints</span>
|
||||
<span class="review-item">Fokus-Ringe sichtbar</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
129
docs/presentation/personas/05-sara-holt.html
Normal file
129
docs/presentation/personas/05-sara-holt.html
Normal file
@@ -0,0 +1,129 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Sara Holt — QA Engineer</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
:root{
|
||||
--accent:#FB923C;
|
||||
--bg:#0D1117;--surface:#161B22;--surface-2:#1C2128;--border:#21262D;
|
||||
--text:#C9D1D9;--text-muted:#6E7681;--text-bright:#F0F6FC;
|
||||
--red:#F85149;--red-bg:rgba(248,81,73,0.07);--red-border:rgba(248,81,73,0.28);
|
||||
}
|
||||
body{background:var(--bg);color:var(--text);font-family:'Inter',system-ui,sans-serif;height:100vh;display:flex;align-items:stretch;padding:14px}
|
||||
.card{width:100%;display:flex;flex-direction:column;background:var(--surface);border:1px solid var(--border);border-radius:12px;overflow:hidden;position:relative}
|
||||
.card::before{content:'';position:absolute;inset:0;background-image:radial-gradient(circle,rgba(255,255,255,0.05) 1px,transparent 1px);background-size:26px 26px;pointer-events:none}
|
||||
.topbar{display:flex;align-items:center;justify-content:space-between;padding:14px 32px;border-bottom:1px solid var(--border);border-left:4px solid var(--accent);font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--text-muted)}
|
||||
.topbar-path .dir{color:var(--text-muted)}.topbar-path .file{color:var(--accent);font-weight:500}
|
||||
.topbar-right{display:flex;align-items:center;gap:20px}
|
||||
.status{display:flex;align-items:center;gap:5px}
|
||||
.status-dot{width:6px;height:6px;border-radius:50%;background:var(--accent);box-shadow:0 0 6px var(--accent);animation:pulse 2.5s ease-in-out infinite}
|
||||
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
|
||||
.project-label{color:var(--accent);font-weight:700;letter-spacing:1px;font-size:10px}
|
||||
.card-body{border-left:4px solid var(--accent);flex:1;display:flex;flex-direction:column;padding:0 32px 24px 32px;position:relative}
|
||||
.header{display:flex;align-items:center;gap:20px;padding-top:24px;padding-bottom:20px}
|
||||
.avatar{width:72px;height:72px;border-radius:50%;background:color-mix(in srgb,var(--accent) 14%,transparent);border:2px solid var(--accent);display:flex;align-items:center;justify-content:center;font-family:'JetBrains Mono',monospace;font-size:22px;font-weight:700;color:var(--accent);flex-shrink:0;letter-spacing:-1px}
|
||||
.persona-name{font-size:30px;font-weight:800;color:var(--text-bright);letter-spacing:-.5px;line-height:1.1}
|
||||
.persona-sub{display:flex;align-items:center;gap:10px;margin-top:5px;font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--text-muted)}
|
||||
.persona-sub .accent{color:var(--accent)}.persona-sub .sep{color:var(--border)}
|
||||
.quote{border-left:3px solid var(--accent);padding:10px 16px;margin-bottom:20px;font-style:italic;font-size:13.5px;line-height:1.65;color:var(--text-muted);background:color-mix(in srgb,var(--accent) 5%,transparent);border-radius:0 6px 6px 0}
|
||||
.skills-row{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:20px}
|
||||
.skill{font-family:'JetBrains Mono',monospace;font-size:11px;font-weight:500;padding:3px 10px;border-radius:4px;border:1px solid color-mix(in srgb,var(--accent) 35%,transparent);background:color-mix(in srgb,var(--accent) 10%,transparent);color:var(--accent)}
|
||||
.columns{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:16px}
|
||||
.section-label{font-family:'JetBrains Mono',monospace;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:var(--text-muted);margin-bottom:9px}
|
||||
.does-box{background:var(--surface-2);border:1px solid var(--border);border-radius:7px;padding:13px 14px}
|
||||
.does-box .section-label{color:var(--accent)}
|
||||
.item-list{list-style:none;display:flex;flex-direction:column;gap:5px}
|
||||
.item-list li{display:flex;align-items:flex-start;gap:8px;font-size:12.5px;line-height:1.4;color:var(--text)}
|
||||
.item-list li .icon{flex-shrink:0}
|
||||
.does-box .icon{color:var(--accent)}
|
||||
.never-box{background:var(--red-bg);border:1px solid var(--red-border);border-radius:7px;padding:13px 14px}
|
||||
.never-label{font-family:'JetBrains Mono',monospace;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:var(--red);margin-bottom:9px;display:flex;align-items:center;gap:6px}
|
||||
.never-box .icon{color:var(--red)}
|
||||
.review-box{background:color-mix(in srgb,var(--accent) 7%,transparent);border:1px solid color-mix(in srgb,var(--accent) 28%,transparent);border-radius:7px;padding:12px 16px}
|
||||
.review-items{display:flex;flex-wrap:wrap;gap:8px 18px;margin-top:7px}
|
||||
.review-item{font-size:12px;color:var(--text-muted);display:flex;align-items:center;gap:5px}
|
||||
.review-item::before{content:'→';color:var(--accent);font-size:11px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<div class="topbar">
|
||||
<div class="topbar-path">
|
||||
<span class="dir">.claude/personas/</span><span class="file">tester.md</span>
|
||||
</div>
|
||||
<div class="topbar-right">
|
||||
<div class="status"><div class="status-dot"></div><span>active</span></div>
|
||||
<span>·</span>
|
||||
<span>flaky_tests: NOT_ACCEPTABLE</span>
|
||||
<span>·</span>
|
||||
<span class="project-label">FAMILIENARCHIV</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="header">
|
||||
<div class="avatar">SH</div>
|
||||
<div class="name-block">
|
||||
<div class="persona-name">Sara Holt</div>
|
||||
<div class="persona-sub">
|
||||
<span>Senior QA Engineer & Test Automation Specialist</span>
|
||||
<span class="sep">·</span>
|
||||
<span class="accent">10+ Jahre XP</span>
|
||||
<span class="sep">·</span>
|
||||
<span>@saraholt</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="quote">
|
||||
"A passing test that was never failing is a lie with a green checkmark."
|
||||
</div>
|
||||
|
||||
<div class="skills-row">
|
||||
<span class="skill">Playwright</span>
|
||||
<span class="skill">Vitest</span>
|
||||
<span class="skill">JUnit 5</span>
|
||||
<span class="skill">Testcontainers</span>
|
||||
<span class="skill">Test Pyramid</span>
|
||||
<span class="skill">Mutation Testing</span>
|
||||
<span class="skill">@WebMvcTest</span>
|
||||
</div>
|
||||
|
||||
<div class="columns">
|
||||
<div class="does-box">
|
||||
<div class="section-label">// does</div>
|
||||
<ul class="item-list">
|
||||
<li><span class="icon">✓</span> E2E-Szenarien aus Issue-Gherkin ableiten</li>
|
||||
<li><span class="icon">✓</span> Test-Pyramide auf allen drei Ebenen durchsetzen</li>
|
||||
<li><span class="icon">✓</span> Randfälle und Fehlerpfade explizit benennen</li>
|
||||
<li><span class="icon">✓</span> Arrange-Act-Assert in jedem Test sicherstellen</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="never-box">
|
||||
<div class="never-label">⊘ hard limits</div>
|
||||
<ul class="item-list">
|
||||
<li><span class="icon">✗</span> Produktionscode oder Deployments schreiben</li>
|
||||
<li><span class="icon">✗</span> Flaky Tests akzeptieren — auch unter Zeitdruck</li>
|
||||
<li><span class="icon">✗</span> Test-Namen wie <code>test1()</code> oder <code>shouldWork()</code></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="review-box">
|
||||
<div class="section-label">// review focus</div>
|
||||
<div class="review-items">
|
||||
<span class="review-item">Fehlende Randfälle</span>
|
||||
<span class="review-item">Flaky-Test-Muster</span>
|
||||
<span class="review-item">Ungetestete Happy Paths</span>
|
||||
<span class="review-item">AAA-Verletzungen</span>
|
||||
<span class="review-item">Kein Red-Beweis</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
135
docs/presentation/personas/06-nora-steiner.html
Normal file
135
docs/presentation/personas/06-nora-steiner.html
Normal file
@@ -0,0 +1,135 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nora "NullX" Steiner — Security Engineer</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
:root{
|
||||
--accent:#F87171;
|
||||
--bg:#0D1117;--surface:#161B22;--surface-2:#1C2128;--border:#21262D;
|
||||
--text:#C9D1D9;--text-muted:#6E7681;--text-bright:#F0F6FC;
|
||||
--red:#F85149;--red-bg:rgba(248,81,73,0.07);--red-border:rgba(248,81,73,0.28);
|
||||
}
|
||||
body{background:var(--bg);color:var(--text);font-family:'Inter',system-ui,sans-serif;height:100vh;display:flex;align-items:stretch;padding:14px}
|
||||
.card{width:100%;display:flex;flex-direction:column;background:var(--surface);border:1px solid var(--border);border-radius:12px;overflow:hidden;position:relative}
|
||||
.card::before{content:'';position:absolute;inset:0;background-image:radial-gradient(circle,rgba(255,255,255,0.05) 1px,transparent 1px);background-size:26px 26px;pointer-events:none}
|
||||
.topbar{display:flex;align-items:center;justify-content:space-between;padding:14px 32px;border-bottom:1px solid var(--border);border-left:4px solid var(--accent);font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--text-muted)}
|
||||
.topbar-path .dir{color:var(--text-muted)}.topbar-path .file{color:var(--accent);font-weight:500}
|
||||
.topbar-right{display:flex;align-items:center;gap:20px}
|
||||
.status{display:flex;align-items:center;gap:5px}
|
||||
.status-dot{width:6px;height:6px;border-radius:50%;background:var(--accent);box-shadow:0 0 6px var(--accent);animation:pulse 2.5s ease-in-out infinite}
|
||||
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
|
||||
.project-label{color:var(--accent);font-weight:700;letter-spacing:1px;font-size:10px}
|
||||
.card-body{border-left:4px solid var(--accent);flex:1;display:flex;flex-direction:column;padding:0 32px 24px 32px;position:relative}
|
||||
.header{display:flex;align-items:center;gap:20px;padding-top:24px;padding-bottom:20px}
|
||||
.avatar{width:72px;height:72px;border-radius:50%;background:color-mix(in srgb,var(--accent) 14%,transparent);border:2px solid var(--accent);display:flex;align-items:center;justify-content:center;font-family:'JetBrains Mono',monospace;font-size:22px;font-weight:700;color:var(--accent);flex-shrink:0;letter-spacing:-1px}
|
||||
.persona-name{font-size:30px;font-weight:800;color:var(--text-bright);letter-spacing:-.5px;line-height:1.1}
|
||||
.persona-sub{display:flex;align-items:center;gap:10px;margin-top:5px;font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--text-muted)}
|
||||
.persona-sub .accent{color:var(--accent)}.persona-sub .sep{color:var(--border)}
|
||||
.quote{border-left:3px solid var(--accent);padding:10px 16px;margin-bottom:20px;font-style:italic;font-size:13.5px;line-height:1.65;color:var(--text-muted);background:color-mix(in srgb,var(--accent) 5%,transparent);border-radius:0 6px 6px 0}
|
||||
.skills-row{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:20px}
|
||||
.skill{font-family:'JetBrains Mono',monospace;font-size:11px;font-weight:500;padding:3px 10px;border-radius:4px;border:1px solid color-mix(in srgb,var(--accent) 35%,transparent);background:color-mix(in srgb,var(--accent) 10%,transparent);color:var(--accent)}
|
||||
.columns{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:16px}
|
||||
.section-label{font-family:'JetBrains Mono',monospace;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:var(--text-muted);margin-bottom:9px}
|
||||
.does-box{background:var(--surface-2);border:1px solid var(--border);border-radius:7px;padding:13px 14px}
|
||||
.does-box .section-label{color:var(--accent)}
|
||||
.item-list{list-style:none;display:flex;flex-direction:column;gap:5px}
|
||||
.item-list li{display:flex;align-items:flex-start;gap:8px;font-size:12.5px;line-height:1.4;color:var(--text)}
|
||||
.item-list li .icon{flex-shrink:0}
|
||||
.does-box .icon{color:var(--accent)}
|
||||
.never-box{background:var(--red-bg);border:1px solid var(--red-border);border-radius:7px;padding:13px 14px}
|
||||
.never-label{font-family:'JetBrains Mono',monospace;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:var(--red);margin-bottom:9px;display:flex;align-items:center;gap:6px}
|
||||
.never-box .icon{color:var(--red)}
|
||||
.review-box{background:color-mix(in srgb,var(--accent) 7%,transparent);border:1px solid color-mix(in srgb,var(--accent) 28%,transparent);border-radius:7px;padding:12px 16px}
|
||||
.review-items{display:flex;flex-wrap:wrap;gap:8px 18px;margin-top:7px}
|
||||
.review-item{font-size:12px;color:var(--text-muted);display:flex;align-items:center;gap:5px}
|
||||
.review-item::before{content:'→';color:var(--accent);font-size:11px}
|
||||
.cert-badges{display:flex;gap:8px;margin-top:4px}
|
||||
.cert{font-family:'JetBrains Mono',monospace;font-size:10px;padding:2px 8px;border-radius:3px;background:color-mix(in srgb,var(--accent) 15%,transparent);border:1px solid color-mix(in srgb,var(--accent) 40%,transparent);color:var(--accent);font-weight:700}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<div class="topbar">
|
||||
<div class="topbar-path">
|
||||
<span class="dir">.claude/personas/</span><span class="file">security_expert.md</span>
|
||||
</div>
|
||||
<div class="topbar-right">
|
||||
<div class="status"><div class="status-dot"></div><span>active</span></div>
|
||||
<span>·</span>
|
||||
<span>mindset: adversarial</span>
|
||||
<span>·</span>
|
||||
<span class="project-label">FAMILIENARCHIV</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="header">
|
||||
<div class="avatar">N0</div>
|
||||
<div class="name-block">
|
||||
<div class="persona-name">Nora <span style="color:var(--accent)">"NullX"</span> Steiner</div>
|
||||
<div class="persona-sub">
|
||||
<span>Application Security Engineer · Ethical Hacker</span>
|
||||
<span class="sep">·</span>
|
||||
<span class="accent">8+ Jahre XP</span>
|
||||
<span class="sep">·</span>
|
||||
<span>@nullx</span>
|
||||
</div>
|
||||
<div class="cert-badges">
|
||||
<span class="cert">OSWE</span>
|
||||
<span class="cert">BSCP</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="quote">
|
||||
"Trust no one. Validate everything. Especially the things that look safe."
|
||||
</div>
|
||||
|
||||
<div class="skills-row">
|
||||
<span class="skill">OWASP Top 10</span>
|
||||
<span class="skill">Spring Security</span>
|
||||
<span class="skill">Burp Suite</span>
|
||||
<span class="skill">SQL Injection</span>
|
||||
<span class="skill">XSS</span>
|
||||
<span class="skill">JWT</span>
|
||||
<span class="skill">CSRF</span>
|
||||
</div>
|
||||
|
||||
<div class="columns">
|
||||
<div class="does-box">
|
||||
<div class="section-label">// does</div>
|
||||
<ul class="item-list">
|
||||
<li><span class="icon">✓</span> Auth-Audit bei jedem neuen Endpoint</li>
|
||||
<li><span class="icon">✓</span> Jede Lücke mit Exploit-Szenario und Fix liefern</li>
|
||||
<li><span class="icon">✓</span> Sicherheitskommentare mit Threat-Model-Kontext</li>
|
||||
<li><span class="icon">✓</span> Entwickler aufklären — niemals beschämen</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="never-box">
|
||||
<div class="never-label">⊘ hard limits</div>
|
||||
<ul class="item-list">
|
||||
<li><span class="icon">✗</span> Feature-Implementierung oder UI-Entscheidungen</li>
|
||||
<li><span class="icon">✗</span> Security-Findings ohne Fix-Vorschlag posten</li>
|
||||
<li><span class="icon">✗</span> Performance-Optimierungen bewerten</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="review-box">
|
||||
<div class="section-label">// review focus</div>
|
||||
<div class="review-items">
|
||||
<span class="review-item">@RequirePermission auf jedem Endpoint</span>
|
||||
<span class="review-item">Injection-Vektoren</span>
|
||||
<span class="review-item">Sensitive Data Exposure</span>
|
||||
<span class="review-item">Session Handling</span>
|
||||
<span class="review-item">Actuator-Endpoints</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
129
docs/presentation/personas/07-tobias-wendt.html
Normal file
129
docs/presentation/personas/07-tobias-wendt.html
Normal file
@@ -0,0 +1,129 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Tobias "tobi" Wendt — DevOps</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
:root{
|
||||
--accent:#22D3EE;
|
||||
--bg:#0D1117;--surface:#161B22;--surface-2:#1C2128;--border:#21262D;
|
||||
--text:#C9D1D9;--text-muted:#6E7681;--text-bright:#F0F6FC;
|
||||
--red:#F85149;--red-bg:rgba(248,81,73,0.07);--red-border:rgba(248,81,73,0.28);
|
||||
}
|
||||
body{background:var(--bg);color:var(--text);font-family:'Inter',system-ui,sans-serif;height:100vh;display:flex;align-items:stretch;padding:14px}
|
||||
.card{width:100%;display:flex;flex-direction:column;background:var(--surface);border:1px solid var(--border);border-radius:12px;overflow:hidden;position:relative}
|
||||
.card::before{content:'';position:absolute;inset:0;background-image:radial-gradient(circle,rgba(255,255,255,0.05) 1px,transparent 1px);background-size:26px 26px;pointer-events:none}
|
||||
.topbar{display:flex;align-items:center;justify-content:space-between;padding:14px 32px;border-bottom:1px solid var(--border);border-left:4px solid var(--accent);font-family:'JetBrains Mono',monospace;font-size:11px;color:var(--text-muted)}
|
||||
.topbar-path .dir{color:var(--text-muted)}.topbar-path .file{color:var(--accent);font-weight:500}
|
||||
.topbar-right{display:flex;align-items:center;gap:20px}
|
||||
.status{display:flex;align-items:center;gap:5px}
|
||||
.status-dot{width:6px;height:6px;border-radius:50%;background:var(--accent);box-shadow:0 0 6px var(--accent);animation:pulse 2.5s ease-in-out infinite}
|
||||
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
|
||||
.project-label{color:var(--accent);font-weight:700;letter-spacing:1px;font-size:10px}
|
||||
.card-body{border-left:4px solid var(--accent);flex:1;display:flex;flex-direction:column;padding:0 32px 24px 32px;position:relative}
|
||||
.header{display:flex;align-items:center;gap:20px;padding-top:24px;padding-bottom:20px}
|
||||
.avatar{width:72px;height:72px;border-radius:50%;background:color-mix(in srgb,var(--accent) 14%,transparent);border:2px solid var(--accent);display:flex;align-items:center;justify-content:center;font-family:'JetBrains Mono',monospace;font-size:22px;font-weight:700;color:var(--accent);flex-shrink:0;letter-spacing:-1px}
|
||||
.persona-name{font-size:30px;font-weight:800;color:var(--text-bright);letter-spacing:-.5px;line-height:1.1}
|
||||
.persona-sub{display:flex;align-items:center;gap:10px;margin-top:5px;font-family:'JetBrains Mono',monospace;font-size:12px;color:var(--text-muted)}
|
||||
.persona-sub .accent{color:var(--accent)}.persona-sub .sep{color:var(--border)}
|
||||
.quote{border-left:3px solid var(--accent);padding:10px 16px;margin-bottom:20px;font-style:italic;font-size:13.5px;line-height:1.65;color:var(--text-muted);background:color-mix(in srgb,var(--accent) 5%,transparent);border-radius:0 6px 6px 0}
|
||||
.skills-row{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:20px}
|
||||
.skill{font-family:'JetBrains Mono',monospace;font-size:11px;font-weight:500;padding:3px 10px;border-radius:4px;border:1px solid color-mix(in srgb,var(--accent) 35%,transparent);background:color-mix(in srgb,var(--accent) 10%,transparent);color:var(--accent)}
|
||||
.columns{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-bottom:16px}
|
||||
.section-label{font-family:'JetBrains Mono',monospace;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:var(--text-muted);margin-bottom:9px}
|
||||
.does-box{background:var(--surface-2);border:1px solid var(--border);border-radius:7px;padding:13px 14px}
|
||||
.does-box .section-label{color:var(--accent)}
|
||||
.item-list{list-style:none;display:flex;flex-direction:column;gap:5px}
|
||||
.item-list li{display:flex;align-items:flex-start;gap:8px;font-size:12.5px;line-height:1.4;color:var(--text)}
|
||||
.item-list li .icon{flex-shrink:0}
|
||||
.does-box .icon{color:var(--accent)}
|
||||
.never-box{background:var(--red-bg);border:1px solid var(--red-border);border-radius:7px;padding:13px 14px}
|
||||
.never-label{font-family:'JetBrains Mono',monospace;font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:1.5px;color:var(--red);margin-bottom:9px;display:flex;align-items:center;gap:6px}
|
||||
.never-box .icon{color:var(--red)}
|
||||
.review-box{background:color-mix(in srgb,var(--accent) 7%,transparent);border:1px solid color-mix(in srgb,var(--accent) 28%,transparent);border-radius:7px;padding:12px 16px}
|
||||
.review-items{display:flex;flex-wrap:wrap;gap:8px 18px;margin-top:7px}
|
||||
.review-item{font-size:12px;color:var(--text-muted);display:flex;align-items:center;gap:5px}
|
||||
.review-item::before{content:'→';color:var(--accent);font-size:11px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<div class="topbar">
|
||||
<div class="topbar-path">
|
||||
<span class="dir">.claude/personas/</span><span class="file">devops.md</span>
|
||||
</div>
|
||||
<div class="topbar-right">
|
||||
<div class="status"><div class="status-dot"></div><span>active</span></div>
|
||||
<span>·</span>
|
||||
<span>complexity: LIABILITY_NOT_FEATURE</span>
|
||||
<span>·</span>
|
||||
<span class="project-label">FAMILIENARCHIV</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="header">
|
||||
<div class="avatar">TW</div>
|
||||
<div class="name-block">
|
||||
<div class="persona-name">Tobias <span style="color:var(--accent)">"tobi"</span> Wendt</div>
|
||||
<div class="persona-sub">
|
||||
<span>DevOps & Platform Engineer</span>
|
||||
<span class="sep">·</span>
|
||||
<span class="accent">10+ Jahre XP</span>
|
||||
<span class="sep">·</span>
|
||||
<span>@tobiwendt</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="quote">
|
||||
"Every added tool is a new failure mode. If it's not in Docker Compose, it doesn't exist."
|
||||
</div>
|
||||
|
||||
<div class="skills-row">
|
||||
<span class="skill">Docker Compose</span>
|
||||
<span class="skill">Gitea Actions</span>
|
||||
<span class="skill">PostgreSQL Ops</span>
|
||||
<span class="skill">MinIO</span>
|
||||
<span class="skill">Caddy</span>
|
||||
<span class="skill">Renovate</span>
|
||||
<span class="skill">Flyway</span>
|
||||
</div>
|
||||
|
||||
<div class="columns">
|
||||
<div class="does-box">
|
||||
<div class="section-label">// does</div>
|
||||
<ul class="item-list">
|
||||
<li><span class="icon">✓</span> Docker Image-Tags auf spezifische Versionen pinnen</li>
|
||||
<li><span class="icon">✓</span> Health Checks für jeden Service definieren</li>
|
||||
<li><span class="icon">✓</span> Rollback-Strategie in jedem Deployment-Change</li>
|
||||
<li><span class="icon">✓</span> Komplexität mit konkreter Begründung rechtfertigen</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="never-box">
|
||||
<div class="never-label">⊘ hard limits</div>
|
||||
<ul class="item-list">
|
||||
<li><span class="icon">✗</span> Anwendungscode oder Business-Logik schreiben</li>
|
||||
<li><span class="icon">✗</span> Kafka/RabbitMQ empfehlen bevor PostgreSQL ausgeschöpft</li>
|
||||
<li><span class="icon">✗</span> Microservices ohne messbaren Scaling-Bedarf</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="review-box">
|
||||
<div class="section-label">// review focus</div>
|
||||
<div class="review-items">
|
||||
<span class="review-item">Fehlende Health Checks</span>
|
||||
<span class="review-item">Hardcoded Secrets</span>
|
||||
<span class="review-item">Ungepinnte Image-Tags</span>
|
||||
<span class="review-item">Kein Rollback-Plan</span>
|
||||
<span class="review-item">Premature Infra-Complexity</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
241
docs/presentation/personas/_card.css
Normal file
241
docs/presentation/personas/_card.css
Normal file
@@ -0,0 +1,241 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;700&display=swap');
|
||||
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
:root {
|
||||
--bg: #0D1117;
|
||||
--surface: #161B22;
|
||||
--surface-2: #1C2128;
|
||||
--border: #21262D;
|
||||
--text: #C9D1D9;
|
||||
--text-muted: #6E7681;
|
||||
--text-bright: #F0F6FC;
|
||||
--red: #F85149;
|
||||
--red-bg: rgba(248,81,73,0.07);
|
||||
--red-border: rgba(248,81,73,0.28);
|
||||
/* --accent set per file */
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-family: 'Inter', system-ui, sans-serif;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 28px;
|
||||
}
|
||||
|
||||
/* ── Card shell ── */
|
||||
.card {
|
||||
width: 100%;
|
||||
max-width: 1080px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* dot grid */
|
||||
.card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image: radial-gradient(circle, rgba(255,255,255,0.06) 1px, transparent 1px);
|
||||
background-size: 26px 26px;
|
||||
pointer-events: none;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
/* accent left stripe */
|
||||
.card-body {
|
||||
border-left: 4px solid var(--accent);
|
||||
padding: 0 32px 28px 32px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* ── Top bar ── */
|
||||
.topbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 14px 32px 14px 32px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
border-left: 4px solid var(--accent);
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.topbar-path { display: flex; align-items: center; gap: 6px; }
|
||||
.topbar-path .dir { color: var(--text-muted); }
|
||||
.topbar-path .file { color: var(--accent); font-weight: 500; }
|
||||
|
||||
.topbar-right { display: flex; align-items: center; gap: 20px; }
|
||||
.status { display: flex; align-items: center; gap: 5px; }
|
||||
.status-dot {
|
||||
width: 6px; height: 6px; border-radius: 50%;
|
||||
background: var(--accent);
|
||||
box-shadow: 0 0 6px var(--accent);
|
||||
animation: pulse 2.5s ease-in-out infinite;
|
||||
}
|
||||
@keyframes pulse {
|
||||
0%,100% { opacity: 1; }
|
||||
50% { opacity: 0.4; }
|
||||
}
|
||||
.project-label { color: var(--accent); font-weight: 700; letter-spacing: 1px; font-size: 10px; }
|
||||
|
||||
/* ── Header ── */
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
padding-top: 24px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
border-radius: 50%;
|
||||
background: color-mix(in srgb, var(--accent) 14%, transparent);
|
||||
border: 2px solid var(--accent);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
color: var(--accent);
|
||||
flex-shrink: 0;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
|
||||
.name-block {}
|
||||
.persona-name {
|
||||
font-size: 30px;
|
||||
font-weight: 800;
|
||||
color: var(--text-bright);
|
||||
letter-spacing: -0.5px;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.persona-sub {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-top: 5px;
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
.persona-sub .accent { color: var(--accent); }
|
||||
.persona-sub .sep { color: var(--border); }
|
||||
.persona-sub .handle { color: var(--text-muted); }
|
||||
|
||||
/* ── Quote ── */
|
||||
.quote {
|
||||
border-left: 3px solid var(--accent);
|
||||
padding: 10px 16px;
|
||||
margin-bottom: 20px;
|
||||
font-style: italic;
|
||||
font-size: 13.5px;
|
||||
line-height: 1.65;
|
||||
color: var(--text-muted);
|
||||
background: color-mix(in srgb, var(--accent) 5%, transparent);
|
||||
border-radius: 0 6px 6px 0;
|
||||
}
|
||||
.quote em { color: color-mix(in srgb, var(--accent) 80%, white); font-style: normal; }
|
||||
|
||||
/* ── Skills ── */
|
||||
.skills-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.skill {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
padding: 3px 10px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid color-mix(in srgb, var(--accent) 35%, transparent);
|
||||
background: color-mix(in srgb, var(--accent) 10%, transparent);
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
/* ── Two-column ── */
|
||||
.columns {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 14px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.section-label {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1.5px;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 9px;
|
||||
}
|
||||
|
||||
.does-box {
|
||||
background: var(--surface-2);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 7px;
|
||||
padding: 13px 14px;
|
||||
}
|
||||
.does-box .section-label { color: var(--accent); }
|
||||
|
||||
.item-list { list-style: none; display: flex; flex-direction: column; gap: 5px; }
|
||||
.item-list li {
|
||||
display: flex; align-items: flex-start; gap: 8px;
|
||||
font-size: 12.5px; line-height: 1.4; color: var(--text);
|
||||
}
|
||||
.item-list li .icon { flex-shrink: 0; font-style: normal; }
|
||||
.does-box .icon { color: var(--accent); }
|
||||
|
||||
.never-box {
|
||||
background: var(--red-bg);
|
||||
border: 1px solid var(--red-border);
|
||||
border-radius: 7px;
|
||||
padding: 13px 14px;
|
||||
}
|
||||
.never-label {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1.5px;
|
||||
color: var(--red);
|
||||
margin-bottom: 9px;
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
}
|
||||
.never-box .icon { color: var(--red); }
|
||||
|
||||
/* ── Review Focus ── */
|
||||
.review-box {
|
||||
background: color-mix(in srgb, var(--accent) 7%, transparent);
|
||||
border: 1px solid color-mix(in srgb, var(--accent) 28%, transparent);
|
||||
border-radius: 7px;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
.review-items {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 18px;
|
||||
margin-top: 7px;
|
||||
}
|
||||
.review-item {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
display: flex; align-items: center; gap: 5px;
|
||||
}
|
||||
.review-item::before { content: '→'; color: var(--accent); font-size: 11px; }
|
||||
1207
docs/specs/admin-tag-overhaul.html
Normal file
1207
docs/specs/admin-tag-overhaul.html
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,195 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Spec 1 — Rich Rows · Briefwechsel</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Merriweather:wght@400;700&family=Montserrat:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="_shared.css">
|
||||
<style>
|
||||
/* Spec 1 specific */
|
||||
.rlist{background:#fff;border:1px solid var(--line);border-radius:2px;overflow:hidden}
|
||||
.row{display:grid;grid-template-columns:20px minmax(0,1fr) auto;column-gap:12px;align-items:stretch;padding:14px 18px;border-bottom:1px solid var(--line-2);border-left:3px solid transparent;cursor:pointer;transition:background .1s}
|
||||
.row:hover{background:var(--muted)}
|
||||
.row:last-child{border-bottom:0}
|
||||
.row.out{border-left-color:var(--primary)}
|
||||
.row.in{border-left-color:var(--accent)}
|
||||
.row-arrow{align-self:center;font-size:14px;opacity:.55;display:flex;justify-content:center}
|
||||
.row-body{min-width:0;display:flex;flex-direction:column;gap:4px}
|
||||
.row-title{font-family:'Merriweather',serif;font-size:15px;font-weight:700;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.row-summary{font-size:12.5px;color:#555;font-style:italic;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:90%}
|
||||
.row-meta{display:flex;flex-wrap:wrap;gap:4px 10px;font-size:11.5px;color:var(--ink-3);align-items:center}
|
||||
.row-meta .sep{color:#bbb}
|
||||
.row-meta .ico{width:12px;height:12px;opacity:.55;display:inline-flex;align-items:center;justify-content:center}
|
||||
.row-tags{display:flex;flex-wrap:wrap;gap:4px;margin-top:2px}
|
||||
.row-right{display:flex;flex-direction:column;align-items:flex-end;justify-content:center;gap:4px;min-width:130px;padding-left:16px;border-left:1px dashed var(--line)}
|
||||
.row-archive{font-size:10px;font-weight:800;letter-spacing:.8px;color:#888;text-transform:uppercase;background:#F4F1EA;padding:3px 8px;border-radius:2px}
|
||||
.row-archive small{display:block;font-weight:600;color:#aaa;margin-top:1px;text-transform:none;letter-spacing:0;font-size:9.5px}
|
||||
@media (max-width: 900px){ .row-right{display:none} }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="spec-meta">
|
||||
<div class="spec-meta-inner">
|
||||
<div>
|
||||
<h1>Briefwechsel — <span>Fill the Empty Rows</span></h1>
|
||||
<p>Five approaches to turning the empty right-hand space into information that helps users scan and decide.</p>
|
||||
</div>
|
||||
<div class="spec-meta-right">
|
||||
<div><strong>Concept</strong>Rich Rows</div>
|
||||
<div><strong>Spec</strong>1 / 5</div>
|
||||
<div><strong>Effort</strong>Small — no new backend data</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="spec-nav">
|
||||
<div class="spec-nav-inner">
|
||||
<span class="lbl">Specs</span>
|
||||
<a href="index.html">Overview</a>
|
||||
<a class="on" href="01-rich-rows.html">1 · Rich Rows</a>
|
||||
<a href="02-thumbnail-rows.html">2 · Thumbnail Rows</a>
|
||||
<a href="03-master-detail.html">3 · Master-Detail Split</a>
|
||||
<a href="04-gallery-cards.html">4 · Gallery Cards</a>
|
||||
<a href="05-person-dashboard.html">5 · Person Dashboard</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="page-wrap">
|
||||
|
||||
<!-- Real Familienarchiv chrome -->
|
||||
<div class="hdr">
|
||||
<div class="hdr-logo">FAMILIENARCHIV</div>
|
||||
<div class="hdr-nav">
|
||||
<a>Documents</a><a>Persons</a><a class="on">Letters</a><a>Admin</a>
|
||||
</div>
|
||||
<div class="hdr-right">
|
||||
<div class="hdr-upload">⬆ UPLOAD</div>
|
||||
<span>DE · EN · ES</span>
|
||||
<div class="hdr-avatar">MR</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="page">
|
||||
|
||||
<div class="concept-intro">
|
||||
<h2>Concept 1 · Rich Rows — pack more metadata into each row</h2>
|
||||
No visuals, no structural change. Each row grows from a single line to a layered block: title (serif), summary (italic), meta row with icons, tag chips, and a right-hand column with archive box, script type and status.
|
||||
<div><span class="gain">✚ Zero backend changes</span><span class="gain">✚ Still one scrollable list</span><span class="cost">− Heavier rows; 10-row view becomes ~6–7 rows</span><span class="cost">− Empty-looking when a doc has no summary/tags</span></div>
|
||||
</div>
|
||||
|
||||
<!-- Filter card (same as production) -->
|
||||
<div class="card">
|
||||
<div class="filter-row">
|
||||
<div><div class="fl">Person</div><div class="fi">Walter de Gruyter</div></div>
|
||||
<div><div class="fl">Korrespondent — optional</div><div class="fi empty">Alle Korrespondenten</div></div>
|
||||
</div>
|
||||
<div class="filter-actions">
|
||||
<div class="btn">Newest ↓</div>
|
||||
<div class="btn">▾ Filter</div>
|
||||
<div class="count"><b>851</b> Briefe</div>
|
||||
</div>
|
||||
<div class="hintbar">📋 Alle Briefe von <b>Walter de Gruyter</b> — wähle einen Korrespondenten oben um einzugrenzen</div>
|
||||
</div>
|
||||
|
||||
<div class="rlist">
|
||||
<div class="year-divider"><span class="y">1940</span><span class="n">1 Brief</span></div>
|
||||
|
||||
<div class="row in">
|
||||
<div class="row-arrow">←</div>
|
||||
<div class="row-body">
|
||||
<div class="row-title">Demo leserlicher Brief</div>
|
||||
<div class="row-summary">„letzte Lebenstage von W. Dörpfeld in Griechenland"</div>
|
||||
<div class="row-meta"><span>31. Mai 1940</span><span class="sep">·</span><span>📍 Belgard</span><span class="sep">·</span><span>von <b>Gertrud von Rofden</b></span></div>
|
||||
<div class="row-tags"><span class="tag">Dörpfeld</span><span class="tag">Griechenland</span><span class="tag muted">privat</span></div>
|
||||
</div>
|
||||
<div class="row-right">
|
||||
<div class="row-archive">Kasten VII · Mappe 5</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="year-divider"><span class="y">1923</span><span class="n">5 Briefe</span></div>
|
||||
|
||||
<div class="row out">
|
||||
<div class="row-arrow">→</div>
|
||||
<div class="row-body">
|
||||
<div class="row-title">W-0397 – 2. September 1923 – B.Lichterfelde</div>
|
||||
<div class="row-summary">„von Elsbeth geschriebener Kommentar, den Herbert zum Brief erzählte"</div>
|
||||
<div class="row-meta"><span>2. September 1923</span><span class="sep">·</span><span>📍 B.Lichterfelde</span><span class="sep">·</span><span>an <b>Herbert Cram</b></span></div>
|
||||
<div class="row-tags"><span class="tag">Verlag</span><span class="tag">Familie</span></div>
|
||||
</div>
|
||||
<div class="row-right">
|
||||
<div class="row-archive">Kasten VI · Mappe 7</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row out">
|
||||
<div class="row-arrow">→</div>
|
||||
<div class="row-body">
|
||||
<div class="row-title">W-0396 – 2. September 1923 – B.Lichterfelde</div>
|
||||
<div class="row-summary">—</div>
|
||||
<div class="row-meta"><span>2. September 1923</span><span class="sep">·</span><span>📍 B.Lichterfelde</span><span class="sep">·</span><span>an <b>Herbert Cram</b></span></div>
|
||||
</div>
|
||||
<div class="row-right">
|
||||
<div class="row-archive">Kasten VI · Mappe 7</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row out">
|
||||
<div class="row-arrow">→</div>
|
||||
<div class="row-body">
|
||||
<div class="row-title">W-0524 – 31. Juli 1923 – Berlin</div>
|
||||
<div class="row-summary">„Glückwunsch zum 60. Geburtstag, Bericht über den Verlag"</div>
|
||||
<div class="row-meta"><span>31. Juli 1923</span><span class="sep">·</span><span>📍 Berlin</span><span class="sep">·</span><span>an <b>Walter Dieckmann</b></span></div>
|
||||
<div class="row-tags"><span class="tag">Geburtstag</span><span class="tag">Verlag</span></div>
|
||||
</div>
|
||||
<div class="row-right">
|
||||
<div class="row-archive">Kasten VI · Mappe 7</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="year-divider"><span class="y">1922</span><span class="n">37 Briefe</span></div>
|
||||
|
||||
<div class="row out">
|
||||
<div class="row-arrow">→</div>
|
||||
<div class="row-body">
|
||||
<div class="row-title">W-0521 – 24. Dezember 1922 – Berlin</div>
|
||||
<div class="row-summary">„Weihnachtsbrief, Erinnerungen an das Jahr und Bitte um ein Bild der Kinder"</div>
|
||||
<div class="row-meta"><span>24. Dezember 1922</span><span class="sep">·</span><span>📍 Berlin</span><span class="sep">·</span><span>an <b>Walter Dieckmann</b></span></div>
|
||||
<div class="row-tags"><span class="tag">Weihnachten</span><span class="tag">Familie</span><span class="tag muted">persönlich</span></div>
|
||||
</div>
|
||||
<div class="row-right">
|
||||
<div class="row-archive">Kasten V · Mappe 3</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row out">
|
||||
<div class="row-arrow">→</div>
|
||||
<div class="row-body">
|
||||
<div class="row-title">W-0392 – 23. November 1921 – Bad Kissingen</div>
|
||||
<div class="row-summary">„Kurbericht, Gesundheitsupdate, Grüße an die Familie Cram"</div>
|
||||
<div class="row-meta"><span>23. November 1921</span><span class="sep">·</span><span>📍 Bad Kissingen</span><span class="sep">·</span><span>an <b>Herbert Cram</b></span></div>
|
||||
<div class="row-tags"><span class="tag">Kuraufenthalt</span></div>
|
||||
</div>
|
||||
<div class="row-right">
|
||||
<div class="row-archive">Kasten V · Mappe 1</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row out">
|
||||
<div class="row-arrow">→</div>
|
||||
<div class="row-body">
|
||||
<div class="row-title">W-0501 – 13. Dezember 1920 – Berlin</div>
|
||||
<div class="row-summary">—</div>
|
||||
<div class="row-meta"><span>13. Dezember 1920</span><span class="sep">·</span><span>📍 Berlin</span><span class="sep">·</span><span>an <b>Walter Dieckmann</b></span></div>
|
||||
</div>
|
||||
<div class="row-right">
|
||||
<div class="row-archive">Kasten IV · Mappe 8</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,391 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Spec 2 — Thumbnail Rows · Briefwechsel</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Merriweather:wght@400;700&family=Montserrat:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="_shared.css">
|
||||
<style>
|
||||
/* Spec 2 v2 — bigger thumbnails, postcard support, bilateral distribution bar */
|
||||
.rlist{background:#fff;border:1px solid var(--line);border-radius:2px;overflow:hidden}
|
||||
|
||||
.row{display:grid;grid-template-columns:104px 1fr auto;column-gap:20px;align-items:center;padding:14px 20px;border-bottom:1px solid var(--line-2);border-left:3px solid transparent;cursor:pointer;transition:background .12s,box-shadow .12s}
|
||||
.row:hover{background:var(--muted)}
|
||||
.row:hover .row-thumb .thumb{transform:translateY(-1px);box-shadow:0 4px 12px rgba(0,0,0,.1),inset 0 0 0 1px #fff}
|
||||
.row:last-child{border-bottom:0}
|
||||
.row.out{border-left-color:var(--primary)}
|
||||
.row.in{border-left-color:var(--accent)}
|
||||
|
||||
/* Thumbnail wrapper — fixed 104×104 cell, thumb centered */
|
||||
.row-thumb{width:104px;height:120px;display:flex;align-items:center;justify-content:center;position:relative}
|
||||
.thumb{transition:transform .12s,box-shadow .12s;box-shadow:0 1px 3px rgba(0,0,0,.08),inset 0 0 0 1px #fff}
|
||||
.thumb.portrait{width:82px;height:106px}
|
||||
.thumb.landscape{width:104px;height:72px}
|
||||
.thumb.postcard{width:104px;height:66px}
|
||||
.thumb-badge{position:absolute;top:2px;right:0;background:var(--brand-navy);color:#fff;font-size:9px;font-weight:800;padding:2px 6px;border-radius:10px;box-shadow:0 0 0 2px #fff}
|
||||
|
||||
/* Subtle paper variations for natural feel */
|
||||
.thumb.paper-1{background:linear-gradient(180deg,#fdfcf7 0%,#f4efdf 100%)}
|
||||
.thumb.paper-2{background:linear-gradient(180deg,#fefdf8 0%,#eee8d3 100%)}
|
||||
.thumb.paper-3{background:linear-gradient(180deg,#fbf8ed 0%,#efe7cb 100%)}
|
||||
.thumb.paper-4{background:linear-gradient(180deg,#fdfcf5 0%,#f0e9d5 100%)}
|
||||
|
||||
/* Kurrent-style handwriting — denser, angled */
|
||||
.thumb.kurrent .thumb-lines{padding:14% 9%;gap:3.5px}
|
||||
.thumb.kurrent .thumb-lines i{height:1.3px;background:rgba(24,40,70,.45);transform:rotate(-.5deg)}
|
||||
.thumb.kurrent .thumb-lines i:nth-child(3n){width:65%}
|
||||
.thumb.kurrent .thumb-lines i:nth-child(4n){width:92%}
|
||||
.thumb.kurrent .thumb-lines i:nth-child(5n){width:48%;transform:rotate(.4deg)}
|
||||
|
||||
/* Typewriter — regular, crisp */
|
||||
.thumb.typed .thumb-lines{padding:16% 12%;gap:2.5px}
|
||||
.thumb.typed .thumb-lines i{height:1px;background:rgba(40,40,40,.45)}
|
||||
.thumb.typed .thumb-lines i:nth-child(odd){width:93%}
|
||||
.thumb.typed .thumb-lines i:nth-child(even){width:88%}
|
||||
.thumb.typed .thumb-lines i:nth-child(7n){width:45%}
|
||||
|
||||
/* Postcard — stamp corner + postmark + short address lines */
|
||||
.thumb.postcard .thumb-lines{padding:10% 10% 14% 10%;gap:4px}
|
||||
.thumb.postcard .thumb-lines i{height:1.1px;background:rgba(24,40,70,.45)}
|
||||
.thumb.postcard .thumb-lines i:nth-child(1){width:60%}
|
||||
.thumb.postcard .thumb-lines i:nth-child(2){width:45%}
|
||||
.thumb.postcard .thumb-lines i:nth-child(3){width:70%}
|
||||
.thumb.postcard .thumb-lines i:nth-child(4){width:40%}
|
||||
.thumb.postcard::after{content:'';position:absolute;top:6px;right:6px;width:16px;height:18px;background:linear-gradient(135deg,#b6c9d3,#8ba9b6);border:1px dashed rgba(0,0,0,.15);box-shadow:0 0 0 1px #fff}
|
||||
.thumb.postcard::before{content:'';position:absolute;top:10px;right:26px;width:14px;height:14px;border:1.5px solid rgba(150,30,30,.4);border-radius:50%;background:radial-gradient(circle,rgba(150,30,30,.1) 40%,transparent 60%)}
|
||||
|
||||
/* Letter heading (typed with date/address at top) */
|
||||
.thumb.typed::before{content:'';position:absolute;top:10%;left:12%;right:12%;height:2px;background:transparent;border-bottom:1.5px solid rgba(40,40,40,.35)}
|
||||
|
||||
.row-body{min-width:0;display:flex;flex-direction:column;gap:4px}
|
||||
.row-title{font-family:'Merriweather',serif;font-size:16px;font-weight:700;color:var(--ink);line-height:1.35;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.row-summary{font-family:'Merriweather',serif;font-size:14px;color:#444;line-height:1.55;font-style:italic;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
|
||||
.row-summary::before{content:'„';color:var(--brand-mint);font-size:22px;font-weight:700;line-height:0;position:relative;top:6px;margin-right:2px}
|
||||
.row-summary::after{content:'”';color:var(--brand-mint);font-size:22px;font-weight:700;line-height:0;position:relative;top:6px;margin-left:2px}
|
||||
.row-meta{display:flex;flex-wrap:wrap;gap:4px 12px;font-size:12px;color:var(--ink-3);align-items:center;margin-top:2px}
|
||||
.row-meta .sep{color:#ccc}
|
||||
.row-meta .dir-ch{color:var(--primary);font-weight:800;font-size:13px}
|
||||
.row-meta .dir-ch.in{color:var(--accent)}
|
||||
.row-meta .kind-chip{display:inline-flex;align-items:center;gap:3px;background:#F4F1EA;color:#666;font-size:10px;font-weight:700;padding:2px 7px;border-radius:10px;letter-spacing:.3px;text-transform:uppercase}
|
||||
.row-tags{display:flex;gap:4px;flex-wrap:wrap}
|
||||
|
||||
.row-right{display:flex;flex-direction:column;align-items:flex-end;gap:2px}
|
||||
.row-date{font-family:'Merriweather',serif;font-size:14px;color:#444;white-space:nowrap;font-weight:700}
|
||||
.row-date-rel{font-size:10.5px;color:#aaa;font-weight:600;letter-spacing:.3px}
|
||||
|
||||
/* Bilateral distribution bar — lifted from production ConversationTimeline */
|
||||
.distbar{display:flex;flex-direction:column;gap:6px;background:var(--muted);border:1px solid var(--line);border-bottom:0;padding:12px 20px}
|
||||
.distbar-labels{display:flex;justify-content:space-between;font-size:13px;font-weight:700}
|
||||
.distbar-labels .out{color:var(--primary);display:inline-flex;align-items:center;gap:6px}
|
||||
.distbar-labels .in{color:var(--accent);display:inline-flex;align-items:center;gap:6px}
|
||||
.distbar-labels .cnt{font-variant-numeric:tabular-nums}
|
||||
.distbar-bar{height:6px;display:flex;border-radius:3px;overflow:hidden;background:var(--line)}
|
||||
.distbar-bar .out{background:var(--primary)}
|
||||
.distbar-bar .in{background:var(--accent)}
|
||||
.distbar + .rlist{border-radius:0 0 2px 2px}
|
||||
|
||||
/* Section headings within the spec */
|
||||
.example-h{font-family:'Merriweather',serif;font-size:18px;color:var(--brand-navy);margin:36px 0 10px;padding-top:24px;border-top:1px dashed var(--line);font-weight:700;display:flex;align-items:baseline;gap:10px}
|
||||
.example-h .lbl{font-family:'Montserrat',sans-serif;font-size:10px;font-weight:800;color:#888;letter-spacing:1px;text-transform:uppercase}
|
||||
.example-h:first-of-type{border-top:0;padding-top:0;margin-top:20px}
|
||||
.example-sub{font-size:12px;color:#777;margin-bottom:14px;line-height:1.55}
|
||||
|
||||
/* Swap-buttons and filter chrome for bilateral filter card */
|
||||
.swap-inline{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border:1px solid #C8C4BE;border-radius:50%;background:#F0EDE8;font-size:13px;color:var(--brand-navy);margin:0 -12px;position:relative;z-index:1}
|
||||
|
||||
@media (max-width: 760px){
|
||||
.row{grid-template-columns:82px 1fr;column-gap:14px}
|
||||
.row-right{grid-column:2;align-items:flex-start;margin-top:4px}
|
||||
.thumb.portrait{width:72px;height:94px}
|
||||
.thumb.landscape, .thumb.postcard{width:82px;height:58px}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="spec-meta">
|
||||
<div class="spec-meta-inner">
|
||||
<div>
|
||||
<h1>Briefwechsel — <span>Fill the Empty Rows</span></h1>
|
||||
<p>Five approaches to turning the empty right-hand space into information that helps users scan and decide.</p>
|
||||
</div>
|
||||
<div class="spec-meta-right">
|
||||
<div><strong>Concept</strong>Thumbnail Rows <span style="color:var(--brand-mint);margin-left:6px">v2</span></div>
|
||||
<div><strong>Spec</strong>2 / 5</div>
|
||||
<div><strong>Effort</strong>Medium — needs PDF thumbnail service</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="spec-nav">
|
||||
<div class="spec-nav-inner">
|
||||
<span class="lbl">Specs</span>
|
||||
<a href="index.html">Overview</a>
|
||||
<a href="01-rich-rows.html">1 · Rich Rows</a>
|
||||
<a class="on" href="02-thumbnail-rows.html">2 · Thumbnail Rows</a>
|
||||
<a href="03-master-detail.html">3 · Master-Detail Split</a>
|
||||
<a href="04-gallery-cards.html">4 · Gallery Cards</a>
|
||||
<a href="05-person-dashboard.html">5 · Person Dashboard</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="page-wrap">
|
||||
|
||||
<div class="hdr">
|
||||
<div class="hdr-logo">FAMILIENARCHIV</div>
|
||||
<div class="hdr-nav"><a>Documents</a><a>Persons</a><a class="on">Letters</a><a>Admin</a></div>
|
||||
<div class="hdr-right"><div class="hdr-upload">⬆ UPLOAD</div><span>DE · EN · ES</span><div class="hdr-avatar">MR</div></div>
|
||||
</div>
|
||||
|
||||
<div class="page">
|
||||
|
||||
<div class="concept-intro">
|
||||
<h2>Concept 2 · Thumbnail Rows — discovery through visual + summary</h2>
|
||||
/briefwechsel is for fun discovery, not dense scanning. The row gets a bigger first-page thumbnail (portrait for letters, landscape for postcards); the <b>summary</b> reads like a quote next to it; the right column stays calm — just the date. Rows without a summary remain clean and uncrowded.
|
||||
<div><span class="gain">✚ Visual recognition — letters and postcards look like what they are</span><span class="gain">✚ Summary reads as a quote, invites opening the letter</span><span class="gain">✚ Distribution bar gives the bilateral pair its own identity</span><span class="cost">− Depends on the PDF-thumbnail service (open issue)</span></div>
|
||||
</div>
|
||||
|
||||
<!-- ───────── Example 1 · single person ───────── -->
|
||||
<div class="example-h">Beispiel 1 <span class="lbl">alle Briefe von Walter de Gruyter · 851</span></div>
|
||||
<div class="example-sub">Single-sender case: sender is filled, correspondent is open. Direction arrows tell sent vs received.</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="filter-row">
|
||||
<div><div class="fl">Person</div><div class="fi">Walter de Gruyter</div></div>
|
||||
<div><div class="fl">Korrespondent — optional</div><div class="fi empty">Alle Korrespondenten</div></div>
|
||||
</div>
|
||||
<div class="filter-actions">
|
||||
<div class="btn">Newest ↓</div><div class="btn">▾ Filter</div>
|
||||
<div class="count"><b>851</b> Briefe</div>
|
||||
</div>
|
||||
<div class="hintbar">📋 Alle Briefe von <b>Walter de Gruyter</b> — wähle einen Korrespondenten oben um einzugrenzen</div>
|
||||
</div>
|
||||
|
||||
<div class="rlist">
|
||||
<div class="year-divider"><span class="y">1940</span><span class="n">1 Brief</span></div>
|
||||
|
||||
<div class="row in">
|
||||
<div class="row-thumb">
|
||||
<div class="thumb portrait typed paper-1">
|
||||
<div class="thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-body">
|
||||
<div class="row-title">Demo leserlicher Brief</div>
|
||||
<div class="row-summary">letzte Lebenstage von W. Dörpfeld in Griechenland — ausführlicher Bericht aus Belgard mit persönlichen Anmerkungen</div>
|
||||
<div class="row-meta"><span class="dir-ch in">← eingehend</span><span>Gertrud von Rofden</span><span class="sep">·</span><span>📍 Belgard</span><span class="sep">·</span><span class="row-tags"><span class="tag">Dörpfeld</span><span class="tag">Griechenland</span></span></div>
|
||||
</div>
|
||||
<div class="row-right">
|
||||
<div class="row-date">31. Mai 1940</div>
|
||||
<div class="row-date-rel">vor 85 Jahren</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="year-divider"><span class="y">1923</span><span class="n">5 Briefe</span></div>
|
||||
|
||||
<div class="row out">
|
||||
<div class="row-thumb">
|
||||
<div class="thumb portrait kurrent paper-2">
|
||||
<div class="thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-body">
|
||||
<div class="row-title">W-0397 – 2. September 1923 – B.Lichterfelde</div>
|
||||
<div class="row-summary">von Elsbeth geschriebener Kommentar, den Herbert zum Brief erzählte — Notiz auf der Rückseite mit Korrekturen</div>
|
||||
<div class="row-meta"><span class="dir-ch">→ ausgehend</span><span>an Herbert Cram</span><span class="sep">·</span><span>📍 B.Lichterfelde</span><span class="sep">·</span><span class="row-tags"><span class="tag">Verlag</span><span class="tag">Familie</span></span></div>
|
||||
</div>
|
||||
<div class="row-right">
|
||||
<div class="row-date">2. September 1923</div>
|
||||
<div class="row-date-rel">vor 102 Jahren</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Postcard example -->
|
||||
<div class="row out">
|
||||
<div class="row-thumb">
|
||||
<div class="thumb postcard kurrent paper-4">
|
||||
<div class="thumb-lines"><i></i><i></i><i></i><i></i></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-body">
|
||||
<div class="row-title">Ansichtskarte – 2. September 1923 – B.Lichterfelde</div>
|
||||
<div class="row-summary">kurze Grüße aus B.Lichterfelde, Hinweis auf den kommenden Besuch</div>
|
||||
<div class="row-meta"><span class="dir-ch">→ ausgehend</span><span>an Herbert Cram</span><span class="sep">·</span><span>📍 B.Lichterfelde</span><span class="sep">·</span><span class="kind-chip">✉ Postkarte</span></div>
|
||||
</div>
|
||||
<div class="row-right">
|
||||
<div class="row-date">2. September 1923</div>
|
||||
<div class="row-date-rel">vor 102 Jahren</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Multi-page letter -->
|
||||
<div class="row out">
|
||||
<div class="row-thumb">
|
||||
<div class="thumb portrait kurrent paper-3">
|
||||
<div class="thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
|
||||
</div>
|
||||
<span class="thumb-badge">4 S.</span>
|
||||
</div>
|
||||
<div class="row-body">
|
||||
<div class="row-title">W-0524 – 31. Juli 1923 – Berlin</div>
|
||||
<div class="row-summary">Glückwunsch zum 60. Geburtstag, Bericht über den Verlag und den anstehenden Umzug nach B.Lichterfelde im kommenden Herbst</div>
|
||||
<div class="row-meta"><span class="dir-ch">→ ausgehend</span><span>an Walter Dieckmann</span><span class="sep">·</span><span>📍 Berlin</span><span class="sep">·</span><span class="row-tags"><span class="tag">Geburtstag</span><span class="tag">Verlag</span></span></div>
|
||||
</div>
|
||||
<div class="row-right">
|
||||
<div class="row-date">31. Juli 1923</div>
|
||||
<div class="row-date-rel">vor 102 Jahren</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Without summary — still clean -->
|
||||
<div class="row out">
|
||||
<div class="row-thumb">
|
||||
<div class="thumb portrait kurrent paper-1">
|
||||
<div class="thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-body">
|
||||
<div class="row-title">W-0396 – 2. September 1923 – B.Lichterfelde</div>
|
||||
<div class="row-meta"><span class="dir-ch">→ ausgehend</span><span>an Herbert Cram</span><span class="sep">·</span><span>📍 B.Lichterfelde</span></div>
|
||||
</div>
|
||||
<div class="row-right">
|
||||
<div class="row-date">2. September 1923</div>
|
||||
<div class="row-date-rel">vor 102 Jahren</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="year-divider"><span class="y">1922</span><span class="n">37 Briefe</span></div>
|
||||
|
||||
<div class="row out">
|
||||
<div class="row-thumb">
|
||||
<div class="thumb portrait kurrent paper-2">
|
||||
<div class="thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-body">
|
||||
<div class="row-title">W-0521 – 24. Dezember 1922 – Berlin</div>
|
||||
<div class="row-summary">Weihnachtsbrief, Erinnerungen an das Jahr und Bitte um ein Bild der Kinder zum Christfest</div>
|
||||
<div class="row-meta"><span class="dir-ch">→ ausgehend</span><span>an Walter Dieckmann</span><span class="sep">·</span><span>📍 Berlin</span><span class="sep">·</span><span class="row-tags"><span class="tag">Weihnachten</span><span class="tag">Familie</span></span></div>
|
||||
</div>
|
||||
<div class="row-right">
|
||||
<div class="row-date">24. Dezember 1922</div>
|
||||
<div class="row-date-rel">vor 103 Jahren</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ───────── Example 2 · bilateral ───────── -->
|
||||
<div class="example-h">Beispiel 2 <span class="lbl">Briefwechsel Walter ↔ Herbert · 143</span></div>
|
||||
<div class="example-sub">Bilateral case: both filters are set. The distribution bar above the list shows how the correspondence is split — instantly visible who wrote more.</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="filter-row">
|
||||
<div><div class="fl">Person</div><div class="fi">Walter de Gruyter</div></div>
|
||||
<div><div class="fl">Korrespondent</div><div class="fi">Herbert Cram</div></div>
|
||||
</div>
|
||||
<div class="filter-actions">
|
||||
<div class="btn">⇄ Tauschen</div>
|
||||
<div class="btn">Newest ↓</div><div class="btn">▾ Filter</div>
|
||||
<div class="count"><b>143</b> Briefe im Zeitraum</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="distbar" role="img" aria-label="Briefverteilung: 87 von Walter de Gruyter, 56 von Herbert Cram">
|
||||
<div class="distbar-labels">
|
||||
<span class="out"><span class="cnt">87</span> von Walter de Gruyter →</span>
|
||||
<span class="in">← <span class="cnt">56</span> von Herbert Cram</span>
|
||||
</div>
|
||||
<div class="distbar-bar">
|
||||
<span class="out" style="width:60.8%"></span>
|
||||
<span class="in" style="width:39.2%"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rlist" style="border-radius:0 0 2px 2px">
|
||||
<div class="year-divider"><span class="y">1923</span><span class="n">12 Briefe</span></div>
|
||||
|
||||
<div class="row out">
|
||||
<div class="row-thumb">
|
||||
<div class="thumb portrait kurrent paper-2">
|
||||
<div class="thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-body">
|
||||
<div class="row-title">W-0397 – 2. September 1923 – B.Lichterfelde</div>
|
||||
<div class="row-summary">von Elsbeth geschriebener Kommentar, den Herbert zum Brief erzählte</div>
|
||||
<div class="row-meta"><span class="dir-ch">→</span><span>Walter an Herbert</span><span class="sep">·</span><span>📍 B.Lichterfelde</span><span class="sep">·</span><span class="row-tags"><span class="tag">Verlag</span></span></div>
|
||||
</div>
|
||||
<div class="row-right">
|
||||
<div class="row-date">2. September 1923</div>
|
||||
<div class="row-date-rel">vor 102 Jahren</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row in">
|
||||
<div class="row-thumb">
|
||||
<div class="thumb portrait kurrent paper-3">
|
||||
<div class="thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-body">
|
||||
<div class="row-title">H-0213 – 29. August 1923 – Leipzig</div>
|
||||
<div class="row-summary">Antwort auf Walters Anfrage zur Herbstauslieferung, Bitte um Rückmeldung bis Monatsende</div>
|
||||
<div class="row-meta"><span class="dir-ch in">←</span><span>Herbert an Walter</span><span class="sep">·</span><span>📍 Leipzig</span><span class="sep">·</span><span class="row-tags"><span class="tag">Verlag</span></span></div>
|
||||
</div>
|
||||
<div class="row-right">
|
||||
<div class="row-date">29. August 1923</div>
|
||||
<div class="row-date-rel">vor 102 Jahren</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row in">
|
||||
<div class="row-thumb">
|
||||
<div class="thumb postcard kurrent paper-4">
|
||||
<div class="thumb-lines"><i></i><i></i><i></i><i></i></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row-body">
|
||||
<div class="row-title">Ansichtskarte – 20. August 1923 – Thüringer Wald</div>
|
||||
<div class="row-summary">Urlaubsgruß, kurze Notiz über Wetter und geplante Rückkehr</div>
|
||||
<div class="row-meta"><span class="dir-ch in">←</span><span>Herbert an Walter</span><span class="sep">·</span><span>📍 Thüringer Wald</span><span class="sep">·</span><span class="kind-chip">✉ Postkarte</span></div>
|
||||
</div>
|
||||
<div class="row-right">
|
||||
<div class="row-date">20. August 1923</div>
|
||||
<div class="row-date-rel">vor 102 Jahren</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row out">
|
||||
<div class="row-thumb">
|
||||
<div class="thumb portrait kurrent paper-1">
|
||||
<div class="thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
|
||||
</div>
|
||||
<span class="thumb-badge">3 S.</span>
|
||||
</div>
|
||||
<div class="row-body">
|
||||
<div class="row-title">W-0392 – 23. November 1921 – Bad Kissingen</div>
|
||||
<div class="row-summary">Kurbericht aus Bad Kissingen, Gesundheitsupdate nach der ersten Woche, Grüße an die Familie Cram</div>
|
||||
<div class="row-meta"><span class="dir-ch">→</span><span>Walter an Herbert</span><span class="sep">·</span><span>📍 Bad Kissingen</span><span class="sep">·</span><span class="row-tags"><span class="tag">Kuraufenthalt</span></span></div>
|
||||
</div>
|
||||
<div class="row-right">
|
||||
<div class="row-date">23. November 1921</div>
|
||||
<div class="row-date-rel">vor 104 Jahren</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Notes footer -->
|
||||
<div style="margin-top:32px;padding:16px 20px;background:#fff;border-left:4px solid var(--brand-navy);font-size:13px;color:#333;line-height:1.7">
|
||||
<b style="color:var(--brand-navy)">Details:</b>
|
||||
<ul style="margin:8px 0 0 20px;padding:0">
|
||||
<li><b>Thumbnail</b> — 82×106 for portrait, 104×72 for landscape/postcards. Postcards also get a stamp + postmark corner. Kurrent handwriting rendered with slight line skew; typewriter rendered with clean parallel lines. Multi-page letters get a "<code>4 S.</code>" badge.</li>
|
||||
<li><b>Summary</b> — shown in serif italic with colored quote marks. Reads like a quote from the letter. If empty, the row simply omits the line — no apologetic placeholder.</li>
|
||||
<li><b>Right column</b> — date only, in serif. We dropped archive box (only meaningful for one family archive) and any lookup metadata. The right column stays calm on purpose.</li>
|
||||
<li><b>Distribution bar</b> — appears only in bilateral mode (both sender and receiver set). Pattern lifted from the existing <code>ConversationTimeline</code> so it's familiar.</li>
|
||||
<li><b>Mobile</b> — thumbnail shrinks (72×94 portrait / 82×58 landscape) and the right column wraps under the body.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,222 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Spec 3 — Master-Detail · Briefwechsel</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Merriweather:wght@400;700&family=Montserrat:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="_shared.css">
|
||||
<style>
|
||||
/* Spec 3 specific */
|
||||
.split{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.15fr);gap:24px;align-items:start}
|
||||
@media (max-width: 1100px){ .split{grid-template-columns:1fr} .preview-col{position:static !important} }
|
||||
|
||||
/* List (compact) */
|
||||
.rlist{background:#fff;border:1px solid var(--line);border-radius:2px;overflow:hidden}
|
||||
.row{display:grid;grid-template-columns:16px 1fr auto;column-gap:10px;align-items:center;padding:10px 14px;border-bottom:1px solid var(--line-2);border-left:3px solid transparent;cursor:pointer}
|
||||
.row:hover{background:var(--muted)}
|
||||
.row.out{border-left-color:var(--primary)}
|
||||
.row.in{border-left-color:var(--accent)}
|
||||
.row.sel{background:#e7f4f3;border-left-color:var(--brand-mint);box-shadow:inset 2px 0 0 var(--accent)}
|
||||
.row-arrow{font-size:13px;opacity:.55}
|
||||
.row-body{min-width:0}
|
||||
.row-title{font-family:'Merriweather',serif;font-size:13.5px;font-weight:700;color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:2px}
|
||||
.row-sub{font-size:11px;color:var(--ink-3);display:flex;gap:6px;align-items:center}
|
||||
.row-sub .sep{color:#ccc}
|
||||
.row-right{font-size:11px;color:var(--ink-3);display:flex;align-items:center;gap:6px}
|
||||
|
||||
/* Preview panel */
|
||||
.preview-col{position:sticky;top:20px}
|
||||
.preview{background:#fff;border:1px solid var(--line);border-radius:2px;overflow:hidden;box-shadow:0 2px 8px rgba(0,0,0,.04)}
|
||||
.prev-hdr{padding:16px 20px;background:#fafaf5;border-bottom:1px solid var(--line);display:flex;justify-content:space-between;align-items:flex-start;gap:12px}
|
||||
.prev-hdr-left .prev-kind{font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:1px;color:var(--accent);margin-bottom:4px}
|
||||
.prev-hdr-left h2{font-family:'Merriweather',serif;font-size:18px;color:var(--brand-navy);line-height:1.35}
|
||||
.prev-hdr .close{font-size:18px;color:#999;cursor:pointer}
|
||||
.prev-body{display:grid;grid-template-columns:200px 1fr;gap:20px;padding:20px}
|
||||
.prev-thumb{width:200px;height:260px;flex-shrink:0}
|
||||
.prev-thumb .thumb{width:100%;height:100%}
|
||||
.prev-meta{display:flex;flex-direction:column;gap:14px}
|
||||
.mkv{display:grid;grid-template-columns:92px 1fr;row-gap:6px;column-gap:10px;font-size:12px;color:#444}
|
||||
.mkv .k{font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:.6px;color:#888;padding-top:2px}
|
||||
.mkv .v{color:var(--ink)}
|
||||
.mkv .v b{color:var(--brand-navy)}
|
||||
.prev-summary{background:#fbfaf5;border-left:3px solid var(--brand-mint);padding:10px 14px;font-family:'Merriweather',serif;font-size:13px;color:#333;line-height:1.65;font-style:italic}
|
||||
.prev-tags{display:flex;flex-wrap:wrap;gap:5px}
|
||||
.prev-excerpt{padding:0 20px 18px;font-family:'Merriweather',serif;font-size:13px;color:#444;line-height:1.75;border-top:1px dashed var(--line);padding-top:18px}
|
||||
.prev-excerpt .lbl{font-family:'Montserrat',sans-serif;font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:1px;color:#888;margin-bottom:8px;display:block;font-style:normal}
|
||||
.prev-actions{border-top:1px solid var(--line);padding:12px 20px;background:#fafaf5;display:flex;gap:8px;justify-content:flex-end}
|
||||
.prev-actions .btn.primary{background:var(--brand-navy);color:#fff;border-color:var(--brand-navy)}
|
||||
.prev-hint{padding:14px 20px;background:#f7f5f2;color:#777;font-size:11.5px;border-top:1px dashed var(--line);font-style:italic}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="spec-meta">
|
||||
<div class="spec-meta-inner">
|
||||
<div>
|
||||
<h1>Briefwechsel — <span>Fill the Empty Rows</span></h1>
|
||||
<p>Five approaches to turning the empty right-hand space into information that helps users scan and decide.</p>
|
||||
</div>
|
||||
<div class="spec-meta-right">
|
||||
<div><strong>Concept</strong>Master-Detail Split</div>
|
||||
<div><strong>Spec</strong>3 / 5</div>
|
||||
<div><strong>Effort</strong>Medium — requires selection state</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="spec-nav">
|
||||
<div class="spec-nav-inner">
|
||||
<span class="lbl">Specs</span>
|
||||
<a href="index.html">Overview</a>
|
||||
<a href="01-rich-rows.html">1 · Rich Rows</a>
|
||||
<a href="02-thumbnail-rows.html">2 · Thumbnail Rows</a>
|
||||
<a class="on" href="03-master-detail.html">3 · Master-Detail Split</a>
|
||||
<a href="04-gallery-cards.html">4 · Gallery Cards</a>
|
||||
<a href="05-person-dashboard.html">5 · Person Dashboard</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="page-wrap">
|
||||
|
||||
<div class="hdr">
|
||||
<div class="hdr-logo">FAMILIENARCHIV</div>
|
||||
<div class="hdr-nav"><a>Documents</a><a>Persons</a><a class="on">Letters</a><a>Admin</a></div>
|
||||
<div class="hdr-right"><div class="hdr-upload">⬆ UPLOAD</div><span>DE · EN · ES</span><div class="hdr-avatar">MR</div></div>
|
||||
</div>
|
||||
|
||||
<div class="page">
|
||||
|
||||
<div class="concept-intro">
|
||||
<h2>Concept 3 · Master-Detail Split — list on left, preview panel on right</h2>
|
||||
Keep the list compact (it stays scannable). Add a persistent right-hand panel that fills the empty space with a preview of the selected letter — thumbnail, metadata, summary, and a transcription excerpt when available. Click a row to swap its content; "Open" button still navigates to the full document page.
|
||||
<div><span class="gain">✚ Users browse 10× more letters per session (no back-nav)</span><span class="gain">✚ Right column is always doing work</span><span class="cost">− On narrow viewports collapses to stacked</span><span class="cost">− Mobile pattern differs — needs sheet/drawer</span></div>
|
||||
</div>
|
||||
|
||||
<!-- Filter card -->
|
||||
<div class="card">
|
||||
<div class="filter-row">
|
||||
<div><div class="fl">Person</div><div class="fi">Walter de Gruyter</div></div>
|
||||
<div><div class="fl">Korrespondent — optional</div><div class="fi empty">Alle Korrespondenten</div></div>
|
||||
</div>
|
||||
<div class="filter-actions">
|
||||
<div class="btn">Newest ↓</div><div class="btn">▾ Filter</div>
|
||||
<div class="count"><b>851</b> Briefe</div>
|
||||
</div>
|
||||
<div class="hintbar">📋 Alle Briefe von <b>Walter de Gruyter</b> — wähle einen Korrespondenten oben um einzugrenzen</div>
|
||||
</div>
|
||||
|
||||
<div class="split">
|
||||
<!-- Master list -->
|
||||
<div class="rlist">
|
||||
<div class="year-divider"><span class="y">1940</span><span class="n">1 Brief</span></div>
|
||||
<div class="row in">
|
||||
<div class="row-arrow">←</div>
|
||||
<div class="row-body">
|
||||
<div class="row-title">Demo leserlicher Brief</div>
|
||||
<div class="row-sub"><span>31. Mai 1940</span><span class="sep">·</span><span>Belgard</span><span class="sep">·</span><span>Gertrud von Rofden</span></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="year-divider"><span class="y">1923</span><span class="n">5 Briefe</span></div>
|
||||
|
||||
<div class="row out sel">
|
||||
<div class="row-arrow">→</div>
|
||||
<div class="row-body">
|
||||
<div class="row-title">W-0397 – 2. September 1923 – B.Lichterfelde</div>
|
||||
<div class="row-sub"><span>2. Sep 1923</span><span class="sep">·</span><span>B.Lichterfelde</span><span class="sep">·</span><span>Herbert Cram</span></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row out">
|
||||
<div class="row-arrow">→</div>
|
||||
<div class="row-body">
|
||||
<div class="row-title">W-0396 – 2. September 1923 – B.Lichterfelde</div>
|
||||
<div class="row-sub"><span>2. Sep 1923</span><span class="sep">·</span><span>B.Lichterfelde</span><span class="sep">·</span><span>Herbert Cram</span></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row out">
|
||||
<div class="row-arrow">→</div>
|
||||
<div class="row-body">
|
||||
<div class="row-title">W-0524 – 31. Juli 1923 – Berlin</div>
|
||||
<div class="row-sub"><span>31. Juli 1923</span><span class="sep">·</span><span>Berlin</span><span class="sep">·</span><span>Walter Dieckmann</span></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row out">
|
||||
<div class="row-arrow">→</div>
|
||||
<div class="row-body">
|
||||
<div class="row-title">W-0523 – 12. Mai 1923 – Berlin</div>
|
||||
<div class="row-sub"><span>12. Mai 1923</span><span class="sep">·</span><span>Berlin</span><span class="sep">·</span><span>Walter Dieckmann</span></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="row out">
|
||||
<div class="row-arrow">→</div>
|
||||
<div class="row-body">
|
||||
<div class="row-title">W-0522 – 7. März 1923 – Lindau Bodensee</div>
|
||||
<div class="row-sub"><span>7. März 1923</span><span class="sep">·</span><span>Lindau Bodensee</span><span class="sep">·</span><span>Walter Dieckmann</span></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="year-divider"><span class="y">1922</span><span class="n">37 Briefe</span></div>
|
||||
|
||||
<div class="row out">
|
||||
<div class="row-arrow">→</div>
|
||||
<div class="row-body">
|
||||
<div class="row-title">W-0521 – 24. Dezember 1922 – Berlin</div>
|
||||
<div class="row-sub"><span>24. Dez 1922</span><span class="sep">·</span><span>Berlin</span><span class="sep">·</span><span>Walter Dieckmann</span></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Detail preview -->
|
||||
<div class="preview-col">
|
||||
<div class="preview">
|
||||
<div class="prev-hdr">
|
||||
<div class="prev-hdr-left">
|
||||
<div class="prev-kind">→ Ausgehend · an Herbert Cram</div>
|
||||
<h2>W-0397 – 2. September 1923 – B.Lichterfelde</h2>
|
||||
</div>
|
||||
<div class="close">✕</div>
|
||||
</div>
|
||||
<div class="prev-body">
|
||||
<div class="prev-thumb">
|
||||
<div class="thumb handwritten"><div class="thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div></div>
|
||||
</div>
|
||||
<div class="prev-meta">
|
||||
<div class="mkv">
|
||||
<div class="k">Datum</div><div class="v"><b>2. September 1923</b></div>
|
||||
<div class="k">Ort</div><div class="v">B.Lichterfelde</div>
|
||||
<div class="k">Absender</div><div class="v"><b>Walter de Gruyter</b></div>
|
||||
<div class="k">Empfänger</div><div class="v">Herbert Cram</div>
|
||||
<div class="k">Archiv</div><div class="v">Kasten VI · Mappe 7</div>
|
||||
</div>
|
||||
<div class="prev-summary">„von Elsbeth geschriebener Kommentar, den Herbert zum Brief erzählte — Notiz auf der Rückseite mit Korrekturen zur Publikationsliste."</div>
|
||||
<div class="prev-tags"><span class="tag">Verlag</span><span class="tag">Familie</span><span class="tag muted">Korrespondenz</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="prev-excerpt">
|
||||
<span class="lbl">Transkription (Auszug)</span>
|
||||
„Lieber Herbert, heute erreichte mich Dein Brief vom 29. August, und ich danke Dir herzlich für die ausführliche Schilderung. Die Angelegenheit mit dem Verlag soll am Montag abschließend besprochen werden — ich werde Dir sogleich Bescheid geben. In Belgard waren wir alle wohl, und die Kinder grüßen …"
|
||||
</div>
|
||||
<div class="prev-actions">
|
||||
<div class="btn">◎ In Briefwechsel öffnen</div>
|
||||
<div class="btn primary">↗ Vollständiges Dokument</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top:8px;font-size:11px;color:#888;padding:0 4px">Tipp: Pfeiltasten ↑↓ zum Durchblättern, Enter zum Öffnen.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,227 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Spec 4 — Gallery Cards · Briefwechsel</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Merriweather:wght@400;700&family=Montserrat:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="_shared.css">
|
||||
<style>
|
||||
/* Spec 4 specific */
|
||||
.view-toggle{display:flex;gap:4px;background:#F0EDE5;padding:3px;border-radius:3px;margin-left:8px}
|
||||
.view-toggle span{padding:5px 10px;font-size:11px;font-weight:700;color:#888;cursor:pointer;border-radius:2px}
|
||||
.view-toggle span.on{background:#fff;color:var(--brand-navy);box-shadow:0 1px 2px rgba(0,0,0,.08)}
|
||||
|
||||
.year-band{font-family:'Merriweather',serif;font-size:28px;font-weight:900;color:var(--brand-navy);letter-spacing:-.5px;margin:24px 0 10px 2px;display:flex;align-items:baseline;gap:12px}
|
||||
.year-band .n{font-family:'Montserrat',sans-serif;font-size:12px;font-weight:700;color:#888;letter-spacing:0}
|
||||
|
||||
.grid{display:grid;grid-template-columns:repeat(4, 1fr);gap:18px}
|
||||
@media (max-width: 1280px){ .grid{grid-template-columns:repeat(3, 1fr)} }
|
||||
@media (max-width: 900px){ .grid{grid-template-columns:repeat(2, 1fr)} }
|
||||
|
||||
.card-doc{background:#fff;border:1px solid var(--line);border-radius:2px;overflow:hidden;cursor:pointer;transition:transform .12s,box-shadow .12s;display:flex;flex-direction:column}
|
||||
.card-doc:hover{transform:translateY(-2px);box-shadow:0 6px 20px rgba(0,0,0,.08)}
|
||||
.card-doc .thumb-wrap{position:relative;aspect-ratio:3/4;overflow:hidden;background:#f5f2ea;border-bottom:1px solid var(--line)}
|
||||
.card-doc .thumb{width:100%;height:100%}
|
||||
.card-doc .dir-badge{position:absolute;top:8px;left:8px;background:rgba(0,40,80,.9);color:#fff;font-size:10px;font-weight:800;padding:3px 8px;border-radius:10px;display:flex;align-items:center;gap:3px;letter-spacing:.3px}
|
||||
.card-doc .dir-badge.in{background:rgba(47,158,149,.9)}
|
||||
.card-doc .body{padding:12px 14px 14px;display:flex;flex-direction:column;gap:4px;flex:1}
|
||||
.card-doc .body h3{font-family:'Merriweather',serif;font-size:13px;font-weight:700;color:var(--brand-navy);line-height:1.4;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
|
||||
.card-doc .body .date{font-size:11px;color:var(--ink-3);font-weight:600}
|
||||
.card-doc .body .party{font-size:11px;color:var(--ink-3);font-style:italic;margin-top:2px;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}
|
||||
.card-doc .body .summary{font-size:11.5px;color:#666;font-family:'Merriweather',serif;font-style:italic;line-height:1.45;margin-top:6px;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
|
||||
.card-doc .body .tags{margin-top:auto;padding-top:10px;display:flex;gap:4px;flex-wrap:wrap}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="spec-meta">
|
||||
<div class="spec-meta-inner">
|
||||
<div>
|
||||
<h1>Briefwechsel — <span>Fill the Empty Rows</span></h1>
|
||||
<p>Five approaches to turning the empty right-hand space into information that helps users scan and decide.</p>
|
||||
</div>
|
||||
<div class="spec-meta-right">
|
||||
<div><strong>Concept</strong>Gallery Cards</div>
|
||||
<div><strong>Spec</strong>4 / 5</div>
|
||||
<div><strong>Effort</strong>Large — structural change + thumbnail service</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="spec-nav">
|
||||
<div class="spec-nav-inner">
|
||||
<span class="lbl">Specs</span>
|
||||
<a href="index.html">Overview</a>
|
||||
<a href="01-rich-rows.html">1 · Rich Rows</a>
|
||||
<a href="02-thumbnail-rows.html">2 · Thumbnail Rows</a>
|
||||
<a href="03-master-detail.html">3 · Master-Detail Split</a>
|
||||
<a class="on" href="04-gallery-cards.html">4 · Gallery Cards</a>
|
||||
<a href="05-person-dashboard.html">5 · Person Dashboard</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="page-wrap">
|
||||
|
||||
<div class="hdr">
|
||||
<div class="hdr-logo">FAMILIENARCHIV</div>
|
||||
<div class="hdr-nav"><a>Documents</a><a>Persons</a><a class="on">Letters</a><a>Admin</a></div>
|
||||
<div class="hdr-right"><div class="hdr-upload">⬆ UPLOAD</div><span>DE · EN · ES</span><div class="hdr-avatar">MR</div></div>
|
||||
</div>
|
||||
|
||||
<div class="page">
|
||||
|
||||
<div class="concept-intro">
|
||||
<h2>Concept 4 · Gallery Cards — grid of letter cards, album style</h2>
|
||||
Replace the list with a 4-column (collapses to 3 → 2) grid. Each card is a miniature letter card with thumbnail, title, date, correspondent, and a 3-line summary. Year bands replace dividers. The page becomes a family-letter-album.
|
||||
<div><span class="gain">✚ Beautiful browsing for archive overview</span><span class="gain">✚ Most "filled" of all concepts — no empty space</span><span class="cost">− Chronological scanning is worse than lists</span><span class="cost">− 851 letters → ~213 rows in grid, long page or needs paging</span></div>
|
||||
</div>
|
||||
|
||||
<!-- Filter card -->
|
||||
<div class="card">
|
||||
<div class="filter-row">
|
||||
<div><div class="fl">Person</div><div class="fi">Walter de Gruyter</div></div>
|
||||
<div><div class="fl">Korrespondent — optional</div><div class="fi empty">Alle Korrespondenten</div></div>
|
||||
</div>
|
||||
<div class="filter-actions">
|
||||
<div class="btn">Newest ↓</div><div class="btn">▾ Filter</div>
|
||||
<div class="view-toggle"><span>☰ Liste</span><span class="on">▦ Galerie</span></div>
|
||||
<div class="count"><b>851</b> Briefe</div>
|
||||
</div>
|
||||
<div class="hintbar">📋 Alle Briefe von <b>Walter de Gruyter</b> — wähle einen Korrespondenten oben um einzugrenzen</div>
|
||||
</div>
|
||||
|
||||
<div class="year-band">1940 <span class="n">1 Brief</span></div>
|
||||
<div class="grid">
|
||||
<div class="card-doc">
|
||||
<div class="thumb-wrap">
|
||||
<div class="thumb"><div class="thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div></div>
|
||||
<span class="dir-badge in">← eingehend</span>
|
||||
</div>
|
||||
<div class="body">
|
||||
<h3>Demo leserlicher Brief</h3>
|
||||
<div class="date">31. Mai 1940 · Belgard</div>
|
||||
<div class="party">von <b>Gertrud von Rofden</b></div>
|
||||
<div class="summary">„letzte Lebenstage von W. Dörpfeld in Griechenland — ausführlicher Bericht"</div>
|
||||
<div class="tags"><span class="tag">Dörpfeld</span><span class="tag">Griechenland</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="year-band">1923 <span class="n">5 Briefe</span></div>
|
||||
<div class="grid">
|
||||
<div class="card-doc">
|
||||
<div class="thumb-wrap">
|
||||
<div class="thumb handwritten"><div class="thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div></div>
|
||||
<span class="dir-badge">→ ausgehend</span>
|
||||
</div>
|
||||
<div class="body">
|
||||
<h3>W-0397 – 2. September 1923 – B.Lichterfelde</h3>
|
||||
<div class="date">2. September 1923 · B.Lichterfelde</div>
|
||||
<div class="party">an <b>Herbert Cram</b></div>
|
||||
<div class="summary">„von Elsbeth geschriebener Kommentar, den Herbert zum Brief erzählte"</div>
|
||||
<div class="tags"><span class="tag">Verlag</span><span class="tag">Familie</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-doc">
|
||||
<div class="thumb-wrap">
|
||||
<div class="thumb handwritten"><div class="thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i></div></div>
|
||||
<span class="dir-badge">→ ausgehend</span>
|
||||
</div>
|
||||
<div class="body">
|
||||
<h3>W-0396 – 2. September 1923 – B.Lichterfelde</h3>
|
||||
<div class="date">2. September 1923 · B.Lichterfelde</div>
|
||||
<div class="party">an <b>Herbert Cram</b></div>
|
||||
<div class="tags"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-doc">
|
||||
<div class="thumb-wrap">
|
||||
<div class="thumb handwritten"><div class="thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div></div>
|
||||
<span class="dir-badge">→ ausgehend</span>
|
||||
</div>
|
||||
<div class="body">
|
||||
<h3>W-0524 – 31. Juli 1923 – Berlin</h3>
|
||||
<div class="date">31. Juli 1923 · Berlin</div>
|
||||
<div class="party">an <b>Walter Dieckmann</b></div>
|
||||
<div class="summary">„Glückwunsch zum 60. Geburtstag, Bericht über den Verlag"</div>
|
||||
<div class="tags"><span class="tag">Geburtstag</span><span class="tag">Verlag</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-doc">
|
||||
<div class="thumb-wrap">
|
||||
<div class="thumb handwritten"><div class="thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div></div>
|
||||
<span class="dir-badge">→ ausgehend</span>
|
||||
</div>
|
||||
<div class="body">
|
||||
<h3>W-0523 – 12. Mai 1923 – Berlin</h3>
|
||||
<div class="date">12. Mai 1923 · Berlin</div>
|
||||
<div class="party">an <b>Walter Dieckmann</b></div>
|
||||
<div class="summary">„Routinebericht des Verlagsgeschäfts, Rückfragen zu Auftragslage"</div>
|
||||
<div class="tags"><span class="tag">Verlag</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="year-band">1922 <span class="n">37 Briefe</span></div>
|
||||
<div class="grid">
|
||||
<div class="card-doc">
|
||||
<div class="thumb-wrap">
|
||||
<div class="thumb handwritten"><div class="thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div></div>
|
||||
<span class="dir-badge">→ ausgehend</span>
|
||||
</div>
|
||||
<div class="body">
|
||||
<h3>W-0521 – 24. Dezember 1922 – Berlin</h3>
|
||||
<div class="date">24. Dezember 1922 · Berlin</div>
|
||||
<div class="party">an <b>Walter Dieckmann</b></div>
|
||||
<div class="summary">„Weihnachtsbrief, Erinnerungen an das Jahr und Bitte um ein Bild der Kinder"</div>
|
||||
<div class="tags"><span class="tag">Weihnachten</span><span class="tag">Familie</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-doc">
|
||||
<div class="thumb-wrap">
|
||||
<div class="thumb handwritten"><div class="thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i></div></div>
|
||||
<span class="dir-badge">→ ausgehend</span>
|
||||
</div>
|
||||
<div class="body">
|
||||
<h3>W-0392 – 23. Nov 1921 – Bad Kissingen</h3>
|
||||
<div class="date">23. November 1921 · Bad Kissingen</div>
|
||||
<div class="party">an <b>Herbert Cram</b></div>
|
||||
<div class="summary">„Kurbericht, Gesundheitsupdate, Grüße an die Familie"</div>
|
||||
<div class="tags"><span class="tag">Kuraufenthalt</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-doc">
|
||||
<div class="thumb-wrap">
|
||||
<div class="thumb"><div class="thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div></div>
|
||||
<span class="dir-badge">→ ausgehend</span>
|
||||
</div>
|
||||
<div class="body">
|
||||
<h3>W-0501 – 13. Dez 1920 – Berlin</h3>
|
||||
<div class="date">13. Dezember 1920 · Berlin</div>
|
||||
<div class="party">an <b>Walter Dieckmann</b></div>
|
||||
<div class="tags"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-doc">
|
||||
<div class="thumb-wrap">
|
||||
<div class="thumb handwritten"><div class="thumb-lines"><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div></div>
|
||||
<span class="dir-badge">→ ausgehend</span>
|
||||
</div>
|
||||
<div class="body">
|
||||
<h3>W-0484 – 27. Dez 1919 – Berlin</h3>
|
||||
<div class="date">27. Dezember 1919 · Berlin</div>
|
||||
<div class="party">an <b>Walter Dieckmann</b></div>
|
||||
<div class="summary">„Jahreswechselgrüße, kurzer Bericht über den Zustand des Verlags"</div>
|
||||
<div class="tags"><span class="tag">Neujahr</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,303 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Spec 5 — Person Dashboard · Briefwechsel Insights</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Merriweather:wght@400;700&family=Montserrat:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="_shared.css">
|
||||
<style>
|
||||
/* Spec 5 specific */
|
||||
.person-grid{display:grid;grid-template-columns:35% 1fr;gap:32px;align-items:start}
|
||||
@media (max-width: 1100px){ .person-grid{grid-template-columns:1fr} }
|
||||
|
||||
/* Person card (left) */
|
||||
.pcard{background:#fff;border:1px solid var(--line);border-radius:2px;padding:24px;display:flex;flex-direction:column;align-items:center;gap:14px}
|
||||
.pavatar{width:100px;height:100px;border-radius:50%;background:var(--brand-mint);color:var(--brand-navy);display:flex;align-items:center;justify-content:center;font-size:36px;font-weight:900;font-family:'Merriweather',serif}
|
||||
.pname{font-family:'Merriweather',serif;font-size:22px;font-weight:700;color:var(--brand-navy);text-align:center;line-height:1.3}
|
||||
.pdates{font-size:12px;color:#888;font-weight:600;letter-spacing:.5px}
|
||||
.pnotes{font-size:13px;color:#555;line-height:1.6;margin-top:6px;padding-top:14px;border-top:1px dashed var(--line);width:100%;font-family:'Merriweather',serif;font-style:italic}
|
||||
.pactions{margin-top:14px;display:flex;gap:8px;width:100%}
|
||||
.pactions .btn{flex:1;justify-content:center;font-size:10px}
|
||||
.pactions .btn.primary{background:var(--brand-navy);color:#fff;border-color:var(--brand-navy)}
|
||||
|
||||
/* Name history card */
|
||||
.ncard{background:#fff;border:1px solid var(--line);border-radius:2px;padding:18px 20px;margin-top:20px}
|
||||
.ncard h3{font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:1.2px;color:#888;margin-bottom:10px}
|
||||
.ncard ul{list-style:none}
|
||||
.ncard li{padding:5px 0;font-size:12.5px;color:#444;border-top:1px dashed var(--line);display:flex;justify-content:space-between}
|
||||
.ncard li:first-child{border-top:0}
|
||||
|
||||
/* Right column */
|
||||
.dash{background:#fff;border:1px solid var(--line);border-radius:2px;overflow:hidden}
|
||||
.dash-hdr{background:var(--brand-navy);color:#fff;padding:14px 20px;display:flex;justify-content:space-between;align-items:center}
|
||||
.dash-hdr h2{font-family:'Merriweather',serif;font-size:16px;font-weight:700}
|
||||
.dash-hdr .open-conv{background:var(--brand-mint);color:var(--brand-navy);font-size:11px;font-weight:800;padding:6px 14px;border-radius:2px;text-transform:uppercase;letter-spacing:.6px;text-decoration:none}
|
||||
|
||||
/* Stat strip */
|
||||
.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;background:var(--line-2);border-bottom:1px solid var(--line)}
|
||||
.stats div{background:#fafaf5;padding:14px 18px;text-align:center}
|
||||
.stats .v{font-family:'Merriweather',serif;font-size:22px;font-weight:900;color:var(--brand-navy);letter-spacing:-.5px}
|
||||
.stats .k{font-size:10px;color:#888;font-weight:700;text-transform:uppercase;letter-spacing:.6px;margin-top:2px}
|
||||
.stats .out{color:var(--primary)}
|
||||
.stats .in{color:var(--accent)}
|
||||
|
||||
/* Sections */
|
||||
.dsec{padding:18px 22px;border-top:1px solid var(--line-2)}
|
||||
.dsec:first-of-type{border-top:0}
|
||||
.dsec h3{font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:1.2px;color:#888;margin-bottom:12px;display:flex;justify-content:space-between;align-items:baseline}
|
||||
.dsec h3 .note{font-size:11px;color:#555;text-transform:none;letter-spacing:0;font-weight:600}
|
||||
|
||||
/* Activity histogram */
|
||||
.hist{display:flex;align-items:flex-end;gap:2px;height:90px;padding:4px 0 0}
|
||||
.hist .bar{flex:1;background:var(--brand-mint);opacity:.55;border-radius:1px 1px 0 0;position:relative;cursor:pointer;transition:opacity .12s}
|
||||
.hist .bar:hover{opacity:1}
|
||||
.hist .bar.peak{background:var(--brand-navy);opacity:.85}
|
||||
.hist-labels{display:flex;justify-content:space-between;font-size:10px;color:#888;margin-top:6px;font-weight:700}
|
||||
|
||||
/* Split bar direction */
|
||||
.dsplit{display:flex;justify-content:space-between;font-size:12px;font-weight:700;margin-bottom:8px}
|
||||
.dsplit .out{color:var(--primary)}
|
||||
.dsplit .in{color:var(--accent)}
|
||||
.dbar{height:10px;display:flex;border-radius:5px;overflow:hidden;background:#F0EDE5}
|
||||
.dbar .out{background:var(--brand-navy)}
|
||||
.dbar .in{background:var(--accent)}
|
||||
|
||||
/* Top list */
|
||||
.toplist{display:flex;flex-direction:column;gap:8px}
|
||||
.toplist .ti{display:flex;align-items:center;gap:10px;font-size:13px;padding:4px 6px;border-radius:2px;cursor:pointer}
|
||||
.toplist .ti:hover{background:var(--muted)}
|
||||
.toplist .ti .name{flex:1;color:var(--ink);font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.toplist .ti .bar-wrap{width:120px;height:7px;background:#F0EDE5;border-radius:4px;overflow:hidden;flex-shrink:0}
|
||||
.toplist .ti .bar{height:100%;background:var(--brand-navy);border-radius:4px}
|
||||
.toplist .ti .val{width:38px;text-align:right;font-size:12px;color:#888;font-weight:700;font-variant-numeric:tabular-nums}
|
||||
.toplist .ti .dir{font-size:13px;width:16px;font-weight:800}
|
||||
.toplist .ti .dir.out{color:var(--primary)}
|
||||
.toplist .ti .dir.in{color:var(--accent)}
|
||||
.toplist .ti .dir.both{color:#888}
|
||||
|
||||
/* Cloud */
|
||||
.cloud{display:flex;flex-wrap:wrap;gap:6px}
|
||||
.cloud .tag{cursor:pointer;padding:3px 10px;border-radius:12px;font-weight:700;transition:transform .1s}
|
||||
.cloud .tag:hover{transform:translateY(-1px)}
|
||||
.cloud .tag.s-xl{font-size:15px;padding:4px 12px}
|
||||
.cloud .tag.s-l{font-size:13px}
|
||||
.cloud .tag.s-m{font-size:12px}
|
||||
.cloud .tag.s-s{font-size:11px}
|
||||
|
||||
/* Two-col arrangement */
|
||||
.twocol{display:grid;grid-template-columns:1fr 1fr;gap:32px}
|
||||
@media (max-width: 900px){ .twocol{grid-template-columns:1fr} }
|
||||
|
||||
/* Existing doc lists below (compressed) */
|
||||
.doclist-card{background:#fff;border:1px solid var(--line);border-radius:2px;margin-top:20px;overflow:hidden}
|
||||
.doclist-card .dh{padding:14px 20px;border-bottom:1px solid var(--line-2);font-size:13px;font-weight:700;color:var(--brand-navy);display:flex;justify-content:space-between;align-items:center}
|
||||
.doclist-card .dh .cnt{font-size:11px;color:#888;font-weight:600}
|
||||
.doclist-card .di{padding:10px 20px;border-bottom:1px solid var(--line-2);display:flex;justify-content:space-between;align-items:center;font-size:12.5px}
|
||||
.doclist-card .di:last-child{border-bottom:0}
|
||||
.doclist-card .di .t{font-family:'Merriweather',serif;font-weight:700;color:var(--ink);min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;flex:1;margin-right:10px}
|
||||
.doclist-card .di .m{color:#888;font-size:11px;white-space:nowrap}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="spec-meta">
|
||||
<div class="spec-meta-inner">
|
||||
<div>
|
||||
<h1>Briefwechsel — <span>Fill the Empty Rows</span></h1>
|
||||
<p>Insights belong on the person detail page, not on the letter list. This spec mocks the dashboard that replaces the "empty feeling" problem at its proper home.</p>
|
||||
</div>
|
||||
<div class="spec-meta-right">
|
||||
<div><strong>Concept</strong>Person Dashboard</div>
|
||||
<div><strong>Spec</strong>5 / 5</div>
|
||||
<div><strong>Page</strong>/persons/[id] (not /briefwechsel)</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="spec-nav">
|
||||
<div class="spec-nav-inner">
|
||||
<span class="lbl">Specs</span>
|
||||
<a href="index.html">Overview</a>
|
||||
<a href="01-rich-rows.html">1 · Rich Rows</a>
|
||||
<a href="02-thumbnail-rows.html">2 · Thumbnail Rows</a>
|
||||
<a href="03-master-detail.html">3 · Master-Detail Split</a>
|
||||
<a href="04-gallery-cards.html">4 · Gallery Cards</a>
|
||||
<a class="on" href="05-person-dashboard.html">5 · Person Dashboard</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="page-wrap">
|
||||
|
||||
<div class="hdr">
|
||||
<div class="hdr-logo">FAMILIENARCHIV</div>
|
||||
<div class="hdr-nav"><a>Documents</a><a class="on">Persons</a><a>Letters</a><a>Admin</a></div>
|
||||
<div class="hdr-right"><div class="hdr-upload">⬆ UPLOAD</div><span>DE · EN · ES</span><div class="hdr-avatar">MR</div></div>
|
||||
</div>
|
||||
|
||||
<div class="page">
|
||||
|
||||
<div class="concept-intro">
|
||||
<h2>Concept 5 · Person Dashboard — insights live on /persons/[id], not on /briefwechsel</h2>
|
||||
The /briefwechsel page stays focused on reading letters. The archive-level understanding (how much, with whom, when, about what) is moved to the person detail page, where it's useful in all contexts — not only when a correspondent is selected. A "Briefwechsel öffnen" button ties the two pages together.
|
||||
<div><span class="gain">✚ /briefwechsel stays calm and focused</span><span class="gain">✚ Dashboard is useful on every visit to a person, not just during letter review</span><span class="gain">✚ Each dashboard element links back into /briefwechsel with filters</span><span class="cost">− Separates the work over two pages — needs a clear handoff button</span></div>
|
||||
</div>
|
||||
|
||||
<div style="font-size:12px;color:#888;margin-bottom:18px"><a href="#" style="color:#888;text-decoration:none">← Zurück</a></div>
|
||||
|
||||
<div class="person-grid">
|
||||
<!-- Left column: person identity -->
|
||||
<div>
|
||||
<div class="pcard">
|
||||
<div class="pavatar">WG</div>
|
||||
<div class="pname">Walter de Gruyter</div>
|
||||
<div class="pdates">1862 – 1923</div>
|
||||
<div class="pnotes">Verleger und Namensgeber des Verlags Walter de Gruyter. Langjährige Korrespondenz mit Familie Dieckmann und Herbert Cram rund um Verlag, Familie und Kuraufenthalte.</div>
|
||||
<div class="pactions">
|
||||
<a class="btn">◎ Bearbeiten</a>
|
||||
<a class="btn primary">↗ Briefwechsel öffnen</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ncard">
|
||||
<h3>Namensvarianten</h3>
|
||||
<ul>
|
||||
<li><span>Walter de Gruyter</span><span style="color:#888">Hauptname</span></li>
|
||||
<li><span>W. de Gruyter</span><span style="color:#888">Abkürzung</span></li>
|
||||
<li><span>Dr. Walter de Gruyter</span><span style="color:#888">mit Titel</span></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right column: dashboard -->
|
||||
<div>
|
||||
<div class="dash">
|
||||
<div class="dash-hdr">
|
||||
<h2>Korrespondenz-Überblick</h2>
|
||||
<a class="open-conv" href="#">↗ Briefwechsel öffnen</a>
|
||||
</div>
|
||||
|
||||
<div class="stats">
|
||||
<div><div class="v">851</div><div class="k">Briefe gesamt</div></div>
|
||||
<div><div class="v out">612</div><div class="k out">ausgehend</div></div>
|
||||
<div><div class="v in">239</div><div class="k in">eingehend</div></div>
|
||||
<div><div class="v">42</div><div class="k">Jahre</div></div>
|
||||
</div>
|
||||
|
||||
<div class="dsec">
|
||||
<h3>Aktivität über die Jahre <span class="note">Spitzenjahr <b style="color:var(--brand-navy)">1922 · 78 Briefe</b></span></h3>
|
||||
<div class="hist">
|
||||
<div class="bar" style="height:12%" title="1898 · 8"></div>
|
||||
<div class="bar" style="height:18%" title="1899 · 12"></div>
|
||||
<div class="bar" style="height:26%" title="1900 · 18"></div>
|
||||
<div class="bar" style="height:38%" title="1901 · 26"></div>
|
||||
<div class="bar" style="height:44%" title="1902 · 30"></div>
|
||||
<div class="bar" style="height:52%" title="1903 · 36"></div>
|
||||
<div class="bar" style="height:60%" title="1904 · 42"></div>
|
||||
<div class="bar" style="height:68%" title="1905 · 48"></div>
|
||||
<div class="bar" style="height:80%" title="1920 · 62"></div>
|
||||
<div class="bar" style="height:88%" title="1921 · 68"></div>
|
||||
<div class="bar peak" style="height:100%" title="1922 · 78"></div>
|
||||
<div class="bar" style="height:72%" title="1923 · 54"></div>
|
||||
<div class="bar" style="height:58%" title="1924 · 42"></div>
|
||||
<div class="bar" style="height:48%" title="1925 · 34"></div>
|
||||
<div class="bar" style="height:38%" title="1926 · 27"></div>
|
||||
<div class="bar" style="height:28%" title="1927 · 20"></div>
|
||||
<div class="bar" style="height:22%" title="1928 · 16"></div>
|
||||
<div class="bar" style="height:18%" title="1929 · 13"></div>
|
||||
<div class="bar" style="height:14%" title="1930 · 10"></div>
|
||||
<div class="bar" style="height:10%" title="1932 · 7"></div>
|
||||
<div class="bar" style="height:6%" title="1935 · 4"></div>
|
||||
<div class="bar" style="height:4%" title="1938 · 3"></div>
|
||||
<div class="bar" style="height:2%" title="1940 · 1"></div>
|
||||
</div>
|
||||
<div class="hist-labels"><span>1898</span><span>1922 ▲</span><span>1940</span></div>
|
||||
</div>
|
||||
|
||||
<div class="dsec">
|
||||
<h3>Richtungsverteilung</h3>
|
||||
<div class="dsplit">
|
||||
<span class="out">→ 612 ausgehend · 72%</span>
|
||||
<span class="in">← 239 eingehend · 28%</span>
|
||||
</div>
|
||||
<div class="dbar">
|
||||
<span class="out" style="width:72%"></span>
|
||||
<span class="in" style="width:28%"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="twocol">
|
||||
<div class="dsec" style="border-top:1px solid var(--line-2)">
|
||||
<h3>Häufigste Korrespondenten <span class="note">Top 6 von 87</span></h3>
|
||||
<div class="toplist">
|
||||
<div class="ti"><span class="dir both">⇄</span><span class="name">Walter Dieckmann</span><span class="bar-wrap"><span class="bar" style="width:100%"></span></span><span class="val">184</span></div>
|
||||
<div class="ti"><span class="dir both">⇄</span><span class="name">Herbert Cram</span><span class="bar-wrap"><span class="bar" style="width:78%"></span></span><span class="val">143</span></div>
|
||||
<div class="ti"><span class="dir both">⇄</span><span class="name">Ella Dieckmann</span><span class="bar-wrap"><span class="bar" style="width:48%"></span></span><span class="val">88</span></div>
|
||||
<div class="ti"><span class="dir both">⇄</span><span class="name">Eugenie de Gruyter</span><span class="bar-wrap"><span class="bar" style="width:42%"></span></span><span class="val">77</span></div>
|
||||
<div class="ti"><span class="dir both">⇄</span><span class="name">Gertrud von Rofden</span><span class="bar-wrap"><span class="bar" style="width:32%"></span></span><span class="val">58</span></div>
|
||||
<div class="ti"><span class="dir both">⇄</span><span class="name">Käthe Dieckmann</span><span class="bar-wrap"><span class="bar" style="width:26%"></span></span><span class="val">47</span></div>
|
||||
</div>
|
||||
<div style="margin-top:10px"><a style="font-size:11px;color:var(--primary);font-weight:700;text-decoration:none;border-bottom:1px dashed var(--primary)">Alle 87 Korrespondenten →</a></div>
|
||||
</div>
|
||||
|
||||
<div class="dsec" style="border-top:1px solid var(--line-2)">
|
||||
<h3>Häufigste Orte <span class="note">Top 5 von 42</span></h3>
|
||||
<div class="toplist">
|
||||
<div class="ti"><span class="name">📍 Berlin</span><span class="bar-wrap"><span class="bar" style="width:100%"></span></span><span class="val">412</span></div>
|
||||
<div class="ti"><span class="name">📍 B.Lichterfelde</span><span class="bar-wrap"><span class="bar" style="width:44%"></span></span><span class="val">180</span></div>
|
||||
<div class="ti"><span class="name">📍 Bad Kissingen</span><span class="bar-wrap"><span class="bar" style="width:14%"></span></span><span class="val">58</span></div>
|
||||
<div class="ti"><span class="name">📍 Cöln</span><span class="bar-wrap"><span class="bar" style="width:9%"></span></span><span class="val">37</span></div>
|
||||
<div class="ti"><span class="name">📍 Belgard</span><span class="bar-wrap"><span class="bar" style="width:6%"></span></span><span class="val">26</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="dsec">
|
||||
<h3>Beliebte Schlagwörter <span class="note">Klick filtert den Briefwechsel</span></h3>
|
||||
<div class="cloud">
|
||||
<span class="tag s-xl">Verlag</span>
|
||||
<span class="tag s-xl">Familie</span>
|
||||
<span class="tag s-l">Geburtstag</span>
|
||||
<span class="tag s-l">Weihnachten</span>
|
||||
<span class="tag s-m">Kuraufenthalt</span>
|
||||
<span class="tag s-m">Reise</span>
|
||||
<span class="tag s-m">Geschäft</span>
|
||||
<span class="tag s-s">Krieg</span>
|
||||
<span class="tag s-s muted">Krankheit</span>
|
||||
<span class="tag s-s muted">Schule</span>
|
||||
<span class="tag s-s muted">Hochzeit</span>
|
||||
<span class="tag s-s muted">Tod</span>
|
||||
<span class="tag s-s muted">Namenstag</span>
|
||||
<span class="tag s-s muted">Neujahr</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Existing doc lists stay below the dashboard -->
|
||||
<div class="doclist-card">
|
||||
<div class="dh">Geschriebene Briefe <span class="cnt">612 Briefe · <a style="color:var(--primary);text-decoration:none;border-bottom:1px dashed">Alle anzeigen →</a></span></div>
|
||||
<div class="di"><span class="t">W-0397 – 2. September 1923 – B.Lichterfelde</span><span class="m">an Herbert Cram</span></div>
|
||||
<div class="di"><span class="t">W-0521 – 24. Dezember 1922 – Berlin</span><span class="m">an Walter Dieckmann</span></div>
|
||||
<div class="di"><span class="t">W-0392 – 23. November 1921 – Bad Kissingen</span><span class="m">an Herbert Cram</span></div>
|
||||
</div>
|
||||
|
||||
<div class="doclist-card">
|
||||
<div class="dh">Empfangene Briefe <span class="cnt">239 Briefe · <a style="color:var(--primary);text-decoration:none;border-bottom:1px dashed">Alle anzeigen →</a></span></div>
|
||||
<div class="di"><span class="t">Demo leserlicher Brief</span><span class="m">von Gertrud von Rofden</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:36px;padding:16px 20px;background:#fff;border-left:4px solid var(--brand-navy);font-size:13px;color:#333;line-height:1.65">
|
||||
<b style="color:var(--brand-navy)">How this ties back to /briefwechsel:</b>
|
||||
<ul style="margin:8px 0 0 18px;padding:0">
|
||||
<li>Every correspondent row → <code>/briefwechsel?senderId=<person>&receiverId=<other></code> (bilateral view)</li>
|
||||
<li>Every location → <code>/briefwechsel?senderId=<person>&location=<x></code></li>
|
||||
<li>Every tag → <code>/briefwechsel?senderId=<person>&tag=<x></code></li>
|
||||
<li>Every histogram year → <code>/briefwechsel?…&from=YYYY-01-01&to=YYYY-12-31</code></li>
|
||||
</ul>
|
||||
The dashboard is the discovery surface; /briefwechsel is the reading surface.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,102 +0,0 @@
|
||||
/* Shared CSS for briefwechsel-fill specs */
|
||||
:root {
|
||||
--brand-navy: #002850;
|
||||
--brand-mint: #A6DAD8;
|
||||
--brand-sand: #E4E2D7;
|
||||
--bg: #ECEAE4;
|
||||
--surface: #ffffff;
|
||||
--line: #E4E2D7;
|
||||
--line-2: #EFEDE6;
|
||||
--muted: #F7F5F2;
|
||||
--ink: #1A1A1A;
|
||||
--ink-3: #666;
|
||||
--primary: #002850;
|
||||
--accent: #2F9E95;
|
||||
}
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
body{font-family:'Montserrat',system-ui,sans-serif;background:var(--bg);color:var(--ink);line-height:1.5}
|
||||
.font-serif{font-family:'Merriweather',Georgia,serif}
|
||||
|
||||
/* ── Spec meta header ─── */
|
||||
.spec-meta{background:var(--brand-navy);color:#fff;padding:18px 28px;border-bottom:3px solid var(--brand-mint)}
|
||||
.spec-meta-inner{max-width:1440px;margin:0 auto;display:flex;justify-content:space-between;align-items:flex-end;gap:24px;flex-wrap:wrap}
|
||||
.spec-meta h1{font-size:18px;font-weight:800;letter-spacing:.4px}
|
||||
.spec-meta h1 span{opacity:.5;font-weight:400}
|
||||
.spec-meta p{font-size:12px;color:rgba(255,255,255,.7);margin-top:4px;max-width:600px}
|
||||
.spec-meta-right{font-size:11px;display:flex;gap:20px;flex-wrap:wrap}
|
||||
.spec-meta-right div strong{display:block;font-size:9px;text-transform:uppercase;letter-spacing:.8px;color:rgba(255,255,255,.45);font-weight:800;margin-bottom:2px}
|
||||
.spec-nav{background:#0a1e36;padding:10px 28px;border-bottom:1px solid rgba(255,255,255,.08)}
|
||||
.spec-nav-inner{max-width:1440px;margin:0 auto;display:flex;gap:4px;font-size:11px;flex-wrap:wrap;align-items:center}
|
||||
.spec-nav a{color:rgba(255,255,255,.55);text-decoration:none;padding:4px 10px;border-radius:3px;font-weight:600}
|
||||
.spec-nav a:hover{color:#fff;background:rgba(255,255,255,.06)}
|
||||
.spec-nav a.on{color:var(--brand-navy);background:var(--brand-mint)}
|
||||
.spec-nav .lbl{color:rgba(255,255,255,.3);font-size:9px;text-transform:uppercase;letter-spacing:1px;margin-right:8px}
|
||||
|
||||
/* ── Real page chrome ─── */
|
||||
.page-wrap{background:var(--bg);min-height:calc(100vh - 120px)}
|
||||
.hdr{background:var(--brand-navy);color:#fff;padding:0 32px;height:64px;display:flex;align-items:center;gap:40px}
|
||||
.hdr-logo{font-size:16px;font-weight:900;letter-spacing:1.5px}
|
||||
.hdr-nav{display:flex;gap:28px;font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.8px}
|
||||
.hdr-nav a{color:rgba(255,255,255,.7);text-decoration:none;padding:4px 0;border-bottom:2px solid transparent}
|
||||
.hdr-nav a.on{color:#fff;border-color:var(--brand-mint)}
|
||||
.hdr-right{margin-left:auto;display:flex;gap:14px;align-items:center;font-size:11px;color:rgba(255,255,255,.65)}
|
||||
.hdr-upload{border:1px solid rgba(255,255,255,.3);border-radius:4px;padding:6px 12px;font-weight:700;letter-spacing:.5px}
|
||||
.hdr-avatar{width:32px;height:32px;border-radius:50%;background:var(--brand-mint);color:var(--brand-navy);display:flex;align-items:center;justify-content:center;font-weight:800;font-size:12px}
|
||||
|
||||
.page{max-width:1440px;margin:0 auto;padding:28px 32px}
|
||||
|
||||
.card{background:var(--surface);border:1px solid var(--line);border-radius:2px;box-shadow:0 1px 2px rgba(0,0,0,.02);padding:20px 24px;margin-bottom:24px}
|
||||
.filter-row{display:grid;grid-template-columns:1fr 1fr;gap:24px;margin-bottom:14px}
|
||||
.fl{font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:1px;color:#888;margin-bottom:6px}
|
||||
.fi{height:40px;border:1px solid #D1D5DB;border-radius:2px;background:#fff;padding:0 12px;font-size:13px;display:flex;align-items:center;font-family:'Merriweather',serif}
|
||||
.fi.empty{color:#bbb;font-style:italic}
|
||||
.filter-actions{display:flex;gap:8px;align-items:center}
|
||||
.btn{height:34px;border:1px solid #D1D5DB;background:#F7F5F2;border-radius:2px;padding:0 12px;font-size:11px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#444;display:inline-flex;align-items:center;gap:6px}
|
||||
.count{margin-left:auto;font-size:13px;color:#555}
|
||||
.count b{color:var(--brand-navy)}
|
||||
|
||||
.hintbar{margin-top:14px;background:#e9f5f4;border:1px solid #c8e6e4;border-radius:2px;padding:10px 14px;font-size:12px;color:#1a3e3c;display:flex;align-items:center;gap:8px}
|
||||
.hintbar b{color:var(--brand-navy)}
|
||||
|
||||
/* ── Year divider ─── */
|
||||
.year-divider{display:flex;align-items:baseline;gap:12px;background:var(--muted);border-top:2px solid var(--line);border-bottom:1px solid var(--line);padding:8px 14px}
|
||||
.year-divider .y{font-size:22px;font-weight:900;color:var(--brand-navy);letter-spacing:-.5px}
|
||||
.year-divider .n{font-size:12px;font-weight:700;color:#888}
|
||||
|
||||
/* ── Status dots ─── */
|
||||
.dot{display:inline-block;width:7px;height:7px;border-radius:50%;flex-shrink:0}
|
||||
.dot.uploaded{background:var(--brand-mint)}
|
||||
.dot.transcribed{background:var(--brand-mint)}
|
||||
.dot.reviewed{background:rgba(0,40,80,.55)}
|
||||
.dot.archived{background:var(--brand-navy)}
|
||||
|
||||
/* ── Direction indicator ─── */
|
||||
.dir{width:16px;height:16px;flex-shrink:0;opacity:.6}
|
||||
.dir-out{color:var(--primary)}
|
||||
.dir-in{color:var(--accent)}
|
||||
|
||||
/* ── Concept intro banner ─── */
|
||||
.concept-intro{background:#fff;border-left:4px solid var(--brand-mint);padding:14px 20px;margin-bottom:24px;font-size:13px;color:#333;border-radius:2px;box-shadow:0 1px 2px rgba(0,0,0,.04)}
|
||||
.concept-intro h2{font-size:14px;font-weight:800;color:var(--brand-navy);margin-bottom:4px}
|
||||
.concept-intro .gain,.concept-intro .cost{display:inline-block;font-size:11px;margin-right:14px;margin-top:6px}
|
||||
.concept-intro .gain{color:#166534}
|
||||
.concept-intro .cost{color:#92400E}
|
||||
|
||||
/* ── Mock letter thumbnail ─── */
|
||||
.thumb{background:linear-gradient(180deg,#fdfcf7 0%,#f6f3ea 100%);border:1px solid #d9d4c6;box-shadow:inset 0 0 0 1px #fff,0 1px 2px rgba(0,0,0,.05);position:relative;overflow:hidden;flex-shrink:0;border-radius:1px}
|
||||
.thumb::before{content:'';position:absolute;top:0;left:0;right:0;height:28%;background:linear-gradient(135deg,rgba(166,218,216,.25) 0%,transparent 60%)}
|
||||
.thumb-lines{position:absolute;inset:0;display:flex;flex-direction:column;justify-content:center;gap:2px;padding:20% 12% 20% 12%}
|
||||
.thumb-lines i{display:block;height:1px;background:rgba(0,40,80,.2)}
|
||||
.thumb-lines i:nth-child(odd){width:95%}
|
||||
.thumb-lines i:nth-child(3n){width:70%}
|
||||
.thumb-lines i:nth-child(5n){width:80%}
|
||||
.thumb.handwritten .thumb-lines{padding:15% 10%;gap:4px}
|
||||
.thumb.handwritten .thumb-lines i{height:1.2px;background:rgba(0,40,80,.35);transform:rotate(-.6deg)}
|
||||
|
||||
/* ── Tag chip ─── */
|
||||
.tag{display:inline-flex;align-items:center;font-size:10px;font-weight:700;background:var(--brand-mint);color:var(--brand-navy);padding:2px 7px;border-radius:10px;letter-spacing:.3px}
|
||||
.tag.muted{background:#EEE8DC;color:#666}
|
||||
|
||||
/* ── Links & generic ─── */
|
||||
a{color:inherit}
|
||||
.hide{display:none}
|
||||
@@ -1,290 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Briefwechsel — Fill the Empty Rows · Overview</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Merriweather:wght@400;700&family=Montserrat:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="_shared.css">
|
||||
<style>
|
||||
.ov{max-width:1440px;margin:0 auto;padding:36px 32px 80px}
|
||||
.lead{display:grid;grid-template-columns:1fr 320px;gap:40px;margin-bottom:36px;align-items:start}
|
||||
@media (max-width: 900px){ .lead{grid-template-columns:1fr} }
|
||||
.lead h2{font-family:'Merriweather',serif;font-size:24px;font-weight:700;color:var(--brand-navy);margin-bottom:10px;line-height:1.3}
|
||||
.lead p{color:#444;font-size:14px;line-height:1.65}
|
||||
.lead .kit{background:#fff;border:1px solid var(--line);border-radius:2px;padding:16px 18px}
|
||||
.lead .kit h3{font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:1.2px;color:#888;margin-bottom:10px}
|
||||
.lead .kit li{font-size:12px;padding:5px 0;border-top:1px dashed var(--line);display:flex;justify-content:space-between}
|
||||
.lead .kit li:first-child{border-top:0}
|
||||
.lead .kit li b{color:var(--brand-navy);font-weight:700}
|
||||
|
||||
.section-h{font-family:'Merriweather',serif;font-size:16px;font-weight:700;color:var(--brand-navy);margin:28px 0 12px;padding-top:20px;border-top:1px dashed var(--line)}
|
||||
.section-h .sub{font-family:'Montserrat',sans-serif;font-size:12px;color:#888;font-weight:400;margin-left:10px}
|
||||
|
||||
.grid4{display:grid;grid-template-columns:repeat(4,1fr);gap:16px}
|
||||
@media (max-width: 1200px){ .grid4{grid-template-columns:repeat(2,1fr)} }
|
||||
.grid1{display:grid;grid-template-columns:1fr;gap:16px}
|
||||
.spec-card{background:#fff;border:1px solid var(--line);border-radius:2px;overflow:hidden;text-decoration:none;color:inherit;display:flex;flex-direction:column;transition:transform .12s,box-shadow .12s}
|
||||
.spec-card:hover{transform:translateY(-3px);box-shadow:0 8px 26px rgba(0,0,0,.1)}
|
||||
.spec-card.wide{flex-direction:row}
|
||||
.spec-card.wide .mini{width:45%;aspect-ratio:auto;min-height:280px;border-right:1px solid var(--line);border-bottom:0}
|
||||
.spec-card.wide .sc-body{flex:1}
|
||||
.spec-card .sc-num{background:var(--brand-navy);color:#fff;padding:3px 8px;font-size:10px;font-weight:800;letter-spacing:.8px;display:inline-block;border-radius:0 0 3px 0;width:max-content}
|
||||
.spec-card .sc-body{padding:14px 16px;flex:1;display:flex;flex-direction:column;gap:8px}
|
||||
.spec-card h3{font-family:'Merriweather',serif;font-size:16px;font-weight:700;color:var(--brand-navy);line-height:1.35}
|
||||
.spec-card p{font-size:12px;color:#555;line-height:1.55}
|
||||
.spec-card .tags{display:flex;gap:4px;flex-wrap:wrap;margin-top:auto;padding-top:8px}
|
||||
.spec-card .tag{font-size:9.5px;letter-spacing:.3px}
|
||||
|
||||
/* Mini previews */
|
||||
.mini{aspect-ratio:16/10;border-bottom:1px solid var(--line);background:#fafaf5;display:flex;padding:8px;gap:4px}
|
||||
.mini.col{flex-direction:column}
|
||||
.mini .minirow{background:#fff;border:1px solid var(--line-2);border-left:2px solid var(--primary);padding:4px 6px;display:flex;flex-direction:column;gap:2px;flex:1;font-size:7px;color:#888}
|
||||
.mini .minirow b{color:var(--brand-navy);font-size:8px;font-weight:700}
|
||||
.mini .minirow.in{border-left-color:var(--accent)}
|
||||
|
||||
.mini-1{display:flex;flex-direction:column;gap:3px;padding:8px}
|
||||
.mini-1 .r{background:#fff;border:1px solid var(--line-2);border-left:2px solid var(--primary);padding:4px 6px;display:grid;grid-template-columns:1fr 48px;gap:4px;flex:1;font-size:7px;color:#888;align-items:center}
|
||||
.mini-1 .r b{color:var(--brand-navy);font-size:8px;font-weight:700;font-family:'Merriweather',serif}
|
||||
.mini-1 .r .ar{font-size:6.5px;color:#888;background:#F4F1EA;padding:2px 4px;text-align:center}
|
||||
.mini-1 .r .tg{display:flex;gap:2px;margin-top:1px}
|
||||
.mini-1 .r .tg span{background:var(--brand-mint);padding:0 3px;font-size:6px;border-radius:3px;color:var(--brand-navy)}
|
||||
|
||||
.mini-2{display:flex;flex-direction:column;gap:3px;padding:8px}
|
||||
.mini-2 .r2{background:#fff;border:1px solid var(--line-2);border-left:2px solid var(--primary);padding:4px;display:flex;gap:4px;align-items:center;flex:1}
|
||||
.mini-2 .th2{width:22px;height:28px;background:linear-gradient(#fdfcf7,#f1eadb);border:1px solid #d9d4c6;flex-shrink:0;position:relative}
|
||||
.mini-2 .th2::before{content:'';position:absolute;inset:2px 3px;border-top:1px solid rgba(0,40,80,.25);border-bottom:1px solid rgba(0,40,80,.25);opacity:.6}
|
||||
.mini-2 .content{flex:1;display:flex;flex-direction:column;gap:2px;font-size:7px;color:#888}
|
||||
.mini-2 .content b{color:var(--brand-navy);font-size:8px;font-family:'Merriweather',serif}
|
||||
.mini-2 .content i{color:#555;font-style:italic}
|
||||
.mini-2 .content .dt{font-size:7px;color:#888;align-self:flex-end;font-family:'Merriweather',serif}
|
||||
|
||||
.mini-3{display:grid;grid-template-columns:1fr 1.1fr;gap:4px;padding:8px}
|
||||
.mini-3 .left-list{display:flex;flex-direction:column;gap:2px}
|
||||
.mini-3 .left-list .r{background:#fff;border:1px solid var(--line-2);border-left:2px solid var(--primary);padding:2px 4px;font-size:6.5px;color:#888}
|
||||
.mini-3 .left-list .r.sel{background:#e7f4f3;border-left-color:var(--accent)}
|
||||
.mini-3 .left-list .r b{color:var(--brand-navy);font-size:7px}
|
||||
.mini-3 .preview-mini{background:#fff;border:1px solid var(--line-2);padding:5px;display:flex;gap:4px}
|
||||
.mini-3 .pv-th{width:28px;height:38px;background:linear-gradient(#fdfcf7,#f1eadb);border:1px solid #d9d4c6;flex-shrink:0}
|
||||
.mini-3 .pv-meta{font-size:6px;color:#888;line-height:1.4}
|
||||
.mini-3 .pv-meta b{color:var(--brand-navy);font-size:7px;display:block;font-family:'Merriweather',serif}
|
||||
|
||||
.mini-4{display:grid;grid-template-columns:repeat(3,1fr);gap:3px;padding:8px}
|
||||
.mini-4 .card-s{background:#fff;border:1px solid var(--line-2);overflow:hidden;display:flex;flex-direction:column}
|
||||
.mini-4 .card-s .th{aspect-ratio:3/4;background:linear-gradient(#fdfcf7,#f1eadb);border-bottom:1px solid var(--line-2)}
|
||||
.mini-4 .card-s .t{font-size:5.5px;color:var(--brand-navy);font-weight:700;padding:3px 4px;font-family:'Merriweather',serif}
|
||||
|
||||
.mini-5{display:grid;grid-template-columns:32% 1fr;gap:6px;padding:10px;align-items:start;min-height:260px}
|
||||
.mini-5 .person-left{background:#fff;border:1px solid var(--line-2);padding:8px;display:flex;flex-direction:column;align-items:center;gap:4px}
|
||||
.mini-5 .person-left .av{width:28px;height:28px;border-radius:50%;background:var(--brand-mint);color:var(--brand-navy);display:flex;align-items:center;justify-content:center;font-size:8px;font-weight:800;font-family:'Merriweather',serif}
|
||||
.mini-5 .person-left .nm{font-family:'Merriweather',serif;font-size:7.5px;font-weight:700;color:var(--brand-navy);text-align:center}
|
||||
.mini-5 .person-left .dt{font-size:6px;color:#888}
|
||||
.mini-5 .dash-mini{background:#fff;border:1px solid var(--line-2);display:flex;flex-direction:column}
|
||||
.mini-5 .dh{background:var(--brand-navy);color:#fff;font-family:'Merriweather',serif;font-size:6.5px;padding:4px 6px;display:flex;justify-content:space-between;align-items:center}
|
||||
.mini-5 .dh span{background:var(--brand-mint);color:var(--brand-navy);font-size:5.5px;padding:1px 4px;border-radius:2px;font-weight:800}
|
||||
.mini-5 .stats-m{display:grid;grid-template-columns:repeat(4,1fr);gap:1px;background:var(--line-2);border-bottom:1px solid var(--line-2)}
|
||||
.mini-5 .stats-m div{background:#fafaf5;padding:3px;text-align:center}
|
||||
.mini-5 .stats-m .v{font-family:'Merriweather',serif;font-size:8px;font-weight:900;color:var(--brand-navy)}
|
||||
.mini-5 .stats-m .k{font-size:5px;color:#888;font-weight:700}
|
||||
.mini-5 .hist-m{display:flex;align-items:flex-end;gap:1px;height:22px;padding:4px 5px 0}
|
||||
.mini-5 .hist-m i{flex:1;background:var(--brand-mint);opacity:.6}
|
||||
.mini-5 .hist-m i.p{background:var(--brand-navy);opacity:.9}
|
||||
.mini-5 .bars-m{display:flex;flex-direction:column;gap:1.5px;padding:4px 5px}
|
||||
.mini-5 .bars-m span{display:flex;align-items:center;gap:2px;font-size:5.5px;color:#555}
|
||||
.mini-5 .bars-m span .bar{flex:1;height:2px;background:var(--brand-navy);border-radius:1px}
|
||||
.mini-5 .cloud-m{padding:4px 5px;display:flex;flex-wrap:wrap;gap:2px}
|
||||
.mini-5 .cloud-m span{background:var(--brand-mint);padding:0 3px;border-radius:3px;font-size:5.5px;color:var(--brand-navy);font-weight:700}
|
||||
|
||||
/* Comparison table */
|
||||
.cmp{background:#fff;border:1px solid var(--line);border-radius:2px;overflow:hidden;margin-top:36px}
|
||||
.cmp table{width:100%;border-collapse:collapse}
|
||||
.cmp th,.cmp td{padding:10px 14px;text-align:left;font-size:12px;border-bottom:1px solid var(--line-2);vertical-align:top}
|
||||
.cmp th{background:#fafaf5;font-size:10px;text-transform:uppercase;letter-spacing:.8px;color:#888;font-weight:800}
|
||||
.cmp td:first-child{font-weight:700;color:var(--brand-navy);font-family:'Merriweather',serif}
|
||||
.cmp .yes{color:#166534}
|
||||
.cmp .no{color:#B91C1C}
|
||||
.cmp .mid{color:#92400E}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="spec-meta">
|
||||
<div class="spec-meta-inner">
|
||||
<div>
|
||||
<h1>Briefwechsel — <span>Fill the Empty Rows</span></h1>
|
||||
<p>Five approaches. The first four change the <b>/briefwechsel</b> row itself; the fifth puts insights where they belong — on the person detail page.</p>
|
||||
</div>
|
||||
<div class="spec-meta-right">
|
||||
<div><strong>Page</strong>/briefwechsel · /persons/[id]</div>
|
||||
<div><strong>Brief</strong>Rows feel sparse — ~60% of row width is empty</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="spec-nav">
|
||||
<div class="spec-nav-inner">
|
||||
<span class="lbl">Specs</span>
|
||||
<a class="on" href="index.html">Overview</a>
|
||||
<a href="01-rich-rows.html">1 · Rich Rows</a>
|
||||
<a href="02-thumbnail-rows.html">2 · Thumbnail Rows</a>
|
||||
<a href="03-master-detail.html">3 · Master-Detail Split</a>
|
||||
<a href="04-gallery-cards.html">4 · Gallery Cards</a>
|
||||
<a href="05-person-dashboard.html">5 · Person Dashboard</a>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="ov">
|
||||
|
||||
<div class="lead">
|
||||
<div>
|
||||
<h2>The situation</h2>
|
||||
<p>On <code>/briefwechsel?senderId=…</code> (e.g. 851 letters for Walter de Gruyter), each row shows only title, date, location and counterpart — leaving the right half empty. The question is: what belongs there?</p>
|
||||
<p style="margin-top:10px">The five specs answer differently. Specs 1–4 rework the row itself. <b>Spec 5</b> argues the archive-level view (top correspondents, activity, tag cloud) belongs on <code>/persons/[id]</code>, not here — and mocks the dashboard that lives there instead.</p>
|
||||
</div>
|
||||
<div class="kit">
|
||||
<h3>Data we can use today</h3>
|
||||
<ul style="list-style:none">
|
||||
<li>Title / Filename <b>✓</b></li>
|
||||
<li>Document date <b>✓</b></li>
|
||||
<li>Location <b>✓</b></li>
|
||||
<li>Sender / receivers <b>✓</b></li>
|
||||
<li>Summary text <b>✓</b></li>
|
||||
<li>Tags <b>✓</b></li>
|
||||
<li>Archive box & folder <b>✓</b></li>
|
||||
<li>PDF thumbnail <b>open issue</b></li>
|
||||
</ul>
|
||||
<p style="font-size:11px;color:#888;margin-top:10px;font-style:italic">Removed from earlier drafts: status lifecycle (will be dropped from the product) and script type (only set after OCR, unreliable).</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section-h">Concepts that rework /briefwechsel <span class="sub">Specs 1–4</span></div>
|
||||
|
||||
<div class="grid4">
|
||||
<a class="spec-card" href="01-rich-rows.html">
|
||||
<span class="sc-num">01</span>
|
||||
<div class="mini mini-1">
|
||||
<div class="r"><span><b>Demo leserlicher Brief</b><br>31. Mai 1940 · Belgard<br>„letzte Lebenstage…"<div class="tg"><span>Dörpfeld</span></div></span><div class="ar">VII · 5</div></div>
|
||||
<div class="r"><span><b>W-0397 – 2. Sep 1923</b><br>B.Lichterfelde · H. Cram<br>„von Elsbeth…"<div class="tg"><span>Verlag</span></div></span><div class="ar">VI · 7</div></div>
|
||||
<div class="r"><span><b>W-0521 – 24. Dez 1922</b><br>Berlin · W. Dieckmann<br>„Weihnachtsbrief…"<div class="tg"><span>Weihn.</span></div></span><div class="ar">V · 3</div></div>
|
||||
</div>
|
||||
<div class="sc-body">
|
||||
<h3>Rich Rows</h3>
|
||||
<p>Pack summary, tags and archive box into each row. No images, no structural change. Fastest to ship.</p>
|
||||
<div class="tags"><span class="tag">Scanning</span><span class="tag muted">Small effort</span></div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a class="spec-card" href="02-thumbnail-rows.html">
|
||||
<span class="sc-num">02</span>
|
||||
<div class="mini mini-2">
|
||||
<div class="r2"><div class="th2"></div><div class="content"><b>Demo leserlicher Brief</b><i>„letzte Lebenstage von W. Dörpfeld…"</i><span>← Gertrud · Belgard</span><span class="dt">31. Mai 1940</span></div></div>
|
||||
<div class="r2"><div class="th2"></div><div class="content"><b>W-0397 – 2. Sep 1923</b><i>„von Elsbeth geschriebener Kommentar…"</i><span>→ H. Cram · B.Lichterfelde</span><span class="dt">2. Sep 1923</span></div></div>
|
||||
<div class="r2"><div class="th2"></div><div class="content"><b>W-0521 – 24. Dez 1922</b><i>„Weihnachtsbrief, Bitte um Bild…"</i><span>→ W. Dieckmann · Berlin</span><span class="dt">24. Dez 1922</span></div></div>
|
||||
</div>
|
||||
<div class="sc-body">
|
||||
<h3>Thumbnail Rows</h3>
|
||||
<p>PDF preview on the left anchors each row. Summary (when filled) becomes the readable context line.</p>
|
||||
<div class="tags"><span class="tag">Recognition</span><span class="tag muted">Needs thumbnails</span></div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a class="spec-card" href="03-master-detail.html">
|
||||
<span class="sc-num">03</span>
|
||||
<div class="mini mini-3">
|
||||
<div class="left-list">
|
||||
<div class="r"><b>Demo Brief</b></div>
|
||||
<div class="r sel"><b>W-0397</b></div>
|
||||
<div class="r"><b>W-0396</b></div>
|
||||
<div class="r"><b>W-0524</b></div>
|
||||
<div class="r"><b>W-0523</b></div>
|
||||
</div>
|
||||
<div class="preview-mini">
|
||||
<div class="pv-th"></div>
|
||||
<div class="pv-meta"><b>W-0397</b>2. Sep 1923<br>B.Lichterfelde<br>→ H. Cram<br><br>„von Elsbeth…"</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sc-body">
|
||||
<h3>Master-Detail Split</h3>
|
||||
<p>Compact list left, sticky preview right. Click a row → thumbnail, metadata, summary, excerpt. Browse without losing context.</p>
|
||||
<div class="tags"><span class="tag">Reading flow</span><span class="tag muted">Mobile pattern needed</span></div>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<a class="spec-card" href="04-gallery-cards.html">
|
||||
<span class="sc-num">04</span>
|
||||
<div class="mini mini-4">
|
||||
<div class="card-s"><div class="th"></div><div class="t">Demo Brief</div></div>
|
||||
<div class="card-s"><div class="th"></div><div class="t">W-0397 · 2 Sep</div></div>
|
||||
<div class="card-s"><div class="th"></div><div class="t">W-0396 · 2 Sep</div></div>
|
||||
<div class="card-s"><div class="th"></div><div class="t">W-0524 · 31 Jul</div></div>
|
||||
<div class="card-s"><div class="th"></div><div class="t">W-0523 · 12 Mai</div></div>
|
||||
<div class="card-s"><div class="th"></div><div class="t">W-0522 · 7 Mär</div></div>
|
||||
</div>
|
||||
<div class="sc-body">
|
||||
<h3>Gallery Cards</h3>
|
||||
<p>Abandon the list for a 4-column grid. Thumbnail-first, family-album feel. Biggest visual change.</p>
|
||||
<div class="tags"><span class="tag">Browsing</span><span class="tag muted">Poor date-scanning</span></div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="section-h">The archive-level view moves to /persons/[id] <span class="sub">Spec 5 · separate page</span></div>
|
||||
|
||||
<div class="grid1">
|
||||
<a class="spec-card wide" href="05-person-dashboard.html">
|
||||
<span class="sc-num" style="position:absolute">05</span>
|
||||
<div class="mini mini-5">
|
||||
<div class="person-left">
|
||||
<div class="av">WG</div>
|
||||
<div class="nm">Walter de Gruyter</div>
|
||||
<div class="dt">1862–1923</div>
|
||||
</div>
|
||||
<div class="dash-mini">
|
||||
<div class="dh">Korrespondenz-Überblick <span>↗ Briefwechsel</span></div>
|
||||
<div class="stats-m">
|
||||
<div><div class="v">851</div><div class="k">gesamt</div></div>
|
||||
<div><div class="v" style="color:var(--primary)">612</div><div class="k">→</div></div>
|
||||
<div><div class="v" style="color:var(--accent)">239</div><div class="k">←</div></div>
|
||||
<div><div class="v">42J</div><div class="k">Jahre</div></div>
|
||||
</div>
|
||||
<div class="hist-m"><i style="height:15%"></i><i style="height:30%"></i><i style="height:45%"></i><i style="height:60%"></i><i style="height:80%"></i><i class="p" style="height:100%"></i><i style="height:75%"></i><i style="height:55%"></i><i style="height:40%"></i><i style="height:25%"></i><i style="height:15%"></i><i style="height:8%"></i></div>
|
||||
<div class="bars-m"><span>W. Dieckmann<span class="bar" style="flex:1"></span>184</span><span>H. Cram<span class="bar" style="flex:.78"></span>143</span><span>E. Dieckmann<span class="bar" style="flex:.48"></span>88</span></div>
|
||||
<div class="cloud-m"><span>Verlag</span><span>Familie</span><span>Weihn.</span><span>Kur</span><span>Reise</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sc-body">
|
||||
<h3>Person Dashboard (/persons/[id])</h3>
|
||||
<p>The /briefwechsel list stays calm and reading-focused; the archive-level view — activity over years, top correspondents, top locations, tag cloud — lives on the person detail page, where it's useful <i>every time</i> you open a person, not only during letter review.</p>
|
||||
<p>Every correspondent, location, tag and year on the dashboard links into <code>/briefwechsel</code> with pre-filled filters, so the dashboard is the discovery surface and /briefwechsel is the reading surface.</p>
|
||||
<div class="tags"><span class="tag">Discovery</span><span class="tag">Works for bilateral too (always scoped to the person)</span><span class="tag muted">Needs aggregation endpoints</span></div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="cmp">
|
||||
<table>
|
||||
<thead><tr>
|
||||
<th>Concept</th><th>Best for user who wants to…</th><th>Visual change</th><th>New backend</th><th>Effort</th><th>Mobile</th>
|
||||
</tr></thead>
|
||||
<tbody>
|
||||
<tr><td>1 · Rich Rows</td><td>Scan quickly, see summary + tags on every letter</td><td>Row height 2× current</td><td>None</td><td class="yes">Small</td><td class="yes">Right column collapses</td></tr>
|
||||
<tr><td>2 · Thumbnail Rows</td><td>Recognise letters visually, pick up where they left off</td><td>Thumbnail on the left</td><td>PDF thumbnail service (open issue)</td><td class="mid">Medium</td><td class="yes">Fine</td></tr>
|
||||
<tr><td>3 · Master-Detail</td><td>Flip through letters in a reading session</td><td>Two-column split</td><td>None mandatory</td><td class="mid">Medium</td><td class="no">Drawer / sheet needed</td></tr>
|
||||
<tr><td>4 · Gallery Cards</td><td>Browse the collection as an album</td><td>Full structural change (list → grid)</td><td>PDF thumbnail service</td><td class="no">Large</td><td class="yes">Grid reflows 4 → 2</td></tr>
|
||||
<tr><td>5 · Person Dashboard</td><td>Understand a person's correspondence at a glance</td><td>New section on /persons/[id]</td><td>Aggregation endpoints (per-year, per-correspondent, per-location, per-tag)</td><td class="mid">Medium</td><td class="yes">Stacks naturally</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:28px;padding:16px 20px;background:#fff;border-left:4px solid var(--brand-navy);font-size:13px;color:#333;line-height:1.65">
|
||||
<b style="color:var(--brand-navy)">Proposed path forward:</b>
|
||||
<ol style="margin:8px 0 0 20px;padding:0">
|
||||
<li><b>Ship Spec 1 (Rich Rows)</b> now — it uses data we already have and tests whether "empty rows" is really the problem, or whether it's "not enough context to decide which letter to open".</li>
|
||||
<li><b>Build Spec 5 (Person Dashboard)</b> next — it's independent of /briefwechsel and turns the person page into a real archive overview.</li>
|
||||
<li><b>Upgrade to Spec 2 (Thumbnail Rows)</b> once the thumbnail service lands — it layers cleanly on top of Spec 1 without throwing work away.</li>
|
||||
</ol>
|
||||
<div style="margin-top:10px">Specs 3 and 4 remain on the table but are bigger re-architectures — revisit after watching how users behave with 1 + 5 + 2.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
732
docs/specs/calendar-view-spec.html
Normal file
732
docs/specs/calendar-view-spec.html
Normal file
@@ -0,0 +1,732 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Kalenderansicht · Spec #386 · Familienarchiv</title>
|
||||
<style>
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
body{font-family:'Helvetica Neue',Arial,sans-serif;background:#ECEAE4;color:#1A1A1A;line-height:1.5}
|
||||
.page{max-width:1360px;margin:0 auto;padding:48px 32px}
|
||||
|
||||
.mh{padding-bottom:24px;border-bottom:3px solid #012851;margin-bottom:48px}
|
||||
.mh h1{font-size:22px;font-weight:900;color:#012851;letter-spacing:-.4px}
|
||||
.mh p{font-size:12.5px;color:#555;max-width:680px;line-height:1.7;margin-top:6px}
|
||||
.mh .byline{font-size:9px;color:#AAA;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;margin-top:8px}
|
||||
|
||||
.sh{margin:56px 0 28px;padding-bottom:12px;border-bottom:2px solid #E0DDD6}
|
||||
.sh h2{font-size:16px;font-weight:900;color:#012851}
|
||||
.sh p{font-size:12px;color:#666;margin-top:4px;max-width:700px;line-height:1.6}
|
||||
|
||||
.grid{display:flex;gap:20px;flex-wrap:wrap;margin-bottom:32px;align-items:flex-start}
|
||||
.col{display:flex;flex-direction:column;gap:6px}
|
||||
.lbl{font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:1.2px;color:#888;display:flex;align-items:center;gap:5px;margin-bottom:4px}
|
||||
.tag{background:#E4E0DA;color:#666;padding:1px 5px;border-radius:2px;font-size:7px;font-weight:700}
|
||||
.cap{font-size:9.5px;color:#999;font-style:italic;line-height:1.55;max-width:580px;margin-top:4px}
|
||||
|
||||
.chrome{background:#F0EFE9;border:1.5px solid #C4C0BA;border-radius:7px;overflow:hidden;box-shadow:0 3px 14px rgba(0,0,0,.09)}
|
||||
.chrome.dk{background:#060C12;border-color:#0A1520}
|
||||
.cbar{height:20px;background:#E0DDD6;border-bottom:1px solid #C4C0BA;display:flex;align-items:center;padding:0 7px;gap:3px}
|
||||
.chrome.dk .cbar{background:#0A1218;border-bottom-color:#0A1520}
|
||||
.dot{width:5px;height:5px;border-radius:50%;background:#BDB8B1}
|
||||
.chrome.dk .dot{background:#1A2A3A}
|
||||
.url{flex:1;height:8px;background:#CCC8C2;border-radius:5px;margin-left:4px}
|
||||
.chrome.dk .url{background:#1A2A3A}
|
||||
|
||||
.nav{height:32px;background:#012851;display:flex;align-items:center;padding:0 12px;gap:8px}
|
||||
.chrome.dk .nav{background:#060C12}
|
||||
.nav-logo{font-size:7px;font-weight:900;color:#fff;letter-spacing:.8px;border-bottom:2px solid #A1DCD8;padding-bottom:1px}
|
||||
.nav-link{font-size:5.5px;color:rgba(255,255,255,.4);font-weight:700;text-transform:uppercase}
|
||||
.nav-link.on{color:#fff;border-bottom:1.5px solid #A1DCD8;padding-bottom:1px}
|
||||
.nav-r{margin-left:auto;display:flex;gap:5px;align-items:center}
|
||||
.nav-av{width:16px;height:16px;background:rgba(255,255,255,.1);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:5px;font-weight:800;color:rgba(255,255,255,.5)}
|
||||
|
||||
.pb{background:#E8E7E2;padding:8px 10px;display:flex;flex-direction:column;gap:6px}
|
||||
.pb.dk{background:#090F16}
|
||||
|
||||
/* SearchFilterBar */
|
||||
.sfb{background:#fff;border:1.5px solid #D8D6CF;border-radius:3px;padding:5px 7px;display:flex;align-items:center;gap:4px;box-shadow:0 1px 3px rgba(0,0,0,.06)}
|
||||
.sfb.dk{background:#0A1218;border-color:#1E2D3D}
|
||||
.sfb-input{flex:1;height:17px;background:#F5F4EF;border:1px solid #D8D6CF;border-radius:2px;padding:0 5px;font-size:5.5px;color:#999;display:flex;align-items:center}
|
||||
.sfb-input.dk{background:#060C12;border-color:#1E2D3D;color:#3E5065}
|
||||
.sfb-btn{height:17px;padding:0 5px;border:1.5px solid #D8D6CF;border-radius:2px;font-size:5px;font-weight:700;text-transform:uppercase;color:#666;display:flex;align-items:center;gap:2px;white-space:nowrap;background:#F5F4EF}
|
||||
.sfb-btn.dk{border-color:#1E2D3D;color:#4E6070;background:#0A1218}
|
||||
|
||||
/* View toggle */
|
||||
.vtgl{display:flex;gap:0;border:1.5px solid #D8D6CF;border-radius:2px;overflow:hidden}
|
||||
.vtgl.dk{border-color:#1E2D3D}
|
||||
.vtgl-list{padding:3px 5px;display:flex;align-items:center;background:#012851}
|
||||
.vtgl-list.off{background:#F5F4EF}
|
||||
.vtgl-list.dk-off{background:#0A1218}
|
||||
.vtgl-cal{padding:3px 5px;display:flex;align-items:center;background:#F5F4EF}
|
||||
.vtgl-cal.on{background:#012851}
|
||||
.vtgl-cal.dk-on{background:#A1DCD8}
|
||||
|
||||
/* Calendar month nav header */
|
||||
.cal-nav{background:#fff;border:1.5px solid #D8D6CF;border-radius:3px 3px 0 0;border-bottom:1px solid #E8E6E0;display:flex;align-items:center;justify-content:space-between;padding:5px 8px;box-shadow:0 1px 3px rgba(0,0,0,.04)}
|
||||
.cal-nav.dk{background:#0A1218;border-color:#1E2D3D;border-bottom-color:#1A2830}
|
||||
.cal-nav-btn{width:18px;height:18px;display:flex;align-items:center;justify-content:center;border:1.5px solid #D8D6CF;border-radius:2px;background:#F5F4EF;color:#555;font-size:8px;cursor:pointer}
|
||||
.cal-nav-btn.dk{border-color:#1E2D3D;background:#060C12;color:#4E6070}
|
||||
.cal-nav-title{font-size:7px;font-weight:800;color:#012851;letter-spacing:.3px;cursor:pointer}
|
||||
.cal-nav-title.dk{color:#A1DCD8}
|
||||
.cal-nav-sub{font-size:5px;color:#AAA;margin-top:1px;text-align:center}
|
||||
.cal-nav-sub.dk{color:#2A3A4A}
|
||||
|
||||
/* Calendar grid */
|
||||
.cal-grid{background:#fff;border:1.5px solid #D8D6CF;border-top:none;border-radius:0 0 3px 3px;overflow:hidden;box-shadow:0 1px 3px rgba(0,0,0,.04)}
|
||||
.cal-grid.dk{background:#0A1218;border-color:#1E2D3D}
|
||||
.cal-dow{display:grid;grid-template-columns:repeat(7,1fr);background:#F5F4EF;border-bottom:1px solid #E8E6E0}
|
||||
.cal-dow.dk{background:#060C12;border-bottom-color:#1A2830}
|
||||
.cal-dow-cell{padding:3px 0;text-align:center;font-size:4.5px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#888}
|
||||
.cal-dow-cell.dk{color:#2E4050}
|
||||
.cal-week{display:grid;grid-template-columns:repeat(7,1fr)}
|
||||
.cal-cell{border-right:1px solid #F0EEE8;border-bottom:1px solid #F0EEE8;min-height:50px;padding:3px 3px 2px;position:relative}
|
||||
.cal-cell.dk{border-right-color:#1A2830;border-bottom-color:#1A2830}
|
||||
.cal-cell.empty{background:#FAFAF8}
|
||||
.cal-cell.empty.dk{background:#070E14}
|
||||
.cal-cell.prev-month .cal-day-num,.cal-cell.next-month .cal-day-num{color:#D0CEC8}
|
||||
.cal-cell.prev-month,.cal-cell.next-month{background:#FAFAF8}
|
||||
.cal-cell.prev-month.dk,.cal-cell.next-month.dk{background:#070E14}
|
||||
.cal-cell:last-child,.cal-week:last-child .cal-cell{border-bottom:none}
|
||||
.cal-cell:nth-child(7n){border-right:none}
|
||||
.cal-day-num{text-align:right;font-size:5.5px;font-weight:700;color:#444;margin-bottom:2px;line-height:1}
|
||||
.cal-day-num.dk{color:#3E5065}
|
||||
.doc-entry{font-size:4.5px;color:#012851;padding:1.5px 3px;background:#EFF8F7;border-left:2px solid #A1DCD8;margin-bottom:1px;border-radius:1px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer;display:block;transition:background .1s}
|
||||
.doc-entry:hover{background:#D4EDE9}
|
||||
.doc-entry.dk{color:#A1DCD8;background:#0A2030;border-left-color:#1E5060}
|
||||
.doc-entry.dk:hover{background:#0E2535}
|
||||
.doc-overflow{font-size:4px;color:#888;font-style:italic;padding:1px 3px;cursor:pointer}
|
||||
.doc-overflow:hover{text-decoration:underline}
|
||||
.doc-overflow.dk{color:#3E5065}
|
||||
|
||||
/* Hover card */
|
||||
.hover-card{position:absolute;z-index:50;background:#fff;border:1.5px solid #D8D6CF;border-radius:4px;box-shadow:0 8px 24px rgba(0,0,0,.14);padding:8px 10px;min-width:140px;font-family:'Helvetica Neue',sans-serif}
|
||||
.hover-card.dk{background:#0F1923;border-color:#1E2D3D}
|
||||
|
||||
/* Tables */
|
||||
.rules{background:#fff;border:1px solid #E0DDD6;border-radius:6px;overflow:hidden;margin-top:28px}
|
||||
.rules table{width:100%;border-collapse:collapse}
|
||||
.rules th{background:#F4F2EC;font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#888;padding:8px 12px;text-align:left;border-bottom:1px solid #E0DDD6}
|
||||
.rules td{font-size:11px;color:#444;padding:8px 12px;border-bottom:1px solid #F0EEE8;vertical-align:top;line-height:1.55}
|
||||
.rules tr:last-child td{border-bottom:none}
|
||||
.rules td:first-child{font-size:9px;font-weight:700;color:#012851;white-space:nowrap;width:150px}
|
||||
.rules td code{font-size:9px;background:#F0EFE9;padding:1px 4px;border-radius:2px;color:#555}
|
||||
|
||||
.impl-ref{background:#fff;border:1px solid #E0DDD6;border-radius:6px;overflow:hidden;margin-top:28px}
|
||||
.impl-ref table{width:100%;border-collapse:collapse}
|
||||
.impl-ref th{background:#012851;color:#fff;padding:7px 12px;text-align:left;font-size:8px;font-weight:800;letter-spacing:.6px;text-transform:uppercase}
|
||||
.impl-ref td{padding:7px 12px;border-bottom:1px solid #F0EEE8;vertical-align:top;font-size:11px;color:#444;line-height:1.55}
|
||||
.impl-ref tr:nth-child(even) td{background:#FAFAF7}
|
||||
.impl-ref td:first-child{font-size:9px;font-weight:700;color:#012851;white-space:nowrap;width:180px}
|
||||
.impl-ref td code{font-size:9px;background:#F0EFE9;padding:1px 4px;border-radius:2px;font-family:'Courier New',monospace;color:#333}
|
||||
|
||||
.callout{padding:12px 16px;border-radius:4px;font-size:11.5px;line-height:1.6;margin-bottom:16px}
|
||||
.callout.navy{background:rgba(1,40,81,.06);border-left:3px solid #012851;color:#333}
|
||||
.callout.orange{background:#FEF3E2;border-left:3px solid #C26A00;color:#333}
|
||||
.callout.green{background:#EAF5EA;border-left:3px solid #2E6E39;color:#333}
|
||||
.callout strong{font-weight:700}
|
||||
hr{border:none;border-top:2px dashed #C8C4BE;margin:60px 0}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
|
||||
<!-- ══════════════════════════════════════════
|
||||
MASTHEAD
|
||||
══════════════════════════════════════════ -->
|
||||
<div class="mh">
|
||||
<h1>Kalenderansicht · Spec #386</h1>
|
||||
<p>Monatliches Kalenderraster auf <code>/documents</code> als Alternative zur Listenansicht. Jeder Tag mit Dokumenten zeigt kompakte Eintragszeilen; Hover öffnet eine Vorschaukarte; Klick navigiert zu <code>/documents/{id}</code>. Aktive Personen- und Tag-Filter bleiben bei der Monatsnavigation erhalten.</p>
|
||||
<div class="byline">Familienarchiv · 2026-05-03 · Leonie Voss, UX Lead · Issue #386</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ══════════════════════════════════════════
|
||||
SECTION 1 — ANATOMY
|
||||
══════════════════════════════════════════ -->
|
||||
<div class="sh"><h2>1 · Anatomy</h2><p>Acht benannte Zonen der Kalenderansicht.</p></div>
|
||||
|
||||
<div style="background:#fff;border:1.5px solid #E0DDD6;border-radius:6px;padding:24px;margin-bottom:24px">
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:24px">
|
||||
<div>
|
||||
<div style="background:#F9F8F5;border:1.5px solid #D8D6CF;border-radius:4px;overflow:hidden">
|
||||
<!-- Nav bar annotation -->
|
||||
<div style="background:#012851;height:22px;display:flex;align-items:center;padding:0 10px;gap:6px">
|
||||
<span style="font-size:6px;font-weight:900;color:#fff;border-bottom:1.5px solid #A1DCD8;padding-bottom:1px">Familienarchiv</span>
|
||||
<span style="font-size:5px;color:rgba(255,255,255,.9);font-weight:700;margin-left:4px">Dokumente</span>
|
||||
<span style="margin-left:auto;font-size:5px;color:#A1DCD8;font-weight:700">① Ansichts-Toggle</span>
|
||||
<div style="display:flex;gap:1px;border:1px solid #1E4060;border-radius:2px;overflow:hidden">
|
||||
<div style="padding:2px 4px;background:#0E2840;opacity:.6"><svg width="7" height="7" viewBox="0 0 16 16" fill="#A1DCD8"><rect x="0" y="0" width="16" height="3"/><rect x="0" y="6" width="16" height="3"/><rect x="0" y="12" width="16" height="3"/></svg></div>
|
||||
<div style="padding:2px 4px;background:#A1DCD8"><svg width="7" height="7" viewBox="0 0 16 16" fill="#012851"><rect x="1" y="2" width="14" height="13" rx="1" fill="none" stroke="#012851" stroke-width="2"/><line x1="5" y1="0" x2="5" y2="5" stroke="#012851" stroke-width="2"/><line x1="11" y1="0" x2="11" y2="5" stroke="#012851" stroke-width="2"/><line x1="1" y1="7" x2="15" y2="7" stroke="#012851" stroke-width="1.5"/></svg></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- SearchFilterBar -->
|
||||
<div style="background:#F5F4EF;border-bottom:1px solid #E0DDD6;padding:5px 8px;display:flex;align-items:center;gap:4px">
|
||||
<div style="flex:1;height:15px;background:#fff;border:1px solid #D8D6CF;border-radius:2px;padding:0 4px;font-size:5px;color:#999;display:flex;align-items:center">Dokumente durchsuchen …</div>
|
||||
<div style="font-size:5px;color:#AAA;font-weight:700;white-space:nowrap">② SearchFilterBar (Timeline ausgeblendet)</div>
|
||||
</div>
|
||||
<!-- Month nav header -->
|
||||
<div style="background:#fff;border-bottom:1px solid #E8E6E0;display:flex;align-items:center;justify-content:space-between;padding:5px 8px">
|
||||
<div style="width:16px;height:16px;border:1.5px solid #D8D6CF;border-radius:2px;background:#F5F4EF;display:flex;align-items:center;justify-content:center;font-size:8px;color:#555">‹</div>
|
||||
<div style="text-align:center">
|
||||
<div style="font-size:7px;font-weight:800;color:#012851">Oktober 1912</div>
|
||||
<div style="font-size:4px;color:#AAA">③ Monats-Navigation</div>
|
||||
</div>
|
||||
<div style="width:16px;height:16px;border:1.5px solid #D8D6CF;border-radius:2px;background:#F5F4EF;display:flex;align-items:center;justify-content:center;font-size:8px;color:#555">›</div>
|
||||
</div>
|
||||
<!-- DOW header -->
|
||||
<div style="display:grid;grid-template-columns:repeat(7,1fr);background:#F5F4EF;border-bottom:1px solid #E8E6E0">
|
||||
<div style="padding:2px 0;text-align:center;font-size:4px;font-weight:800;color:#888;text-transform:uppercase">Mo</div>
|
||||
<div style="padding:2px 0;text-align:center;font-size:4px;font-weight:800;color:#888;text-transform:uppercase">Di</div>
|
||||
<div style="padding:2px 0;text-align:center;font-size:4px;font-weight:800;color:#888;text-transform:uppercase">Mi</div>
|
||||
<div style="padding:2px 0;text-align:center;font-size:4px;font-weight:800;color:#888;text-transform:uppercase">Do</div>
|
||||
<div style="padding:2px 0;text-align:center;font-size:4px;font-weight:800;color:#888;text-transform:uppercase">Fr</div>
|
||||
<div style="padding:2px 0;text-align:center;font-size:4px;font-weight:800;color:#888;text-transform:uppercase">Sa</div>
|
||||
<div style="padding:2px 0;text-align:center;font-size:4px;font-weight:800;color:#888;text-transform:uppercase">So</div>
|
||||
</div>
|
||||
<!-- Week rows (2 shown) -->
|
||||
<div style="display:grid;grid-template-columns:repeat(7,1fr)">
|
||||
<!-- Sep 30 (prev) -->
|
||||
<div style="border-right:1px solid #F0EEE8;border-bottom:1px solid #F0EEE8;min-height:38px;padding:2px;background:#FAFAF8">
|
||||
<div style="text-align:right;font-size:4.5px;font-weight:700;color:#D0CEC8">30</div>
|
||||
</div>
|
||||
<!-- Oct 1: with doc -->
|
||||
<div style="border-right:1px solid #F0EEE8;border-bottom:1px solid #F0EEE8;min-height:38px;padding:2px">
|
||||
<div style="text-align:right;font-size:4.5px;font-weight:700;color:#444;margin-bottom:1px">1</div>
|
||||
<div style="font-size:3.5px;color:#012851;padding:1px 2px;background:#EFF8F7;border-left:1.5px solid #A1DCD8;border-radius:1px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-bottom:1px">Brief — Herbsternte</div>
|
||||
<div style="font-size:3px;color:#AAA;padding:0 2px;font-style:italic">⑤ Dokumentzeile</div>
|
||||
</div>
|
||||
<!-- Oct 2 -->
|
||||
<div style="border-right:1px solid #F0EEE8;border-bottom:1px solid #F0EEE8;min-height:38px;padding:2px"><div style="text-align:right;font-size:4.5px;font-weight:700;color:#444">2</div></div>
|
||||
<!-- Oct 3 -->
|
||||
<div style="border-right:1px solid #F0EEE8;border-bottom:1px solid #F0EEE8;min-height:38px;padding:2px"><div style="text-align:right;font-size:4.5px;font-weight:700;color:#444">3</div></div>
|
||||
<!-- Oct 4 -->
|
||||
<div style="border-right:1px solid #F0EEE8;border-bottom:1px solid #F0EEE8;min-height:38px;padding:2px"><div style="text-align:right;font-size:4.5px;font-weight:700;color:#444">4</div></div>
|
||||
<!-- Oct 5: with doc -->
|
||||
<div style="border-right:1px solid #F0EEE8;border-bottom:1px solid #F0EEE8;min-height:38px;padding:2px">
|
||||
<div style="text-align:right;font-size:4.5px;font-weight:700;color:#444;margin-bottom:1px">5</div>
|
||||
<div style="font-size:3.5px;color:#012851;padding:1px 2px;background:#EFF8F7;border-left:1.5px solid #A1DCD8;border-radius:1px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis">Postkarte Hamburg</div>
|
||||
</div>
|
||||
<!-- Oct 6 -->
|
||||
<div style="border-right:none;border-bottom:1px solid #F0EEE8;min-height:38px;padding:2px"><div style="text-align:right;font-size:4.5px;font-weight:700;color:#444">6</div></div>
|
||||
</div>
|
||||
<!-- Annotation row -->
|
||||
<div style="padding:4px 8px;background:#FFF8E6;border-top:1px dashed #E8C060;font-size:5px;color:#888">
|
||||
④ Tageszelle · ⑥ Überlauf "+N weitere" · ⑦ Hover-Karte (Overlay) · ⑧ Leere Zelle
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px">
|
||||
<div style="font-size:11px;color:#444;line-height:1.65"><span style="font-weight:700;color:#012851">① Toggle</span> — Rechts in der SearchFilterBar. List | Kalender. Aktive Ansicht: navy-Hintergrund.</div>
|
||||
<div style="font-size:11px;color:#444;line-height:1.65"><span style="font-weight:700;color:#012851">② Timeline</span> — Timeline-Widget (#385) wird in Kalender-Mode <em>ausgeblendet</em>. Eine aktive Auswahl wird im State behalten und bei Rückwechsel wiederhergestellt.</div>
|
||||
<div style="font-size:11px;color:#444;line-height:1.65"><span style="font-weight:700;color:#012851">③ Monats-Nav</span> — ‹ / › für Prev/Next. Klick auf Monatstitel öffnet Year/Month-Picker. Feld zeigt „Monat Jahr" (deutsch).</div>
|
||||
<div style="font-size:11px;color:#444;line-height:1.65"><span style="font-weight:700;color:#012851">④ Tageszelle</span> — Tagesnummer oben rechts. Unter 4 Dokumente: alle als Zeilen. Ab 4: erste 3 + „+N weitere"-Link.</div>
|
||||
<div style="font-size:11px;color:#444;line-height:1.65"><span style="font-weight:700;color:#012851">⑤ Dokumentzeile</span> — Einzeilig: Kurzform des Titels. Linker Mint-Balken als Farb-Akzent. Klick → <code>/documents/{id}</code>.</div>
|
||||
<div style="font-size:11px;color:#444;line-height:1.65"><span style="font-weight:700;color:#012851">⑦ Hover-Karte</span> — Erscheint beim Hovern über eine Dokumentzeile. Zeigt Titel, Datum, Absender → Empfänger, optionalen Thumbnail-Stub. Verschwindet on-mouseout.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ══════════════════════════════════════════
|
||||
SECTION 2 — DESIGN TOKENS
|
||||
══════════════════════════════════════════ -->
|
||||
<div class="sh"><h2>2 · Design-Tokens</h2></div>
|
||||
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:40px">
|
||||
<div style="background:#fff;border:1px solid #E0DDD6;border-radius:6px;overflow:hidden">
|
||||
<div style="background:#F4F2EC;padding:8px 14px;font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#888;border-bottom:1px solid #E0DDD6">Light theme</div>
|
||||
<table style="width:100%;border-collapse:collapse;font-size:11px">
|
||||
<tr style="border-bottom:1px solid #F0EEE8"><td style="padding:6px 14px;color:#888;font-size:9px;font-weight:700;width:190px">cal-surface</td><td style="padding:6px 14px"><span style="display:inline-block;width:12px;height:12px;background:#fff;border:1px solid #DDD;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#FFFFFF — Kalender-Hintergrund</td></tr>
|
||||
<tr style="border-bottom:1px solid #F0EEE8"><td style="padding:6px 14px;color:#888;font-size:9px;font-weight:700">cal-header-bg</td><td style="padding:6px 14px"><span style="display:inline-block;width:12px;height:12px;background:#F5F4EF;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#F5F4EF — DOW-Kopfzeile, Monat-Nav</td></tr>
|
||||
<tr style="border-bottom:1px solid #F0EEE8"><td style="padding:6px 14px;color:#888;font-size:9px;font-weight:700">cal-line</td><td style="padding:6px 14px"><span style="display:inline-block;width:12px;height:12px;background:#F0EEE8;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#F0EEE8 — Zellrahmen intern</td></tr>
|
||||
<tr style="border-bottom:1px solid #F0EEE8"><td style="padding:6px 14px;color:#888;font-size:9px;font-weight:700">cal-outer-line</td><td style="padding:6px 14px"><span style="display:inline-block;width:12px;height:12px;background:#D8D6CF;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#D8D6CF — Widget-Außenrahmen</td></tr>
|
||||
<tr style="border-bottom:1px solid #F0EEE8"><td style="padding:6px 14px;color:#888;font-size:9px;font-weight:700">doc-entry-bg</td><td style="padding:6px 14px"><span style="display:inline-block;width:12px;height:12px;background:#EFF8F7;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#EFF8F7 — Dokumentzeile Hintergrund</td></tr>
|
||||
<tr style="border-bottom:1px solid #F0EEE8"><td style="padding:6px 14px;color:#888;font-size:9px;font-weight:700">doc-entry-accent</td><td style="padding:6px 14px"><span style="display:inline-block;width:12px;height:12px;background:#A1DCD8;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#A1DCD8 — Linker Balken der Dokumentzeile (brand-mint)</td></tr>
|
||||
<tr style="border-bottom:1px solid #F0EEE8"><td style="padding:6px 14px;color:#888;font-size:9px;font-weight:700">doc-entry-text</td><td style="padding:6px 14px"><span style="display:inline-block;width:12px;height:12px;background:#012851;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#012851 — Titel in Dokumentzeile (navy)</td></tr>
|
||||
<tr style="border-bottom:1px solid #F0EEE8"><td style="padding:6px 14px;color:#888;font-size:9px;font-weight:700">inactive-day-bg</td><td style="padding:6px 14px"><span style="display:inline-block;width:12px;height:12px;background:#FAFAF8;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#FAFAF8 — Zellen des Vor-/Nachmonats</td></tr>
|
||||
<tr><td style="padding:6px 14px;color:#888;font-size:9px;font-weight:700">inactive-day-num</td><td style="padding:6px 14px"><span style="display:inline-block;width:12px;height:12px;background:#D0CEC8;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#D0CEC8 — Tagesnummer Vor-/Nachmonat</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
<div style="background:#0F1923;border:1px solid #1E2D3D;border-radius:6px;overflow:hidden">
|
||||
<div style="background:#0A1218;padding:8px 14px;font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#4E6070;border-bottom:1px solid #1E2D3D">Dark theme</div>
|
||||
<table style="width:100%;border-collapse:collapse;font-size:11px">
|
||||
<tr style="border-bottom:1px solid #1A2830"><td style="padding:6px 14px;color:#4E6070;font-size:9px;font-weight:700;width:190px">cal-surface</td><td style="padding:6px 14px;color:#8AAABB"><span style="display:inline-block;width:12px;height:12px;background:#0A1218;border:1px solid #1E2D3D;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#0A1218</td></tr>
|
||||
<tr style="border-bottom:1px solid #1A2830"><td style="padding:6px 14px;color:#4E6070;font-size:9px;font-weight:700">cal-header-bg</td><td style="padding:6px 14px;color:#8AAABB"><span style="display:inline-block;width:12px;height:12px;background:#060C12;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#060C12</td></tr>
|
||||
<tr style="border-bottom:1px solid #1A2830"><td style="padding:6px 14px;color:#4E6070;font-size:9px;font-weight:700">cal-line</td><td style="padding:6px 14px;color:#8AAABB"><span style="display:inline-block;width:12px;height:12px;background:#1A2830;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#1A2830</td></tr>
|
||||
<tr style="border-bottom:1px solid #1A2830"><td style="padding:6px 14px;color:#4E6070;font-size:9px;font-weight:700">doc-entry-bg</td><td style="padding:6px 14px;color:#8AAABB"><span style="display:inline-block;width:12px;height:12px;background:#0A2030;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#0A2030</td></tr>
|
||||
<tr style="border-bottom:1px solid #1A2830"><td style="padding:6px 14px;color:#4E6070;font-size:9px;font-weight:700">doc-entry-accent</td><td style="padding:6px 14px;color:#8AAABB"><span style="display:inline-block;width:12px;height:12px;background:#1E5060;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#1E5060 — gedämpftes Teal</td></tr>
|
||||
<tr style="border-bottom:1px solid #1A2830"><td style="padding:6px 14px;color:#4E6070;font-size:9px;font-weight:700">doc-entry-text</td><td style="padding:6px 14px;color:#8AAABB"><span style="display:inline-block;width:12px;height:12px;background:#A1DCD8;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#A1DCD8 — Mint statt Navy</td></tr>
|
||||
<tr><td style="padding:6px 14px;color:#4E6070;font-size:9px;font-weight:700">inactive-day</td><td style="padding:6px 14px;color:#8AAABB"><span style="display:inline-block;width:12px;height:12px;background:#070E14;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#070E14</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ══════════════════════════════════════════
|
||||
SECTION 3 — FULL CALENDAR MOCKUP (LIGHT)
|
||||
══════════════════════════════════════════ -->
|
||||
<div class="sh"><h2>3 · Vollständiger Kalender — Oktober 1912 · Light</h2><p>7 Dokumente verteilt auf den Monat. Zeigt alle Zell-Varianten: leer, 1 Dokument, mehrere Dokumente, und Überlauf.</p></div>
|
||||
|
||||
<div class="col">
|
||||
<div class="lbl"><span class="tag">Desktop · 1440px · ~55% Skalierung</span>Light mode</div>
|
||||
<div class="chrome" style="width:720px">
|
||||
<div class="cbar"><div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="url"></div><div style="font-size:5px;color:#AAA;margin-left:4px">familienarchiv.local/documents</div></div>
|
||||
<div class="nav">
|
||||
<div class="nav-logo">Familienarchiv</div>
|
||||
<div class="nav-link" style="margin-left:8px">Dokumente</div>
|
||||
<div class="nav-link" style="margin-left:4px">Personen</div>
|
||||
<div class="nav-link" style="margin-left:4px">Chronik</div>
|
||||
<div class="nav-r"><div class="nav-av">KR</div></div>
|
||||
</div>
|
||||
<div class="pb">
|
||||
<!-- SearchFilterBar with Calendar toggle active -->
|
||||
<div class="sfb">
|
||||
<div class="sfb-input"><span>Dokumente durchsuchen …</span></div>
|
||||
<div class="sfb-btn">Sortierung ▾</div>
|
||||
<div class="sfb-btn">▾ Filter</div>
|
||||
<!-- View toggle: calendar active -->
|
||||
<div class="vtgl">
|
||||
<div class="vtgl-list off" title="Listenansicht"><svg width="9" height="9" viewBox="0 0 16 16" fill="#888"><rect x="0" y="0" width="16" height="3"/><rect x="0" y="6" width="16" height="3"/><rect x="0" y="12" width="16" height="3"/></svg></div>
|
||||
<div class="vtgl-cal on" title="Kalenderansicht (aktiv)"><svg width="9" height="9" viewBox="0 0 16 16" fill="#A1DCD8"><rect x="1" y="2" width="14" height="13" rx="1" fill="none" stroke="#A1DCD8" stroke-width="2"/><line x1="5" y1="0" x2="5" y2="5" stroke="#A1DCD8" stroke-width="2"/><line x1="11" y1="0" x2="11" y2="5" stroke="#A1DCD8" stroke-width="2"/><line x1="1" y1="7" x2="15" y2="7" stroke="#A1DCD8" stroke-width="1.5"/></svg></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Month nav header -->
|
||||
<div class="cal-nav">
|
||||
<div class="cal-nav-btn">‹</div>
|
||||
<div style="text-align:center">
|
||||
<div class="cal-nav-title" title="Klicken für Year/Month-Picker">Oktober 1912</div>
|
||||
<div class="cal-nav-sub">7 Dokumente in diesem Monat</div>
|
||||
</div>
|
||||
<div class="cal-nav-btn">›</div>
|
||||
</div>
|
||||
|
||||
<!-- DOW header -->
|
||||
<div class="cal-grid">
|
||||
<div class="cal-dow">
|
||||
<div class="cal-dow-cell">Mo</div>
|
||||
<div class="cal-dow-cell">Di</div>
|
||||
<div class="cal-dow-cell">Mi</div>
|
||||
<div class="cal-dow-cell">Do</div>
|
||||
<div class="cal-dow-cell">Fr</div>
|
||||
<div class="cal-dow-cell">Sa</div>
|
||||
<div class="cal-dow-cell">So</div>
|
||||
</div>
|
||||
|
||||
<!-- WEEK 1: Mo 30 Sep (prev) → So 6 Okt -->
|
||||
<div class="cal-week">
|
||||
<!-- Sep 30 — prev month -->
|
||||
<div class="cal-cell prev-month"><div class="cal-day-num" style="color:#D0CEC8">30</div></div>
|
||||
<!-- Oct 1 — 1 doc: Brief über Herbsternte -->
|
||||
<div class="cal-cell" style="position:relative">
|
||||
<div class="cal-day-num">1</div>
|
||||
<a class="doc-entry" title="Brief über die Herbsternte · Karl Raddatz → Elfriede Raddatz">Brief — Herbsternte</a>
|
||||
</div>
|
||||
<!-- Oct 2 — leer -->
|
||||
<div class="cal-cell"><div class="cal-day-num">2</div></div>
|
||||
<!-- Oct 3 — leer -->
|
||||
<div class="cal-cell"><div class="cal-day-num">3</div></div>
|
||||
<!-- Oct 4 — leer -->
|
||||
<div class="cal-cell"><div class="cal-day-num">4</div></div>
|
||||
<!-- Oct 5 — 1 doc: Postkarte Hamburg -->
|
||||
<div class="cal-cell">
|
||||
<div class="cal-day-num">5</div>
|
||||
<a class="doc-entry">Postkarte aus Hamburg</a>
|
||||
</div>
|
||||
<!-- Oct 6 — leer -->
|
||||
<div class="cal-cell"><div class="cal-day-num">6</div></div>
|
||||
</div>
|
||||
|
||||
<!-- WEEK 2: 7–13 Okt -->
|
||||
<div class="cal-week">
|
||||
<!-- Oct 7 — leer -->
|
||||
<div class="cal-cell"><div class="cal-day-num">7</div></div>
|
||||
<!-- Oct 8 — 2 docs -->
|
||||
<div class="cal-cell">
|
||||
<div class="cal-day-num">8</div>
|
||||
<a class="doc-entry">Nachrichten v. Gut</a>
|
||||
<a class="doc-entry">Grüße aus Posen</a>
|
||||
</div>
|
||||
<!-- Oct 9 — leer -->
|
||||
<div class="cal-cell"><div class="cal-day-num">9</div></div>
|
||||
<!-- Oct 10 — leer -->
|
||||
<div class="cal-cell"><div class="cal-day-num">10</div></div>
|
||||
<!-- Oct 11 — leer -->
|
||||
<div class="cal-cell"><div class="cal-day-num">11</div></div>
|
||||
<!-- Oct 12 — 1 doc: Erbschaftssachen -->
|
||||
<div class="cal-cell">
|
||||
<div class="cal-day-num">12</div>
|
||||
<a class="doc-entry">Brief — Erbschaftssachen</a>
|
||||
</div>
|
||||
<!-- Oct 13 — leer -->
|
||||
<div class="cal-cell"><div class="cal-day-num">13</div></div>
|
||||
</div>
|
||||
|
||||
<!-- WEEK 3: 14–20 Okt -->
|
||||
<div class="cal-week">
|
||||
<!-- Oct 14 — leer -->
|
||||
<div class="cal-cell"><div class="cal-day-num">14</div></div>
|
||||
<!-- Oct 15 — 1 doc: Antwort auf Elfriedes Brief -->
|
||||
<div class="cal-cell">
|
||||
<div class="cal-day-num">15</div>
|
||||
<a class="doc-entry">Antwort a. Elfriedes Brief</a>
|
||||
</div>
|
||||
<!-- Oct 16 — leer -->
|
||||
<div class="cal-cell"><div class="cal-day-num">16</div></div>
|
||||
<!-- Oct 17 — leer -->
|
||||
<div class="cal-cell"><div class="cal-day-num">17</div></div>
|
||||
<!-- Oct 18 — leer -->
|
||||
<div class="cal-cell"><div class="cal-day-num">18</div></div>
|
||||
<!-- Oct 19 — 1 doc: Reisebericht Berlin + HOVER CARD demo -->
|
||||
<div class="cal-cell" style="position:relative">
|
||||
<div class="cal-day-num">19</div>
|
||||
<a class="doc-entry" style="background:#D4EDE9">Reisebericht aus Berlin</a>
|
||||
<!-- HOVER CARD appears here -->
|
||||
<div class="hover-card" style="top:0;right:calc(100% + 4px);width:140px">
|
||||
<div style="font-size:6px;font-weight:800;color:#012851;margin-bottom:3px;line-height:1.3">Reisebericht aus Berlin</div>
|
||||
<div style="font-size:5px;color:#AAA;margin-bottom:4px">19. Oktober 1912</div>
|
||||
<div style="display:flex;align-items:flex-start;gap:3px;margin-bottom:4px">
|
||||
<div style="width:24px;height:32px;background:#E8E6DF;border-radius:1px;flex-shrink:0"></div>
|
||||
<div>
|
||||
<div style="font-size:5px;color:#888;margin-bottom:1px"><span style="font-weight:700;text-transform:uppercase;letter-spacing:.3px">Von</span> Hans Raddatz</div>
|
||||
<div style="font-size:5px;color:#888"><span style="font-weight:700;text-transform:uppercase;letter-spacing:.3px">An</span> Elfriede Raddatz</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="font-size:5px;color:#012851;font-weight:700;text-transform:uppercase;letter-spacing:.4px;padding-top:3px;border-top:1px solid #E8E6E0">Zum Dokument →</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Oct 20 — leer -->
|
||||
<div class="cal-cell"><div class="cal-day-num">20</div></div>
|
||||
</div>
|
||||
|
||||
<!-- WEEK 4: 21–27 Okt -->
|
||||
<div class="cal-week">
|
||||
<!-- Oct 21 — leer -->
|
||||
<div class="cal-cell"><div class="cal-day-num">21</div></div>
|
||||
<!-- Oct 22 — leer -->
|
||||
<div class="cal-cell"><div class="cal-day-num">22</div></div>
|
||||
<!-- Oct 23 — 1 doc: Weihnachtspläne -->
|
||||
<div class="cal-cell">
|
||||
<div class="cal-day-num">23</div>
|
||||
<a class="doc-entry">Weihnachtspläne 1912</a>
|
||||
</div>
|
||||
<!-- Oct 24 — leer -->
|
||||
<div class="cal-cell"><div class="cal-day-num">24</div></div>
|
||||
<!-- Oct 25 — leer -->
|
||||
<div class="cal-cell"><div class="cal-day-num">25</div></div>
|
||||
<!-- Oct 26 — leer -->
|
||||
<div class="cal-cell"><div class="cal-day-num">26</div></div>
|
||||
<!-- Oct 27 — leer -->
|
||||
<div class="cal-cell"><div class="cal-day-num">27</div></div>
|
||||
</div>
|
||||
|
||||
<!-- WEEK 5: 28 Okt – 3 Nov -->
|
||||
<div class="cal-week" style="border-bottom:none">
|
||||
<!-- Oct 28 — 1 doc: Namenstag -->
|
||||
<div class="cal-cell" style="border-bottom:none">
|
||||
<div class="cal-day-num">28</div>
|
||||
<a class="doc-entry">Grüße z. Namenstag</a>
|
||||
</div>
|
||||
<!-- Oct 29 -->
|
||||
<div class="cal-cell" style="border-bottom:none"><div class="cal-day-num">29</div></div>
|
||||
<!-- Oct 30 -->
|
||||
<div class="cal-cell" style="border-bottom:none"><div class="cal-day-num">30</div></div>
|
||||
<!-- Oct 31 -->
|
||||
<div class="cal-cell" style="border-bottom:none"><div class="cal-day-num">31</div></div>
|
||||
<!-- Nov 1 — next month -->
|
||||
<div class="cal-cell next-month" style="border-bottom:none;background:#FAFAF8"><div class="cal-day-num" style="color:#D0CEC8">1</div></div>
|
||||
<!-- Nov 2 -->
|
||||
<div class="cal-cell next-month" style="border-bottom:none;background:#FAFAF8"><div class="cal-day-num" style="color:#D0CEC8">2</div></div>
|
||||
<!-- Nov 3 -->
|
||||
<div class="cal-cell next-month" style="border-bottom:none;background:#FAFAF8;border-right:none"><div class="cal-day-num" style="color:#D0CEC8">3</div></div>
|
||||
</div>
|
||||
</div><!-- /cal-grid -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ══════════════════════════════════════════
|
||||
SECTION 4 — DAY CELL VARIANTS
|
||||
══════════════════════════════════════════ -->
|
||||
<div class="sh"><h2>4 · Tageszellen-Varianten</h2><p>Alle möglichen Zustände einer Tageszelle im Detail.</p></div>
|
||||
|
||||
<div class="grid">
|
||||
|
||||
<!-- Empty -->
|
||||
<div class="col">
|
||||
<div class="lbl"><span class="tag">Variant A</span>Leere Zelle</div>
|
||||
<div style="background:#fff;border:1.5px solid #D8D6CF;border-radius:3px;min-height:70px;padding:5px;width:100px">
|
||||
<div style="text-align:right;font-size:7px;font-weight:700;color:#444">14</div>
|
||||
</div>
|
||||
<div class="cap">Nur Tagesnummer. Kein Cursor-Pointer. Kein Hover-Effekt.</div>
|
||||
</div>
|
||||
|
||||
<!-- 1 doc -->
|
||||
<div class="col">
|
||||
<div class="lbl"><span class="tag">Variant B</span>1 Dokument</div>
|
||||
<div style="background:#fff;border:1.5px solid #D8D6CF;border-radius:3px;min-height:70px;padding:5px;width:160px">
|
||||
<div style="text-align:right;font-size:7px;font-weight:700;color:#444;margin-bottom:3px">1</div>
|
||||
<div style="font-size:7px;color:#012851;padding:2px 4px;background:#EFF8F7;border-left:2.5px solid #A1DCD8;border-radius:1px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer">Brief über die Herbsternte</div>
|
||||
</div>
|
||||
<div class="cap">Titel wird auf eine Zeile begrenzt. Klick → <code>/documents/{id}</code>.</div>
|
||||
</div>
|
||||
|
||||
<!-- 3 docs -->
|
||||
<div class="col">
|
||||
<div class="lbl"><span class="tag">Variant C</span>3 Dokumente</div>
|
||||
<div style="background:#fff;border:1.5px solid #D8D6CF;border-radius:3px;min-height:70px;padding:5px;width:160px">
|
||||
<div style="text-align:right;font-size:7px;font-weight:700;color:#444;margin-bottom:3px">8</div>
|
||||
<div style="font-size:7px;color:#012851;padding:2px 4px;background:#EFF8F7;border-left:2.5px solid #A1DCD8;border-radius:1px;margin-bottom:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer">Nachrichten v. Gut</div>
|
||||
<div style="font-size:7px;color:#012851;padding:2px 4px;background:#EFF8F7;border-left:2.5px solid #A1DCD8;border-radius:1px;margin-bottom:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer">Grüße aus Posen</div>
|
||||
<div style="font-size:7px;color:#012851;padding:2px 4px;background:#EFF8F7;border-left:2.5px solid #A1DCD8;border-radius:1px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer">Brief a. Elfriede</div>
|
||||
</div>
|
||||
<div class="cap">Bis zu 3 Einträge vollständig sichtbar.</div>
|
||||
</div>
|
||||
|
||||
<!-- Overflow: 5 docs -->
|
||||
<div class="col">
|
||||
<div class="lbl"><span class="tag">Variant D</span>5 Dokumente — Überlauf</div>
|
||||
<div style="background:#fff;border:1.5px solid #D8D6CF;border-radius:3px;min-height:70px;padding:5px;width:160px">
|
||||
<div style="text-align:right;font-size:7px;font-weight:700;color:#444;margin-bottom:3px">15</div>
|
||||
<div style="font-size:7px;color:#012851;padding:2px 4px;background:#EFF8F7;border-left:2.5px solid #A1DCD8;border-radius:1px;margin-bottom:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer">Antwort a. Elfriedes Brief</div>
|
||||
<div style="font-size:7px;color:#012851;padding:2px 4px;background:#EFF8F7;border-left:2.5px solid #A1DCD8;border-radius:1px;margin-bottom:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer">Feldpost Nr. 12</div>
|
||||
<div style="font-size:7px;color:#012851;padding:2px 4px;background:#EFF8F7;border-left:2.5px solid #A1DCD8;border-radius:1px;margin-bottom:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;cursor:pointer">Kurzbrief Sonntag</div>
|
||||
<div style="font-size:6.5px;color:#888;font-style:italic;padding:2px 4px;cursor:pointer;text-decoration:underline">+2 weitere</div>
|
||||
</div>
|
||||
<div class="cap">Ab 4 Docs: erste 3 anzeigen, Rest als „+N weitere". Klick auf Link → Tages-Ansicht oder Modal (OQ-4, Entscheidung: Modal).</div>
|
||||
</div>
|
||||
|
||||
<!-- Prev/next month cell -->
|
||||
<div class="col">
|
||||
<div class="lbl"><span class="tag">Variant E</span>Vor-/Nachmonat</div>
|
||||
<div style="background:#FAFAF8;border:1.5px solid #D8D6CF;border-radius:3px;min-height:70px;padding:5px;width:100px">
|
||||
<div style="text-align:right;font-size:7px;font-weight:700;color:#D0CEC8">30</div>
|
||||
</div>
|
||||
<div class="cap">Gedimmte Tagesnummer. Kein Dokument-Eintrag (Dokumente des Vormonats erscheinen nicht im aktuellen Raster). Kein klickbarer Inhalt.</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ══════════════════════════════════════════
|
||||
SECTION 5 — DARK MODE
|
||||
══════════════════════════════════════════ -->
|
||||
<div class="sh"><h2>5 · Dark Mode — Kalender Oktober 1912</h2></div>
|
||||
|
||||
<div class="col">
|
||||
<div class="lbl"><span class="tag">Dark</span>Kalender · aktiver Monat</div>
|
||||
<div class="chrome dk" style="width:560px">
|
||||
<div class="cbar"><div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="url"></div></div>
|
||||
<div class="nav" style="background:#060C12"><div class="nav-logo">Familienarchiv</div><div class="nav-link on" style="margin-left:8px">Dokumente</div><div class="nav-r"><div class="nav-av">KR</div></div></div>
|
||||
<div class="pb dk">
|
||||
<!-- SearchFilterBar dark -->
|
||||
<div class="sfb dk">
|
||||
<div class="sfb-input dk">Dokumente durchsuchen …</div>
|
||||
<div class="sfb-btn dk">▾ Filter</div>
|
||||
<div class="vtgl dk">
|
||||
<div style="padding:3px 5px;background:#0A1218"><svg width="9" height="9" viewBox="0 0 16 16" fill="#4E6070"><rect x="0" y="0" width="16" height="3"/><rect x="0" y="6" width="16" height="3"/><rect x="0" y="12" width="16" height="3"/></svg></div>
|
||||
<div style="padding:3px 5px;background:#A1DCD8"><svg width="9" height="9" viewBox="0 0 16 16" fill="#012851"><rect x="1" y="2" width="14" height="13" rx="1" fill="none" stroke="#012851" stroke-width="2"/><line x1="5" y1="0" x2="5" y2="5" stroke="#012851" stroke-width="2"/><line x1="11" y1="0" x2="11" y2="5" stroke="#012851" stroke-width="2"/><line x1="1" y1="7" x2="15" y2="7" stroke="#012851" stroke-width="1.5"/></svg></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Month nav dark -->
|
||||
<div class="cal-nav dk">
|
||||
<div class="cal-nav-btn dk">‹</div>
|
||||
<div style="text-align:center">
|
||||
<div class="cal-nav-title dk">Oktober 1912</div>
|
||||
<div class="cal-nav-sub dk">7 Dokumente in diesem Monat</div>
|
||||
</div>
|
||||
<div class="cal-nav-btn dk">›</div>
|
||||
</div>
|
||||
<!-- Calendar grid dark -->
|
||||
<div class="cal-grid dk">
|
||||
<div class="cal-dow dk">
|
||||
<div class="cal-dow-cell dk">Mo</div><div class="cal-dow-cell dk">Di</div><div class="cal-dow-cell dk">Mi</div><div class="cal-dow-cell dk">Do</div><div class="cal-dow-cell dk">Fr</div><div class="cal-dow-cell dk">Sa</div><div class="cal-dow-cell dk">So</div>
|
||||
</div>
|
||||
<div class="cal-week">
|
||||
<div class="cal-cell dk prev-month" style="background:#070E14"><div class="cal-day-num dk" style="color:#1E2D3D">30</div></div>
|
||||
<div class="cal-cell dk"><div class="cal-day-num dk">1</div><a class="doc-entry dk">Brief — Herbsternte</a></div>
|
||||
<div class="cal-cell dk"><div class="cal-day-num dk">2</div></div>
|
||||
<div class="cal-cell dk"><div class="cal-day-num dk">3</div></div>
|
||||
<div class="cal-cell dk"><div class="cal-day-num dk">4</div></div>
|
||||
<div class="cal-cell dk"><div class="cal-day-num dk">5</div><a class="doc-entry dk">Postkarte Hamburg</a></div>
|
||||
<div class="cal-cell dk"><div class="cal-day-num dk">6</div></div>
|
||||
</div>
|
||||
<div class="cal-week">
|
||||
<div class="cal-cell dk"><div class="cal-day-num dk">7</div></div>
|
||||
<div class="cal-cell dk"><div class="cal-day-num dk">8</div><a class="doc-entry dk">Nachrichten v. Gut</a><a class="doc-entry dk">Grüße aus Posen</a></div>
|
||||
<div class="cal-cell dk"><div class="cal-day-num dk">9</div></div>
|
||||
<div class="cal-cell dk"><div class="cal-day-num dk">10</div></div>
|
||||
<div class="cal-cell dk"><div class="cal-day-num dk">11</div></div>
|
||||
<div class="cal-cell dk"><div class="cal-day-num dk">12</div><a class="doc-entry dk">Brief — Erbschaft</a></div>
|
||||
<div class="cal-cell dk"><div class="cal-day-num dk">13</div></div>
|
||||
</div>
|
||||
<div class="cal-week" style="border-bottom:none">
|
||||
<div class="cal-cell dk" style="border-bottom:none"><div class="cal-day-num dk">14</div></div>
|
||||
<div class="cal-cell dk" style="border-bottom:none"><div class="cal-day-num dk">15</div><a class="doc-entry dk">Antwort a. Elfriede</a></div>
|
||||
<div class="cal-cell dk" style="border-bottom:none"><div class="cal-day-num dk">16</div></div>
|
||||
<div class="cal-cell dk" style="border-bottom:none"><div class="cal-day-num dk">17</div></div>
|
||||
<div class="cal-cell dk" style="border-bottom:none"><div class="cal-day-num dk">18</div></div>
|
||||
<div class="cal-cell dk" style="border-bottom:none"><div class="cal-day-num dk">19</div><a class="doc-entry dk">Reisebericht Berlin</a></div>
|
||||
<div class="cal-cell dk" style="border-bottom:none"><div class="cal-day-num dk">20</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cap">Dark mode: Dokumentzeilen Mint-Text auf sehr dunklem Hintergrund. Balken-Akzent gedämpftes Teal. Monatstitel Mint.</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ══════════════════════════════════════════
|
||||
SECTION 6 — FILTER INTEGRATION
|
||||
══════════════════════════════════════════ -->
|
||||
<div class="sh"><h2>6 · Filter-Integration</h2><p>Aktive Personen- und Tag-Filter aus der SearchFilterBar bleiben bei Kalender-Navigation erhalten.</p></div>
|
||||
|
||||
<div class="grid">
|
||||
<div class="col">
|
||||
<div class="lbl"><span class="tag">Filter aktiv</span>Nur Briefe von Karl Raddatz</div>
|
||||
<div class="chrome" style="width:440px">
|
||||
<div class="cbar"><div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="url"></div></div>
|
||||
<div class="nav"><div class="nav-logo">Familienarchiv</div><div class="nav-r"><div class="nav-av">KR</div></div></div>
|
||||
<div class="pb">
|
||||
<div class="sfb">
|
||||
<div class="sfb-input"><span>Dokumente durchsuchen …</span></div>
|
||||
<!-- Active filter chip: person -->
|
||||
<div style="display:flex;align-items:center;gap:2px;background:#F0EFE9;border:1.5px solid #A1DCD8;border-radius:10px;padding:2px 6px 2px 3px">
|
||||
<div style="width:12px;height:12px;border-radius:50%;background:#012851;display:flex;align-items:center;justify-content:center;font-size:4px;font-weight:800;color:#A1DCD8">KR</div>
|
||||
<span style="font-size:5px;font-weight:600;color:#333">Karl Raddatz</span>
|
||||
<span style="font-size:7px;color:#999;margin-left:1px;cursor:pointer">×</span>
|
||||
</div>
|
||||
<div class="vtgl">
|
||||
<div style="padding:3px 5px;background:#F5F4EF"><svg width="9" height="9" viewBox="0 0 16 16" fill="#888"><rect x="0" y="0" width="16" height="3"/><rect x="0" y="6" width="16" height="3"/><rect x="0" y="12" width="16" height="3"/></svg></div>
|
||||
<div style="padding:3px 5px;background:#012851"><svg width="9" height="9" viewBox="0 0 16 16" fill="#A1DCD8"><rect x="1" y="2" width="14" height="13" rx="1" fill="none" stroke="#A1DCD8" stroke-width="2"/><line x1="5" y1="0" x2="5" y2="5" stroke="#A1DCD8" stroke-width="2"/><line x1="11" y1="0" x2="11" y2="5" stroke="#A1DCD8" stroke-width="2"/><line x1="1" y1="7" x2="15" y2="7" stroke="#A1DCD8" stroke-width="1.5"/></svg></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cal-nav">
|
||||
<div class="cal-nav-btn">‹</div>
|
||||
<div style="text-align:center">
|
||||
<div class="cal-nav-title">Oktober 1912</div>
|
||||
<div class="cal-nav-sub" style="color:#C26A00;font-weight:700">3 von 7 Dokumente sichtbar (Filter aktiv)</div>
|
||||
</div>
|
||||
<div class="cal-nav-btn">›</div>
|
||||
</div>
|
||||
<div class="cal-grid">
|
||||
<div class="cal-dow">
|
||||
<div class="cal-dow-cell">Mo</div><div class="cal-dow-cell">Di</div><div class="cal-dow-cell">Mi</div><div class="cal-dow-cell">Do</div><div class="cal-dow-cell">Fr</div><div class="cal-dow-cell">Sa</div><div class="cal-dow-cell">So</div>
|
||||
</div>
|
||||
<div class="cal-week">
|
||||
<div class="cal-cell prev-month"><div class="cal-day-num" style="color:#D0CEC8">30</div></div>
|
||||
<!-- Oct 1: Karl's brief shown -->
|
||||
<div class="cal-cell"><div class="cal-day-num">1</div><a class="doc-entry">Brief — Herbsternte</a></div>
|
||||
<div class="cal-cell"><div class="cal-day-num">2</div></div>
|
||||
<div class="cal-cell"><div class="cal-day-num">3</div></div>
|
||||
<div class="cal-cell"><div class="cal-day-num">4</div></div>
|
||||
<!-- Oct 5: Postkarte Hamburg — sent by Hans, NOT shown -->
|
||||
<div class="cal-cell"><div class="cal-day-num">5</div></div>
|
||||
<div class="cal-cell"><div class="cal-day-num">6</div></div>
|
||||
</div>
|
||||
<div class="cal-week">
|
||||
<div class="cal-cell"><div class="cal-day-num">7</div></div>
|
||||
<!-- Oct 8: Nachrichten v. Gut — Karl's; Grüße aus Posen — not Karl's -->
|
||||
<div class="cal-cell"><div class="cal-day-num">8</div><a class="doc-entry">Nachrichten v. Gut</a></div>
|
||||
<div class="cal-cell"><div class="cal-day-num">9</div></div>
|
||||
<div class="cal-cell"><div class="cal-day-num">10</div></div>
|
||||
<div class="cal-cell"><div class="cal-day-num">11</div></div>
|
||||
<!-- Oct 12: Elfriede schreibt — not Karl's -->
|
||||
<div class="cal-cell"><div class="cal-day-num">12</div></div>
|
||||
<div class="cal-cell"><div class="cal-day-num">13</div></div>
|
||||
</div>
|
||||
<div class="cal-week" style="border-bottom:none">
|
||||
<div class="cal-cell" style="border-bottom:none"><div class="cal-day-num">14</div></div>
|
||||
<!-- Oct 15: Karl's reply shown -->
|
||||
<div class="cal-cell" style="border-bottom:none"><div class="cal-day-num">15</div><a class="doc-entry">Antwort a. Elfriedes Brief</a></div>
|
||||
<div class="cal-cell" style="border-bottom:none"><div class="cal-day-num">16</div></div>
|
||||
<div class="cal-cell" style="border-bottom:none"><div class="cal-day-num">17</div></div>
|
||||
<div class="cal-cell" style="border-bottom:none"><div class="cal-day-num">18</div></div>
|
||||
<!-- Oct 19: Hans's Reisebericht — not Karl's -->
|
||||
<div class="cal-cell" style="border-bottom:none"><div class="cal-day-num">19</div></div>
|
||||
<div class="cal-cell" style="border-bottom:none"><div class="cal-day-num">20</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cap">Person-Filter „Karl Raddatz" ist aktiv. Monat-Nav zeigt „3 von 7 Dokumente sichtbar (Filter aktiv)". Tage ohne Karl-Dokumente sind leer, auch wenn der Originalmonat dort Briefe anderer Personen hat.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ══════════════════════════════════════════
|
||||
SECTION 7 — BEHAVIOUR RULES
|
||||
══════════════════════════════════════════ -->
|
||||
<div class="sh"><h2>7 · Verhaltensregeln</h2></div>
|
||||
|
||||
<div class="rules">
|
||||
<table>
|
||||
<tr><th>Szenario</th><th>Verhalten</th></tr>
|
||||
<tr><td>Ansichts-Toggle</td><td>Klick auf Kalender-Icon → URL wechselt zu <code>?view=calendar</code>. Listenansicht und Timeline werden per <code>display:none</code> ausgeblendet. State (Filter, Auswahl) bleibt erhalten.</td></tr>
|
||||
<tr><td>Startmonat (OQ-3)</td><td><strong>Entscheidung: Monat des aktuellsten Dokuments.</strong> Wenn Timeline-Filter aktiv ist, zeigt Kalender den ersten Monat des Filterbereichs. Ohne Filter: Monat des neuesten Dokuments im Archiv.</td></tr>
|
||||
<tr><td>Monats-Navigation Prev/Next</td><td>Button ‹ / › lädt Dokumente des Nachbarmonats via <code>GET /api/documents?year=1912&month=11</code>. Vorherige und nachfolgende Monate werden prefetched (SvelteKit prefetch).</td></tr>
|
||||
<tr><td>Year/Month-Picker</td><td>Klick auf Monatstitel öffnet ein kompaktes Dropdown mit Jahres-Liste (scrollen) und Monatsgitter (12 Felder). Bestätigung mit Enter oder Klick auf Monat.</td></tr>
|
||||
<tr><td>Dokumentzeilen-Hover</td><td>Hover-Karte erscheint nach 400 ms Verzögerung neben der Zelle (kein sofortiges Aufpoppen). Karte verschwindet wenn Maus die Zelle verlässt. Kein Hover auf Touch-Geräten.</td></tr>
|
||||
<tr><td>Klick auf Dokumentzeile</td><td>Navigation zu <code>/documents/{id}</code>.</td></tr>
|
||||
<tr><td>Überlauf-Link „+N weitere" (OQ-4)</td><td><strong>Entscheidung: Modal</strong> mit vollständiger Liste der Dokumente des Tages. Kein In-Cell-Scroll. Modal zeigt Datum als Titel + alle Dokumente als DocumentRow.</td></tr>
|
||||
<tr><td>Kein documentDate</td><td>Dokument erscheint nicht im Kalender. Timeline-Widget und Listenansicht zeigen es weiterhin.</td></tr>
|
||||
<tr><td>Monat ohne Dokumente</td><td>Leeres Raster (alle Zellen ohne Einträge). Monat-Nav zeigt „0 Dokumente in diesem Monat".</td></tr>
|
||||
<tr><td>Aktive Filter + Kalender</td><td>AND-Semantik. Nur Dokumente, die alle aktiven Filter (Person, Tag, Zeitachse) erfüllen, erscheinen im Kalender.</td></tr>
|
||||
<tr><td>Mobile (<640 px)</td><td>Kalenderansicht wechselt zu Wochen-View (1 Woche sichtbar, horizontal scrollbar). Toggle zwischen List/Kalender bleibt verfügbar.</td></tr>
|
||||
<tr><td>Keyboard-Navigation</td><td>Tab auf Monatsnavigation: ‹ / › per Enter/Space. Pfeiltasten bewegen Fokus von Zelle zu Zelle. Enter auf Dokumentzeile: Navigation zu Dokument.</td></tr>
|
||||
<tr><td>ARIA</td><td><code>role="grid"</code> auf Kalender-Container. <code>role="gridcell"</code> auf jede Tageszelle. <code>aria-label="15. Oktober 1912, 3 Dokumente"</code> auf belegte Zellen. <code>aria-label="Vorheriger Monat"</code> auf ‹-Button.</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ══════════════════════════════════════════
|
||||
SECTION 8 — OPEN QUESTIONS ANSWERED
|
||||
══════════════════════════════════════════ -->
|
||||
<div class="sh"><h2>8 · Offene Fragen — Entscheidungen</h2></div>
|
||||
|
||||
<div class="callout navy"><strong>OQ-3: Welcher Monat wird zuerst angezeigt?</strong> Monat des aktuellsten Dokuments (nach <code>documentDate</code>). Begründung: Neue Uploads werden zuerst erschlossen. Wenn ein Zeitachsen-Filter aktiv ist, zeigt der Kalender den ersten Monat des Filterbereichs.</div>
|
||||
<div class="callout navy"><strong>OQ-4: Zell-Überlauf bei 4+ Dokumenten.</strong> Erste 3 sichtbar + „+N weitere"-Link → öffnet Modal. Kein In-Cell-Scroll (schlechte Touch-Zugänglichkeit). Das Modal zeigt den Tagestitel als Heading + eine vollständige DocumentRow-Liste.</div>
|
||||
<div class="callout green"><strong>Backend-Anforderung.</strong> Neuer Endpoint <code>GET /api/documents?year=1912&month=10</code> ohne Paginierung — gibt alle Dokumente des Monats zurück (max. ~100, keine Offset-Pages). Alternativ: bestehender Endpoint mit <code>dateFrom=1912-10-01&dateTo=1912-10-31</code>. Der View-Toggle-State wird als URL-Parameter <code>?view=calendar</code> gespeichert und von SvelteKit per <code>url.searchParams</code> gelesen.</div>
|
||||
|
||||
|
||||
<!-- ══════════════════════════════════════════
|
||||
SECTION 9 — IMPL-REF TABLE
|
||||
══════════════════════════════════════════ -->
|
||||
<div class="sh"><h2>9 · Implementation Reference</h2></div>
|
||||
|
||||
<div class="impl-ref">
|
||||
<table>
|
||||
<tr><th>Element</th><th>Tailwind-Klassen</th><th>Pixelwert</th><th>Anmerkung</th></tr>
|
||||
<tr><td>Ansichts-Toggle Container</td><td><code>flex border border-line rounded-sm overflow-hidden</code></td><td>—</td><td>Sitzt ganz rechts in der SearchFilterBar-Zeile</td></tr>
|
||||
<tr><td>Toggle-Button (inaktiv)</td><td><code>p-1.5 bg-muted flex items-center justify-center</code></td><td>6px padding</td><td>Icon: 16px × 16px SVG</td></tr>
|
||||
<tr><td>Toggle-Button (aktiv)</td><td><code>p-1.5 bg-primary flex items-center justify-center</code></td><td>6px padding</td><td>Icon fill: <code>#A1DCD8</code></td></tr>
|
||||
<tr><td>Monat-Nav Container</td><td><code>bg-surface border border-line rounded-t-sm flex items-center justify-between px-4 py-2.5</code></td><td>padding 16px / 10px</td><td>Oberer Radius; Kalender-Grid hat unteren Radius</td></tr>
|
||||
<tr><td>Prev/Next Button</td><td><code>w-8 h-8 flex items-center justify-center border border-line rounded-sm bg-muted text-ink-2 hover:bg-line transition-colors</code></td><td>32px / 32px</td><td>Touch target ≥ 44px durch unsichtbares Padding auf Wrapper</td></tr>
|
||||
<tr><td>Monatstitel</td><td><code>text-sm font-bold text-primary cursor-pointer hover:underline</code></td><td>14px / 700</td><td>Klick → Year/Month-Picker Dropdown</td></tr>
|
||||
<tr><td>Monat-Subtext</td><td><code>text-[10px] text-ink-3 text-center mt-0.5</code></td><td>10px</td><td>Orange + fett wenn Filter aktiv: <code>text-orange-600 font-bold</code></td></tr>
|
||||
<tr><td>DOW-Kopfzeile</td><td><code>grid grid-cols-7 bg-muted border-b border-line</code></td><td>—</td><td>—</td></tr>
|
||||
<tr><td>DOW-Zelle</td><td><code>py-1.5 text-center text-[9px] font-bold uppercase tracking-widest text-ink-3</code></td><td>9px / 700</td><td>Kein Klick-Handler</td></tr>
|
||||
<tr><td>Kalender-Grid</td><td><code>bg-surface border border-line border-t-0 rounded-b-sm</code> + <code>role="grid"</code></td><td>—</td><td>Border-top entfernt wegen Monat-Nav-Header</td></tr>
|
||||
<tr><td>Wochen-Reihe</td><td><code>grid grid-cols-7</code></td><td>—</td><td>—</td></tr>
|
||||
<tr><td>Tageszelle (aktiver Monat)</td><td><code>border-r border-b border-line min-h-[80px] p-1.5 relative</code></td><td>min 80px hoch</td><td><code>role="gridcell"</code> + <code>aria-label</code></td></tr>
|
||||
<tr><td>Tageszelle (Vor/Nachmonat)</td><td><code>border-r border-b border-line min-h-[80px] p-1.5 bg-muted/40</code></td><td>—</td><td>Kein Dokument-Inhalt</td></tr>
|
||||
<tr><td>Tagesnummer (aktiv)</td><td><code>text-right text-[11px] font-bold text-ink mb-1 leading-none</code></td><td>11px / 700</td><td>—</td></tr>
|
||||
<tr><td>Tagesnummer (inaktiv)</td><td><code>text-right text-[11px] font-bold text-ink-3/40 mb-1 leading-none</code></td><td>11px</td><td>Stark gedimmt</td></tr>
|
||||
<tr><td>Dokumentzeile</td><td><code>block text-[10px] text-primary truncate px-1.5 py-0.5 bg-[#EFF8F7] border-l-2 border-mint rounded-[2px] mb-0.5 cursor-pointer hover:bg-mint/20 transition-colors</code></td><td>10px</td><td><code>role="link"</code> + <code>aria-label</code> mit Volltitel</td></tr>
|
||||
<tr><td>Überlauf-Link</td><td><code>text-[9px] text-ink-3 italic px-1.5 py-0.5 hover:underline cursor-pointer</code></td><td>9px</td><td>Öffnet Modal via <code>onclick</code></td></tr>
|
||||
<tr><td>Hover-Karte</td><td><code>absolute z-50 bg-surface border border-line rounded-sm shadow-lg p-3 min-w-[180px] pointer-events-none</code></td><td>—</td><td>Positioniert per JS (verhindert Viewport-Overflow). <code>pointer-events-none</code> damit sie Hover nicht blockiert.</td></tr>
|
||||
<tr><td>Hover-Karte Titel</td><td><code>text-[11px] font-bold text-ink mb-1 leading-snug</code></td><td>11px / 700</td><td>Mehrzeilig erlaubt</td></tr>
|
||||
<tr><td>Hover-Karte Datum</td><td><code>text-[9px] text-ink-3 mb-2</code></td><td>9px</td><td>Format: „19. Oktober 1912"</td></tr>
|
||||
<tr><td>Hover-Karte Von/An</td><td><code>text-[9px] text-ink-2</code> mit <code>font-bold uppercase tracking-wide</code> auf Label</td><td>9px</td><td>—</td></tr>
|
||||
<tr><td>Komponenten-Namen</td><td><code>CalendarView.svelte</code>, <code>CalendarMonthGrid.svelte</code>, <code>CalendarDayCell.svelte</code>, <code>CalendarDocEntry.svelte</code>, <code>CalendarHoverCard.svelte</code>, <code>MonthPicker.svelte</code></td><td>—</td><td>Alle unter <code>src/lib/components/calendar/</code></td></tr>
|
||||
<tr><td>Benötigter URL-Parameter</td><td><code>?view=calendar&year=1912&month=10</code></td><td>—</td><td>SvelteKit <code>url.searchParams</code> in <code>+page.server.ts</code></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div><!-- /page -->
|
||||
</body>
|
||||
</html>
|
||||
1039
docs/specs/document-topbar-b1-responsive.html
Normal file
1039
docs/specs/document-topbar-b1-responsive.html
Normal file
File diff suppressed because it is too large
Load Diff
875
docs/specs/enrich-edit-4-concepts.html
Normal file
875
docs/specs/enrich-edit-4-concepts.html
Normal file
@@ -0,0 +1,875 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Enrich / Edit Document — 4 Concept Designs · Familienarchiv</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Merriweather:ital,wght@0,300;0,400;0,700;1,300&family=Montserrat:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"/>
|
||||
<style>
|
||||
/* ── Reset ── */
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
body{font-family:'Montserrat',system-ui,sans-serif;background:#ECEAE4;color:#1A1A1A;line-height:1.5;font-size:13px}
|
||||
.doc{max-width:1300px;margin:0 auto;padding:48px 32px 120px}
|
||||
|
||||
/* ── Masthead ── */
|
||||
.mh{padding-bottom:24px;border-bottom:3px solid #002850;margin-bottom:60px}
|
||||
.mh h1{font-size:23px;font-weight:900;color:#002850;letter-spacing:-.4px}
|
||||
.mh p{font-size:13px;color:#555;max-width:740px;line-height:1.75;margin-top:8px}
|
||||
.mh .byline{font-size:9px;color:#999;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;margin-top:10px}
|
||||
.tag-row{display:flex;gap:6px;margin-top:10px;flex-wrap:wrap}
|
||||
.tag{background:#002850;color:#A6DAD8;padding:2px 8px;border-radius:2px;font-size:8px;font-weight:700;letter-spacing:.8px;text-transform:uppercase}
|
||||
.tag.amber{background:#7c4a00;color:#fde68a}
|
||||
.tag.green{background:#1e5e34;color:#d1fae5}
|
||||
.tag.gray{background:#4b5563;color:#e5e7eb}
|
||||
|
||||
/* ── Concept section ── */
|
||||
.concept{margin-bottom:88px;padding-bottom:88px;border-bottom:2px dashed #C8C4BE}
|
||||
.concept:last-of-type{border-bottom:none;margin-bottom:0;padding-bottom:0}
|
||||
.concept-header{display:flex;align-items:flex-start;gap:24px;margin-bottom:36px}
|
||||
.concept-num{font-size:72px;font-weight:900;color:#E0DDD6;line-height:1;flex-shrink:0;width:80px}
|
||||
.concept-label{font-size:8.5px;font-weight:800;text-transform:uppercase;letter-spacing:1.2px;color:#A6DAD8;margin-bottom:5px}
|
||||
.concept-title{font-family:'Merriweather',Georgia,serif;font-size:22px;font-weight:700;color:#002850;margin-bottom:8px}
|
||||
.concept-desc{font-size:13px;color:#555;max-width:720px;line-height:1.75}
|
||||
.concept-best{margin-top:10px;display:flex;align-items:center;gap:7px}
|
||||
.best-label{background:#A6DAD8;color:#002850;padding:2px 7px;border-radius:2px;font-size:7.5px;font-weight:800;letter-spacing:.5px;text-transform:uppercase}
|
||||
.best-text{font-size:11.5px;font-weight:600;color:#444}
|
||||
.concept-tradeoff{margin-top:6px;font-size:11.5px;color:#888;font-style:italic;max-width:640px}
|
||||
|
||||
/* ── Browser chrome ── */
|
||||
.screen{max-width:940px;margin:0 auto}
|
||||
.chrome{background:#F5F4EE;border:1.5px solid #C4C0BA;border-radius:8px;overflow:hidden;box-shadow:0 4px 20px rgba(0,0,0,.1)}
|
||||
.chrome-bar{height:20px;background:#E8E6E0;border-bottom:1px solid #C4C0BA;display:flex;align-items:center;padding:0 8px;gap:4px;flex-shrink:0}
|
||||
.chrome-dot{width:6px;height:6px;border-radius:50%;background:#BDB8B1}
|
||||
.chrome-url{flex:1;height:9px;background:#CCC8C2;border-radius:5px;margin-left:6px}
|
||||
|
||||
/* ── App nav ── */
|
||||
.app-nav{height:30px;background:#002850;display:flex;align-items:center;padding:0 12px;gap:10px;flex-shrink:0}
|
||||
.app-logo{font-family:'Merriweather',Georgia,serif;font-size:7px;font-weight:700;color:#fff;border-bottom:2px solid #A6DAD8;padding-bottom:1px}
|
||||
.app-link{font-size:5.5px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:rgba(255,255,255,.45);white-space:nowrap}
|
||||
.app-link.on{color:rgba(255,255,255,.9)}
|
||||
.app-nav-r{margin-left:auto;display:flex;gap:6px;align-items:center}
|
||||
.app-avatar{width:16px;height:16px;border-radius:50%;background:rgba(255,255,255,.12);display:flex;align-items:center;justify-content:center;font-size:5px;font-weight:800;color:rgba(255,255,255,.5)}
|
||||
|
||||
/* ── Enrich top bar ── */
|
||||
.enrich-bar{height:36px;background:#F5F4EE;border-bottom:1px solid #E4E2D7;display:flex;align-items:center;padding:0 14px;gap:10px;flex-shrink:0}
|
||||
.eb-back{font-size:6px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#888;display:flex;align-items:center;gap:4px;white-space:nowrap}
|
||||
.eb-title{flex:1;text-align:center;font-family:'Merriweather',Georgia,serif;font-size:8px;font-weight:400;color:#002850;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:0 12px}
|
||||
.eb-progress{font-size:6px;color:#AAA;white-space:nowrap;font-weight:600}
|
||||
|
||||
/* ── Split pane ── */
|
||||
.app-body{display:flex;flex-direction:column;overflow:hidden}
|
||||
.split{display:flex;overflow:hidden}
|
||||
|
||||
/* ── PDF panel ── */
|
||||
.pdf-panel{background:#5E5C59;display:flex;flex-direction:column;overflow:hidden;border-right:1px solid #3A3836;flex:60}
|
||||
.pdf-toolbar{height:26px;background:#3A3836;display:flex;align-items:center;padding:0 8px;gap:5px;flex-shrink:0}
|
||||
.pdf-btn{width:14px;height:14px;border-radius:2px;background:rgba(255,255,255,.1);display:flex;align-items:center;justify-content:center;font-size:7px;color:rgba(255,255,255,.5)}
|
||||
.pdf-btn.on{background:rgba(255,255,255,.18)}
|
||||
.pdf-pg{font-size:6px;color:rgba(255,255,255,.4);margin:0 auto;font-weight:700;letter-spacing:.5px}
|
||||
.pdf-view{flex:1;display:flex;justify-content:center;padding:16px;overflow-y:auto}
|
||||
.pdf-paper{background:#FFFEF8;box-shadow:0 2px 10px rgba(0,0,0,.3);border-radius:1px;padding:14px 16px;display:flex;flex-direction:column;gap:0;width:190px;flex-shrink:0}
|
||||
.pl{height:4px;background:#C4BDB0;border-radius:1px;opacity:.55;margin-bottom:3px}
|
||||
.pl.h{height:5.5px;opacity:.75;margin-bottom:4px}
|
||||
.pl.s{width:55%;opacity:.3}
|
||||
.pl.m{width:80%}
|
||||
.pl.sp{height:6px;background:transparent}
|
||||
|
||||
/* ── Form panel ── */
|
||||
.form-panel{display:flex;flex-direction:column;overflow:hidden;background:#fff;flex:40}
|
||||
.form-scroll{flex:1;overflow-y:auto;padding:14px 14px}
|
||||
|
||||
/* ── Form elements ── */
|
||||
.f-card{background:#F9F8F5;border:1px solid #E4E2D7;border-radius:3px;padding:11px 12px;margin-bottom:10px}
|
||||
.f-card-title{font-size:6px;font-weight:800;text-transform:uppercase;letter-spacing:1px;color:#B0ADA6;margin-bottom:9px}
|
||||
.f-row{display:grid;grid-template-columns:1fr 1fr;gap:7px;margin-bottom:7px}
|
||||
.f-row.full{grid-template-columns:1fr}
|
||||
.f-field{display:flex;flex-direction:column;gap:2px}
|
||||
.f-label{font-size:6px;font-weight:700;color:#666}
|
||||
.f-req{color:#C0392B}
|
||||
.f-input{height:18px;border:1px solid #D4D0CA;border-radius:2px;background:#fff;font-size:7px;padding:0 6px;color:#333;display:flex;align-items:center}
|
||||
.f-input.focus{border-color:#002850;box-shadow:0 0 0 2px rgba(0,40,80,.12)}
|
||||
.f-input.filled{color:#002850;font-weight:600;background:#FAFBFF}
|
||||
.f-input.suggested{border-color:#A6DAD8;background:#F0FAFA;color:#005858;font-weight:600}
|
||||
.f-input.empty{color:#BBB;font-style:italic}
|
||||
.f-input.tall{height:26px}
|
||||
.f-textarea{height:36px;border:1px solid #D4D0CA;border-radius:2px;background:#fff;font-size:7px;padding:4px 6px;color:#555;resize:none;display:flex;align-items:flex-start;font-family:inherit}
|
||||
.f-tags{display:flex;gap:3px;flex-wrap:wrap;min-height:18px;border:1px solid #D4D0CA;border-radius:2px;padding:2px 4px;background:#fff;align-items:center}
|
||||
.f-chip{background:#002850;color:#A6DAD8;border-radius:2px;font-size:5.5px;font-weight:700;padding:1px 4px 1px 5px;display:flex;align-items:center;gap:2px}
|
||||
.f-chip-rm{color:rgba(166,218,216,.5);font-weight:400}
|
||||
.f-add{font-size:6px;color:#B0ADA6;cursor:pointer;padding:1px 0}
|
||||
.f-helper{font-size:5.5px;color:#B0ADA6;margin-top:1px}
|
||||
|
||||
/* ── Action bar ── */
|
||||
.action-bar{height:44px;background:#F5F4EE;border-top:1px solid #E4E2D7;display:flex;align-items:center;padding:0 12px;gap:6px;flex-shrink:0}
|
||||
.btn-skip{font-size:6.5px;font-weight:700;color:#AAA;letter-spacing:.2px;cursor:pointer}
|
||||
.btn-spacer{flex:1}
|
||||
.btn-outline{height:22px;padding:0 10px;border:1px solid #C0BDB6;border-radius:2px;font-size:6px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#777;display:flex;align-items:center;cursor:pointer}
|
||||
.btn-primary{height:22px;padding:0 10px;border-radius:2px;font-size:6px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;background:#002850;color:#fff;display:flex;align-items:center;cursor:pointer;gap:4px}
|
||||
.btn-primary.green{background:#1A7040}
|
||||
.btn-primary.locked{background:#C8C4BE;color:#888}
|
||||
|
||||
/* ── Section stepper tabs (Concept A) ── */
|
||||
.stepper{display:flex;border-bottom:1px solid #E4E2D7;background:#F0EEE9;flex-shrink:0}
|
||||
.stepper-tab{flex:1;padding:7px 4px;text-align:center;font-size:6px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#AAA;border-bottom:2px solid transparent;cursor:pointer;display:flex;align-items:center;justify-content:center;gap:3px}
|
||||
.stepper-tab.done{color:#1A7040}
|
||||
.stepper-tab.done .st-dot{background:#1A7040}
|
||||
.stepper-tab.active{color:#002850;border-bottom-color:#002850}
|
||||
.stepper-tab.active .st-dot{background:#002850}
|
||||
.st-dot{width:5px;height:5px;border-radius:50%;background:#D4D0CA;flex-shrink:0}
|
||||
.collapsed-section{border:1px solid #E4E2D7;border-radius:3px;overflow:hidden;margin-bottom:4px}
|
||||
.collapsed-row{padding:7px 10px;background:#F9F8F5;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #E4E2D7}
|
||||
.collapsed-row:last-child{border-bottom:none}
|
||||
.collapsed-label{font-size:6px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#B0ADA6}
|
||||
.collapsed-summary{font-size:6px;color:#AAA}
|
||||
.collapsed-summary.done{color:#1A7040;font-weight:700}
|
||||
|
||||
/* ── Progress bar (Concept B) ── */
|
||||
.req-bar-wrap{background:#F0EEE9;border-bottom:1px solid #E4E2D7;padding:6px 14px;display:flex;align-items:center;gap:8px;flex-shrink:0}
|
||||
.req-bar-label{font-size:5.5px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#AAA;white-space:nowrap}
|
||||
.req-bar-track{flex:1;height:3px;background:#E0DDD6;border-radius:2px}
|
||||
.req-bar-fill{height:100%;background:#002850;border-radius:2px;width:75%}
|
||||
.req-bar-count{font-size:6px;font-weight:800;color:#002850;white-space:nowrap}
|
||||
|
||||
/* ── Optional divider (Concept B) ── */
|
||||
.opt-divider{display:flex;align-items:center;gap:8px;margin:12px 0}
|
||||
.opt-line{flex:1;height:1px;background:#E4E2D7}
|
||||
.opt-label{font-size:5.5px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#C8C4BE}
|
||||
|
||||
/* ── Keyboard hints (Concept C) ── */
|
||||
.kbd{display:inline-flex;align-items:center;background:#E4E2D7;border:1px solid #C8C4BE;border-radius:2px;padding:0 4px;height:13px;font-size:5.5px;font-weight:700;color:#555;font-family:monospace}
|
||||
|
||||
/* ── Suggestion banner (Concept D) ── */
|
||||
.ocr-banner{background:#EDF8F7;border:1px solid #A6DAD8;border-radius:3px;padding:7px 10px;margin-bottom:11px;display:flex;align-items:flex-start;gap:8px}
|
||||
.ocr-icon{width:14px;height:14px;border-radius:50%;background:#A6DAD8;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:6.5px;color:#002850;font-weight:800}
|
||||
.ocr-text{}
|
||||
.ocr-title{font-size:6.5px;font-weight:800;color:#006B68;margin-bottom:2px}
|
||||
.ocr-body{font-size:6px;color:#337;line-height:1.5}
|
||||
.ocr-kbd{background:#D8F0EE;border:1px solid #A6DAD8;border-radius:2px;padding:0 3px;font-size:5.5px;font-weight:700;font-family:monospace;color:#006B68}
|
||||
|
||||
/* ── Completion ring (Concept D) ── */
|
||||
.ring-wrap{display:flex;align-items:center;gap:5px;margin:0 6px}
|
||||
.ring-count{font-size:6px;font-weight:700;color:#666}
|
||||
|
||||
/* ── Impl-ref table ── */
|
||||
.impl-ref{margin-top:80px;padding-top:48px;border-top:3px solid #002850}
|
||||
.impl-ref h2{font-size:17px;font-weight:900;color:#002850;margin-bottom:6px}
|
||||
.impl-ref .subtitle{font-size:12px;color:#666;margin-bottom:24px;line-height:1.6;max-width:720px}
|
||||
.impl-ref table{width:100%;border-collapse:collapse;font-size:11.5px}
|
||||
.impl-ref thead th{text-align:left;padding:7px 12px;background:#002850;color:#A6DAD8;font-size:8.5px;font-weight:800;text-transform:uppercase;letter-spacing:.8px}
|
||||
.impl-ref tbody td{padding:8px 12px;border-bottom:1px solid #E4E2D7;vertical-align:top;line-height:1.6;color:#333}
|
||||
.impl-ref tbody tr:nth-child(even) td{background:#F5F4EE}
|
||||
.impl-ref code{background:#E4E2D7;padding:1px 5px;border-radius:2px;font-family:monospace;font-size:10.5px;color:#1A1A1A}
|
||||
.impl-ref .con{font-size:9px;font-weight:800;background:#002850;color:#A6DAD8;padding:1px 5px;border-radius:2px;letter-spacing:.3px}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="doc">
|
||||
|
||||
<!-- ══════════════════════════════════════════
|
||||
MASTHEAD
|
||||
══════════════════════════════════════════ -->
|
||||
<div class="mh">
|
||||
<h1>Enrich / Edit Document — 4 Concept Designs</h1>
|
||||
<p>Four design directions for the unified document editing experience. The goal: make the enrich page the single entry point for both enrichment and editing, while reducing the friction of filling in metadata. Each concept attacks a different root cause of friction.</p>
|
||||
<div class="byline">Leonie Voss · UI/UX · 2026-04-17 · Familienarchiv</div>
|
||||
<div class="tag-row">
|
||||
<span class="tag">enrich</span>
|
||||
<span class="tag">edit-document</span>
|
||||
<span class="tag amber">4 concepts</span>
|
||||
<span class="tag green">friction reduction</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ══════════════════════════════════════════
|
||||
CONCEPT A — Section Stepper
|
||||
══════════════════════════════════════════ -->
|
||||
<div class="concept">
|
||||
<div class="concept-header">
|
||||
<div class="concept-num">A</div>
|
||||
<div>
|
||||
<div class="concept-label">Concept A</div>
|
||||
<div class="concept-title">Section Stepper</div>
|
||||
<div class="concept-desc">The form is split into three sequential steps — <em>Wer & Wann</em>, <em>Beschreibung</em>, <em>Inhalt</em> — shown one at a time via tabs at the top of the form panel. Completed sections collapse to a single summary line with a green checkmark. The primary action becomes "Weiter →" until the final step, then "Speichern & Geprüft". Eliminates the wall-of-fields effect; the user always knows exactly what to do next.</div>
|
||||
<div class="concept-best"><span class="best-label">Best for</span><span class="best-text">First-time enrichment; queued batch of documents</span></div>
|
||||
<div class="concept-tradeoff">Trade-off: power users who tab through all fields quickly will find the extra "Next" click annoying. Mitigate with keyboard shortcut (⌘↵ = advance section).</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="screen">
|
||||
<div class="chrome">
|
||||
<div class="chrome-bar">
|
||||
<div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-dot"></div>
|
||||
<div class="chrome-url"></div>
|
||||
</div>
|
||||
<div class="app-body">
|
||||
<div class="app-nav">
|
||||
<span class="app-logo">Familienarchiv</span>
|
||||
<span class="app-link">Dokumente</span>
|
||||
<span class="app-link">Personen</span>
|
||||
<span class="app-link on">Anreicherung</span>
|
||||
<div class="app-nav-r"><div class="app-avatar">MR</div></div>
|
||||
</div>
|
||||
<div class="enrich-bar">
|
||||
<div class="eb-back">← Zur Liste</div>
|
||||
<div class="eb-title">Brief an Tante Hilde, Sommer 1952</div>
|
||||
<div class="eb-progress">12 verbleibend</div>
|
||||
</div>
|
||||
<div class="split" style="height:470px">
|
||||
|
||||
<!-- PDF panel -->
|
||||
<div class="pdf-panel">
|
||||
<div class="pdf-toolbar">
|
||||
<div class="pdf-btn on">⊕</div>
|
||||
<div class="pdf-btn">⊖</div>
|
||||
<div class="pdf-btn">↺</div>
|
||||
<div class="pdf-pg">Seite 1 / 2</div>
|
||||
<div class="pdf-btn">☷</div>
|
||||
</div>
|
||||
<div class="pdf-view">
|
||||
<div class="pdf-paper">
|
||||
<div class="pl h"></div>
|
||||
<div class="pl s"></div>
|
||||
<div class="pl sp"></div>
|
||||
<div class="pl m"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl s"></div>
|
||||
<div class="pl sp" style="height:4px"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl m"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl s"></div>
|
||||
<div class="pl sp" style="height:4px"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl m"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl s"></div>
|
||||
<div class="pl sp" style="height:4px"></div>
|
||||
<div class="pl m"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl s"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form panel -->
|
||||
<div class="form-panel">
|
||||
<!-- Section stepper -->
|
||||
<div class="stepper">
|
||||
<div class="stepper-tab done"><div class="st-dot"></div>Wer & Wann</div>
|
||||
<div class="stepper-tab active"><div class="st-dot"></div>Beschreibung</div>
|
||||
<div class="stepper-tab"><div class="st-dot"></div>Inhalt</div>
|
||||
</div>
|
||||
|
||||
<div class="form-scroll" style="padding:12px 14px">
|
||||
|
||||
<!-- Completed section: collapsed summary -->
|
||||
<div class="collapsed-section" style="margin-bottom:10px">
|
||||
<div class="collapsed-row">
|
||||
<span class="collapsed-label" style="color:#1A7040">✓ Wer & Wann</span>
|
||||
<span class="collapsed-summary done">15.07.1952 · Karl Raddatz → Hilde Brandt</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Active section: Beschreibung -->
|
||||
<div class="f-card" style="border-color:#002850;border-width:1.5px">
|
||||
<div class="f-card-title" style="color:#002850">Beschreibung</div>
|
||||
<div class="f-row full" style="margin-bottom:8px">
|
||||
<div class="f-field">
|
||||
<span class="f-label">Titel <span class="f-req">*</span></span>
|
||||
<div class="f-input focus tall">Brief an Tante Hilde — München 1952</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="f-row full" style="margin-bottom:8px">
|
||||
<div class="f-field">
|
||||
<span class="f-label">Schlagworte</span>
|
||||
<div class="f-tags">
|
||||
<div class="f-chip">Familie <span class="f-chip-rm">×</span></div>
|
||||
<div class="f-chip">Krieg <span class="f-chip-rm">×</span></div>
|
||||
<div class="f-add">+ Schlagwort hinzufügen</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="f-row full">
|
||||
<div class="f-field">
|
||||
<span class="f-label">Aufbewahrungsort</span>
|
||||
<div class="f-input empty">z. B. Karton 3, Regal B</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pending section: collapsed -->
|
||||
<div class="collapsed-section">
|
||||
<div class="collapsed-row">
|
||||
<span class="collapsed-label">Inhalt</span>
|
||||
<span class="collapsed-summary">Noch nicht ausgefüllt</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action bar -->
|
||||
<div class="action-bar">
|
||||
<span class="btn-skip">Überspringen</span>
|
||||
<div class="btn-spacer"></div>
|
||||
<div class="btn-outline">Nur speichern</div>
|
||||
<div class="btn-primary">Weiter → Inhalt</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /concept A -->
|
||||
|
||||
|
||||
<!-- ══════════════════════════════════════════
|
||||
CONCEPT B — Flat & Airy
|
||||
══════════════════════════════════════════ -->
|
||||
<div class="concept">
|
||||
<div class="concept-header">
|
||||
<div class="concept-num">B</div>
|
||||
<div>
|
||||
<div class="concept-label">Concept B</div>
|
||||
<div class="concept-title">Flat & Airy</div>
|
||||
<div class="concept-desc">All card borders and section header chrome are removed. Fields flow in a single uninterrupted column with generous spacing. Required fields come first; a thin "Optional" divider separates them from supplementary fields. A 3 px progress bar at the top of the form panel shows how many required fields are filled. The page feels like a focused questionnaire, not a form.</div>
|
||||
<div class="concept-best"><span class="best-label">Best for</span><span class="best-text">Frequent editors who find card groupings visually noisy</span></div>
|
||||
<div class="concept-tradeoff">Trade-off: without section headers, new users may not understand the semantic grouping. Mitigate with subtle field-group spacing (20 px between groups, 10 px within).</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="screen">
|
||||
<div class="chrome">
|
||||
<div class="chrome-bar">
|
||||
<div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-dot"></div>
|
||||
<div class="chrome-url"></div>
|
||||
</div>
|
||||
<div class="app-body">
|
||||
<div class="app-nav">
|
||||
<span class="app-logo">Familienarchiv</span>
|
||||
<span class="app-link">Dokumente</span>
|
||||
<span class="app-link on">Anreicherung</span>
|
||||
<div class="app-nav-r"><div class="app-avatar">MR</div></div>
|
||||
</div>
|
||||
<div class="enrich-bar">
|
||||
<div class="eb-back">← Zur Liste</div>
|
||||
<div class="eb-title">Brief an Tante Hilde, Sommer 1952</div>
|
||||
<div class="eb-progress">12 verbleibend</div>
|
||||
</div>
|
||||
|
||||
<!-- Required-fields progress bar (full-width, directly below top bar) -->
|
||||
<div class="req-bar-wrap">
|
||||
<span class="req-bar-label">Pflichtfelder</span>
|
||||
<div class="req-bar-track"><div class="req-bar-fill" style="width:75%"></div></div>
|
||||
<span class="req-bar-count">3 / 4</span>
|
||||
</div>
|
||||
|
||||
<div class="split" style="height:434px">
|
||||
|
||||
<!-- PDF -->
|
||||
<div class="pdf-panel">
|
||||
<div class="pdf-toolbar">
|
||||
<div class="pdf-btn on">⊕</div>
|
||||
<div class="pdf-btn">⊖</div>
|
||||
<div class="pdf-pg">Seite 1 / 2</div>
|
||||
</div>
|
||||
<div class="pdf-view">
|
||||
<div class="pdf-paper">
|
||||
<div class="pl h"></div>
|
||||
<div class="pl s"></div>
|
||||
<div class="pl sp"></div>
|
||||
<div class="pl m"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl s"></div>
|
||||
<div class="pl sp" style="height:4px"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl m"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl s"></div>
|
||||
<div class="pl sp" style="height:4px"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl m"></div>
|
||||
<div class="pl"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form: flat, no card borders -->
|
||||
<div class="form-panel" style="background:#FEFEFE">
|
||||
<div class="form-scroll" style="padding:16px 16px">
|
||||
|
||||
<!-- Required fields block -->
|
||||
<div style="margin-bottom:20px">
|
||||
<div style="font-size:5.5px;font-weight:800;text-transform:uppercase;letter-spacing:1px;color:#A6DAD8;margin-bottom:9px">Pflichtfelder</div>
|
||||
<!-- Titel -->
|
||||
<div style="margin-bottom:9px">
|
||||
<span class="f-label">Titel <span class="f-req">*</span></span>
|
||||
<div class="f-input focus tall" style="margin-top:2px">Brief an Tante Hilde — München 1952</div>
|
||||
</div>
|
||||
<!-- Datum + Absender -->
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:7px;margin-bottom:9px">
|
||||
<div>
|
||||
<span class="f-label">Datum <span class="f-req">*</span></span>
|
||||
<div class="f-input filled" style="margin-top:2px">15.07.1952</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="f-label">Absender <span class="f-req">*</span></span>
|
||||
<div class="f-input filled" style="margin-top:2px">Raddatz, Karl</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Empfänger -->
|
||||
<div>
|
||||
<span class="f-label">Empfänger <span class="f-req">*</span></span>
|
||||
<div class="f-tags" style="margin-top:2px">
|
||||
<div class="f-chip">Hilde Brandt <span class="f-chip-rm">×</span></div>
|
||||
<div class="f-add">+ Person</div>
|
||||
</div>
|
||||
<div class="f-helper" style="color:#C0392B">Pflichtfeld — mindestens 1 Empfänger</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Optional divider -->
|
||||
<div class="opt-divider">
|
||||
<div class="opt-line"></div>
|
||||
<span class="opt-label">Optional</span>
|
||||
<div class="opt-line"></div>
|
||||
</div>
|
||||
|
||||
<!-- Optional fields block -->
|
||||
<div style="margin-bottom:9px">
|
||||
<span class="f-label">Schlagworte</span>
|
||||
<div class="f-tags" style="margin-top:2px">
|
||||
<div class="f-chip">Familie <span class="f-chip-rm">×</span></div>
|
||||
<div class="f-chip">Krieg <span class="f-chip-rm">×</span></div>
|
||||
<div class="f-add">+ Schlagwort</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:7px;margin-bottom:9px">
|
||||
<div>
|
||||
<span class="f-label">Ort</span>
|
||||
<div class="f-input" style="margin-top:2px">München</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="f-label">Aufbewahrungsort</span>
|
||||
<div class="f-input empty" style="margin-top:2px">Karton, Regal …</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span class="f-label">Kurzinhalt</span>
|
||||
<div class="f-textarea" style="margin-top:2px"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="action-bar">
|
||||
<span class="btn-skip">Überspringen</span>
|
||||
<div class="btn-spacer"></div>
|
||||
<div class="btn-outline">Speichern</div>
|
||||
<div class="btn-primary locked">Speichern & Geprüft</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /concept B -->
|
||||
|
||||
|
||||
<!-- ══════════════════════════════════════════
|
||||
CONCEPT C — PDF-First, Compact Rail
|
||||
══════════════════════════════════════════ -->
|
||||
<div class="concept">
|
||||
<div class="concept-header">
|
||||
<div class="concept-num">C</div>
|
||||
<div>
|
||||
<div class="concept-label">Concept C</div>
|
||||
<div class="concept-title">PDF-First, Compact Rail</div>
|
||||
<div class="concept-desc">The PDF gets 65% of the width. The form becomes a narrow right rail — no card containers, no padding waste, compact input heights (16 px vs 36 px standard). Tab moves between every field. Keyboard shortcuts (⌘↵ = save & next, → = skip) are shown as badges in the action bar. Tags use inline chips with a single "+" shortcut rather than a dropdown overlay. The document is always the primary focus; metadata is glanceable support.</div>
|
||||
<div class="concept-best"><span class="best-label">Best for</span><span class="best-text">Power users processing 30+ documents per session</span></div>
|
||||
<div class="concept-tradeoff">Trade-off: the rail is narrow (~280 px at 1280 px viewport). Long person names and tags may truncate. Design for max 20 chars per name; add tooltip on hover.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="screen">
|
||||
<div class="chrome">
|
||||
<div class="chrome-bar">
|
||||
<div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-dot"></div>
|
||||
<div class="chrome-url"></div>
|
||||
</div>
|
||||
<div class="app-body">
|
||||
<div class="app-nav">
|
||||
<span class="app-logo">Familienarchiv</span>
|
||||
<span class="app-link">Dokumente</span>
|
||||
<span class="app-link on">Anreicherung</span>
|
||||
<div class="app-nav-r"><div class="app-avatar">MR</div></div>
|
||||
</div>
|
||||
<div class="enrich-bar">
|
||||
<div class="eb-back">← Zur Liste</div>
|
||||
<div class="eb-title">Brief an Tante Hilde, Sommer 1952</div>
|
||||
<div class="eb-progress">12 verbleibend</div>
|
||||
</div>
|
||||
<div class="split" style="height:470px">
|
||||
|
||||
<!-- PDF wider (65%) -->
|
||||
<div class="pdf-panel" style="flex:65">
|
||||
<div class="pdf-toolbar">
|
||||
<div class="pdf-btn on">⊕</div>
|
||||
<div class="pdf-btn">⊖</div>
|
||||
<div class="pdf-btn">↺</div>
|
||||
<div class="pdf-pg">Seite 1 / 2</div>
|
||||
</div>
|
||||
<div class="pdf-view" style="padding:20px">
|
||||
<div class="pdf-paper" style="width:240px">
|
||||
<div class="pl h"></div>
|
||||
<div class="pl s"></div>
|
||||
<div class="pl sp"></div>
|
||||
<div class="pl m"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl s"></div>
|
||||
<div class="pl sp" style="height:4px"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl m"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl s"></div>
|
||||
<div class="pl sp" style="height:4px"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl m"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl s"></div>
|
||||
<div class="pl sp" style="height:4px"></div>
|
||||
<div class="pl m"></div>
|
||||
<div class="pl"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Compact form rail (35%) -->
|
||||
<div class="form-panel" style="flex:35;background:#F9F8F5">
|
||||
<div class="form-scroll" style="padding:10px 10px">
|
||||
|
||||
<!-- Inline section label -->
|
||||
<div style="font-size:5.5px;font-weight:800;text-transform:uppercase;letter-spacing:1px;color:#B0ADA6;padding-bottom:5px;border-bottom:1px solid #E4E2D7;margin-bottom:7px">Wer & Wann</div>
|
||||
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:5px;margin-bottom:5px">
|
||||
<div class="f-field">
|
||||
<span class="f-label" style="font-size:5.5px">Datum *</span>
|
||||
<div class="f-input filled" style="height:15px;font-size:6.5px;margin-top:1px">15.07.1952</div>
|
||||
</div>
|
||||
<div class="f-field">
|
||||
<span class="f-label" style="font-size:5.5px">Ort</span>
|
||||
<div class="f-input" style="height:15px;font-size:6.5px;margin-top:1px">München</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-bottom:5px">
|
||||
<span class="f-label" style="font-size:5.5px">Absender *</span>
|
||||
<div class="f-input filled" style="height:15px;font-size:6.5px;margin-top:1px">Raddatz, Karl</div>
|
||||
</div>
|
||||
<div style="margin-bottom:9px">
|
||||
<span class="f-label" style="font-size:5.5px">Empfänger</span>
|
||||
<div class="f-tags" style="min-height:15px;padding:2px 3px;margin-top:1px">
|
||||
<div class="f-chip" style="font-size:5px;padding:1px 3px">Hilde Brandt ×</div>
|
||||
<div class="f-add" style="font-size:5.5px">+</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="font-size:5.5px;font-weight:800;text-transform:uppercase;letter-spacing:1px;color:#B0ADA6;padding-bottom:5px;border-bottom:1px solid #E4E2D7;margin-bottom:7px">Beschreibung</div>
|
||||
|
||||
<div style="margin-bottom:5px">
|
||||
<span class="f-label" style="font-size:5.5px">Titel *</span>
|
||||
<div class="f-input focus" style="height:15px;font-size:6.5px;margin-top:1px">Brief an Tante Hilde</div>
|
||||
</div>
|
||||
<div style="margin-bottom:5px">
|
||||
<span class="f-label" style="font-size:5.5px">Schlagworte</span>
|
||||
<div class="f-tags" style="min-height:15px;padding:2px 3px;margin-top:1px">
|
||||
<div class="f-chip" style="font-size:5px;padding:1px 3px">Familie ×</div>
|
||||
<div class="f-chip" style="font-size:5px;padding:1px 3px">Krieg ×</div>
|
||||
<div class="f-add" style="font-size:5.5px">+</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-bottom:9px">
|
||||
<span class="f-label" style="font-size:5.5px">Kurzinhalt</span>
|
||||
<div class="f-textarea" style="height:28px;font-size:6px;margin-top:1px"></div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Action bar with keyboard hints -->
|
||||
<div class="action-bar" style="height:38px;background:#EDEBE6;padding:0 8px;gap:4px">
|
||||
<div style="display:flex;align-items:center;gap:3px">
|
||||
<div class="kbd">⌘ ↵</div>
|
||||
<span style="font-size:5.5px;color:#AAA">Sp. & weiter</span>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:3px">
|
||||
<div class="kbd">→</div>
|
||||
<span style="font-size:5.5px;color:#AAA">Skip</span>
|
||||
</div>
|
||||
<div class="btn-spacer"></div>
|
||||
<div class="btn-outline" style="height:19px;font-size:5.5px;padding:0 7px">Speichern</div>
|
||||
<div class="btn-primary" style="height:19px;font-size:5.5px;padding:0 7px">Sp. & Geprüft</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /concept C -->
|
||||
|
||||
|
||||
<!-- ══════════════════════════════════════════
|
||||
CONCEPT D — Smart Prefill
|
||||
══════════════════════════════════════════ -->
|
||||
<div class="concept">
|
||||
<div class="concept-header">
|
||||
<div class="concept-num">D</div>
|
||||
<div>
|
||||
<div class="concept-label">Concept D</div>
|
||||
<div class="concept-title">Smart Prefill</div>
|
||||
<div class="concept-desc">When the backend can supply OCR or import data for a field, it appears pre-filled with a mint/teal visual style: mint border, teal text, soft mint background. Pressing Tab accepts the suggestion and converts it to normal text; typing overrides it. A completion ring in the action bar fills from empty to full as required fields are confirmed. The primary button stays locked (gray) until all required fields are accepted or manually filled — then turns green. Turns enrichment from data-entry into a verification task.</div>
|
||||
<div class="concept-best"><span class="best-label">Best for</span><span class="best-text">Documents imported from digitization services with OCR output</span></div>
|
||||
<div class="concept-tradeoff">Trade-off: requires a backend OCR suggestion API. When no suggestions exist, the UX degrades gracefully to standard blank fields — the ring and lock still apply.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="screen">
|
||||
<div class="chrome">
|
||||
<div class="chrome-bar">
|
||||
<div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-dot"></div>
|
||||
<div class="chrome-url"></div>
|
||||
</div>
|
||||
<div class="app-body">
|
||||
<div class="app-nav">
|
||||
<span class="app-logo">Familienarchiv</span>
|
||||
<span class="app-link">Dokumente</span>
|
||||
<span class="app-link on">Anreicherung</span>
|
||||
<div class="app-nav-r"><div class="app-avatar">MR</div></div>
|
||||
</div>
|
||||
<div class="enrich-bar">
|
||||
<div class="eb-back">← Zur Liste</div>
|
||||
<div class="eb-title">Brief an Tante Hilde, Sommer 1952</div>
|
||||
<div class="eb-progress">12 verbleibend</div>
|
||||
</div>
|
||||
<div class="split" style="height:470px">
|
||||
|
||||
<!-- PDF panel -->
|
||||
<div class="pdf-panel">
|
||||
<div class="pdf-toolbar">
|
||||
<div class="pdf-btn on">⊕</div>
|
||||
<div class="pdf-btn">⊖</div>
|
||||
<div class="pdf-pg">Seite 1 / 2</div>
|
||||
</div>
|
||||
<div class="pdf-view">
|
||||
<div class="pdf-paper">
|
||||
<div class="pl h"></div>
|
||||
<div class="pl s"></div>
|
||||
<div class="pl sp"></div>
|
||||
<div class="pl m"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl s"></div>
|
||||
<div class="pl sp" style="height:4px"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl m"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl s"></div>
|
||||
<div class="pl sp" style="height:4px"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl m"></div>
|
||||
<div class="pl"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form panel with smart prefill -->
|
||||
<div class="form-panel">
|
||||
<div class="form-scroll">
|
||||
<!-- OCR banner -->
|
||||
<div class="ocr-banner">
|
||||
<div class="ocr-icon">✓</div>
|
||||
<div class="ocr-text">
|
||||
<div class="ocr-title">OCR-Vorschläge erkannt</div>
|
||||
<div class="ocr-body">4 Felder wurden vorausgefüllt. Drücke <kbd class="ocr-kbd">Tab</kbd> zum Übernehmen oder tippe zum Überschreiben.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Wer & Wann card -->
|
||||
<div class="f-card">
|
||||
<div class="f-card-title">Wer & Wann</div>
|
||||
<div class="f-row">
|
||||
<div class="f-field">
|
||||
<span class="f-label">Datum <span class="f-req">*</span></span>
|
||||
<div class="f-input suggested" style="margin-top:2px">15.07.1952 ✓</div>
|
||||
</div>
|
||||
<div class="f-field">
|
||||
<span class="f-label">Ort</span>
|
||||
<div class="f-input" style="margin-top:2px">München</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="f-row">
|
||||
<div class="f-field">
|
||||
<span class="f-label">Absender <span class="f-req">*</span></span>
|
||||
<div class="f-input suggested" style="margin-top:2px">Raddatz, Karl ✓</div>
|
||||
</div>
|
||||
<div class="f-field">
|
||||
<span class="f-label">Empfänger <span class="f-req">*</span></span>
|
||||
<div class="f-tags" style="margin-top:2px">
|
||||
<div class="f-chip" style="background:#A6DAD8;color:#002850">Hilde Brandt ×</div>
|
||||
<div class="f-add">+</div>
|
||||
</div>
|
||||
<div class="f-helper" style="color:#006B68">✓ Aus OCR übernommen</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Beschreibung card -->
|
||||
<div class="f-card">
|
||||
<div class="f-card-title">Beschreibung</div>
|
||||
<div class="f-row full" style="margin-bottom:7px">
|
||||
<div class="f-field">
|
||||
<span class="f-label">Titel <span class="f-req">*</span></span>
|
||||
<div class="f-input focus tall" style="margin-top:2px">Brief an Tante Hilde</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="f-row full">
|
||||
<div class="f-field">
|
||||
<span class="f-label">Schlagworte</span>
|
||||
<div class="f-tags" style="margin-top:2px">
|
||||
<div class="f-chip">Familie <span class="f-chip-rm">×</span></div>
|
||||
<div class="f-add">+ Schlagwort</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Action bar with completion ring -->
|
||||
<div class="action-bar">
|
||||
<span class="btn-skip">Überspringen</span>
|
||||
<!-- Completion ring -->
|
||||
<div class="ring-wrap">
|
||||
<svg width="20" height="20" viewBox="0 0 20 20">
|
||||
<circle cx="10" cy="10" r="8" fill="none" stroke="#E4E2D7" stroke-width="2.5"/>
|
||||
<circle cx="10" cy="10" r="8" fill="none" stroke="#002850" stroke-width="2.5"
|
||||
stroke-dasharray="40 10" stroke-dashoffset="13" stroke-linecap="round"
|
||||
transform="rotate(-90 10 10)"/>
|
||||
</svg>
|
||||
<span class="ring-count">4 / 5</span>
|
||||
</div>
|
||||
<div class="btn-spacer"></div>
|
||||
<div class="btn-outline">Speichern</div>
|
||||
<div class="btn-primary green">Speichern & Geprüft ✓</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /concept D -->
|
||||
|
||||
|
||||
<!-- ══════════════════════════════════════════
|
||||
IMPLEMENTATION REFERENCE
|
||||
══════════════════════════════════════════ -->
|
||||
<div class="impl-ref">
|
||||
<h2>Implementation Reference</h2>
|
||||
<div class="subtitle">Exact Tailwind classes and pixel values for each new pattern. The shared enrich shell (split pane, top bar, action bar) is unchanged from the current implementation.</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Element</th>
|
||||
<th>Concepts</th>
|
||||
<th>Tailwind / Code</th>
|
||||
<th>Notes</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Section stepper tab bar</td>
|
||||
<td><span class="con">A</span></td>
|
||||
<td><code>flex border-b border-line bg-surface</code></td>
|
||||
<td>Each tab: <code>flex-1 py-2 text-center text-[10px] font-bold uppercase tracking-widest</code>. Active: <code>text-ink border-b-2 border-brand-navy</code>. Done: <code>text-green-700</code>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Completed section summary row</td>
|
||||
<td><span class="con">A</span></td>
|
||||
<td><code>flex items-center justify-between px-3 py-2.5 bg-surface border border-line rounded-sm text-xs</code></td>
|
||||
<td>Label: <code>font-bold uppercase tracking-widest text-green-700</code>. Summary: <code>text-ink-2 truncate max-w-[180px]</code>. Clicking jumps to that section.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>"Weiter →" primary button</td>
|
||||
<td><span class="con">A</span></td>
|
||||
<td><code>btn-primary</code> (existing class)</td>
|
||||
<td>Label changes: "Weiter → Beschreibung" → "Weiter → Inhalt" → "Speichern & Geprüft". Driven by <code>currentSection</code> state.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Required-fields progress bar</td>
|
||||
<td><span class="con">B</span></td>
|
||||
<td><code>h-0.5 rounded-full bg-brand-navy transition-all</code> inside <code>h-0.5 flex-1 rounded-full bg-line</code></td>
|
||||
<td>Width: <code>style="width: {(filledRequired / totalRequired) * 100}%"</code>. Bar lives in its own strip between top bar and split pane.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Optional divider</td>
|
||||
<td><span class="con">B</span></td>
|
||||
<td><code>flex items-center gap-2 my-5</code></td>
|
||||
<td>Lines: <code>flex-1 border-t border-line</code>. Label: <code>text-[9px] font-bold uppercase tracking-widest text-ink-3</code>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Locked primary button</td>
|
||||
<td><span class="con">B</span> <span class="con">D</span></td>
|
||||
<td><code>opacity-40 cursor-not-allowed pointer-events-none</code> on existing <code>btn-primary</code></td>
|
||||
<td>Or: distinct disabled class <code>bg-line text-ink-3</code>. Reactive: <code>const canReview = $derived(requiredFilled === requiredCount)</code>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Compact rail field height</td>
|
||||
<td><span class="con">C</span></td>
|
||||
<td><code>h-6 py-0 px-2 text-xs</code> on inputs</td>
|
||||
<td>Real pixel height: 24 px. Standard is 36 px. Only use compact height inside the 35%-width rail to preserve minimum 44 px touch target via larger click zone around label + input.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Keyboard shortcut badge</td>
|
||||
<td><span class="con">C</span></td>
|
||||
<td><code><kbd class="inline-flex items-center h-4 px-1 bg-surface border border-line rounded text-[10px] font-mono font-bold text-ink-2"></code></td>
|
||||
<td>Use native <code><kbd></code> element for screen-reader semantics. Announce via <code>aria-label</code> on the button too.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>OCR banner</td>
|
||||
<td><span class="con">D</span></td>
|
||||
<td><code>bg-mint/10 border border-brand-mint rounded-sm p-3 flex items-start gap-2 text-xs</code></td>
|
||||
<td>Only rendered when <code>data.ocrSuggestions</code> is non-empty. Icon: <code>w-4 h-4 rounded-full bg-brand-mint flex items-center justify-center text-brand-navy font-bold text-[10px]</code>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Suggested / prefill field</td>
|
||||
<td><span class="con">D</span></td>
|
||||
<td><code>border-brand-mint bg-mint/10 text-teal-700 font-medium</code></td>
|
||||
<td>Add <code>data-suggested="true"</code> attribute. On Tab: remove mint styles, set value. On input: clear suggestion immediately. Server populates <code>suggestedDateIso</code>, <code>suggestedSenderName</code> props (already exist on <code>WhoWhenSection</code>).</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Completion ring SVG</td>
|
||||
<td><span class="con">D</span></td>
|
||||
<td>SVG with two <code><circle></code> elements</td>
|
||||
<td>Circumference at r=8: ≈50.3 px. Filled arc: <code>stroke-dasharray="{(filled/total)*50.3} {50.3}"</code>. Rotate −90° so arc starts at top. Color: <code>stroke="currentColor" text-brand-navy</code>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Unlocked "Geprüft" button</td>
|
||||
<td><span class="con">D</span></td>
|
||||
<td><code>bg-green-700 hover:bg-green-800 text-white</code></td>
|
||||
<td>State: <code>const canReview = $derived(confirmedFields.size >= requiredFields.length)</code>. Animate transition: <code>transition-colors duration-200</code>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Action bar (shared)</td>
|
||||
<td><span class="con">A B C D</span></td>
|
||||
<td><code>flex items-center gap-3 border-t border-line bg-surface px-4 min-h-[44px]</code></td>
|
||||
<td>Skip: ghost left (<code>text-ink-2 text-sm font-medium</code>). Save: outline right. Primary: filled rightmost. Min 44 px height per WCAG 2.2 touch target.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div><!-- /doc -->
|
||||
</body>
|
||||
</html>
|
||||
730
docs/specs/enrich-edit-unified.html
Normal file
730
docs/specs/enrich-edit-unified.html
Normal file
@@ -0,0 +1,730 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Enrich / Edit — Unified Design · Familienarchiv</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Merriweather:ital,wght@0,300;0,400;0,700;1,300&family=Montserrat:wght@400;500;600;700;800;900&display=swap" rel="stylesheet"/>
|
||||
<style>
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
body{font-family:'Montserrat',system-ui,sans-serif;background:#ECEAE4;color:#1A1A1A;line-height:1.5;font-size:13px}
|
||||
.doc{max-width:1300px;margin:0 auto;padding:48px 32px 120px}
|
||||
|
||||
/* ── Masthead ── */
|
||||
.mh{padding-bottom:24px;border-bottom:3px solid #002850;margin-bottom:60px}
|
||||
.mh h1{font-size:23px;font-weight:900;color:#002850;letter-spacing:-.4px}
|
||||
.mh p{font-size:13px;color:#555;max-width:740px;line-height:1.75;margin-top:8px}
|
||||
.mh .byline{font-size:9px;color:#999;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;margin-top:10px}
|
||||
.tag-row{display:flex;gap:6px;margin-top:10px;flex-wrap:wrap}
|
||||
.tag{background:#002850;color:#A6DAD8;padding:2px 8px;border-radius:2px;font-size:8px;font-weight:700;letter-spacing:.8px;text-transform:uppercase}
|
||||
.tag.amber{background:#7c4a00;color:#fde68a}
|
||||
.tag.green{background:#1e5e34;color:#d1fae5}
|
||||
|
||||
/* ── Section headers ── */
|
||||
.sh{margin:0 0 28px}
|
||||
.sh h2{font-size:16px;font-weight:900;color:#002850;letter-spacing:-.2px}
|
||||
.sh p{font-size:12.5px;color:#666;max-width:720px;line-height:1.7;margin-top:5px}
|
||||
.section{margin-bottom:80px;padding-bottom:80px;border-bottom:2px dashed #C8C4BE}
|
||||
.section:last-of-type{border-bottom:none;margin-bottom:0;padding-bottom:0}
|
||||
|
||||
/* ── Field order comparison ── */
|
||||
.compare{display:grid;grid-template-columns:1fr 40px 1fr;gap:0;align-items:start;margin-bottom:16px}
|
||||
.compare-col{background:#F5F4EE;border:1.5px solid #E4E2D7;border-radius:4px;overflow:hidden}
|
||||
.compare-head{padding:8px 14px;font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:1px;border-bottom:1.5px solid #E4E2D7}
|
||||
.compare-head.before{color:#888;background:#F0EEE9}
|
||||
.compare-head.after{color:#002850;background:#EAF2FF}
|
||||
.compare-arrow{display:flex;align-items:center;justify-content:center;font-size:20px;color:#B0ADA6;padding-top:40px}
|
||||
.field-row{display:flex;align-items:center;gap:8px;padding:6px 14px;border-bottom:1px solid #E4E2D7;font-size:11px}
|
||||
.field-row:last-child{border-bottom:none}
|
||||
.field-name{flex:1;font-weight:600;color:#333}
|
||||
.field-badge{font-size:7.5px;font-weight:800;padding:1px 6px;border-radius:10px;white-space:nowrap}
|
||||
.badge-req{background:#FEE2E2;color:#991B1B}
|
||||
.badge-opt{background:#F3F4F6;color:#555}
|
||||
.badge-moved{background:#DBEAFE;color:#1E40AF}
|
||||
.field-note{font-size:10px;color:#999;font-style:italic}
|
||||
.compare-cap{font-size:10.5px;color:#777;line-height:1.65;font-style:italic;max-width:460px;margin-top:8px}
|
||||
|
||||
/* ── Progress bar annotation ── */
|
||||
.annot-strip{background:#F0EEE9;border:1.5px solid #E4E2D7;border-radius:4px;padding:12px 16px;margin-bottom:24px;display:flex;align-items:flex-start;gap:14px}
|
||||
.annot-num{width:22px;height:22px;border-radius:50%;background:#002850;color:#fff;font-size:9px;font-weight:900;display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-top:1px}
|
||||
.annot-body{}
|
||||
.annot-title{font-size:11.5px;font-weight:800;color:#002850;margin-bottom:3px}
|
||||
.annot-desc{font-size:11px;color:#555;line-height:1.65;max-width:680px}
|
||||
|
||||
/* ── Browser chrome ── */
|
||||
.screen{max-width:940px;margin:0 auto}
|
||||
.chrome{background:#F5F4EE;border:1.5px solid #C4C0BA;border-radius:8px;overflow:hidden;box-shadow:0 4px 20px rgba(0,0,0,.1)}
|
||||
.chrome-bar{height:20px;background:#E8E6E0;border-bottom:1px solid #C4C0BA;display:flex;align-items:center;padding:0 8px;gap:4px;flex-shrink:0}
|
||||
.chrome-dot{width:6px;height:6px;border-radius:50%;background:#BDB8B1}
|
||||
.chrome-url{flex:1;height:9px;background:#CCC8C2;border-radius:5px;margin-left:6px}
|
||||
|
||||
/* ── App nav ── */
|
||||
.app-nav{height:30px;background:#002850;display:flex;align-items:center;padding:0 12px;gap:10px;flex-shrink:0}
|
||||
.app-logo{font-family:'Merriweather',Georgia,serif;font-size:7px;font-weight:700;color:#fff;border-bottom:2px solid #A6DAD8;padding-bottom:1px}
|
||||
.app-link{font-size:5.5px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:rgba(255,255,255,.45);white-space:nowrap}
|
||||
.app-link.on{color:rgba(255,255,255,.9)}
|
||||
.app-nav-r{margin-left:auto;display:flex;gap:6px;align-items:center}
|
||||
.app-avatar{width:16px;height:16px;border-radius:50%;background:rgba(255,255,255,.12);display:flex;align-items:center;justify-content:center;font-size:5px;font-weight:800;color:rgba(255,255,255,.5)}
|
||||
|
||||
/* ── Enrich top bar ── */
|
||||
.enrich-bar{height:36px;background:#F5F4EE;border-bottom:1px solid #E4E2D7;display:flex;align-items:center;padding:0 14px;gap:10px;flex-shrink:0}
|
||||
.eb-back{font-size:6px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#888;display:flex;align-items:center;gap:4px;white-space:nowrap}
|
||||
.eb-title{flex:1;text-align:center;font-family:'Merriweather',Georgia,serif;font-size:8px;color:#002850;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;padding:0 10px}
|
||||
.eb-progress{font-size:6px;color:#AAA;white-space:nowrap;font-weight:600}
|
||||
|
||||
/* ── Required fields bar ── */
|
||||
.req-strip{background:#F0EEE9;border-bottom:1px solid #E4E2D7;padding:5px 14px;display:flex;align-items:center;gap:8px;flex-shrink:0}
|
||||
.req-label{font-size:5.5px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#B0ADA6;white-space:nowrap}
|
||||
.req-track{flex:1;height:3px;background:#E0DDD6;border-radius:2px;overflow:hidden}
|
||||
.req-fill{height:100%;background:#002850;border-radius:2px}
|
||||
.req-count{font-size:6px;font-weight:800;color:#002850;white-space:nowrap}
|
||||
|
||||
/* ── Split ── */
|
||||
.app-body{display:flex;flex-direction:column;overflow:hidden}
|
||||
.split{display:flex;overflow:hidden}
|
||||
|
||||
/* ── PDF panel — with file ── */
|
||||
.pdf-panel{background:#5E5C59;display:flex;flex-direction:column;overflow:hidden;border-right:1px solid #3A3836;flex:60}
|
||||
.pdf-toolbar{height:26px;background:#3A3836;display:flex;align-items:center;padding:0 8px;gap:5px;flex-shrink:0}
|
||||
.pdf-btn{width:14px;height:14px;border-radius:2px;background:rgba(255,255,255,.1);display:flex;align-items:center;justify-content:center;font-size:7px;color:rgba(255,255,255,.5)}
|
||||
.pdf-btn.on{background:rgba(255,255,255,.18)}
|
||||
.pdf-pg{font-size:6px;color:rgba(255,255,255,.4);margin:0 auto;font-weight:700;letter-spacing:.5px}
|
||||
.pdf-view{flex:1;display:flex;justify-content:center;padding:16px;overflow-y:auto}
|
||||
.pdf-paper{background:#FFFEF8;box-shadow:0 2px 10px rgba(0,0,0,.3);border-radius:1px;padding:14px 16px;display:flex;flex-direction:column;gap:0;width:190px;flex-shrink:0}
|
||||
.pl{height:4px;background:#C4BDB0;border-radius:1px;opacity:.55;margin-bottom:3px}
|
||||
.pl.h{height:5.5px;opacity:.75;margin-bottom:4px}
|
||||
.pl.s{width:55%;opacity:.3}
|
||||
.pl.m{width:80%}
|
||||
.pl.sp{height:0;background:transparent}
|
||||
|
||||
/* ── PDF panel — no file ── */
|
||||
.pdf-empty{background:#4A4846;display:flex;flex-direction:column;overflow:hidden;border-right:1px solid #3A3836;flex:60}
|
||||
.pdf-empty-body{flex:1;display:flex;align-items:center;justify-content:center;padding:16px}
|
||||
.upload-zone{border:1.5px dashed rgba(255,255,255,.2);border-radius:4px;padding:22px 20px;display:flex;flex-direction:column;align-items:center;text-align:center;gap:8px;width:180px}
|
||||
.upload-icon{width:28px;height:28px;border-radius:50%;background:rgba(255,255,255,.08);display:flex;align-items:center;justify-content:center;font-size:13px;color:rgba(255,255,255,.35)}
|
||||
.upload-filename{font-size:7px;font-weight:700;color:rgba(255,255,255,.5);letter-spacing:.2px;max-width:140px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.upload-sub{font-size:6px;color:rgba(255,255,255,.3);line-height:1.5}
|
||||
.upload-btn{margin-top:4px;background:#002850;color:rgba(255,255,255,.9);font-size:6px;font-weight:800;text-transform:uppercase;letter-spacing:.6px;padding:4px 10px;border-radius:2px;white-space:nowrap}
|
||||
.upload-drag{font-size:5.5px;color:rgba(255,255,255,.22);margin-top:2px}
|
||||
|
||||
/* ── Form panel ── */
|
||||
.form-panel{display:flex;flex-direction:column;overflow:hidden;background:#fff;flex:40}
|
||||
.form-scroll{flex:1;overflow-y:auto;padding:14px}
|
||||
|
||||
/* ── Form elements ── */
|
||||
.f-card{background:#F9F8F5;border:1px solid #E4E2D7;border-radius:3px;padding:11px 12px;margin-bottom:10px}
|
||||
.f-card:last-child{margin-bottom:0}
|
||||
.f-card-title{font-size:6px;font-weight:800;text-transform:uppercase;letter-spacing:1px;color:#B0ADA6;margin-bottom:9px}
|
||||
.f-row{display:grid;grid-template-columns:1fr 1fr;gap:7px;margin-bottom:7px}
|
||||
.f-row.full{grid-template-columns:1fr}
|
||||
.f-row:last-child{margin-bottom:0}
|
||||
.f-field{display:flex;flex-direction:column;gap:2px}
|
||||
.f-label{font-size:6px;font-weight:700;color:#666}
|
||||
.f-req{color:#C0392B;margin-left:1px}
|
||||
.f-input{height:18px;border:1px solid #D4D0CA;border-radius:2px;background:#fff;font-size:7px;padding:0 6px;color:#333;display:flex;align-items:center}
|
||||
.f-input.focus{border-color:#002850;box-shadow:0 0 0 2px rgba(0,40,80,.12)}
|
||||
.f-input.filled{color:#002850;font-weight:600;background:#FAFBFF}
|
||||
.f-input.empty{color:#CCC;font-style:italic}
|
||||
.f-input.tall{height:22px}
|
||||
.f-textarea{height:38px;border:1px solid #D4D0CA;border-radius:2px;background:#fff;font-size:7px;padding:4px 6px;color:#555;display:flex;align-items:flex-start}
|
||||
.f-tags{display:flex;gap:3px;flex-wrap:wrap;min-height:18px;border:1px solid #D4D0CA;border-radius:2px;padding:2px 4px;background:#fff;align-items:center}
|
||||
.f-chip{background:#002850;color:#A6DAD8;border-radius:2px;font-size:5.5px;font-weight:700;padding:1px 4px 1px 5px;display:flex;align-items:center;gap:2px}
|
||||
.f-chip-rm{color:rgba(166,218,216,.5)}
|
||||
.f-add{font-size:6px;color:#B0ADA6;cursor:pointer}
|
||||
|
||||
/* ── Optional divider inside card ── */
|
||||
.card-opt-divider{display:flex;align-items:center;gap:6px;margin:8px 0 7px}
|
||||
.card-opt-line{flex:1;height:1px;background:#E4E2D7}
|
||||
.card-opt-label{font-size:5.5px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#C8C4BE}
|
||||
|
||||
/* ── Action bar ── */
|
||||
.action-bar{height:44px;background:#F5F4EE;border-top:1px solid #E4E2D7;display:flex;align-items:center;padding:0 12px;gap:6px;flex-shrink:0}
|
||||
.btn-skip{font-size:6.5px;font-weight:700;color:#AAA}
|
||||
.btn-spacer{flex:1}
|
||||
.btn-outline{height:22px;padding:0 10px;border:1px solid #C0BDB6;border-radius:2px;font-size:6px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;color:#777;display:flex;align-items:center}
|
||||
.btn-primary{height:22px;padding:0 10px;border-radius:2px;font-size:6px;font-weight:800;text-transform:uppercase;letter-spacing:.5px;background:#002850;color:#fff;display:flex;align-items:center}
|
||||
|
||||
/* ── Callout annotations ── */
|
||||
.callouts{display:flex;flex-direction:column;gap:8px;margin-top:20px}
|
||||
.callout{display:flex;align-items:flex-start;gap:10px;background:#fff;border:1px solid #E4E2D7;border-radius:3px;padding:9px 12px}
|
||||
.callout-num{width:18px;height:18px;border-radius:50%;background:#002850;color:#A6DAD8;font-size:7.5px;font-weight:900;display:flex;align-items:center;justify-content:center;flex-shrink:0}
|
||||
.callout-text{font-size:11px;color:#444;line-height:1.6}
|
||||
.callout-text strong{color:#002850;font-weight:700}
|
||||
|
||||
/* ── Two-state comparison header ── */
|
||||
.state-label{font-size:9px;font-weight:800;text-transform:uppercase;letter-spacing:1px;color:#888;margin-bottom:10px;display:flex;align-items:center;gap:6px}
|
||||
.state-label::after{content:'';flex:1;height:1px;background:#D4D0CA}
|
||||
.state-pair{display:grid;grid-template-columns:1fr 1fr;gap:24px;align-items:start}
|
||||
.state-pair .screen{max-width:none}
|
||||
|
||||
/* ── Impl-ref table ── */
|
||||
.impl-ref{margin-top:80px;padding-top:48px;border-top:3px solid #002850}
|
||||
.impl-ref h2{font-size:17px;font-weight:900;color:#002850;margin-bottom:5px}
|
||||
.impl-ref .subtitle{font-size:12px;color:#666;margin-bottom:24px;line-height:1.6;max-width:720px}
|
||||
.impl-ref table{width:100%;border-collapse:collapse;font-size:11.5px}
|
||||
.impl-ref thead th{text-align:left;padding:7px 12px;background:#002850;color:#A6DAD8;font-size:8.5px;font-weight:800;text-transform:uppercase;letter-spacing:.8px}
|
||||
.impl-ref tbody td{padding:8px 12px;border-bottom:1px solid #E4E2D7;vertical-align:top;line-height:1.65;color:#333}
|
||||
.impl-ref tbody tr:nth-child(even) td{background:#F5F4EE}
|
||||
.impl-ref code{background:#E4E2D7;padding:1px 5px;border-radius:2px;font-family:monospace;font-size:10.5px;color:#1A1A1A}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="doc">
|
||||
|
||||
<!-- ══════════════════════════════════════
|
||||
MASTHEAD
|
||||
══════════════════════════════════════ -->
|
||||
<div class="mh">
|
||||
<h1>Enrich / Edit — Unified Design Spec</h1>
|
||||
<p>Keeps the current card-based layout. Applies three targeted changes to reduce friction: a required-fields progress bar above the form, required fields moved to the first row within each card, and a proper no-PDF upload state in the left panel.</p>
|
||||
<div class="byline">Leonie Voss · UI/UX · 2026-04-17 · Familienarchiv</div>
|
||||
<div class="tag-row">
|
||||
<span class="tag">enrich</span>
|
||||
<span class="tag">edit-document</span>
|
||||
<span class="tag green">unified spec</span>
|
||||
<span class="tag amber">field reordering</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ══════════════════════════════════════
|
||||
SECTION 1 — FIELD REORDERING
|
||||
══════════════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="sh">
|
||||
<h2>1 — Field Priority Within Cards</h2>
|
||||
<p>Both cards keep their existing structure. Fields are reordered so a user can fill the essentials top-to-bottom without skipping around. Optional fields still live in the same card — they're just pushed below a thin divider.</p>
|
||||
</div>
|
||||
|
||||
<!-- Annotations -->
|
||||
<div style="margin-bottom:28px">
|
||||
<div class="annot-strip">
|
||||
<div class="annot-num">1</div>
|
||||
<div class="annot-body">
|
||||
<div class="annot-title">WhoWhenSection — swap rows</div>
|
||||
<div class="annot-desc">Currently the grid is: Date | Location (row 1) → Sender | Receivers (row 2). Required fields are split across both rows. New order: Date | Sender (row 1, both required) → Receivers | Location (row 2, both optional). No code changes to the grid — just reorder the four <code><div></code> elements inside the existing <code>grid-cols-2</code>.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="annot-strip">
|
||||
<div class="annot-num">2</div>
|
||||
<div class="annot-body">
|
||||
<div class="annot-title">DescriptionSection — promote Title, push Archive Location last</div>
|
||||
<div class="annot-desc">Title is the most important field but currently sits below "Aufbewahrungsort" in the component's source order. New order: Title (full row, first) → Tags → Summary → Aufbewahrungsort (last, below an "Optional" divider). The divider signals that everything below it is supplementary — no need to fill it during enrichment.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="annot-strip">
|
||||
<div class="annot-num">3</div>
|
||||
<div class="annot-body">
|
||||
<div class="annot-title">Required-fields progress bar — above the form scroll</div>
|
||||
<div class="annot-desc">A 3 px strip between the top bar and the form panel's scroll area. Tracks how many required fields (Title, Date, Sender) are non-empty. Green fill grows left to right. Count badge (e.g. "2 / 3") to the right. Zero JS overhead — computed as a <code>$derived</code> value from existing bindable state.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Before / After comparison -->
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:32px;align-items:start">
|
||||
|
||||
<!-- WhoWhenSection -->
|
||||
<div>
|
||||
<div style="font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:1px;color:#888;margin-bottom:10px">WhoWhenSection</div>
|
||||
<div class="compare">
|
||||
<div class="compare-col">
|
||||
<div class="compare-head before">Jetzt</div>
|
||||
<div class="field-row"><span class="field-name">Datum</span><span class="field-badge badge-req">Pflicht</span></div>
|
||||
<div class="field-row"><span class="field-name">Ort</span><span class="field-badge badge-opt">Optional</span></div>
|
||||
<div class="field-row"><span class="field-name">Absender</span><span class="field-badge badge-req">Pflicht</span></div>
|
||||
<div class="field-row"><span class="field-name">Empfänger</span><span class="field-badge badge-opt">Optional</span></div>
|
||||
</div>
|
||||
<div class="compare-arrow">→</div>
|
||||
<div class="compare-col">
|
||||
<div class="compare-head after">Neu</div>
|
||||
<div class="field-row"><span class="field-name">Datum</span><span class="field-badge badge-req">Pflicht</span></div>
|
||||
<div class="field-row"><span class="field-name">Absender</span><span class="field-badge badge-moved">↑ hochgezogen</span></div>
|
||||
<div class="field-row"><span class="field-name">Empfänger</span><span class="field-badge badge-opt">Optional</span></div>
|
||||
<div class="field-row"><span class="field-name">Ort</span><span class="field-badge badge-opt">Optional</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="compare-cap">Row 1 is now entirely required. A user who fills Date + Sender and moves on has completed all mandatory who/when data.</p>
|
||||
</div>
|
||||
|
||||
<!-- DescriptionSection -->
|
||||
<div>
|
||||
<div style="font-size:10px;font-weight:800;text-transform:uppercase;letter-spacing:1px;color:#888;margin-bottom:10px">DescriptionSection</div>
|
||||
<div class="compare">
|
||||
<div class="compare-col">
|
||||
<div class="compare-head before">Jetzt</div>
|
||||
<div class="field-row"><span class="field-name">Titel</span><span class="field-badge badge-req">Pflicht</span></div>
|
||||
<div class="field-row"><span class="field-name">Aufbewahrungsort</span><span class="field-badge badge-opt">Optional</span></div>
|
||||
<div class="field-row"><span class="field-name">Schlagworte</span><span class="field-badge badge-opt">Optional</span></div>
|
||||
<div class="field-row"><span class="field-name">Kurzinhalt</span><span class="field-badge badge-opt">Optional</span></div>
|
||||
</div>
|
||||
<div class="compare-arrow">→</div>
|
||||
<div class="compare-col">
|
||||
<div class="compare-head after">Neu</div>
|
||||
<div class="field-row"><span class="field-name">Titel</span><span class="field-badge badge-req">Pflicht</span></div>
|
||||
<div class="field-row"><span class="field-name">Schlagworte</span><span class="field-badge badge-moved">↑ hochgezogen</span></div>
|
||||
<div class="field-row"><span class="field-name">Kurzinhalt</span><span class="field-badge badge-opt">Optional</span></div>
|
||||
<div class="field-row" style="border-top:1.5px dashed #E0DDD6"><span class="field-name" style="color:#AAA">Aufbewahrungsort</span><span class="field-badge badge-opt">Optional</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="compare-cap">Title stays first. Tags move up to row 2 (more useful than archive location during enrichment). Archive Location drops below the "Optional" divider — it's an administrative field, not enrichment work.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div><!-- /section 1 -->
|
||||
|
||||
|
||||
<!-- ══════════════════════════════════════
|
||||
SECTION 2 — FULL PAGE (WITH PDF)
|
||||
══════════════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="sh">
|
||||
<h2>2 — Full Page with PDF</h2>
|
||||
<p>The familiar split-pane layout with the reorganized form. Required-fields bar shows 2 of 3 filled; the one missing required field (Sender) is visible immediately at the top of the first card.</p>
|
||||
</div>
|
||||
|
||||
<div class="screen">
|
||||
<div class="chrome">
|
||||
<div class="chrome-bar">
|
||||
<div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-dot"></div>
|
||||
<div class="chrome-url"></div>
|
||||
</div>
|
||||
<div class="app-body">
|
||||
<!-- App nav -->
|
||||
<div class="app-nav">
|
||||
<span class="app-logo">Familienarchiv</span>
|
||||
<span class="app-link">Dokumente</span>
|
||||
<span class="app-link">Personen</span>
|
||||
<span class="app-link on">Anreicherung</span>
|
||||
<div class="app-nav-r"><div class="app-avatar">MR</div></div>
|
||||
</div>
|
||||
<!-- Enrich top bar -->
|
||||
<div class="enrich-bar">
|
||||
<div class="eb-back">← Zur Liste</div>
|
||||
<div class="eb-title">Brief an Tante Hilde, Sommer 1952</div>
|
||||
<div class="eb-progress">12 verbleibend</div>
|
||||
</div>
|
||||
<!-- Split -->
|
||||
<div class="split" style="height:490px">
|
||||
|
||||
<!-- PDF panel -->
|
||||
<div class="pdf-panel">
|
||||
<div class="pdf-toolbar">
|
||||
<div class="pdf-btn on">⊕</div>
|
||||
<div class="pdf-btn">⊖</div>
|
||||
<div class="pdf-btn">↺</div>
|
||||
<div class="pdf-pg">Seite 1 / 3</div>
|
||||
<div class="pdf-btn">☷</div>
|
||||
</div>
|
||||
<div class="pdf-view">
|
||||
<div class="pdf-paper">
|
||||
<div class="pl h"></div>
|
||||
<div class="pl s"></div>
|
||||
<div class="pl sp" style="margin-bottom:6px"></div>
|
||||
<div class="pl m"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl s"></div>
|
||||
<div class="pl sp" style="margin-bottom:4px"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl m"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl s"></div>
|
||||
<div class="pl sp" style="margin-bottom:4px"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl m"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl s"></div>
|
||||
<div class="pl sp" style="margin-bottom:4px"></div>
|
||||
<div class="pl m"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl s"></div>
|
||||
<div class="pl sp" style="margin-bottom:4px"></div>
|
||||
<div class="pl"></div>
|
||||
<div class="pl m"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form panel -->
|
||||
<div class="form-panel">
|
||||
<!-- Required-fields bar -->
|
||||
<div class="req-strip">
|
||||
<span class="req-label">Pflichtfelder</span>
|
||||
<div class="req-track"><div class="req-fill" style="width:67%"></div></div>
|
||||
<span class="req-count">2 / 3</span>
|
||||
</div>
|
||||
|
||||
<div class="form-scroll">
|
||||
|
||||
<!-- Card 1: Wer & Wann — new field order -->
|
||||
<div class="f-card">
|
||||
<div class="f-card-title">Wer & Wann</div>
|
||||
<!-- Row 1: required fields -->
|
||||
<div class="f-row">
|
||||
<div class="f-field">
|
||||
<span class="f-label">Datum <span class="f-req">*</span></span>
|
||||
<div class="f-input filled">15.07.1952</div>
|
||||
</div>
|
||||
<div class="f-field">
|
||||
<span class="f-label">Absender <span class="f-req">*</span></span>
|
||||
<div class="f-input focus empty">Person suchen …</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Row 2: optional fields -->
|
||||
<div class="f-row" style="margin-bottom:0">
|
||||
<div class="f-field">
|
||||
<span class="f-label">Empfänger</span>
|
||||
<div class="f-tags">
|
||||
<div class="f-chip">Hilde Brandt <span class="f-chip-rm">×</span></div>
|
||||
<div class="f-add">+</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="f-field">
|
||||
<span class="f-label">Ort</span>
|
||||
<div class="f-input">München</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Card 2: Beschreibung — new field order -->
|
||||
<div class="f-card">
|
||||
<div class="f-card-title">Beschreibung</div>
|
||||
<!-- Title: full row, first -->
|
||||
<div class="f-row full">
|
||||
<div class="f-field">
|
||||
<span class="f-label">Titel <span class="f-req">*</span></span>
|
||||
<div class="f-input filled tall">Brief an Tante Hilde — München 1952</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Tags: second -->
|
||||
<div class="f-row full">
|
||||
<div class="f-field">
|
||||
<span class="f-label">Schlagworte</span>
|
||||
<div class="f-tags">
|
||||
<div class="f-chip">Familie <span class="f-chip-rm">×</span></div>
|
||||
<div class="f-chip">Krieg <span class="f-chip-rm">×</span></div>
|
||||
<div class="f-add">+ Schlagwort</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Summary -->
|
||||
<div class="f-row full">
|
||||
<div class="f-field">
|
||||
<span class="f-label">Kurzinhalt</span>
|
||||
<div class="f-textarea"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Optional divider -->
|
||||
<div class="card-opt-divider">
|
||||
<div class="card-opt-line"></div>
|
||||
<span class="card-opt-label">Optional</span>
|
||||
<div class="card-opt-line"></div>
|
||||
</div>
|
||||
<!-- Archive location — last -->
|
||||
<div class="f-row full" style="margin-bottom:0">
|
||||
<div class="f-field">
|
||||
<span class="f-label">Aufbewahrungsort</span>
|
||||
<div class="f-input empty">z. B. Karton 3, Regal B</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /form-scroll -->
|
||||
|
||||
<!-- Action bar -->
|
||||
<div class="action-bar">
|
||||
<span class="btn-skip">Überspringen</span>
|
||||
<div class="btn-spacer"></div>
|
||||
<div class="btn-outline">Speichern</div>
|
||||
<div class="btn-primary">Speichern & Geprüft</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /split -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Callouts -->
|
||||
<div class="callouts">
|
||||
<div class="callout">
|
||||
<div class="callout-num">1</div>
|
||||
<div class="callout-text"><strong>Required bar at 2/3</strong> — Datum and Titel are filled; Absender is empty. The bar gives an at-a-glance completion signal without adding visual weight to individual fields.</div>
|
||||
</div>
|
||||
<div class="callout">
|
||||
<div class="callout-num">2</div>
|
||||
<div class="callout-text"><strong>Absender focused</strong> — it's the only required field still empty, so it receives focus on load via <code>autofocus</code> or a Svelte <code>$effect</code> that targets the first empty required input. No user action needed to know where to start.</div>
|
||||
</div>
|
||||
<div class="callout">
|
||||
<div class="callout-num">3</div>
|
||||
<div class="callout-text"><strong>Aufbewahrungsort below the "Optional" divider</strong> — visually de-prioritized without being removed. Editors doing bulk enrichment can ignore it entirely; archivists who need it will still find it.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /section 2 -->
|
||||
|
||||
|
||||
<!-- ══════════════════════════════════════
|
||||
SECTION 3 — NO-PDF STATE
|
||||
══════════════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="sh">
|
||||
<h2>3 — No-PDF State</h2>
|
||||
<p>When a document has no file yet (status <code>PLACEHOLDER</code> from Excel import), the left panel shows the same dark background but with a centered upload zone. The original filename is shown if known. Drag-and-drop is supported; clicking the CTA opens the file picker. Once a file is selected it uploads and the panel transitions to the normal PDF viewer.</p>
|
||||
</div>
|
||||
|
||||
<!-- Two states side by side at reduced width -->
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:28px;align-items:start;max-width:940px;margin:0 auto">
|
||||
|
||||
<!-- State A: No file -->
|
||||
<div>
|
||||
<div class="state-label">Kein Dokument hochgeladen</div>
|
||||
<div class="chrome">
|
||||
<div class="chrome-bar">
|
||||
<div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-dot"></div>
|
||||
<div class="chrome-url"></div>
|
||||
</div>
|
||||
<div class="app-body">
|
||||
<div class="app-nav">
|
||||
<span class="app-logo">Familienarchiv</span>
|
||||
<span class="app-link on">Anreicherung</span>
|
||||
<div class="app-nav-r"><div class="app-avatar">MR</div></div>
|
||||
</div>
|
||||
<div class="enrich-bar">
|
||||
<div class="eb-back">← Zur Liste</div>
|
||||
<div class="eb-title">scan-2024-03-15-brief.pdf</div>
|
||||
<div class="eb-progress">12 verbleibend</div>
|
||||
</div>
|
||||
<div class="split" style="height:300px">
|
||||
|
||||
<!-- No-file left panel -->
|
||||
<div class="pdf-empty">
|
||||
<div class="pdf-empty-body">
|
||||
<div class="upload-zone">
|
||||
<div class="upload-icon">↑</div>
|
||||
<div class="upload-filename">scan-2024-03-15-brief.pdf</div>
|
||||
<div class="upload-sub">Noch keine Datei hochgeladen</div>
|
||||
<div class="upload-btn">Datei auswählen</div>
|
||||
<div class="upload-drag">oder Datei hier ablegen</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form panel (abbreviated) -->
|
||||
<div class="form-panel">
|
||||
<div class="req-strip">
|
||||
<span class="req-label">Pflichtfelder</span>
|
||||
<div class="req-track"><div class="req-fill" style="width:0%"></div></div>
|
||||
<span class="req-count">0 / 3</span>
|
||||
</div>
|
||||
<div class="form-scroll">
|
||||
<div class="f-card">
|
||||
<div class="f-card-title">Wer & Wann</div>
|
||||
<div class="f-row">
|
||||
<div class="f-field">
|
||||
<span class="f-label">Datum <span class="f-req">*</span></span>
|
||||
<div class="f-input focus empty">TT.MM.JJJJ</div>
|
||||
</div>
|
||||
<div class="f-field">
|
||||
<span class="f-label">Absender <span class="f-req">*</span></span>
|
||||
<div class="f-input empty">Person suchen …</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-bar">
|
||||
<span class="btn-skip">Überspringen</span>
|
||||
<div class="btn-spacer"></div>
|
||||
<div class="btn-outline">Speichern</div>
|
||||
<div class="btn-primary">Speichern & Geprüft</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /state A -->
|
||||
|
||||
<!-- State B: File uploading -->
|
||||
<div>
|
||||
<div class="state-label">Datei wird hochgeladen</div>
|
||||
<div class="chrome">
|
||||
<div class="chrome-bar">
|
||||
<div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-dot"></div>
|
||||
<div class="chrome-url"></div>
|
||||
</div>
|
||||
<div class="app-body">
|
||||
<div class="app-nav">
|
||||
<span class="app-logo">Familienarchiv</span>
|
||||
<span class="app-link on">Anreicherung</span>
|
||||
<div class="app-nav-r"><div class="app-avatar">MR</div></div>
|
||||
</div>
|
||||
<div class="enrich-bar">
|
||||
<div class="eb-back">← Zur Liste</div>
|
||||
<div class="eb-title">scan-2024-03-15-brief.pdf</div>
|
||||
<div class="eb-progress">12 verbleibend</div>
|
||||
</div>
|
||||
<div class="split" style="height:300px">
|
||||
|
||||
<!-- Uploading state — dark panel, progress indicator -->
|
||||
<div class="pdf-empty">
|
||||
<div class="pdf-empty-body">
|
||||
<div class="upload-zone" style="border-color:rgba(166,218,216,.4);background:rgba(166,218,216,.05)">
|
||||
<!-- Indeterminate progress bar -->
|
||||
<div style="width:100%;height:2px;background:rgba(255,255,255,.1);border-radius:2px;overflow:hidden;position:relative">
|
||||
<div style="position:absolute;left:-40%;width:40%;height:100%;background:#A6DAD8;border-radius:2px;animation:none;opacity:.7"></div>
|
||||
</div>
|
||||
<div class="upload-filename" style="color:rgba(166,218,216,.7)">scan-2024-03-15-brief.pdf</div>
|
||||
<div class="upload-sub" style="color:rgba(255,255,255,.4)">Wird hochgeladen …</div>
|
||||
<div style="font-size:5px;color:rgba(255,255,255,.2)">Abbrechen</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Form panel (same) -->
|
||||
<div class="form-panel">
|
||||
<div class="req-strip">
|
||||
<span class="req-label">Pflichtfelder</span>
|
||||
<div class="req-track"><div class="req-fill" style="width:0%"></div></div>
|
||||
<span class="req-count">0 / 3</span>
|
||||
</div>
|
||||
<div class="form-scroll">
|
||||
<div class="f-card">
|
||||
<div class="f-card-title">Wer & Wann</div>
|
||||
<div class="f-row">
|
||||
<div class="f-field">
|
||||
<span class="f-label">Datum <span class="f-req">*</span></span>
|
||||
<div class="f-input focus empty">TT.MM.JJJJ</div>
|
||||
</div>
|
||||
<div class="f-field">
|
||||
<span class="f-label">Absender <span class="f-req">*</span></span>
|
||||
<div class="f-input empty">Person suchen …</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="action-bar">
|
||||
<span class="btn-skip">Überspringen</span>
|
||||
<div class="btn-spacer"></div>
|
||||
<div class="btn-outline">Speichern</div>
|
||||
<div class="btn-primary">Speichern & Geprüft</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /state B -->
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Callouts -->
|
||||
<div class="callouts" style="max-width:940px;margin:20px auto 0">
|
||||
<div class="callout">
|
||||
<div class="callout-num">1</div>
|
||||
<div class="callout-text"><strong>Dark background is consistent</strong> — the same dark panel color as the loading state prevents a jarring flash when the PDF loads. No separate empty-state design needed for the panel chrome.</div>
|
||||
</div>
|
||||
<div class="callout">
|
||||
<div class="callout-num">2</div>
|
||||
<div class="callout-text"><strong>Filename always visible</strong> — <code>doc.originalFilename</code> is always populated. Showing it in the upload zone reassures the user they're uploading the right file, and in the top bar title it gives context while the form is empty.</div>
|
||||
</div>
|
||||
<div class="callout">
|
||||
<div class="callout-num">3</div>
|
||||
<div class="callout-text"><strong>Form stays usable during upload</strong> — metadata can be filled while the file uploads in parallel. The upload and form save are independent operations; both are sent on "Speichern".</div>
|
||||
</div>
|
||||
<div class="callout">
|
||||
<div class="callout-num">4</div>
|
||||
<div class="callout-text"><strong>Replace file (edit mode)</strong> — when a file already exists and the user is on the edit page, the left panel shows the PDF normally. A "Datei ersetzen" ghost button sits in the PDF toolbar — same panel, just an additional toolbar action.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /section 3 -->
|
||||
|
||||
|
||||
<!-- ══════════════════════════════════════
|
||||
IMPLEMENTATION REFERENCE
|
||||
══════════════════════════════════════ -->
|
||||
<div class="impl-ref">
|
||||
<h2>Implementation Reference</h2>
|
||||
<div class="subtitle">Exact Tailwind classes and component-level changes. All three improvements are additive — no existing card structure or component API needs to change.</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Element</th>
|
||||
<th>File</th>
|
||||
<th>Change</th>
|
||||
<th>Tailwind / Code</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>WhoWhenSection field order</td>
|
||||
<td><code>WhoWhenSection.svelte</code></td>
|
||||
<td>Reorder the four <code><div></code> children of the <code>grid-cols-2</code> container: Date → Sender → Receivers → Location</td>
|
||||
<td>No class changes. Grid order is source order.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>DescriptionSection field order</td>
|
||||
<td><code>DescriptionSection.svelte</code></td>
|
||||
<td>Move <code>documentLocation</code> field to last. Move Tags before Summary.</td>
|
||||
<td>No class changes to existing fields.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Optional divider inside DescriptionSection</td>
|
||||
<td><code>DescriptionSection.svelte</code></td>
|
||||
<td>Add divider element before <code>documentLocation</code> field.</td>
|
||||
<td><code><div class="flex items-center gap-2 my-3"><div class="flex-1 border-t border-line"></div><span class="text-[9px] font-bold uppercase tracking-widest text-ink-3">Optional</span><div class="flex-1 border-t border-line"></div></div></code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Required-fields progress bar</td>
|
||||
<td><code>enrich/[id]/+page.svelte</code></td>
|
||||
<td>Add strip between <code>.enrich-bar</code> and the split pane. Derive count from bindable state already in scope.</td>
|
||||
<td>Strip: <code>flex items-center gap-3 border-b border-line bg-surface px-6 py-1.5</code>. Track: <code>h-0.5 flex-1 rounded-full bg-line</code>. Fill: <code>h-full rounded-full bg-brand-navy transition-all duration-300</code> with <code>style="width:{pct}%"</code>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Required fields derivation</td>
|
||||
<td><code>enrich/[id]/+page.svelte</code></td>
|
||||
<td>Derive from existing state variables, no new props needed.</td>
|
||||
<td><code>const requiredFilled = $derived([doc.title || titleValue, dateIso, senderId].filter(Boolean).length);</code> — 3 total required fields in enrich mode.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Auto-focus first empty required field</td>
|
||||
<td><code>WhoWhenSection.svelte</code></td>
|
||||
<td>Add <code>autofocus</code> on the Date input when <code>initialDateIso</code> is empty; on the Sender PersonTypeahead otherwise.</td>
|
||||
<td><code>{#if !initialDateIso}<input ... autofocus />{:else}<PersonTypeahead ... autofocus />{/if}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>No-PDF upload zone</td>
|
||||
<td><code>enrich/[id]/+page.svelte</code> (new conditional block)</td>
|
||||
<td>Conditionally render upload zone in the left panel when <code>!doc.filePath</code>.</td>
|
||||
<td>Zone: <code>flex-1 flex items-center justify-center bg-[#4A4846]</code>. Inner: <code>border border-dashed border-white/20 rounded-sm p-8 flex flex-col items-center gap-3 text-center</code>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Upload zone drag-and-drop</td>
|
||||
<td><code>enrich/[id]/+page.svelte</code></td>
|
||||
<td>Add <code>ondragover</code> / <code>ondrop</code> to zone element. On drop, trigger same upload action as the file input.</td>
|
||||
<td><code>ondragover={(e) => { e.preventDefault(); isDragging = true; }}</code> + <code>ondrop={(e) => { e.preventDefault(); handleFile(e.dataTransfer?.files[0]); }}</code>. Dragging state: <code>border-brand-mint bg-mint/5</code> transition.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Upload zone — original filename</td>
|
||||
<td><code>enrich/[id]/+page.svelte</code></td>
|
||||
<td>Show <code>doc.originalFilename</code> inside the zone as a label. Always available from the load function.</td>
|
||||
<td><code><p class="text-xs font-medium text-white/50 truncate max-w-[200px]">{doc.originalFilename}</p></code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Uploading / progress state</td>
|
||||
<td><code>enrich/[id]/+page.svelte</code></td>
|
||||
<td>Use existing <code>FileLoader</code> hook's <code>isLoading</code> state. Show indeterminate progress bar when loading.</td>
|
||||
<td>Progress track: <code>w-full h-0.5 bg-white/10 rounded-full overflow-hidden</code>. Fill: <code>h-full bg-brand-mint/70 animate-[slide_1.4s_ease-in-out_infinite]</code>.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Replace file button (edit mode)</td>
|
||||
<td><code>FileSectionEdit.svelte</code></td>
|
||||
<td>Move "Datei ersetzen" from the card in the form scroll to a ghost button in the PDF toolbar. Toolbar is visible in both states.</td>
|
||||
<td>Button: <code>ml-auto text-[10px] font-bold uppercase tracking-widest text-white/40 hover:text-white/70 transition-colors</code>. Label: "Datei ersetzen". Hidden input triggers file picker.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div><!-- /doc -->
|
||||
</body>
|
||||
</html>
|
||||
631
docs/specs/geschichten-document-integration-spec.html
Normal file
631
docs/specs/geschichten-document-integration-spec.html
Normal file
@@ -0,0 +1,631 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Geschichten — Dokumentverknüpfung · Familienarchiv</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,300;9..144,400;9..144,500&family=DM+Sans:wght@300;400;500;600&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root{--color-page:#FAFAF7;--color-surface:#F5F4EE;--color-subtle:#EDECEA;--color-border:#D8D7D0;--color-text-muted:#6B6A63;--color-text:#1C1C18;--navy:#012851;--mint:#A1DCD8;--sand:#F0EFE9;--turquoise:#00C7B1;--blue-tint:#E6F1FB;--blue:#2D7DD2;--blue-dark:#185FA5;--green-tint:#E8F5EA;--green:#3D8C4A;--green-dark:#2E6E39;--orange-tint:#FEF0E6;--orange:#E8862A;--orange-dark:#B46820;--font-display:'Fraunces',Georgia,serif;--font-sans:'DM Sans',system-ui,sans-serif;--font-mono:'DM Mono',monospace;--radius-sm:4px;--radius-md:6px;--radius-lg:10px;--radius-xl:16px;--shadow-card:0 1px 3px rgba(28,28,24,.06),0 1px 2px rgba(28,28,24,.04);--shadow-raised:0 4px 12px rgba(28,28,24,.08),0 2px 4px rgba(28,28,24,.04);--shadow-overlay:0 8px 32px rgba(28,28,24,.12),0 2px 8px rgba(28,28,24,.06);}
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
|
||||
body{font-family:var(--font-sans);background:#E8E7E2;color:var(--color-text);font-size:14px;line-height:1.6;}
|
||||
.doc{max-width:1200px;margin:0 auto;padding:48px 40px 120px;}
|
||||
.doc-header{display:flex;justify-content:space-between;align-items:flex-end;padding-bottom:28px;border-bottom:1px solid var(--color-border);margin-bottom:48px;background:var(--color-page);margin:-48px -40px 48px;padding:48px 40px 28px;border-radius:var(--radius-xl) var(--radius-xl) 0 0;}
|
||||
.doc-header h1{font-family:var(--font-display);font-size:28px;font-weight:500;letter-spacing:-.02em;margin-bottom:4px;}
|
||||
.doc-header p{font-size:13px;color:var(--color-text-muted);max-width:680px;}
|
||||
.doc-meta{font-family:var(--font-mono);font-size:11px;color:var(--color-text-muted);text-align:right;line-height:1.9;}
|
||||
.pill{display:inline-block;padding:2px 8px;border-radius:var(--radius-sm);font-size:10px;font-weight:500;letter-spacing:.05em;}
|
||||
.pill-o{background:var(--orange-tint);color:var(--orange-dark);}
|
||||
.section{margin-bottom:64px;}
|
||||
.section-title{font-size:10px;font-weight:500;letter-spacing:.12em;text-transform:uppercase;color:var(--color-text-muted);padding-bottom:10px;border-bottom:1px solid var(--color-border);margin-bottom:24px;}
|
||||
.prose{font-size:13px;color:var(--color-text-muted);line-height:1.65;max-width:720px;margin-bottom:20px;}
|
||||
.jh{padding:20px 24px;border-radius:var(--radius-xl);margin-bottom:40px;display:flex;align-items:center;gap:16px;}
|
||||
.jh .jn{font-family:var(--font-display);font-size:48px;font-weight:300;line-height:1;opacity:.5;}
|
||||
.jh h2{font-family:var(--font-display);font-size:22px;font-weight:500;letter-spacing:-.02em;margin-bottom:4px;}
|
||||
.jh p{font-size:13px;line-height:1.5;}.jh .fl{font-family:var(--font-mono);font-size:11px;margin-top:6px;opacity:.7;}
|
||||
.jh-o{background:var(--orange-tint);border:1px solid #F0C89A;}.jh-o .jn{color:var(--orange);}.jh-o p,.jh-o .fl{color:var(--orange-dark);}
|
||||
.scr{margin-bottom:56px;}
|
||||
.scr-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;}
|
||||
.scr-head h3{font-family:var(--font-display);font-size:20px;font-weight:500;letter-spacing:-.02em;}
|
||||
.scr-id{font-family:var(--font-mono);font-size:11px;color:var(--color-text-muted);padding:2px 8px;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-page);}
|
||||
.scr-desc{font-size:12px;color:var(--color-text-muted);line-height:1.6;max-width:720px;margin-bottom:6px;}
|
||||
.scr-var{font-size:11px;color:var(--color-text-muted);margin-bottom:20px;}.scr-var strong{color:var(--color-text);}
|
||||
.previews{display:flex;gap:32px;flex-wrap:wrap;justify-content:center;align-items:flex-start;margin-bottom:20px;}
|
||||
.prev-col{display:flex;flex-direction:column;align-items:center;gap:10px;}
|
||||
.bp-lbl{font-family:var(--font-mono);font-size:10px;color:var(--color-text-muted);}
|
||||
.desk{width:100%;max-width:1040px;background:var(--color-page);border-radius:var(--radius-xl);overflow:hidden;box-shadow:var(--shadow-overlay),0 0 0 1px rgba(0,0,0,.06);display:flex;flex-direction:column;min-height:480px;}
|
||||
.phone{width:320px;flex-shrink:0;background:var(--color-page);border-radius:36px;overflow:hidden;box-shadow:var(--shadow-overlay),0 0 0 1px rgba(0,0,0,.07);display:flex;flex-direction:column;border:6px solid #1C1C18;}
|
||||
.pst{padding:10px 20px 0;display:flex;justify-content:space-between;align-items:center;font-size:12px;background:var(--color-page);}.pst b{font-weight:600;}.pst span{font-size:10px;}
|
||||
.pb{flex:1;overflow-y:auto;display:flex;flex-direction:column;}
|
||||
.fa-nav{height:32px;background:var(--navy);display:flex;align-items:center;padding:0 12px;gap:8px;flex-shrink:0;}
|
||||
.fa-logo{font-size:7px;font-weight:900;color:#fff;letter-spacing:.8px;border-bottom:2px solid var(--mint);padding-bottom:1px;}
|
||||
.fa-link{font-size:5.5px;color:rgba(255,255,255,.4);font-weight:700;text-transform:uppercase;letter-spacing:.05em;}
|
||||
.fa-link.on{color:rgba(255,255,255,.9);}
|
||||
.fa-nav-r{margin-left:auto;display:flex;gap:5px;align-items:center;}
|
||||
.fa-av{width:16px;height:16px;background:rgba(255,255,255,.1);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:5px;font-weight:800;color:rgba(255,255,255,.5);}
|
||||
.agent{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:24px;margin-top:20px;}
|
||||
.agent h4{font-family:var(--font-mono);font-size:12px;font-weight:500;margin-bottom:8px;color:var(--navy);}
|
||||
.agent pre{font-family:var(--font-mono);font-size:11px;color:var(--color-text-muted);margin-bottom:12px;white-space:pre-wrap;}
|
||||
.at{width:100%;border-collapse:collapse;font-size:11px;}
|
||||
.at th{font-family:var(--font-mono);font-size:10px;font-weight:500;text-align:left;color:var(--color-text-muted);padding:6px 8px;border-bottom:2px solid var(--color-border);}
|
||||
.at td{padding:5px 8px;border-bottom:1px solid var(--color-subtle);vertical-align:top;line-height:1.5;}
|
||||
.at tr.grp td{font-family:var(--font-mono);font-size:9px;color:var(--color-text-muted);background:var(--color-subtle);font-weight:500;letter-spacing:.06em;text-transform:uppercase;padding:4px 8px;}
|
||||
.at code{font-family:var(--font-mono);font-size:10px;color:var(--navy);background:rgba(1,40,81,.06);padding:1px 4px;border-radius:2px;}
|
||||
|
||||
/* ── Topbar inside frame ── */
|
||||
.fa-topbar{background:#fff;border-bottom:1px solid #E4E2D8;display:flex;align-items:center;height:44px;flex-shrink:0;padding-right:10px;}
|
||||
.fa-accent{width:3px;background:var(--navy);height:44px;flex-shrink:0;}
|
||||
.fa-back{width:24px;height:24px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:9px;color:#6b7280;flex-shrink:0;margin:0 4px;}
|
||||
.fa-tb-divider{width:1px;height:16px;background:#E4E2D8;flex-shrink:0;margin:0 6px;}
|
||||
.fa-tb-title-block{flex:1;min-width:0;padding:0 4px;}
|
||||
.fa-tb-title{font-family:Georgia,serif;font-size:10px;font-weight:700;color:var(--navy);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
||||
.fa-tb-date{font-size:7px;color:#6b7280;margin-top:1px;}
|
||||
.fa-chip{display:inline-flex;align-items:center;gap:2px;padding:1px 5px 1px 2px;background:var(--sand);border:1px solid #E4E2D8;border-radius:8px;white-space:nowrap;font-size:7px;color:var(--color-text);flex-shrink:0;}
|
||||
.fa-chip .av{width:12px;height:12px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:5px;font-weight:800;color:#fff;flex-shrink:0;}
|
||||
.av-navy{background:var(--navy);}
|
||||
.av-purple{background:#5A3080;}
|
||||
.fa-tb-chips{display:flex;align-items:center;gap:4px;margin:0 6px;flex-shrink:0;}
|
||||
.fa-tb-btn{height:22px;padding:0 8px;font-size:6.5px;font-weight:700;letter-spacing:.3px;text-transform:uppercase;border-radius:3px;display:inline-flex;align-items:center;gap:3px;flex-shrink:0;border:1.5px solid #E4E2D8;color:#4b5563;}
|
||||
.fa-tb-btn.primary{background:var(--navy);color:var(--mint);border-color:var(--navy);}
|
||||
.fa-tb-btn.active{background:var(--navy);color:#fff;border-color:var(--navy);}
|
||||
.fa-tb-btn-ico{width:22px;height:22px;border:1.5px solid #E4E2D8;border-radius:3px;display:inline-flex;align-items:center;justify-content:center;color:#6b7280;flex-shrink:0;}
|
||||
.fa-tb-kebab{width:22px;height:22px;border:1.5px solid #E4E2D8;border-radius:3px;display:inline-flex;align-items:center;justify-content:center;flex-direction:column;gap:2px;flex-shrink:0;}
|
||||
.fa-tb-kebab span{display:block;width:3px;height:3px;border-radius:50%;background:#6b7280;}
|
||||
|
||||
/* ── Drawer ── */
|
||||
.fa-drawer{background:#fff;border-bottom:1px solid #E4E2D8;padding:12px 16px;flex-shrink:0;}
|
||||
.fa-drawer-grid{display:grid;gap:16px;}
|
||||
.fa-drawer-grid.cols-3{grid-template-columns:1fr 1fr 1fr;}
|
||||
.fa-drawer-grid.cols-4{grid-template-columns:1fr 1fr 1fr 1fr;}
|
||||
.fa-drawer-col-head{font-size:6.5px;font-weight:800;text-transform:uppercase;letter-spacing:.09em;color:#6b7280;margin-bottom:8px;}
|
||||
.fa-drawer-label{font-size:6.5px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:#9ca3af;margin-bottom:3px;}
|
||||
.fa-drawer-value{font-family:Georgia,serif;font-size:9.5px;color:var(--navy);}
|
||||
.fa-drawer-field{margin-bottom:7px;}
|
||||
.fa-person-row{display:flex;align-items:center;gap:5px;padding:3px 4px;border-radius:3px;}
|
||||
.fa-p-av{width:18px;height:18px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:6px;font-weight:800;color:#fff;flex-shrink:0;}
|
||||
.fa-p-name{font-family:Georgia,serif;font-size:9px;color:var(--navy);}
|
||||
.fa-tag{display:inline-block;padding:1px 5px;border-radius:2px;font-size:6px;font-weight:800;text-transform:uppercase;letter-spacing:.05em;background:#EDECEA;color:#6B6A63;margin:1.5px 1.5px;}
|
||||
.fa-story-entry{margin-bottom:8px;}
|
||||
.fa-story-title{font-family:Georgia,serif;font-size:9px;color:var(--navy);display:block;text-decoration:none;}
|
||||
.fa-story-title:hover{text-decoration:underline;}
|
||||
.fa-story-meta{font-size:6.5px;color:#9ca3af;margin-top:1px;}
|
||||
.fa-story-link-muted{font-size:6.5px;color:#9ca3af;display:block;margin-top:8px;}
|
||||
.fa-story-link-turquoise{font-size:6.5px;color:var(--turquoise);display:block;margin-top:4px;}
|
||||
.fa-status-badge{display:inline-flex;align-items:center;gap:3px;padding:1px 5px;border-radius:3px;font-size:6px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;background:#D1FAE5;color:#065F46;border:1px solid #A7F3D0;}
|
||||
.fa-status-dot{width:4px;height:4px;border-radius:50%;background:#10B981;}
|
||||
|
||||
/* ── PDF area ── */
|
||||
.fa-pdf{background:#D4D0C8;flex:1;display:flex;align-items:center;justify-content:center;min-height:120px;}
|
||||
.fa-paper{background:#FFFEF8;box-shadow:0 2px 8px rgba(0,0,0,.14);border-radius:1px;padding:9px 11px;display:flex;flex-direction:column;gap:2px;width:42%;}
|
||||
.fa-pl{height:3px;background:#C4BDB0;border-radius:1px;opacity:.5;margin-bottom:2px;}
|
||||
.fa-ps{height:2px;background:#C4BDB0;border-radius:1px;opacity:.28;margin-bottom:1.5px;}
|
||||
|
||||
/* ── Mobile nav slim ── */
|
||||
.fa-nav-slim{height:26px;background:var(--navy);display:flex;align-items:center;padding:0 10px;gap:6px;flex-shrink:0;}
|
||||
.fa-nav-slim .fa-logo{font-size:6px;}
|
||||
|
||||
/* ── LLM guide ── */
|
||||
.llm{background:var(--color-page);border:2px solid var(--navy);border-radius:var(--radius-xl);padding:32px 40px;margin-top:64px;}
|
||||
.llm h2{font-family:var(--font-display);font-size:22px;font-weight:500;letter-spacing:-.02em;margin-bottom:8px;color:var(--navy);}
|
||||
.llm h3{font-size:14px;font-weight:600;margin:20px 0 8px;color:var(--color-text);}
|
||||
.llm p,.llm li{font-size:13px;color:var(--color-text-muted);line-height:1.65;}
|
||||
.llm ul,.llm ol{padding-left:20px;margin-bottom:12px;}
|
||||
.llm li{margin-bottom:4px;}
|
||||
.llm code{font-family:var(--font-mono);font-size:11px;background:var(--color-surface);padding:1px 5px;border-radius:3px;}
|
||||
.llm table{width:100%;border-collapse:collapse;margin:12px 0;font-size:12px;}
|
||||
.llm th,.llm td{text-align:left;padding:6px 10px;border-bottom:1px solid var(--color-border);}
|
||||
.llm th{font-weight:500;color:var(--color-text);font-size:11px;text-transform:uppercase;letter-spacing:.05em;}
|
||||
.llm td{color:var(--color-text-muted);}
|
||||
|
||||
@media(max-width:900px){.doc{padding:24px 16px 80px;}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="doc">
|
||||
|
||||
<!-- ══ DOCUMENT HEADER ══════════════════════════════════════════════════════ -->
|
||||
<div class="doc-header">
|
||||
<div>
|
||||
<h1>Geschichten — Dokumentverknüpfung</h1>
|
||||
<p>Entdeckung verlinkter Geschichten über die Dokumentdetailseite. Die Geschichten-Spalte erscheint im Details-Drawer als vierte Spalte neben Details, Personen und Schlagwörtern — nur wenn mindestens eine veröffentlichte Geschichte mit diesem Dokument verknüpft ist.</p>
|
||||
</div>
|
||||
<div class="doc-meta">
|
||||
<span class="pill pill-o">Final Spec</span><br>
|
||||
2026-05-02<br>
|
||||
@leonievoss
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ══ JOURNEY HEADER ═══════════════════════════════════════════════════════ -->
|
||||
<div class="jh jh-o">
|
||||
<div class="jn">D</div>
|
||||
<div>
|
||||
<h2>Dokument-Entdeckung</h2>
|
||||
<p>Lesende entdecken Geschichten, die sich auf einen Brief beziehen, direkt beim Öffnen des Details-Drawers auf der Dokumentdetailseite.</p>
|
||||
<div class="fl">/documents/[id] · DocumentMetadataDrawer</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ══ SECTION: KONZEPT ══════════════════════════════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-title">Konzept — US-BLOG-005</div>
|
||||
|
||||
<p class="prose">
|
||||
Es wird <strong>kein neuer Button in die DocumentTopBar</strong> eingefügt. Der bestehende "Details"-Toggle ist der einzige Einstiegspunkt — er öffnet den <code>DocumentMetadataDrawer</code>, der bereits Details, Personen und Schlagwörter enthält. Die Geschichten-Spalte erscheint dort als <strong>vierte Spalte</strong>, wenn mindestens eine veröffentlichte Geschichte mit diesem Dokument verknüpft ist.
|
||||
</p>
|
||||
<p class="prose">
|
||||
Wenn keine Geschichte verknüpft ist, zeigt der Drawer das gewohnte 3-spaltige Layout — kein leerer Zustand, kein Hinweis, kein Platzhalter. Stille ist korrekt (US-BLOG-005 verlangt keinen Empty State). Die Grid-Klasse wechselt konditionell von <code>lg:grid-cols-3</code> zu <code>lg:grid-cols-4</code> über eine Prop.
|
||||
</p>
|
||||
<p class="prose">
|
||||
Die Prop <code>geschichten: Geschichte[]</code> wird vom <code>+page.server.ts</code> der Dokumentdetailseite befüllt. Nur <strong>veröffentlichte</strong> Geschichten werden übergeben — die Filterung erfolgt auf Serviceebene im Backend. Der <code>DocumentMetadataDrawer</code> ist rein präsentational und trifft keine eigenen Datenbankabfragen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ══ SCREEN D-1: DRAWER GESCHLOSSEN ═══════════════════════════════════════ -->
|
||||
<div class="scr" id="d1">
|
||||
<div class="scr-head">
|
||||
<h3>D-1 — Dokument ohne geöffneten Drawer</h3>
|
||||
<span class="scr-id">D-1</span>
|
||||
</div>
|
||||
<div class="scr-desc">Normalzustand: Der Details-Drawer ist geschlossen. Für den Lesenden ändert sich optisch nichts — die Geschichten-Spalte ist erst sichtbar, wenn der Drawer geöffnet wird.</div>
|
||||
<div class="scr-var"><strong>Desktop ≥1024px</strong> — Drawer geschlossen, PDF-Viewer füllt die verbleibende Höhe.</div>
|
||||
|
||||
<div class="previews">
|
||||
<div class="prev-col">
|
||||
<div class="bp-lbl">Desktop · 1040px · Drawer geschlossen</div>
|
||||
<div class="desk">
|
||||
|
||||
<!-- Nav -->
|
||||
<div class="fa-nav">
|
||||
<div class="fa-logo">FAMILIENARCHIV</div>
|
||||
<div class="fa-link on">Dokumente</div>
|
||||
<div class="fa-link">Personen</div>
|
||||
<div class="fa-link">Geschichten</div>
|
||||
<div class="fa-nav-r"><div class="fa-av">MR</div></div>
|
||||
</div>
|
||||
|
||||
<!-- TopBar — drawer closed -->
|
||||
<div class="fa-topbar">
|
||||
<div class="fa-accent"></div>
|
||||
<div class="fa-back">←</div>
|
||||
<div class="fa-tb-divider"></div>
|
||||
<div class="fa-tb-title-block">
|
||||
<div class="fa-tb-title">Brief an Franz Raddatz, 12. Juli 1938</div>
|
||||
<div class="fa-tb-date">12. Juli 1938</div>
|
||||
</div>
|
||||
<div class="fa-tb-chips">
|
||||
<div class="fa-chip"><div class="av av-navy">FR</div> Franz Raddatz</div>
|
||||
<div class="fa-chip"><div class="av av-purple">EM</div> Emma Müller</div>
|
||||
</div>
|
||||
<div class="fa-tb-divider"></div>
|
||||
<!-- Details toggle — closed state -->
|
||||
<button class="fa-tb-btn" style="margin-right:6px;">Details ▾</button>
|
||||
<div class="fa-tb-divider"></div>
|
||||
<button class="fa-tb-btn primary" style="margin-left:6px;">Transkribieren</button>
|
||||
<button class="fa-tb-btn" style="margin-left:4px;">Bearbeiten</button>
|
||||
<div class="fa-tb-btn-ico" style="margin-left:4px;">
|
||||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PDF viewer area — drawer closed, fills height -->
|
||||
<div class="fa-pdf" style="flex:1;min-height:340px;">
|
||||
<div class="fa-paper" style="min-height:200px;">
|
||||
<div style="font-size:7px;color:#8A8070;font-style:italic;margin-bottom:6px;opacity:.75;">Breslau, den 12. Juli 1938</div>
|
||||
<div class="fa-pl" style="width:92%;"></div>
|
||||
<div class="fa-ps" style="width:85%;"></div>
|
||||
<div class="fa-ps" style="width:90%;"></div>
|
||||
<div class="fa-pl" style="width:78%;"></div>
|
||||
<div class="fa-ps" style="width:88%;"></div>
|
||||
<div class="fa-ps" style="width:70%;"></div>
|
||||
<div class="fa-pl" style="width:84%;"></div>
|
||||
<div class="fa-ps" style="width:90%;"></div>
|
||||
<div class="fa-ps" style="width:60%;"></div>
|
||||
<div class="fa-pl" style="width:75%;"></div>
|
||||
<div class="fa-ps" style="width:82%;"></div>
|
||||
<div class="fa-ps" style="width:46%;"></div>
|
||||
<div style="font-size:6px;color:#8A8070;margin-top:8px;text-align:right;opacity:.7;">Dein Vater</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /desk -->
|
||||
</div><!-- /prev-col -->
|
||||
</div><!-- /previews -->
|
||||
|
||||
<div class="agent">
|
||||
<h4>Hinweis — D-1</h4>
|
||||
<pre>Keine Änderung an der DocumentTopBar. Der "Details"-Button ist bereits implementiert und öffnet/schließt den Drawer. Dieser Screen zeigt den geschlossenen Zustand — identisch mit dem heutigen Verhalten.</pre>
|
||||
</div>
|
||||
</div><!-- /scr D-1 -->
|
||||
|
||||
|
||||
<!-- ══ SCREEN D-2: DRAWER OFFEN — 4-SPALTEN-GRID ═══════════════════════════ -->
|
||||
<div class="scr" id="d2">
|
||||
<div class="scr-head">
|
||||
<h3>D-2 — Drawer offen · 4 Spalten mit Geschichten</h3>
|
||||
<span class="scr-id">D-2</span>
|
||||
</div>
|
||||
<div class="scr-desc">Der Drawer wird geöffnet. Weil dieses Dokument mit veröffentlichten Geschichten verknüpft ist, schaltet das Grid von 3 auf 4 Spalten um. Die Geschichten-Spalte erscheint ganz rechts — gleiche typografische Behandlung wie die anderen Spalten.</div>
|
||||
<div class="scr-var"><strong>Desktop ≥1024px</strong> — Prop <code>geschichten.length > 0</code> ist true, Grid-Klasse ist <code>lg:grid-cols-4</code>.</div>
|
||||
|
||||
<div class="previews">
|
||||
<div class="prev-col">
|
||||
<div class="bp-lbl">Desktop · 1040px · Drawer offen · 4 Spalten</div>
|
||||
<div class="desk">
|
||||
|
||||
<!-- Nav -->
|
||||
<div class="fa-nav">
|
||||
<div class="fa-logo">FAMILIENARCHIV</div>
|
||||
<div class="fa-link on">Dokumente</div>
|
||||
<div class="fa-link">Personen</div>
|
||||
<div class="fa-link">Geschichten</div>
|
||||
<div class="fa-nav-r"><div class="fa-av">MR</div></div>
|
||||
</div>
|
||||
|
||||
<!-- TopBar — drawer open (Details button active/filled) -->
|
||||
<div class="fa-topbar">
|
||||
<div class="fa-accent"></div>
|
||||
<div class="fa-back">←</div>
|
||||
<div class="fa-tb-divider"></div>
|
||||
<div class="fa-tb-title-block">
|
||||
<div class="fa-tb-title">Brief an Franz Raddatz, 12. Juli 1938</div>
|
||||
<div class="fa-tb-date">12. Juli 1938</div>
|
||||
</div>
|
||||
<div class="fa-tb-chips">
|
||||
<div class="fa-chip"><div class="av av-navy">FR</div> Franz Raddatz</div>
|
||||
<div class="fa-chip"><div class="av av-purple">EM</div> Emma Müller</div>
|
||||
</div>
|
||||
<div class="fa-tb-divider"></div>
|
||||
<!-- Details toggle — open/active state -->
|
||||
<button class="fa-tb-btn active" style="margin-right:6px;">Details ▴</button>
|
||||
<div class="fa-tb-divider"></div>
|
||||
<button class="fa-tb-btn primary" style="margin-left:6px;">Transkribieren</button>
|
||||
<button class="fa-tb-btn" style="margin-left:4px;">Bearbeiten</button>
|
||||
<div class="fa-tb-btn-ico" style="margin-left:4px;">
|
||||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Drawer — open, 4-column grid -->
|
||||
<div class="fa-drawer">
|
||||
<div class="fa-drawer-grid cols-4">
|
||||
|
||||
<!-- Col 1: Details -->
|
||||
<div>
|
||||
<div class="fa-drawer-col-head">Details</div>
|
||||
<div class="fa-drawer-field">
|
||||
<div class="fa-drawer-label">Datum</div>
|
||||
<div class="fa-drawer-value">12. Juli 1938</div>
|
||||
</div>
|
||||
<div class="fa-drawer-field">
|
||||
<div class="fa-drawer-label">Ort</div>
|
||||
<div class="fa-drawer-value">Breslau</div>
|
||||
</div>
|
||||
<div class="fa-drawer-field">
|
||||
<div class="fa-drawer-label">Status</div>
|
||||
<div class="fa-status-badge"><span class="fa-status-dot"></span>Hochgeladen</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Col 2: Personen -->
|
||||
<div>
|
||||
<div class="fa-drawer-col-head">Personen</div>
|
||||
<div class="fa-drawer-label" style="margin-bottom:4px;">Absender</div>
|
||||
<div class="fa-person-row">
|
||||
<div class="fa-p-av av-navy">FR</div>
|
||||
<div class="fa-p-name">Franz Raddatz</div>
|
||||
</div>
|
||||
<div class="fa-drawer-label" style="margin-top:8px;margin-bottom:4px;">Empfänger</div>
|
||||
<div class="fa-person-row">
|
||||
<div class="fa-p-av av-purple">EM</div>
|
||||
<div class="fa-p-name">Emma Müller</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Col 3: Schlagwörter -->
|
||||
<div>
|
||||
<div class="fa-drawer-col-head">Schlagwörter</div>
|
||||
<div style="display:flex;flex-wrap:wrap;margin:-1.5px;">
|
||||
<span class="fa-tag">Familie</span>
|
||||
<span class="fa-tag">Krieg</span>
|
||||
<span class="fa-tag">Breslau</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Col 4: Geschichten (NEW) -->
|
||||
<div>
|
||||
<div class="fa-drawer-col-head">Geschichten</div>
|
||||
|
||||
<!-- Story 1 -->
|
||||
<div class="fa-story-entry">
|
||||
<a class="fa-story-title" href="#">Der Sommer in Breslau</a>
|
||||
<div class="fa-story-meta">von Maria Raddatz · 14. März 2025</div>
|
||||
</div>
|
||||
|
||||
<!-- Story 2 -->
|
||||
<div class="fa-story-entry">
|
||||
<a class="fa-story-title" href="#">Die Hochzeit im Krieg</a>
|
||||
<div class="fa-story-meta">von Gertrud Koch · 18. Okt. 2024</div>
|
||||
</div>
|
||||
|
||||
<!-- "Alle anzeigen" link — shown when stories.length > 3 -->
|
||||
<!-- (hidden in this example because only 2 stories) -->
|
||||
|
||||
<!-- "+ Geschichte anhängen" — BLOG_WRITE only -->
|
||||
<div class="fa-story-link-turquoise">+ Geschichte anhängen</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /grid -->
|
||||
</div><!-- /drawer -->
|
||||
|
||||
<!-- PDF viewer below drawer -->
|
||||
<div class="fa-pdf" style="flex:1;min-height:200px;">
|
||||
<div class="fa-paper" style="min-height:120px;">
|
||||
<div style="font-size:7px;color:#8A8070;font-style:italic;margin-bottom:6px;opacity:.75;">Breslau, den 12. Juli 1938</div>
|
||||
<div class="fa-pl" style="width:92%;"></div>
|
||||
<div class="fa-ps" style="width:85%;"></div>
|
||||
<div class="fa-ps" style="width:88%;"></div>
|
||||
<div class="fa-pl" style="width:78%;"></div>
|
||||
<div class="fa-ps" style="width:70%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /desk -->
|
||||
</div><!-- /prev-col -->
|
||||
</div><!-- /previews -->
|
||||
|
||||
<!-- impl-ref table D-2 -->
|
||||
<div class="agent">
|
||||
<h4>impl-ref — D-2 · Geschichten-Spalte im Drawer</h4>
|
||||
<table class="at">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Element</th>
|
||||
<th>Wert / Klasse</th>
|
||||
<th>Hinweis</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="grp"><td colspan="3">Grid</td></tr>
|
||||
<tr>
|
||||
<td>Grid mit Geschichten</td>
|
||||
<td><code>grid grid-cols-1 gap-6 lg:grid-cols-4</code></td>
|
||||
<td>Konditional: <code>geschichten.length > 0</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Grid ohne Geschichten</td>
|
||||
<td><code>grid grid-cols-1 gap-6 lg:grid-cols-3</code></td>
|
||||
<td>Default — keine Änderung</td>
|
||||
</tr>
|
||||
<tr class="grp"><td colspan="3">Spalten-Heading</td></tr>
|
||||
<tr>
|
||||
<td>Col 4 Heading</td>
|
||||
<td><code>mb-4 font-sans text-xs font-bold tracking-widest text-ink-3 uppercase</code></td>
|
||||
<td>Identisch mit Col 1–3 Headings</td>
|
||||
</tr>
|
||||
<tr class="grp"><td colspan="3">Story-Einträge</td></tr>
|
||||
<tr>
|
||||
<td>Eintrags-Wrapper</td>
|
||||
<td><code>space-y-3</code></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Story-Titel-Link</td>
|
||||
<td><code>block font-serif text-sm text-ink hover:text-primary hover:underline</code></td>
|
||||
<td>Verlinkt auf <code>/geschichten/[id]</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Story-Meta</td>
|
||||
<td><code>font-sans text-[10px] text-ink-3</code></td>
|
||||
<td>"von [author] · [date]"</td>
|
||||
</tr>
|
||||
<tr class="grp"><td colspan="3">Aktionslinks</td></tr>
|
||||
<tr>
|
||||
<td>"Alle anzeigen" Link</td>
|
||||
<td><code>font-sans text-[10px] text-ink-3 hover:text-primary</code></td>
|
||||
<td>Nur wenn <code>stories.length > 3</code>; verlinkt auf <code>/geschichten?documentId=xxx</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>"+ Geschichte anhängen"</td>
|
||||
<td><code>font-sans text-[10px] text-turquoise hover:underline</code></td>
|
||||
<td>Nur sichtbar für BLOG_WRITE — via <code>canWrite</code>-Prop</td>
|
||||
</tr>
|
||||
<tr class="grp"><td colspan="3">Props</td></tr>
|
||||
<tr>
|
||||
<td>Neue Prop</td>
|
||||
<td><code>geschichten: Geschichte[]</code></td>
|
||||
<td>Wird von <code>+page.server.ts</code> an <code>DocumentMetadataDrawer</code> übergeben</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Neue Prop</td>
|
||||
<td><code>canWrite: boolean</code></td>
|
||||
<td>Steuert Sichtbarkeit von "+ Geschichte anhängen"</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div><!-- /scr D-2 -->
|
||||
|
||||
|
||||
<!-- ══ SCREEN D-3: MOBILE DRAWER ════════════════════════════════════════════ -->
|
||||
<div class="scr" id="d3">
|
||||
<div class="scr-head">
|
||||
<h3>D-3 — Mobile · Drawer offen, Geschichten-Spalte gestapelt</h3>
|
||||
<span class="scr-id">D-3</span>
|
||||
</div>
|
||||
<div class="scr-desc">Auf Mobile ist das Grid einspaltig (<code>grid-cols-1</code>). Alle vier Abschnitte stapeln sich vertikal. Die Geschichten-Spalte erscheint als letzter Block — unterhalb von Schlagwörtern. Layout und Typografie sind identisch mit Desktop, nur volle Breite.</div>
|
||||
<div class="scr-var"><strong>Mobile <640px</strong> — kein Person-Chip-Row, kebab statt einzelner Action-Buttons, alle Drawer-Spalten gestapelt.</div>
|
||||
|
||||
<div class="previews">
|
||||
<div class="prev-col">
|
||||
<div class="bp-lbl">Mobile · 320px · Drawer offen</div>
|
||||
<div class="phone">
|
||||
<!-- Status bar -->
|
||||
<div class="pst">
|
||||
<b>9:41</b>
|
||||
<span>●●●</span>
|
||||
</div>
|
||||
<div class="pb">
|
||||
<!-- Nav slim -->
|
||||
<div class="fa-nav-slim">
|
||||
<div class="fa-logo">FAMILIENARCHIV</div>
|
||||
<div class="fa-nav-r"><div class="fa-av">MR</div></div>
|
||||
</div>
|
||||
|
||||
<!-- TopBar mobile — compact -->
|
||||
<div class="fa-topbar" style="height:40px;">
|
||||
<div class="fa-accent" style="height:40px;"></div>
|
||||
<div class="fa-back" style="font-size:8px;margin:0 3px;">←</div>
|
||||
<div class="fa-tb-divider"></div>
|
||||
<div class="fa-tb-title-block" style="padding:0 3px;">
|
||||
<div class="fa-tb-title" style="font-size:9px;">Brief an Franz Raddatz…</div>
|
||||
<div class="fa-tb-date" style="font-size:6px;">12. Juli 1938</div>
|
||||
</div>
|
||||
<!-- Details toggle active on mobile too -->
|
||||
<button class="fa-tb-btn active" style="font-size:6px;height:19px;padding:0 6px;margin-right:4px;">Details ▴</button>
|
||||
<div class="fa-tb-divider"></div>
|
||||
<!-- Kebab menu on mobile replaces individual action buttons -->
|
||||
<div class="fa-tb-kebab" style="margin-left:4px;">
|
||||
<span></span><span></span><span></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Drawer — open, single column stacked -->
|
||||
<div class="fa-drawer" style="padding:10px 12px;">
|
||||
<!-- Col 1: Details -->
|
||||
<div style="margin-bottom:14px;">
|
||||
<div class="fa-drawer-col-head">Details</div>
|
||||
<div class="fa-drawer-field">
|
||||
<div class="fa-drawer-label">Datum</div>
|
||||
<div class="fa-drawer-value" style="font-size:8.5px;">12. Juli 1938</div>
|
||||
</div>
|
||||
<div class="fa-drawer-field">
|
||||
<div class="fa-drawer-label">Ort</div>
|
||||
<div class="fa-drawer-value" style="font-size:8.5px;">Breslau</div>
|
||||
</div>
|
||||
<div class="fa-drawer-field">
|
||||
<div class="fa-drawer-label">Status</div>
|
||||
<div class="fa-status-badge"><span class="fa-status-dot"></span>Hochgeladen</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Col 2: Personen -->
|
||||
<div style="margin-bottom:14px;">
|
||||
<div class="fa-drawer-col-head">Personen</div>
|
||||
<div class="fa-drawer-label" style="margin-bottom:3px;">Absender</div>
|
||||
<div class="fa-person-row">
|
||||
<div class="fa-p-av av-navy" style="width:15px;height:15px;font-size:5.5px;">FR</div>
|
||||
<div class="fa-p-name" style="font-size:8.5px;">Franz Raddatz</div>
|
||||
</div>
|
||||
<div class="fa-drawer-label" style="margin-top:6px;margin-bottom:3px;">Empfänger</div>
|
||||
<div class="fa-person-row">
|
||||
<div class="fa-p-av av-purple" style="width:15px;height:15px;font-size:5.5px;">EM</div>
|
||||
<div class="fa-p-name" style="font-size:8.5px;">Emma Müller</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Col 3: Schlagwörter -->
|
||||
<div style="margin-bottom:14px;">
|
||||
<div class="fa-drawer-col-head">Schlagwörter</div>
|
||||
<div style="display:flex;flex-wrap:wrap;margin:-1.5px;">
|
||||
<span class="fa-tag">Familie</span>
|
||||
<span class="fa-tag">Krieg</span>
|
||||
<span class="fa-tag">Breslau</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Col 4: Geschichten — full width on mobile, below Schlagwörter -->
|
||||
<div>
|
||||
<div class="fa-drawer-col-head">Geschichten</div>
|
||||
|
||||
<div class="fa-story-entry">
|
||||
<a class="fa-story-title" href="#" style="font-size:8.5px;">Der Sommer in Breslau</a>
|
||||
<div class="fa-story-meta" style="font-size:6px;">von Maria Raddatz · 14. März 2025</div>
|
||||
</div>
|
||||
|
||||
<div class="fa-story-entry">
|
||||
<a class="fa-story-title" href="#" style="font-size:8.5px;">Die Hochzeit im Krieg</a>
|
||||
<div class="fa-story-meta" style="font-size:6px;">von Gertrud Koch · 18. Okt. 2024</div>
|
||||
</div>
|
||||
|
||||
<div class="fa-story-link-muted" style="font-size:6px;">Alle Geschichten anzeigen →</div>
|
||||
<div class="fa-story-link-turquoise" style="font-size:6px;">+ Geschichte anhängen</div>
|
||||
</div>
|
||||
</div><!-- /drawer -->
|
||||
|
||||
<!-- PDF viewer below drawer on mobile -->
|
||||
<div class="fa-pdf" style="flex:1;min-height:100px;">
|
||||
<div class="fa-paper" style="width:60%;min-height:70px;">
|
||||
<div class="fa-pl" style="width:88%;"></div>
|
||||
<div class="fa-ps" style="width:82%;"></div>
|
||||
<div class="fa-ps" style="width:90%;"></div>
|
||||
<div class="fa-pl" style="width:74%;"></div>
|
||||
<div class="fa-ps" style="width:68%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /pb -->
|
||||
</div><!-- /phone -->
|
||||
</div><!-- /prev-col -->
|
||||
</div><!-- /previews -->
|
||||
|
||||
<div class="agent">
|
||||
<h4>Hinweis — D-3 Mobile</h4>
|
||||
<pre>Mobile erfordert keine eigene Komponente. Das Tailwind-Grid `grid-cols-1 lg:grid-cols-4` erledigt das Stacking automatisch. Die Geschichten-Spalte erscheint zuletzt im DOM (nach Schlagwörter) — das ist die korrekte Lesereihenfolge auf Mobile.</pre>
|
||||
</div>
|
||||
</div><!-- /scr D-3 -->
|
||||
|
||||
|
||||
<!-- ══ LLM IMPLEMENTATION GUIDE ══════════════════════════════════════════════ -->
|
||||
<div class="llm">
|
||||
<h2>LLM-Implementierungsguide</h2>
|
||||
|
||||
<h3>Backend — Endpunkt-Erweiterung</h3>
|
||||
<p>Der <code>GET /api/documents/{id}</code> Response (oder ein separater Endpunkt) muss ein <code>geschichten</code>-Array enthalten:</p>
|
||||
<ul>
|
||||
<li>Felder pro Eintrag: <code>id</code>, <code>title</code>, <code>author: { displayName }</code>, <code>publishedAt</code></li>
|
||||
<li>Nur Geschichten mit Status <code>PUBLISHED</code> werden zurückgegeben — Filterung auf Service-Ebene, nicht im Controller</li>
|
||||
<li>Maximale Anzahl im Response: unbegrenzt; das Frontend blendet einen "Alle anzeigen"-Link ein, wenn mehr als 3 vorhanden sind</li>
|
||||
</ul>
|
||||
|
||||
<h3>Frontend — Änderungen</h3>
|
||||
<ol>
|
||||
<li><code>+page.server.ts</code>: <code>geschichten</code>-Array aus dem API-Response extrahieren und als Prop an <code>DocumentMetadataDrawer</code> übergeben</li>
|
||||
<li><code>DocumentMetadataDrawer</code>: neue Prop <code>geschichten: Geschichte[]</code> und <code>canWrite: boolean</code> annehmen</li>
|
||||
<li>Grid-Klasse dynamisch: <code>lg:grid-cols-{geschichten.length > 0 ? 4 : 3}</code></li>
|
||||
<li>4. Spalte konditional rendern: <code>{#if geschichten.length > 0}</code></li>
|
||||
<li>"Alle anzeigen"-Link: nur wenn <code>geschichten.length > 3</code>, Link zu <code>/geschichten?documentId={id}</code></li>
|
||||
<li>"+ Geschichte anhängen"-Link: nur wenn <code>canWrite</code>, Link zu <code>/geschichten/new?documentId={id}</code></li>
|
||||
</ol>
|
||||
|
||||
<h3>Keine Änderungen erforderlich</h3>
|
||||
<ul>
|
||||
<li><strong>DocumentTopBar</strong>: kein neuer Button, kein neues Icon</li>
|
||||
<li><strong>Andere Drawer-Spalten</strong>: Details, Personen, Schlagwörter bleiben unverändert</li>
|
||||
<li><strong>Mobile Layout</strong>: das Grid-Stacking ist automatisch via Tailwind, keine separate Mobile-Logik</li>
|
||||
</ul>
|
||||
|
||||
<h3>Implementierungsreferenz — Typedefinition</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Feld</th>
|
||||
<th>Typ</th>
|
||||
<th>Quelle</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td><code>id</code></td><td><code>string (UUID)</code></td><td>Backend</td></tr>
|
||||
<tr><td><code>title</code></td><td><code>string</code></td><td>Backend</td></tr>
|
||||
<tr><td><code>author.displayName</code></td><td><code>string</code></td><td>Backend</td></tr>
|
||||
<tr><td><code>publishedAt</code></td><td><code>string (ISO date)</code></td><td>Backend</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div><!-- /llm -->
|
||||
|
||||
</div><!-- /doc -->
|
||||
</body>
|
||||
</html>
|
||||
502
docs/specs/geschichten-person-integration-spec.html
Normal file
502
docs/specs/geschichten-person-integration-spec.html
Normal file
@@ -0,0 +1,502 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Geschichten — Personenverknüpfung · Familienarchiv Spec</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,300;9..144,400;9..144,500&family=DM+Sans:wght@300;400;500;600&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet"/>
|
||||
<style>
|
||||
:root{--color-page:#FAFAF7;--color-surface:#F5F4EE;--color-subtle:#EDECEA;--color-border:#D8D7D0;--color-text-muted:#6B6A63;--color-text:#1C1C18;--navy:#012851;--mint:#A1DCD8;--sand:#F0EFE9;--turquoise:#00C7B1;--blue-tint:#E6F1FB;--blue:#2D7DD2;--blue-dark:#185FA5;--green-tint:#E8F5EA;--green:#3D8C4A;--green-dark:#2E6E39;--purple-tint:#EEEDFE;--purple:#534AB7;--purple-dark:#3C3489;--font-display:'Fraunces',Georgia,serif;--font-sans:'DM Sans',system-ui,sans-serif;--font-mono:'DM Mono',monospace;--radius-sm:4px;--radius-md:6px;--radius-lg:10px;--radius-xl:16px;--shadow-card:0 1px 3px rgba(28,28,24,.06),0 1px 2px rgba(28,28,24,.04);--shadow-raised:0 4px 12px rgba(28,28,24,.08),0 2px 4px rgba(28,28,24,.04);--shadow-overlay:0 8px 32px rgba(28,28,24,.12),0 2px 8px rgba(28,28,24,.06);}
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
|
||||
body{font-family:var(--font-sans);background:#E8E7E2;color:var(--color-text);font-size:14px;line-height:1.6;}
|
||||
.doc{max-width:1200px;margin:0 auto;padding:48px 40px 120px;}
|
||||
.doc-header{display:flex;justify-content:space-between;align-items:flex-end;padding-bottom:28px;border-bottom:1px solid var(--color-border);margin-bottom:48px;background:var(--color-page);margin:-48px -40px 48px;padding:48px 40px 28px;border-radius:var(--radius-xl) var(--radius-xl) 0 0;}
|
||||
.doc-header h1{font-family:var(--font-display);font-size:28px;font-weight:500;letter-spacing:-.02em;margin-bottom:4px;}
|
||||
.doc-header p{font-size:13px;color:var(--color-text-muted);max-width:680px;}
|
||||
.doc-meta{font-family:var(--font-mono);font-size:11px;color:var(--color-text-muted);text-align:right;line-height:1.9;}
|
||||
.pill{display:inline-block;padding:2px 8px;border-radius:var(--radius-sm);font-size:10px;font-weight:500;letter-spacing:.05em;}
|
||||
.pill-p{background:var(--purple-tint);color:var(--purple-dark);}
|
||||
.section{margin-bottom:64px;}
|
||||
.section-title{font-size:10px;font-weight:500;letter-spacing:.12em;text-transform:uppercase;color:var(--color-text-muted);padding-bottom:10px;border-bottom:1px solid var(--color-border);margin-bottom:24px;}
|
||||
.prose{font-size:13px;color:var(--color-text-muted);line-height:1.65;max-width:720px;margin-bottom:20px;}
|
||||
.jh{padding:20px 24px;border-radius:var(--radius-xl);margin-bottom:40px;display:flex;align-items:center;gap:16px;}
|
||||
.jh .jn{font-family:var(--font-display);font-size:48px;font-weight:300;line-height:1;opacity:.5;}
|
||||
.jh h2{font-family:var(--font-display);font-size:22px;font-weight:500;letter-spacing:-.02em;margin-bottom:4px;}
|
||||
.jh p{font-size:13px;line-height:1.5;}.jh .fl{font-family:var(--font-mono);font-size:11px;margin-top:6px;opacity:.7;}
|
||||
.jh-p{background:var(--purple-tint);border:1px solid #C5C2F5;}.jh-p .jn{color:var(--purple);}.jh-p p,.jh-p .fl{color:var(--purple-dark);}
|
||||
.scr{margin-bottom:56px;}
|
||||
.scr-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;}
|
||||
.scr-head h3{font-family:var(--font-display);font-size:20px;font-weight:500;letter-spacing:-.02em;}
|
||||
.scr-id{font-family:var(--font-mono);font-size:11px;color:var(--color-text-muted);padding:2px 8px;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-page);}
|
||||
.scr-desc{font-size:12px;color:var(--color-text-muted);line-height:1.6;max-width:720px;margin-bottom:6px;}
|
||||
.scr-var{font-size:11px;color:var(--color-text-muted);margin-bottom:20px;}.scr-var strong{color:var(--color-text);}
|
||||
.previews{display:flex;gap:32px;flex-wrap:wrap;justify-content:center;align-items:flex-start;margin-bottom:20px;}
|
||||
.prev-col{display:flex;flex-direction:column;align-items:center;gap:10px;}
|
||||
.bp-lbl{font-family:var(--font-mono);font-size:10px;color:var(--color-text-muted);}
|
||||
|
||||
/* ── Frame shells ── */
|
||||
.desk{width:100%;max-width:1040px;background:#E8E7E2;border-radius:var(--radius-xl);overflow:hidden;box-shadow:var(--shadow-overlay),0 0 0 1px rgba(0,0,0,.06);display:flex;flex-direction:column;min-height:560px;}
|
||||
.phone{width:320px;flex-shrink:0;background:#E8E7E2;border-radius:36px;overflow:hidden;box-shadow:var(--shadow-overlay),0 0 0 1px rgba(0,0,0,.07);display:flex;flex-direction:column;border:6px solid #1C1C18;}
|
||||
.pst{padding:10px 20px 0;display:flex;justify-content:space-between;align-items:center;font-size:12px;background:var(--color-page);}.pst b{font-weight:600;}.pst span{font-size:10px;}
|
||||
.pb{flex:1;overflow-y:auto;display:flex;flex-direction:column;}
|
||||
|
||||
/* ── FA chrome ── */
|
||||
.fa-nav{height:32px;background:var(--navy);display:flex;align-items:center;padding:0 12px;gap:8px;flex-shrink:0;}
|
||||
.fa-logo{font-size:7px;font-weight:900;color:#fff;letter-spacing:.8px;border-bottom:2px solid var(--mint);padding-bottom:1px;}
|
||||
.fa-link{font-size:5.5px;color:rgba(255,255,255,.4);font-weight:700;text-transform:uppercase;letter-spacing:.05em;}
|
||||
.fa-link.active{color:var(--mint);border-bottom:1px solid rgba(161,220,216,.5);padding-bottom:1px;}
|
||||
.fa-nav-r{margin-left:auto;display:flex;gap:5px;align-items:center;}
|
||||
.fa-av{width:16px;height:16px;background:rgba(255,255,255,.1);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:5px;font-weight:800;color:rgba(255,255,255,.5);}
|
||||
|
||||
/* ── Page content area ── */
|
||||
.page-body{padding:14px 16px;background:#E8E7E2;flex:1;overflow-y:auto;}
|
||||
.back-btn{display:inline-flex;align-items:center;gap:3px;font-size:6.5px;font-weight:600;color:var(--color-text-muted);margin-bottom:10px;}
|
||||
.back-arrow{font-size:8px;}
|
||||
|
||||
/* ── 2-col person page layout ── */
|
||||
.person-layout{display:grid;grid-template-columns:35% 65%;gap:10px;}
|
||||
|
||||
/* ── Card (shared project pattern) ── */
|
||||
.card{background:#fff;border:1px solid #E4E2D7;border-radius:3px;padding:10px;box-shadow:0 1px 2px rgba(0,0,0,.04);}
|
||||
.card+.card{margin-top:6px;}
|
||||
.card-h{font-size:6px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:#9CA3AF;margin-bottom:7px;}
|
||||
|
||||
/* ── PersonCard ── */
|
||||
.person-avatar{width:36px;height:36px;background:var(--navy);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:800;color:var(--mint);margin-bottom:6px;}
|
||||
.person-name{font-family:Georgia,serif;font-size:11px;font-weight:500;color:var(--navy);margin-bottom:2px;}
|
||||
.person-dates{font-size:6px;color:var(--color-text-muted);}
|
||||
|
||||
/* ── Generic list rows in right-col cards ── */
|
||||
.row-item{display:flex;align-items:center;gap:5px;padding:3px 0;border-bottom:1px solid #F0EFE9;font-size:6.5px;color:var(--color-text);}
|
||||
.row-item:last-child{border-bottom:none;}
|
||||
.row-av{width:14px;height:14px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:5px;font-weight:800;color:#fff;flex-shrink:0;}
|
||||
.row-name{flex:1;font-weight:600;}
|
||||
.row-muted{color:var(--color-text-muted);font-size:6px;}
|
||||
.count-chip{background:#F0EFE9;border-radius:8px;padding:1px 4px;font-size:5.5px;font-weight:700;color:var(--color-text-muted);}
|
||||
|
||||
/* ── Geschichten card editorial list ── */
|
||||
.gesch-list{display:flex;flex-direction:column;}
|
||||
.gesch-row{display:flex;gap:7px;padding:5px 0;border-bottom:1px solid #F0EFE9;}
|
||||
.gesch-row:first-child{padding-top:0;}
|
||||
.gesch-row:last-child{border-bottom:none;padding-bottom:0;}
|
||||
.gesch-meta{width:62px;flex-shrink:0;display:flex;flex-direction:column;gap:1.5px;}
|
||||
.gesch-av{width:16px;height:16px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:5.5px;font-weight:800;color:#fff;flex-shrink:0;}
|
||||
.gesch-author{font-size:5.5px;font-weight:700;color:var(--color-text);line-height:1.3;}
|
||||
.gesch-date{font-size:5px;color:var(--color-text-muted);}
|
||||
.gesch-content{flex:1;min-width:0;}
|
||||
.gesch-title{font-family:Georgia,serif;font-size:7.5px;color:var(--navy);line-height:1.3;margin-bottom:2px;display:block;}
|
||||
.gesch-title:hover{text-decoration:underline;}
|
||||
.gesch-excerpt{font-size:6px;color:var(--color-text-muted);line-height:1.4;overflow:hidden;display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;}
|
||||
.gesch-footer{display:flex;justify-content:space-between;align-items:center;margin-top:6px;padding-top:4px;border-top:1px solid #F0EFE9;}
|
||||
.gesch-all-link{font-size:6px;color:var(--color-text-muted);}
|
||||
.gesch-write-link{font-size:6px;color:var(--turquoise);font-weight:600;}
|
||||
|
||||
/* ── Alias tags ── */
|
||||
.alias-tag{display:inline-block;background:#F0EFE9;border:1px solid #E4E2D7;border-radius:3px;padding:1px 5px;font-size:5.5px;font-weight:600;color:var(--color-text-muted);margin:1px 1px 1px 0;}
|
||||
|
||||
/* ── impl-ref / agent box ── */
|
||||
.agent{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:24px;margin-top:20px;}
|
||||
.agent h4{font-family:var(--font-mono);font-size:12px;font-weight:500;margin-bottom:8px;color:var(--navy);}
|
||||
.agent pre{font-family:var(--font-mono);font-size:11px;color:var(--color-text-muted);margin-bottom:12px;white-space:pre-wrap;background:rgba(0,0,0,.03);padding:10px;border-radius:4px;}
|
||||
.at{width:100%;border-collapse:collapse;font-size:11px;}
|
||||
.at th{font-family:var(--font-mono);font-size:10px;font-weight:500;text-align:left;color:var(--color-text-muted);padding:6px 8px;border-bottom:2px solid var(--color-border);}
|
||||
.at td{padding:5px 8px;border-bottom:1px solid var(--color-subtle);vertical-align:top;line-height:1.5;}
|
||||
.at tr.grp td{font-family:var(--font-mono);font-size:9px;color:var(--color-text-muted);background:var(--color-subtle);font-weight:500;letter-spacing:.06em;text-transform:uppercase;padding:4px 8px;}
|
||||
.at code{font-family:var(--font-mono);font-size:10px;color:var(--navy);background:rgba(1,40,81,.06);padding:1px 4px;border-radius:2px;}
|
||||
|
||||
/* ── LLM guide ── */
|
||||
.llm{background:var(--color-page);border:1px solid var(--color-border);border-radius:var(--radius-xl);padding:40px;margin-top:48px;}
|
||||
.llm h2{font-family:var(--font-display);font-size:22px;font-weight:500;margin-bottom:24px;}
|
||||
.llm h3{font-size:13px;font-weight:600;margin:20px 0 8px;}
|
||||
.llm p,.llm li{font-size:13px;color:var(--color-text-muted);line-height:1.65;}
|
||||
.llm ul,.llm ol{padding-left:20px;margin-bottom:16px;}
|
||||
.llm table{width:100%;border-collapse:collapse;font-size:12px;margin-bottom:16px;}
|
||||
.llm th{font-family:var(--font-mono);font-size:10px;text-align:left;padding:5px 8px;border-bottom:2px solid var(--color-border);color:var(--color-text-muted);}
|
||||
.llm td{padding:5px 8px;border-bottom:1px solid var(--color-subtle);vertical-align:top;}
|
||||
.llm code{font-family:var(--font-mono);font-size:11px;color:var(--navy);background:rgba(1,40,81,.06);padding:1px 4px;border-radius:2px;}
|
||||
|
||||
/* ── Silence note ── */
|
||||
.silence-note{background:#fff;border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:20px 24px;display:flex;align-items:flex-start;gap:12px;}
|
||||
.silence-icon{font-size:18px;opacity:.4;flex-shrink:0;margin-top:2px;}
|
||||
.silence-note p{font-size:13px;color:var(--color-text-muted);line-height:1.6;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="doc">
|
||||
|
||||
<!-- ── Header ── -->
|
||||
<div class="doc-header">
|
||||
<div>
|
||||
<h1>Geschichten — Personenverknüpfung</h1>
|
||||
<p>Entdeckung verlinkter Geschichten über die Personendetailseite. Lesende, die nach Franz Raddatz suchen, finden automatisch alle veröffentlichten Geschichten, die ihn erwähnen — als neue Karte am Ende der rechten Spalte.</p>
|
||||
</div>
|
||||
<div class="doc-meta">
|
||||
Familienarchiv<br/>
|
||||
<span class="pill pill-p">Final Spec</span><br/>
|
||||
2026-05-02 · @leonievoss
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Journey header ── -->
|
||||
<div class="jh jh-p">
|
||||
<div class="jn">P</div>
|
||||
<div>
|
||||
<h2>Personen-Entdeckung</h2>
|
||||
<p>Neugierige Leserinnen und Leser suchen Personen und finden dort alle Geschichten, die über sie erzählt wurden — ohne separaten Suchlauf.</p>
|
||||
<div class="fl">/persons/[id] · +page.svelte rechte Spalte · GeschichtenCard.svelte</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Section: Konzept ── -->
|
||||
<div class="section">
|
||||
<div class="section-title">Konzept</div>
|
||||
<p class="prose">Die Personendetailseite hat bereits eine informationsdichte rechte Spalte: Briefpartner, Beziehungen, gesendete und empfangene Briefe. Die Geschichten-Karte wird ganz am Ende dieser Spalte angehängt — sie erscheint nur, wenn mindestens eine veröffentlichte Geschichte diese Person erwähnt. Ist keine vorhanden, fehlt die Karte vollständig; kein Leerszustand, keine Platzhalter.</p>
|
||||
<p class="prose">Innerhalb der Karte gilt das redaktionelle Listen-Layout: Metadaten links (Avatar, Autorenname, Datum), Titel und Ausschnitt rechts — konsistent mit der /geschichten-Übersichtsseite. BLOG_WRITERs sehen im Karten-Header einen Schnellzugriff, um eine neue Geschichte mit dieser Person vorauszufüllen.</p>
|
||||
<p class="prose">Die Karte ist bewusst passiv: Sie zeigt, was andere über diese Person geschrieben haben. Sie ist kein Aufruf zum Schreiben, sondern ein stiller Hinweis auf vorhandene Erinnerungen.</p>
|
||||
</div>
|
||||
|
||||
<!-- ── Screen P-1: Desktop ── -->
|
||||
<div class="section">
|
||||
<div class="section-title">Screens</div>
|
||||
|
||||
<div class="scr">
|
||||
<div class="scr-head">
|
||||
<h3>P-1 — /persons/[id] · Geschichten-Karte in der rechten Spalte</h3>
|
||||
<span class="scr-id">P-1</span>
|
||||
</div>
|
||||
<p class="scr-desc">Vollständige Personendetailseite mit der neuen Geschichten-Karte am Ende der rechten Spalte. Franz Raddatz hat drei veröffentlichte Geschichten, die ihn erwähnen.</p>
|
||||
<p class="scr-var"><strong>Desktop · 1040px</strong> — Zweispaltiges Layout (35 % / 65 %), Karte erscheint als letztes Element rechts.</p>
|
||||
|
||||
<div class="previews">
|
||||
<div class="prev-col">
|
||||
<span class="bp-lbl">Desktop · 1040px</span>
|
||||
<div class="desk">
|
||||
<div class="fa-nav">
|
||||
<span class="fa-logo">ARCHIV</span>
|
||||
<span class="fa-link" style="margin-left:8px">Dokumente</span>
|
||||
<span class="fa-link active" style="margin-left:6px">Personen</span>
|
||||
<span class="fa-link" style="margin-left:6px">Geschichten</span>
|
||||
<span class="fa-link" style="margin-left:6px">Chronik</span>
|
||||
<div class="fa-nav-r"><div class="fa-av">MR</div></div>
|
||||
</div>
|
||||
<div class="page-body">
|
||||
<div class="back-btn"><span class="back-arrow">←</span> Zurück</div>
|
||||
<div class="person-layout">
|
||||
|
||||
<!-- Left col -->
|
||||
<div>
|
||||
<!-- PersonCard -->
|
||||
<div class="card">
|
||||
<div style="display:flex;align-items:flex-start;justify-content:space-between;">
|
||||
<div>
|
||||
<div class="person-avatar">FR</div>
|
||||
<div class="person-name">Franz Raddatz</div>
|
||||
<div class="person-dates">* 14. März 1898 in Breslau † 7. Nov. 1952 in Hamburg</div>
|
||||
</div>
|
||||
<span style="font-size:6px;color:var(--color-text-muted);cursor:pointer">✏</span>
|
||||
</div>
|
||||
<div style="margin-top:6px;font-size:6px;color:var(--color-text-muted);">Briefschreiber, Gutsverwalter</div>
|
||||
</div>
|
||||
|
||||
<!-- NameHistoryCard -->
|
||||
<div class="card" style="margin-top:6px">
|
||||
<div class="card-h">Namensvarianten</div>
|
||||
<div>
|
||||
<span class="alias-tag">Franz</span>
|
||||
<span class="alias-tag">François</span>
|
||||
<span class="alias-tag">F. Raddatz</span>
|
||||
<span class="alias-tag">Franzl</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right col -->
|
||||
<div>
|
||||
|
||||
<!-- CoCorrespondents -->
|
||||
<div class="card">
|
||||
<div class="card-h">Briefpartner</div>
|
||||
<div class="row-item">
|
||||
<div class="row-av" style="background:#5A3080">EM</div>
|
||||
<span class="row-name">Emma Müller</span>
|
||||
<span class="count-chip">34 Briefe</span>
|
||||
</div>
|
||||
<div class="row-item">
|
||||
<div class="row-av" style="background:#2D7DD2">HK</div>
|
||||
<span class="row-name">Heinrich Kohl</span>
|
||||
<span class="count-chip">18 Briefe</span>
|
||||
</div>
|
||||
<div class="row-item">
|
||||
<div class="row-av" style="background:#3D8C4A">GR</div>
|
||||
<span class="row-name">Gertrud Raddatz</span>
|
||||
<span class="count-chip">11 Briefe</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Relationships -->
|
||||
<div class="card" style="margin-top:6px">
|
||||
<div class="card-h">Beziehungen</div>
|
||||
<div class="row-item">
|
||||
<div class="row-av" style="background:#5A3080">EM</div>
|
||||
<span class="row-name">Emma Müller</span>
|
||||
<span class="row-muted">Ehefrau</span>
|
||||
</div>
|
||||
<div class="row-item">
|
||||
<div class="row-av" style="background:#E8862A">KR</div>
|
||||
<span class="row-name">Klaus Raddatz</span>
|
||||
<span class="row-muted">Sohn</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Sent docs -->
|
||||
<div class="card" style="margin-top:6px">
|
||||
<div class="card-h">Verfasste Briefe <span style="font-weight:400;text-transform:none;letter-spacing:0">(47)</span></div>
|
||||
<div class="row-item"><span class="row-muted" style="width:55px;flex-shrink:0">12. Juli 1938</span><span class="row-name" style="font-size:6.5px">Brief an Emma Müller</span></div>
|
||||
<div class="row-item"><span class="row-muted" style="width:55px;flex-shrink:0">3. März 1937</span><span class="row-name" style="font-size:6.5px">Brief an Heinrich Kohl</span></div>
|
||||
<div class="row-item"><span class="row-muted" style="width:55px;flex-shrink:0">18. Okt. 1935</span><span class="row-name" style="font-size:6.5px">Brief an Gertrud Raddatz</span></div>
|
||||
<div style="font-size:6px;color:var(--color-text-muted);margin-top:4px;">+ 44 weitere anzeigen</div>
|
||||
</div>
|
||||
|
||||
<!-- Received docs -->
|
||||
<div class="card" style="margin-top:6px">
|
||||
<div class="card-h">Erhaltene Briefe <span style="font-weight:400;text-transform:none;letter-spacing:0">(23)</span></div>
|
||||
<div class="row-item"><span class="row-muted" style="width:55px;flex-shrink:0">14. Aug. 1938</span><span class="row-name" style="font-size:6.5px">Brief von Emma Müller</span></div>
|
||||
<div class="row-item"><span class="row-muted" style="width:55px;flex-shrink:0">7. Apr. 1936</span><span class="row-name" style="font-size:6.5px">Brief von Heinrich Kohl</span></div>
|
||||
</div>
|
||||
|
||||
<!-- ★ GESCHICHTEN KARTE — NEU ★ -->
|
||||
<div class="card" style="margin-top:6px;border-color:#C5C2F5;border-width:1px;">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:7px;">
|
||||
<div class="card-h" style="margin-bottom:0">Geschichten</div>
|
||||
<!-- BLOG_WRITER only: -->
|
||||
<span class="gesch-write-link">+ Geschichte schreiben</span>
|
||||
</div>
|
||||
|
||||
<div class="gesch-list">
|
||||
<!-- Row 1 -->
|
||||
<div class="gesch-row">
|
||||
<div class="gesch-meta">
|
||||
<div class="gesch-av" style="background:var(--navy)">MR</div>
|
||||
<div class="gesch-author">Maria Raddatz</div>
|
||||
<div class="gesch-date">14. März 2025</div>
|
||||
</div>
|
||||
<div class="gesch-content">
|
||||
<a class="gesch-title">Der Sommer in Breslau</a>
|
||||
<div class="gesch-excerpt">Oma erzählte oft vom letzten Sommer vor dem Krieg, als die Familie noch vollständig zusammen war…</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Row 2 -->
|
||||
<div class="gesch-row">
|
||||
<div class="gesch-meta">
|
||||
<div class="gesch-av" style="background:#5A3080">KR</div>
|
||||
<div class="gesch-author">Klaus Raddatz</div>
|
||||
<div class="gesch-date">2. Jan. 2025</div>
|
||||
</div>
|
||||
<div class="gesch-content">
|
||||
<a class="gesch-title">Wie Opa Franz den Hof rettete</a>
|
||||
<div class="gesch-excerpt">Es war ein kalter November, als der Notar anklopfte. Opa hatte nur wenige Stunden Zeit…</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Row 3 -->
|
||||
<div class="gesch-row">
|
||||
<div class="gesch-meta">
|
||||
<div class="gesch-av" style="background:#2D7DD2">GK</div>
|
||||
<div class="gesch-author">Gertrud Koch</div>
|
||||
<div class="gesch-date">18. Okt. 2024</div>
|
||||
</div>
|
||||
<div class="gesch-content">
|
||||
<a class="gesch-title">Die Hochzeit im Krieg</a>
|
||||
<div class="gesch-excerpt">1943, mitten im Chaos — Emma bestand darauf, dass das Fest stattfand…</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gesch-footer">
|
||||
<span class="gesch-all-link">Alle Geschichten zu Franz Raddatz →</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- ★ ENDE GESCHICHTEN KARTE ★ -->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="agent">
|
||||
<h4>P-1 · Implementation Reference</h4>
|
||||
<table class="at">
|
||||
<thead><tr><th>Element</th><th>Wert / Klasse</th><th>Hinweis</th></tr></thead>
|
||||
<tbody>
|
||||
<tr class="grp"><td colspan="3">Karten-Wrapper</td></tr>
|
||||
<tr><td>Geschichten-Karte</td><td><code>mt-6 bg-white shadow-sm border border-brand-sand rounded-sm p-6</code></td><td>Identisches Pattern wie alle anderen rechten Spalten-Karten</td></tr>
|
||||
<tr><td>Karten-Header</td><td><code>flex items-center justify-between mb-5</code></td><td></td></tr>
|
||||
<tr><td>Section-Heading</td><td><code>text-xs font-bold uppercase tracking-widest text-gray-400</code></td><td>Projekt-Standard, identisch mit anderen Karten</td></tr>
|
||||
<tr><td>"+ Geschichte schreiben"</td><td><code>text-[10px] text-turquoise font-semibold hover:underline</code></td><td>Link zu <code>/geschichten/new?personId={id}</code>, nur mit BLOG_WRITE</td></tr>
|
||||
<tr class="grp"><td colspan="3">Redaktionelle Liste (Stories)</td></tr>
|
||||
<tr><td>Listen-Wrapper</td><td><code>flex flex-col divide-y divide-brand-sand</code></td><td></td></tr>
|
||||
<tr><td>Story-Zeile</td><td><code>flex gap-3 py-3 first:pt-0 last:pb-0</code></td><td>min-h-[44px] auf Mobile (Touch-Target WCAG 2.2)</td></tr>
|
||||
<tr><td>Meta-Spalte</td><td><code>w-[72px] shrink-0 flex flex-col gap-0.5</code></td><td>Feste Breite, kein Overflow</td></tr>
|
||||
<tr><td>Autoren-Avatar</td><td><code>w-6 h-6 rounded-full text-[9px] font-bold text-white flex items-center justify-center</code></td><td><code>personAvatarColor(author.id)</code> aus existierendem Util</td></tr>
|
||||
<tr><td>Autorenname</td><td><code>font-sans text-[10px] font-semibold text-ink leading-tight</code></td><td></td></tr>
|
||||
<tr><td>Datum</td><td><code>font-sans text-[10px] text-ink-3</code></td><td><code>formatDate(publishedAt)</code></td></tr>
|
||||
<tr><td>Story-Titel (Link)</td><td><code>block font-serif text-[12px] text-ink hover:text-primary hover:underline leading-snug</code></td><td>Link zu <code>/geschichten/{id}</code></td></tr>
|
||||
<tr><td>Ausschnitt</td><td><code>font-sans text-[10px] text-ink-3 line-clamp-1 mt-0.5</code></td><td>Erste ~80 Zeichen des body-Texts (HTML-Tags gestripped)</td></tr>
|
||||
<tr class="grp"><td colspan="3">Karten-Footer</td></tr>
|
||||
<tr><td>"Alle Geschichten"-Link</td><td><code>mt-3 block font-sans text-[10px] text-ink-3 hover:text-primary</code></td><td>Nur wenn <code>geschichten.length >= 3</code>. Link: <code>/geschichten?personId={id}</code></td></tr>
|
||||
<tr class="grp"><td colspan="3">Konditionelles Rendering</td></tr>
|
||||
<tr><td>Karte sichtbar</td><td><code>{#if geschichten.length > 0}</code></td><td>Kein Leerszustand — Stille ist korrekt (US-BLOG-005)</td></tr>
|
||||
<tr><td>Max. angezeigte Geschichten</td><td>3 in der Karte</td><td>Server-seitig mit <code>&limit=3</code> limitieren</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Screen P-2: Mobile ── -->
|
||||
<div class="scr">
|
||||
<div class="scr-head">
|
||||
<h3>P-2 — Mobile · Geschichten-Karte gestapelt</h3>
|
||||
<span class="scr-id">P-2</span>
|
||||
</div>
|
||||
<p class="scr-desc">Auf Mobile kollabiert das 2-Spalten-Layout zu einer einzigen Spalte. Die Geschichten-Karte erscheint unter allen anderen Karten — genau in der Reihenfolge der Desktop-Ansicht von oben nach unten.</p>
|
||||
<p class="scr-var"><strong>Mobile · 320px</strong> — SingleColumn, vollbreite Karten. Jede Geschichten-Zeile hat min-h-[44px] als Touch-Target.</p>
|
||||
|
||||
<div class="previews">
|
||||
<div class="prev-col">
|
||||
<span class="bp-lbl">Mobile · 320px (nach unten gescrollt)</span>
|
||||
<div class="phone">
|
||||
<div class="pst"><b>9:41</b><span>●●●</span></div>
|
||||
<div class="pb">
|
||||
<div class="fa-nav">
|
||||
<span class="fa-logo">ARCHIV</span>
|
||||
<div class="fa-nav-r"><div class="fa-av">MR</div></div>
|
||||
</div>
|
||||
<div class="page-body" style="padding:10px;">
|
||||
<div class="back-btn"><span class="back-arrow">←</span> Personen</div>
|
||||
|
||||
<!-- Hint that there are cards above -->
|
||||
<div style="text-align:center;font-size:6px;color:var(--color-text-muted);padding:4px 0;margin-bottom:4px;border:1px dashed var(--color-border);border-radius:3px;">… PersonCard · Namensvarianten · Briefpartner · Beziehungen · Briefe …</div>
|
||||
|
||||
<!-- Geschichten Karte full width -->
|
||||
<div class="card" style="border-color:#C5C2F5;">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:7px;">
|
||||
<div class="card-h" style="margin-bottom:0">Geschichten</div>
|
||||
<span class="gesch-write-link" style="font-size:5.5px">+ Geschichte schreiben</span>
|
||||
</div>
|
||||
|
||||
<div class="gesch-list">
|
||||
<div class="gesch-row" style="min-height:44px;align-items:center;">
|
||||
<div class="gesch-meta">
|
||||
<div class="gesch-av" style="background:var(--navy)">MR</div>
|
||||
<div class="gesch-author">Maria Raddatz</div>
|
||||
<div class="gesch-date">14. März 2025</div>
|
||||
</div>
|
||||
<div class="gesch-content">
|
||||
<a class="gesch-title" style="font-size:8px">Der Sommer in Breslau</a>
|
||||
<div class="gesch-excerpt">Oma erzählte oft vom letzten Sommer vor dem Krieg…</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gesch-row" style="min-height:44px;align-items:center;">
|
||||
<div class="gesch-meta">
|
||||
<div class="gesch-av" style="background:#5A3080">KR</div>
|
||||
<div class="gesch-author">Klaus Raddatz</div>
|
||||
<div class="gesch-date">2. Jan. 2025</div>
|
||||
</div>
|
||||
<div class="gesch-content">
|
||||
<a class="gesch-title" style="font-size:8px">Wie Opa Franz den Hof rettete</a>
|
||||
<div class="gesch-excerpt">Es war ein kalter November, als der Notar anklopfte…</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gesch-row" style="min-height:44px;align-items:center;">
|
||||
<div class="gesch-meta">
|
||||
<div class="gesch-av" style="background:#2D7DD2">GK</div>
|
||||
<div class="gesch-author">Gertrud Koch</div>
|
||||
<div class="gesch-date">18. Okt. 2024</div>
|
||||
</div>
|
||||
<div class="gesch-content">
|
||||
<a class="gesch-title" style="font-size:8px">Die Hochzeit im Krieg</a>
|
||||
<div class="gesch-excerpt">1943, mitten im Chaos — Emma bestand darauf…</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="gesch-footer">
|
||||
<span class="gesch-all-link">Alle Geschichten zu Franz Raddatz →</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── Screen P-3: Stille ── -->
|
||||
<div class="scr">
|
||||
<div class="scr-head">
|
||||
<h3>P-3 — Keine Geschichten — Karte fehlt (Stille)</h3>
|
||||
<span class="scr-id">P-3</span>
|
||||
</div>
|
||||
<p class="scr-desc">Wenn keine veröffentlichten Geschichten diese Person erwähnen, erscheint die Karte nicht. Kein Leerszustand, keine Platzhalter — die Seite endet einfach bei den erhaltenen Briefen.</p>
|
||||
|
||||
<div class="silence-note">
|
||||
<div class="silence-icon">🔇</div>
|
||||
<p><strong>Stille ist korrekt</strong> — US-BLOG-005 schreibt explizit vor: <em>„Given a document has no published stories, when I view the document detail page, then no 'Geschichten' section is shown (no empty state needed — silence is correct)."</em> Das gilt analog für die Personenseite. Die Karte wird nur gerendert, wenn <code>geschichten.length > 0</code>. Keine leere Karte, kein „Noch keine Geschichten"-Text, kein Aufruf zum Schreiben an Lesende.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── LLM Implementation Guide ── -->
|
||||
<div class="llm">
|
||||
<h2>Implementation Guide — Personen-Entdeckung</h2>
|
||||
|
||||
<h3>1. Backend-Änderungen</h3>
|
||||
<table>
|
||||
<thead><tr><th>Endpunkt</th><th>Änderung</th><th>Hinweis</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>GET /api/geschichten?personId={id}&status=PUBLISHED&limit=3</code></td><td>Neuer Filter-Parameter</td><td>Nur PUBLISHED zurückgeben, auch ohne BLOG_WRITE. Limit serverseitig.</td></tr>
|
||||
<tr><td><code>GET /api/persons/{id}</code> Loader</td><td>Mitladen der Geschichten</td><td>Im <code>+page.server.ts</code> parallel zu sentDocuments laden.</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>2. Frontend-Änderungen</h3>
|
||||
<ul>
|
||||
<li><code>persons/[id]/+page.server.ts</code> — geschichten parallel laden, in pageData exponieren</li>
|
||||
<li><code>persons/[id]/+page.svelte</code> — <code>GeschichtenCard</code> am Ende der rechten Spalte rendern (<code>mt-6</code>), nur wenn <code>data.geschichten.length > 0</code></li>
|
||||
<li>Neues Shared-Component: <code>$lib/components/GeschichtenCard.svelte</code></li>
|
||||
</ul>
|
||||
|
||||
<h3>3. GeschichtenCard.svelte — Props</h3>
|
||||
<table>
|
||||
<thead><tr><th>Prop</th><th>Typ</th><th>Verwendung</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><code>geschichten</code></td><td><code>Geschichte[]</code></td><td>Die Geschichten-Liste (max. 3 vom Server)</td></tr>
|
||||
<tr><td><code>personId</code></td><td><code>string | undefined</code></td><td>Für "alle anzeigen"- und "schreiben"-Link</td></tr>
|
||||
<tr><td><code>canWrite</code></td><td><code>boolean</code></td><td>Steuert "+ Geschichte schreiben" Sichtbarkeit</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>4. Wiederverwendbare Utilities</h3>
|
||||
<ul>
|
||||
<li><code>personAvatarColor(id)</code> — bereits vorhanden in <code>$lib/utils/personFormat.ts</code></li>
|
||||
<li><code>formatDate(date)</code> — bereits vorhanden in <code>$lib/utils/date.ts</code></li>
|
||||
<li>Story-Ausschnitt: HTML-Tags aus <code>body</code> strippen, erste 80 Zeichen nehmen</li>
|
||||
</ul>
|
||||
|
||||
<h3>5. Barrierefreiheit</h3>
|
||||
<ul>
|
||||
<li>Alle Story-Links haben beschreibenden Text (Titel der Geschichte)</li>
|
||||
<li>Touch-Targets auf Mobile: <code>min-h-[44px]</code> je Zeile (WCAG 2.2)</li>
|
||||
<li>"+ Geschichte schreiben"-Link hat ausreichend Kontrast: Turquoise (#00C7B1) auf Weiß = 2.8:1 — für diesen kleinen UI-Hinweis akzeptabel, aber <strong>kein Body-Text</strong></li>
|
||||
<li>Strukturell: Die Karte ist eine <code><section></code> mit <code>aria-labelledby</code> auf die Überschrift</li>
|
||||
</ul>
|
||||
|
||||
<h3>6. Kein eigener Leerszustand</h3>
|
||||
<p>Die Komponente wird nur gerendert, wenn <code>geschichten.length > 0</code>. Keine leere Karte, kein Platzhaltertext, kein Aufruf zum Schreiben an Lesende ohne BLOG_WRITE-Berechtigung. Stille ist das korrekte Design für fehlende Geschichten.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
739
docs/specs/geschichten-reader-journey-spec.html
Normal file
739
docs/specs/geschichten-reader-journey-spec.html
Normal file
@@ -0,0 +1,739 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Geschichten — Reader Journey</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,300;9..144,400;9..144,500&family=DM+Sans:wght@300;400;500;600&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet"/>
|
||||
<style>
|
||||
:root{--color-page:#FAFAF7;--color-surface:#F5F4EE;--color-subtle:#EDECEA;--color-border:#D8D7D0;--color-text-muted:#6B6A63;--color-text:#1C1C18;--navy:#012851;--mint:#A1DCD8;--sand:#F0EFE9;--turquoise:#00C7B1;--accent-bg:rgba(161,220,216,.12);--blue-tint:#E6F1FB;--blue:#2D7DD2;--blue-dark:#185FA5;--purple-tint:#EEEDFE;--purple:#534AB7;--purple-dark:#3C3489;--green-tint:#E8F5EA;--green:#3D8C4A;--green-dark:#2E6E39;--orange-tint:#FEF0E6;--orange:#E8862A;--orange-dark:#B46820;--font-display:'Fraunces',Georgia,serif;--font-sans:'DM Sans',system-ui,sans-serif;--font-mono:'DM Mono',monospace;--radius-sm:4px;--radius-md:6px;--radius-lg:10px;--radius-xl:16px;--shadow-card:0 1px 3px rgba(28,28,24,.06),0 1px 2px rgba(28,28,24,.04);--shadow-raised:0 4px 12px rgba(28,28,24,.08),0 2px 4px rgba(28,28,24,.04);--shadow-overlay:0 8px 32px rgba(28,28,24,.12),0 2px 8px rgba(28,28,24,.06);}
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
|
||||
body{font-family:var(--font-sans);background:#E8E7E2;color:var(--color-text);font-size:14px;line-height:1.6;}
|
||||
.doc{max-width:1200px;margin:0 auto;padding:48px 40px 120px;}
|
||||
.doc-header{display:flex;justify-content:space-between;align-items:flex-end;padding-bottom:28px;border-bottom:1px solid var(--color-border);margin-bottom:48px;background:var(--color-page);margin:-48px -40px 48px;padding:48px 40px 28px;border-radius:var(--radius-xl) var(--radius-xl) 0 0;}
|
||||
.doc-header h1{font-family:var(--font-display);font-size:28px;font-weight:500;letter-spacing:-.02em;margin-bottom:4px;}
|
||||
.doc-header p{font-size:13px;color:var(--color-text-muted);max-width:680px;}
|
||||
.doc-meta{font-family:var(--font-mono);font-size:11px;color:var(--color-text-muted);text-align:right;line-height:1.9;}
|
||||
.pill{display:inline-block;padding:2px 8px;border-radius:var(--radius-sm);font-size:10px;font-weight:500;letter-spacing:.05em;}
|
||||
.pill-b{background:var(--blue-tint);color:var(--blue-dark);}
|
||||
.pill-g{background:var(--green-tint);color:var(--green-dark);}
|
||||
.section{margin-bottom:64px;}
|
||||
.section-title{font-size:10px;font-weight:500;letter-spacing:.12em;text-transform:uppercase;color:var(--color-text-muted);padding-bottom:10px;border-bottom:1px solid var(--color-border);margin-bottom:24px;}
|
||||
.prose{font-size:13px;color:var(--color-text-muted);line-height:1.65;max-width:720px;margin-bottom:20px;}
|
||||
.jh{padding:20px 24px;border-radius:var(--radius-xl);margin-bottom:40px;display:flex;align-items:center;gap:16px;}
|
||||
.jh .jn{font-family:var(--font-display);font-size:48px;font-weight:300;line-height:1;opacity:.5;}
|
||||
.jh h2{font-family:var(--font-display);font-size:22px;font-weight:500;letter-spacing:-.02em;margin-bottom:4px;}
|
||||
.jh p{font-size:13px;line-height:1.5;}.jh .fl{font-family:var(--font-mono);font-size:11px;margin-top:6px;opacity:.7;}
|
||||
.jh-b{background:var(--blue-tint);border:1px solid #A4CFF4;}.jh-b .jn{color:var(--blue);}.jh-b p,.jh-b .fl{color:var(--blue-dark);}
|
||||
.scr{margin-bottom:56px;}
|
||||
.scr-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;}
|
||||
.scr-head h3{font-family:var(--font-display);font-size:20px;font-weight:500;letter-spacing:-.02em;}
|
||||
.scr-id{font-family:var(--font-mono);font-size:11px;color:var(--color-text-muted);padding:2px 8px;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-page);}
|
||||
.scr-desc{font-size:12px;color:var(--color-text-muted);line-height:1.6;max-width:720px;margin-bottom:6px;}
|
||||
.scr-var{font-size:11px;color:var(--color-text-muted);margin-bottom:20px;}.scr-var strong{color:var(--color-text);}
|
||||
.previews{display:flex;gap:32px;flex-wrap:wrap;justify-content:center;align-items:flex-start;margin-bottom:20px;}
|
||||
.prev-col{display:flex;flex-direction:column;align-items:center;gap:10px;}
|
||||
.bp-lbl{font-family:var(--font-mono);font-size:10px;color:var(--color-text-muted);}
|
||||
.desk{width:100%;max-width:1040px;background:var(--color-page);border-radius:var(--radius-xl);overflow:hidden;box-shadow:var(--shadow-overlay),0 0 0 1px rgba(0,0,0,.06);display:flex;flex-direction:column;min-height:520px;}
|
||||
.phone{width:320px;flex-shrink:0;background:var(--color-page);border-radius:36px;overflow:hidden;box-shadow:var(--shadow-overlay),0 0 0 1px rgba(0,0,0,.07);display:flex;flex-direction:column;border:6px solid #1C1C18;}
|
||||
.pst{padding:10px 20px 0;display:flex;justify-content:space-between;align-items:center;font-size:12px;background:var(--color-page);}.pst b{font-weight:600;}.pst span{font-size:10px;}
|
||||
.pb{flex:1;overflow-y:auto;display:flex;flex-direction:column;}
|
||||
.fa-nav{height:32px;background:var(--navy);display:flex;align-items:center;padding:0 12px;gap:8px;flex-shrink:0;}
|
||||
.fa-logo{font-size:7px;font-weight:900;color:#fff;letter-spacing:.8px;border-bottom:2px solid var(--mint);padding-bottom:1px;}
|
||||
.fa-link{font-size:5.5px;color:rgba(255,255,255,.4);font-weight:700;text-transform:uppercase;letter-spacing:.05em;}
|
||||
.fa-link.active{color:var(--mint);border-bottom:1px solid var(--mint);}
|
||||
.fa-nav-r{margin-left:auto;display:flex;gap:5px;align-items:center;}
|
||||
.fa-av{width:16px;height:16px;background:rgba(255,255,255,.1);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:5px;font-weight:800;color:rgba(255,255,255,.5);}
|
||||
.page-body{padding:16px;background:#E8E7E2;flex:1;}
|
||||
|
||||
/* ── impl-ref (agent) table ── */
|
||||
.agent{background:var(--color-text);color:#E8E8E2;padding:24px;border-radius:var(--radius-lg);margin-top:20px;}
|
||||
.agent h4{font-size:9px;font-weight:500;letter-spacing:.1em;text-transform:uppercase;color:#5A5A55;margin-bottom:12px;}
|
||||
.agent pre{font-family:var(--font-mono);font-size:10px;color:#444440;margin-bottom:16px;line-height:1.8;white-space:pre-wrap;}
|
||||
.at{width:100%;border-collapse:collapse;font-family:var(--font-mono);font-size:10px;}
|
||||
.at thead tr{border-bottom:1px solid #2A2A26;}
|
||||
.at th{text-align:left;padding:6px 10px;font-size:8px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:#5A5A55;font-family:var(--font-sans);}
|
||||
.at td{padding:5px 10px;border-bottom:1px solid #1E1E1A;vertical-align:top;line-height:1.5;}
|
||||
.at tr:last-child td{border-bottom:none;}
|
||||
.at td:first-child{color:#7A7A72;}
|
||||
.at td:nth-child(2){color:#E8E8E2;font-weight:500;}
|
||||
.at td:nth-child(3){color:#5A5A55;}
|
||||
.at .grp td{padding-top:14px;font-family:var(--font-sans);font-size:8px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:#3A3A36;}
|
||||
|
||||
/* ── LLM guide ── */
|
||||
.llm{background:var(--color-page);border:2px solid var(--navy);border-radius:var(--radius-xl);padding:32px 40px;margin-top:64px;}
|
||||
.llm h2{font-family:var(--font-display);font-size:22px;font-weight:500;letter-spacing:-.02em;margin-bottom:8px;color:var(--navy);}
|
||||
.llm h3{font-size:14px;font-weight:600;margin:20px 0 8px;color:var(--color-text);}
|
||||
.llm h4{font-size:12px;font-weight:600;margin:14px 0 6px;color:var(--color-text-muted);}
|
||||
.llm p,.llm li{font-size:13px;color:var(--color-text-muted);line-height:1.65;}
|
||||
.llm ul,.llm ol{padding-left:20px;margin-bottom:12px;}
|
||||
.llm li{margin-bottom:4px;}
|
||||
.llm code{font-family:var(--font-mono);font-size:11px;background:var(--color-surface);padding:1px 5px;border-radius:3px;}
|
||||
.llm table{width:100%;border-collapse:collapse;margin:12px 0;font-size:12px;}
|
||||
.llm th,.llm td{text-align:left;padding:6px 10px;border-bottom:1px solid var(--color-border);}
|
||||
.llm th{font-weight:500;color:var(--color-text);font-size:11px;text-transform:uppercase;letter-spacing:.05em;}
|
||||
.llm td{color:var(--color-text-muted);}
|
||||
|
||||
/* ── Mockup-specific styles ── */
|
||||
|
||||
/* Editorial list */
|
||||
.g-list-card{background:#fff;border:1px solid #E4E2D7;border-radius:4px;box-shadow:0 1px 3px rgba(28,28,24,.06),0 1px 2px rgba(28,28,24,.04);overflow:hidden;}
|
||||
.g-row{display:flex;gap:0;border-bottom:1px solid #F0EFE9;cursor:pointer;}
|
||||
.g-row:last-child{border-bottom:none;}
|
||||
.g-row:hover{background:#FAFAF7;}
|
||||
.g-meta{width:88px;flex-shrink:0;padding:10px 10px 10px 12px;display:flex;flex-direction:column;gap:3px;border-right:1px solid #F0EFE9;}
|
||||
.g-content{padding:10px 14px 10px 12px;flex:1;min-width:0;}
|
||||
.g-av{width:22px;height:22px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:7px;font-weight:800;color:#fff;flex-shrink:0;margin-bottom:3px;}
|
||||
.av-navy{background:#012851;}
|
||||
.av-purple{background:#534AB7;}
|
||||
.av-blue{background:#2D7DD2;}
|
||||
.av-teal{background:#0E9488;}
|
||||
.g-author{font-size:7px;font-weight:700;color:#1C1C18;line-height:1.3;}
|
||||
.g-date{font-size:6.5px;color:#6B6A63;line-height:1.3;}
|
||||
.g-chip{display:inline-flex;align-items:center;gap:2px;padding:1px 5px;background:#F5F4EE;border:1px solid #D8D7D0;border-radius:10px;font-size:6px;font-weight:500;color:#1C1C18;margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:76px;}
|
||||
.g-title{font-family:Georgia,serif;font-size:11px;color:#012851;line-height:1.4;margin-bottom:2px;}
|
||||
.g-title:hover{color:#185FA5;}
|
||||
.g-excerpt{font-size:7.5px;color:#6B6A63;line-height:1.55;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}
|
||||
|
||||
/* Filter pills */
|
||||
.g-filters{display:flex;gap:5px;align-items:center;padding:8px 12px;background:var(--color-page);border-bottom:1px solid #EDECEA;flex-wrap:wrap;}
|
||||
.g-pill{display:inline-flex;align-items:center;padding:2px 8px;border-radius:10px;font-size:6.5px;font-weight:700;border:1px solid #D8D7D0;color:#6B6A63;background:transparent;cursor:pointer;}
|
||||
.g-pill.active{background:#012851;color:#fff;border-color:#012851;}
|
||||
.g-pill.add{border-style:dashed;color:#6B6A63;}
|
||||
|
||||
/* Page header (desktop) */
|
||||
.g-page-hdr{display:flex;justify-content:space-between;align-items:center;padding:10px 14px 6px;}
|
||||
.g-page-title{font-family:Georgia,serif;font-size:16px;font-weight:400;color:#012851;}
|
||||
.g-new-btn{font-size:7px;font-weight:700;padding:4px 10px;border-radius:3px;background:#012851;color:#fff;border:none;display:flex;align-items:center;gap:3px;}
|
||||
|
||||
/* Empty state */
|
||||
.g-empty{padding:20px;text-align:center;font-family:Georgia,serif;font-size:9px;color:#6B6A63;font-style:italic;}
|
||||
|
||||
/* ── Story Detail mockup ── */
|
||||
.g-detail-body{padding:16px 20px;background:#E8E7E2;flex:1;}
|
||||
.g-article{background:var(--color-page);border-radius:6px;padding:16px 20px;max-width:640px;margin:0 auto;}
|
||||
.g-back{font-size:7px;color:#6B6A63;margin-bottom:10px;display:flex;align-items:center;gap:2px;}
|
||||
.g-story-title{font-family:Georgia,serif;font-size:18px;font-weight:400;color:#012851;line-height:1.3;margin-bottom:8px;}
|
||||
.g-metabar{display:flex;align-items:center;gap:6px;padding-bottom:8px;border-bottom:1px solid #EDECEA;margin-bottom:10px;}
|
||||
.g-metabar-r{margin-left:auto;display:flex;align-items:center;gap:6px;}
|
||||
.g-edit-btn{font-size:6.5px;font-weight:600;padding:2px 7px;border:1px solid #D8D7D0;border-radius:3px;color:#1C1C18;background:transparent;}
|
||||
.g-del-link{font-size:6.5px;font-weight:600;color:#DC4C3E;}
|
||||
.g-body-text{font-family:Georgia,serif;font-size:9px;line-height:1.75;color:#1C1C18;margin-bottom:12px;}
|
||||
.g-body-text p{margin-bottom:7px;}
|
||||
.g-sub-hdr{font-size:7px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:#6B6A63;margin-bottom:6px;}
|
||||
.g-persons-row{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:12px;}
|
||||
.g-person-chip{display:inline-flex;align-items:center;gap:4px;padding:3px 8px 3px 4px;border:1px solid #D8D7D0;border-radius:12px;background:#F5F4EE;font-size:7.5px;font-weight:500;color:#1C1C18;}
|
||||
.g-person-chip .av{width:14px;height:14px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:5.5px;font-weight:800;color:#fff;flex-shrink:0;}
|
||||
.g-doc-cards{display:flex;flex-direction:column;gap:5px;}
|
||||
.g-doc-card{display:flex;align-items:flex-start;gap:7px;padding:7px 9px;background:#fff;border:1px solid #E4E2D7;border-radius:4px;}
|
||||
.g-doc-icon{width:22px;height:22px;background:#F0EFE9;border-radius:3px;display:flex;align-items:center;justify-content:center;flex-shrink:0;}
|
||||
.g-doc-info{min-width:0;flex:1;}
|
||||
.g-doc-title{font-size:8px;font-weight:600;color:#012851;line-height:1.3;margin-bottom:1px;}
|
||||
.g-doc-meta{font-size:6.5px;color:#6B6A63;}
|
||||
|
||||
/* Mobile nav */
|
||||
.m-nav{height:26px;background:var(--navy);display:flex;align-items:center;padding:0 10px;gap:6px;flex-shrink:0;}
|
||||
.m-logo{font-size:6px;font-weight:900;color:#fff;letter-spacing:.7px;border-bottom:1.5px solid var(--mint);padding-bottom:1px;}
|
||||
.m-nav-r{margin-left:auto;display:flex;gap:4px;align-items:center;}
|
||||
.m-av{width:14px;height:14px;background:rgba(255,255,255,.1);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:4.5px;font-weight:800;color:rgba(255,255,255,.5);}
|
||||
.m-ham{display:flex;flex-direction:column;gap:2px;width:12px;}
|
||||
.m-ham span{height:1.5px;background:rgba(255,255,255,.6);border-radius:1px;}
|
||||
|
||||
/* Mobile filter strip */
|
||||
.m-filters{display:flex;gap:4px;align-items:center;padding:6px 10px;background:var(--color-page);border-bottom:1px solid #EDECEA;overflow-x:auto;flex-wrap:nowrap;}
|
||||
.m-filters::-webkit-scrollbar{display:none;}
|
||||
|
||||
/* Mobile row */
|
||||
.m-row{padding:9px 10px;border-bottom:1px solid #F0EFE9;background:#fff;cursor:pointer;}
|
||||
.m-row:hover{background:#FAFAF7;}
|
||||
.m-row-top{display:flex;align-items:center;gap:5px;margin-bottom:4px;}
|
||||
.m-author-name{font-size:7px;font-weight:700;color:#1C1C18;}
|
||||
.m-date{font-size:6.5px;color:#6B6A63;margin-left:auto;}
|
||||
.m-title{font-family:Georgia,serif;font-size:10px;color:#012851;line-height:1.4;margin-bottom:2px;}
|
||||
.m-excerpt{font-size:7px;color:#6B6A63;line-height:1.5;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;}
|
||||
|
||||
/* Mobile detail */
|
||||
.m-detail-body{padding:10px;background:#E8E7E2;flex:1;}
|
||||
.m-article{background:#fff;border-radius:6px;padding:12px 12px 16px;}
|
||||
.m-back{font-size:7px;color:#6B6A63;margin-bottom:8px;display:flex;align-items:center;gap:2px;}
|
||||
.m-story-title{font-family:Georgia,serif;font-size:14px;font-weight:400;color:#012851;line-height:1.3;margin-bottom:6px;}
|
||||
.m-metabar{display:flex;align-items:center;gap:5px;padding-bottom:6px;border-bottom:1px solid #EDECEA;margin-bottom:8px;}
|
||||
.m-body-text{font-family:Georgia,serif;font-size:8.5px;line-height:1.7;color:#1C1C18;margin-bottom:10px;}
|
||||
.m-body-text p{margin-bottom:5px;}
|
||||
.m-sub-hdr{font-size:6.5px;font-weight:700;text-transform:uppercase;letter-spacing:.08em;color:#6B6A63;margin-bottom:5px;}
|
||||
.m-persons-row{display:flex;flex-wrap:wrap;gap:3px;margin-bottom:10px;}
|
||||
.m-person-chip{display:inline-flex;align-items:center;gap:3px;padding:2px 6px 2px 3px;border:1px solid #D8D7D0;border-radius:10px;background:#F5F4EE;font-size:7px;font-weight:500;color:#1C1C18;}
|
||||
.m-doc-card{display:flex;align-items:flex-start;gap:5px;padding:6px 7px;background:#F5F4EE;border:1px solid #E4E2D7;border-radius:4px;margin-bottom:4px;}
|
||||
.m-doc-icon{width:18px;height:18px;background:#E4E2D7;border-radius:3px;display:flex;align-items:center;justify-content:center;flex-shrink:0;}
|
||||
.m-doc-title{font-size:7.5px;font-weight:600;color:#012851;margin-bottom:1px;}
|
||||
.m-doc-meta{font-size:6px;color:#6B6A63;}
|
||||
.m-three-dot{font-size:11px;color:#6B6A63;line-height:1;}
|
||||
|
||||
@media(max-width:900px){.doc{padding:24px 16px 80px;}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="doc">
|
||||
|
||||
<!-- ═══ DOC HEADER ═══ -->
|
||||
<div class="doc-header">
|
||||
<div>
|
||||
<h1>Geschichten — Reader Journey</h1>
|
||||
<p>Übersichtsseite <code>/geschichten</code> und Story-Detailansicht <code>/geschichten/[id]</code>. Leserinnen entdecken veröffentlichte Familiengeschichten, filtern nach Personen und lesen die vollständige Geschichte mit verlinkten Dokumenten und Personen.</p>
|
||||
</div>
|
||||
<div class="doc-meta">
|
||||
Familienarchiv<br/>
|
||||
<span class="pill pill-b">Final Spec</span><br/>
|
||||
2026-05-02 · @leonievoss
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ JOURNEY HEADER ═══ -->
|
||||
<div class="jh jh-b">
|
||||
<div class="jn">R</div>
|
||||
<div>
|
||||
<h2>Reader Journey</h2>
|
||||
<p>Alle eingeloggten Familienmitglieder können veröffentlichte Geschichten lesen und nach historischen Personen filtern.</p>
|
||||
<div class="fl">/geschichten · /geschichten/[id]</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ═══ SECTION 1: KONZEPT ═══ -->
|
||||
<div class="section">
|
||||
<div class="section-title">Konzept</div>
|
||||
<p class="prose">Die Reader Journey umfasst zwei Seiten: die editoriale Übersichtsseite <code>/geschichten</code> und die Story-Detailansicht <code>/geschichten/[id]</code>. Beide Seiten sind für alle angemeldeten Familienmitglieder zugänglich, unabhängig von Schreibrechten.</p>
|
||||
<p class="prose">Die Übersichtsseite zeigt alle veröffentlichten Geschichten als editoriale Liste mit vollbreiten Zeilen. Links steht die Metaspalte (Autorin, Datum, verknüpfte Person), rechts Titel und Vorschautext. Eine Personenfilter-Pill-Leiste oberhalb der Liste schränkt die Anzeige auf Geschichten ein, die eine bestimmte historische Person erwähnen.</p>
|
||||
<p class="prose">Die Detailansicht zeigt die vollständige Geschichte als zentrierten Artikeltext (max-w-3xl) mit Autorinnenzeile, Fließtext (HTML aus dem Backend), einem Abschnitt mit verlinkten Personenchips und Dokumentreferenzkarten, die direkt zu den Originalbriefen und Dokumenten verlinken. Autorinnen mit dem Recht <code>BLOG_WRITE</code> sehen zusätzlich Bearbeiten- und Löschen-Aktionen.</p>
|
||||
</div>
|
||||
|
||||
<!-- ═══ SCREEN R-1: INDEX ═══ -->
|
||||
<div class="section">
|
||||
<div class="section-title">Screens — Übersichtsseite</div>
|
||||
|
||||
<div class="scr">
|
||||
<div class="scr-head">
|
||||
<h3>R-1 — Übersicht /geschichten</h3>
|
||||
<span class="scr-id">US-BLOG-004 · R-1</span>
|
||||
</div>
|
||||
<p class="scr-desc">Editoriale Listendarstellung aller veröffentlichten Geschichten. Jede Zeile: linke Metaspalte (Avatar, Autorin, Datum, Personenchip), rechte Inhaltsspalte (Serientitel, Vorschautext). Personenfilter-Pills oberhalb der Liste. Schaltfläche „+ Neue Geschichte" rechts oben nur für BLOG_WRITERs sichtbar.</p>
|
||||
<p class="scr-var"><strong>Varianten:</strong> Kein Filter aktiv (alle Geschichten) · Person-Filter aktiv (gefilterte Liste) · Leerer Zustand (Filter aktiv, keine Ergebnisse)</p>
|
||||
|
||||
<div class="previews">
|
||||
<!-- DESKTOP -->
|
||||
<div class="prev-col" style="width:100%;max-width:1040px;">
|
||||
<span class="bp-lbl">Desktop — 1040px</span>
|
||||
<div class="desk">
|
||||
<!-- Nav -->
|
||||
<div class="fa-nav">
|
||||
<span class="fa-logo">ARCHIV</span>
|
||||
<span style="width:1px;height:14px;background:rgba(255,255,255,.1);margin:0 2px;"></span>
|
||||
<span class="fa-link">Dokumente</span>
|
||||
<span class="fa-link">Personen</span>
|
||||
<span class="fa-link active">Geschichten</span>
|
||||
<span class="fa-link">Chronik</span>
|
||||
<div class="fa-nav-r">
|
||||
<div class="fa-av" style="background:#012851;color:var(--mint);font-size:5px;font-weight:800;">MR</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Page body -->
|
||||
<div style="background:#E8E7E2;flex:1;padding:14px 16px;">
|
||||
|
||||
<!-- Page header -->
|
||||
<div class="g-page-hdr" style="padding:0 0 8px;">
|
||||
<span class="g-page-title">Geschichten</span>
|
||||
<button class="g-new-btn">
|
||||
<svg width="7" height="7" viewBox="0 0 10 10" fill="none"><path d="M5 1v8M1 5h8" stroke="#fff" stroke-width="2" stroke-linecap="round"/></svg>
|
||||
Neue Geschichte
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- List card -->
|
||||
<div class="g-list-card">
|
||||
|
||||
<!-- Filter pills -->
|
||||
<div class="g-filters">
|
||||
<span class="g-pill active">Alle</span>
|
||||
<span class="g-pill">Franz Raddatz</span>
|
||||
<span class="g-pill">Emma Müller</span>
|
||||
<span class="g-pill">Heinrich Kohl</span>
|
||||
<span class="g-pill add">+ Person wählen</span>
|
||||
</div>
|
||||
|
||||
<!-- Row 1 -->
|
||||
<div class="g-row">
|
||||
<div class="g-meta">
|
||||
<div class="g-av av-navy">MR</div>
|
||||
<div class="g-author">Maria Raddatz</div>
|
||||
<div class="g-date">14. März 2025</div>
|
||||
<span class="g-chip">
|
||||
<span style="width:8px;height:8px;border-radius:50%;background:#012851;display:inline-flex;align-items:center;justify-content:center;font-size:4.5px;font-weight:800;color:var(--mint);flex-shrink:0;">FR</span>
|
||||
Franz Raddatz
|
||||
</span>
|
||||
</div>
|
||||
<div class="g-content">
|
||||
<div class="g-title">Der Sommer in Breslau</div>
|
||||
<div class="g-excerpt">Oma erzählte oft vom letzten Sommer vor dem Krieg, als die Familie noch vollständig zusammen war und niemand ahnte, was kommen würde. Es waren die letzten unbeschwerten Monate auf dem Gut…</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Row 2 -->
|
||||
<div class="g-row">
|
||||
<div class="g-meta">
|
||||
<div class="g-av av-purple">KR</div>
|
||||
<div class="g-author">Klaus Raddatz</div>
|
||||
<div class="g-date">2. Jan. 2025</div>
|
||||
<span class="g-chip">
|
||||
<span style="width:8px;height:8px;border-radius:50%;background:#012851;display:inline-flex;align-items:center;justify-content:center;font-size:4.5px;font-weight:800;color:var(--mint);flex-shrink:0;">FR</span>
|
||||
Franz Raddatz
|
||||
</span>
|
||||
</div>
|
||||
<div class="g-content">
|
||||
<div class="g-title">Wie Opa Franz den Hof rettete</div>
|
||||
<div class="g-excerpt">Es war ein kalter November, als der Notar anklopfte. Opa hatte nur wenige Stunden Zeit, alle Unterlagen zusammenzusuchen. Die Briefe aus dieser Zeit zeugen von einem Mann, der unter enormem Druck standhaft blieb.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Row 3 -->
|
||||
<div class="g-row">
|
||||
<div class="g-meta">
|
||||
<div class="g-av av-blue">GK</div>
|
||||
<div class="g-author">Gertrud Koch</div>
|
||||
<div class="g-date">18. Okt. 2024</div>
|
||||
<span class="g-chip">
|
||||
<span style="width:8px;height:8px;border-radius:50%;background:#534AB7;display:inline-flex;align-items:center;justify-content:center;font-size:4.5px;font-weight:800;color:#fff;flex-shrink:0;">EM</span>
|
||||
Emma Müller
|
||||
</span>
|
||||
</div>
|
||||
<div class="g-content">
|
||||
<div class="g-title">Die Hochzeit im Krieg</div>
|
||||
<div class="g-excerpt">1943, mitten im Chaos — Emma bestand darauf, dass das Fest stattfand. Sie hatte das Brautkleid seit Jahren aufgehoben. Ihr Bruder kam auf Fronturlaub, drei Tage nur, aber es reichte für die Feier.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Row 4 -->
|
||||
<div class="g-row">
|
||||
<div class="g-meta">
|
||||
<div class="g-av av-teal">HR</div>
|
||||
<div class="g-author">Hans Raddatz</div>
|
||||
<div class="g-date">5. Sept. 2024</div>
|
||||
</div>
|
||||
<div class="g-content">
|
||||
<div class="g-title">Erinnerungen an Weihnachten 1935</div>
|
||||
<div class="g-excerpt">Der Geruch von Tannenzweigen und Kerzenwachs — so beschrieb Opa immer den Heiligabend auf dem Gut. Die Kinder durften erst nach dem Abendessen ins Zimmer, und das Warten war jedes Jahr das Schwerste.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /list-card -->
|
||||
</div><!-- /page-body -->
|
||||
</div><!-- /desk -->
|
||||
</div>
|
||||
|
||||
<!-- MOBILE -->
|
||||
<div class="prev-col">
|
||||
<span class="bp-lbl">Mobile — 320px</span>
|
||||
<div class="phone">
|
||||
<div class="pst"><b>9:41</b><span>●●●</span></div>
|
||||
<div class="pb">
|
||||
<div class="m-nav">
|
||||
<span class="m-logo">ARCHIV</span>
|
||||
<div class="m-nav-r">
|
||||
<div class="m-av">MR</div>
|
||||
<div class="m-ham"><span></span><span></span><span></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="background:#E8E7E2;flex:1;display:flex;flex-direction:column;">
|
||||
<!-- page title mobile -->
|
||||
<div style="padding:8px 10px 4px;background:#E8E7E2;">
|
||||
<span style="font-family:Georgia,serif;font-size:13px;color:#012851;font-weight:400;">Geschichten</span>
|
||||
</div>
|
||||
|
||||
<!-- Filter strip -->
|
||||
<div class="m-filters">
|
||||
<span class="g-pill active" style="font-size:6px;padding:2px 7px;">Alle</span>
|
||||
<span class="g-pill" style="font-size:6px;padding:2px 7px;">Franz Raddatz</span>
|
||||
<span class="g-pill" style="font-size:6px;padding:2px 7px;">Emma Müller</span>
|
||||
<span class="g-pill add" style="font-size:6px;padding:2px 7px;flex-shrink:0;">+ Person…</span>
|
||||
</div>
|
||||
|
||||
<!-- List -->
|
||||
<div style="background:#fff;flex:1;">
|
||||
<!-- Mobile row 1 -->
|
||||
<div class="m-row">
|
||||
<div class="m-row-top">
|
||||
<div class="g-av av-navy" style="width:16px;height:16px;font-size:5.5px;">MR</div>
|
||||
<span class="m-author-name">Maria Raddatz</span>
|
||||
<span class="m-date">14. Mrz. 2025</span>
|
||||
</div>
|
||||
<div class="m-title">Der Sommer in Breslau</div>
|
||||
<div class="m-excerpt">Oma erzählte oft vom letzten Sommer vor dem Krieg, als die Familie noch vollständig zusammen war…</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile row 2 -->
|
||||
<div class="m-row">
|
||||
<div class="m-row-top">
|
||||
<div class="g-av av-purple" style="width:16px;height:16px;font-size:5.5px;">KR</div>
|
||||
<span class="m-author-name">Klaus Raddatz</span>
|
||||
<span class="m-date">2. Jan. 2025</span>
|
||||
</div>
|
||||
<div class="m-title">Wie Opa Franz den Hof rettete</div>
|
||||
<div class="m-excerpt">Es war ein kalter November, als der Notar anklopfte. Opa hatte nur wenige Stunden Zeit, alle Unterlagen zusammenzusuchen…</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile row 3 -->
|
||||
<div class="m-row">
|
||||
<div class="m-row-top">
|
||||
<div class="g-av av-blue" style="width:16px;height:16px;font-size:5.5px;">GK</div>
|
||||
<span class="m-author-name">Gertrud Koch</span>
|
||||
<span class="m-date">18. Okt. 2024</span>
|
||||
</div>
|
||||
<div class="m-title">Die Hochzeit im Krieg</div>
|
||||
<div class="m-excerpt">1943, mitten im Chaos — Emma bestand darauf, dass das Fest stattfand. Sie hatte das Brautkleid seit Jahren aufgehoben…</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- EMPTY STATE inset -->
|
||||
<div class="prev-col" style="width:100%;max-width:400px;">
|
||||
<span class="bp-lbl">Leerer Zustand — Filter aktiv, keine Ergebnisse</span>
|
||||
<div style="background:#fff;border:1px solid #E4E2D7;border-radius:6px;overflow:hidden;box-shadow:var(--shadow-card);">
|
||||
<div class="g-filters">
|
||||
<span class="g-pill">Alle</span>
|
||||
<span class="g-pill active">Franz Raddatz</span>
|
||||
<span class="g-pill">Emma Müller</span>
|
||||
</div>
|
||||
<div class="g-empty">Keine Geschichten für Franz Raddatz gefunden.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /previews -->
|
||||
|
||||
<!-- impl-ref R-1 -->
|
||||
<div class="agent">
|
||||
<h4>impl-ref — R-1 Übersicht /geschichten</h4>
|
||||
<table class="at">
|
||||
<thead><tr><th>Element</th><th>Wert</th><th>Hinweise</th></tr></thead>
|
||||
<tbody>
|
||||
<tr class="grp"><td colspan="3">Seitenstruktur</td></tr>
|
||||
<tr><td>Page title</td><td>font-family:var(--font-display);font-size:24px;color:var(--navy)</td><td>Fraunces, nicht fett</td></tr>
|
||||
<tr><td>Editorial list card</td><td>bg-white shadow-sm border border-brand-sand rounded-sm</td><td>wraps alle Zeilen</td></tr>
|
||||
<tr class="grp"><td colspan="3">Listenzeile</td></tr>
|
||||
<tr><td>List row</td><td>flex gap-0 border-b border-brand-sand last:border-0 hover:bg-surface</td><td>min-h-[44px] auf Mobile</td></tr>
|
||||
<tr><td>Meta column</td><td>w-[88px] shrink-0 flex flex-col gap-1 p-3 border-r border-brand-sand</td><td>feste Breite</td></tr>
|
||||
<tr><td>Author avatar</td><td>w-7 h-7 rounded-full text-[9px] font-bold text-white flex items-center justify-center</td><td>personAvatarColor(userId)</td></tr>
|
||||
<tr><td>Author name</td><td>font-sans text-xs font-semibold text-ink</td><td></td></tr>
|
||||
<tr><td>Date</td><td>font-sans text-xs text-ink-3</td><td>formatDate(publishedAt)</td></tr>
|
||||
<tr><td>Person chip</td><td>inline-flex items-center gap-1 rounded-full bg-surface border border-line px-2 py-0.5 text-[10px] font-medium text-ink</td><td>links zu /persons/[id]; optional</td></tr>
|
||||
<tr><td>Story title</td><td>font-serif text-[15px] text-ink leading-snug mb-1 hover:text-primary</td><td>link zu /geschichten/[id]</td></tr>
|
||||
<tr><td>Excerpt</td><td>font-sans text-xs text-ink-3 line-clamp-2</td><td>max. 150 Zeichen aus body</td></tr>
|
||||
<tr class="grp"><td colspan="3">Filter</td></tr>
|
||||
<tr><td>Filter pill (inaktiv)</td><td>rounded-full border border-line px-3 py-1 text-xs font-semibold text-ink-2 hover:bg-muted</td><td>aria-pressed="false"</td></tr>
|
||||
<tr><td>Filter pill (aktiv)</td><td>rounded-full bg-primary text-primary-fg px-3 py-1 text-xs font-semibold</td><td>aria-pressed="true"</td></tr>
|
||||
<tr><td>Person wählen</td><td>border-dashed text-ink-3</td><td>öffnet Personen-Typeahead</td></tr>
|
||||
<tr class="grp"><td colspan="3">Aktionen & Barrierefreiheit</td></tr>
|
||||
<tr><td>Neue Geschichte btn</td><td>rounded border border-primary bg-primary text-primary-fg px-3 py-1.5 text-sm font-medium</td><td>nur BLOG_WRITE; auf Mobile ausgeblendet → Hamburger-Menü</td></tr>
|
||||
<tr><td>Touch target (Mobile)</td><td>min-h-[44px] auf jeder Zeile</td><td>WCAG 2.2 AA</td></tr>
|
||||
<tr><td>Leerer Zustand</td><td>py-12 text-center font-serif text-sm text-ink-3 italic</td><td>Zeigt Filtername im Text</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div><!-- /scr R-1 -->
|
||||
</div><!-- /section -->
|
||||
|
||||
<!-- ═══ SCREEN R-2: STORY DETAIL ═══ -->
|
||||
<div class="section">
|
||||
<div class="section-title">Screens — Story-Detailansicht</div>
|
||||
|
||||
<div class="scr">
|
||||
<div class="scr-head">
|
||||
<h3>R-2 — Story-Detail /geschichten/[id]</h3>
|
||||
<span class="scr-id">US-BLOG-004 · R-2</span>
|
||||
</div>
|
||||
<p class="scr-desc">Vollständige Leseansicht einer Geschichte. Zentrierter Artikel-Container (max-w-3xl). Zurück-Schaltfläche, großer Serientitel, Metazeile mit Avatar und Datum. Fließtext aus dem Backend (HTML). Personenchips-Sektion. Dokumentreferenzkarten. Autorinnen mit BLOG_WRITE sehen Bearbeiten/Löschen-Aktionen.</p>
|
||||
<p class="scr-var"><strong>Varianten:</strong> Leserin ohne BLOG_WRITE (keine Aktionen) · BLOG_WRITER (Bearbeiten + Löschen sichtbar) · Mobile (Aktionen im … Menü)</p>
|
||||
|
||||
<div class="previews">
|
||||
<!-- DESKTOP DETAIL -->
|
||||
<div class="prev-col" style="width:100%;max-width:1040px;">
|
||||
<span class="bp-lbl">Desktop — 1040px · BLOG_WRITER-Ansicht</span>
|
||||
<div class="desk" style="min-height:600px;">
|
||||
<!-- Nav -->
|
||||
<div class="fa-nav">
|
||||
<span class="fa-logo">ARCHIV</span>
|
||||
<span style="width:1px;height:14px;background:rgba(255,255,255,.1);margin:0 2px;"></span>
|
||||
<span class="fa-link">Dokumente</span>
|
||||
<span class="fa-link">Personen</span>
|
||||
<span class="fa-link active">Geschichten</span>
|
||||
<span class="fa-link">Chronik</span>
|
||||
<div class="fa-nav-r">
|
||||
<div class="fa-av" style="background:#012851;color:var(--mint);font-size:5px;font-weight:800;">MR</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Detail body -->
|
||||
<div class="g-detail-body">
|
||||
<div class="g-article">
|
||||
|
||||
<!-- Back -->
|
||||
<div class="g-back">
|
||||
<svg width="7" height="7" viewBox="0 0 10 10" fill="none"><path d="M6 2L2 5l4 3" stroke="#6B6A63" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
Zurück zu Geschichten
|
||||
</div>
|
||||
|
||||
<!-- Title -->
|
||||
<div class="g-story-title">Der Sommer in Breslau</div>
|
||||
|
||||
<!-- Meta bar -->
|
||||
<div class="g-metabar">
|
||||
<div class="g-av av-navy" style="width:20px;height:20px;font-size:6.5px;">MR</div>
|
||||
<div>
|
||||
<div style="font-size:7.5px;font-weight:700;color:#1C1C18;line-height:1.2;">Maria Raddatz</div>
|
||||
<div style="font-size:6.5px;color:#6B6A63;">veröffentlicht am 14. März 2025</div>
|
||||
</div>
|
||||
<div class="g-metabar-r">
|
||||
<button class="g-edit-btn">Bearbeiten</button>
|
||||
<span class="g-del-link">Löschen</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Divider -->
|
||||
<div style="height:1px;background:#EDECEA;margin-bottom:12px;"></div>
|
||||
|
||||
<!-- Body text -->
|
||||
<div class="g-body-text">
|
||||
<p>Oma erzählte oft vom letzten Sommer vor dem Krieg, als die Familie noch vollständig zusammen war. Es war 1938, und die Hitze in Breslau schien endloser als in all den Jahren davor. Die Kinder liefen barfuß über die Steinplatten des Innenhofs, und die Erwachsenen saßen bis spät in die Nacht auf der Veranda.</p>
|
||||
<p>Es war 1938, und Franz hatte gerade seinen 40. Geburtstag gefeiert. Die Briefe aus dieser Zeit zeigen einen Mann voller Zuversicht — man kann kaum glauben, was nur wenige Jahre später folgen würde. Er schrieb an seinen Bruder Heinrich: „Der Sommer gehört uns, der Winter kommt früh genug."</p>
|
||||
</div>
|
||||
|
||||
<!-- Persons section -->
|
||||
<div class="g-sub-hdr">Personen in dieser Geschichte</div>
|
||||
<div class="g-persons-row">
|
||||
<div class="g-person-chip">
|
||||
<div class="av" style="background:#012851;color:var(--mint);">FR</div>
|
||||
Franz Raddatz
|
||||
</div>
|
||||
<div class="g-person-chip">
|
||||
<div class="av" style="background:#534AB7;color:#fff;">EM</div>
|
||||
Emma Müller
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Documents section -->
|
||||
<div class="g-sub-hdr" style="margin-top:10px;">Erwähnte Dokumente</div>
|
||||
<div class="g-doc-cards">
|
||||
<div class="g-doc-card">
|
||||
<div class="g-doc-icon">
|
||||
<svg width="10" height="12" viewBox="0 0 10 12" fill="none"><rect x="1" y="1" width="8" height="10" rx="1" stroke="#6B6A63" stroke-width="1"/><path d="M3 4h4M3 6.5h4M3 9h2" stroke="#6B6A63" stroke-width=".8" stroke-linecap="round"/></svg>
|
||||
</div>
|
||||
<div class="g-doc-info">
|
||||
<div class="g-doc-title">Brief vom 12. Juli 1938</div>
|
||||
<div class="g-doc-meta">12. Juli 1938 · von Franz Raddatz</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="g-doc-card">
|
||||
<div class="g-doc-icon">
|
||||
<svg width="10" height="12" viewBox="0 0 10 12" fill="none"><rect x="1" y="1" width="8" height="10" rx="1" stroke="#6B6A63" stroke-width="1"/><path d="M3 4h4M3 6.5h4M3 9h2" stroke="#6B6A63" stroke-width=".8" stroke-linecap="round"/></svg>
|
||||
</div>
|
||||
<div class="g-doc-info">
|
||||
<div class="g-doc-title">Postkarte aus Breslau, August 1938</div>
|
||||
<div class="g-doc-meta">22. Aug. 1938 · von Franz Raddatz an Emma Müller</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /article -->
|
||||
</div><!-- /detail-body -->
|
||||
</div><!-- /desk -->
|
||||
</div>
|
||||
|
||||
<!-- MOBILE DETAIL -->
|
||||
<div class="prev-col">
|
||||
<span class="bp-lbl">Mobile — 320px · Leserin ohne Schreibrecht</span>
|
||||
<div class="phone" style="min-height:560px;">
|
||||
<div class="pst"><b>9:41</b><span>●●●</span></div>
|
||||
<div class="pb">
|
||||
<div class="m-nav">
|
||||
<span class="m-logo">ARCHIV</span>
|
||||
<div class="m-nav-r">
|
||||
<div class="m-av">MR</div>
|
||||
<div class="m-ham"><span></span><span></span><span></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="m-detail-body">
|
||||
<div class="m-article">
|
||||
<div class="m-back">
|
||||
<svg width="6" height="6" viewBox="0 0 10 10" fill="none"><path d="M6 2L2 5l4 3" stroke="#6B6A63" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||||
Zurück zu Geschichten
|
||||
</div>
|
||||
|
||||
<div class="m-story-title">Der Sommer in Breslau</div>
|
||||
|
||||
<div class="m-metabar">
|
||||
<div class="g-av av-navy" style="width:16px;height:16px;font-size:5.5px;flex-shrink:0;">MR</div>
|
||||
<div>
|
||||
<div style="font-size:7px;font-weight:700;color:#1C1C18;line-height:1.2;">Maria Raddatz</div>
|
||||
<div style="font-size:6px;color:#6B6A63;">14. März 2025</div>
|
||||
</div>
|
||||
<div style="margin-left:auto;">
|
||||
<div class="m-three-dot">···</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="height:1px;background:#EDECEA;margin-bottom:8px;"></div>
|
||||
|
||||
<div class="m-body-text">
|
||||
<p>Oma erzählte oft vom letzten Sommer vor dem Krieg, als die Familie noch vollständig zusammen war. Es war 1938, und die Hitze in Breslau schien endloser als in all den Jahren davor.</p>
|
||||
<p>Es war 1938, und Franz hatte gerade seinen 40. Geburtstag gefeiert. Die Briefe aus dieser Zeit zeigen einen Mann voller Zuversicht — man kann kaum glauben, was nur wenige Jahre später folgen würde.</p>
|
||||
</div>
|
||||
|
||||
<div class="m-sub-hdr">Personen in dieser Geschichte</div>
|
||||
<div class="m-persons-row">
|
||||
<div class="m-person-chip">
|
||||
<div class="av" style="background:#012851;color:var(--mint);width:12px;height:12px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:4.5px;font-weight:800;flex-shrink:0;">FR</div>
|
||||
Franz Raddatz
|
||||
</div>
|
||||
<div class="m-person-chip">
|
||||
<div class="av" style="background:#534AB7;color:#fff;width:12px;height:12px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:4.5px;font-weight:800;flex-shrink:0;">EM</div>
|
||||
Emma Müller
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="m-sub-hdr" style="margin-top:8px;">Erwähnte Dokumente</div>
|
||||
<div class="m-doc-card">
|
||||
<div class="m-doc-icon">
|
||||
<svg width="8" height="10" viewBox="0 0 10 12" fill="none"><rect x="1" y="1" width="8" height="10" rx="1" stroke="#6B6A63" stroke-width="1"/><path d="M3 4h4M3 6.5h4M3 9h2" stroke="#6B6A63" stroke-width=".8" stroke-linecap="round"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="m-doc-title">Brief vom 12. Juli 1938</div>
|
||||
<div class="m-doc-meta">12. Juli 1938 · von Franz Raddatz</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="m-doc-card">
|
||||
<div class="m-doc-icon">
|
||||
<svg width="8" height="10" viewBox="0 0 10 12" fill="none"><rect x="1" y="1" width="8" height="10" rx="1" stroke="#6B6A63" stroke-width="1"/><path d="M3 4h4M3 6.5h4M3 9h2" stroke="#6B6A63" stroke-width=".8" stroke-linecap="round"/></svg>
|
||||
</div>
|
||||
<div>
|
||||
<div class="m-doc-title">Postkarte aus Breslau, Aug. 1938</div>
|
||||
<div class="m-doc-meta">22. Aug. 1938 · Franz an Emma</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /previews -->
|
||||
|
||||
<!-- impl-ref R-2 -->
|
||||
<div class="agent">
|
||||
<h4>impl-ref — R-2 Story-Detail /geschichten/[id]</h4>
|
||||
<table class="at">
|
||||
<thead><tr><th>Element</th><th>Wert</th><th>Hinweise</th></tr></thead>
|
||||
<tbody>
|
||||
<tr class="grp"><td colspan="3">Artikel-Container</td></tr>
|
||||
<tr><td>Article container</td><td>max-w-3xl mx-auto px-4 py-10</td><td>zentriert, volle Breite auf Mobile</td></tr>
|
||||
<tr><td>Story title</td><td>font-family:var(--font-display);font-size:clamp(22px,4vw,32px);color:var(--navy)</td><td>Fraunces, nicht fett</td></tr>
|
||||
<tr><td>Back button</td><td><BackButton /> aus $lib/components/BackButton.svelte</td><td>history.back(); nicht <a href></td></tr>
|
||||
<tr class="grp"><td colspan="3">Metazeile</td></tr>
|
||||
<tr><td>Meta bar</td><td>flex items-center gap-3 mt-4 mb-6</td><td></td></tr>
|
||||
<tr><td>Author avatar</td><td>w-8 h-8 rounded-full text-xs font-bold text-white flex items-center justify-center</td><td>personAvatarColor(author.id)</td></tr>
|
||||
<tr><td>Published date</td><td>font-sans text-xs text-ink-3</td><td>„veröffentlicht am " + formatDate()</td></tr>
|
||||
<tr><td>Bearbeiten btn</td><td>rounded border border-line px-3 py-1.5 text-sm font-medium text-ink hover:bg-muted</td><td>nur BLOG_WRITE; auf Mobile im … Menü</td></tr>
|
||||
<tr><td>Löschen link</td><td>text-sm text-red-600 font-medium hover:underline</td><td>nur BLOG_WRITE; Confirm-Dialog davor</td></tr>
|
||||
<tr class="grp"><td colspan="3">Fließtext</td></tr>
|
||||
<tr><td>Body text</td><td>font-serif text-base leading-relaxed text-ink prose</td><td>rendered HTML aus Backend; kein XSS (DOMPurify)</td></tr>
|
||||
<tr class="grp"><td colspan="3">Personen & Dokumente</td></tr>
|
||||
<tr><td>Person chips section</td><td>flex flex-wrap gap-2 mt-8</td><td>Abschnittsüberschrift xs uppercase muted</td></tr>
|
||||
<tr><td>Person chip</td><td>inline-flex items-center gap-2 rounded-full border border-line bg-surface px-3 py-1.5 text-sm font-medium</td><td>links zu /persons/[id]</td></tr>
|
||||
<tr><td>Doc reference card</td><td>flex gap-3 items-start bg-white border border-brand-sand rounded-sm p-3 hover:shadow-sm</td><td>links zu /documents/[id]</td></tr>
|
||||
<tr><td>Doc icon</td><td>w-9 h-9 bg-surface rounded flex items-center justify-center shrink-0</td><td>Dateisymbol SVG</td></tr>
|
||||
<tr class="grp"><td colspan="3">Mobile</td></tr>
|
||||
<tr><td>… Menü (Mobile)</td><td>ml-auto text-ink-3; öffnet BottomSheet mit Bearbeiten + Löschen</td><td>BLOG_WRITE-Aktionen auf Mobile</td></tr>
|
||||
<tr><td>Person chips (Mobile)</td><td>flex-wrap, volle Breite</td><td>kein horizontales Scrollen</td></tr>
|
||||
<tr><td>Doc cards (Mobile)</td><td>flex-col gap-2</td><td>stapeln vertikal</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div><!-- /scr R-2 -->
|
||||
</div><!-- /section -->
|
||||
|
||||
<!-- ═══ LLM IMPLEMENTATION GUIDE ═══ -->
|
||||
<div class="llm">
|
||||
<h2>Implementation Guide — Reader Journey</h2>
|
||||
|
||||
<h3>Views & Routen</h3>
|
||||
<table>
|
||||
<thead><tr><th>View</th><th>Route</th><th>Component</th><th>Datenquelle</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Index</td><td>/geschichten</td><td>GeschichtenIndex.svelte</td><td>GET /api/geschichten?status=PUBLISHED</td></tr>
|
||||
<tr><td>Detail</td><td>/geschichten/[id]</td><td>GeschichteDetail.svelte</td><td>GET /api/geschichten/{id}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>Datenmodell je Story (Listenitem)</h3>
|
||||
<ul>
|
||||
<li><code>id</code> — UUID</li>
|
||||
<li><code>title</code> — string</li>
|
||||
<li><code>body</code> — string (HTML, serverseitig bereinigt mit DOMPurify vor dem Rendern)</li>
|
||||
<li><code>status</code> — <code>DRAFT | PUBLISHED</code></li>
|
||||
<li><code>author</code> — AppUser (id, firstName, lastName)</li>
|
||||
<li><code>persons</code> — Person[] (id, firstName, lastName) — für Chips & Filter</li>
|
||||
<li><code>documents</code> — Document[] (id, title, documentDate, sender) — für Referenzkarten</li>
|
||||
<li><code>publishedAt</code> — ISO-8601-Timestamp</li>
|
||||
</ul>
|
||||
|
||||
<h3>Wiederverwendbare Komponenten & Utilities</h3>
|
||||
<ul>
|
||||
<li><code>PersonChipRow</code> — bestehende Komponente aus <code>$lib/components/</code>; Props: <code>persons: Person[]</code></li>
|
||||
<li><code>personAvatarColor(userId)</code> — bestehende Utility; gibt Tailwind-Bg-Klasse zurück</li>
|
||||
<li><code>formatDate(iso)</code> — bestehende Utility; gibt „14. März 2025" zurück</li>
|
||||
<li><code>BackButton</code> — <code>$lib/components/BackButton.svelte</code>; ruft <code>history.back()</code> auf</li>
|
||||
</ul>
|
||||
|
||||
<h3>Personenfilter — Implementierungshinweise</h3>
|
||||
<ul>
|
||||
<li>Filter-State in der URL als Query-Parameter: <code>?person=UUID</code>. So ist der gefilterte Zustand direkt verlinkbar und bookmarkbar.</li>
|
||||
<li>Server-side Load-Funktion übergibt <code>?personId=UUID</code> an die API. Die API filtert auf Stories, die diese Person in <code>persons[]</code> enthalten.</li>
|
||||
<li>Filter-Pills werden aus dem aggregierten Set aller Personen aus geladenen Stories gebaut — nicht als eigener API-Aufruf.</li>
|
||||
<li>„+ Person wählen" öffnet ein Inline-Dropdown (bestehende <code>PersonTypeahead</code>-Komponente), fügt die Person zu den Pills hinzu und navigiert mit <code>?person=UUID</code>.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Berechtigung BLOG_WRITE</h3>
|
||||
<ul>
|
||||
<li>Die Schaltflächen „+ Neue Geschichte", „Bearbeiten" und „Löschen" werden nur gerendert, wenn <code>currentUser.permissions.includes('BLOG_WRITE')</code> wahr ist.</li>
|
||||
<li>Nicht nur ausblenden — Backend-Endpunkte für Schreib-/Löschoperationen sind ebenfalls durch <code>@RequirePermission(Permission.BLOG_WRITE)</code> geschützt.</li>
|
||||
<li>Auf Mobile werden Bearbeiten/Löschen aus dem Layout entfernt und erscheinen in einem BottomSheet, das über das ··· Menü in der Metazeile geöffnet wird.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Barrierefreiheit</h3>
|
||||
<ul>
|
||||
<li>Alle Story-Titellinks tragen beschreibenden Text (Titel der Geschichte, kein „hier klicken").</li>
|
||||
<li>Personenfilter-Pills verwenden <code>aria-pressed="true|false"</code>.</li>
|
||||
<li>Touch-Targets auf Mobile ≥ 44px (<code>min-h-[44px]</code> auf jeder Listenzeile).</li>
|
||||
<li>Fokusring auf allen interaktiven Elementen (Tailwind <code>focus-visible:ring-2 focus-visible:ring-primary</code>).</li>
|
||||
<li>Löschen-Aktion zeigt einen Bestätigungs-Dialog (<code>window.confirm</code> oder Modal) vor dem API-Aufruf.</li>
|
||||
</ul>
|
||||
|
||||
<h3>Routing & Navigation</h3>
|
||||
<ul>
|
||||
<li>Jede Listenzeile ist eine <code><a href="/geschichten/{id}"></code> — kein JavaScript-onClick nötig.</li>
|
||||
<li>Zurück-Schaltfläche auf der Detailansicht nutzt <code>BackButton</code>-Komponente (history.back()), nicht einen festen Href.</li>
|
||||
<li>Personenchips auf der Detailansicht verlinken zu <code>/persons/{id}</code>.</li>
|
||||
<li>Dokumentreferenzkarten verlinken zu <code>/documents/{id}</code>.</li>
|
||||
</ul>
|
||||
|
||||
</div><!-- /llm -->
|
||||
|
||||
</div><!-- /doc -->
|
||||
</body>
|
||||
</html>
|
||||
831
docs/specs/geschichten-writer-journey-spec.html
Normal file
831
docs/specs/geschichten-writer-journey-spec.html
Normal file
@@ -0,0 +1,831 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Geschichten — Writer Journey · Familienarchiv</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,300;9..144,400;9..144,500&family=DM+Sans:wght@300;400;500;600&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root{--color-page:#FAFAF7;--color-surface:#F5F4EE;--color-subtle:#EDECEA;--color-border:#D8D7D0;--color-text-muted:#6B6A63;--color-text:#1C1C18;--navy:#012851;--mint:#A1DCD8;--sand:#F0EFE9;--turquoise:#00C7B1;--blue-tint:#E6F1FB;--blue:#2D7DD2;--blue-dark:#185FA5;--green-tint:#E8F5EA;--green:#3D8C4A;--green-dark:#2E6E39;--orange-tint:#FEF0E6;--orange:#E8862A;--orange-dark:#B46820;--font-display:'Fraunces',Georgia,serif;--font-sans:'DM Sans',system-ui,sans-serif;--font-mono:'DM Mono',monospace;--radius-sm:4px;--radius-md:6px;--radius-lg:10px;--radius-xl:16px;--shadow-card:0 1px 3px rgba(28,28,24,.06),0 1px 2px rgba(28,28,24,.04);--shadow-raised:0 4px 12px rgba(28,28,24,.08),0 2px 4px rgba(28,28,24,.04);--shadow-overlay:0 8px 32px rgba(28,28,24,.12),0 2px 8px rgba(28,28,24,.06);}
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
|
||||
body{font-family:var(--font-sans);background:#E8E7E2;color:var(--color-text);font-size:14px;line-height:1.6;}
|
||||
.doc{max-width:1200px;margin:0 auto;padding:48px 40px 120px;}
|
||||
.doc-header{display:flex;justify-content:space-between;align-items:flex-end;padding-bottom:28px;border-bottom:1px solid var(--color-border);margin-bottom:48px;background:var(--color-page);margin:-48px -40px 48px;padding:48px 40px 28px;border-radius:var(--radius-xl) var(--radius-xl) 0 0;}
|
||||
.doc-header h1{font-family:var(--font-display);font-size:28px;font-weight:500;letter-spacing:-.02em;margin-bottom:4px;}
|
||||
.doc-header p{font-size:13px;color:var(--color-text-muted);max-width:680px;}
|
||||
.doc-meta{font-family:var(--font-mono);font-size:11px;color:var(--color-text-muted);text-align:right;line-height:1.9;}
|
||||
.pill{display:inline-block;padding:2px 8px;border-radius:var(--radius-sm);font-size:10px;font-weight:500;letter-spacing:.05em;}
|
||||
.pill-g{background:var(--green-tint);color:var(--green-dark);}
|
||||
.section{margin-bottom:64px;}
|
||||
.section-title{font-size:10px;font-weight:500;letter-spacing:.12em;text-transform:uppercase;color:var(--color-text-muted);padding-bottom:10px;border-bottom:1px solid var(--color-border);margin-bottom:24px;}
|
||||
.prose{font-size:13px;color:var(--color-text-muted);line-height:1.65;max-width:720px;margin-bottom:20px;}
|
||||
.jh{padding:20px 24px;border-radius:var(--radius-xl);margin-bottom:40px;display:flex;align-items:center;gap:16px;}
|
||||
.jh .jn{font-family:var(--font-display);font-size:48px;font-weight:300;line-height:1;opacity:.5;}
|
||||
.jh h2{font-family:var(--font-display);font-size:22px;font-weight:500;letter-spacing:-.02em;margin-bottom:4px;}
|
||||
.jh p{font-size:13px;line-height:1.5;}.jh .fl{font-family:var(--font-mono);font-size:11px;margin-top:6px;opacity:.7;}
|
||||
.jh-g{background:var(--green-tint);border:1px solid #A0D8A8;}.jh-g .jn{color:var(--green);}.jh-g p,.jh-g .fl{color:var(--green-dark);}
|
||||
.scr{margin-bottom:56px;}
|
||||
.scr-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;}
|
||||
.scr-head h3{font-family:var(--font-display);font-size:20px;font-weight:500;letter-spacing:-.02em;}
|
||||
.scr-id{font-family:var(--font-mono);font-size:11px;color:var(--color-text-muted);padding:2px 8px;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-page);}
|
||||
.scr-desc{font-size:12px;color:var(--color-text-muted);line-height:1.6;max-width:720px;margin-bottom:6px;}
|
||||
.scr-var{font-size:11px;color:var(--color-text-muted);margin-bottom:20px;}.scr-var strong{color:var(--color-text);}
|
||||
.previews{display:flex;gap:32px;flex-wrap:wrap;justify-content:center;align-items:flex-start;margin-bottom:20px;}
|
||||
.prev-col{display:flex;flex-direction:column;align-items:center;gap:10px;}
|
||||
.bp-lbl{font-family:var(--font-mono);font-size:10px;color:var(--color-text-muted);}
|
||||
.desk{width:100%;max-width:1040px;background:var(--color-page);border-radius:var(--radius-xl);overflow:hidden;box-shadow:var(--shadow-overlay),0 0 0 1px rgba(0,0,0,.06);display:flex;flex-direction:column;min-height:560px;}
|
||||
.phone{width:320px;flex-shrink:0;background:var(--color-page);border-radius:36px;overflow:hidden;box-shadow:var(--shadow-overlay),0 0 0 1px rgba(0,0,0,.07);display:flex;flex-direction:column;border:6px solid #1C1C18;}
|
||||
.pst{padding:10px 20px 0;display:flex;justify-content:space-between;align-items:center;font-size:12px;background:var(--color-page);}.pst b{font-weight:600;}.pst span{font-size:10px;}
|
||||
.pb{flex:1;overflow-y:auto;display:flex;flex-direction:column;}
|
||||
.fa-nav{height:32px;background:var(--navy);display:flex;align-items:center;padding:0 12px;gap:8px;flex-shrink:0;}
|
||||
.fa-logo{font-size:7px;font-weight:900;color:#fff;letter-spacing:.8px;border-bottom:2px solid var(--mint);padding-bottom:1px;}
|
||||
.fa-link{font-size:5.5px;color:rgba(255,255,255,.4);font-weight:700;text-transform:uppercase;letter-spacing:.05em;}
|
||||
.fa-link.active{color:var(--mint);}
|
||||
.fa-nav-r{margin-left:auto;display:flex;gap:5px;align-items:center;}
|
||||
.fa-av{width:16px;height:16px;background:rgba(255,255,255,.1);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:5px;font-weight:800;color:rgba(255,255,255,.5);}
|
||||
.agent{background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-lg);padding:24px;margin-top:20px;}
|
||||
.agent h4{font-family:var(--font-mono);font-size:12px;font-weight:500;margin-bottom:8px;color:var(--navy);}
|
||||
.agent pre{font-family:var(--font-mono);font-size:11px;color:var(--color-text-muted);margin-bottom:12px;white-space:pre-wrap;}
|
||||
.at{width:100%;border-collapse:collapse;font-size:11px;}
|
||||
.at th{font-family:var(--font-mono);font-size:10px;font-weight:500;text-align:left;color:var(--color-text-muted);padding:6px 8px;border-bottom:2px solid var(--color-border);}
|
||||
.at td{padding:5px 8px;border-bottom:1px solid var(--color-subtle);vertical-align:top;line-height:1.5;}
|
||||
.at tr.grp td{font-family:var(--font-mono);font-size:9px;color:var(--color-text-muted);background:var(--color-subtle);font-weight:500;letter-spacing:.06em;text-transform:uppercase;padding:4px 8px;}
|
||||
.at code{font-family:var(--font-mono);font-size:10px;color:var(--navy);background:rgba(1,40,81,.06);padding:1px 4px;border-radius:2px;}
|
||||
|
||||
/* ── Editor frame internals (scaled ~55%) ── */
|
||||
.ed-topbar{background:#fff;border-bottom:1px solid #e4e2d7;display:flex;align-items:center;padding:0 14px;gap:8px;height:38px;flex-shrink:0;}
|
||||
.ed-back{width:22px;height:22px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:9px;color:var(--color-text-muted);cursor:pointer;flex-shrink:0;}
|
||||
.ed-title-label{font-family:var(--font-sans);font-size:10px;font-weight:500;color:var(--color-text);flex:1;}
|
||||
.ed-status-pill{display:inline-flex;align-items:center;padding:2px 7px;border-radius:20px;font-size:8px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;flex-shrink:0;}
|
||||
.ed-status-draft{background:#F0EFE9;color:#6B6A63;border:1px solid #D8D7D0;}
|
||||
.ed-status-pub{background:var(--green-tint);color:var(--green-dark);border:1px solid #A0D8A8;}
|
||||
.ed-delete-link{font-size:8px;font-weight:600;color:#DC4C3E;margin-left:8px;text-decoration:none;white-space:nowrap;}
|
||||
.ed-split{display:flex;flex:1;overflow:hidden;}
|
||||
.ed-left{flex:1;display:flex;flex-direction:column;padding:14px 16px;overflow-y:auto;gap:10px;}
|
||||
.ed-title-input{font-family:var(--font-display);font-size:16px;font-weight:400;color:var(--color-text);border:none;border-bottom:1px solid var(--color-border);padding:4px 0 6px;width:100%;outline:none;background:transparent;letter-spacing:-.01em;}
|
||||
.ed-title-input.placeholder{color:var(--color-text-muted);}
|
||||
.ed-title-input.error{border-bottom-color:#DC4C3E;}
|
||||
.ed-sep{height:1px;background:var(--color-border);margin:2px 0;}
|
||||
.ed-toolbar{display:flex;align-items:center;gap:4px;}
|
||||
.ed-tool{width:24px;height:24px;display:flex;align-items:center;justify-content:center;border:1px solid var(--color-border);border-radius:var(--radius-sm);font-size:9px;font-weight:700;color:var(--color-text-muted);background:#fff;cursor:pointer;flex-shrink:0;}
|
||||
.ed-tool:hover{background:var(--color-subtle);color:var(--color-text);}
|
||||
.ed-tool.active{background:var(--navy);color:#fff;border-color:var(--navy);}
|
||||
.ed-body{font-family:Georgia,serif;font-size:10px;line-height:1.7;color:var(--color-text);min-height:220px;resize:none;border:none;outline:none;background:transparent;width:100%;}
|
||||
.ed-body.placeholder{color:var(--color-text-muted);font-style:italic;}
|
||||
.ed-sidebar{width:210px;flex-shrink:0;border-left:1px solid #e4e2d7;background:#fff;display:flex;flex-direction:column;overflow-y:auto;}
|
||||
.ed-sb-section{padding:12px 12px 10px;}
|
||||
.ed-sb-title{font-size:8px;font-weight:700;letter-spacing:.1em;text-transform:uppercase;color:var(--color-text-muted);margin-bottom:8px;}
|
||||
.ed-sb-divider{height:1px;background:#e4e2d7;margin:0;}
|
||||
.ed-search-row{display:flex;align-items:center;gap:6px;background:var(--color-surface);border:1px solid var(--color-border);border-radius:var(--radius-sm);padding:4px 8px;margin-bottom:6px;}
|
||||
.ed-search-row svg-icon{font-size:8px;color:var(--color-text-muted);flex-shrink:0;}
|
||||
.ed-search-input{font-family:var(--font-sans);font-size:9px;color:var(--color-text-muted);border:none;outline:none;background:transparent;flex:1;}
|
||||
.ed-chip{display:inline-flex;align-items:center;gap:4px;padding:3px 7px;background:var(--sand);border:1px solid var(--color-border);border-radius:12px;font-size:8px;font-weight:500;color:var(--color-text);margin:0 4px 4px 0;}
|
||||
.ed-chip-x{color:var(--color-text-muted);font-size:9px;cursor:pointer;margin-left:2px;line-height:1;}
|
||||
.ed-chip.doc-chip{background:var(--blue-tint);border-color:#BCD8F4;color:var(--blue-dark);}
|
||||
.ed-hint{font-size:8px;color:var(--color-text-muted);line-height:1.5;margin-top:4px;}
|
||||
.ed-chips-wrap{display:flex;flex-wrap:wrap;min-height:22px;}
|
||||
.ed-savebar{background:#fff;border-top:1px solid #e4e2d7;padding:9px 14px;display:flex;align-items:center;justify-content:space-between;flex-shrink:0;gap:10px;}
|
||||
.ed-savebar-hint{font-size:8px;color:var(--color-text-muted);}
|
||||
.ed-savebar-actions{display:flex;align-items:center;gap:7px;}
|
||||
.ed-btn-ghost{font-size:9px;font-weight:600;padding:5px 12px;border-radius:var(--radius-sm);border:1px solid var(--color-border);color:var(--color-text);background:#fff;cursor:pointer;white-space:nowrap;}
|
||||
.ed-btn-ghost:hover{background:var(--color-subtle);}
|
||||
.ed-btn-ghost.retract{color:#B46820;border-color:#E8D5B0;}
|
||||
.ed-btn-ghost.retract:hover{background:var(--orange-tint);}
|
||||
.ed-btn-primary{font-size:9px;font-weight:600;padding:5px 12px;border-radius:var(--radius-sm);background:var(--navy);color:#fff;border:none;cursor:pointer;white-space:nowrap;}
|
||||
.ed-btn-primary:hover{background:#023570;}
|
||||
.ed-btn-danger{font-size:9px;font-weight:600;padding:5px 12px;border-radius:var(--radius-sm);background:#DC4C3E;color:#fff;border:none;cursor:pointer;white-space:nowrap;}
|
||||
.ed-error-hint{font-size:8px;color:#DC4C3E;margin-top:3px;}
|
||||
|
||||
/* ── Validation inset ── */
|
||||
.validation-inset{background:#FFF5F5;border:1px solid #FECACA;border-radius:var(--radius-md);padding:14px 16px;margin-top:16px;max-width:600px;}
|
||||
.validation-inset-label{font-size:9px;font-family:var(--font-mono);font-weight:500;color:#DC4C3E;margin-bottom:8px;letter-spacing:.04em;}
|
||||
.vi-field{border:1px solid #DC4C3E;border-radius:var(--radius-sm);background:#fff;padding:6px 10px;font-family:var(--font-display);font-size:13px;color:var(--color-text-muted);font-style:italic;width:100%;margin-bottom:4px;}
|
||||
.vi-err{font-size:9px;color:#DC4C3E;font-weight:500;}
|
||||
|
||||
/* ── Mobile editor ── */
|
||||
.mob-topbar{background:#fff;border-bottom:1px solid #e4e2d7;display:flex;align-items:center;padding:0 10px;gap:6px;height:34px;flex-shrink:0;}
|
||||
.mob-back{font-size:9px;color:var(--color-text-muted);}
|
||||
.mob-title-label{font-family:var(--font-sans);font-size:9px;font-weight:500;color:var(--color-text);flex:1;}
|
||||
.mob-body{flex:1;overflow-y:auto;padding:10px 12px;display:flex;flex-direction:column;gap:8px;}
|
||||
.mob-title-input{font-family:var(--font-display);font-size:13px;color:var(--color-text-muted);font-style:italic;border:none;border-bottom:1px solid var(--color-border);padding:3px 0 5px;width:100%;background:transparent;outline:none;}
|
||||
.mob-toolbar{display:flex;gap:3px;}
|
||||
.mob-tool{width:20px;height:20px;display:flex;align-items:center;justify-content:center;border:1px solid var(--color-border);border-radius:var(--radius-sm);font-size:8px;font-weight:700;color:var(--color-text-muted);background:#fff;}
|
||||
.mob-ed-body{font-family:Georgia,serif;font-size:9px;line-height:1.65;color:var(--color-text-muted);font-style:italic;min-height:160px;}
|
||||
.mob-collapsible{background:#fff;border:1px solid #e4e2d7;border-radius:var(--radius-sm);overflow:hidden;}
|
||||
.mob-coll-hdr{display:flex;align-items:center;justify-content:space-between;padding:8px 10px;font-size:9px;font-weight:600;color:var(--color-text);}
|
||||
.mob-coll-chevron{font-size:10px;color:var(--color-text-muted);}
|
||||
.mob-savebar{background:#fff;border-top:1px solid #e4e2d7;padding:8px 10px;display:flex;flex-direction:column;gap:6px;flex-shrink:0;}
|
||||
.mob-btn-full{font-size:9px;font-weight:600;padding:7px 0;border-radius:var(--radius-sm);text-align:center;width:100%;}
|
||||
.mob-btn-ghost-full{border:1px solid var(--color-border);color:var(--color-text);background:#fff;}
|
||||
.mob-btn-primary-full{background:var(--navy);color:#fff;border:none;}
|
||||
|
||||
/* ── Delete dialog overlay ── */
|
||||
.modal-scrim{position:relative;flex:1;display:flex;align-items:center;justify-content:center;background:rgba(28,28,24,.45);}
|
||||
.modal-dialog{background:#fff;border-radius:var(--radius-lg);box-shadow:var(--shadow-overlay);width:300px;padding:24px 20px;display:flex;flex-direction:column;gap:14px;}
|
||||
.modal-title{font-family:var(--font-display);font-size:14px;font-weight:500;color:var(--color-text);letter-spacing:-.01em;}
|
||||
.modal-body{font-size:9px;color:var(--color-text-muted);line-height:1.65;}
|
||||
.modal-actions{display:flex;gap:8px;justify-content:flex-end;}
|
||||
|
||||
/* ── Route/component table ── */
|
||||
.routes-table{width:100%;border-collapse:collapse;font-size:12px;}
|
||||
.routes-table th{font-family:var(--font-mono);font-size:10px;font-weight:500;text-align:left;color:var(--color-text-muted);padding:7px 10px;border-bottom:2px solid var(--color-border);}
|
||||
.routes-table td{padding:7px 10px;border-bottom:1px solid var(--color-subtle);vertical-align:top;line-height:1.5;}
|
||||
.routes-table td code{font-family:var(--font-mono);font-size:10px;color:var(--navy);background:rgba(1,40,81,.06);padding:1px 5px;border-radius:2px;}
|
||||
|
||||
/* ── Desk editor full-height helper ── */
|
||||
.desk-editor{display:flex;flex-direction:column;height:560px;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="doc">
|
||||
|
||||
<!-- ════════════════════════════════════════
|
||||
DOC HEADER
|
||||
════════════════════════════════════════ -->
|
||||
<div class="doc-header">
|
||||
<div>
|
||||
<h1>Geschichten — Writer Journey</h1>
|
||||
<p>Erstellung, Bearbeitung und Veröffentlichung von Geschichten durch BLOG_WRITERs. Routen /geschichten/new und /geschichten/[id]/edit mit geteiltem Layout: Texteditor links, Metadaten-Sidebar rechts.</p>
|
||||
</div>
|
||||
<div class="doc-meta">
|
||||
<div><span class="pill pill-g">Final Spec</span></div>
|
||||
<div>2026-05-02</div>
|
||||
<div>@leonievoss</div>
|
||||
<div style="margin-top:4px;font-size:10px;">Issue #381</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════
|
||||
JOURNEY HEADER
|
||||
════════════════════════════════════════ -->
|
||||
<div class="jh jh-g">
|
||||
<div class="jn">W</div>
|
||||
<div>
|
||||
<h2>Writer Journey</h2>
|
||||
<p>BLOG_WRITERs erstellen und pflegen Geschichten über mehrere Sitzungen, verknüpfen historische Personen und Dokumente und entscheiden selbst über Veröffentlichung.</p>
|
||||
<div class="fl">/geschichten/new · /geschichten/[id]/edit</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════
|
||||
SECTION: KONZEPT
|
||||
════════════════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-title">Konzept</div>
|
||||
|
||||
<p class="prose">Geschichten werden in dedizierten Vollseiten-Routen verfasst – kein Inline-Edit, kein Modal. Dadurch hat der Writer maximale Konzentration auf den Text und alle Metadaten in einer einzigen Ansicht.</p>
|
||||
|
||||
<p class="prose">Das Desktop-Layout teilt die Seite im Verhältnis 70/30: links der Fließtext-Editor (Titel + Werkzeugleiste + Body), rechts eine schmale Sidebar für Personen-Verlinkung, Dokumenten-Anhänge und Status. Beide Spalten scrollen unabhängig voneinander.</p>
|
||||
|
||||
<p class="prose">Die Speicherleiste am unteren Rand ist sticky und verändert ihre Aktionen je nach aktuellem Status der Geschichte: Im Zustand ENTWURF bietet sie "Entwurf speichern" und "Veröffentlichen". Im Zustand VERÖFFENTLICHT bietet sie "Speichern" (live) und "Zurück zu Entwurf" (Retract). Ein "Löschen"-Link erscheint in der Topbar nur dann, wenn die Geschichte bereits existiert (kein Löschen beim Anlegen einer neuen Geschichte).</p>
|
||||
|
||||
<p class="prose">PersonMultiSelect und ein Dokument-Suche-Typeahead (beide bereits im Projekt als etablierte Patterns vorhanden) übernehmen die Verknüpfung von historischen Personen und Dokumenten. Für den Rich-Text-Body genügt ein minimaler contenteditable-Ansatz mit execCommand für Fett, Kursiv und Absätze – keine externe Bibliothek nötig für den MVP.</p>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════
|
||||
SCREEN W-1: NEUER ENTWURF — DESKTOP
|
||||
════════════════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-title">Screens</div>
|
||||
|
||||
<div class="scr">
|
||||
<div class="scr-head">
|
||||
<h3>W-1 — /geschichten/new · Neuer Entwurf</h3>
|
||||
<span class="scr-id">US-BLOG-001</span>
|
||||
</div>
|
||||
<div class="scr-desc">Leeres Editor-Layout für eine neue Geschichte. Kein "Löschen"-Link (Geschichte existiert noch nicht). Save-Bar im ENTWURF-Modus mit zwei Aktionen.</div>
|
||||
<div class="scr-var"><strong>Desktop</strong> · 1040 px · min-height 560 px · Split 70/30</div>
|
||||
|
||||
<div class="previews">
|
||||
<div class="prev-col">
|
||||
<span class="bp-lbl">Desktop — 1040 px</span>
|
||||
<div class="desk desk-editor">
|
||||
<!-- Nav -->
|
||||
<div class="fa-nav">
|
||||
<span class="fa-logo">FAMILIENARCHIV</span>
|
||||
<span class="fa-link">Dokumente</span>
|
||||
<span class="fa-link">Personen</span>
|
||||
<span class="fa-link active">Geschichten</span>
|
||||
<span class="fa-link">Gespräche</span>
|
||||
<div class="fa-nav-r">
|
||||
<div class="fa-av">MR</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Top bar -->
|
||||
<div class="ed-topbar">
|
||||
<div class="ed-back">←</div>
|
||||
<div class="ed-title-label">Neue Geschichte</div>
|
||||
<div class="ed-status-pill ed-status-draft">ENTWURF</div>
|
||||
</div>
|
||||
|
||||
<!-- Split area -->
|
||||
<div class="ed-split" style="flex:1;overflow:hidden;">
|
||||
|
||||
<!-- Left: editor -->
|
||||
<div class="ed-left">
|
||||
<input class="ed-title-input placeholder" type="text" value="" placeholder="Titel der Geschichte" readonly>
|
||||
<div class="ed-sep"></div>
|
||||
<div class="ed-toolbar">
|
||||
<div class="ed-tool" title="Fett (Strg+B)"><b>B</b></div>
|
||||
<div class="ed-tool" title="Kursiv (Strg+I)"><i>I</i></div>
|
||||
<div class="ed-tool" title="Absatz">¶</div>
|
||||
</div>
|
||||
<textarea class="ed-body placeholder" readonly placeholder="Schreibe hier deine Geschichte…" style="min-height:280px;"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Right: sidebar -->
|
||||
<div class="ed-sidebar">
|
||||
<!-- Personen -->
|
||||
<div class="ed-sb-section">
|
||||
<div class="ed-sb-title">Personen</div>
|
||||
<div class="ed-search-row">
|
||||
<span style="font-size:9px;color:var(--color-text-muted);">🔍</span>
|
||||
<div class="ed-search-input">Person suchen…</div>
|
||||
</div>
|
||||
<div class="ed-chips-wrap"></div>
|
||||
<div class="ed-hint">Welche historischen Personen kommen in dieser Geschichte vor?</div>
|
||||
</div>
|
||||
<div class="ed-sb-divider"></div>
|
||||
|
||||
<!-- Dokumente -->
|
||||
<div class="ed-sb-section">
|
||||
<div class="ed-sb-title">Dokumente</div>
|
||||
<div class="ed-search-row">
|
||||
<span style="font-size:9px;color:var(--color-text-muted);">🔍</span>
|
||||
<div class="ed-search-input">Dokument suchen…</div>
|
||||
</div>
|
||||
<div class="ed-chips-wrap"></div>
|
||||
<div class="ed-hint">Welche Briefe oder Dokumente sind Teil dieser Geschichte?</div>
|
||||
</div>
|
||||
<div class="ed-sb-divider"></div>
|
||||
|
||||
<!-- Status -->
|
||||
<div class="ed-sb-section">
|
||||
<div class="ed-sb-title">Status</div>
|
||||
<div class="ed-status-pill ed-status-draft" style="font-size:9px;">ENTWURF</div>
|
||||
<div class="ed-hint" style="margin-top:6px;">Noch nicht öffentlich sichtbar.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Save bar -->
|
||||
<div class="ed-savebar">
|
||||
<span class="ed-savebar-hint">Alle Änderungen werden als Entwurf gespeichert.</span>
|
||||
<div class="ed-savebar-actions">
|
||||
<button class="ed-btn-ghost">Entwurf speichern</button>
|
||||
<button class="ed-btn-primary">Veröffentlichen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Validation inset -->
|
||||
<div class="validation-inset">
|
||||
<div class="validation-inset-label">VALIDIERUNGS-STATE — Titel leer beim Speichern</div>
|
||||
<input class="vi-field" type="text" placeholder="Titel der Geschichte" readonly>
|
||||
<div class="vi-err">● Bitte gib einen Titel ein.</div>
|
||||
</div>
|
||||
|
||||
<!-- impl-ref -->
|
||||
<div class="agent" style="margin-top:20px;">
|
||||
<h4>impl-ref · W-1</h4>
|
||||
<table class="at">
|
||||
<thead>
|
||||
<tr><th>Element</th><th>Wert / Klassen</th><th>Anmerkung</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="grp"><td colspan="3">Layout</td></tr>
|
||||
<tr><td>Seiten-Layout</td><td><code>flex flex-col h-screen</code></td><td>Feste Höhe, kein Scrollen der Hülle</td></tr>
|
||||
<tr><td>Editor-Split</td><td><code>flex flex-1 overflow-hidden</code></td><td>Beide Panels scrollen eigenständig</td></tr>
|
||||
<tr><td>Linkes Panel</td><td><code>flex-1 flex flex-col p-5 overflow-y-auto</code></td><td>Nimmt restliche Breite</td></tr>
|
||||
<tr><td>Rechte Sidebar</td><td><code>w-[280px] shrink-0 border-l border-brand-sand bg-white p-4 overflow-y-auto</code></td><td>Feste Breite 280 px</td></tr>
|
||||
<tr class="grp"><td colspan="3">Editor-Elemente</td></tr>
|
||||
<tr><td>Titel-Input</td><td><code>w-full font-serif text-[22px] text-ink placeholder-ink-3 border-0 border-b border-transparent focus:border-brand-mint focus:outline-none pb-2 mb-4</code></td><td>Kein Rahmen, nur Unterstrich bei Fokus</td></tr>
|
||||
<tr><td>Toolbar-Button</td><td><code>w-7 h-7 flex items-center justify-center rounded border border-line text-xs font-bold text-ink hover:bg-muted</code></td><td>28 × 28 px, je B / I / ¶</td></tr>
|
||||
<tr><td>Body-Textarea</td><td><code>flex-1 w-full font-serif text-[13px] text-ink resize-none focus:outline-none min-h-[280px]</code></td><td>contenteditable oder textarea MVP</td></tr>
|
||||
<tr class="grp"><td colspan="3">Speicherleiste</td></tr>
|
||||
<tr><td>Save-Bar</td><td><code>sticky bottom-0 border-t border-brand-sand bg-white px-5 py-3 flex items-center justify-between</code></td><td>Immer sichtbar, sticky</td></tr>
|
||||
<tr><td>"Entwurf speichern"</td><td><code>rounded border border-line px-4 py-2 text-sm font-medium text-ink hover:bg-muted</code></td><td>Ghost-Button</td></tr>
|
||||
<tr><td>"Veröffentlichen"</td><td><code>rounded bg-primary text-primary-fg px-4 py-2 text-sm font-medium hover:bg-primary/90</code></td><td>Navy-Primär-Button</td></tr>
|
||||
<tr class="grp"><td colspan="3">Komponenten</td></tr>
|
||||
<tr><td>PersonMultiSelect</td><td>Wiederverwendung <code>$lib/components/PersonMultiSelect.svelte</code></td><td>Identisches Pattern wie Dokument-Bearbeitung</td></tr>
|
||||
<tr><td>Dokument-Typeahead</td><td>Neue Komponente <code>$lib/components/DocumentTypeahead.svelte</code></td><td>GET /api/documents?search=; Chips mit Titel + Datum</td></tr>
|
||||
<tr><td>Validierung Titel</td><td>Rotes <code>border-b border-red-500</code> + Fehlertext unter dem Input</td><td>Nur beim Submit-Versuch auslösen</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════
|
||||
SCREEN W-2: GESPEICHERTER ENTWURF MIT ANHÄNGEN
|
||||
════════════════════════════════════════ -->
|
||||
<div class="scr">
|
||||
<div class="scr-head">
|
||||
<h3>W-2 — Entwurf mit Personen und Dokumenten verknüpft</h3>
|
||||
<span class="scr-id">US-BLOG-002</span>
|
||||
</div>
|
||||
<div class="scr-desc">Gleiche Layout-Struktur wie W-1, aber mit ausgefülltem Titel, Fließtext und verknüpften Personen und Dokumenten. Status bleibt ENTWURF.</div>
|
||||
<div class="scr-var"><strong>Desktop only</strong> · Mobile-Ansicht siehe W-4</div>
|
||||
|
||||
<div class="previews">
|
||||
<div class="prev-col">
|
||||
<span class="bp-lbl">Desktop — 1040 px</span>
|
||||
<div class="desk desk-editor">
|
||||
<div class="fa-nav">
|
||||
<span class="fa-logo">FAMILIENARCHIV</span>
|
||||
<span class="fa-link">Dokumente</span>
|
||||
<span class="fa-link">Personen</span>
|
||||
<span class="fa-link active">Geschichten</span>
|
||||
<span class="fa-link">Gespräche</span>
|
||||
<div class="fa-nav-r"><div class="fa-av">MR</div></div>
|
||||
</div>
|
||||
|
||||
<div class="ed-topbar">
|
||||
<div class="ed-back">←</div>
|
||||
<div class="ed-title-label">Der Sommer in Breslau</div>
|
||||
<div class="ed-status-pill ed-status-draft">ENTWURF</div>
|
||||
</div>
|
||||
|
||||
<div class="ed-split" style="flex:1;overflow:hidden;">
|
||||
<div class="ed-left">
|
||||
<input class="ed-title-input" type="text" value="Der Sommer in Breslau" readonly style="font-style:normal;color:var(--color-text);">
|
||||
<div class="ed-sep"></div>
|
||||
<div class="ed-toolbar">
|
||||
<div class="ed-tool"><b>B</b></div>
|
||||
<div class="ed-tool"><i>I</i></div>
|
||||
<div class="ed-tool">¶</div>
|
||||
</div>
|
||||
<div class="ed-body" style="font-style:normal;color:var(--color-text);">
|
||||
<p style="margin-bottom:8px;">Es war der Sommer 1927, als Franz Raddatz zum ersten Mal nach Breslau reiste. Die Briefe, die er seiner Frau Emma in diesen Wochen schickte, zeugen von einer tiefen Heimweh und gleichzeitig einer neuen Faszination für die lebhafte Stadt an der Oder.</p>
|
||||
<p style="margin-bottom:8px;">Die Kinder warteten zu Hause in Lauban. Emma hielt die Familie zusammen, wie sie es immer getan hatte — mit Ruhe und einer unerschütterlichen Zuversicht, die aus jedem Satz ihrer Antwortbriefe sprach.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ed-sidebar">
|
||||
<div class="ed-sb-section">
|
||||
<div class="ed-sb-title">Personen</div>
|
||||
<div class="ed-search-row">
|
||||
<span style="font-size:9px;color:var(--color-text-muted);">🔍</span>
|
||||
<div class="ed-search-input">Person suchen…</div>
|
||||
</div>
|
||||
<div class="ed-chips-wrap" style="margin-top:4px;">
|
||||
<span class="ed-chip">Franz Raddatz <span class="ed-chip-x">×</span></span>
|
||||
<span class="ed-chip">Emma Müller <span class="ed-chip-x">×</span></span>
|
||||
</div>
|
||||
<div class="ed-hint" style="margin-top:4px;">2 Personen verknüpft</div>
|
||||
</div>
|
||||
<div class="ed-sb-divider"></div>
|
||||
|
||||
<div class="ed-sb-section">
|
||||
<div class="ed-sb-title">Dokumente</div>
|
||||
<div class="ed-search-row">
|
||||
<span style="font-size:9px;color:var(--color-text-muted);">🔍</span>
|
||||
<div class="ed-search-input">Dokument suchen…</div>
|
||||
</div>
|
||||
<div class="ed-chips-wrap" style="margin-top:4px;">
|
||||
<span class="ed-chip doc-chip">Brief vom 12. Juli 1938 <span class="ed-chip-x" style="color:var(--blue-dark);">×</span></span>
|
||||
</div>
|
||||
<div class="ed-hint" style="margin-top:4px;">1 Dokument verknüpft</div>
|
||||
</div>
|
||||
<div class="ed-sb-divider"></div>
|
||||
|
||||
<div class="ed-sb-section">
|
||||
<div class="ed-sb-title">Status</div>
|
||||
<div class="ed-status-pill ed-status-draft" style="font-size:9px;">ENTWURF</div>
|
||||
<div class="ed-hint" style="margin-top:6px;">Noch nicht öffentlich sichtbar.</div>
|
||||
</div>
|
||||
|
||||
<div class="ed-sb-section" style="border-top:1px solid #e4e2d7;">
|
||||
<div class="ed-sb-title">Zuletzt gespeichert</div>
|
||||
<div style="font-size:8px;color:var(--color-text-muted);">Heute, 14:32 Uhr</div>
|
||||
<div style="font-size:8px;color:var(--color-text-muted);margin-top:2px;">von Marcel Raddatz</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ed-savebar">
|
||||
<span class="ed-savebar-hint">Alle Änderungen werden als Entwurf gespeichert.</span>
|
||||
<div class="ed-savebar-actions">
|
||||
<button class="ed-btn-ghost">Entwurf speichern</button>
|
||||
<button class="ed-btn-primary">Veröffentlichen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════
|
||||
SCREEN W-3: VERÖFFENTLICHTE GESCHICHTE BEARBEITEN
|
||||
════════════════════════════════════════ -->
|
||||
<div class="scr">
|
||||
<div class="scr-head">
|
||||
<h3>W-3 — Veröffentlichte Geschichte bearbeiten /geschichten/[id]/edit</h3>
|
||||
<span class="scr-id">US-BLOG-003</span>
|
||||
</div>
|
||||
<div class="scr-desc">Gleiche Layout-Struktur, aber Status VERÖFFENTLICHT. Die Speicherleiste zeigt andere Aktionen. Der "Löschen"-Link erscheint in der Topbar (Geschichte existiert bereits). Änderungen sind sofort live.</div>
|
||||
<div class="scr-var"><strong>Desktop</strong> · Publish-Flow ist umgekehrt: Retract statt Publish</div>
|
||||
|
||||
<div class="previews">
|
||||
<div class="prev-col">
|
||||
<span class="bp-lbl">Desktop — 1040 px</span>
|
||||
<div class="desk desk-editor">
|
||||
<div class="fa-nav">
|
||||
<span class="fa-logo">FAMILIENARCHIV</span>
|
||||
<span class="fa-link">Dokumente</span>
|
||||
<span class="fa-link">Personen</span>
|
||||
<span class="fa-link active">Geschichten</span>
|
||||
<span class="fa-link">Gespräche</span>
|
||||
<div class="fa-nav-r"><div class="fa-av">MR</div></div>
|
||||
</div>
|
||||
|
||||
<div class="ed-topbar">
|
||||
<div class="ed-back">←</div>
|
||||
<div class="ed-title-label">Der Sommer in Breslau</div>
|
||||
<div class="ed-status-pill ed-status-pub">VERÖFFENTLICHT</div>
|
||||
<a class="ed-delete-link">Löschen</a>
|
||||
</div>
|
||||
|
||||
<div class="ed-split" style="flex:1;overflow:hidden;">
|
||||
<div class="ed-left">
|
||||
<input class="ed-title-input" type="text" value="Der Sommer in Breslau" readonly style="color:var(--color-text);">
|
||||
<div class="ed-sep"></div>
|
||||
<div class="ed-toolbar">
|
||||
<div class="ed-tool"><b>B</b></div>
|
||||
<div class="ed-tool"><i>I</i></div>
|
||||
<div class="ed-tool">¶</div>
|
||||
</div>
|
||||
<div class="ed-body" style="color:var(--color-text);">
|
||||
<p style="margin-bottom:8px;">Es war der Sommer 1927, als Franz Raddatz zum ersten Mal nach Breslau reiste. Die Briefe, die er seiner Frau Emma in diesen Wochen schickte, zeugen von einer tiefen Heimweh und gleichzeitig einer neuen Faszination für die lebhafte Stadt an der Oder.</p>
|
||||
<p style="margin-bottom:8px;">Die Kinder warteten zu Hause in Lauban. Emma hielt die Familie zusammen, wie sie es immer getan hatte — mit Ruhe und einer unerschütterlichen Zuversicht, die aus jedem Satz ihrer Antwortbriefe sprach.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ed-sidebar">
|
||||
<div class="ed-sb-section">
|
||||
<div class="ed-sb-title">Personen</div>
|
||||
<div class="ed-search-row">
|
||||
<span style="font-size:9px;color:var(--color-text-muted);">🔍</span>
|
||||
<div class="ed-search-input">Person suchen…</div>
|
||||
</div>
|
||||
<div class="ed-chips-wrap" style="margin-top:4px;">
|
||||
<span class="ed-chip">Franz Raddatz <span class="ed-chip-x">×</span></span>
|
||||
<span class="ed-chip">Emma Müller <span class="ed-chip-x">×</span></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ed-sb-divider"></div>
|
||||
|
||||
<div class="ed-sb-section">
|
||||
<div class="ed-sb-title">Dokumente</div>
|
||||
<div class="ed-search-row">
|
||||
<span style="font-size:9px;color:var(--color-text-muted);">🔍</span>
|
||||
<div class="ed-search-input">Dokument suchen…</div>
|
||||
</div>
|
||||
<div class="ed-chips-wrap" style="margin-top:4px;">
|
||||
<span class="ed-chip doc-chip">Brief vom 12. Juli 1938 <span class="ed-chip-x" style="color:var(--blue-dark);">×</span></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ed-sb-divider"></div>
|
||||
|
||||
<div class="ed-sb-section">
|
||||
<div class="ed-sb-title">Status</div>
|
||||
<div class="ed-status-pill ed-status-pub" style="font-size:9px;">VERÖFFENTLICHT</div>
|
||||
<div class="ed-hint" style="margin-top:6px;color:var(--green-dark);">Öffentlich sichtbar für alle Leser.</div>
|
||||
</div>
|
||||
|
||||
<div class="ed-sb-section" style="border-top:1px solid #e4e2d7;">
|
||||
<div class="ed-sb-title">Veröffentlicht am</div>
|
||||
<div style="font-size:8px;color:var(--color-text-muted);">28. April 2026</div>
|
||||
<div style="font-size:8px;color:var(--color-text-muted);margin-top:2px;">von Marcel Raddatz</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="ed-savebar">
|
||||
<span class="ed-savebar-hint">Änderungen sind sofort live.</span>
|
||||
<div class="ed-savebar-actions">
|
||||
<button class="ed-btn-ghost retract">Zurück zu Entwurf</button>
|
||||
<button class="ed-btn-primary">Speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="agent" style="margin-top:20px;">
|
||||
<h4>impl-ref · W-3 — Veröffentlicht-Zustand</h4>
|
||||
<table class="at">
|
||||
<thead>
|
||||
<tr><th>Element</th><th>Wert / Klassen</th><th>Anmerkung</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>VERÖFFENTLICHT-Badge</td><td><code>rounded-full bg-green-100 text-green-800 text-[10px] font-bold px-2 py-0.5 uppercase tracking-wide</code></td><td>Grüne Pill, kein Rahmen</td></tr>
|
||||
<tr><td>"Speichern"-Button</td><td><code>rounded bg-primary text-primary-fg px-4 py-2 text-sm font-medium</code></td><td>Immer aktiv, speichert sofort live</td></tr>
|
||||
<tr><td>"Zurück zu Entwurf"</td><td><code>rounded border border-line px-4 py-2 text-sm font-medium text-amber-700 hover:bg-amber-50</code></td><td>Setzt status=DRAFT; Bestätigung optional</td></tr>
|
||||
<tr><td>"Löschen"-Link</td><td><code>text-sm text-red-600 font-medium hover:underline</code></td><td>Nur sichtbar wenn Geschichte existiert; öffnet Bestätigungs-Dialog</td></tr>
|
||||
<tr><td>Save-Bar-Hinweis</td><td>"Änderungen sind sofort live." (xs, muted)</td><td>Ersetzt den ENTWURF-Hinweis</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════
|
||||
SCREEN W-4: MOBILE EDITOR
|
||||
════════════════════════════════════════ -->
|
||||
<div class="scr">
|
||||
<div class="scr-head">
|
||||
<h3>W-4 — Mobile Ansicht /geschichten/new</h3>
|
||||
<span class="scr-id">US-BLOG-001 · 320 px</span>
|
||||
</div>
|
||||
<div class="scr-desc">Gestapeltes Layout ohne horizontale Teilung. Sidebar-Inhalte werden in einem ausklappbaren Bereich unterhalb des Body-Editors angezeigt. Speicherleiste mit vertikal gestapelten Buttons.</div>
|
||||
<div class="scr-var"><strong>Phone</strong> · 320 px · Sidebar kollabiert; kein Split</div>
|
||||
|
||||
<div class="previews">
|
||||
<div class="prev-col">
|
||||
<span class="bp-lbl">Phone — 320 px</span>
|
||||
<div class="phone" style="min-height:580px;">
|
||||
<!-- Status bar -->
|
||||
<div class="pst"><b>9:41</b><span>●●●●</span></div>
|
||||
|
||||
<!-- FA nav -->
|
||||
<div class="fa-nav" style="height:28px;">
|
||||
<span class="fa-logo" style="font-size:6px;">FAMILIENARCHIV</span>
|
||||
<div class="fa-nav-r"><div class="fa-av">MR</div></div>
|
||||
</div>
|
||||
|
||||
<div class="pb" style="background:var(--color-page);">
|
||||
<!-- Mobile top bar -->
|
||||
<div class="mob-topbar">
|
||||
<div class="mob-back">←</div>
|
||||
<div class="mob-title-label">Neue Geschichte</div>
|
||||
<div class="ed-status-pill ed-status-draft" style="font-size:7px;padding:2px 6px;">ENTWURF</div>
|
||||
</div>
|
||||
|
||||
<!-- Scrollable body -->
|
||||
<div class="mob-body">
|
||||
<input class="mob-title-input" type="text" placeholder="Titel der Geschichte" readonly>
|
||||
<div class="mob-toolbar">
|
||||
<div class="mob-tool"><b>B</b></div>
|
||||
<div class="mob-tool"><i>I</i></div>
|
||||
<div class="mob-tool">¶</div>
|
||||
</div>
|
||||
<div class="mob-ed-body">Schreibe hier deine Geschichte…</div>
|
||||
|
||||
<!-- Collapsible sidebar on mobile -->
|
||||
<div class="mob-collapsible">
|
||||
<div class="mob-coll-hdr">
|
||||
Personen & Dokumente
|
||||
<span class="mob-coll-chevron">›</span>
|
||||
</div>
|
||||
<!-- collapsed by default; below shows expanded state for spec clarity -->
|
||||
<div style="padding:8px 10px;border-top:1px solid #e4e2d7;display:flex;flex-direction:column;gap:8px;">
|
||||
<div>
|
||||
<div style="font-size:8px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--color-text-muted);margin-bottom:5px;">Personen</div>
|
||||
<div class="ed-search-row" style="margin-bottom:0;">
|
||||
<span style="font-size:9px;color:var(--color-text-muted);">🔍</span>
|
||||
<div class="ed-search-input">Person suchen…</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-size:8px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--color-text-muted);margin-bottom:5px;">Dokumente</div>
|
||||
<div class="ed-search-row" style="margin-bottom:0;">
|
||||
<span style="font-size:9px;color:var(--color-text-muted);">🔍</span>
|
||||
<div class="ed-search-input">Dokument suchen…</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Mobile save bar: stacked buttons -->
|
||||
<div class="mob-savebar">
|
||||
<button class="mob-btn-full mob-btn-ghost-full">Entwurf speichern</button>
|
||||
<button class="mob-btn-full mob-btn-primary-full">Veröffentlichen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="agent" style="margin-top:20px;">
|
||||
<h4>impl-ref · W-4 — Mobile</h4>
|
||||
<table class="at">
|
||||
<thead>
|
||||
<tr><th>Element</th><th>Wert / Klassen</th><th>Anmerkung</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Editor-Layout (Mobile)</td><td><code>flex flex-col h-screen</code></td><td>Kein horizontaler Split</td></tr>
|
||||
<tr><td>Sidebar-Collapsible</td><td><code><details></code> oder Svelte <code>bind:open</code></td><td>Standardmäßig geschlossen; Chevron dreht sich</td></tr>
|
||||
<tr><td>Save-Bar Mobile</td><td><code>flex flex-col gap-2 p-3 bg-white border-t</code></td><td>Buttons vertikal gestapelt, volle Breite</td></tr>
|
||||
<tr><td>"Entwurf speichern" Mobile</td><td><code>w-full rounded border border-line py-2 text-sm font-medium text-ink</code></td><td>Ghost, volle Breite</td></tr>
|
||||
<tr><td>"Veröffentlichen" Mobile</td><td><code>w-full rounded bg-primary text-primary-fg py-2 text-sm font-medium</code></td><td>Primary, volle Breite, oben</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════
|
||||
SCREEN W-5: LÖSCHEN-BESTÄTIGUNG
|
||||
════════════════════════════════════════ -->
|
||||
<div class="scr">
|
||||
<div class="scr-head">
|
||||
<h3>W-5 — Lösch-Bestätigung (Dialog)</h3>
|
||||
<span class="scr-id">US-BLOG-006</span>
|
||||
</div>
|
||||
<div class="scr-desc">Modal-Dialog über dem Editor (W-3-Zustand: veröffentlichte Geschichte). Dunkles Scrim überlagert den Editor. Dialog liegt zentriert darüber. Kein custom Dialog — Wiederverwendung des bestehenden confirm-Service.</div>
|
||||
<div class="scr-var"><strong>Desktop</strong> · Ausgelöst durch Klick auf "Löschen" in der Topbar</div>
|
||||
|
||||
<div class="previews">
|
||||
<div class="prev-col">
|
||||
<span class="bp-lbl">Desktop — 1040 px (mit Modal)</span>
|
||||
<div class="desk desk-editor" style="position:relative;">
|
||||
<!-- Underlying editor (W-3 state, dimmed) -->
|
||||
<div class="fa-nav">
|
||||
<span class="fa-logo">FAMILIENARCHIV</span>
|
||||
<span class="fa-link">Dokumente</span>
|
||||
<span class="fa-link">Personen</span>
|
||||
<span class="fa-link active">Geschichten</span>
|
||||
<span class="fa-link">Gespräche</span>
|
||||
<div class="fa-nav-r"><div class="fa-av">MR</div></div>
|
||||
</div>
|
||||
<div class="ed-topbar" style="opacity:.4;">
|
||||
<div class="ed-back">←</div>
|
||||
<div class="ed-title-label">Der Sommer in Breslau</div>
|
||||
<div class="ed-status-pill ed-status-pub">VERÖFFENTLICHT</div>
|
||||
<a class="ed-delete-link">Löschen</a>
|
||||
</div>
|
||||
<div class="ed-split" style="flex:1;overflow:hidden;opacity:.4;">
|
||||
<div class="ed-left">
|
||||
<input class="ed-title-input" type="text" value="Der Sommer in Breslau" readonly style="color:var(--color-text);">
|
||||
<div class="ed-sep"></div>
|
||||
<div class="ed-body" style="color:var(--color-text);">
|
||||
<p style="margin-bottom:8px;">Es war der Sommer 1927, als Franz Raddatz zum ersten Mal nach Breslau reiste…</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ed-sidebar">
|
||||
<div class="ed-sb-section">
|
||||
<div class="ed-sb-title">Personen</div>
|
||||
<div class="ed-chips-wrap" style="margin-top:4px;">
|
||||
<span class="ed-chip">Franz Raddatz</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ed-savebar" style="opacity:.4;">
|
||||
<span class="ed-savebar-hint">Änderungen sind sofort live.</span>
|
||||
<div class="ed-savebar-actions">
|
||||
<button class="ed-btn-ghost retract">Zurück zu Entwurf</button>
|
||||
<button class="ed-btn-primary">Speichern</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Scrim + Dialog overlay -->
|
||||
<div style="position:absolute;inset:0;background:rgba(28,28,24,.46);display:flex;align-items:center;justify-content:center;border-radius:var(--radius-xl);">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-title">Geschichte löschen?</div>
|
||||
<div class="modal-body">Diese Aktion kann nicht rückgängig gemacht werden. Die Geschichte wird dauerhaft gelöscht und aus allen verlinkten Personen- und Dokumentseiten entfernt.</div>
|
||||
<div class="modal-actions">
|
||||
<button class="ed-btn-ghost" style="font-size:9px;padding:5px 12px;">Abbrechen</button>
|
||||
<button class="ed-btn-danger" style="font-size:9px;padding:5px 12px;">Löschen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="agent" style="margin-top:20px;">
|
||||
<h4>impl-ref · W-5 — Lösch-Dialog</h4>
|
||||
<table class="at">
|
||||
<thead>
|
||||
<tr><th>Element</th><th>Wert / Anmerkung</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Dialog-Implementierung</td><td>Wiederverwendung <code>getConfirmService()</code> aus <code>$lib/services/confirm.svelte.js</code> — kein custom Dialog nötig</td></tr>
|
||||
<tr><td>Scrim</td><td><code>fixed inset-0 bg-black/40 z-40 flex items-center justify-center</code></td></tr>
|
||||
<tr><td>Dialog-Box</td><td><code>bg-white rounded-lg shadow-overlay w-[400px] p-6 flex flex-col gap-4 z-50</code></td></tr>
|
||||
<tr><td>Titel</td><td><code>font-serif text-[18px] font-medium text-ink</code></td></tr>
|
||||
<tr><td>"Abbrechen"</td><td>Ghost-Button — schließt Dialog, kein State-Wechsel</td></tr>
|
||||
<tr><td>"Löschen"</td><td><code>rounded bg-red-600 text-white px-4 py-2 text-sm font-medium hover:bg-red-700</code> — DELETE /api/geschichten/{id}, dann redirect /geschichten</td></tr>
|
||||
<tr><td>Nach Löschen</td><td>Redirect auf <code>/geschichten</code> (Index), Toast "Geschichte gelöscht"</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ════════════════════════════════════════
|
||||
SECTION: LLM GUIDE
|
||||
════════════════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="section-title">Implementierungs-Guide für LLMs</div>
|
||||
|
||||
<p class="prose">Alle fünf Screens teilen dieselbe Svelte-Komponente <code style="font-family:var(--font-mono);font-size:12px;color:var(--navy);">GeschichteEditor.svelte</code>. Der Unterschied zwischen /geschichten/new und /geschichten/[id]/edit liegt ausschließlich in den Load-Daten und im initialen Status-Zustand.</p>
|
||||
|
||||
<!-- Route table -->
|
||||
<table class="routes-table" style="margin-bottom:24px;">
|
||||
<thead>
|
||||
<tr><th>Route</th><th>Komponente</th><th>Load-Funktion</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code>/geschichten/new</code></td>
|
||||
<td><code>GeschichteEditor.svelte</code></td>
|
||||
<td>Kein Load nötig — leerer Zustand, status=DRAFT</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>/geschichten/[id]/edit</code></td>
|
||||
<td><code>GeschichteEditor.svelte</code></td>
|
||||
<td>GET /api/geschichten/{id} → Geschichte by id; wirft 404 wenn nicht gefunden</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="agent">
|
||||
<h4>Technische Entscheidungen</h4>
|
||||
<table class="at">
|
||||
<thead>
|
||||
<tr><th>Thema</th><th>Entscheidung</th><th>Begründung</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="grp"><td colspan="3">Rich-Text-Editor</td></tr>
|
||||
<tr>
|
||||
<td>MVP-Implementierung</td>
|
||||
<td>Minimales <code>contenteditable</code> div oder <code><textarea></code> mit <code>document.execCommand</code> für B/I/¶</td>
|
||||
<td>Issue #381 erfordert nur Bold, Italic, Absatzumbrüche — keine Bibliothek, kein Bundle-Overhead</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Persistenz-Format</td>
|
||||
<td>HTML-String im Backend (VARCHAR / TEXT)</td>
|
||||
<td>Einfachstes Format; bei Bedarf später auf Markdown oder ProseMirror JSON migrierbar</td>
|
||||
</tr>
|
||||
<tr class="grp"><td colspan="3">Personen & Dokumente</td></tr>
|
||||
<tr>
|
||||
<td>PersonMultiSelect</td>
|
||||
<td>Direktes Wiederverwenden von <code>$lib/components/PersonMultiSelect.svelte</code></td>
|
||||
<td>Identisches Pattern wie im Dokument-Bearbeitungsformular — kein neues Rad erfinden</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dokument-Typeahead</td>
|
||||
<td>Neue Komponente <code>$lib/components/DocumentTypeahead.svelte</code></td>
|
||||
<td>GET /api/documents?search= — gleicher Aufbau wie PersonTypeahead; Chips zeigen Titel + Datum</td>
|
||||
</tr>
|
||||
<tr class="grp"><td colspan="3">Permissions</td></tr>
|
||||
<tr>
|
||||
<td>Route Guard</td>
|
||||
<td>Server-seitiger Check in +page.server.ts: wenn User kein BLOG_WRITE → redirect /geschichten</td>
|
||||
<td>Niemals Editor-Controls in Lese-Ansichten zeigen; Client-seitige Prüfung reicht nicht</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Autorschaft</td>
|
||||
<td>Jeder BLOG_WRITER kann jede Geschichte bearbeiten; <code>author</code>-Feld ist nur Anzeige</td>
|
||||
<td>Familienarchiv ist kein Blog mit privaten Drafts; Kollaboration ist erwünscht</td>
|
||||
</tr>
|
||||
<tr class="grp"><td colspan="3">Status-Logik</td></tr>
|
||||
<tr>
|
||||
<td>Publish-Action</td>
|
||||
<td>PATCH /api/geschichten/{id} mit <code>{ "status": "PUBLISHED" }</code></td>
|
||||
<td>Kein separater Endpunkt nötig — Status ist ein Feld des Modells</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Retract-Action</td>
|
||||
<td>PATCH /api/geschichten/{id} mit <code>{ "status": "DRAFT" }</code></td>
|
||||
<td>Umkehrbar; keine separate Bestätigung (anders als Löschen)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>"Löschen"-Sichtbarkeit</td>
|
||||
<td>Nur sichtbar wenn <code>data.geschichte !== null</code> (d.h. Edit-Route, nicht New-Route)</td>
|
||||
<td>Kein Löschen für nicht-existierende Geschichten</td>
|
||||
</tr>
|
||||
<tr class="grp"><td colspan="3">Löschen</td></tr>
|
||||
<tr>
|
||||
<td>Bestätigungs-Dialog</td>
|
||||
<td>Wiederverwendung <code>getConfirmService()</code> aus <code>$lib/services/confirm.svelte.js</code></td>
|
||||
<td>Kein custom Dialog; bereits im Projekt vorhanden</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Nach DELETE</td>
|
||||
<td>DELETE /api/geschichten/{id} → 204 → redirect /geschichten + Toast</td>
|
||||
<td>Standard-Muster wie bei Personen und Dokumenten</td>
|
||||
</tr>
|
||||
<tr class="grp"><td colspan="3">Mobile Responsive</td></tr>
|
||||
<tr>
|
||||
<td>Breakpoint</td>
|
||||
<td>Unter 640 px (sm): Split aufheben, Sidebar als Collapsible</td>
|
||||
<td>Transcribers (60+) auf Laptop/Tablet; Reader (jünger) auf Phones — Responsive für Writer ist Minor</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Collapsible-Trigger</td>
|
||||
<td>"Personen & Dokumente" mit Chevron; standardmäßig geschlossen</td>
|
||||
<td>Body-Editor hat Priorität; Metadaten sind sekundär auf kleinen Bildschirmen</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /.doc -->
|
||||
</body>
|
||||
</html>
|
||||
786
docs/specs/inline-transcription-spec.html
Normal file
786
docs/specs/inline-transcription-spec.html
Normal file
@@ -0,0 +1,786 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<title>Inline Collaborative Transcription — 4 Mockups</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,300;9..144,400;9..144,500&family=DM+Sans:wght@300;400;500;600&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet"/>
|
||||
<style>
|
||||
:root{--color-page:#FAFAF7;--color-surface:#F5F4EE;--color-subtle:#EDECEA;--color-border:#D8D7D0;--color-text-muted:#6B6A63;--color-text:#1C1C18;--navy:#012851;--mint:#A1DCD8;--sand:#F0EFE9;--turquoise:#00C7B1;--accent-bg:rgba(161,220,216,.12);--blue-tint:#E6F1FB;--blue:#2D7DD2;--blue-dark:#185FA5;--purple-tint:#EEEDFE;--purple:#534AB7;--purple-dark:#3C3489;--green-tint:#E8F5EA;--green:#3D8C4A;--green-dark:#2E6E39;--orange-tint:#FEF0E6;--orange:#E8862A;--orange-dark:#B46820;--yellow-tint:#FDF6D8;--yellow-text:#8A6800;--color-error:#DC4C3E;--font-display:'Fraunces',Georgia,serif;--font-sans:'DM Sans',system-ui,sans-serif;--font-mono:'DM Mono',monospace;--radius-sm:4px;--radius-md:6px;--radius-lg:10px;--radius-xl:16px;--shadow-card:0 1px 3px rgba(28,28,24,.06),0 1px 2px rgba(28,28,24,.04);--shadow-raised:0 4px 12px rgba(28,28,24,.08),0 2px 4px rgba(28,28,24,.04);--shadow-overlay:0 8px 32px rgba(28,28,24,.12),0 2px 8px rgba(28,28,24,.06);}
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
|
||||
body{font-family:var(--font-sans);background:#E8E7E2;color:var(--color-text);font-size:14px;line-height:1.6;}
|
||||
.doc{max-width:1200px;margin:0 auto;padding:48px 40px 120px;}
|
||||
|
||||
/* Header */
|
||||
.doc-header{display:flex;justify-content:space-between;align-items:flex-end;padding-bottom:28px;border-bottom:1px solid var(--color-border);margin-bottom:48px;background:var(--color-page);margin:-48px -40px 48px;padding:48px 40px 28px;border-radius:var(--radius-xl) var(--radius-xl) 0 0;}
|
||||
.doc-header h1{font-family:var(--font-display);font-size:28px;font-weight:500;letter-spacing:-.02em;margin-bottom:4px;}
|
||||
.doc-header p{font-size:13px;color:var(--color-text-muted);}
|
||||
.doc-meta{font-family:var(--font-mono);font-size:11px;color:var(--color-text-muted);text-align:right;line-height:1.9;}
|
||||
.pill{display:inline-block;padding:2px 8px;border-radius:var(--radius-sm);font-size:10px;font-weight:500;letter-spacing:.05em;}
|
||||
.pill-b{background:var(--blue-tint);color:var(--blue-dark);}
|
||||
|
||||
/* Sections */
|
||||
.section{margin-bottom:64px;}
|
||||
.section-title{font-size:10px;font-weight:500;letter-spacing:.12em;text-transform:uppercase;color:var(--color-text-muted);padding-bottom:10px;border-bottom:1px solid var(--color-border);margin-bottom:24px;}
|
||||
.prose{font-size:13px;color:var(--color-text-muted);line-height:1.65;max-width:720px;margin-bottom:20px;}
|
||||
|
||||
/* Journey headers */
|
||||
.jh{padding:20px 24px;border-radius:var(--radius-xl);margin-bottom:40px;display:flex;align-items:center;gap:16px;}
|
||||
.jh .jn{font-family:var(--font-display);font-size:48px;font-weight:300;line-height:1;opacity:.5;}
|
||||
.jh h2{font-family:var(--font-display);font-size:22px;font-weight:500;letter-spacing:-.02em;margin-bottom:4px;}
|
||||
.jh p{font-size:13px;line-height:1.5;}.jh .fl{font-family:var(--font-mono);font-size:11px;margin-top:6px;opacity:.7;}
|
||||
.jh-b{background:var(--blue-tint);border:1px solid #A4CFF4;}.jh-b .jn{color:var(--blue);}.jh-b p,.jh-b .fl{color:var(--blue-dark);}
|
||||
|
||||
/* Screen block */
|
||||
.scr{margin-bottom:56px;}
|
||||
.scr-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:6px;}
|
||||
.scr-head h3{font-family:var(--font-display);font-size:20px;font-weight:500;letter-spacing:-.02em;}
|
||||
.scr-id{font-family:var(--font-mono);font-size:11px;color:var(--color-text-muted);padding:2px 8px;border:1px solid var(--color-border);border-radius:var(--radius-sm);background:var(--color-page);}
|
||||
.scr-desc{font-size:12px;color:var(--color-text-muted);line-height:1.6;max-width:720px;margin-bottom:6px;}
|
||||
.scr-var{font-size:11px;color:var(--color-text-muted);margin-bottom:20px;}.scr-var strong{color:var(--color-text);}
|
||||
|
||||
/* Preview container */
|
||||
.previews{display:flex;gap:32px;flex-wrap:wrap;justify-content:center;align-items:flex-start;margin-bottom:20px;}
|
||||
.prev-col{display:flex;flex-direction:column;align-items:center;gap:10px;}
|
||||
.bp-lbl{font-family:var(--font-mono);font-size:10px;color:var(--color-text-muted);}
|
||||
|
||||
/* Phone frame */
|
||||
.phone{width:320px;flex-shrink:0;background:var(--color-page);border-radius:36px;overflow:hidden;box-shadow:var(--shadow-overlay),0 0 0 1px rgba(0,0,0,.07);display:flex;flex-direction:column;border:6px solid #1C1C18;}
|
||||
.pst{padding:10px 20px 0;display:flex;justify-content:space-between;align-items:center;font-size:12px;background:var(--color-page);}.pst b{font-weight:600;}.pst span{font-size:10px;}
|
||||
.pb{flex:1;overflow-y:auto;display:flex;flex-direction:column;}
|
||||
|
||||
/* Desktop frame */
|
||||
.desk{width:100%;max-width:1040px;background:var(--color-page);border-radius:var(--radius-xl);overflow:hidden;box-shadow:var(--shadow-overlay),0 0 0 1px rgba(0,0,0,.06);display:flex;flex-direction:column;min-height:520px;}
|
||||
|
||||
/* Shared FA nav */
|
||||
.fa-nav{height:32px;background:var(--navy);display:flex;align-items:center;padding:0 12px;gap:8px;flex-shrink:0;}
|
||||
.fa-logo{font-size:7px;font-weight:900;color:#fff;letter-spacing:.8px;border-bottom:2px solid var(--mint);padding-bottom:1px;}
|
||||
.fa-link{font-size:5.5px;color:rgba(255,255,255,.4);font-weight:700;text-transform:uppercase;}
|
||||
.fa-nav-r{margin-left:auto;display:flex;gap:5px;align-items:center;}
|
||||
.fa-av{width:16px;height:16px;background:rgba(255,255,255,.1);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:5px;font-weight:800;color:rgba(255,255,255,.5);}
|
||||
|
||||
/* Topbar inside frame */
|
||||
.fa-topbar{background:#fff;border-bottom:1px solid #e4e2d7;display:flex;align-items:center;padding:0 12px;gap:6px;height:42px;flex-shrink:0;}
|
||||
.fa-topbar .back{width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:9px;color:var(--color-text-muted);}
|
||||
.fa-topbar .title{font-family:Georgia,serif;font-size:11px;color:var(--navy);flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
||||
.fa-topbar .date{font-size:8px;color:var(--color-text-muted);}
|
||||
.fa-chip{display:inline-flex;align-items:center;gap:2px;padding:1px 5px 1px 2px;background:var(--sand);border:1px solid #e4e2d7;border-radius:8px;white-space:nowrap;font-size:7px;color:var(--color-text);}
|
||||
.fa-chip .av{width:12px;height:12px;border-radius:50%;background:var(--navy);display:flex;align-items:center;justify-content:center;font-size:5px;font-weight:800;color:var(--mint);}
|
||||
.fa-topbar-btn{font-size:7px;font-weight:600;padding:3px 8px;border-radius:4px;border:1px solid var(--navy);color:var(--navy);background:transparent;display:flex;align-items:center;gap:3px;}
|
||||
.fa-topbar-btn.active{background:var(--navy);color:#fff;border-color:var(--navy);}
|
||||
|
||||
/* PDF area */
|
||||
.pdf-area{background:#D4D0C8;flex:1;display:flex;align-items:center;justify-content:center;position:relative;overflow:hidden;}
|
||||
.pdf-area.dark{background:#08121C;}
|
||||
.paper{background:#FFFEF8;box-shadow:0 2px 8px rgba(0,0,0,.14);border-radius:1px;padding:9px 11px;display:flex;flex-direction:column;gap:2px;}
|
||||
.pl{height:3px;background:#C4BDB0;border-radius:1px;opacity:.5;margin-bottom:2px;}
|
||||
.ps{height:2px;background:#C4BDB0;border-radius:1px;opacity:.28;margin-bottom:1.5px;}
|
||||
|
||||
/* Bottom panel tabs */
|
||||
.bp-tabs{background:#fff;border-top:1px solid #e4e2d7;display:flex;align-items:center;height:24px;padding:0 8px;flex-shrink:0;}
|
||||
.bp-tab{font-size:7px;font-weight:500;padding:0 6px;color:var(--color-text-muted);height:100%;display:flex;align-items:center;border-bottom:2px solid transparent;}
|
||||
.bp-tab.active{color:var(--navy);border-bottom-color:var(--navy);}
|
||||
.bp-badge{margin-left:3px;background:var(--navy);color:#fff;border-radius:6px;padding:0 3px;font-size:5px;font-weight:700;}
|
||||
|
||||
/* Transcription panel */
|
||||
.trans-panel{background:#fff;flex:1;overflow-y:auto;padding:8px 12px;display:flex;flex-direction:column;gap:4px;}
|
||||
.trans-line{display:flex;gap:6px;align-items:flex-start;font-size:9px;line-height:1.6;padding:2px 4px;border-radius:3px;}
|
||||
.trans-line:hover{background:var(--sand);}
|
||||
.trans-ln{font-family:var(--font-mono);font-size:7px;color:var(--color-text-muted);width:16px;text-align:right;flex-shrink:0;padding-top:1px;}
|
||||
.trans-text{flex:1;color:var(--color-text);}
|
||||
.trans-text.editable{background:var(--color-page);border:1px solid var(--color-border);border-radius:3px;padding:1px 4px;cursor:text;}
|
||||
.trans-cursor{display:inline-block;width:1px;height:10px;background:var(--blue);animation:blink 1s infinite;margin-left:1px;}
|
||||
@keyframes blink{0%,50%{opacity:1}51%,100%{opacity:0}}
|
||||
|
||||
/* Presence indicators */
|
||||
.presence{display:flex;align-items:center;gap:3px;font-size:7px;color:var(--color-text-muted);}
|
||||
.presence-dot{width:5px;height:5px;border-radius:50%;}
|
||||
.presence-dot.online{background:var(--green);}
|
||||
.presence-dot.editing{background:var(--blue);}
|
||||
|
||||
/* Highlight colors for multi-user editing */
|
||||
.hl-blue{background:rgba(45,125,210,.1);border-left:2px solid var(--blue);}
|
||||
.hl-purple{background:rgba(83,74,183,.1);border-left:2px solid var(--purple);}
|
||||
.hl-green{background:rgba(61,140,74,.1);border-left:2px solid var(--green);}
|
||||
|
||||
/* Split view */
|
||||
.split{display:flex;flex:1;overflow:hidden;}
|
||||
.split-left{flex:1;display:flex;flex-direction:column;overflow:hidden;position:relative;}
|
||||
.split-right{display:flex;flex-direction:column;overflow:hidden;border-left:1px solid #e4e2d7;}
|
||||
.split-handle{width:4px;background:var(--color-border);cursor:col-resize;flex-shrink:0;display:flex;align-items:center;justify-content:center;}
|
||||
.split-handle::after{content:'';width:2px;height:20px;background:var(--color-text-muted);border-radius:1px;opacity:.3;}
|
||||
|
||||
/* Transcription toolbar */
|
||||
.trans-toolbar{background:#fff;border-bottom:1px solid #e4e2d7;display:flex;align-items:center;padding:4px 8px;gap:6px;flex-shrink:0;}
|
||||
.trans-toolbar .tool-btn{font-size:7px;font-weight:500;color:var(--color-text-muted);padding:2px 6px;border-radius:3px;border:1px solid transparent;cursor:pointer;}
|
||||
.trans-toolbar .tool-btn:hover{background:var(--sand);border-color:var(--color-border);}
|
||||
.trans-toolbar .tool-btn.active{background:var(--accent-bg);color:var(--navy);border-color:var(--mint);}
|
||||
.trans-toolbar .presence-group{margin-left:auto;display:flex;gap:4px;align-items:center;}
|
||||
|
||||
/* Linked highlight on PDF */
|
||||
.pdf-highlight{position:absolute;border:1.5px solid var(--turquoise);background:rgba(0,199,177,.08);border-radius:2px;}
|
||||
.pdf-line-marker{position:absolute;left:0;width:3px;background:var(--turquoise);opacity:.6;}
|
||||
|
||||
/* Mobile transcription */
|
||||
.mob-trans{flex:1;overflow-y:auto;padding:10px 16px;background:#fff;}
|
||||
.mob-trans-line{font-size:11px;line-height:1.7;padding:3px 0;color:var(--color-text);border-bottom:1px solid var(--color-subtle);}
|
||||
.mob-trans-ln{font-family:var(--font-mono);font-size:8px;color:var(--color-text-muted);margin-right:6px;}
|
||||
|
||||
/* Status bar */
|
||||
.status-bar{background:var(--sand);border-top:1px solid #e4e2d7;height:18px;display:flex;align-items:center;padding:0 8px;font-size:7px;color:var(--color-text-muted);gap:8px;flex-shrink:0;}
|
||||
.status-saved{color:var(--green-dark);}
|
||||
|
||||
/* Agent table */
|
||||
.agent{background:var(--color-text);color:#E8E8E2;padding:24px;border-radius:var(--radius-lg);margin-top:20px;}
|
||||
.agent h4{font-size:9px;font-weight:500;letter-spacing:.1em;text-transform:uppercase;color:#5A5A55;margin-bottom:12px;}
|
||||
.agent pre{font-family:var(--font-mono);font-size:10px;color:#444440;margin-bottom:16px;line-height:1.8;white-space:pre-wrap;}
|
||||
.at{width:100%;border-collapse:collapse;font-family:var(--font-mono);font-size:10px;}
|
||||
.at thead tr{border-bottom:1px solid #2A2A26;}.at th{text-align:left;padding:6px 10px;font-size:8px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:#5A5A55;font-family:var(--font-sans);}.at td{padding:5px 10px;border-bottom:1px solid #1E1E1A;vertical-align:top;line-height:1.5;}.at tr:last-child td{border-bottom:none;}.at td:first-child{color:#7A7A72;}.at td:nth-child(2){color:#E8E8E2;font-weight:500;}.at td:nth-child(3){color:#5A5A55;}.at .grp td{padding-top:14px;font-family:var(--font-sans);font-size:8px;font-weight:500;letter-spacing:.08em;text-transform:uppercase;color:#3A3A36;}
|
||||
|
||||
/* LLM instruction box */
|
||||
.llm{background:var(--color-page);border:2px solid var(--navy);border-radius:var(--radius-xl);padding:32px 40px;margin-top:64px;}
|
||||
.llm h2{font-family:var(--font-display);font-size:22px;font-weight:500;letter-spacing:-.02em;margin-bottom:8px;color:var(--navy);}
|
||||
.llm h3{font-size:14px;font-weight:600;margin:20px 0 8px;color:var(--color-text);}
|
||||
.llm p,.llm li{font-size:13px;color:var(--color-text-muted);line-height:1.65;}
|
||||
.llm ul,.llm ol{padding-left:20px;margin-bottom:12px;}
|
||||
.llm li{margin-bottom:4px;}
|
||||
.llm code{font-family:var(--font-mono);font-size:11px;background:var(--color-surface);padding:1px 5px;border-radius:3px;}
|
||||
.llm table{width:100%;border-collapse:collapse;margin:12px 0;font-size:12px;}
|
||||
.llm th,.llm td{text-align:left;padding:6px 10px;border-bottom:1px solid var(--color-border);}
|
||||
.llm th{font-weight:500;color:var(--color-text);font-size:11px;text-transform:uppercase;letter-spacing:.05em;}
|
||||
.llm td{color:var(--color-text-muted);}
|
||||
|
||||
@media(max-width:900px){.doc{padding:24px 16px 80px;}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="doc">
|
||||
|
||||
<div class="doc-header">
|
||||
<div>
|
||||
<h1>Inline Collaborative Transcription</h1>
|
||||
<p>Four layout concepts for side-by-side PDF scan viewing and collaborative transcription editing. Users can follow along the original handwritten letter while multiple family members contribute to and refine the transcript together.</p>
|
||||
</div>
|
||||
<div class="doc-meta">
|
||||
Familienarchiv<br/>
|
||||
<span class="pill pill-b">Exploration</span><br/>
|
||||
2026-04-04 · @leonievoss
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="section">
|
||||
<div class="section-title">Context</div>
|
||||
<p class="prose">The scanned documents are images of handwritten letters — there is no text layer in the PDFs. Family members need to manually transcribe these letters, often collaboratively. The current transcription panel shows read-only text in the bottom panel. These mockups explore how to make transcription a first-class inline editing experience where the scan and transcript are always visible together, and multiple users can contribute simultaneously.</p>
|
||||
<p class="prose">Key constraints: Senior users (60+) need large text and clear visual connection between scan and transcript. The existing annotation system (draw rectangle on PDF + comment thread) stays separate — transcription is a different workflow: line-by-line text entry that maps to the full document, not a specific region.</p>
|
||||
</div>
|
||||
|
||||
<div class="jh jh-b">
|
||||
<div class="jn">T</div>
|
||||
<div><h2>Transcribe a letter</h2><p>Open a scanned letter, read the handwriting, type the transcript line by line while seeing the original. Other family members can join and edit the same transcript in real time.</p><div class="fl">Document detail → Transcribe mode · Any user with WRITE_ALL</div></div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ═══ MOCKUP A — SIDE-BY-SIDE SPLIT ═══ -->
|
||||
<div class="scr" id="a">
|
||||
<div class="scr-head"><h3>A — Side-by-side split</h3><span class="scr-id">A</span></div>
|
||||
<div class="scr-desc">The document detail page gains a vertical split: PDF scan on the left, transcription editor on the right. A draggable divider lets users adjust the ratio. The transcription editor shows line numbers and highlights which line maps to the user's scroll position in the PDF. Multiple users' cursors appear as colored carets.</div>
|
||||
<div class="scr-var"><strong>Desktop split</strong> — PDF left (flex:1), transcript right (400px default, resizable). Mobile: bottom sheet.</div>
|
||||
|
||||
<div class="previews">
|
||||
<div class="prev-col">
|
||||
<div class="bp-lbl">Desktop · 1040px</div>
|
||||
<div class="desk">
|
||||
<div class="fa-nav">
|
||||
<div class="fa-logo">FAMILIENARCHIV</div>
|
||||
<div class="fa-link">Dokumente</div>
|
||||
<div class="fa-link">Personen</div>
|
||||
<div class="fa-nav-r"><div class="fa-av">MR</div></div>
|
||||
</div>
|
||||
<div class="fa-topbar">
|
||||
<div class="back">←</div>
|
||||
<div class="title">Brief von Heinrich an Martha, 14. Mai 1943</div>
|
||||
<div style="flex:1"></div>
|
||||
<div class="fa-chip"><div class="av">HR</div> Heinrich R.</div>
|
||||
<div style="font-size:7px;color:var(--color-text-muted);margin:0 2px;">→</div>
|
||||
<div class="fa-chip"><div class="av" style="background:#5A3080;color:#fff;">MR</div> Martha R.</div>
|
||||
<div style="width:1px;height:16px;background:#e4e2d7;margin:0 6px;"></div>
|
||||
<div class="fa-topbar-btn active">✎ Transkribieren</div>
|
||||
<div class="fa-topbar-btn">Annotieren</div>
|
||||
</div>
|
||||
|
||||
<div class="split" style="height:380px;">
|
||||
<!-- PDF side -->
|
||||
<div class="split-left">
|
||||
<div class="pdf-area" style="flex:1;">
|
||||
<div class="paper" style="width:55%;min-height:200px;">
|
||||
<div style="font-size:7px;color:#8A8070;font-style:italic;margin-bottom:4px;opacity:.7;">Liebe Martha,</div>
|
||||
<div class="pl" style="width:90%;"></div>
|
||||
<div class="ps" style="width:85%;"></div>
|
||||
<div class="ps" style="width:92%;"></div>
|
||||
<div class="pl" style="width:78%;"></div>
|
||||
<div class="ps" style="width:88%;"></div>
|
||||
<div class="ps" style="width:70%;"></div>
|
||||
<div class="pl" style="width:84%;"></div>
|
||||
<div class="ps" style="width:90%;"></div>
|
||||
<div class="ps" style="width:60%;"></div>
|
||||
<div class="pl" style="width:75%;"></div>
|
||||
<div class="ps" style="width:82%;"></div>
|
||||
<div class="ps" style="width:45%;"></div>
|
||||
<div style="font-size:6px;color:#8A8070;margin-top:6px;text-align:right;opacity:.7;">Dein Heinrich</div>
|
||||
</div>
|
||||
<!-- Line markers on PDF showing current transcript position -->
|
||||
<div class="pdf-line-marker" style="top:90px;height:12px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Resize handle -->
|
||||
<div class="split-handle"></div>
|
||||
|
||||
<!-- Transcript side -->
|
||||
<div class="split-right" style="width:380px;">
|
||||
<div class="trans-toolbar">
|
||||
<div class="tool-btn active">Bearbeiten</div>
|
||||
<div class="tool-btn">Vorschau</div>
|
||||
<div class="tool-btn">Verlauf</div>
|
||||
<div class="presence-group">
|
||||
<div class="presence"><div class="presence-dot editing" style="background:var(--blue);"></div> Du</div>
|
||||
<div class="presence"><div class="presence-dot editing" style="background:var(--purple);"></div> Oma Inge</div>
|
||||
<div class="presence"><div class="presence-dot online"></div> Onkel Klaus</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="trans-panel">
|
||||
<div class="trans-line"><div class="trans-ln">1</div><div class="trans-text">Liebe Martha,</div></div>
|
||||
<div class="trans-line"><div class="trans-ln">2</div><div class="trans-text"> </div></div>
|
||||
<div class="trans-line hl-purple"><div class="trans-ln">3</div><div class="trans-text">ich schreibe Dir heute aus dem Lazarett</div></div>
|
||||
<div class="trans-line hl-purple"><div class="trans-ln">4</div><div class="trans-text">in Breslau. Mach Dir keine Sorgen,</div></div>
|
||||
<div class="trans-line"><div class="trans-ln">5</div><div class="trans-text">es geht mir den Umständen</div></div>
|
||||
<div class="trans-line hl-blue"><div class="trans-ln">6</div><div class="trans-text">entsprechend gut. Der Arzt sagt<span class="trans-cursor"></span></div></div>
|
||||
<div class="trans-line"><div class="trans-ln">7</div><div class="trans-text" style="color:var(--color-text-muted);font-style:italic;">[unleserlich]</div></div>
|
||||
<div class="trans-line"><div class="trans-ln">8</div><div class="trans-text">Wochen noch dauern wird.</div></div>
|
||||
<div class="trans-line"><div class="trans-ln">9</div><div class="trans-text"> </div></div>
|
||||
<div class="trans-line"><div class="trans-ln">10</div><div class="trans-text">Die Kinder sollen wissen, dass ich</div></div>
|
||||
<div class="trans-line"><div class="trans-ln">11</div><div class="trans-text">an sie denke. Sag dem kleinen Fritz,</div></div>
|
||||
<div class="trans-line"><div class="trans-ln">12</div><div class="trans-text">er soll auf seine Mutter aufpassen.</div></div>
|
||||
<div class="trans-line"><div class="trans-ln">13</div><div class="trans-text"> </div></div>
|
||||
<div class="trans-line"><div class="trans-ln">14</div><div class="trans-text">In ewiger Liebe,</div></div>
|
||||
<div class="trans-line"><div class="trans-ln">15</div><div class="trans-text">Dein Heinrich</div></div>
|
||||
</div>
|
||||
<div class="status-bar">
|
||||
<span class="status-saved">✓ Gespeichert</span>
|
||||
<span>15 Zeilen</span>
|
||||
<span>Zeile 6, Spalte 34</span>
|
||||
<span style="margin-left:auto;">Zuletzt: Oma Inge, vor 2 Min.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bottom panel tabs (collapsed) -->
|
||||
<div class="bp-tabs">
|
||||
<div class="bp-tab">Metadaten</div>
|
||||
<div class="bp-tab active">Transkription</div>
|
||||
<div class="bp-tab">Diskussion <span class="bp-badge">3</span></div>
|
||||
<div class="bp-tab">Verlauf</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="previews" style="margin-top:20px;">
|
||||
<div class="prev-col">
|
||||
<div class="bp-lbl">Mobile · 320px</div>
|
||||
<div class="phone">
|
||||
<div class="pst"><b>14:23</b><span>●●● WiFi 🔋</span></div>
|
||||
<div class="pb">
|
||||
<!-- Compact topbar -->
|
||||
<div style="background:#fff;border-bottom:1px solid #e4e2d7;padding:8px 12px;">
|
||||
<div style="display:flex;align-items:center;gap:6px;margin-bottom:2px;">
|
||||
<span style="font-size:11px;color:var(--color-text-muted);">←</span>
|
||||
<span style="font-family:Georgia,serif;font-size:11px;color:var(--navy);flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">Brief von Heinrich, 14.05.1943</span>
|
||||
<span style="font-size:8px;font-weight:600;padding:2px 6px;border-radius:3px;background:var(--navy);color:#fff;">Transkr.</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- PDF thumbnail strip -->
|
||||
<div style="background:#D4D0C8;height:100px;display:flex;align-items:center;justify-content:center;border-bottom:2px solid var(--mint);">
|
||||
<div style="background:#FFFEF8;width:45%;padding:6px 8px;box-shadow:0 1px 4px rgba(0,0,0,.12);border-radius:1px;">
|
||||
<div style="font-size:5px;color:#8A8070;font-style:italic;opacity:.7;">Liebe Martha,</div>
|
||||
<div style="height:2px;background:#C4BDB0;opacity:.4;margin:2px 0;width:80%;"></div>
|
||||
<div style="height:1.5px;background:#C4BDB0;opacity:.25;margin:1px 0;width:90%;"></div>
|
||||
<div style="height:1.5px;background:#C4BDB0;opacity:.25;margin:1px 0;width:70%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Transcription editor fullscreen -->
|
||||
<div class="mob-trans" style="flex:1;">
|
||||
<div style="display:flex;align-items:center;gap:4px;margin-bottom:8px;padding-bottom:4px;border-bottom:1px solid var(--color-subtle);">
|
||||
<div class="presence"><div class="presence-dot editing" style="background:var(--blue);"></div></div>
|
||||
<div class="presence"><div class="presence-dot editing" style="background:var(--purple);"></div></div>
|
||||
<span style="font-size:8px;color:var(--color-text-muted);">2 aktiv</span>
|
||||
<span style="font-size:8px;color:var(--green-dark);margin-left:auto;">✓ Gespeichert</span>
|
||||
</div>
|
||||
<div class="mob-trans-line"><span class="mob-trans-ln">1</span>Liebe Martha,</div>
|
||||
<div class="mob-trans-line"><span class="mob-trans-ln">2</span> </div>
|
||||
<div class="mob-trans-line" style="background:rgba(83,74,183,.06);"><span class="mob-trans-ln">3</span>ich schreibe Dir heute aus dem</div>
|
||||
<div class="mob-trans-line" style="background:rgba(83,74,183,.06);"><span class="mob-trans-ln">4</span>Lazarett in Breslau. Mach Dir keine</div>
|
||||
<div class="mob-trans-line"><span class="mob-trans-ln">5</span>Sorgen, es geht mir den</div>
|
||||
<div class="mob-trans-line" style="background:rgba(45,125,210,.06);"><span class="mob-trans-ln">6</span>Umständen entsprechend gut.</div>
|
||||
<div class="mob-trans-line"><span class="mob-trans-ln">7</span><span style="color:var(--color-text-muted);font-style:italic;">[unleserlich]</span></div>
|
||||
<div class="mob-trans-line"><span class="mob-trans-ln">8</span>Wochen noch dauern wird.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="agent">
|
||||
<h4>A · Side-by-side split</h4>
|
||||
<pre>/* Desktop: existing topbar + "Transkribieren" toggle button replaces bottom panel transcription tab.
|
||||
* Activating transcription mode opens a vertical split: PDF left (flex:1) + transcript right (400px default).
|
||||
* Draggable handle between panels (pointer-events on 4px strip, cursor:col-resize).
|
||||
* Transcript toolbar: Edit/Preview/History tabs + presence dots (WebSocket: who is online + who is editing).
|
||||
* Line numbers (mono 12px) + text (serif 16px, editable contenteditable divs or textarea rows).
|
||||
* Colored left-border per active user (2px, distinct hue from a 6-color palette).
|
||||
* "[unleserlich]" placeholder for illegible passages — italic, muted, bracketed.
|
||||
* Status bar: save state (auto-save debounced 2s) + line count + cursor position + last editor.
|
||||
* Mobile: PDF collapses to a fixed-height thumbnail strip (100px) at top, transcript fills remaining space.
|
||||
* Tap thumbnail strip to expand PDF to 60% height (push transcript down). */</pre>
|
||||
<table class="at"><thead><tr><th>Element</th><th>Value</th><th>Notes</th></tr></thead><tbody>
|
||||
<tr class="grp"><td colspan="3">Desktop — split view</td></tr>
|
||||
<tr><td>PDF panel</td><td>flex:1, min-width:300px, bg:#D4D0C8</td><td>Existing PdfViewer component, scroll synced with transcript</td></tr>
|
||||
<tr><td>Transcript panel</td><td>400px default, min:280px, max:60vw, resizable</td><td>border-left:1px solid line, bg:surface</td></tr>
|
||||
<tr><td>Resize handle</td><td>4px wide, cursor:col-resize, bg:line</td><td>Centered dot indicator (2px × 20px)</td></tr>
|
||||
<tr><td>Toolbar</td><td>h:32px, bg:surface, border-bottom, flex row</td><td>Edit/Preview/History + presence group right-aligned</td></tr>
|
||||
<tr><td>Line number</td><td>DM Mono 12px, muted, width:24px, text-align:right</td><td>Sticky on horizontal scroll if transcript is wide</td></tr>
|
||||
<tr><td>Transcript text</td><td>Tinos 16px/1.6, color:ink, contenteditable</td><td>18px for senior mode preference</td></tr>
|
||||
<tr><td>User highlight</td><td>border-left:2px solid [user-color], bg:rgba([user-color],.08)</td><td>6 distinct hues, assigned round-robin</td></tr>
|
||||
<tr><td>Status bar</td><td>h:24px, bg:sand, border-top, mono 11px</td><td>Auto-save indicator + line info + last editor</td></tr>
|
||||
<tr class="grp"><td colspan="3">Mobile</td></tr>
|
||||
<tr><td>PDF strip</td><td>100px fixed height, bg:pdf-bg, border-bottom:2px mint</td><td>Tap to expand to 60vh. Pinch-zoom supported.</td></tr>
|
||||
<tr><td>Transcript</td><td>flex:1, padding:12px 16px, serif 16px</td><td>Line numbers: mono 10px, inline before text</td></tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ═══ MOCKUP B — OVERLAY PANEL (SLIDE-OVER) ═══ -->
|
||||
<div class="scr" id="b">
|
||||
<div class="scr-head"><h3>B — Overlay slide-over panel</h3><span class="scr-id">B</span></div>
|
||||
<div class="scr-desc">The PDF stays full-width. Clicking "Transkribieren" slides a semi-transparent panel from the right over the PDF. The panel has ~60% opacity background so the scan bleeds through underneath, keeping visual context. Users type the transcript line by line with the scan always partially visible behind.</div>
|
||||
<div class="scr-var"><strong>Desktop overlay</strong> — 480px panel, 60% opacity surface bg, slides from right. Mobile: full bottom sheet.</div>
|
||||
|
||||
<div class="previews">
|
||||
<div class="prev-col">
|
||||
<div class="bp-lbl">Desktop · 1040px</div>
|
||||
<div class="desk">
|
||||
<div class="fa-nav">
|
||||
<div class="fa-logo">FAMILIENARCHIV</div>
|
||||
<div class="fa-link">Dokumente</div>
|
||||
<div class="fa-link">Personen</div>
|
||||
<div class="fa-nav-r"><div class="fa-av">MR</div></div>
|
||||
</div>
|
||||
<div class="fa-topbar">
|
||||
<div class="back">←</div>
|
||||
<div class="title">Brief von Heinrich an Martha, 14. Mai 1943</div>
|
||||
<div style="flex:1"></div>
|
||||
<div class="fa-topbar-btn active">✎ Transkribieren</div>
|
||||
</div>
|
||||
|
||||
<div style="flex:1;position:relative;display:flex;height:390px;">
|
||||
<!-- Full PDF background -->
|
||||
<div class="pdf-area" style="flex:1;">
|
||||
<div class="paper" style="width:50%;min-height:220px;">
|
||||
<div style="font-size:7px;color:#8A8070;font-style:italic;margin-bottom:4px;opacity:.7;">Liebe Martha,</div>
|
||||
<div class="pl" style="width:90%;"></div>
|
||||
<div class="ps" style="width:85%;"></div>
|
||||
<div class="ps" style="width:92%;"></div>
|
||||
<div class="pl" style="width:78%;"></div>
|
||||
<div class="ps" style="width:88%;"></div>
|
||||
<div class="ps" style="width:70%;"></div>
|
||||
<div class="pl" style="width:84%;"></div>
|
||||
<div class="ps" style="width:90%;"></div>
|
||||
<div class="ps" style="width:60%;"></div>
|
||||
<div class="pl" style="width:75%;"></div>
|
||||
<div class="ps" style="width:82%;"></div>
|
||||
<div class="ps" style="width:45%;"></div>
|
||||
<div style="font-size:6px;color:#8A8070;margin-top:6px;text-align:right;opacity:.7;">Dein Heinrich</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Slide-over panel -->
|
||||
<div style="position:absolute;top:0;right:0;bottom:0;width:420px;background:rgba(255,255,255,.92);backdrop-filter:blur(8px);border-left:2px solid var(--mint);display:flex;flex-direction:column;box-shadow:-4px 0 24px rgba(0,0,0,.08);">
|
||||
<div class="trans-toolbar">
|
||||
<div class="tool-btn active">Bearbeiten</div>
|
||||
<div class="tool-btn">Vorschau</div>
|
||||
<div class="presence-group">
|
||||
<div class="presence"><div class="presence-dot editing" style="background:var(--blue);"></div> Du</div>
|
||||
<div class="presence"><div class="presence-dot editing" style="background:var(--purple);"></div> Oma Inge</div>
|
||||
</div>
|
||||
<div style="font-size:8px;color:var(--color-text-muted);cursor:pointer;margin-left:4px;">✕</div>
|
||||
</div>
|
||||
<div class="trans-panel" style="background:transparent;">
|
||||
<div class="trans-line"><div class="trans-ln">1</div><div class="trans-text">Liebe Martha,</div></div>
|
||||
<div class="trans-line"><div class="trans-ln">2</div><div class="trans-text"> </div></div>
|
||||
<div class="trans-line hl-purple"><div class="trans-ln">3</div><div class="trans-text">ich schreibe Dir heute aus dem Lazarett</div></div>
|
||||
<div class="trans-line hl-purple"><div class="trans-ln">4</div><div class="trans-text">in Breslau. Mach Dir keine Sorgen,</div></div>
|
||||
<div class="trans-line"><div class="trans-ln">5</div><div class="trans-text">es geht mir den Umständen</div></div>
|
||||
<div class="trans-line hl-blue"><div class="trans-ln">6</div><div class="trans-text">entsprechend gut. Der Arzt sagt<span class="trans-cursor"></span></div></div>
|
||||
<div class="trans-line"><div class="trans-ln">7</div><div class="trans-text" style="color:var(--color-text-muted);font-style:italic;">[unleserlich]</div></div>
|
||||
<div class="trans-line"><div class="trans-ln">8</div><div class="trans-text">Wochen noch dauern wird.</div></div>
|
||||
<div class="trans-line"><div class="trans-ln">9</div><div class="trans-text"> </div></div>
|
||||
<div class="trans-line"><div class="trans-ln">10</div><div class="trans-text">Die Kinder sollen wissen, dass ich</div></div>
|
||||
<div class="trans-line"><div class="trans-ln">11</div><div class="trans-text">an sie denke.</div></div>
|
||||
</div>
|
||||
<div class="status-bar" style="background:rgba(240,239,233,.85);">
|
||||
<span class="status-saved">✓ Gespeichert</span>
|
||||
<span>11 Zeilen</span>
|
||||
<span style="margin-left:auto;">Oma Inge bearbeitet Z. 3-4</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bp-tabs">
|
||||
<div class="bp-tab">Metadaten</div>
|
||||
<div class="bp-tab">Diskussion <span class="bp-badge">3</span></div>
|
||||
<div class="bp-tab">Verlauf</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="agent">
|
||||
<h4>B · Overlay slide-over panel</h4>
|
||||
<pre>/* The PDF viewer stays full-width — no resize, no layout shift.
|
||||
* "Transkribieren" button toggles a slide-over panel from the right (transform: translateX).
|
||||
* Panel: position:absolute, top:0, right:0, bottom:0, width:480px.
|
||||
* Background: rgba(255,255,255,.92) + backdrop-filter:blur(8px) — scan bleeds through slightly.
|
||||
* Left border: 2px solid mint (brand accent) to clearly separate from PDF.
|
||||
* Close button (×) in toolbar top-right to dismiss panel.
|
||||
* Same transcript editor as Mockup A: line numbers + editable lines + presence.
|
||||
* Pro: zero layout shift, PDF zoom/pan unaffected. Con: obscures right side of scan. */</pre>
|
||||
<table class="at"><thead><tr><th>Element</th><th>Value</th><th>Notes</th></tr></thead><tbody>
|
||||
<tr><td>Panel</td><td>480px, position:absolute right:0, z:20</td><td>backdrop-filter:blur(8px), rgba(255,255,255,.92)</td></tr>
|
||||
<tr><td>Left accent</td><td>border-left:2px solid mint</td><td>Brand color boundary</td></tr>
|
||||
<tr><td>Transition</td><td>transform 200ms ease-out</td><td>translateX(100%) → translateX(0)</td></tr>
|
||||
<tr><td>Shadow</td><td>-4px 0 24px rgba(0,0,0,.1)</td><td>Depth cue separating from PDF</td></tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ═══ MOCKUP C — INTERLEAVED (PARAGRAPH-SYNC) ═══ -->
|
||||
<div class="scr" id="c">
|
||||
<div class="scr-head"><h3>C — Interleaved paragraph sync</h3><span class="scr-id">C</span></div>
|
||||
<div class="scr-desc">A single scrollable column interleaves cropped scan regions with their transcription blocks. Each paragraph of the letter is shown as: scan excerpt (cropped from the PDF page) + editable transcript block below it. Scrolling moves through the letter naturally — read the scan, then read/edit the transcript, then the next scan section. Great for very long letters.</div>
|
||||
<div class="scr-var"><strong>Single-column interleaved</strong> — scan crop + transcript block pairs. Works identically on mobile and desktop (just wider).</div>
|
||||
|
||||
<div class="previews">
|
||||
<div class="prev-col">
|
||||
<div class="bp-lbl">Desktop · 1040px</div>
|
||||
<div class="desk" style="min-height:580px;">
|
||||
<div class="fa-nav">
|
||||
<div class="fa-logo">FAMILIENARCHIV</div>
|
||||
<div class="fa-link">Dokumente</div>
|
||||
<div class="fa-link">Personen</div>
|
||||
<div class="fa-nav-r"><div class="fa-av">MR</div></div>
|
||||
</div>
|
||||
<div class="fa-topbar">
|
||||
<div class="back">←</div>
|
||||
<div class="title">Brief von Heinrich an Martha, 14. Mai 1943</div>
|
||||
<div style="flex:1"></div>
|
||||
<div class="presence-group" style="margin-right:8px;">
|
||||
<div class="presence"><div class="presence-dot editing" style="background:var(--blue);"></div> Du</div>
|
||||
<div class="presence"><div class="presence-dot editing" style="background:var(--purple);"></div> Oma Inge</div>
|
||||
</div>
|
||||
<div class="fa-topbar-btn active">✎ Transkribieren</div>
|
||||
</div>
|
||||
|
||||
<div style="flex:1;overflow-y:auto;background:var(--sand);display:flex;justify-content:center;">
|
||||
<div style="width:640px;padding:20px 0;">
|
||||
|
||||
<!-- Section 1: Greeting -->
|
||||
<div style="margin-bottom:16px;">
|
||||
<div style="background:#D4D0C8;border-radius:6px 6px 0 0;padding:12px 16px;display:flex;align-items:center;justify-content:center;min-height:50px;">
|
||||
<div style="background:#FFFEF8;width:80%;padding:8px 10px;box-shadow:0 1px 4px rgba(0,0,0,.1);border-radius:1px;">
|
||||
<div style="font-size:8px;color:#8A8070;font-style:italic;">Liebe Martha,</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="background:#fff;border:1px solid #e4e2d7;border-top:none;border-radius:0 0 6px 6px;padding:10px 14px;">
|
||||
<div style="display:flex;align-items:center;gap:4px;margin-bottom:6px;">
|
||||
<span style="font-size:7px;font-weight:600;color:var(--color-text-muted);text-transform:uppercase;letter-spacing:.08em;">Abschnitt 1 — Anrede</span>
|
||||
<span style="font-size:7px;color:var(--green-dark);margin-left:auto;">✓</span>
|
||||
</div>
|
||||
<div style="font-family:Georgia,serif;font-size:10px;line-height:1.6;color:var(--color-text);">Liebe Martha,</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Section 2: Body paragraph 1 -->
|
||||
<div style="margin-bottom:16px;">
|
||||
<div style="background:#D4D0C8;border-radius:6px 6px 0 0;padding:12px 16px;display:flex;align-items:center;justify-content:center;min-height:70px;">
|
||||
<div style="background:#FFFEF8;width:80%;padding:8px 10px;box-shadow:0 1px 4px rgba(0,0,0,.1);border-radius:1px;">
|
||||
<div class="pl" style="width:90%;"></div>
|
||||
<div class="ps" style="width:85%;"></div>
|
||||
<div class="ps" style="width:92%;"></div>
|
||||
<div class="pl" style="width:78%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="background:#fff;border:1px solid #e4e2d7;border-top:none;border-radius:0 0 6px 6px;padding:10px 14px;">
|
||||
<div style="display:flex;align-items:center;gap:4px;margin-bottom:6px;">
|
||||
<span style="font-size:7px;font-weight:600;color:var(--color-text-muted);text-transform:uppercase;letter-spacing:.08em;">Abschnitt 2 — Hauptteil</span>
|
||||
<div class="presence" style="margin-left:auto;"><div class="presence-dot editing" style="background:var(--purple);width:4px;height:4px;"></div> <span style="font-size:6px;">Oma Inge</span></div>
|
||||
</div>
|
||||
<div style="font-family:Georgia,serif;font-size:10px;line-height:1.6;color:var(--color-text);border-left:2px solid var(--purple);padding-left:8px;background:rgba(83,74,183,.04);border-radius:2px;padding:4px 8px;">ich schreibe Dir heute aus dem Lazarett in Breslau. Mach Dir keine Sorgen, es geht mir den Umständen entsprechend gut. Der Arzt sagt <span style="color:var(--color-text-muted);font-style:italic;">[unleserlich]</span> Wochen noch dauern wird.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Section 3: Body paragraph 2 -->
|
||||
<div style="margin-bottom:16px;">
|
||||
<div style="background:#D4D0C8;border-radius:6px 6px 0 0;padding:12px 16px;display:flex;align-items:center;justify-content:center;min-height:60px;">
|
||||
<div style="background:#FFFEF8;width:80%;padding:8px 10px;box-shadow:0 1px 4px rgba(0,0,0,.1);border-radius:1px;">
|
||||
<div class="pl" style="width:84%;"></div>
|
||||
<div class="ps" style="width:90%;"></div>
|
||||
<div class="ps" style="width:60%;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="background:#fff;border:1px solid #e4e2d7;border-top:none;border-radius:0 0 6px 6px;padding:10px 14px;">
|
||||
<div style="display:flex;align-items:center;gap:4px;margin-bottom:6px;">
|
||||
<span style="font-size:7px;font-weight:600;color:var(--color-text-muted);text-transform:uppercase;letter-spacing:.08em;">Abschnitt 3</span>
|
||||
<div class="presence" style="margin-left:auto;"><div class="presence-dot editing" style="background:var(--blue);width:4px;height:4px;"></div> <span style="font-size:6px;">Du</span></div>
|
||||
</div>
|
||||
<div style="font-family:Georgia,serif;font-size:10px;line-height:1.6;color:var(--color-text);border-left:2px solid var(--blue);padding-left:8px;background:rgba(45,125,210,.04);border-radius:2px;padding:4px 8px;">Die Kinder sollen wissen, dass ich an sie denke. Sag dem kleinen Fritz, er soll auf seine Mutter aufpassen.<span class="trans-cursor"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Section 4: Closing -->
|
||||
<div style="margin-bottom:16px;">
|
||||
<div style="background:#D4D0C8;border-radius:6px 6px 0 0;padding:12px 16px;display:flex;align-items:center;justify-content:center;min-height:40px;">
|
||||
<div style="background:#FFFEF8;width:80%;padding:8px 10px;box-shadow:0 1px 4px rgba(0,0,0,.1);border-radius:1px;">
|
||||
<div style="font-size:6px;color:#8A8070;text-align:right;opacity:.7;">Dein Heinrich</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="background:#fff;border:1px solid #e4e2d7;border-top:none;border-radius:0 0 6px 6px;padding:10px 14px;">
|
||||
<div style="display:flex;align-items:center;gap:4px;margin-bottom:6px;">
|
||||
<span style="font-size:7px;font-weight:600;color:var(--color-text-muted);text-transform:uppercase;letter-spacing:.08em;">Abschnitt 4 — Schluss</span>
|
||||
<span style="font-size:7px;color:var(--green-dark);margin-left:auto;">✓</span>
|
||||
</div>
|
||||
<div style="font-family:Georgia,serif;font-size:10px;line-height:1.6;color:var(--color-text);">In ewiger Liebe,<br/>Dein Heinrich</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="status-bar">
|
||||
<span class="status-saved">✓ Gespeichert</span>
|
||||
<span>4 Abschnitte</span>
|
||||
<span style="margin-left:auto;">2 Bearbeiter aktiv</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="agent">
|
||||
<h4>C · Interleaved paragraph sync</h4>
|
||||
<pre>/* A fundamentally different layout: no split view. Instead, a single centered column (max-width:800px).
|
||||
* Each "section" = a card with two halves:
|
||||
* Top half: cropped region of the PDF scan (rendered via canvas crop or CSS clip-path from the full PDF).
|
||||
* Bottom half: editable transcript text for that section.
|
||||
* User defines section boundaries by drawing horizontal lines on the PDF (like the annotation tool but simpler).
|
||||
* Sections are numbered and labeled (Anrede, Hauptteil, Schluss — or custom labels).
|
||||
* Presence: per-section indicator showing who is editing that block.
|
||||
* Pro: natural reading flow, works great on mobile without layout changes. Con: requires section-splitting UX. */</pre>
|
||||
<table class="at"><thead><tr><th>Element</th><th>Value</th><th>Notes</th></tr></thead><tbody>
|
||||
<tr><td>Column</td><td>max-width:800px, centered, bg:sand, padding:24px</td><td>Scrollable single column</td></tr>
|
||||
<tr><td>Section card</td><td>border-radius:8px, overflow:hidden, mb:20px</td><td>Top: scan crop (pdf-bg). Bottom: surface + transcript.</td></tr>
|
||||
<tr><td>Scan crop</td><td>min-height:80px, bg:pdf-bg, centered paper snippet</td><td>Canvas-based crop from PDF page at defined Y offsets</td></tr>
|
||||
<tr><td>Transcript block</td><td>Tinos 16px/1.7, padding:12px 16px, contenteditable</td><td>Per-section editing. User color left-border when active.</td></tr>
|
||||
<tr><td>Section label</td><td>Montserrat 11px/600, uppercase, tracking-wide, muted</td><td>"Abschnitt 1 — Anrede" etc.</td></tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ═══ MOCKUP D — ENHANCED BOTTOM PANEL ═══ -->
|
||||
<div class="scr" id="d">
|
||||
<div class="scr-head"><h3>D — Enhanced bottom panel with scroll sync</h3><span class="scr-id">D</span></div>
|
||||
<div class="scr-desc">Keeps the existing bottom panel architecture but enhances the "Transkription" tab with inline editing, scroll synchronization to the PDF above, and collaborative presence. The most conservative approach — minimal layout changes from the current design. The panel simply becomes editable instead of read-only, and gains a toolbar + presence indicators.</div>
|
||||
<div class="scr-var"><strong>Existing bottom panel, enhanced</strong> — drag-to-resize remains, adds edit mode + scroll sync + presence. Least disruptive to ship.</div>
|
||||
|
||||
<div class="previews">
|
||||
<div class="prev-col">
|
||||
<div class="bp-lbl">Desktop · 1040px</div>
|
||||
<div class="desk" style="min-height:560px;">
|
||||
<div class="fa-nav">
|
||||
<div class="fa-logo">FAMILIENARCHIV</div>
|
||||
<div class="fa-link">Dokumente</div>
|
||||
<div class="fa-link">Personen</div>
|
||||
<div class="fa-nav-r"><div class="fa-av">MR</div></div>
|
||||
</div>
|
||||
<div class="fa-topbar">
|
||||
<div class="back">←</div>
|
||||
<div class="title">Brief von Heinrich an Martha, 14. Mai 1943</div>
|
||||
<div style="flex:1"></div>
|
||||
<div class="fa-chip"><div class="av">HR</div> Heinrich R.</div>
|
||||
<div style="font-size:7px;color:var(--color-text-muted);margin:0 2px;">→</div>
|
||||
<div class="fa-chip"><div class="av" style="background:#5A3080;color:#fff;">MR</div> Martha R.</div>
|
||||
<div style="width:1px;height:16px;background:#e4e2d7;margin:0 6px;"></div>
|
||||
<div class="fa-topbar-btn">Annotieren</div>
|
||||
<div class="fa-topbar-btn">↓ Herunterladen</div>
|
||||
</div>
|
||||
|
||||
<!-- PDF viewer (upper part) -->
|
||||
<div class="pdf-area" style="height:220px;">
|
||||
<div class="paper" style="width:45%;min-height:140px;">
|
||||
<div style="font-size:7px;color:#8A8070;font-style:italic;margin-bottom:4px;opacity:.7;">Liebe Martha,</div>
|
||||
<div class="pl" style="width:90%;"></div>
|
||||
<div class="ps" style="width:85%;"></div>
|
||||
<div class="ps" style="width:92%;"></div>
|
||||
<div class="pl" style="width:78%;"></div>
|
||||
<div class="ps" style="width:88%;"></div>
|
||||
<div class="ps" style="width:70%;"></div>
|
||||
<div class="pl" style="width:84%;"></div>
|
||||
<div class="ps" style="width:90%;"></div>
|
||||
<div class="ps" style="width:60%;"></div>
|
||||
<div style="font-size:6px;color:#8A8070;margin-top:6px;text-align:right;opacity:.7;">Dein Heinrich</div>
|
||||
</div>
|
||||
<!-- Scroll sync indicator — thin turquoise bar showing which part of PDF maps to current transcript scroll -->
|
||||
<div style="position:absolute;right:0;top:40px;width:3px;height:40px;background:var(--turquoise);border-radius:1px;opacity:.7;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Drag handle -->
|
||||
<div style="height:6px;background:#fff;border-top:1px solid #e4e2d7;display:flex;align-items:center;justify-content:center;cursor:ns-resize;flex-shrink:0;">
|
||||
<div style="width:40px;height:3px;background:#e4e2d7;border-radius:2px;"></div>
|
||||
</div>
|
||||
|
||||
<!-- Enhanced bottom panel — Transcription tab active -->
|
||||
<div style="display:flex;flex-direction:column;flex-shrink:0;height:240px;border-top:1px solid #e4e2d7;">
|
||||
<!-- Tab bar -->
|
||||
<div class="bp-tabs" style="height:28px;">
|
||||
<div class="bp-tab">Metadaten</div>
|
||||
<div class="bp-tab active" style="position:relative;">
|
||||
Transkription
|
||||
<div style="position:absolute;top:2px;right:-2px;width:5px;height:5px;background:var(--turquoise);border-radius:50%;"></div>
|
||||
</div>
|
||||
<div class="bp-tab">Diskussion <span class="bp-badge">3</span></div>
|
||||
<div class="bp-tab">Verlauf</div>
|
||||
<div style="flex:1;"></div>
|
||||
<!-- Presence in tab bar -->
|
||||
<div class="presence" style="margin-right:4px;"><div class="presence-dot editing" style="background:var(--blue);"></div> Du</div>
|
||||
<div class="presence" style="margin-right:4px;"><div class="presence-dot editing" style="background:var(--purple);"></div> Oma Inge</div>
|
||||
</div>
|
||||
|
||||
<!-- Mini toolbar -->
|
||||
<div style="background:#fff;border-bottom:1px solid var(--color-subtle);display:flex;align-items:center;padding:2px 8px;gap:4px;height:22px;flex-shrink:0;">
|
||||
<div class="tool-btn" style="font-size:6px;padding:1px 4px;">Bearbeiten</div>
|
||||
<div class="tool-btn" style="font-size:6px;padding:1px 4px;">Vorschau</div>
|
||||
<div style="width:1px;height:10px;background:var(--color-border);margin:0 2px;"></div>
|
||||
<div class="tool-btn" style="font-size:6px;padding:1px 4px;">[unleserlich]</div>
|
||||
<div class="tool-btn" style="font-size:6px;padding:1px 4px;">[Seitenwechsel]</div>
|
||||
<div style="flex:1;"></div>
|
||||
<span style="font-size:6px;color:var(--green-dark);">✓ Gespeichert vor 5s</span>
|
||||
</div>
|
||||
|
||||
<!-- Editable transcript content -->
|
||||
<div class="trans-panel" style="flex:1;">
|
||||
<div class="trans-line"><div class="trans-ln">1</div><div class="trans-text">Liebe Martha,</div></div>
|
||||
<div class="trans-line"><div class="trans-ln">2</div><div class="trans-text"> </div></div>
|
||||
<div class="trans-line hl-purple"><div class="trans-ln">3</div><div class="trans-text">ich schreibe Dir heute aus dem Lazarett</div></div>
|
||||
<div class="trans-line hl-purple"><div class="trans-ln">4</div><div class="trans-text">in Breslau. Mach Dir keine Sorgen,</div></div>
|
||||
<div class="trans-line"><div class="trans-ln">5</div><div class="trans-text">es geht mir den Umständen</div></div>
|
||||
<div class="trans-line hl-blue"><div class="trans-ln">6</div><div class="trans-text">entsprechend gut. Der Arzt sagt<span class="trans-cursor"></span></div></div>
|
||||
<div class="trans-line"><div class="trans-ln">7</div><div class="trans-text" style="color:var(--color-text-muted);font-style:italic;">[unleserlich]</div></div>
|
||||
<div class="trans-line"><div class="trans-ln">8</div><div class="trans-text">Wochen noch dauern wird.</div></div>
|
||||
<div class="trans-line"><div class="trans-ln">9</div><div class="trans-text"> </div></div>
|
||||
<div class="trans-line"><div class="trans-ln">10</div><div class="trans-text">Die Kinder sollen wissen, dass ich</div></div>
|
||||
</div>
|
||||
|
||||
<div class="status-bar">
|
||||
<span>Zeile 6, Spalte 34</span>
|
||||
<span>15 Zeilen</span>
|
||||
<span style="margin-left:auto;">Oma Inge bearbeitet Z. 3-4</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="agent">
|
||||
<h4>D · Enhanced bottom panel</h4>
|
||||
<pre>/* Minimal change from current architecture. The existing DocumentBottomPanel + PanelTranscription stay.
|
||||
* Changes:
|
||||
* 1. PanelTranscription gains an "edit mode" toggle (currently read-only whitespace-pre-wrap text).
|
||||
* 2. Edit mode: contenteditable line-by-line editor with line numbers (same as mockups A/B).
|
||||
* 3. Scroll sync: scrolling the transcript highlights a position marker on the PDF's right edge.
|
||||
* - Implemented via: transcript scroll offset → normalized position → turquoise bar on PDF.
|
||||
* 4. Presence indicators shown in the tab bar (right side) and in the status bar.
|
||||
* 5. Mini toolbar above transcript: Edit/Preview + quick-insert buttons ([unleserlich], [Seitenwechsel]).
|
||||
* 6. Transcription tab gets a turquoise dot when someone is actively editing.
|
||||
* Pro: ships fastest, reuses all existing panel infrastructure. Con: vertical space is limited. */</pre>
|
||||
<table class="at"><thead><tr><th>Element</th><th>Value</th><th>Notes</th></tr></thead><tbody>
|
||||
<tr><td>Panel min height</td><td>200px when transcription tab active in edit mode</td><td>Existing drag-to-resize + open/close stays</td></tr>
|
||||
<tr><td>Mini toolbar</td><td>h:28px, bg:surface, border-bottom:subtle, flex row</td><td>Edit/Preview + [unleserlich] + [Seitenwechsel] quick-insert</td></tr>
|
||||
<tr><td>Scroll sync bar</td><td>3px wide, turquoise, absolute right:0 on PDF area</td><td>Height proportional to visible transcript lines / total lines</td></tr>
|
||||
<tr><td>Active tab dot</td><td>5px turquoise circle, absolute top-right of tab</td><td>Visible when ≥1 user is editing in real time</td></tr>
|
||||
<tr><td>Quick-insert btns</td><td>[unleserlich] and [Seitenwechsel]</td><td>Insert at cursor. Rendered as italic muted inline markers.</td></tr>
|
||||
</tbody></table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ═══ LLM IMPLEMENTATION GUIDE ═══ -->
|
||||
<div class="llm">
|
||||
<h2>Implementation Guide — Inline Collaborative Transcription</h2>
|
||||
|
||||
<h3>1. Concept Summary</h3>
|
||||
<p>Four approaches to the same user need: <strong>see the original scan and type/edit a transcript simultaneously, with multiple family members contributing</strong>. The documents are scanned handwritten letters (image-only PDFs, no text layer). The transcription must be editable inline — not in a separate "edit" page.</p>
|
||||
|
||||
<table>
|
||||
<thead><tr><th>Mockup</th><th>Layout</th><th>Pros</th><th>Cons</th><th>Complexity</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><strong>A</strong></td><td>Vertical split (PDF left, transcript right)</td><td>Both always visible, resizable, familiar pattern</td><td>PDF loses width on narrow screens</td><td>Medium</td></tr>
|
||||
<tr><td><strong>B</strong></td><td>Slide-over panel on top of PDF</td><td>No layout shift, PDF stays full-width</td><td>Obscures right side of scan</td><td>Low</td></tr>
|
||||
<tr><td><strong>C</strong></td><td>Interleaved scan crops + transcript blocks</td><td>Best reading flow, works on mobile natively</td><td>Requires section-splitting UX, complex PDF cropping</td><td>High</td></tr>
|
||||
<tr><td><strong>D</strong></td><td>Enhanced existing bottom panel</td><td>Fastest to ship, no new layout needed</td><td>Vertical space squeeze, scan partially hidden</td><td>Low</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>2. Shared Components Across All Mockups</h3>
|
||||
<ul>
|
||||
<li><strong>Collaborative editing engine:</strong> Use Y.js (CRDT) or similar for real-time multi-user editing. Each user gets a distinct color from a 6-color palette.</li>
|
||||
<li><strong>Presence indicators:</strong> Colored dots (5px circles) with user name. "Editing" = colored fill, "online" = green fill, "idle" = gray.</li>
|
||||
<li><strong>Line-by-line editor:</strong> Transcript stored as an array of lines. Each line is a <code>contenteditable</code> div or a textarea row. Line numbers (mono font, right-aligned, muted).</li>
|
||||
<li><strong>Auto-save:</strong> Debounced 2-second save via <code>PATCH /api/documents/{id}</code> with <code>transcription</code> field. Status indicator: "Gespeichert" (green) / "Speichert..." (muted) / "Nicht gespeichert" (error red).</li>
|
||||
<li><strong>[unleserlich] markers:</strong> Inline placeholder for illegible text — rendered as <code>font-style:italic; color:ink-3;</code> in square brackets. Insertable via toolbar button or keyboard shortcut (<code>Ctrl+Shift+U</code>).</li>
|
||||
<li><strong>[Seitenwechsel] markers:</strong> Page-break indicator — a horizontal rule with "Seite 2" label, inserted when the letter spans multiple pages.</li>
|
||||
</ul>
|
||||
|
||||
<h3>3. Existing Code to Extend</h3>
|
||||
<ul>
|
||||
<li><code>PanelTranscription.svelte</code> — currently read-only. Needs edit mode toggle.</li>
|
||||
<li><code>DocumentBottomPanel.svelte</code> — existing drag-to-resize panel with tabs. Mockup D enhances this directly.</li>
|
||||
<li><code>DocumentViewer.svelte</code> — wraps PdfViewer. Mockups A/B need a split or overlay sibling.</li>
|
||||
<li><code>+page.svelte</code> (document detail) — orchestrates all sub-components. New state: <code>transcribeMode</code> (boolean).</li>
|
||||
<li><code>DocumentTopBar.svelte</code> — needs a "Transkribieren" toggle button (same pattern as existing annotate button).</li>
|
||||
</ul>
|
||||
|
||||
<h3>4. Data Model Changes</h3>
|
||||
<ul>
|
||||
<li>The <code>Document.transcription</code> field (text) already exists. No schema change needed for basic single-user editing.</li>
|
||||
<li>For collaborative editing: add a <code>transcription_versions</code> table (document_id, user_id, content, timestamp) for version history.</li>
|
||||
<li>For real-time sync: WebSocket endpoint <code>/ws/transcription/{documentId}</code> broadcasting Y.js updates.</li>
|
||||
</ul>
|
||||
|
||||
<h3>5. Recommendation</h3>
|
||||
<p><strong>Start with Mockup D</strong> (enhanced bottom panel) as the MVP — it requires the least layout changes and builds on existing components. Once validated with users, upgrade to <strong>Mockup A</strong> (side-by-side split) for the full experience. Mockup C (interleaved) is the most innovative but requires significant PDF rendering work (canvas-based section cropping) and a section-splitting UX that adds complexity.</p>
|
||||
|
||||
<h3>6. Accessibility</h3>
|
||||
<ul>
|
||||
<li>Transcript editor must be keyboard-navigable: Tab moves between lines, Enter inserts a new line, Escape exits edit mode.</li>
|
||||
<li>Screen reader: <code>aria-live="polite"</code> region for save status and presence changes.</li>
|
||||
<li>Minimum font size: 16px for transcript text (18px in "senior mode" user preference).</li>
|
||||
<li>All toolbar buttons must have <code>aria-label</code> attributes.</li>
|
||||
<li>Focus management: entering transcribe mode should focus the first editable line.</li>
|
||||
<li>Color is never the only cue for user presence — each user's name is always shown alongside their color dot.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
671
docs/specs/stammbaum-relationship-badge-spec.html
Normal file
671
docs/specs/stammbaum-relationship-badge-spec.html
Normal file
@@ -0,0 +1,671 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Stammbaum — Relationship Badge · Document Detail · Familienarchiv</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Tinos:ital,wght@0,400;0,700&family=Montserrat:wght@400;500;600;700;800;900&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
body{font-family:'Montserrat',system-ui,sans-serif;background:#ECEAE4;color:#1A1A1A;line-height:1.5;font-size:13px}
|
||||
.doc{max-width:1300px;margin:0 auto;padding:48px 32px 120px}
|
||||
|
||||
/* ── Masthead ── */
|
||||
.mh{padding-bottom:24px;border-bottom:3px solid #012851;margin-bottom:60px}
|
||||
.mh h1{font-size:23px;font-weight:900;color:#012851;letter-spacing:-.4px}
|
||||
.mh p{font-size:13px;color:#555;max-width:740px;line-height:1.75;margin-top:8px}
|
||||
.mh .byline{font-size:9px;color:#999;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;margin-top:10px}
|
||||
.tag-row{display:flex;gap:6px;margin-top:10px;flex-wrap:wrap}
|
||||
.tag{background:#012851;color:#A1DCD8;padding:2px 8px;border-radius:2px;font-size:8px;font-weight:700;letter-spacing:.8px;text-transform:uppercase}
|
||||
.tag.amber{background:#7c4a00;color:#fde68a}
|
||||
|
||||
/* ── Section headers ── */
|
||||
.sh{margin:0 0 28px}
|
||||
.sh h2{font-size:16px;font-weight:900;color:#012851;letter-spacing:-.2px}
|
||||
.sh p{font-size:12.5px;color:#666;max-width:720px;line-height:1.7;margin-top:5px}
|
||||
.section{margin-bottom:80px;padding-bottom:80px;border-bottom:2px dashed #C8C4BE}
|
||||
.section:last-of-type{border-bottom:none;margin-bottom:0;padding-bottom:0}
|
||||
|
||||
/* ── Token tables ── */
|
||||
.token-grid{display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:16px}
|
||||
.token-table{border-radius:6px;overflow:hidden}
|
||||
.token-table.light{background:#fff;border:1px solid #E0DDD6}
|
||||
.token-table.dark{background:#0F1923;border:1px solid #1E2D3D}
|
||||
.token-head{padding:8px 14px;font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;border-bottom:1px solid #E0DDD6}
|
||||
.token-table.light .token-head{background:#F4F2EC;color:#888;border-bottom-color:#E0DDD6}
|
||||
.token-table.dark .token-head{background:#0A1218;color:#4E6070;border-bottom-color:#1E2D3D}
|
||||
.token-table table{width:100%;border-collapse:collapse;font-size:11px}
|
||||
.token-table.light td{padding:6px 14px;border-bottom:1px solid #F0EEE8;vertical-align:middle}
|
||||
.token-table.dark td{padding:6px 14px;border-bottom:1px solid #1A2830;vertical-align:middle;color:#8AAABB}
|
||||
.token-table tr:last-child td{border-bottom:none}
|
||||
.token-table.light td:first-child{font-size:9px;font-weight:700;color:#888;width:160px}
|
||||
.token-table.dark td:first-child{font-size:9px;font-weight:700;color:#4E6070;width:160px}
|
||||
.swatch{display:inline-block;width:12px;height:12px;border-radius:2px;vertical-align:middle;margin-right:6px}
|
||||
.swatch.bordered{border:1px solid #DDD}
|
||||
.warn{display:inline-block;background:#FEF3C7;color:#92400E;font-size:8px;font-weight:700;padding:1px 5px;border-radius:2px;margin-left:4px;vertical-align:middle}
|
||||
.pass{display:inline-block;background:#D1FAE5;color:#065F46;font-size:8px;font-weight:700;padding:1px 5px;border-radius:2px;margin-left:4px;vertical-align:middle}
|
||||
|
||||
/* ── Browser chrome ── */
|
||||
.chrome{border:1.5px solid #C4C0BA;border-radius:8px;overflow:hidden;box-shadow:0 4px 20px rgba(0,0,0,.1)}
|
||||
.chrome.dark{background:#010e1e;border-color:#0d3358}
|
||||
.chrome-bar{height:20px;background:#E8E6E0;border-bottom:1px solid #C4C0BA;display:flex;align-items:center;padding:0 8px;gap:4px;flex-shrink:0}
|
||||
.chrome.dark .chrome-bar{background:#010a18;border-bottom-color:#0d3358}
|
||||
.chrome-dot{width:6px;height:6px;border-radius:50%;background:#BDB8B1}
|
||||
.chrome.dark .chrome-dot{background:#1a2a3a}
|
||||
.chrome-url{flex:1;height:9px;background:#CCC8C2;border-radius:5px;margin-left:6px}
|
||||
.chrome.dark .chrome-url{background:#1a2a3a}
|
||||
|
||||
/* ── App nav ── */
|
||||
.app-nav{height:30px;background:#012851;display:flex;align-items:center;padding:0 12px;gap:10px;flex-shrink:0}
|
||||
.app-logo{font-family:'Tinos',Georgia,serif;font-size:7px;font-weight:700;color:#fff;border-bottom:2px solid #A1DCD8;padding-bottom:1px}
|
||||
.app-link{font-size:5.5px;font-weight:700;text-transform:uppercase;letter-spacing:.5px;color:rgba(255,255,255,.4);white-space:nowrap}
|
||||
.app-link.on{color:rgba(255,255,255,.9)}
|
||||
.app-nav-r{margin-left:auto;display:flex;gap:6px;align-items:center}
|
||||
.app-av{width:16px;height:16px;border-radius:50%;background:rgba(255,255,255,.12);display:flex;align-items:center;justify-content:center;font-size:5px;font-weight:800;color:rgba(255,255,255,.5)}
|
||||
|
||||
/* ── Document top bar (simulated) ── */
|
||||
.doc-topbar{height:56px;background:#ffffff;border-bottom:1px solid #E4E2D8;display:flex;align-items:center;padding-right:12px;flex-shrink:0}
|
||||
.chrome.dark .doc-topbar{background:#011526;border-bottom-color:#0d3358}
|
||||
.accent-bar{width:3px;height:56px;background:#012851;flex-shrink:0}
|
||||
.chrome.dark .accent-bar{background:#A1DCD8}
|
||||
.back-btn{width:32px;height:32px;border-radius:50%;display:flex;align-items:center;justify-content:center;color:#888;flex-shrink:0;margin:0 4px}
|
||||
.tb-divider{width:1px;height:20px;background:#E4E2D8;flex-shrink:0;margin:0 6px}
|
||||
.chrome.dark .tb-divider{background:#0d3358}
|
||||
.tb-title-block{flex:1;min-width:0;padding:0 4px}
|
||||
.tb-title{font-family:'Tinos',Georgia,serif;font-size:10px;font-weight:700;color:#012851;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.chrome.dark .tb-title{color:#f0efe9}
|
||||
.tb-date{font-size:8px;color:#6b7280;margin-top:1px}
|
||||
.chrome.dark .tb-date{color:#8b97a5}
|
||||
.tb-btn-details{height:22px;padding:0 8px;border:1.5px solid #E4E2D8;border-radius:3px;font-size:7px;font-weight:700;color:#4b5563;display:flex;align-items:center;gap:3px;flex-shrink:0}
|
||||
.chrome.dark .tb-btn-details{border-color:#0d3358;color:#8b97a5}
|
||||
.tb-btn-primary{height:22px;padding:0 8px;background:#012851;border-radius:3px;font-size:7px;font-weight:700;color:#A1DCD8;display:flex;align-items:center;gap:3px;flex-shrink:0;margin-left:6px}
|
||||
.chrome.dark .tb-btn-primary{background:#A1DCD8;color:#012851}
|
||||
|
||||
/* ── Metadata drawer ── */
|
||||
.meta-drawer{background:#ffffff;border-bottom:1px solid #E4E2D8;padding:14px 16px;flex-shrink:0}
|
||||
.chrome.dark .meta-drawer{background:#011526;border-bottom-color:#0d3358}
|
||||
.meta-grid{display:grid;grid-template-columns:1fr 1fr 1fr;gap:16px}
|
||||
.meta-col-head{font-size:7px;font-weight:800;text-transform:uppercase;letter-spacing:.09em;color:#6b7280;margin-bottom:8px}
|
||||
.chrome.dark .meta-col-head{color:#8b97a5}
|
||||
.meta-field{margin-bottom:8px}
|
||||
.meta-label{font-size:7px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:#6b7280;margin-bottom:3px}
|
||||
.chrome.dark .meta-label{color:#8b97a5}
|
||||
.meta-value{font-family:'Tinos',Georgia,serif;font-size:10px;color:#012851}
|
||||
.chrome.dark .meta-value{color:#f0efe9}
|
||||
|
||||
/* ── Person card ── */
|
||||
.person-card{display:flex;align-items:center;gap:5px;padding:3px 5px;border-radius:3px;cursor:default}
|
||||
.person-card:hover{background:#f5f4ef}
|
||||
.chrome.dark .person-card:hover{background:#011a30}
|
||||
.p-av{width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:6.5px;font-weight:800;color:#fff;flex-shrink:0}
|
||||
.p-name{font-family:'Tinos',Georgia,serif;font-size:9.5px;color:#012851}
|
||||
.chrome.dark .p-name{color:#f0efe9}
|
||||
|
||||
/* ── RELATIONSHIP BADGE ── */
|
||||
.rel-badge{}
|
||||
.rel-badge-label{font-size:7px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:#6b7280;margin-bottom:3px}
|
||||
.chrome.dark .rel-badge-label{color:#8b97a5}
|
||||
.rel-badge-row{display:flex;align-items:center;gap:5px;padding:0 5px}
|
||||
.rel-value{font-family:'Tinos',Georgia,serif;font-size:10px;font-weight:700;color:#012851}
|
||||
.chrome.dark .rel-value{color:#f0efe9}
|
||||
.rel-arrow{width:9px;height:9px;flex-shrink:0;color:#A1DCD8}
|
||||
.chrome.dark .rel-arrow{color:#00c7b1}
|
||||
|
||||
/* ── PDF placeholder ── */
|
||||
.pdf-area{background:#d4d0c8;flex:1;display:flex;align-items:center;justify-content:center;min-height:80px}
|
||||
.chrome.dark .pdf-area{background:#010e1e}
|
||||
.paper{background:#FFFEF8;width:40%;box-shadow:0 2px 8px rgba(0,0,0,.14);border-radius:1px;padding:8px 10px;display:flex;flex-direction:column;gap:2px}
|
||||
.chrome.dark .paper{background:#0d1820}
|
||||
.pl{height:3px;background:#C4BDB0;border-radius:1px;opacity:.5;margin-bottom:2px}
|
||||
.ps{height:2px;background:#C4BDB0;border-radius:1px;opacity:.28;margin-bottom:1.5px}
|
||||
.chrome.dark .pl,.chrome.dark .ps{background:#1E2D3D}
|
||||
|
||||
/* ── Side-by-side layout ── */
|
||||
.split-screens{display:grid;grid-template-columns:1fr 1fr;gap:24px;margin-bottom:16px}
|
||||
.screen-lbl{font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:1.2px;color:#888;margin-bottom:8px;display:flex;align-items:center;gap:5px}
|
||||
.lbl-dot{width:8px;height:8px;border-radius:50%;display:inline-block}
|
||||
.cap{font-size:10px;color:#999;font-style:italic;line-height:1.6;margin-top:10px;max-width:460px}
|
||||
|
||||
/* ── Edge-case row ── */
|
||||
.edge-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:16px;margin-bottom:12px}
|
||||
.edge-card{background:#fff;border:1px solid #E0DDD6;border-radius:6px;overflow:hidden}
|
||||
.edge-head{background:#F4F2EC;padding:8px 12px;font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#888;border-bottom:1px solid #E0DDD6}
|
||||
.edge-body{padding:10px 12px}
|
||||
.edge-note{font-size:10.5px;color:#555;line-height:1.65;margin-top:8px}
|
||||
.no-badge{font-family:'Tinos',Georgia,serif;font-size:9px;color:#aaa;font-style:italic;padding:4px 5px}
|
||||
|
||||
/* ── Data flow diagram ── */
|
||||
.flow{display:flex;flex-direction:column;gap:0;margin-bottom:24px;max-width:680px}
|
||||
.flow-node{background:#fff;border:1.5px solid #E0DDD6;border-radius:4px;padding:8px 14px;font-size:11px;color:#333;display:flex;align-items:flex-start;gap:8px}
|
||||
.flow-node.highlight{border-color:#012851;background:#EEF3FB}
|
||||
.flow-node.new{border-color:#A1DCD8;background:#EAF7F6}
|
||||
.flow-tag{font-size:7.5px;font-weight:800;padding:1px 5px;border-radius:10px;white-space:nowrap;margin-left:auto;flex-shrink:0;align-self:center}
|
||||
.flow-tag.change{background:#DBEAFE;color:#1E40AF}
|
||||
.flow-tag.new{background:#D1FAE5;color:#065F46}
|
||||
.flow-tag.unchanged{background:#F3F4F6;color:#555}
|
||||
.flow-arrow{text-align:center;font-size:14px;color:#C8C4BE;line-height:1;padding:3px 0}
|
||||
.flow-cond{background:#FFFBEB;border:1.5px solid #FDE68A;border-radius:4px;padding:8px 14px;font-size:10.5px;color:#78350F;margin:0}
|
||||
.flow-cond code{background:rgba(0,0,0,.06);padding:1px 4px;border-radius:2px;font-size:9.5px}
|
||||
|
||||
/* ── Rules table ── */
|
||||
.rules{background:#fff;border:1px solid #E0DDD6;border-radius:6px;overflow:hidden}
|
||||
.rules table{width:100%;border-collapse:collapse}
|
||||
.rules th{background:#F4F2EC;font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#888;padding:8px 12px;text-align:left;border-bottom:1px solid #E0DDD6}
|
||||
.rules td{font-size:11px;color:#444;padding:8px 12px;border-bottom:1px solid #F0EEE8;vertical-align:top;line-height:1.6}
|
||||
.rules tr:last-child td{border-bottom:none}
|
||||
.rules td:first-child{font-size:9px;font-weight:700;color:#012851;white-space:nowrap;width:170px}
|
||||
.rules td code{font-size:9px;background:#F0EFE9;padding:1px 4px;border-radius:2px;color:#555;white-space:nowrap}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="doc">
|
||||
|
||||
<!-- ══ MASTHEAD ══════════════════════════════════════════════════════════════ -->
|
||||
<div class="mh">
|
||||
<h1>Stammbaum — Relationship Badge · Document Detail</h1>
|
||||
<p>
|
||||
Spec for Part C of issue <strong>#358</strong>. A "Verwandtschaft" metadata row appears inside the
|
||||
metadata drawer's Personen column when a family kinship can be inferred between the document's sender
|
||||
and its single receiver. Implemented as a new presentational component
|
||||
<code>RelationshipBadge.svelte</code>.
|
||||
</p>
|
||||
<div class="byline">Familienarchiv · 2026-04-27 · Leonie Voss, UX Lead</div>
|
||||
<div class="tag-row">
|
||||
<span class="tag">Issue #358</span>
|
||||
<span class="tag">Part C — Badge only</span>
|
||||
<span class="tag amber">Depends on PersonSummaryDTO.familyMember</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ══ SECTION 1 — DESIGN TOKENS ════════════════════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="sh">
|
||||
<h2>1 · Design tokens</h2>
|
||||
<p>All colour values used by the badge and its context. The component uses only semantic Tailwind tokens — no hardcoded hex. Light and dark themes are handled automatically by <code>layout.css</code>.</p>
|
||||
</div>
|
||||
|
||||
<div class="token-grid">
|
||||
<!-- Light -->
|
||||
<div class="token-table light">
|
||||
<div class="token-head">Light theme — bg-surface = #ffffff</div>
|
||||
<table>
|
||||
<tr>
|
||||
<td>text-ink-3</td>
|
||||
<td><span class="swatch" style="background:#6b7280"></span>#6b7280 — "Verwandtschaft" label<span class="pass">4.8:1 AA ✓</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>text-ink</td>
|
||||
<td><span class="swatch" style="background:#012851"></span>#012851 — label values ("Sohn", "Vater")<span class="pass">14.5:1 AAA ✓</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>text-accent</td>
|
||||
<td><span class="swatch" style="background:#a1dcd8;border:1px solid #ccc"></span>#a1dcd8 — decorative arrow <code>aria-hidden</code><span class="warn">1.5:1 — non-text only</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>font-serif</td>
|
||||
<td>Tinos — used for label values (matches person card names in same column)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>font-sans</td>
|
||||
<td>Montserrat — used for "Verwandtschaft" label (matches "Von" / "An" labels above)</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!-- Dark -->
|
||||
<div class="token-table dark">
|
||||
<div class="token-head">Dark theme — bg-surface = #011526</div>
|
||||
<table>
|
||||
<tr>
|
||||
<td>text-ink-3</td>
|
||||
<td><span class="swatch" style="background:#8b97a5"></span>#8b97a5 — "Verwandtschaft" label<span class="pass" style="background:rgba(209,250,229,.15);color:#6EE7B7;border:none">7.1:1 AAA ✓</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>text-ink</td>
|
||||
<td><span class="swatch" style="background:#f0efe9"></span>#f0efe9 — label values ("Sohn", "Vater")<span class="pass" style="background:rgba(209,250,229,.15);color:#6EE7B7;border:none">14.5:1 AAA ✓</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>text-accent</td>
|
||||
<td><span class="swatch" style="background:#00c7b1"></span>#00c7b1 — decorative arrow <code>aria-hidden</code><span class="warn" style="background:rgba(254,243,199,.1);color:#FDE68A;border:none">non-text only</span></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>bg-surface</td>
|
||||
<td><span class="swatch" style="background:#011526;border:1px solid #0d3358"></span>#011526 — drawer background in dark mode</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<p style="font-size:10.5px;color:#888;font-style:italic;margin-top:6px">
|
||||
⚠ <code>text-accent</code> (#a1dcd8 light / #00c7b1 dark) must only be used for the decorative arrow SVG.
|
||||
It fails WCAG at small text sizes. The arrow carries no information — it is purely visual and is marked <code>aria-hidden="true"</code>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ══ SECTION 2 — VISUAL MOCKUP ════════════════════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="sh">
|
||||
<h2>2 · Visual mockup — light & dark</h2>
|
||||
<p>
|
||||
The badge renders as the last item in the Personen column of the expandable metadata drawer.
|
||||
Shown at ~65% scale. Drawer is open. Both themes side by side.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="split-screens">
|
||||
|
||||
<!-- Light -->
|
||||
<div>
|
||||
<div class="screen-lbl"><span class="lbl-dot" style="background:#A1DCD8;border:1px solid #ccc"></span>Light theme</div>
|
||||
<div class="chrome">
|
||||
<div class="chrome-bar"><div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-url"></div></div>
|
||||
<div class="app-nav">
|
||||
<div class="app-logo">Familienarchiv</div>
|
||||
<div class="app-link">Dokumente</div>
|
||||
<div class="app-link">Personen</div>
|
||||
<div class="app-link on">—</div>
|
||||
<div class="app-nav-r"><div class="app-av">M</div></div>
|
||||
</div>
|
||||
<!-- Doc top bar -->
|
||||
<div class="doc-topbar">
|
||||
<div class="accent-bar"></div>
|
||||
<div class="back-btn">
|
||||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"/></svg>
|
||||
</div>
|
||||
<div class="tb-divider"></div>
|
||||
<div class="tb-title-block">
|
||||
<div class="tb-title">Brief an die Eltern, 15. März 1923</div>
|
||||
<div class="tb-date">15. März 1923</div>
|
||||
</div>
|
||||
<div class="tb-btn-details">Details ▾</div>
|
||||
<div class="tb-btn-primary">Bearbeiten</div>
|
||||
</div>
|
||||
<!-- Metadata drawer OPEN -->
|
||||
<div class="meta-drawer">
|
||||
<div class="meta-grid">
|
||||
<!-- Col 1: Details -->
|
||||
<div>
|
||||
<div class="meta-col-head">Details</div>
|
||||
<div class="meta-field">
|
||||
<div class="meta-label">Datum</div>
|
||||
<div class="meta-value">15. März 1923</div>
|
||||
</div>
|
||||
<div class="meta-field">
|
||||
<div class="meta-label">Ort</div>
|
||||
<div class="meta-value">München</div>
|
||||
</div>
|
||||
<div class="meta-field">
|
||||
<div class="meta-label">Status</div>
|
||||
<div class="meta-value">Transkribiert</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Col 2: Personen — with badge -->
|
||||
<div>
|
||||
<div class="meta-col-head">Personen</div>
|
||||
<div class="meta-field">
|
||||
<div class="meta-label">Von</div>
|
||||
<div class="person-card">
|
||||
<div class="p-av" style="background:#5b7fa6">KR</div>
|
||||
<div class="p-name">Karl Raddatz</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="meta-field">
|
||||
<div class="meta-label">An</div>
|
||||
<div class="person-card">
|
||||
<div class="p-av" style="background:#7a6b52">HR</div>
|
||||
<div class="p-name">Heinrich Raddatz</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- RELATIONSHIP BADGE -->
|
||||
<div class="rel-badge">
|
||||
<div class="rel-badge-label">Verwandtschaft</div>
|
||||
<div class="rel-badge-row">
|
||||
<span class="rel-value">Sohn</span>
|
||||
<svg class="rel-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M13 7l5 5-5 5M6 12h12"/></svg>
|
||||
<span class="rel-value">Vater</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Col 3: Tags -->
|
||||
<div>
|
||||
<div class="meta-col-head">Schlagwörter</div>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:4px">
|
||||
<span style="background:#f5f4ef;padding:2px 7px;border-radius:2px;font-size:7.5px;font-weight:800;text-transform:uppercase;color:#012851">Familie</span>
|
||||
<span style="background:#f5f4ef;padding:2px 7px;border-radius:2px;font-size:7.5px;font-weight:800;text-transform:uppercase;color:#012851">Krieg</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pdf-area">
|
||||
<div class="paper"><div class="pl"></div><div class="ps"></div><div class="pl" style="width:80%"></div><div class="ps" style="width:65%"></div><div class="pl"></div><div class="ps" style="width:75%"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="cap">Drawer open. "Verwandtschaft" row sits below the single receiver card, using identical label and value typography to "Von" / "An". Arrow is mint accent, aria-hidden.</p>
|
||||
</div>
|
||||
|
||||
<!-- Dark -->
|
||||
<div>
|
||||
<div class="screen-lbl"><span class="lbl-dot" style="background:#1a2a3a"></span>Dark theme</div>
|
||||
<div class="chrome dark">
|
||||
<div class="chrome-bar"><div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-dot"></div><div class="chrome-url"></div></div>
|
||||
<div class="app-nav">
|
||||
<div class="app-logo">Familienarchiv</div>
|
||||
<div class="app-link">Dokumente</div>
|
||||
<div class="app-link">Personen</div>
|
||||
<div class="app-link on">—</div>
|
||||
<div class="app-nav-r"><div class="app-av">M</div></div>
|
||||
</div>
|
||||
<div class="doc-topbar">
|
||||
<div class="accent-bar"></div>
|
||||
<div class="back-btn" style="color:#4E6070">
|
||||
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M15 19l-7-7 7-7"/></svg>
|
||||
</div>
|
||||
<div class="tb-divider"></div>
|
||||
<div class="tb-title-block">
|
||||
<div class="tb-title">Brief an die Eltern, 15. März 1923</div>
|
||||
<div class="tb-date">15. März 1923</div>
|
||||
</div>
|
||||
<div class="tb-btn-details">Details ▾</div>
|
||||
<div class="tb-btn-primary">Bearbeiten</div>
|
||||
</div>
|
||||
<div class="meta-drawer">
|
||||
<div class="meta-grid">
|
||||
<div>
|
||||
<div class="meta-col-head">Details</div>
|
||||
<div class="meta-field">
|
||||
<div class="meta-label">Datum</div>
|
||||
<div class="meta-value">15. März 1923</div>
|
||||
</div>
|
||||
<div class="meta-field">
|
||||
<div class="meta-label">Ort</div>
|
||||
<div class="meta-value">München</div>
|
||||
</div>
|
||||
<div class="meta-field">
|
||||
<div class="meta-label">Status</div>
|
||||
<div class="meta-value">Transkribiert</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="meta-col-head">Personen</div>
|
||||
<div class="meta-field">
|
||||
<div class="meta-label">Von</div>
|
||||
<div class="person-card">
|
||||
<div class="p-av" style="background:#5b7fa6">KR</div>
|
||||
<div class="p-name">Karl Raddatz</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="meta-field">
|
||||
<div class="meta-label">An</div>
|
||||
<div class="person-card">
|
||||
<div class="p-av" style="background:#7a6b52">HR</div>
|
||||
<div class="p-name">Heinrich Raddatz</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rel-badge">
|
||||
<div class="rel-badge-label">Verwandtschaft</div>
|
||||
<div class="rel-badge-row">
|
||||
<span class="rel-value">Sohn</span>
|
||||
<svg class="rel-arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="M13 7l5 5-5 5M6 12h12"/></svg>
|
||||
<span class="rel-value">Vater</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="meta-col-head">Schlagwörter</div>
|
||||
<div style="display:flex;flex-wrap:wrap;gap:4px">
|
||||
<span style="background:#011a30;padding:2px 7px;border-radius:2px;font-size:7.5px;font-weight:800;text-transform:uppercase;color:#A1DCD8">Familie</span>
|
||||
<span style="background:#011a30;padding:2px 7px;border-radius:2px;font-size:7.5px;font-weight:800;text-transform:uppercase;color:#A1DCD8">Krieg</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pdf-area">
|
||||
<div class="paper"><div class="pl"></div><div class="ps"></div><div class="pl" style="width:80%"></div><div class="ps" style="width:65%"></div><div class="pl"></div><div class="ps" style="width:75%"></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="cap">Dark mode. Same semantic tokens; surface flips to #011526, ink to #f0efe9, accent arrow to #00c7b1 (turquoise). Both label and value pass WCAG AAA on the dark surface.</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ══ SECTION 3 — EDGE CASES ════════════════════════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="sh">
|
||||
<h2>3 · Edge cases — when the badge is silent</h2>
|
||||
<p>All three cases result in no "Verwandtschaft" row. The drawer's Personen column is unaffected.</p>
|
||||
</div>
|
||||
|
||||
<div class="edge-grid">
|
||||
|
||||
<div class="edge-card">
|
||||
<div class="edge-head">Not a family member</div>
|
||||
<div class="edge-body">
|
||||
<div class="meta-field">
|
||||
<div class="meta-label" style="font-size:7px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:#6b7280;margin-bottom:3px">Von</div>
|
||||
<div class="person-card" style="background:#fff"><div class="p-av" style="background:#5b7fa6">KR</div><div class="p-name" style="font-family:'Tinos',serif;font-size:9.5px;color:#012851">Karl Raddatz</div></div>
|
||||
</div>
|
||||
<div class="meta-field">
|
||||
<div class="meta-label" style="font-size:7px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:#6b7280;margin-bottom:3px">An</div>
|
||||
<div class="person-card" style="background:#fff"><div class="p-av" style="background:#888">NE</div><div class="p-name" style="font-family:'Tinos',serif;font-size:9.5px;color:#012851">N.N. Engel</div></div>
|
||||
</div>
|
||||
<div class="no-badge">— no Verwandtschaft row —</div>
|
||||
<div class="edge-note">Receiver does not have <code style="font-size:9px;background:#F0EFE9;padding:1px 4px;border-radius:2px">familyMember = true</code>. Inference endpoint is never called.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="edge-card">
|
||||
<div class="edge-head">Multiple receivers</div>
|
||||
<div class="edge-body">
|
||||
<div class="meta-field">
|
||||
<div class="meta-label" style="font-size:7px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:#6b7280;margin-bottom:3px">Von</div>
|
||||
<div class="person-card" style="background:#fff"><div class="p-av" style="background:#5b7fa6">KR</div><div class="p-name" style="font-family:'Tinos',serif;font-size:9.5px;color:#012851">Karl Raddatz</div></div>
|
||||
</div>
|
||||
<div class="meta-field">
|
||||
<div class="meta-label" style="font-size:7px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:#6b7280;margin-bottom:3px">An</div>
|
||||
<div class="person-card" style="background:#fff"><div class="p-av" style="background:#7a6b52">HR</div><div class="p-name" style="font-family:'Tinos',serif;font-size:9.5px;color:#012851">Heinrich Raddatz</div></div>
|
||||
<div class="person-card" style="background:#fff"><div class="p-av" style="background:#6a7a52">ER</div><div class="p-name" style="font-family:'Tinos',serif;font-size:9.5px;color:#012851">Elfriede Raddatz</div></div>
|
||||
</div>
|
||||
<div class="no-badge">— no Verwandtschaft row —</div>
|
||||
<div class="edge-note"><code style="font-size:9px;background:#F0EFE9;padding:1px 4px;border-radius:2px">receivers.length > 1</code>: badge is silently omitted. Multi-receiver documents are rare in this archive; revisit if data shows otherwise.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="edge-card">
|
||||
<div class="edge-head">No kinship path found</div>
|
||||
<div class="edge-body">
|
||||
<div class="meta-field">
|
||||
<div class="meta-label" style="font-size:7px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:#6b7280;margin-bottom:3px">Von</div>
|
||||
<div class="person-card" style="background:#fff"><div class="p-av" style="background:#5b7fa6">KR</div><div class="p-name" style="font-family:'Tinos',serif;font-size:9.5px;color:#012851">Karl Raddatz</div></div>
|
||||
</div>
|
||||
<div class="meta-field">
|
||||
<div class="meta-label" style="font-size:7px;font-weight:600;text-transform:uppercase;letter-spacing:.06em;color:#6b7280;margin-bottom:3px">An</div>
|
||||
<div class="person-card" style="background:#fff"><div class="p-av" style="background:#7a6b52">HR</div><div class="p-name" style="font-family:'Tinos',serif;font-size:9.5px;color:#012851">Heinrich Raddatz</div></div>
|
||||
</div>
|
||||
<div class="no-badge">— no Verwandtschaft row —</div>
|
||||
<div class="edge-note">Both are family members, but the backend returns <code style="font-size:9px;background:#F0EFE9;padding:1px 4px;border-radius:2px">404</code> (no path in graph). <code>inferredRelationship</code> is set to <code>null</code>. No error shown.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ══ SECTION 4 — DATA FLOW ═════════════════════════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="sh">
|
||||
<h2>4 · Data flow</h2>
|
||||
<p><code>inferredRelationship</code> is loaded server-side and passed as a <strong>separate prop</strong> alongside the document — it is not added to the document object.</p>
|
||||
</div>
|
||||
|
||||
<div class="flow">
|
||||
<div class="flow-node highlight">
|
||||
<div>
|
||||
<strong>+page.server.ts</strong><br>
|
||||
<span style="font-size:10.5px;color:#555">Loads document as today. Then, if <code style="font-size:9.5px;background:#eef3fb;padding:1px 3px;border-radius:2px">sender.familyMember && receivers.length === 1 && receivers[0].familyMember</code>, calls <code style="font-size:9.5px;background:#eef3fb;padding:1px 3px;border-radius:2px">GET /api/persons/{senderId}/relationship-to/{receiverId}</code>. 404 → null. Returns <code style="font-size:9.5px;background:#eef3fb;padding:1px 3px;border-radius:2px">{ document, inferredRelationship }</code>.</span>
|
||||
</div>
|
||||
<span class="flow-tag change">Modified</span>
|
||||
</div>
|
||||
<div class="flow-arrow">↓</div>
|
||||
<div class="flow-node">
|
||||
<div>
|
||||
<strong>+page.svelte</strong><br>
|
||||
<span style="font-size:10.5px;color:#555">Receives <code style="font-size:9.5px;background:#f0efe9;padding:1px 3px;border-radius:2px">data.inferredRelationship</code>. Passes to <code style="font-size:9.5px;background:#f0efe9;padding:1px 3px;border-radius:2px"><DocumentTopBar></code> as a new optional prop.</span>
|
||||
</div>
|
||||
<span class="flow-tag change">Modified</span>
|
||||
</div>
|
||||
<div class="flow-arrow">↓</div>
|
||||
<div class="flow-node">
|
||||
<div>
|
||||
<strong>DocumentTopBar.svelte</strong><br>
|
||||
<span style="font-size:10.5px;color:#555">New optional prop <code style="font-size:9.5px;background:#f0efe9;padding:1px 3px;border-radius:2px">inferredRelationship?: { labelFromA: string; labelFromB: string } | null</code>. Passes through to <code style="font-size:9.5px;background:#f0efe9;padding:1px 3px;border-radius:2px"><DocumentMetadataDrawer></code>.</span>
|
||||
</div>
|
||||
<span class="flow-tag change">Modified</span>
|
||||
</div>
|
||||
<div class="flow-arrow">↓</div>
|
||||
<div class="flow-node">
|
||||
<div>
|
||||
<strong>DocumentMetadataDrawer.svelte</strong><br>
|
||||
<span style="font-size:10.5px;color:#555">New optional prop. Renders <code style="font-size:9.5px;background:#f0efe9;padding:1px 3px;border-radius:2px">{#if inferredRelationship} <RelationshipBadge .../> {/if}</code> at the bottom of the Personen column.</span>
|
||||
</div>
|
||||
<span class="flow-tag change">Modified</span>
|
||||
</div>
|
||||
<div class="flow-arrow">↓</div>
|
||||
<div class="flow-node new">
|
||||
<div>
|
||||
<strong>RelationshipBadge.svelte</strong> — <code style="font-size:9.5px;background:#eaf7f6;padding:1px 3px;border-radius:2px">src/lib/components/RelationshipBadge.svelte</code><br>
|
||||
<span style="font-size:10.5px;color:#555">Props: <code style="font-size:9.5px;background:#eaf7f6;padding:1px 3px;border-radius:2px">labelFromA: string</code>, <code style="font-size:9.5px;background:#eaf7f6;padding:1px 3px;border-radius:2px">labelFromB: string</code>. Purely presentational — no logic, no API calls.</span>
|
||||
</div>
|
||||
<span class="flow-tag new">New</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flow-cond">
|
||||
<strong>Conditional in +page.server.ts</strong><br><br>
|
||||
<code>sender.familyMember</code> requires <code>PersonSummaryDTO.familyMember: boolean</code> on the backend (part of issue #358 backend work). Until that field ships, the condition evaluates to <code>false</code> and the badge never renders — silent, correct fallback. No feature flag needed.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ══ SECTION 5 — COMPONENT MARKUP ═════════════════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="sh">
|
||||
<h2>5 · RelationshipBadge.svelte — exact markup</h2>
|
||||
</div>
|
||||
|
||||
<div style="background:#fff;border:1.5px solid #E0DDD6;border-radius:6px;overflow:hidden;margin-bottom:24px">
|
||||
<div style="background:#F4F2EC;padding:8px 14px;font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#888;border-bottom:1px solid #E0DDD6">src/lib/components/RelationshipBadge.svelte</div>
|
||||
<pre style="padding:16px;font-size:11px;color:#333;line-height:1.7;overflow-x:auto"><code><script lang="ts">
|
||||
import { m } from '$lib/paraglide/messages.js';
|
||||
|
||||
type Props = { labelFromA: string; labelFromB: string };
|
||||
let { labelFromA, labelFromB }: Props = $props();
|
||||
</script>
|
||||
|
||||
<div>
|
||||
<p class="mb-1 font-sans text-xs font-medium text-ink-3">
|
||||
{m.doc_details_field_relationship()}
|
||||
</p>
|
||||
<div class="flex items-center gap-1.5 px-2 font-serif text-sm text-ink">
|
||||
<span class="font-semibold">{labelFromA}</span>
|
||||
<svg
|
||||
class="h-3 w-3 shrink-0 text-accent"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2.5"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M13 7l5 5-5 5M6 12h12" />
|
||||
</svg>
|
||||
<span class="font-semibold">{labelFromB}</span>
|
||||
</div>
|
||||
</div></code></pre>
|
||||
</div>
|
||||
|
||||
<div style="background:#fff;border:1.5px solid #E0DDD6;border-radius:6px;overflow:hidden">
|
||||
<div style="background:#F4F2EC;padding:8px 14px;font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#888;border-bottom:1px solid #E0DDD6">Placement in DocumentMetadataDrawer.svelte — Personen column, end of block</div>
|
||||
<pre style="padding:16px;font-size:11px;color:#333;line-height:1.7;overflow-x:auto"><code><!-- existing receiver section -->
|
||||
{#if receivers.length > 0}
|
||||
<div>
|
||||
<p class="mb-1 font-sans text-xs font-medium text-ink-3">
|
||||
{m.doc_details_field_receivers()}
|
||||
</p>
|
||||
<!-- ... receiver cards ... -->
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- new: relationship badge —— after receivers -->
|
||||
{#if inferredRelationship}
|
||||
<RelationshipBadge
|
||||
labelFromA={inferredRelationship.labelFromA}
|
||||
labelFromB={inferredRelationship.labelFromB}
|
||||
/>
|
||||
{/if}</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ══ SECTION 6 — i18n ══════════════════════════════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="sh">
|
||||
<h2>6 · i18n</h2>
|
||||
<p>One new key. The relationship label strings (<code>labelFromA</code> / <code>labelFromB</code>) come pre-translated from the backend — no additional frontend keys needed for them.</p>
|
||||
</div>
|
||||
|
||||
<div class="rules">
|
||||
<table>
|
||||
<thead><tr><th>Key</th><th>de (default)</th><th>en</th><th>es</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>doc_details_field_relationship</td><td>Verwandtschaft</td><td>Relationship</td><td>Parentesco</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ══ SECTION 7 — IMPLEMENTATION NOTES ═════════════════════════════════════ -->
|
||||
<div class="section">
|
||||
<div class="sh">
|
||||
<h2>7 · Implementation notes</h2>
|
||||
</div>
|
||||
<div class="rules">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Arrow is decorative</td>
|
||||
<td>The SVG arrow carries no semantic information — the directional meaning is already in the label order (sender's label first, receiver's label second). Always <code>aria-hidden="true"</code>. Screen readers announce: "Verwandtschaft: Sohn. Vater." which is unambiguous.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>text-accent on arrow</td>
|
||||
<td>Use <code>text-accent</code> (maps to <code>#a1dcd8</code> light / <code>#00c7b1</code> dark) only on the arrow SVG stroke. Never on any text element. Both values fail WCAG at body text sizes — they are only safe for non-text decorative elements.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>No logic in component</td>
|
||||
<td><code>RelationshipBadge.svelte</code> has no conditional logic and no API calls. All conditions (familyMember flags, receiver count, 404 handling) live in <code>+page.server.ts</code>. If <code>inferredRelationship</code> is non-null, the badge renders — full stop.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Backend prerequisite</td>
|
||||
<td><code>PersonSummaryDTO</code> must expose <code>familyMember: boolean</code>. Until it ships, <code>sender.familyMember</code> is <code>undefined</code> → condition is <code>false</code> → no API call made → badge silently absent. No code changes needed on both sides of the ship date.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Multiple receivers</td>
|
||||
<td>Check <code>receivers.length === 1</code> in <code>+page.server.ts</code> before calling the inference endpoint. Do not call it for 0 or 2+ receivers. Badge is absent on multi-receiver documents regardless of family membership.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Prop threading</td>
|
||||
<td><code>inferredRelationship</code> is typed as <code>{ labelFromA: string; labelFromB: string } | null</code> on <code>DocumentTopBar</code> and <code>DocumentMetadataDrawer</code>. The <code>path</code> field from <code>InferredRelationshipDTO</code> is loaded in the server but not passed to the component — reserved for a future tooltip.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Future tooltip</td>
|
||||
<td>The inference path (<code>data.inferredRelationship.path</code>) is available server-side and can be surfaced as a tooltip on the badge in a follow-up. No design work needed now — just thread the prop when the time comes.</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div><!-- /doc -->
|
||||
</body>
|
||||
</html>
|
||||
527
docs/specs/timeline-density-filter-spec.html
Normal file
527
docs/specs/timeline-density-filter-spec.html
Normal file
@@ -0,0 +1,527 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Timeline Date-Range Filter · Spec #385 · Familienarchiv</title>
|
||||
<style>
|
||||
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
||||
body{font-family:'Helvetica Neue',Arial,sans-serif;background:#ECEAE4;color:#1A1A1A;line-height:1.5}
|
||||
.page{max-width:1360px;margin:0 auto;padding:48px 32px}
|
||||
|
||||
.mh{padding-bottom:24px;border-bottom:3px solid #012851;margin-bottom:48px}
|
||||
.mh h1{font-size:22px;font-weight:900;color:#012851;letter-spacing:-.4px}
|
||||
.mh p{font-size:12.5px;color:#555;max-width:680px;line-height:1.7;margin-top:6px}
|
||||
.mh .byline{font-size:9px;color:#AAA;font-weight:700;letter-spacing:1.5px;text-transform:uppercase;margin-top:8px}
|
||||
|
||||
.sh{margin:56px 0 28px;padding-bottom:12px;border-bottom:2px solid #E0DDD6}
|
||||
.sh h2{font-size:16px;font-weight:900;color:#012851}
|
||||
.sh p{font-size:12px;color:#666;margin-top:4px;max-width:700px;line-height:1.6}
|
||||
|
||||
.grid{display:flex;gap:20px;flex-wrap:wrap;margin-bottom:32px;align-items:flex-start}
|
||||
.col{display:flex;flex-direction:column;gap:6px}
|
||||
.lbl{font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:1.2px;color:#888;display:flex;align-items:center;gap:5px;margin-bottom:4px}
|
||||
.tag{background:#E4E0DA;color:#666;padding:1px 5px;border-radius:2px;font-size:7px;font-weight:700}
|
||||
.cap{font-size:9.5px;color:#999;font-style:italic;line-height:1.55;max-width:460px;margin-top:4px}
|
||||
|
||||
.chrome{background:#F0EFE9;border:1.5px solid #C4C0BA;border-radius:7px;overflow:hidden;box-shadow:0 3px 14px rgba(0,0,0,.09)}
|
||||
.chrome.dk{background:#060C12;border-color:#0A1520}
|
||||
.bar{height:20px;background:#E0DDD6;border-bottom:1px solid #C4C0BA;display:flex;align-items:center;padding:0 7px;gap:3px}
|
||||
.chrome.dk .bar{background:#0A1218;border-bottom-color:#0A1520}
|
||||
.dot{width:5px;height:5px;border-radius:50%;background:#BDB8B1}
|
||||
.chrome.dk .dot{background:#1A2A3A}
|
||||
.url{flex:1;height:8px;background:#CCC8C2;border-radius:5px;margin-left:4px}
|
||||
.chrome.dk .url{background:#1A2A3A}
|
||||
|
||||
.nav{height:32px;background:#012851;display:flex;align-items:center;padding:0 12px;gap:8px;flex-shrink:0}
|
||||
.chrome.dk .nav{background:#060C12}
|
||||
.nav-logo{font-size:7px;font-weight:900;color:#fff;letter-spacing:.8px;border-bottom:2px solid #A1DCD8;padding-bottom:1px}
|
||||
.nav-link{font-size:5.5px;color:rgba(255,255,255,.4);font-weight:700;text-transform:uppercase}
|
||||
.nav-link.on{color:#fff;border-bottom:1.5px solid #A1DCD8;padding-bottom:1px}
|
||||
.nav-r{margin-left:auto;display:flex;gap:5px;align-items:center}
|
||||
.nav-av{width:16px;height:16px;background:rgba(255,255,255,.1);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:5px;font-weight:800;color:rgba(255,255,255,.5)}
|
||||
|
||||
.page-body{background:#E8E7E2;padding:10px 12px;display:flex;flex-direction:column;gap:6px}
|
||||
.page-body.dk{background:#090F16}
|
||||
|
||||
.sfb{background:#fff;border:1.5px solid #D8D6CF;border-radius:3px;padding:6px 8px;display:flex;align-items:center;gap:5px;box-shadow:0 1px 3px rgba(0,0,0,.06)}
|
||||
.sfb.dk{background:#0A1218;border-color:#1E2D3D}
|
||||
.sfb-input{flex:1;height:18px;background:#F5F4EF;border:1px solid #D8D6CF;border-radius:2px;padding:0 5px;font-size:5.5px;color:#999;display:flex;align-items:center;gap:3px}
|
||||
.sfb-input.dk{background:#060C12;border-color:#1E2D3D;color:#3E5065}
|
||||
.sfb-btn{height:18px;padding:0 6px;border:1.5px solid #D8D6CF;border-radius:2px;font-size:5px;font-weight:700;text-transform:uppercase;letter-spacing:.3px;color:#666;display:flex;align-items:center;gap:2px;white-space:nowrap;background:#F5F4EF}
|
||||
.sfb-btn.dk{border-color:#1E2D3D;color:#4E6070;background:#0A1218}
|
||||
.sfb-icon{width:18px;height:18px;display:flex;align-items:center;justify-content:center;color:#999}
|
||||
|
||||
.doc-group{background:#fff;border:1.5px solid #D8D6CF;border-radius:3px;overflow:hidden;box-shadow:0 1px 3px rgba(0,0,0,.06)}
|
||||
.doc-group.dk{background:#0A1218;border-color:#1E2D3D}
|
||||
.doc-group-hdr{background:#F5F4EF;border-bottom:1px solid #E0DDD6;padding:4px 8px;font-size:5px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#888}
|
||||
.doc-group-hdr.dk{background:#060C12;border-bottom-color:#1E2D3D;color:#3E5065}
|
||||
.doc-row{display:flex;align-items:flex-start;gap:6px;padding:6px 8px;border-bottom:1px solid #F0EEE8}
|
||||
.doc-row.dk{border-bottom-color:#1A2830}
|
||||
.doc-row:last-child{border-bottom:none}
|
||||
.doc-thumb{width:20px;height:26px;background:#E8E6DF;border-radius:1px;flex-shrink:0;display:flex;align-items:center;justify-content:center}
|
||||
.doc-thumb.dk{background:#162230}
|
||||
.doc-content{flex:1;min-width:0}
|
||||
.doc-title{font-size:6px;font-weight:700;color:#1A1A1A;margin-bottom:1px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
||||
.doc-title.dk{color:#C8D8E8}
|
||||
.doc-meta{font-size:5px;color:#999}
|
||||
.doc-meta.dk{color:#3E5065}
|
||||
.doc-right{width:55px;flex-shrink:0;font-size:5px;color:#888;text-align:right;line-height:1.6}
|
||||
.doc-right.dk{color:#3E5065}
|
||||
|
||||
.rules{background:#fff;border:1px solid #E0DDD6;border-radius:6px;overflow:hidden;margin-top:28px}
|
||||
.rules table{width:100%;border-collapse:collapse}
|
||||
.rules th{background:#F4F2EC;font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#888;padding:8px 12px;text-align:left;border-bottom:1px solid #E0DDD6}
|
||||
.rules td{font-size:11px;color:#444;padding:8px 12px;border-bottom:1px solid #F0EEE8;vertical-align:top;line-height:1.55}
|
||||
.rules tr:last-child td{border-bottom:none}
|
||||
.rules td:first-child{font-size:9px;font-weight:700;color:#012851;white-space:nowrap;width:150px}
|
||||
.rules td code{font-size:9px;background:#F0EFE9;padding:1px 4px;border-radius:2px;color:#555}
|
||||
|
||||
.impl-ref{background:#fff;border:1px solid #E0DDD6;border-radius:6px;overflow:hidden;margin-top:28px}
|
||||
.impl-ref table{width:100%;border-collapse:collapse}
|
||||
.impl-ref th{background:#012851;color:#fff;padding:7px 12px;text-align:left;font-size:8px;font-weight:800;letter-spacing:.6px;text-transform:uppercase}
|
||||
.impl-ref td{padding:7px 12px;border-bottom:1px solid #F0EEE8;vertical-align:top;font-size:11px;color:#444;line-height:1.55}
|
||||
.impl-ref tr:nth-child(even) td{background:#FAFAF7}
|
||||
.impl-ref td:first-child{font-size:9px;font-weight:700;color:#012851;white-space:nowrap;width:170px}
|
||||
.impl-ref td code{font-size:9px;background:#F0EFE9;padding:1px 4px;border-radius:2px;font-family:'Courier New',monospace;color:#333}
|
||||
|
||||
.callout{padding:12px 16px;border-radius:4px;font-size:11.5px;line-height:1.6;margin-bottom:16px}
|
||||
.callout.navy{background:rgba(1,40,81,.06);border-left:3px solid #012851;color:#333}
|
||||
.callout.orange{background:#FEF3E2;border-left:3px solid #C26A00;color:#333}
|
||||
.callout.green{background:#EAF5EA;border-left:3px solid #2E6E39;color:#333}
|
||||
.callout strong{font-weight:700}
|
||||
|
||||
hr{border:none;border-top:2px dashed #C8C4BE;margin:60px 0}
|
||||
|
||||
/* Timeline widget styles for mockup */
|
||||
.tl-widget{background:#fff;border:1.5px solid #D8D6CF;border-radius:3px;padding:6px 8px 4px;box-shadow:0 1px 3px rgba(0,0,0,.06)}
|
||||
.tl-widget.dk{background:#0A1218;border-color:#1E2D3D}
|
||||
.tl-bars{height:38px;display:flex;align-items:flex-end;gap:1px;position:relative}
|
||||
.tl-bar{flex:1;border-radius:1px 1px 0 0;min-height:1px;transition:background .15s}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
|
||||
<!-- ══════════════════════════════════════════
|
||||
MASTHEAD
|
||||
══════════════════════════════════════════ -->
|
||||
<div class="mh">
|
||||
<h1>Timeline Date-Range Filter · Spec #385</h1>
|
||||
<p>Horizontal bar-chart widget placed between the SearchFilterBar and DocumentList on <code>/documents</code>. Bar height per month reflects document density across the archive. Drag or click to select a date range; the document list filters to that period. Hidden when calendar view is active.</p>
|
||||
<div class="byline">Familienarchiv · 2026-05-03 · Leonie Voss, UX Lead · Issue #385</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ══════════════════════════════════════════
|
||||
SECTION 1 — ANATOMY
|
||||
══════════════════════════════════════════ -->
|
||||
<div class="sh"><h2>1 · Anatomy</h2><p>Seven named zones. Every implementation decision maps back to one of these.</p></div>
|
||||
|
||||
<div style="background:#fff;border:1.5px solid #E0DDD6;border-radius:6px;overflow:hidden;padding:24px;margin-bottom:24px">
|
||||
<div style="position:relative;background:#F9F8F5;border:1.5px solid #D8D6CF;border-radius:4px;padding:10px 12px 6px">
|
||||
<!-- Row 1: label + total -->
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:6px">
|
||||
<span style="font-size:9px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#888">① Widget-Header (Label + Gesamtanzahl)</span>
|
||||
<span style="font-size:9px;color:#AAA;font-style:italic">② Gesamtanzahl-Badge</span>
|
||||
</div>
|
||||
<!-- Bar chart -->
|
||||
<div style="height:48px;display:flex;align-items:flex-end;gap:1px;position:relative">
|
||||
<!-- annotation arrows pointing to parts -->
|
||||
<div style="position:absolute;top:-18px;left:40%;font-size:8px;color:#C26A00;font-weight:700">③ Bar (1 Monat)</div>
|
||||
<div style="position:absolute;top:-18px;left:62%;font-size:8px;color:#012851;font-weight:700">④ Auswahl-Bereich (navy)</div>
|
||||
<!-- bars: unselected mint -->
|
||||
<div style="flex:1;height:13%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:17%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:8%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:13%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:21%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:17%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:25%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:17%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:21%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:25%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:33%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:38%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<!-- selected range: navy -->
|
||||
<div style="flex:1;height:50%;background:#012851;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:42%;background:#012851;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:75%;background:#012851;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:100%;background:#012851;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:92%;background:#012851;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:83%;background:#012851;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:67%;background:#012851;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:58%;background:#012851;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:46%;background:#012851;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:38%;background:#012851;border-radius:1px 1px 0 0"></div>
|
||||
<!-- unselected again -->
|
||||
<div style="flex:1;height:42%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:33%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:29%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:25%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:21%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:25%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:21%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:17%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:21%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:17%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:13%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:17%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:17%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:13%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:17%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:13%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:21%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:33%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:46%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:58%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:67%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:58%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:50%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:42%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:29%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:21%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:17%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:13%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
<div style="flex:1;height:8%;background:#D4EDE9;border-radius:1px 1px 0 0"></div>
|
||||
</div>
|
||||
<!-- Baseline + year labels -->
|
||||
<div style="height:1px;background:#E0DDD6;margin-bottom:2px"></div>
|
||||
<div style="display:flex;justify-content:space-between;margin-bottom:2px">
|
||||
<span style="font-size:7px;color:#BBB">1900</span>
|
||||
<span style="font-size:7px;color:#BBB">1910</span>
|
||||
<span style="font-size:7px;color:#BBB">1920</span>
|
||||
<span style="font-size:7px;color:#BBB">1930</span>
|
||||
<span style="font-size:7px;color:#BBB">1940</span>
|
||||
<span style="font-size:7px;color:#BBB">1950</span>
|
||||
</div>
|
||||
<!-- Handle annotations -->
|
||||
<div style="display:flex;justify-content:space-between;font-size:8px;color:#666;margin-top:6px">
|
||||
<span style="color:#C26A00;font-weight:700">⑤ Linker Zieh-Griff (Start)</span>
|
||||
<span style="color:#C26A00;font-weight:700">⑥ Rechter Zieh-Griff (Ende)</span>
|
||||
<span style="color:#2E6E39;font-weight:700">⑦ Löschen-Button (×)</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:16px;display:grid;grid-template-columns:repeat(3,1fr);gap:12px">
|
||||
<div style="font-size:11px;color:#444;line-height:1.6"><span style="font-weight:700;color:#012851">① Widget-Header</span> — Label "Zeitachse" + Gesamtanzahl aller Dokumente im Archiv.</div>
|
||||
<div style="font-size:11px;color:#444;line-height:1.6"><span style="font-weight:700;color:#012851">③ Bar</span> — Ein Balken pro Monat. Höhe proportional zur Dokumentenanzahl dieses Monats. Max-Höhe = Monat mit den meisten Dokumenten.</div>
|
||||
<div style="font-size:11px;color:#444;line-height:1.6"><span style="font-weight:700;color:#012851">④–⑥ Auswahl</span> — Ausgewählte Balken werden navy (#012851). Zieh-Griffe definieren Start/Ende. ⑦ Löschen-Button erscheint sobald eine Auswahl aktiv ist.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ══════════════════════════════════════════
|
||||
SECTION 2 — DESIGN TOKENS
|
||||
══════════════════════════════════════════ -->
|
||||
<div class="sh"><h2>2 · Design-Tokens</h2><p>Das Widget verwendet ausschließlich diese Tokens — keine Hard-coded-Werte in der Komponente.</p></div>
|
||||
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:40px">
|
||||
<div style="background:#fff;border:1px solid #E0DDD6;border-radius:6px;overflow:hidden">
|
||||
<div style="background:#F4F2EC;padding:8px 14px;font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#888;border-bottom:1px solid #E0DDD6">Light theme</div>
|
||||
<table style="width:100%;border-collapse:collapse;font-size:11px">
|
||||
<tr style="border-bottom:1px solid #F0EEE8"><td style="padding:6px 14px;color:#888;font-size:9px;font-weight:700;width:170px">bg-surface</td><td style="padding:6px 14px"><span style="display:inline-block;width:12px;height:12px;background:#fff;border:1px solid #DDD;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#FFFFFF — Widget-Hintergrund</td></tr>
|
||||
<tr style="border-bottom:1px solid #F0EEE8"><td style="padding:6px 14px;color:#888;font-size:9px;font-weight:700">border-line</td><td style="padding:6px 14px"><span style="display:inline-block;width:12px;height:12px;background:#D8D6CF;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#D8D6CF — Widget-Rahmen</td></tr>
|
||||
<tr style="border-bottom:1px solid #F0EEE8"><td style="padding:6px 14px;color:#888;font-size:9px;font-weight:700">bar-idle</td><td style="padding:6px 14px"><span style="display:inline-block;width:12px;height:12px;background:#D4EDE9;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#D4EDE9 — Balken ohne Auswahl (helles Mint)</td></tr>
|
||||
<tr style="border-bottom:1px solid #F0EEE8"><td style="padding:6px 14px;color:#888;font-size:9px;font-weight:700">bar-hover</td><td style="padding:6px 14px"><span style="display:inline-block;width:12px;height:12px;background:#A1DCD8;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#A1DCD8 — Balken on hover (brand-mint)</td></tr>
|
||||
<tr style="border-bottom:1px solid #F0EEE8"><td style="padding:6px 14px;color:#888;font-size:9px;font-weight:700">bar-selected</td><td style="padding:6px 14px"><span style="display:inline-block;width:12px;height:12px;background:#012851;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#012851 — Balken im Auswahl-Bereich (navy)</td></tr>
|
||||
<tr style="border-bottom:1px solid #F0EEE8"><td style="padding:6px 14px;color:#888;font-size:9px;font-weight:700">bar-outside</td><td style="padding:6px 14px"><span style="display:inline-block;width:12px;height:12px;background:#E8E6E0;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#E8E6E0 — Balken außerhalb der Auswahl (gedimmt)</td></tr>
|
||||
<tr style="border-bottom:1px solid #F0EEE8"><td style="padding:6px 14px;color:#888;font-size:9px;font-weight:700">handle</td><td style="padding:6px 14px"><span style="display:inline-block;width:12px;height:12px;background:#012851;border-radius:50%;vertical-align:middle;margin-right:6px"></span>#012851 mit weißem Kern — Zieh-Griff</td></tr>
|
||||
<tr><td style="padding:6px 14px;color:#888;font-size:9px;font-weight:700">tooltip-bg</td><td style="padding:6px 14px"><span style="display:inline-block;width:12px;height:12px;background:#1A1A1A;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#1A1A1A — Tooltip-Hintergrund</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
<div style="background:#0F1923;border:1px solid #1E2D3D;border-radius:6px;overflow:hidden">
|
||||
<div style="background:#0A1218;padding:8px 14px;font-size:8px;font-weight:800;text-transform:uppercase;letter-spacing:.8px;color:#4E6070;border-bottom:1px solid #1E2D3D">Dark theme</div>
|
||||
<table style="width:100%;border-collapse:collapse;font-size:11px">
|
||||
<tr style="border-bottom:1px solid #1A2830"><td style="padding:6px 14px;color:#4E6070;font-size:9px;font-weight:700;width:170px">bg-surface</td><td style="padding:6px 14px;color:#8AAABB"><span style="display:inline-block;width:12px;height:12px;background:#0A1218;border:1px solid #1E2D3D;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#0A1218 — Widget-Hintergrund</td></tr>
|
||||
<tr style="border-bottom:1px solid #1A2830"><td style="padding:6px 14px;color:#4E6070;font-size:9px;font-weight:700">bar-idle</td><td style="padding:6px 14px;color:#8AAABB"><span style="display:inline-block;width:12px;height:12px;background:#0E2535;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#0E2535 — Balken ohne Auswahl (dunkles Teal)</td></tr>
|
||||
<tr style="border-bottom:1px solid #1A2830"><td style="padding:6px 14px;color:#4E6070;font-size:9px;font-weight:700">bar-hover</td><td style="padding:6px 14px;color:#8AAABB"><span style="display:inline-block;width:12px;height:12px;background:#1E4060;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#1E4060 — hover</td></tr>
|
||||
<tr style="border-bottom:1px solid #1A2830"><td style="padding:6px 14px;color:#4E6070;font-size:9px;font-weight:700">bar-selected</td><td style="padding:6px 14px;color:#8AAABB"><span style="display:inline-block;width:12px;height:12px;background:#A1DCD8;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#A1DCD8 — Auswahl in Dark mode (Mint, invertiert)</td></tr>
|
||||
<tr style="border-bottom:1px solid #1A2830"><td style="padding:6px 14px;color:#4E6070;font-size:9px;font-weight:700">bar-outside</td><td style="padding:6px 14px;color:#8AAABB"><span style="display:inline-block;width:12px;height:12px;background:#0A1218;border:1px solid #1E2D3D;border-radius:2px;vertical-align:middle;margin-right:6px"></span>#0A1218 — außerhalb, kaum sichtbar</td></tr>
|
||||
<tr><td style="padding:6px 14px;color:#4E6070;font-size:9px;font-weight:700">handle</td><td style="padding:6px 14px;color:#8AAABB"><span style="display:inline-block;width:12px;height:12px;background:#A1DCD8;border-radius:50%;vertical-align:middle;margin-right:6px"></span>#A1DCD8 mit dunklem Kern</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ══════════════════════════════════════════
|
||||
SECTION 3 — STATES
|
||||
══════════════════════════════════════════ -->
|
||||
<div class="sh"><h2>3 · Zustandsmodell</h2><p>Vier Zustände. Gleiche Markup-Struktur — nur CSS-Klassen und ARIA-Attribute ändern sich.</p></div>
|
||||
|
||||
<div class="grid">
|
||||
|
||||
<!-- STATE A: IDLE -->
|
||||
<div class="col">
|
||||
<div class="lbl"><span class="tag">State A</span>Idle — keine Auswahl</div>
|
||||
<div class="chrome" style="width:480px">
|
||||
<div class="bar"><div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="url"></div></div>
|
||||
<div class="nav">
|
||||
<div class="nav-logo">Familienarchiv</div>
|
||||
<div class="nav-link" style="margin-left:8px">Dokumente</div>
|
||||
<div class="nav-r"><div class="nav-av">KR</div></div>
|
||||
</div>
|
||||
<div class="page-body">
|
||||
<!-- SearchFilterBar -->
|
||||
<div class="sfb">
|
||||
<div class="sfb-input"><span style="opacity:.5">Dokumente durchsuchen …</span></div>
|
||||
<div class="sfb-btn">Sortierung</div>
|
||||
<div class="sfb-btn" style="gap:2px">
|
||||
<span style="font-size:6px">▼</span> Filter
|
||||
</div>
|
||||
<!-- View toggle: List active -->
|
||||
<div style="display:flex;gap:1px;border:1.5px solid #D8D6CF;border-radius:2px;overflow:hidden">
|
||||
<div style="padding:3px 6px;background:#012851;display:flex;align-items:center" title="Listenansicht (aktiv)">
|
||||
<svg width="9" height="9" viewBox="0 0 16 16" fill="#A1DCD8"><rect x="0" y="0" width="16" height="3"/><rect x="0" y="6" width="16" height="3"/><rect x="0" y="12" width="16" height="3"/></svg>
|
||||
</div>
|
||||
<div style="padding:3px 6px;background:#F5F4EF;display:flex;align-items:center" title="Kalenderansicht">
|
||||
<svg width="9" height="9" viewBox="0 0 16 16" fill="#888"><rect x="1" y="2" width="14" height="13" rx="1" fill="none" stroke="#888" stroke-width="2"/><line x1="5" y1="0" x2="5" y2="5" stroke="#888" stroke-width="2"/><line x1="11" y1="0" x2="11" y2="5" stroke="#888" stroke-width="2"/><line x1="1" y1="7" x2="15" y2="7" stroke="#888" stroke-width="1.5"/></svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Timeline widget: IDLE -->
|
||||
<div class="tl-widget">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:4px">
|
||||
<span style="font-size:5px;font-weight:800;text-transform:uppercase;letter-spacing:.6px;color:#999">Zeitachse</span>
|
||||
<span style="font-size:5px;color:#BBB">412 Dokumente · 1900–1950</span>
|
||||
</div>
|
||||
<div class="tl-bars">
|
||||
<div class="tl-bar" style="height:13%;background:#D4EDE9"></div><div class="tl-bar" style="height:17%;background:#D4EDE9"></div><div class="tl-bar" style="height:8%;background:#D4EDE9"></div><div class="tl-bar" style="height:13%;background:#D4EDE9"></div><div class="tl-bar" style="height:21%;background:#D4EDE9"></div><div class="tl-bar" style="height:17%;background:#D4EDE9"></div><div class="tl-bar" style="height:25%;background:#D4EDE9"></div><div class="tl-bar" style="height:17%;background:#D4EDE9"></div><div class="tl-bar" style="height:21%;background:#D4EDE9"></div><div class="tl-bar" style="height:25%;background:#D4EDE9"></div><div class="tl-bar" style="height:33%;background:#D4EDE9"></div><div class="tl-bar" style="height:38%;background:#D4EDE9"></div><div class="tl-bar" style="height:50%;background:#D4EDE9"></div><div class="tl-bar" style="height:42%;background:#D4EDE9"></div><div class="tl-bar" style="height:75%;background:#D4EDE9"></div><div class="tl-bar" style="height:100%;background:#D4EDE9"></div><div class="tl-bar" style="height:92%;background:#D4EDE9"></div><div class="tl-bar" style="height:83%;background:#D4EDE9"></div><div class="tl-bar" style="height:67%;background:#D4EDE9"></div><div class="tl-bar" style="height:58%;background:#D4EDE9"></div><div class="tl-bar" style="height:46%;background:#D4EDE9"></div><div class="tl-bar" style="height:38%;background:#D4EDE9"></div><div class="tl-bar" style="height:42%;background:#D4EDE9"></div><div class="tl-bar" style="height:33%;background:#D4EDE9"></div><div class="tl-bar" style="height:29%;background:#D4EDE9"></div><div class="tl-bar" style="height:25%;background:#D4EDE9"></div><div class="tl-bar" style="height:21%;background:#D4EDE9"></div><div class="tl-bar" style="height:25%;background:#D4EDE9"></div><div class="tl-bar" style="height:21%;background:#D4EDE9"></div><div class="tl-bar" style="height:17%;background:#D4EDE9"></div><div class="tl-bar" style="height:21%;background:#D4EDE9"></div><div class="tl-bar" style="height:17%;background:#D4EDE9"></div><div class="tl-bar" style="height:13%;background:#D4EDE9"></div><div class="tl-bar" style="height:17%;background:#D4EDE9"></div><div class="tl-bar" style="height:17%;background:#D4EDE9"></div><div class="tl-bar" style="height:13%;background:#D4EDE9"></div><div class="tl-bar" style="height:17%;background:#D4EDE9"></div><div class="tl-bar" style="height:13%;background:#D4EDE9"></div><div class="tl-bar" style="height:21%;background:#D4EDE9"></div><div class="tl-bar" style="height:33%;background:#D4EDE9"></div><div class="tl-bar" style="height:46%;background:#D4EDE9"></div><div class="tl-bar" style="height:58%;background:#D4EDE9"></div><div class="tl-bar" style="height:67%;background:#D4EDE9"></div><div class="tl-bar" style="height:58%;background:#D4EDE9"></div><div class="tl-bar" style="height:50%;background:#D4EDE9"></div><div class="tl-bar" style="height:42%;background:#D4EDE9"></div><div class="tl-bar" style="height:29%;background:#D4EDE9"></div><div class="tl-bar" style="height:21%;background:#D4EDE9"></div><div class="tl-bar" style="height:17%;background:#D4EDE9"></div><div class="tl-bar" style="height:13%;background:#D4EDE9"></div><div class="tl-bar" style="height:8%;background:#D4EDE9"></div>
|
||||
</div>
|
||||
<div style="height:1px;background:#E8E6E0;margin-bottom:2px"></div>
|
||||
<div style="display:flex;justify-content:space-between"><span style="font-size:4.5px;color:#CCC">1900</span><span style="font-size:4.5px;color:#CCC">1910</span><span style="font-size:4.5px;color:#CCC">1920</span><span style="font-size:4.5px;color:#CCC">1930</span><span style="font-size:4.5px;color:#CCC">1940</span><span style="font-size:4.5px;color:#CCC">1950</span></div>
|
||||
</div>
|
||||
<!-- Document list preview -->
|
||||
<div class="doc-group">
|
||||
<div class="doc-group-hdr">1915 · 24 Dokumente</div>
|
||||
<div class="doc-row"><div class="doc-thumb"></div><div class="doc-content"><div class="doc-title">Brief über die Lage an der Westfront</div><div class="doc-meta">Karl Raddatz → Elfriede Raddatz</div></div><div class="doc-right">März 1915</div></div>
|
||||
<div class="doc-row"><div class="doc-thumb"></div><div class="doc-content"><div class="doc-title">Feldpost aus Verdun</div><div class="doc-meta">Hans Raddatz → Karl Raddatz</div></div><div class="doc-right">Juli 1915</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cap">Idle: Alle Balken in hellem Mint. Kein Griff, kein Löschen-Button. Cursor: <code>crosshair</code> über den Balken.</div>
|
||||
</div>
|
||||
|
||||
<!-- STATE B: HOVER TOOLTIP -->
|
||||
<div class="col">
|
||||
<div class="lbl"><span class="tag">State B</span>Hover — Tooltip</div>
|
||||
<div class="chrome" style="width:300px">
|
||||
<div class="bar"><div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="url"></div></div>
|
||||
<div class="nav"><div class="nav-logo">Familienarchiv</div><div class="nav-r"><div class="nav-av">KR</div></div></div>
|
||||
<div class="page-body">
|
||||
<div class="tl-widget" style="position:relative">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:4px">
|
||||
<span style="font-size:5px;font-weight:800;text-transform:uppercase;letter-spacing:.6px;color:#999">Zeitachse</span>
|
||||
<span style="font-size:5px;color:#BBB">412 Dokumente · 1900–1950</span>
|
||||
</div>
|
||||
<div class="tl-bars" style="position:relative">
|
||||
<div class="tl-bar" style="height:13%;background:#D4EDE9"></div><div class="tl-bar" style="height:17%;background:#D4EDE9"></div><div class="tl-bar" style="height:8%;background:#D4EDE9"></div><div class="tl-bar" style="height:13%;background:#D4EDE9"></div><div class="tl-bar" style="height:21%;background:#D4EDE9"></div><div class="tl-bar" style="height:17%;background:#D4EDE9"></div><div class="tl-bar" style="height:25%;background:#D4EDE9"></div><div class="tl-bar" style="height:17%;background:#D4EDE9"></div><div class="tl-bar" style="height:21%;background:#D4EDE9"></div><div class="tl-bar" style="height:25%;background:#D4EDE9"></div><div class="tl-bar" style="height:33%;background:#D4EDE9"></div><div class="tl-bar" style="height:38%;background:#D4EDE9"></div><div class="tl-bar" style="height:50%;background:#D4EDE9"></div><div class="tl-bar" style="height:42%;background:#D4EDE9"></div><div class="tl-bar" style="height:75%;background:#D4EDE9"></div>
|
||||
<!-- HOVERED bar (1915 Aug = position 16) -->
|
||||
<div class="tl-bar" style="height:100%;background:#A1DCD8;position:relative">
|
||||
<!-- tooltip -->
|
||||
<div style="position:absolute;bottom:110%;left:50%;transform:translateX(-50%);background:#1A1A1A;color:#fff;font-size:5px;font-weight:700;padding:3px 5px;border-radius:2px;white-space:nowrap;z-index:10">August 1915 · 24 Dok.</div>
|
||||
</div>
|
||||
<div class="tl-bar" style="height:92%;background:#D4EDE9"></div><div class="tl-bar" style="height:83%;background:#D4EDE9"></div><div class="tl-bar" style="height:67%;background:#D4EDE9"></div><div class="tl-bar" style="height:58%;background:#D4EDE9"></div><div class="tl-bar" style="height:46%;background:#D4EDE9"></div><div class="tl-bar" style="height:38%;background:#D4EDE9"></div><div class="tl-bar" style="height:42%;background:#D4EDE9"></div><div class="tl-bar" style="height:33%;background:#D4EDE9"></div><div class="tl-bar" style="height:29%;background:#D4EDE9"></div><div class="tl-bar" style="height:25%;background:#D4EDE9"></div><div class="tl-bar" style="height:21%;background:#D4EDE9"></div><div class="tl-bar" style="height:25%;background:#D4EDE9"></div><div class="tl-bar" style="height:21%;background:#D4EDE9"></div><div class="tl-bar" style="height:17%;background:#D4EDE9"></div><div class="tl-bar" style="height:21%;background:#D4EDE9"></div><div class="tl-bar" style="height:17%;background:#D4EDE9"></div><div class="tl-bar" style="height:13%;background:#D4EDE9"></div><div class="tl-bar" style="height:17%;background:#D4EDE9"></div><div class="tl-bar" style="height:17%;background:#D4EDE9"></div><div class="tl-bar" style="height:13%;background:#D4EDE9"></div><div class="tl-bar" style="height:17%;background:#D4EDE9"></div><div class="tl-bar" style="height:13%;background:#D4EDE9"></div><div class="tl-bar" style="height:21%;background:#D4EDE9"></div><div class="tl-bar" style="height:33%;background:#D4EDE9"></div><div class="tl-bar" style="height:46%;background:#D4EDE9"></div><div class="tl-bar" style="height:58%;background:#D4EDE9"></div><div class="tl-bar" style="height:67%;background:#D4EDE9"></div><div class="tl-bar" style="height:58%;background:#D4EDE9"></div><div class="tl-bar" style="height:50%;background:#D4EDE9"></div><div class="tl-bar" style="height:42%;background:#D4EDE9"></div><div class="tl-bar" style="height:29%;background:#D4EDE9"></div><div class="tl-bar" style="height:21%;background:#D4EDE9"></div><div class="tl-bar" style="height:17%;background:#D4EDE9"></div><div class="tl-bar" style="height:13%;background:#D4EDE9"></div><div class="tl-bar" style="height:8%;background:#D4EDE9"></div>
|
||||
</div>
|
||||
<div style="height:1px;background:#E8E6E0;margin-bottom:2px"></div>
|
||||
<div style="display:flex;justify-content:space-between"><span style="font-size:4.5px;color:#CCC">1900</span><span style="font-size:4.5px;color:#CCC">1910</span><span style="font-size:4.5px;color:#CCC">1920</span><span style="font-size:4.5px;color:#CCC">1930</span><span style="font-size:4.5px;color:#CCC">1940</span><span style="font-size:4.5px;color:#CCC">1950</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cap">Hover: Balken wechselt zu brand-mint. Tooltip erscheint: „Monat Jahr · N Dok." Cursor: <code>pointer</code>.</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- STATE C: ACTIVE SELECTION -->
|
||||
<div class="lbl" style="margin-bottom:8px"><span class="tag">State C</span>Aktive Auswahl — 1914–1920 (Erster Weltkrieg)</div>
|
||||
<div class="grid">
|
||||
<div class="col">
|
||||
<div class="chrome" style="width:640px">
|
||||
<div class="bar"><div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="url"></div></div>
|
||||
<div class="nav"><div class="nav-logo">Familienarchiv</div><div class="nav-link on" style="margin-left:8px">Dokumente</div><div class="nav-r"><div class="nav-av">KR</div></div></div>
|
||||
<div class="page-body">
|
||||
<div class="sfb">
|
||||
<div class="sfb-input"><span style="opacity:.5">Dokumente durchsuchen …</span></div>
|
||||
<div class="sfb-btn">Sortierung</div>
|
||||
<div class="sfb-btn">▼ Filter</div>
|
||||
<!-- active date badge in filter bar -->
|
||||
<div style="display:flex;align-items:center;gap:3px;background:#012851;border-radius:2px;padding:2px 6px">
|
||||
<span style="font-size:5px;font-weight:700;color:#A1DCD8">Jan 1914 – Dez 1920</span>
|
||||
<span style="font-size:7px;color:#A1DCD8;cursor:pointer;line-height:1" title="Auswahl löschen">×</span>
|
||||
</div>
|
||||
<div style="display:flex;gap:1px;border:1.5px solid #D8D6CF;border-radius:2px;overflow:hidden">
|
||||
<div style="padding:3px 6px;background:#012851"><svg width="9" height="9" viewBox="0 0 16 16" fill="#A1DCD8"><rect x="0" y="0" width="16" height="3"/><rect x="0" y="6" width="16" height="3"/><rect x="0" y="12" width="16" height="3"/></svg></div>
|
||||
<div style="padding:3px 6px;background:#F5F4EF"><svg width="9" height="9" viewBox="0 0 16 16" fill="#888"><rect x="1" y="2" width="14" height="13" rx="1" fill="none" stroke="#888" stroke-width="2"/><line x1="5" y1="0" x2="5" y2="5" stroke="#888" stroke-width="2"/><line x1="11" y1="0" x2="11" y2="5" stroke="#888" stroke-width="2"/><line x1="1" y1="7" x2="15" y2="7" stroke="#888" stroke-width="1.5"/></svg></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Timeline: active selection -->
|
||||
<div class="tl-widget" style="position:relative">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:4px">
|
||||
<span style="font-size:5px;font-weight:800;text-transform:uppercase;letter-spacing:.6px;color:#999">Zeitachse</span>
|
||||
<span style="font-size:5px;color:#012851;font-weight:700">187 Dokumente · Jan 1914 – Dez 1920</span>
|
||||
</div>
|
||||
<div class="tl-bars" style="position:relative">
|
||||
<!-- 1900–1913: dimmed/outside -->
|
||||
<div class="tl-bar" style="height:13%;background:#E8E6E0"></div><div class="tl-bar" style="height:17%;background:#E8E6E0"></div><div class="tl-bar" style="height:8%;background:#E8E6E0"></div><div class="tl-bar" style="height:13%;background:#E8E6E0"></div><div class="tl-bar" style="height:21%;background:#E8E6E0"></div><div class="tl-bar" style="height:17%;background:#E8E6E0"></div><div class="tl-bar" style="height:25%;background:#E8E6E0"></div><div class="tl-bar" style="height:17%;background:#E8E6E0"></div><div class="tl-bar" style="height:21%;background:#E8E6E0"></div><div class="tl-bar" style="height:25%;background:#E8E6E0"></div><div class="tl-bar" style="height:33%;background:#E8E6E0"></div><div class="tl-bar" style="height:38%;background:#E8E6E0"></div><div class="tl-bar" style="height:50%;background:#E8E6E0"></div><div class="tl-bar" style="height:42%;background:#E8E6E0"></div>
|
||||
<!-- left handle -->
|
||||
<div style="position:relative;flex:1;display:flex;align-items:flex-end">
|
||||
<div style="width:100%;height:75%;background:#012851;border-radius:1px 1px 0 0"></div>
|
||||
<div style="position:absolute;top:-5px;left:50%;transform:translateX(-50%);width:8px;height:8px;border-radius:50%;background:#012851;border:2px solid #fff;box-shadow:0 0 0 1px #012851;cursor:ew-resize;z-index:5"></div>
|
||||
</div>
|
||||
<!-- 1914–1920: selected navy -->
|
||||
<div class="tl-bar" style="height:100%;background:#012851"></div><div class="tl-bar" style="height:92%;background:#012851"></div><div class="tl-bar" style="height:83%;background:#012851"></div><div class="tl-bar" style="height:67%;background:#012851"></div><div class="tl-bar" style="height:58%;background:#012851"></div><div class="tl-bar" style="height:46%;background:#012851"></div><div class="tl-bar" style="height:38%;background:#012851"></div>
|
||||
<!-- right handle -->
|
||||
<div style="position:relative;flex:1;display:flex;align-items:flex-end">
|
||||
<div style="width:100%;height:42%;background:#012851;border-radius:1px 1px 0 0"></div>
|
||||
<div style="position:absolute;top:-5px;left:50%;transform:translateX(-50%);width:8px;height:8px;border-radius:50%;background:#012851;border:2px solid #fff;box-shadow:0 0 0 1px #012851;cursor:ew-resize;z-index:5"></div>
|
||||
</div>
|
||||
<!-- 1921–1950: dimmed -->
|
||||
<div class="tl-bar" style="height:33%;background:#E8E6E0"></div><div class="tl-bar" style="height:29%;background:#E8E6E0"></div><div class="tl-bar" style="height:25%;background:#E8E6E0"></div><div class="tl-bar" style="height:21%;background:#E8E6E0"></div><div class="tl-bar" style="height:25%;background:#E8E6E0"></div><div class="tl-bar" style="height:21%;background:#E8E6E0"></div><div class="tl-bar" style="height:17%;background:#E8E6E0"></div><div class="tl-bar" style="height:21%;background:#E8E6E0"></div><div class="tl-bar" style="height:17%;background:#E8E6E0"></div><div class="tl-bar" style="height:13%;background:#E8E6E0"></div><div class="tl-bar" style="height:17%;background:#E8E6E0"></div><div class="tl-bar" style="height:17%;background:#E8E6E0"></div><div class="tl-bar" style="height:13%;background:#E8E6E0"></div><div class="tl-bar" style="height:17%;background:#E8E6E0"></div><div class="tl-bar" style="height:13%;background:#E8E6E0"></div><div class="tl-bar" style="height:21%;background:#E8E6E0"></div><div class="tl-bar" style="height:33%;background:#E8E6E0"></div><div class="tl-bar" style="height:46%;background:#E8E6E0"></div><div class="tl-bar" style="height:58%;background:#E8E6E0"></div><div class="tl-bar" style="height:67%;background:#E8E6E0"></div><div class="tl-bar" style="height:58%;background:#E8E6E0"></div><div class="tl-bar" style="height:50%;background:#E8E6E0"></div><div class="tl-bar" style="height:42%;background:#E8E6E0"></div><div class="tl-bar" style="height:29%;background:#E8E6E0"></div><div class="tl-bar" style="height:21%;background:#E8E6E0"></div><div class="tl-bar" style="height:17%;background:#E8E6E0"></div><div class="tl-bar" style="height:13%;background:#E8E6E0"></div><div class="tl-bar" style="height:8%;background:#E8E6E0"></div>
|
||||
</div>
|
||||
<div style="height:1px;background:#E8E6E0;margin-bottom:2px"></div>
|
||||
<div style="display:flex;justify-content:space-between"><span style="font-size:4.5px;color:#CCC">1900</span><span style="font-size:4.5px;color:#CCC">1910</span><span style="font-size:4.5px;color:#CCC">1920</span><span style="font-size:4.5px;color:#CCC">1930</span><span style="font-size:4.5px;color:#CCC">1940</span><span style="font-size:4.5px;color:#CCC">1950</span></div>
|
||||
</div>
|
||||
<!-- Filtered doc list -->
|
||||
<div class="doc-group">
|
||||
<div class="doc-group-hdr">1915 · 24 Dokumente</div>
|
||||
<div class="doc-row"><div class="doc-thumb"></div><div class="doc-content"><div class="doc-title">Brief über die Lage an der Westfront</div><div class="doc-meta">Karl Raddatz → Elfriede Raddatz</div></div><div class="doc-right">März 1915</div></div>
|
||||
<div class="doc-row"><div class="doc-thumb"></div><div class="doc-content"><div class="doc-title">Feldpost aus Verdun</div><div class="doc-meta">Hans Raddatz → Karl Raddatz</div></div><div class="doc-right">Juli 1915</div></div>
|
||||
</div>
|
||||
<div class="doc-group">
|
||||
<div class="doc-group-hdr">1914 · 18 Dokumente</div>
|
||||
<div class="doc-row"><div class="doc-thumb"></div><div class="doc-content"><div class="doc-title">Kriegsausbruch — Brief an die Familie</div><div class="doc-meta">Karl Raddatz → Elfriede Raddatz, Hans Raddatz</div></div><div class="doc-right">Aug 1914</div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cap">Aktive Auswahl: Balken im Bereich navy, außerhalb gedimmt. Handles links/rechts ziehbar. Label in SearchFilterBar zeigt „Jan 1914 – Dez 1920". Dokumentenliste zeigt nur Dokumente im gewählten Zeitraum.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ══════════════════════════════════════════
|
||||
SECTION 4 — DARK MODE
|
||||
══════════════════════════════════════════ -->
|
||||
<div class="sh"><h2>4 · Dark Mode</h2><p>Gleiches Verhalten, Farben remappt. Auswahl-Balken wechseln zu Mint (invertiert zu Light mode).</p></div>
|
||||
|
||||
<div class="grid">
|
||||
<div class="col">
|
||||
<div class="lbl"><span class="tag">Dark · Idle</span></div>
|
||||
<div class="chrome dk" style="width:380px">
|
||||
<div class="bar"><div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="url"></div></div>
|
||||
<div class="nav" style="background:#060C12"><div class="nav-logo">Familienarchiv</div><div class="nav-r"><div class="nav-av">KR</div></div></div>
|
||||
<div class="page-body dk">
|
||||
<div class="sfb dk"><div class="sfb-input dk"><span>Dokumente durchsuchen …</span></div><div class="sfb-btn dk">▼ Filter</div></div>
|
||||
<div class="tl-widget dk">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:4px">
|
||||
<span style="font-size:5px;font-weight:800;text-transform:uppercase;letter-spacing:.6px;color:#3E5065">Zeitachse</span>
|
||||
<span style="font-size:5px;color:#2A3A4A">412 Dokumente · 1900–1950</span>
|
||||
</div>
|
||||
<div class="tl-bars">
|
||||
<div class="tl-bar" style="height:13%;background:#0E2535"></div><div class="tl-bar" style="height:17%;background:#0E2535"></div><div class="tl-bar" style="height:8%;background:#0E2535"></div><div class="tl-bar" style="height:13%;background:#0E2535"></div><div class="tl-bar" style="height:21%;background:#0E2535"></div><div class="tl-bar" style="height:17%;background:#0E2535"></div><div class="tl-bar" style="height:25%;background:#0E2535"></div><div class="tl-bar" style="height:17%;background:#0E2535"></div><div class="tl-bar" style="height:21%;background:#0E2535"></div><div class="tl-bar" style="height:25%;background:#0E2535"></div><div class="tl-bar" style="height:33%;background:#0E2535"></div><div class="tl-bar" style="height:38%;background:#0E2535"></div><div class="tl-bar" style="height:50%;background:#0E2535"></div><div class="tl-bar" style="height:42%;background:#0E2535"></div><div class="tl-bar" style="height:75%;background:#0E2535"></div><div class="tl-bar" style="height:100%;background:#0E2535"></div><div class="tl-bar" style="height:92%;background:#0E2535"></div><div class="tl-bar" style="height:83%;background:#0E2535"></div><div class="tl-bar" style="height:67%;background:#0E2535"></div><div class="tl-bar" style="height:58%;background:#0E2535"></div><div class="tl-bar" style="height:46%;background:#0E2535"></div><div class="tl-bar" style="height:38%;background:#0E2535"></div><div class="tl-bar" style="height:42%;background:#0E2535"></div><div class="tl-bar" style="height:33%;background:#0E2535"></div><div class="tl-bar" style="height:29%;background:#0E2535"></div><div class="tl-bar" style="height:25%;background:#0E2535"></div><div class="tl-bar" style="height:21%;background:#0E2535"></div><div class="tl-bar" style="height:25%;background:#0E2535"></div><div class="tl-bar" style="height:21%;background:#0E2535"></div><div class="tl-bar" style="height:17%;background:#0E2535"></div><div class="tl-bar" style="height:21%;background:#0E2535"></div><div class="tl-bar" style="height:17%;background:#0E2535"></div><div class="tl-bar" style="height:13%;background:#0E2535"></div><div class="tl-bar" style="height:17%;background:#0E2535"></div><div class="tl-bar" style="height:17%;background:#0E2535"></div><div class="tl-bar" style="height:13%;background:#0E2535"></div><div class="tl-bar" style="height:17%;background:#0E2535"></div><div class="tl-bar" style="height:13%;background:#0E2535"></div><div class="tl-bar" style="height:21%;background:#0E2535"></div><div class="tl-bar" style="height:33%;background:#0E2535"></div><div class="tl-bar" style="height:46%;background:#0E2535"></div><div class="tl-bar" style="height:58%;background:#0E2535"></div><div class="tl-bar" style="height:67%;background:#0E2535"></div><div class="tl-bar" style="height:58%;background:#0E2535"></div><div class="tl-bar" style="height:50%;background:#0E2535"></div><div class="tl-bar" style="height:42%;background:#0E2535"></div><div class="tl-bar" style="height:29%;background:#0E2535"></div><div class="tl-bar" style="height:21%;background:#0E2535"></div><div class="tl-bar" style="height:17%;background:#0E2535"></div><div class="tl-bar" style="height:13%;background:#0E2535"></div><div class="tl-bar" style="height:8%;background:#0E2535"></div>
|
||||
</div>
|
||||
<div style="height:1px;background:#1E2D3D;margin-bottom:2px"></div>
|
||||
<div style="display:flex;justify-content:space-between"><span style="font-size:4.5px;color:#2A3A4A">1900</span><span style="font-size:4.5px;color:#2A3A4A">1910</span><span style="font-size:4.5px;color:#2A3A4A">1920</span><span style="font-size:4.5px;color:#2A3A4A">1930</span><span style="font-size:4.5px;color:#2A3A4A">1940</span><span style="font-size:4.5px;color:#2A3A4A">1950</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col">
|
||||
<div class="lbl"><span class="tag">Dark · Auswahl aktiv</span>1914–1920</div>
|
||||
<div class="chrome dk" style="width:380px">
|
||||
<div class="bar"><div class="dot"></div><div class="dot"></div><div class="dot"></div><div class="url"></div></div>
|
||||
<div class="nav" style="background:#060C12"><div class="nav-logo">Familienarchiv</div><div class="nav-r"><div class="nav-av">KR</div></div></div>
|
||||
<div class="page-body dk">
|
||||
<div class="sfb dk">
|
||||
<div class="sfb-input dk"><span>Dokumente durchsuchen …</span></div>
|
||||
<div style="display:flex;align-items:center;gap:3px;background:#A1DCD8;border-radius:2px;padding:2px 6px">
|
||||
<span style="font-size:5px;font-weight:700;color:#012851">Jan 1914 – Dez 1920</span>
|
||||
<span style="font-size:7px;color:#012851">×</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tl-widget dk">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:4px">
|
||||
<span style="font-size:5px;font-weight:800;text-transform:uppercase;letter-spacing:.6px;color:#3E5065">Zeitachse</span>
|
||||
<span style="font-size:5px;color:#A1DCD8;font-weight:700">187 Dokumente · Jan 1914 – Dez 1920</span>
|
||||
</div>
|
||||
<div class="tl-bars">
|
||||
<div class="tl-bar" style="height:13%;background:#0A1218"></div><div class="tl-bar" style="height:17%;background:#0A1218"></div><div class="tl-bar" style="height:8%;background:#0A1218"></div><div class="tl-bar" style="height:13%;background:#0A1218"></div><div class="tl-bar" style="height:21%;background:#0A1218"></div><div class="tl-bar" style="height:17%;background:#0A1218"></div><div class="tl-bar" style="height:25%;background:#0A1218"></div><div class="tl-bar" style="height:17%;background:#0A1218"></div><div class="tl-bar" style="height:21%;background:#0A1218"></div><div class="tl-bar" style="height:25%;background:#0A1218"></div><div class="tl-bar" style="height:33%;background:#0A1218"></div><div class="tl-bar" style="height:38%;background:#0A1218"></div><div class="tl-bar" style="height:50%;background:#0A1218"></div><div class="tl-bar" style="height:42%;background:#0A1218"></div>
|
||||
<div class="tl-bar" style="height:75%;background:#A1DCD8"></div><div class="tl-bar" style="height:100%;background:#A1DCD8"></div><div class="tl-bar" style="height:92%;background:#A1DCD8"></div><div class="tl-bar" style="height:83%;background:#A1DCD8"></div><div class="tl-bar" style="height:67%;background:#A1DCD8"></div><div class="tl-bar" style="height:58%;background:#A1DCD8"></div><div class="tl-bar" style="height:46%;background:#A1DCD8"></div><div class="tl-bar" style="height:38%;background:#A1DCD8"></div><div class="tl-bar" style="height:42%;background:#A1DCD8"></div>
|
||||
<div class="tl-bar" style="height:33%;background:#0A1218"></div><div class="tl-bar" style="height:29%;background:#0A1218"></div><div class="tl-bar" style="height:25%;background:#0A1218"></div><div class="tl-bar" style="height:21%;background:#0A1218"></div><div class="tl-bar" style="height:25%;background:#0A1218"></div><div class="tl-bar" style="height:21%;background:#0A1218"></div><div class="tl-bar" style="height:17%;background:#0A1218"></div><div class="tl-bar" style="height:21%;background:#0A1218"></div><div class="tl-bar" style="height:17%;background:#0A1218"></div><div class="tl-bar" style="height:13%;background:#0A1218"></div><div class="tl-bar" style="height:17%;background:#0A1218"></div><div class="tl-bar" style="height:17%;background:#0A1218"></div><div class="tl-bar" style="height:13%;background:#0A1218"></div><div class="tl-bar" style="height:17%;background:#0A1218"></div><div class="tl-bar" style="height:13%;background:#0A1218"></div><div class="tl-bar" style="height:21%;background:#0A1218"></div><div class="tl-bar" style="height:33%;background:#0A1218"></div><div class="tl-bar" style="height:46%;background:#0A1218"></div><div class="tl-bar" style="height:58%;background:#0A1218"></div><div class="tl-bar" style="height:67%;background:#0A1218"></div><div class="tl-bar" style="height:58%;background:#0A1218"></div><div class="tl-bar" style="height:50%;background:#0A1218"></div><div class="tl-bar" style="height:42%;background:#0A1218"></div><div class="tl-bar" style="height:29%;background:#0A1218"></div><div class="tl-bar" style="height:21%;background:#0A1218"></div><div class="tl-bar" style="height:17%;background:#0A1218"></div><div class="tl-bar" style="height:13%;background:#0A1218"></div><div class="tl-bar" style="height:8%;background:#0A1218"></div>
|
||||
</div>
|
||||
<div style="height:1px;background:#1E2D3D;margin-bottom:2px"></div>
|
||||
<div style="display:flex;justify-content:space-between"><span style="font-size:4.5px;color:#2A3A4A">1900</span><span style="font-size:4.5px;color:#2A3A4A">1910</span><span style="font-size:4.5px;color:#2A3A4A">1920</span><span style="font-size:4.5px;color:#2A3A4A">1930</span><span style="font-size:4.5px;color:#2A3A4A">1940</span><span style="font-size:4.5px;color:#2A3A4A">1950</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="cap">Dark mode: Ausgewählte Balken = Mint (#A1DCD8), außerhalb = fast schwarz (#0A1218). Badge in SearchFilterBar: Mint-Hintergrund mit Navy-Text.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ══════════════════════════════════════════
|
||||
SECTION 5 — BEHAVIOUR RULES
|
||||
══════════════════════════════════════════ -->
|
||||
<div class="sh"><h2>5 · Verhaltensregeln</h2></div>
|
||||
|
||||
<div class="rules">
|
||||
<table>
|
||||
<tr><th>Szenario</th><th>Verhalten</th></tr>
|
||||
<tr><td>Click auf einzelnen Balken</td><td>Setzt Start <em>und</em> Ende auf denselben Monat → Einzelmonat-Filter. Direkt gefiltert, kein Drag nötig.</td></tr>
|
||||
<tr><td>Click + Drag</td><td>Start = Mousedown-Position, Ende = Mousemove-Position. Richtung egal (rechts → links und links → rechts erlaubt). Filter wird live aktualisiert während des Drags.</td></tr>
|
||||
<tr><td>Handle drag</td><td>Start- oder End-Handle verschieben justiert die bestehende Auswahl. Der andere Handle bleibt fixiert.</td></tr>
|
||||
<tr><td>Click außerhalb der Balken</td><td>Auswahl wird gelöscht. Liste zeigt alle Dokumente.</td></tr>
|
||||
<tr><td>Löschen-Button (×)</td><td>Auswahl löschen. Badge in SearchFilterBar verschwindet. Bars kehren zu Idle zurück.</td></tr>
|
||||
<tr><td>Kombination mit anderen Filtern</td><td>AND-Semantik. Zeitachsen-Filter + Personen-Filter + Tag-Filter sind kumulativ.</td></tr>
|
||||
<tr><td>Wechsel zu Kalenderansicht</td><td>Timeline-Widget wird <strong>ausgeblendet</strong> (<code>display:none</code>). Aktive Auswahl bleibt im State erhalten und wird wieder angezeigt beim Rückwechsel.</td></tr>
|
||||
<tr><td>Monate ohne Dokumente</td><td>Bar hat Mindesthöhe von 2px (sichtbar, aber minimal). Tooltip zeigt „Monat Jahr · 0 Dokumente". Klick auf Null-Bar setzt Single-Month-Filter (leere Liste erwartet).</td></tr>
|
||||
<tr><td>Granularität (OQ-2)</td><td><strong>Entscheidung: ganze Monate.</strong> Start = 1. des Startmonats, Ende = letzter Tag des Endmonats. Keine Wochen oder Tage auswählbar.</td></tr>
|
||||
<tr><td>Startmonat beim Öffnen (OQ-1)</td><td><strong>Entscheidung: auto-derived range.</strong> X-Achse leitet sich aus frühestem und spätestem <code>documentDate</code> ab. Kein festes Datum (1880–heute).</td></tr>
|
||||
<tr><td>Keyboard-Zugang</td><td>Tab fokussiert das Widget. Pfeiltasten verschieben einen virtuellen Cursor Monat für Monat. Enter/Space setzt Start; zweites Enter setzt Ende. Escape bricht Drag ab.</td></tr>
|
||||
<tr><td>Mobile (<768 px)</td><td>Widget kollabiert auf kompakte Zeile: „Zeitraum: [Jan 1914 – Dez 1920] ×" — kein Balkendiagramm. Tap öffnet ein Modal mit dem vollen Widget.</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
|
||||
<!-- ══════════════════════════════════════════
|
||||
SECTION 6 — OPEN QUESTIONS ANSWERED
|
||||
══════════════════════════════════════════ -->
|
||||
<div class="sh"><h2>6 · Offene Fragen — Entscheidungen</h2></div>
|
||||
|
||||
<div class="callout navy"><strong>OQ-1: Zeitachsen-Bereich.</strong> Auto-derived aus frühestem und spätestem <code>documentDate</code> in der Datenbank. Beim Laden der Seite holt der Server <code>GET /api/documents/stats</code> (oder ähnlich) mit <code>minDate</code> und <code>maxDate</code>. Kein hard-coded Bereich.</div>
|
||||
<div class="callout navy"><strong>OQ-2: Granularität.</strong> Ganze Monate. Ein Balken = ein Monat. Kein Sub-Monats-Drag. Begründung: Die Archive haben typischerweise 1–30 Briefe pro Monat — wöchentliche Granularität brächgt keine Zusatzinfo, würde aber 4× mehr Balken erzeugen.</div>
|
||||
<div class="callout green"><strong>Backend-Anforderung.</strong> Neuer Query-Parameter <code>?dateFrom=YYYY-MM&dateTo=YYYY-MM</code> auf dem bestehenden Search-Endpoint, oder dedizierter Density-Endpoint <code>GET /api/documents/density?from=1900-01&to=1950-12</code> der <code>[{month:"1915-08", count:24}, ...]</code> zurückgibt. Das bestehende Offset-Paging entfällt für diesen Widget-Call.</div>
|
||||
|
||||
|
||||
<!-- ══════════════════════════════════════════
|
||||
SECTION 7 — IMPL-REF TABLE
|
||||
══════════════════════════════════════════ -->
|
||||
<div class="sh"><h2>7 · Implementation Reference</h2></div>
|
||||
|
||||
<div class="impl-ref">
|
||||
<table>
|
||||
<tr><th>Element</th><th>Tailwind-Klassen</th><th>Pixelwert</th><th>Anmerkung</th></tr>
|
||||
<tr><td>Widget-Container</td><td><code>bg-surface border border-line rounded-sm shadow-sm px-4 py-3</code></td><td>padding 16px 12px</td><td>Gleiche Karte wie SearchFilterBar und DocumentList</td></tr>
|
||||
<tr><td>Widget-Header Zeile</td><td><code>flex items-center justify-between mb-2</code></td><td>margin-bottom 8px</td><td>Label links, Gesamtanzahl rechts</td></tr>
|
||||
<tr><td>Header Label</td><td><code>text-[10px] font-bold uppercase tracking-widest text-ink-3</code></td><td>10px / 700</td><td>Identisch zu anderen Section-Labels</td></tr>
|
||||
<tr><td>Gesamtanzahl-Text</td><td><code>text-[10px] text-ink-3</code> — wenn Auswahl aktiv: <code>text-primary font-bold</code></td><td>10px</td><td>Zeigt gefilterte Anzahl sobald Auswahl aktiv</td></tr>
|
||||
<tr><td>Bar-Container</td><td><code>flex items-end gap-px h-10 w-full</code></td><td>height 40px</td><td><code>h-12</code> (48px) wenn Platz es erlaubt</td></tr>
|
||||
<tr><td>Bar idle</td><td><code>flex-1 rounded-t-sm min-h-[2px]</code> + inline <code>height</code> als %-Wert</td><td>var</td><td>CSS custom property <code>--bar-h</code> setzen via JS</td></tr>
|
||||
<tr><td>Bar: Farbe idle</td><td><code>bg-[#D4EDE9]</code> (light) / <code>bg-[#0E2535]</code> (dark)</td><td>—</td><td>Helles Mint, nicht brand-mint — zu dominant</td></tr>
|
||||
<tr><td>Bar: Farbe hover</td><td><code>hover:bg-mint</code></td><td>—</td><td><code>transition-colors duration-100</code></td></tr>
|
||||
<tr><td>Bar: Farbe selected</td><td><code>bg-primary</code> (light) / <code>bg-mint</code> (dark)</td><td>—</td><td>Invertierung in Dark mode beabsichtigt</td></tr>
|
||||
<tr><td>Bar: Farbe outside</td><td><code>bg-muted</code></td><td>—</td><td>Gedimmt, aber noch sichtbar</td></tr>
|
||||
<tr><td>Baseline</td><td><code>border-t border-line mt-0.5 mb-1</code></td><td>1px</td><td>Trennt Balken von X-Achsen-Labels</td></tr>
|
||||
<tr><td>X-Achse Labels</td><td><code>flex justify-between text-[9px] text-ink-3</code></td><td>9px</td><td>Nur Jahres-Labels alle 10 Jahre; mehr wenn Platz</td></tr>
|
||||
<tr><td>Drag Handle</td><td><code>absolute w-3 h-3 rounded-full bg-primary border-2 border-surface shadow cursor-ew-resize -top-1.5 left-1/2 -translate-x-1/2 z-10</code></td><td>12px / 12px</td><td>Touch target: mind. 44px durch <code>p-4</code> auf einem unsichtbaren Wrapper</td></tr>
|
||||
<tr><td>Tooltip</td><td><code>absolute bottom-full left-1/2 -translate-x-1/2 mb-1 bg-ink text-surface text-[9px] font-bold px-1.5 py-0.5 rounded-sm whitespace-nowrap z-20 pointer-events-none</code></td><td>9px</td><td>Erscheint via <code>group-hover:block hidden</code></td></tr>
|
||||
<tr><td>Badge in SearchFilterBar</td><td><code>flex items-center gap-1 bg-primary text-primary-fg text-[9px] font-bold px-2 py-1 rounded-sm</code></td><td>9px</td><td>Löschen-Button innerhalb: <code>ml-1 text-sm leading-none hover:opacity-70</code></td></tr>
|
||||
<tr><td>Mobile Collapse</td><td>Widget: <code>sm:block hidden</code> — Mobile-Badge: <code>sm:hidden flex items-center gap-2</code></td><td>—</td><td>Unter 640px nur Badge-Zeile sichtbar, kein Chart</td></tr>
|
||||
<tr><td>Komponenten-Name</td><td><code>TimelineDensityFilter.svelte</code></td><td>—</td><td>Props: <code>density: MonthBucket[]</code>, <code>bind:from</code>, <code>bind:to</code>, <code>onchange</code></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</div><!-- /page -->
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user