Files
business-card-scanner/static/index.html
Lucas Orth bf0a397597
All checks were successful
Build & Deploy / deploy (push) Successful in 10m41s
Anlass, Dubletten, Rückseite, Teilen; Startseite und App-Anmutung
Vier Funktionen:

- Anlass: ein Feld auf der Startseite gilt für alle folgenden Scans und
  wird im Browser gemerkt. Ordnet den Bestand verlässlicher als der
  Ortsname aus den Koordinaten. Durchsuchbar, nachträglich änderbar.
- Dubletten: die Detailansicht zeigt Karten, die dieselbe Person meinen
  könnten - gleiche E-Mail, gleiche Telefonnummer (verglichen werden die
  letzten acht Ziffern, damit +49 511 123456 und 0511/123456 aufeinander
  passen) oder gleicher Nachname bei gleicher Firma.
- Rückseite: nachträglich ein zweites Bild zur Karte. Es füllt nur
  Felder, die die Vorderseite offen gelassen hat.
- Schnellwahl bei Erinnerungen: in 3 Tagen / 1 Woche / 2 Wochen /
  1 Monat, jeweils 9 Uhr. Das Datumsfeld braucht man damit selten.

Teilen als vCard über das System-Teilenblatt, mit Download als
Rückfallebene, wo es kein Teilenblatt gibt.

Oberfläche:

- Startseite führt jetzt: Anlass, große Aufnahmefläche mit Symbol statt
  Textzeile, leise Alternative, darunter die zuletzt erfassten Karten.
  Vorher stand dort eine Überschrift mit Zahlen und man musste raten.
- Weniger Webseite, mehr App: kein Seitenrahmen auf dem Telefon, Symbole
  in der Tableiste mit Zählerblasen, kein Tap-Highlight, keine
  Textmarkierung auf Bedienelementen, Safe-Area oben, Einblendung beim
  Ansichtswechsel, Mindesthöhe 54 px für Schaltflächen.
- Die Marken "exportiert" / "nicht exportiert" sind aus der Liste raus;
  dafür gibt es den Filter.

Neue Spalten occasion und back_image_file kommen per ALTER TABLE in
bestehende Datenbanken - CREATE TABLE IF NOT EXISTS rührt eine
vorhandene Tabelle nicht an.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 15:09:08 +02:00

157 lines
6.3 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="theme-color" content="#000000">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Karten">
<title>Visitenkarten Scanner</title>
<link rel="manifest" href="/manifest.webmanifest">
<link rel="apple-touch-icon" href="/static/icons/icon-192.png">
<link rel="icon" href="/static/icons/icon-192.png">
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<div class="app">
<header class="topbar">
<span class="mark" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2.2">
<rect x="2" y="5" width="20" height="14"></rect>
<path d="M6 10h5M6 14h8"></path>
</svg>
</span>
<span class="wordmark">
<span class="display d-md">Visitenkarten</span>
<span class="label">Scanner</span>
</span>
<span class="spacer"></span>
<button class="chip" id="logout-button" hidden>Abmelden</button>
</header>
<main>
<!-- ------------------------------------------------------------- Scan -->
<section class="screen stack" id="screen-scan">
<label class="field">
<span class="label">Anlass · gilt für die nächsten Scans</span>
<input id="occasion-input" placeholder="z. B. Hannover Messe 2026"
autocomplete="off" enterkeyhint="done">
</label>
<label class="action" for="input-camera">
<span class="action-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
<path d="M3 7h3l1.5-2h9L18 7h3v12H3z"></path>
<circle cx="12" cy="13" r="3.6"></circle>
</svg>
</span>
<span class="action-body">
<span class="display d-lg">Foto aufnehmen</span>
<span class="action-hint">
Karten nebeneinander auf einen dunklen Untergrund legen.
Ein Foto reicht für den ganzen Tisch.
</span>
</span>
<input id="input-camera" type="file" accept="image/*" capture="environment" hidden>
</label>
<label class="btn btn-quiet" for="input-library">
Aus Fotos wählen <span class="arrow">↗</span>
<input id="input-library" type="file" accept="image/*" hidden>
</label>
<div id="scan-result"></div>
<div class="section-label"><span class="label">Zuletzt erfasst</span></div>
<div id="recent-list"></div>
</section>
<!-- ------------------------------------------------------------ Karten -->
<section class="screen stack" id="screen-cards" hidden>
<div class="search">
<input id="search-input" type="search" placeholder="Name, Firma, Anlass, Notiz …"
autocomplete="off" enterkeyhint="search">
<button id="search-clear" type="button" aria-label="Suche leeren">×</button>
</div>
<div class="chips" id="filter-chips">
<button class="chip" data-filter="alle" aria-pressed="true">Alle</button>
<button class="chip" data-filter="offen" aria-pressed="false">Nicht exportiert</button>
<button class="chip" data-filter="unvollstaendig" aria-pressed="false">Unvollständig</button>
<button class="chip" data-filter="erinnerung" aria-pressed="false">Mit Erinnerung</button>
</div>
<div id="cards-list"></div>
</section>
<!-- ------------------------------------------------------------ Detail -->
<section class="screen stack" id="screen-detail" hidden></section>
<!-- ------------------------------------------------------ Erinnerungen -->
<section class="screen stack" id="screen-reminders" hidden>
<div class="section-label"><span class="label">Erinnerungen</span></div>
<div id="reminders-list"></div>
</section>
</main>
<nav class="tabs" role="tablist">
<button class="tab" role="tab" data-route="scan" aria-selected="true">
<span class="tab-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9">
<path d="M3 7h3l1.5-2h9L18 7h3v12H3z"></path>
<circle cx="12" cy="13" r="3.4"></circle>
</svg>
</span>
<span class="tab-label">Scannen</span>
</button>
<button class="tab" role="tab" data-route="karten" aria-selected="false">
<span class="tab-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9">
<rect x="3" y="6" width="18" height="13"></rect>
<path d="M7 11h5M7 15h8"></path>
</svg>
</span>
<span class="tab-label">Karten</span>
<span class="tab-badge mono-num" id="tab-count-cards" hidden>0</span>
</button>
<button class="tab" role="tab" data-route="erinnerungen" aria-selected="false">
<span class="tab-icon" aria-hidden="true">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9">
<circle cx="12" cy="12" r="8.5"></circle>
<path d="M12 7v5.2l3.4 2"></path>
</svg>
</span>
<span class="tab-label">Termine</span>
<span class="tab-badge mono-num" id="tab-count-reminders" hidden>0</span>
</button>
</nav>
</div>
<!-- ------------------------------------------------------------------ Login -->
<div class="overlay" id="login-overlay" hidden>
<form class="block stack" id="login-form">
<p class="label muted">Zugang</p>
<h2 class="display d-lg">Anmelden</h2>
<label class="field">
<span class="label">Passwort</span>
<input type="password" id="login-password" autocomplete="current-password" required>
</label>
<button class="btn btn-primary" type="submit">Weiter <span class="arrow">→</span></button>
<p class="hint" id="login-error" hidden></p>
</form>
</div>
<!-- -------------------------------------------------------------- Fortschritt -->
<div class="overlay" id="busy-overlay" hidden>
<div class="block-invert stack" style="text-align:center">
<div class="spinner" style="margin:0 auto"></div>
<p class="label" id="busy-text">Karten werden freigestellt</p>
</div>
</div>
<div class="toast" id="toast" hidden></div>
<script src="/static/app.js"></script>
</body>
</html>