Files
business-card-scanner/static/style.css
Lucas Orth 1e8918e0cd
All checks were successful
Build & Deploy / deploy (push) Successful in 1m29s
Bildeditor, klickbare Felder, Einstellungen; Kopfzeile und Namensfeld raus
Bildbearbeitung im Nachgang: Drehen in 90-Grad-Schritten, Ausrichten per
Schieberegler, Zuschneiden per Rahmen mit anfassbaren Ecken. Der Server
rechnet jede Korrektur vom unbearbeiteten Original, das beim ersten
Eingriff daneben abgelegt wird - sonst summieren sich die Verluste über
mehrere Korrekturen, und Zurücksetzen wäre nicht möglich. Zwei
Zuschnitte hintereinander werden ineinander verrechnet.

Der Ort ließ sich nicht übernehmen, weil beide Quellen versagen können:
iOS entfernt beim Weitergeben an eine Webseite oft die GPS-Daten aus dem
Foto, und der Browserstandort scheiterte bisher stumm. Jetzt meldet er
seinen Grund ("Standortfreigabe fehlt"), die Detailansicht zeigt die
Herkunft des Ortes, und der Scan-Ort ist von Hand nachtragbar.

E-Mail, Web, Telefon und Mobil sind in der Tabelle jetzt Links; Straße,
PLZ, Ort und Land stehen als eine Adresszeile, die die eingestellte
Navigations-App öffnet.

Einstellungen als vierter Reiter: Navigations-App (Apple Karten, Google
Maps, OpenStreetMap), Statusübersicht und Abmelden - der Knopf saß
vorher in der Kopfzeile.

Die Kopfzeile ist weg. Sie kostete auf dem Telefon eine Bildschirmzeile
und wiederholte nur, was die Tableiste schon sagt.

Kein full_name mehr, nur Vor- und Nachname; akademische Titel gehören in
den Vornamen. Bestehende Datensätze werden bei der Migration am letzten
Leerzeichen aufgeteilt. Die Spalte bleibt ungenutzt stehen: SQLite baut
zum Löschen die ganze Tabelle neu, der Gewinn wären ein paar Byte.

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

606 lines
14 KiB
CSS

