diff --git a/README.md b/README.md index 252c183..7d33720 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,8 @@ Mobile Progressive Web App für die Baustellen-Doku — Foto-Upload, Sprach- und - ✅ Auftragsdetail mit Kunde, Adresse, Telefon (Click-to-Call) - ✅ **Neuer Auftrag** (FAB): Kunde per Suche/Zuletzt-verwendet wählen, dann **Ihr Zeichen** (Pflicht), **Auftragsbeschreibung** (optional), **Geplanter Liefertermin** (Datum + Uhrzeit) und Checkbox **Auftrag direkt freigeben**. Kunden-Defaults (Zahlungsbedingung/Zahlart, Ansprechpartner) werden übernommen. Bei „direkt freigeben" wird der Auftrag **ohne Position** validiert — die Leistungen kommen aus dem Stundenzettel. (Bis Bericht 1.3.1 hängte das Backend hier eine Std-Lohn-Zeile mit Menge 1 an; die verfälschte den Auftragswert, stand als offene Restmenge in der Lieferauflistung und im Kunden-PDF.) +**Aussehen:** Knöpfe tragen farbige Symbole statt Emojis (`icon()` in app.js, Material Icons; Farben je Symbol in app.css `.ikon-`) — Emojis erscheinen je nach Handy als Kästchen. Große Knöpfe: weißes Symbol im farbigen runden Feld; kleine Knöpfe: farbiges Symbol. Untere Leiste: farbiges Symbol über dem Wort. Überschriften fett in normaler Schreibweise, ohne Versalien und ohne Emoji (ROADMAP Punkt 12). + **Foto- und Video-Upload** - ✅ Live-Kamera-Modal (getUserMedia): bleibt für Serien-Aufnahmen offen — Auslöser + Filmstreifen mit Upload-Status je Foto (💾 gesichert → ⏳ lädt hoch → ✓ fertig), Front-/Rückkamera-Umschaltung (nur bei zwei Kameras), Fehlschuss löschbar - ✅ **Volle Kameraauflösung**: ausgelöst wird über `ImageCapture.takePhoto()` (native JPEG der Kamera inkl. EXIF) — ein Standbild aus dem Video-Stream hat nur Stream-Auflösung und ist nur noch die Rückfallebene (iOS/Safari, ältere Browser) diff --git a/ROADMAP.md b/ROADMAP.md index 80d4db3..17cb383 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -61,6 +61,26 @@ EV_ABS:ABS_MT_TRACKING_ID: EV_ABS:ABS_MT_POSITION_X:<0..32767> … EV_SYN:0 `BTN_TOUCH` und frische Tracking-IDs sind Pflicht, sonst meldet Android keine Berührung bzw. alte Finger „hängen"). Skript: KB-Eintrag zur Baustellen-Kamera. +## 12. Farbige Symbole statt Emojis (Eddy 19./20.10.2026: „so dass es gut aussieht", dann zu reinem Weiß: „viel zu schlicht") + +Anlass: der 🔄-Wechselknopf erschien als blaues Kästchen („den hässlichen Wechselbutton lässt du da doch nicht"). +- [x] ✅ 20.10.2026: Alle Knöpfe mit Symbol nutzen `icon()` (app.js, Material Icons, `fill=currentColor`): Kopfzeilen der + Ansichten (Teilen, Zeichnen, Löschen, Download), Auswahlleiste (Alle, Teilen), Kopfzeile oben (Installieren, + Hilfe statt rotem ❓), Blitz-Knopf (Aus/Auto/An/Licht als Blitz-Symbole), die großen Knöpfe am Auftrag + (Kamera, Galerie, Mikrofon, Notizen, Liste, Bericht, Lieferung), Einstellungen, Unterschrift, Warteschlange, + Telefonnummer (jetzt lesbar blau statt dunkelblau unterstrichen). +- [x] ✅ 20.10.2026 nach „viel zu schlicht": **Farbe zurück, ohne Emojis** — große Knöpfe tragen ein weißes Symbol im + farbigen runden Feld (Kamera blau, Galerie grün, Sprachnotiz rot, Notizen gelb, Material orange, Bericht lila, + Lieferungen türkis …), kleine Knöpfe ein farbiges Symbol (Teilen blau, Löschen rot, Stift/Blitz/Hilfe gelb, + Download/Telefon grün). Farben je Symbol in `app.css` (`.ikon-`: `--ikon-farbe`, `--ikon-grund`). +- [x] ✅ Untere Leiste wie in Handy-Apps: Symbol über dem Wort, farbig (Heute als Sonne), aktive Seite blau; das Zahnrad heißt jetzt „Einstellungen". +- [x] ✅ Titel der Vollbild-Ansichten und Abschnitts-Überschriften ohne Emoji (reiner Text). +- Bewusst gelassen: Zustandsanzeige 🟢/🟡/🔴 (Farbe ist dort die Aussage), Stempel im Skizzen-Editor (werden so ins Bild + gezeichnet), Bilder in leeren Listen, Emojis in Meldungen. +- [ ] Offen, nicht angefasst: Die untere Leiste steht seit v1.0.0 am **Seitenende** statt fest unten (`#app` nur + `min-height: 100dvh`, `#main` wächst mit) — bei langen Listen erst nach Scrollen erreichbar. Wenn gewünscht: + Leiste fest unten, dabei FAB und „Geteiltes ablegen"-Leiste (Punkt 8) mit hochrücken. + ## 11. Videos aufnehmen (gewünscht 2026-10-19) Eddy: *„Bei Fotos aufnehmen auch Videos unterstützen bitte"*. diff --git a/app.css b/app.css index dfd8034..068ad93 100644 --- a/app.css +++ b/app.css @@ -45,7 +45,14 @@ body { padding: 4px 8px; cursor: pointer; -webkit-appearance: none; + display: inline-flex; + align-items: center; + justify-content: center; + gap: 4px; } +/* Weisse Symbole statt Emojis (icon() in app.js) — folgen der Schriftfarbe */ +.icon-btn svg { display: block; flex: 0 0 auto; } +.icon-btn[hidden] { display: none; } /* inline-flex wuerde das hidden-Attribut sonst aushebeln */ #status-badge { font-size: 14px; } #main { @@ -67,10 +74,18 @@ body { background: transparent; border: none; color: #888; - padding: 14px 8px; - font-size: 12px; + padding: 8px 2px; + font-size: 11px; cursor: pointer; + /* Symbol ueber dem Wort, wie in Handy-Apps ueblich (vorher bunte Emojis) */ + display: flex; + flex-direction: column; + align-items: center; + gap: 3px; + white-space: nowrap; } +#bottom-nav button svg { display: block; opacity: 0.7; } +#bottom-nav button.active svg { opacity: 1; } #bottom-nav button.active { color: #7aa2f7; } /* ----- FAB (Neuer Auftrag) ----- */ @@ -176,6 +191,58 @@ body { transition: transform 0.1s; } .btn:active { transform: scale(0.97); } +/* ---- Farbige Symbole (icon() in app.js, Klasse .ikon-) ---- + Eddy 20.10.2026: Emojis erscheinen je nach Handy als Kaestchen, rein weisse Symbole waren + „viel zu schlicht". Also: kleine Knoepfe = farbiges Symbol (--ikon-farbe), grosse Knoepfe = + weisses Symbol im farbigen runden Feld (--ikon-grund). */ +.ikon { color: var(--ikon-farbe, currentColor); } +.btn:has(> .ikon) { display: flex; align-items: center; justify-content: center; gap: 12px; } +.btn > .ikon { + flex: 0 0 auto; + box-sizing: content-box; + padding: 7px; + border-radius: 50%; + color: #fff; + background: var(--ikon-grund, #555); +} +.btn-large > .ikon { background: rgba(255, 255, 255, 0.22); } /* blauer Hauptknopf: helles Feld */ +.btn-small:has(> .ikon) { gap: 6px; } +.btn-small > .ikon { padding: 3px; } + +.ikon-kamera { --ikon-farbe: #60a5fa; --ikon-grund: #3b82f6; } +.ikon-galerie { --ikon-farbe: #4ade80; --ikon-grund: #16a34a; } +.ikon-mikrofon { --ikon-farbe: #f87171; --ikon-grund: #dc2626; } +.ikon-notizen { --ikon-farbe: #facc15; --ikon-grund: #ca8a04; } +.ikon-liste { --ikon-farbe: #fb923c; --ikon-grund: #ea580c; } +.ikon-bericht { --ikon-farbe: #c084fc; --ikon-grund: #9333ea; } +.ikon-lieferung { --ikon-farbe: #2dd4bf; --ikon-grund: #0d9488; } +.ikon-karte { --ikon-farbe: #34d399; --ikon-grund: #059669; } +.ikon-zeichnen { --ikon-farbe: #fbbf24; --ikon-grund: #d97706; } +.ikon-oeffnen { --ikon-farbe: #818cf8; --ikon-grund: #4f46e5; } +.ikon-ansehen { --ikon-farbe: #a78bfa; --ikon-grund: #7c3aed; } +.ikon-speichern { --ikon-farbe: #60a5fa; --ikon-grund: #2563eb; } +.ikon-glocke { --ikon-farbe: #fbbf24; --ikon-grund: #d97706; } +.ikon-schluessel { --ikon-farbe: #facc15; --ikon-grund: #ca8a04; } +.ikon-sync { --ikon-farbe: #22d3ee; --ikon-grund: #0891b2; } +.ikon-abmelden { --ikon-farbe: #f87171; --ikon-grund: #dc2626; } +.ikon-loeschen { --ikon-farbe: #f87171; --ikon-grund: #dc2626; } +.ikon-teilen { --ikon-farbe: #60a5fa; --ikon-grund: #2563eb; } +.ikon-download { --ikon-farbe: #4ade80; --ikon-grund: #16a34a; } +.ikon-auswahl { --ikon-farbe: #60a5fa; --ikon-grund: #2563eb; } +.ikon-alle { --ikon-farbe: #c4b5fd; } +.ikon-telefon { --ikon-farbe: #4ade80; } +.ikon-hilfe { --ikon-farbe: #fbbf24; } +.ikon-installieren { --ikon-farbe: #60a5fa; } +.ikon-wechseln { --ikon-farbe: #93c5fd; } +.ikon-blitzAus { --ikon-farbe: #d1d5db; } +.ikon-blitzAuto, .ikon-blitzAn, .ikon-licht { --ikon-farbe: #facc15; } +/* untere Leiste */ +.ikon-heute { --ikon-farbe: #fbbf24; } +.ikon-auftraege { --ikon-farbe: #60a5fa; } +.ikon-kunden { --ikon-farbe: #4ade80; } +.ikon-berichte { --ikon-farbe: #c084fc; } +.ikon-einstellungen { --ikon-farbe: #cbd5e1; } +.tel-link { display: inline-flex; align-items: center; gap: 6px; color: #7aa2f7; text-decoration: none; font-weight: 600; } .btn-secondary { background: #2a2a30; border: 1px solid #444; @@ -1383,9 +1450,7 @@ body { } /* Blitz-Knopf: Text statt nur Symbol, damit der Zustand lesbar ist (Aus/Auto/An/Licht) */ .camera-modal .cam-blitz { font-size: 15px; white-space: nowrap; } -/* Kamera wechseln: weisses Symbol wie ✕, kein Emoji (das erschien als blaues Kaestchen) */ -.camera-modal .cam-flip { display: inline-flex; align-items: center; } -.camera-modal .cam-flip svg { display: block; } +/* Kamera wechseln: weisses Symbol wie ✕, kein Emoji (das erschien als blaues Kaestchen) — Stil: .icon-btn */ /* Scharfstellen per Antippen */ .camera-modal .cam-fokus-ring { position: absolute; diff --git a/app.js b/app.js index a21f6f7..3dd6d50 100644 --- a/app.js +++ b/app.js @@ -665,8 +665,8 @@ router.on('/today', async () => { const waypoints = todays.map(o => encodeURIComponent([o.customer.address, o.customer.zip, o.customer.town].filter(Boolean).join(' '))).join('/'); html += `
-

🚗 Route für heute (${todays.length})

- 🗺 Alle in Google Maps öffnen +

Route für heute (${todays.length})

+ ${icon('karte', 20)}Alle in Google Maps öffnen

Heute (${todays.length})

${renderOrderList(todays)} @@ -839,7 +839,7 @@ router.on('/orders/:id', async (args) => {

${escapeHtml(data.customer.name)}

${escapeHtml(data.customer.address || '')}

${escapeHtml((data.customer.zip || '') + ' ' + (data.customer.town || ''))}

- ${data.customer.phone ? `

📞 ${escapeHtml(data.customer.phone)}

` : ''} + ${data.customer.phone ? `

${icon('telefon', 18)}${escapeHtml(data.customer.phone)}

` : ''} ${data.order.auftragsbeschreibung ? ` @@ -848,22 +848,22 @@ router.on('/orders/:id', async (args) => {

${escapeHtml(data.order.auftragsbeschreibung)}

` : ''} - + - + - - - - - + + + + +

Hochgeladene Fotos (${imagePhotos.length})

- ${(imagePhotos.length + videoFiles.length + otherDocs.length) ? '' : ''} + ${(imagePhotos.length + videoFiles.length + otherDocs.length) ? '' : ''}
${imagePhotos.map(p => `
⏳
✓
`).join('')} @@ -871,7 +871,7 @@ router.on('/orders/:id', async (args) => {
${videoFiles.length ? `
-

🎬 Videos (${videoFiles.length})

+

Videos (${videoFiles.length})

${videoFiles.map(v => `
@@ -883,7 +883,7 @@ router.on('/orders/:id', async (args) => {
` : ''} ${audioFiles.length ? `
-

🎙 Sprachnotizen (${audioFiles.length})

+

Sprachnotizen (${audioFiles.length})

${audioFiles.map(a => `
${escapeHtml(a.filename)} @@ -979,8 +979,8 @@ router.on('/orders/:id', async (args) => { bar.innerHTML = `
0 ausgewählt
- - + + `; document.body.appendChild(bar); bar.querySelector('#sb-cancel').onclick = cancelViaBack; @@ -1186,9 +1186,9 @@ function openVideoPlayer(orderId, relpath, filename) { modal.innerHTML = `
-
🎬 ${escapeHtml(filename || 'Video')}
- - +
${escapeHtml(filename || 'Video')}
+ +
`; @@ -1282,7 +1282,7 @@ async function renderPendingPhotos() { const more = items.length - shown.length; box.innerHTML = '
' - + '

⏳ Warten auf Upload (' + items.length + ')

' + + '

Warten auf Upload (' + items.length + ')

' + lines.map(l => '

' + escapeHtml(l) + '

').join('') + '
' + thumbs + (more > 0 ? '
+' + more + '
' : '') @@ -1580,7 +1580,7 @@ function renderCustomerList(list) {
${escapeHtml(c.name || '')}
${escapeHtml((c.zip || '') + ' ' + (c.town || '')).trim() || '—'}
- ${c.phone ? '📞 ' + escapeHtml(c.phone) : ''} + ${c.phone ? icon('telefon', 14) + ' ' + escapeHtml(c.phone) : ''} ${c.bericht_count > 0 ? `📑 ${c.bericht_count}` : ''}
@@ -1614,11 +1614,11 @@ router.on('/customers/:id', async (args) => {

${escapeHtml(data.customer.name)}

${data.customer.code ? `

Kundennr: ${escapeHtml(data.customer.code)}

` : ''} ${addr ? `

${escapeHtml(addr).replace(/\n/g, '
')}

` : ''} - ${data.customer.phone ? `

📞 ${escapeHtml(data.customer.phone)}

` : ''} + ${data.customer.phone ? `

${icon('telefon', 18)}${escapeHtml(data.customer.phone)}

` : ''} ${data.customer.email ? `

✉ ${escapeHtml(data.customer.email)}

` : ''} ${data.customer.siret || data.customer.vat ? `

USt-ID: ${escapeHtml(data.customer.vat || '')}

` : ''}
- ${data.customer.address || data.customer.town ? `🗺 Route` : ''} + ${data.customer.address || data.customer.town ? `${icon('karte', 20)}Route` : ''}
@@ -1755,9 +1755,9 @@ router.on('/reports/:id', async (args) => {
` : '
📭
Dieser Bericht hat noch keine Seiten. Fotos aufnehmen, um Seiten hinzuzufügen.
'} - ${hasPages ? '' : ''} - - + ${hasPages ? '' : ''} + + `; @@ -1857,12 +1857,12 @@ router.on('/settings', async () => {
-

🔔 Benachrichtigungen

+

Benachrichtigungen

Per ntfy-Topic. Wenn gesetzt, zeigt die App Notifications bei neuen Nachrichten (z. B. neue Aufträge, Team-Pings).

- - + +

@@ -1873,11 +1873,11 @@ router.on('/settings', async () => { PIN beim Öffnen abfragen - ${pinEnabled ? '' : ''} + ${pinEnabled ? '' : ''}
- - + +

Baustelle PWA v1.0

`; document.getElementById('btn-sync').onclick = async () => { @@ -2112,7 +2112,7 @@ async function openMaterialModal(elementType, elementId) { modal.innerHTML = `
-
📦 Materialliste
+
Materialliste
@@ -2203,7 +2203,7 @@ async function openNewReportModal(orderId) { modal.innerHTML = `
-
📑 Neuer Bericht
+
Neuer Bericht
@@ -2291,7 +2291,7 @@ async function openNewOrderModal() { modal.innerHTML = `
-
➕ Neuer Auftrag
+
Neuer Auftrag
@@ -2501,7 +2501,7 @@ async function openPageActionsModal(berichtId, pageId, relpath, note, title) {
Seite bearbeiten
- +
${url ? `` : '
⚠
'} @@ -2509,7 +2509,7 @@ async function openPageActionsModal(berichtId, pageId, relpath, note, title) { - +
`; document.body.appendChild(modal); @@ -2572,9 +2572,9 @@ function openPdfModal(blobUrl, filename, beiDownload) { modal.innerHTML = `
-
📑 ${escapeHtml(fname)}
- - ⬇ +
${escapeHtml(fname)}
+ + ${icon('download')}
@@ -2751,7 +2751,7 @@ function openSignatureModal(berichtId) { modal.innerHTML = `
-
✍️ Kunden-Unterschrift
+
Kunden-Unterschrift
@@ -2927,7 +2927,7 @@ router.on('/shipments/:id', async (args) => { : '

Noch nicht unterschrieben

'}
- + `; // PDF inline rendern (PDF.js Canvas wie bei Reports) @@ -3056,7 +3056,7 @@ function openShipmentSignatureModal(shipmentId, info) {
- +
@@ -3228,7 +3228,7 @@ function openHelpModal() { modal.innerHTML = `
-
❓ Hilfe / Anleitung
+
Hilfe / Anleitung
@@ -3322,7 +3322,7 @@ function openHelpModal() {

8. Einstellungen

-

Im Reiter ⚙️:

+

Im Reiter Einstellungen (Zahnrad unten rechts):

  • 🔄 Offline-Queue manuell synchronisieren
  • 🚪 Abmelden
  • @@ -3353,6 +3353,46 @@ function layoutBadge(layout) { } } +/* Weisse Symbole fuer Knoepfe (Material Icons, fill = currentColor → folgt der Schriftfarbe). + * Emojis erscheinen je nach Handy bunt oder als blaues Kaestchen — Eddy (19.10.2026) beim + * Kamera-Wechselknopf: „den hässlichen Wechselbutton lässt du da doch nicht", dann fuer alle + * Knoepfe: „so dass es gut aussieht". Knoepfe nehmen deshalb diese Symbole, keine Emojis. */ +const ICON_PFADE = { + teilen: 'M18 16.08c-.76 0-1.44.3-1.96.77L8.91 12.7c.05-.23.09-.46.09-.7s-.04-.47-.09-.7l7.05-4.11c.54.5 1.25.81 2.04.81 1.66 0 3-1.34 3-3s-1.34-3-3-3-3 1.34-3 3c0 .24.04.47.09.7L8.04 9.81C7.5 9.31 6.79 9 6 9c-1.66 0-3 1.34-3 3s1.34 3 3 3c.79 0 1.5-.31 2.04-.81l7.12 4.16c-.05.21-.08.43-.08.65 0 1.61 1.31 2.92 2.92 2.92s2.92-1.31 2.92-2.92-1.31-2.92-2.92-2.92z', + loeschen: 'M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z', + zeichnen: 'M3 17.25V21h3.75L17.81 9.94l-3.75-3.75L3 17.25zM20.71 7.04a1 1 0 0 0 0-1.41l-2.34-2.34a1 1 0 0 0-1.41 0l-1.83 1.83 3.75 3.75 1.83-1.83z', + download: 'M5 20h14v-2H5v2zM19 9h-4V3H9v6H5l7 7 7-7z', + alle: 'M3 5h2V3c-1.1 0-2 .9-2 2zm0 8h2v-2H3v2zm4 8h2v-2H7v2zM3 9h2V7H3v2zm10-6h-2v2h2V3zm6 0v2h2c0-1.1-.9-2-2-2zM5 21v-2H3c0 1.1.9 2 2 2zm-2-4h2v-2H3v2zM9 3H7v2h2V3zm2 18h2v-2h-2v2zm8-8h2v-2h-2v2zm0 8c1.1 0 2-.9 2-2h-2v2zm0-12h2V7h-2v2zm0 8h2v-2h-2v2zm-4 4h2v-2h-2v2zm0-16h2V3h-2v2zM7 17h10V7H7v10zm2-8h6v6H9V9z', + blitzAus: 'M3.27 3L2 4.27l5 5V13h3v9l3.58-6.14L17.73 20 19 18.73 3.27 3zM17 10h-4l4-8H7v2.18l8.46 8.46L17 10z', + blitzAuto: 'M3 2v12h3v9l7-12H9l4-9H3zm16 0h-2l-3.2 9h1.9l.7-2h3.2l.7 2h1.9L19 2zm-2.15 5.65L18 4l1.15 3.65h-2.3z', + blitzAn: 'M7 2v11h3v9l7-12h-4l4-8z', + licht: 'M6 2h12v3H6zm0 5v1l2 3v11h8V11l2-3V7H6zm6 8.5c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z', + kamera: 'M12 15.2a3.2 3.2 0 1 0 0-6.4 3.2 3.2 0 0 0 0 6.4zM9 2L7.17 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2h-3.17L15 2H9zm3 15c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5z', + galerie: 'M22 16V4c0-1.1-.9-2-2-2H8c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2zm-11-4l2.03 2.71L16 11l4 5H8l3-4zM2 6v14c0 1.1.9 2 2 2h14v-2H4V6H2z', + mikrofon: 'M12 14c1.66 0 2.99-1.34 2.99-3L15 5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm5.3-3c0 3-2.54 5.1-5.3 5.1S6.7 14 6.7 11H5c0 3.41 2.72 6.23 6 6.72V21h2v-3.28c3.28-.48 6-3.3 6-6.72h-1.7z', + notizen: 'M3 18h12v-2H3v2zM3 6v2h18V6H3zm0 7h18v-2H3v2z', + liste: 'M19 5v14H5V5h14m1.1-2H3.9c-.5 0-.9.4-.9.9v16.2c0 .4.4.9.9.9h16.2c.4 0 .9-.5.9-.9V3.9c0-.5-.5-.9-.9-.9zM11 7h6v2h-6V7zm0 4h6v2h-6v-2zm0 4h6v2h-6v-2zM7 7h2v2H7V7zm0 4h2v2H7v-2zm0 4h2v2H7v-2z', + bericht: 'M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 14h-3v3h-2v-3H8v-2h3v-3h2v3h3v2zm-3-7V3.5L18.5 9H13z', + lieferung: 'M20 8h-3V4H3c-1.1 0-2 .9-2 2v11h2c0 1.66 1.34 3 3 3s3-1.34 3-3h6c0 1.66 1.34 3 3 3s3-1.34 3-3h2v-5l-3-4zM6 18.5c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5zm13.5-9l1.96 2.5H17V9.5h2.5zm-1.5 9c-.83 0-1.5-.67-1.5-1.5s.67-1.5 1.5-1.5 1.5.67 1.5 1.5-.67 1.5-1.5 1.5z', + karte: 'M20.5 3l-.16.03L15 5.1 9 3 3.36 4.9c-.21.07-.36.25-.36.48V20.5c0 .28.22.5.5.5l.16-.03L9 18.9l6 2.1 5.64-1.9c.21-.07.36-.25.36-.48V3.5c0-.28-.22-.5-.5-.5zM15 19l-6-2.11V5l6 2.11V19z', + telefon: 'M6.62 10.79c1.44 2.83 3.76 5.14 6.59 6.59l2.2-2.2c.27-.27.67-.36 1.02-.24 1.12.37 2.33.57 3.57.57.55 0 1 .45 1 1V20c0 .55-.45 1-1 1-9.39 0-17-7.61-17-17 0-.55.45-1 1-1h3.5c.55 0 1 .45 1 1 0 1.25.2 2.45.57 3.57.11.35.03.74-.25 1.02l-2.2 2.2z', + auswahl: 'M19 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.11 0 2-.9 2-2V5c0-1.1-.89-2-2-2zm-9 14l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z', + ansehen: 'M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z', + oeffnen: 'M19 19H5V5h7V3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2v-7h-2v7zM14 3v2h3.59l-9.83 9.83 1.41 1.41L19 6.41V10h2V3h-7z', + speichern: 'M17 3H5c-1.11 0-2 .9-2 2v14c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V7l-4-4zm-5 16c-1.66 0-3-1.34-3-3s1.34-3 3-3 3 1.34 3 3-1.34 3-3 3zm3-10H5V5h10v4z', + glocke: 'M12 22c1.1 0 2-.9 2-2h-4c0 1.1.89 2 2 2zm6-6v-5c0-3.07-1.64-5.64-4.5-6.32V4c0-.83-.67-1.5-1.5-1.5s-1.5.67-1.5 1.5v.68C7.63 5.36 6 7.92 6 11v5l-2 2v1h16v-1l-2-2z', + schluessel: 'M12.65 10C11.83 7.67 9.61 6 7 6c-3.31 0-6 2.69-6 6s2.69 6 6 6c2.61 0 4.83-1.67 5.65-4H17v4h4v-4h2v-4H12.65zM7 14c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2z', + sync: 'M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z', + abmelden: 'M17 7l-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.58L17 17l5-5zM4 5h8V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8v-2H4V5z', +}; +/* Farbe kommt aus app.css (.ikon-): klein = farbiges Symbol, im grossen Knopf = weisses + * Symbol im farbigen runden Feld. Eddy (20.10.2026) zu rein weissen Symbolen: „viel zu schlicht". */ +function icon(name, size) { + const g = size || 24; + return ''; +} + function escapeHtml(s) { return String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); } @@ -3418,7 +3458,7 @@ function openFileViewer({ url, blob, mime }, filename, relpath) {
    ${escapeHtml(filename || '')}
    - +
    @@ -3468,9 +3508,9 @@ async function openPhotoModal(orderId, relpath) {
    - - - + + +
    @@ -3650,7 +3690,7 @@ async function openVoiceModal(orderId) { modal.innerHTML = `
    -
    🎙 Sprachnotiz
    +
    Sprachnotiz
    @@ -3751,10 +3791,10 @@ async function openCameraModal(orderId) { modal.innerHTML = `
    -
    📷 Kamera
    +
    Kamera
    @@ -3956,7 +3996,9 @@ async function openCameraModal(orderId) { * (SCALER_CROP_REGION, gilt auch fuer takePhoto). Beim Zoomen verwirft Chrome den Tippbereich. * Frontkamera, Webcam am Tablet (X12) usw. melden meist nichts davon → kein Knopf, kein Antippen. */ - const BLITZ_TEXT = { off: '⚡ Aus', auto: '⚡ Auto', flash: '⚡ An', torch: '🔦 Licht' }; + const BLITZ_TEXT = { off: 'Aus', auto: 'Auto', flash: 'An', torch: 'Licht' }; + const BLITZ_ICON = { off: 'blitzAus', auto: 'blitzAuto', flash: 'blitzAn', torch: 'licht' }; + const blitzZeigen = () => { blitzBtn.innerHTML = icon(BLITZ_ICON[blitz], 22) + '' + BLITZ_TEXT[blitz] + ''; }; const BLITZ_KEY = 'baustelle.kamera.blitz'; let imageCap = null; let blitzModi = []; // Modi des aktuellen Kamera-Modus (Foto bzw. Video) @@ -4087,7 +4129,7 @@ async function openCameraModal(orderId) { blitz = blitzModi[(blitzModi.indexOf(blitz) + 1) % blitzModi.length]; } } - blitzBtn.textContent = BLITZ_TEXT[blitz]; + blitzZeigen(); if (modus === 'video') { blitzVideo = blitz; } else { @@ -4104,7 +4146,7 @@ async function openCameraModal(orderId) { blitz = modus === 'video' ? blitzVideo : blitzFoto; if (!blitzModi.includes(blitz)) blitz = 'off'; blitzBtn.hidden = blitzModi.length < 2; - blitzBtn.textContent = BLITZ_TEXT[blitz]; + blitzZeigen(); if ((vorher === 'torch') !== (blitz === 'torch')) lichtSchalten(blitz === 'torch'); } @@ -4437,14 +4479,14 @@ async function openUploadQueueModal() { modal.innerHTML = `
    -
    ☁ Upload-Warteschlange
    +
    Upload-Warteschlange

    ${pending.length} warten auf Upload${failed.length ? ', ' + failed.length + ' fehlgeschlagen' : ''}.

    - + ${failed.length ? '' : ''} - ${failed.length ? '' : ''} + ${failed.length ? '' : ''}

    Fotos und Videos bleiben gespeichert, bis der Upload bestätigt ist. Schließe die App nicht, solange hier noch Einträge stehen.

    `; @@ -4494,7 +4536,7 @@ async function openSketchEditor(orderId, imageUrl, sourceRelpath) { modal.innerHTML = `
    -
    ✏️ Skizze
    +
    Skizze
    @@ -4864,7 +4906,7 @@ async function openNotesModal(orderId) { modal.className = 'fullscreen-modal notes-modal'; modal.innerHTML = `