/* Schwarzweiss mit Grau als Hierarchiestufe. Harte Kanten, keine Rundungen.
Mobile first.
Drei Ebenen, konsequent durchgehalten:
Flaeche weiss -> --fill (gruppiert) -> schwarz (das eine Wichtige)
Text --ink -> --ink-2 (erlaeuternd) -> --ink-3 (Beiwerk)
Linie 3px schwarz (traegt) -> 1px schwarz (gliedert) -> --rule (trennt) */
:root {
color-scheme: light;
--ink: #000;
--ink-2: #4a4a4a;
--ink-3: #8a8a8a;
--bg: #fff;
--fill: #f4f4f4;
--page: #e4e4e4;
--rule: #d4d4d4;
--sans: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, Arial, sans-serif;
--pad: 18px;
}
* { box-sizing: border-box; }
html, body {
margin: 0;
padding: 0;
background: var(--bg);
color: var(--ink);
font-family: var(--sans);
font-size: 15px;
line-height: 1.45;
-webkit-text-size-adjust: 100%;
}
body {
overflow-x: hidden;
overscroll-behavior-y: none;
-webkit-tap-highlight-color: transparent;
}
/* Bedienelemente verhalten sich wie in einer App, nicht wie Fliesstext. */
.btn, .chip, .tab, .row, .badge, .pill, .action, .label {
-webkit-user-select: none;
user-select: none;
}
button, input, textarea, select {
font: inherit;
color: inherit;
border-radius: 0;
-webkit-appearance: none;
appearance: none;
}
a { color: inherit; }
/* ------------------------------------------------------------------ Typografie */
.display {
font-weight: 800;
font-stretch: 85%;
letter-spacing: -0.025em;
text-transform: uppercase;
line-height: 0.94;
margin: 0;
}
.d-xl { font-size: 42px; }
.d-lg { font-size: 30px; }
.d-md { font-size: 19px; letter-spacing: -0.01em; }
.label {
font-size: 10px;
font-weight: 700;
letter-spacing: 0.18em;
text-transform: uppercase;
margin: 0;
}
.muted { color: var(--ink-2); }
.hint { font-size: 12.5px; line-height: 1.5; color: var(--ink-3); }
.mono-num { font-variant-numeric: tabular-nums; }
/* ---------------------------------------------------------------------- Geruest */
.app {
max-width: 560px;
margin: 0 auto;
min-height: 100vh;
background: var(--bg);
display: flex;
flex-direction: column;
}
main { flex: 1; display: flex; flex-direction: column; }
.screen {
flex: 1;
padding: calc(var(--pad) + env(safe-area-inset-top)) var(--pad) 30px;
animation: rise 0.18s ease-out;
}
@keyframes rise {
from { opacity: 0; transform: translateY(7px); }
to { opacity: 1; transform: none; }
}
.screen[hidden] { display: none; }
.stack > * + * { margin-top: 12px; }
.stack-tight > * + * { margin-top: 8px; }
/* ---------------------------------------------------------------------- Bloecke */
/* Oberste Ebene: das eine Wichtige pro Ansicht. Nur einmal verwenden. */
.block-invert {
background: var(--ink);
color: var(--bg);
padding: 20px 18px;
}
.block-invert .muted { color: #a9a9a9; }
.block-invert .label { color: #a9a9a9; }
/* Mittlere Ebene: gruppiert, tritt zurueck. */
.block {
background: var(--fill);
border: 1px solid var(--rule);
padding: 16px;
}
/* Kopfband ueber einer Liste - ordnet zu, konkurriert aber nicht. */
.strip {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 11px 14px;
background: var(--fill);
border: 1px solid var(--ink);
border-bottom: none;
}
.strip .label { color: var(--ink-2); }
.section-label {
display: flex;
align-items: center;
gap: 12px;
margin: 34px 0 12px;
}
.section-label .label { color: var(--ink); }
.section-label::after {
content: "";
flex: 1;
height: 1px;
background: var(--rule);
}
.figures {
display: flex;
gap: 32px;
margin-top: 20px;
padding-top: 16px;
border-top: 1px solid rgba(255, 255, 255, 0.28);
}
.figure { display: flex; flex-direction: column; }
.figure .label { margin-bottom: 6px; }
/* --------------------------------------------------------------------- Buttons */
.btn {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
width: 100%;
min-height: 54px;
padding: 16px;
background: var(--bg);
border: 2px solid var(--ink);
font-weight: 800;
font-size: 14px;
letter-spacing: 0.06em;
text-transform: uppercase;
text-decoration: none;
cursor: pointer;
text-align: left;
}
/* Die eine Sache, die man auf der Startseite tut. Grosse Flaeche statt
Textzeile - auf dem Telefon zaehlt die Trefferflaeche, nicht die Kante. */
.action {
display: flex;
align-items: center;
gap: 16px;
padding: 22px 18px;
background: var(--ink);
color: var(--bg);
cursor: pointer;
}
.action:active { background: #1f1f1f; }
.action-icon { flex: 0 0 42px; color: var(--bg); }
.action-icon svg { width: 42px; height: 42px; display: block; }
.action-body { display: flex; flex-direction: column; gap: 7px; }
.action-hint { font-size: 12.5px; line-height: 1.45; color: #a9a9a9; }
/* Was man hier tun soll. */
.btn-primary { background: var(--ink); color: var(--bg); border-color: var(--ink); }
/* Vorhanden, aber leise. */
.btn-quiet {
border: 1px solid var(--rule);
color: var(--ink-2);
font-weight: 700;
}
.btn:disabled { opacity: 0.35; cursor: not-allowed; }
.btn .arrow { font-weight: 400; font-size: 17px; }
.btn-quiet .arrow { color: var(--ink-3); }
/* --------------------------------------------------------------------- Formular */
.field { display: block; }
.field .label { color: var(--ink-3); margin-bottom: 6px; }
.field input,
.field textarea {
width: 100%;
padding: 11px 12px;
background: var(--bg);
border: 1px solid var(--ink);
}
.field input:focus,
.field textarea:focus { outline: 2px solid var(--ink); outline-offset: 1px; }
.field textarea { min-height: 88px; resize: vertical; }
/* iOS rendert Datumsfelder eigenwillig: der Wert sitzt zentriert, die Hoehe
sackt ein, der Innenabstand kommt vom System. Das zieht sie auf die Form
der uebrigen Felder. */
.field input[type="datetime-local"] {
min-height: 46px;
line-height: 1.2;
text-align: left;
font-variant-numeric: tabular-nums;
}
.field input[type="datetime-local"]::-webkit-date-and-time-value {
text-align: left;
margin: 0;
min-height: 20px;
}
.field input[type="datetime-local"]::-webkit-calendar-picker-indicator {
opacity: 0.5;
filter: grayscale(1);
}
.search { display: flex; border: 2px solid var(--ink); }
.search input { flex: 1; border: none; padding: 13px 14px; background: var(--bg); }
.search input:focus { outline: none; }
.search button {
border: none;
border-left: 1px solid var(--rule);
background: var(--fill);
color: var(--ink-2);
padding: 0 16px;
cursor: pointer;
font-weight: 700;
}
.chips { display: flex; flex-wrap: wrap; gap: 7px; margin-top: 10px; }
/* Ungewaehlt zurueckgenommen, damit die Auswahl ueberhaupt auffaellt. */
.chip {
padding: 9px 12px;
border: 1px solid var(--rule);
background: var(--bg);
color: var(--ink-3);
font-size: 10px;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
cursor: pointer;
}
.chip[aria-pressed="true"] {
background: var(--ink);
border-color: var(--ink);
color: var(--bg);
}
/* --------------------------------------------------------------------- Listen */
.rows { border-top: 1px solid var(--ink); border-bottom: 1px solid var(--ink); }
.row {
display: block;
width: 100%;
text-align: left;
background: var(--bg);
border: none;
border-bottom: 1px solid var(--rule);
padding: 14px;
cursor: pointer;
}
.row:last-child { border-bottom: none; }
.row-head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 12px;
}
.row-title { font-weight: 800; font-size: 17px; letter-spacing: -0.01em; }
.row-sub { color: var(--ink-2); font-size: 13.5px; margin-top: 3px; }
.row-meta {
font-size: 10px;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--ink-3);
white-space: nowrap;
}
.row-tags { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 9px; }
.pill {
display: inline-block;
padding: 3px 7px;
border: 1px solid var(--rule);
background: var(--bg);
color: var(--ink-3);
font-size: 9px;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
}
.pill-solid { background: var(--ink); border-color: var(--ink); color: var(--bg); }
.badge {
display: inline-block;
padding: 5px 9px;
border: 1px solid var(--ink);
background: var(--bg);
color: var(--ink);
font-size: 9px;
font-weight: 700;
letter-spacing: 0.12em;
text-transform: uppercase;
text-decoration: none;
cursor: pointer;
}
/* --------------------------------------------------------------------- Tabelle */
.data {
width: 100%;
border-collapse: collapse;
border-top: 1px solid var(--ink);
border-bottom: 1px solid var(--ink);
}
.data tr:nth-child(even) { background: var(--fill); }
.data th, .data td { padding: 10px 12px; text-align: left; vertical-align: top; }
.data th {
width: 36%;
font-weight: 600;
font-size: 10px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--ink-3);
padding-top: 13px;
}
.data td { font-weight: 600; word-break: break-word; }
/* Anrufen, schreiben, Route starten - direkt aus der Tabelle. */
.data td a {
text-decoration: none;
border-bottom: 2px solid var(--ink);
padding-bottom: 1px;
}
.data td a::after {
content: " ↗";
font-weight: 400;
color: var(--ink-3);
}
/* ----------------------------------------------------------------------- Bilder */
.card-image {
display: block;
width: 100%;
border: 1px solid var(--ink);
background: var(--fill);
}
/* -------------------------------------------------------------------- Navigation */
.tabs {
position: sticky;
bottom: 0;
z-index: 20;
display: flex;
background: var(--bg);
border-top: 3px solid var(--ink);
}
.tab {
position: relative;
flex: 1;
padding: 10px 4px calc(10px + env(safe-area-inset-bottom));
background: var(--bg);
border: none;
border-left: 1px solid var(--rule);
color: var(--ink-3);
font-size: 10px;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
cursor: pointer;
}
.tab:first-child { border-left: none; }
.tab[aria-selected="true"] { background: var(--ink); color: var(--bg); }
.tab-icon { display: block; width: 24px; height: 24px; margin: 0 auto 4px; }
.tab-icon svg { width: 24px; height: 24px; display: block; }
.tab-label { display: block; }
.tab-badge {
position: absolute;
top: 6px;
right: 50%;
transform: translateX(26px);
min-width: 18px;
padding: 1px 4px;
background: var(--ink);
color: var(--bg);
border: 1px solid var(--bg);
font-size: 10px;
font-weight: 800;
letter-spacing: 0;
}
.tab-badge[hidden] { display: none; }
.tab[aria-selected="true"] .tab-badge { background: var(--bg); color: var(--ink); border-color: var(--ink); }
/* ------------------------------------------------------------------- Zustaende */
.empty {
border: 1px dashed var(--rule);
background: var(--fill);
padding: 30px 18px;
text-align: center;
}
.empty .label { color: var(--ink-2); }
.overlay {
position: fixed;
inset: 0;
z-index: 60;
background: rgba(0, 0, 0, 0.86);
display: grid;
place-items: center;
padding: var(--pad);
}
.overlay[hidden] { display: none; }
.overlay .block {
background: var(--bg);
border: 3px solid var(--ink);
width: 100%;
max-width: 380px;
}
/* ------------------------------------------------------------- Bildeditor */
.editor {
background: rgba(0, 0, 0, 0.92);
align-content: start;
overflow-y: auto;
}
.editor-panel {
width: 100%;
max-width: 460px;
background: var(--bg);
border: 3px solid var(--ink);
padding: 14px;
}
.editor-stage {
position: relative;
background: var(--fill);
touch-action: none;
user-select: none;
line-height: 0;
}
.editor-stage img { width: 100%; display: block; }
.crop-box {
position: absolute;
border: 2px solid var(--bg);
outline: 9999px solid rgba(0, 0, 0, 0.55);
box-sizing: border-box;
}
.crop-handle {
position: absolute;
width: 26px;
height: 26px;
background: var(--bg);
border: 2px solid var(--ink);
}
.crop-handle[data-corner="nw"] { left: -13px; top: -13px; }
.crop-handle[data-corner="ne"] { right: -13px; top: -13px; }
.crop-handle[data-corner="se"] { right: -13px; bottom: -13px; }
.crop-handle[data-corner="sw"] { left: -13px; bottom: -13px; }
.editor-row { display: flex; gap: 8px; margin-top: 12px; }
.editor-row .chip { flex: 1; text-align: center; }
.slider-row { display: flex; align-items: center; gap: 12px; margin-top: 14px; }
.slider-row .label { color: var(--ink-3); white-space: nowrap; }
.slider-row output { font-weight: 800; font-variant-numeric: tabular-nums; min-width: 42px; text-align: right; }
input[type="range"] {
flex: 1;
-webkit-appearance: none;
appearance: none;
height: 3px;
background: var(--ink);
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 26px;
height: 26px;
background: var(--ink);
border: 3px solid var(--bg);
box-shadow: 0 0 0 1px var(--ink);
}
input[type="range"]::-moz-range-thumb {
width: 22px;
height: 22px;
border-radius: 0;
background: var(--ink);
border: 3px solid var(--bg);
}
.spinner {
width: 26px;
height: 26px;
border: 3px solid var(--bg);
border-top-color: transparent;
animation: spin 0.8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
.toast {
position: fixed;
left: 50%;
bottom: 96px;
transform: translateX(-50%);
z-index: 70;
max-width: 90vw;
padding: 12px 16px;
background: var(--ink);
color: var(--bg);
font-size: 13px;
font-weight: 600;
}
.toast[hidden] { display: none; }
/* Nur am grossen Bildschirm ein Rahmen - auf dem Telefon fuellt die App
den Schirm, wie eine App das tut. */
@media (min-width: 600px) {
:root { --pad: 26px; }
body { background: var(--page); }
.app { border: 1px solid var(--ink); margin: 26px auto; min-height: calc(100vh - 52px); }
.d-xl { font-size: 52px; }
}