Weiße Seite, Name groß und mittig in Tinte, luftiger Blocksatz — und korallfarbene Punkte in den Rändern, teils vom Seitenrand angeschnitten, als einzige Farbe des Blattes. Der Lebenslauf greift das Motiv auf: die Punkte markieren die Abschnitte, führen die Bullets und bilden als Fünfer-Reihe die Sprach-Skala (gespeist aus niveauLevel(), also aus der Formulierung des Lebenslaufs selbst). Die Punkte skalieren bewusst nicht mit der Schrift: sie gehören zur Seite wie die Ränder. Das Anschreiben reserviert dafür unten ein Band, in das der Text nicht hineinläuft. Der Brief behält das DIN-Anschriftenfeld, das der Vorlage fehlt — es sitzt in deren zweispaltiger Meta-Zeile (Empfänger links, Datum und Kontakt rechts), so bleibt die Komposition erhalten und die Bewerbung deutsch. Dazu: - neue Akzentfarbe "Koralle" - write() kann optional Blocksatz. jsPDF stretcht eine Zeile nur, wenn es weiß, dass eine weitere folgt — der Absatz geht deshalb in einem Aufruf rüber, wodurch die Schlusszeile ungestretcht bleibt. - Infobox unter /vorlagen beschreibt jetzt auch Sunny (fehlte) und Tupfen Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
510 lines
34 KiB
Plaintext
510 lines
34 KiB
Plaintext
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<%- include('partials/head') %>
|
||
</head>
|
||
<body class="min-h-screen flex flex-col transition-colors duration-300 bg-gray-50 dark:bg-gray-900 text-gray-900 dark:text-gray-100" id="body">
|
||
<%- include('partials/header', { hideSettings: true }) %>
|
||
|
||
<main class="flex-1 container mx-auto px-4 py-8 max-w-3xl">
|
||
<!-- Back link -->
|
||
<a href="/" class="inline-flex items-center gap-2 text-sm text-blue-600 dark:text-blue-400 hover:underline mb-6">
|
||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"></path>
|
||
</svg>
|
||
Zurück zur Übersicht
|
||
</a>
|
||
|
||
<!-- Abgewiesener Upload (zu groß, falscher Dateityp): ohne diesen Banner
|
||
käme die Seite kommentarlos ohne die Datei zurück. -->
|
||
<% if (typeof fehler !== 'undefined' && fehler) { %>
|
||
<div class="mb-6 rounded-lg border border-red-300 dark:border-red-700 bg-red-50 dark:bg-red-900/20 p-4" role="alert">
|
||
<p class="text-sm text-red-800 dark:text-red-200"><%= fehler %></p>
|
||
</div>
|
||
<% } %>
|
||
|
||
<h1 class="text-2xl font-bold text-gray-800 dark:text-white mb-2">Basis-Unterlagen (Vorlagen)</h1>
|
||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-6">
|
||
Diese Unterlagen dienen der KI als Faktengrundlage. Beim Import einer Stelle über die Browser-Erweiterung
|
||
werden daraus automatisch zugeschnittene Bewerbungsunterlagen erstellt – es werden ausschließlich die
|
||
hier hinterlegten Informationen verwendet, es wird nichts hinzuerfunden. Füge z. B. dein
|
||
Standard-Anschreiben und deinen Lebenslauf als Text ein.
|
||
</p>
|
||
|
||
<!-- API key warning -->
|
||
<% if (!hasApiKey) { %>
|
||
<div class="rounded-lg bg-amber-50 dark:bg-amber-900/30 border border-amber-200 dark:border-amber-800 px-4 py-3 mb-6">
|
||
<div class="flex items-start gap-2">
|
||
<svg class="w-5 h-5 text-amber-600 dark:text-amber-400 shrink-0 mt-0.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"></path>
|
||
</svg>
|
||
<div class="text-sm text-amber-800 dark:text-amber-200">
|
||
<p class="font-semibold">Kein API-Schlüssel konfiguriert</p>
|
||
<p>Trage den Ollama-API-Schlüssel unter
|
||
<a href="/einstellungen" class="underline font-medium hover:no-underline">Einstellungen</a>
|
||
ein, damit Bewerbungsunterlagen automatisch generiert werden können.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<% } %>
|
||
|
||
<!-- Persönliche Angaben (formerly the index-page settings modal) -->
|
||
<h2 id="persoenlich" class="text-xl font-bold text-gray-800 dark:text-white mt-10 mb-2 scroll-mt-4">Persönliche Angaben</h2>
|
||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-6">
|
||
Deine Kontaktdaten fließen in jede Bewerbung ein – oben in Lebenslauf und Anschreiben
|
||
sowie in die Kontaktleiste. Die KI übernimmt sie unverändert und erfindet nichts mehr;
|
||
sie müssen dafür nicht länger in den Basis-Unterlagen stehen.
|
||
</p>
|
||
|
||
<section class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mb-8">
|
||
<form action="/vorlagen/persoenlich" method="POST" class="space-y-4">
|
||
<div>
|
||
<label for="name" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||
Vollständiger Name *
|
||
</label>
|
||
<input type="text" id="name" name="name" required
|
||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white"
|
||
placeholder="Max Mustermann" value="<%= settings.name || '' %>">
|
||
</div>
|
||
|
||
<div>
|
||
<label for="adresse" class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||
Adresse
|
||
</label>
|
||
<textarea id="adresse" name="adresse" rows="2"
|
||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white"
|
||
placeholder="Musterstraße 1, 12345 Musterstadt"><%= settings.adresse || '' %></textarea>
|
||
</div>
|
||
|
||
<div class="grid gap-4 sm:grid-cols-2">
|
||
<div>
|
||
<label for="email" class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">E-Mail</label>
|
||
<input type="email" id="email" name="email"
|
||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white"
|
||
placeholder="name@example.com" value="<%= settings.email || '' %>">
|
||
</div>
|
||
<div>
|
||
<label for="telefon" class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Telefon</label>
|
||
<input type="text" id="telefon" name="telefon"
|
||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white"
|
||
placeholder="z. B. 030 12345678" value="<%= settings.telefon || '' %>">
|
||
</div>
|
||
<div>
|
||
<label for="ort" class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Wohnort</label>
|
||
<input type="text" id="ort" name="ort"
|
||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white"
|
||
placeholder="z. B. Berlin" value="<%= settings.ort || '' %>">
|
||
</div>
|
||
<div>
|
||
<label for="webseite" class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Webseite</label>
|
||
<input type="text" id="webseite" name="webseite"
|
||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white"
|
||
placeholder="z. B. nextjobs.cc" value="<%= settings.webseite || '' %>">
|
||
</div>
|
||
<div>
|
||
<label for="geburtsdatum" class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Geburtsdatum</label>
|
||
<input type="text" id="geburtsdatum" name="geburtsdatum"
|
||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white"
|
||
placeholder="TT.MM.JJJJ" value="<%= settings.geburtsdatum || '' %>">
|
||
</div>
|
||
<div>
|
||
<label for="kundennummer" class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Jobcenter-Kundennummer</label>
|
||
<input type="text" id="kundennummer" name="kundennummer"
|
||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white"
|
||
placeholder="z. B. 123456789" value="<%= settings.kundennummer || '' %>">
|
||
</div>
|
||
</div>
|
||
|
||
<div class="flex justify-end">
|
||
<button type="submit" class="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-md transition-colors">
|
||
Speichern
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</section>
|
||
|
||
<!-- Existing base documents -->
|
||
<section class="space-y-4 mb-8">
|
||
<% if (basisDokumente.length === 0) { %>
|
||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 text-center text-gray-500 dark:text-gray-400">
|
||
Noch keine Basis-Unterlagen hinterlegt. Füge unten dein erstes Dokument hinzu.
|
||
</div>
|
||
<% } %>
|
||
<% basisDokumente.forEach(doc => { %>
|
||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
|
||
<form action="/vorlagen/<%= doc.id %>" method="POST" class="space-y-3">
|
||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||
<div>
|
||
<label class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Typ</label>
|
||
<select name="typ" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white">
|
||
<% basisTypOptions.forEach(opt => { %>
|
||
<option value="<%= opt %>" <%= doc.typ === opt ? 'selected' : '' %>><%= opt %></option>
|
||
<% }); %>
|
||
</select>
|
||
</div>
|
||
<div class="sm:col-span-2">
|
||
<label class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Bezeichnung</label>
|
||
<input type="text" name="name" value="<%= doc.name || '' %>"
|
||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white"
|
||
placeholder="z. B. Standard-Anschreiben">
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Inhalt (Text)</label>
|
||
<textarea name="inhalt" rows="8"
|
||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white leading-relaxed font-mono text-sm"><%= doc.inhalt %></textarea>
|
||
</div>
|
||
<div class="flex justify-end gap-2">
|
||
<button type="submit"
|
||
class="px-3 py-1.5 text-sm bg-blue-600 hover:bg-blue-700 text-white rounded-md transition-colors">
|
||
Speichern
|
||
</button>
|
||
</div>
|
||
</form>
|
||
<form action="/vorlagen/<%= doc.id %>/delete" method="POST" class="mt-1 text-right"
|
||
onsubmit="return confirm('Dieses Basis-Dokument löschen?');">
|
||
<button type="submit"
|
||
class="text-xs text-red-600 hover:text-red-800 dark:text-red-400 dark:hover:text-red-300 hover:underline">
|
||
Dokument löschen
|
||
</button>
|
||
</form>
|
||
</div>
|
||
<% }); %>
|
||
</section>
|
||
|
||
<!-- Add new base document -->
|
||
<section class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
|
||
<h2 class="text-lg font-semibold text-gray-800 dark:text-white mb-4">Basis-Dokument hinzufügen</h2>
|
||
<form action="/vorlagen" method="POST" class="space-y-3">
|
||
<div class="grid grid-cols-1 sm:grid-cols-3 gap-3">
|
||
<div>
|
||
<label class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Typ</label>
|
||
<select name="typ" class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white">
|
||
<% basisTypOptions.forEach(opt => { %>
|
||
<option value="<%= opt %>"><%= opt %></option>
|
||
<% }); %>
|
||
</select>
|
||
</div>
|
||
<div class="sm:col-span-2">
|
||
<label class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Bezeichnung</label>
|
||
<input type="text" name="name"
|
||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white"
|
||
placeholder="z. B. Standard-Anschreiben">
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Inhalt (Text)</label>
|
||
<textarea name="inhalt" rows="10" required
|
||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white leading-relaxed font-mono text-sm"
|
||
placeholder="Füge hier den vollständigen Text deines Anschreibens oder Lebenslaufs ein…"></textarea>
|
||
</div>
|
||
<div class="flex justify-end">
|
||
<button type="submit" class="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-md transition-colors">
|
||
Hinzufügen
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</section>
|
||
|
||
<!-- Static extra attachments (Zeugnisse etc.) -->
|
||
<h2 class="text-xl font-bold text-gray-800 dark:text-white mt-10 mb-2">Zusätzliche Anlagen</h2>
|
||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-6">
|
||
Feste Dateien (z. B. Arbeitszeugnisse, Zertifikate), die jeder generierten Bewerbung
|
||
automatisch als Anhang beigelegt und im Anschreiben unter „Anlagen" aufgeführt werden.
|
||
Für einzelne Bewerbungen kannst du den Anhang später auf der Bewerbungsseite wieder entfernen.
|
||
</p>
|
||
|
||
<section class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mb-6">
|
||
<% if (basisAnhaenge.length === 0) { %>
|
||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4">Noch keine zusätzlichen Anlagen hinterlegt.</p>
|
||
<% } else { %>
|
||
<ul class="space-y-2 mb-2">
|
||
<% basisAnhaenge.forEach(a => { %>
|
||
<li class="flex items-center justify-between gap-3 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/40 px-4 py-3">
|
||
<div class="flex items-center gap-3 min-w-0">
|
||
<svg class="w-6 h-6 text-red-500 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z"></path>
|
||
</svg>
|
||
<div class="min-w-0">
|
||
<p class="text-sm font-medium text-gray-800 dark:text-gray-100 truncate"><%= a.name || a.dateiname %></p>
|
||
<p class="text-xs text-gray-500 dark:text-gray-400 truncate"><%= a.dateiname %></p>
|
||
</div>
|
||
</div>
|
||
<div class="flex items-center gap-3 shrink-0">
|
||
<a href="/anlagen/<%= a.id %>/download" class="text-sm text-blue-600 hover:text-blue-800 dark:text-blue-400 hover:underline">Download</a>
|
||
<form action="/anlagen/<%= a.id %>/delete" method="POST" onsubmit="return confirm('Diese Anlage löschen?');">
|
||
<button type="submit" class="text-red-500 hover:text-red-700 dark:text-red-400" aria-label="Anlage löschen">
|
||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"></path>
|
||
</svg>
|
||
</button>
|
||
</form>
|
||
</div>
|
||
</li>
|
||
<% }); %>
|
||
</ul>
|
||
<% } %>
|
||
|
||
<form action="/anlagen" method="POST" enctype="multipart/form-data" class="border-t border-gray-200 dark:border-gray-700 pt-4 mt-4 space-y-3">
|
||
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||
<div>
|
||
<label class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">
|
||
Datei (PDF) · max. <%= typeof anhangMaxMb !== 'undefined' ? anhangMaxMb : 15 %> MB
|
||
</label>
|
||
<input type="file" name="datei" accept="application/pdf,image/*" required
|
||
class="w-full text-sm text-gray-700 dark:text-gray-300 file:mr-3 file:py-2 file:px-3 file:rounded-md file:border-0 file:bg-blue-600 file:text-white hover:file:bg-blue-700">
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Bezeichnung (optional)</label>
|
||
<input type="text" name="name"
|
||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-800 dark:text-white"
|
||
placeholder="z. B. Arbeitszeugnis">
|
||
</div>
|
||
</div>
|
||
<div class="flex justify-end">
|
||
<button type="submit" class="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-md transition-colors">
|
||
Anlage hochladen
|
||
</button>
|
||
</div>
|
||
</form>
|
||
</section>
|
||
|
||
<!-- Signature -->
|
||
<h2 class="text-xl font-bold text-gray-800 dark:text-white mt-10 mb-2">Unterschrift</h2>
|
||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-6">
|
||
Lade dein Unterschriftsbild (PNG/JPG, am besten mit transparentem Hintergrund) hoch. Es wird im
|
||
Anschreiben direkt unter „Mit freundlichen Grüßen" anstelle des getippten Namens eingefügt.
|
||
</p>
|
||
|
||
<section class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mb-6">
|
||
<% if (hasSignatur) { %>
|
||
<div class="flex items-center justify-between gap-4 mb-4">
|
||
<div class="rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/40 p-3">
|
||
<img src="/unterschrift?t=<%= Date.now() %>" alt="Unterschrift" class="max-h-24 max-w-[240px] object-contain">
|
||
</div>
|
||
<form action="/unterschrift/delete" method="POST" onsubmit="return confirm('Unterschrift entfernen?');">
|
||
<button type="submit" class="text-sm text-red-600 hover:text-red-800 dark:text-red-400 hover:underline">Entfernen</button>
|
||
</form>
|
||
</div>
|
||
<% } else { %>
|
||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4">Noch keine Unterschrift hinterlegt.</p>
|
||
<% } %>
|
||
|
||
<form action="/unterschrift" method="POST" enctype="multipart/form-data"
|
||
class="border-t border-gray-200 dark:border-gray-700 pt-4 mt-2 flex flex-wrap items-end gap-3">
|
||
<div class="flex-1 min-w-[220px]">
|
||
<label class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Unterschrift-Bild (PNG/JPG)</label>
|
||
<input type="file" name="signatur" accept="image/png,image/jpeg" required
|
||
class="w-full text-sm text-gray-700 dark:text-gray-300 file:mr-3 file:py-2 file:px-3 file:rounded-md file:border-0 file:bg-blue-600 file:text-white hover:file:bg-blue-700">
|
||
</div>
|
||
<button type="submit" class="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-md transition-colors">
|
||
<%= hasSignatur ? 'Ersetzen' : 'Hochladen' %>
|
||
</button>
|
||
</form>
|
||
</section>
|
||
|
||
<!-- Applicant photo (Bewerberfoto) -->
|
||
<h2 class="text-xl font-bold text-gray-800 dark:text-white mt-10 mb-2">Bewerberfoto</h2>
|
||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-6">
|
||
Lade optional dein Bewerbungsfoto (PNG/JPG, Hochformat) hoch. Es wird oben rechts im
|
||
Lebenslauf platziert; ohne Foto bleibt der Lebenslauf textlich wie bisher.
|
||
</p>
|
||
|
||
<section class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mb-6">
|
||
<% if (hasFoto) { %>
|
||
<div class="flex items-center justify-between gap-4 mb-4">
|
||
<div class="rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-700/40 p-3">
|
||
<img src="/bewerbungsfoto?t=<%= Date.now() %>" alt="Bewerberfoto" class="max-h-40 max-w-[120px] object-contain">
|
||
</div>
|
||
<form action="/bewerbungsfoto/delete" method="POST" onsubmit="return confirm('Bewerberfoto entfernen?');">
|
||
<button type="submit" class="text-sm text-red-600 hover:text-red-800 dark:text-red-400 hover:underline">Entfernen</button>
|
||
</form>
|
||
</div>
|
||
<% } else { %>
|
||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-4">Noch kein Bewerberfoto hinterlegt.</p>
|
||
<% } %>
|
||
|
||
<form action="/bewerbungsfoto" method="POST" enctype="multipart/form-data"
|
||
class="border-t border-gray-200 dark:border-gray-700 pt-4 mt-2 flex flex-wrap items-end gap-3">
|
||
<div class="flex-1 min-w-[220px]">
|
||
<label class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1">Bewerberfoto (PNG/JPG, Hochformat)</label>
|
||
<input type="file" name="foto" accept="image/png,image/jpeg" required
|
||
class="w-full text-sm text-gray-700 dark:text-gray-300 file:mr-3 file:py-2 file:px-3 file:rounded-md file:border-0 file:bg-blue-600 file:text-white hover:file:bg-blue-700">
|
||
</div>
|
||
<button type="submit" class="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-md transition-colors">
|
||
<%= hasFoto ? 'Ersetzen' : 'Hochladen' %>
|
||
</button>
|
||
</form>
|
||
</section>
|
||
|
||
<!-- Design of the generated PDFs -->
|
||
<h2 id="design" class="text-xl font-bold text-gray-800 dark:text-white mt-10 mb-2 scroll-mt-4">Design der Unterlagen</h2>
|
||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-6">
|
||
Aussehen von Anschreiben und Lebenslauf. <strong>Das Anschreiben übernimmt immer das Layout des
|
||
Lebenslaufs</strong>, damit beide Dokumente als ein Set ankommen. Der Lebenslauf passt in jedem Layout
|
||
garantiert auf eine Seite. Die Vorschau zeigt Musterinhalte mit deinen Kontaktdaten, Foto und deiner
|
||
Unterschrift, ohne dass die KI läuft.
|
||
</p>
|
||
|
||
<div class="rounded-lg bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800 px-4 py-3 mb-6 text-sm text-blue-900 dark:text-blue-200">
|
||
<p><strong>Klassisch mit Sidebar</strong> – zurückhaltend, monochrom, für klassische Bewerbungen (Standard).</p>
|
||
<p class="mt-1"><strong>Social Media</strong> – verspielt: getönte Seite, runde Karten, Herzchen, Schreibschrift.
|
||
Bewusst auffällig – passend für kreative und Social-Media-Stellen, nicht für die Bank.</p>
|
||
<p class="mt-1"><strong>Sunny</strong> – plakativ: weiße Seite, sehr große Überschriften, pastellige Farbbänder,
|
||
ein Foto, das aus dem Kopfband ragt. Auffällig, aber aufgeräumt – für Marketing, Medien und Design.</p>
|
||
<p class="mt-1"><strong>Tupfen</strong> – dezent mit Augenzwinkern: weiße Seite, Name groß und mittig, luftiger
|
||
Blocksatz – und verstreute Punkte in den Rändern als einzige Farbe. Der Lebenslauf greift sie als
|
||
Abschnittsmarker und Sprach-Skala wieder auf. Passt überall, wirkt trotzdem nicht wie von der Stange.</p>
|
||
</div>
|
||
|
||
<section class="bg-white dark:bg-gray-800 rounded-lg shadow-md p-6 mb-8">
|
||
<form action="/vorlagen/design" method="POST" class="space-y-4" id="designForm">
|
||
<div class="grid gap-4 sm:grid-cols-2">
|
||
<% designFelder.forEach(f => { %>
|
||
<div data-feld="<%= f.key %>" <%- f.nurLayout ? 'data-nur-layout="' + f.nurLayout + '"' : '' %>>
|
||
<label class="block text-xs font-medium text-gray-500 dark:text-gray-400 mb-1"><%= f.label %></label>
|
||
<select name="<%= f.key %>"
|
||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white">
|
||
<% f.optionen.forEach(o => { %>
|
||
<option value="<%= o.key %>" <%= o.gewaehlt ? 'selected' : '' %>><%= o.label %></option>
|
||
<% }) %>
|
||
</select>
|
||
</div>
|
||
<% }) %>
|
||
</div>
|
||
|
||
<!-- The hidden field must come first: when the box is checked both
|
||
values are submitted and the *last* one wins. -->
|
||
<input type="hidden" name="foto_anzeigen" value="0">
|
||
<label class="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
||
<input type="checkbox" name="foto_anzeigen" value="1" <%= designFotoAn ? 'checked' : '' %>
|
||
class="rounded border-gray-300 dark:border-gray-600">
|
||
Bewerberfoto im Lebenslauf anzeigen
|
||
<% if (!hasFoto) { %>
|
||
<span class="text-xs text-gray-400 dark:text-gray-500">(noch kein Foto hinterlegt)</span>
|
||
<% } %>
|
||
</label>
|
||
|
||
<div class="flex flex-wrap items-center gap-3 border-t border-gray-200 dark:border-gray-700 pt-4">
|
||
<button type="submit" class="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-md transition-colors">
|
||
Speichern
|
||
</button>
|
||
<button type="button" id="vorschauAnschreiben"
|
||
class="px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-200 rounded-md hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
||
Vorschau Anschreiben
|
||
</button>
|
||
<button type="button" id="vorschauLebenslauf"
|
||
class="px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-200 rounded-md hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors">
|
||
Vorschau Lebenslauf
|
||
</button>
|
||
<% if (designAngepasst) { %>
|
||
<button type="submit" formaction="/vorlagen/design/reset" formnovalidate
|
||
onclick="return confirm('Design auf die Standardwerte zurücksetzen?');"
|
||
class="text-sm text-gray-600 dark:text-gray-400 hover:underline">
|
||
Auf Standard zurücksetzen
|
||
</button>
|
||
<% } %>
|
||
</div>
|
||
<p class="text-xs text-gray-400 dark:text-gray-500">
|
||
Die Vorschau zeigt die aktuell <em>ausgewählten</em> Werte – auch ungespeicherte.
|
||
</p>
|
||
</form>
|
||
</section>
|
||
|
||
<!-- Editable KI prompts -->
|
||
<h2 id="prompts" class="text-xl font-bold text-gray-800 dark:text-white mt-10 mb-2 scroll-mt-4">KI-Prompts</h2>
|
||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-6">
|
||
Hier steht, <em>wie</em> die KI schreibt: Rolle, Tonfall und Regeln für Unterlagen, Chat und E-Mails.
|
||
Du kannst jeden Text anpassen – etwa um zu duzen, Formulierungen zu verbieten oder eine andere
|
||
Sprache zu verlangen. Der technische Aufbau (Aufbau des Lebenslaufs, Felder der E-Mail) ist fest
|
||
verdrahtet und wird davon nicht berührt, damit nichts kaputtgehen kann.
|
||
Mit <strong>Zurücksetzen</strong> holst du jederzeit den Standardtext zurück.
|
||
</p>
|
||
|
||
<section class="space-y-4 mb-8">
|
||
<% prompts.forEach(p => { %>
|
||
<details class="bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden" <%= p.angepasst ? 'open' : '' %>>
|
||
<summary class="cursor-pointer select-none px-6 py-4 flex items-center gap-3">
|
||
<span class="font-semibold text-gray-800 dark:text-white"><%= p.titel %></span>
|
||
<% if (p.angepasst) { %>
|
||
<span class="text-xs px-2 py-0.5 rounded-full bg-blue-100 text-blue-800 dark:bg-blue-900/50 dark:text-blue-300">angepasst</span>
|
||
<% } else { %>
|
||
<span class="text-xs px-2 py-0.5 rounded-full bg-gray-100 text-gray-500 dark:bg-gray-700 dark:text-gray-400">Standard</span>
|
||
<% } %>
|
||
</summary>
|
||
|
||
<div class="px-6 pb-6 border-t border-gray-200 dark:border-gray-700 pt-4">
|
||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-3"><%= p.beschreibung %></p>
|
||
|
||
<% if (p.platzhalter && p.platzhalter.length) { %>
|
||
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">
|
||
Platzhalter:
|
||
<% p.platzhalter.forEach(ph => { %>
|
||
<code class="px-1 rounded bg-gray-100 dark:bg-gray-700">{{<%= ph %>}}</code>
|
||
<% }) %>
|
||
– wird beim Generieren automatisch ersetzt (Name aus den Einstellungen).
|
||
</p>
|
||
<% } %>
|
||
|
||
<form action="/vorlagen/prompts/<%= p.key %>" method="POST" class="space-y-3">
|
||
<textarea name="inhalt" rows="9"
|
||
class="w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md bg-white dark:bg-gray-700 text-gray-900 dark:text-white font-mono text-xs leading-relaxed"><%= p.inhalt %></textarea>
|
||
<div class="flex items-center gap-3">
|
||
<button type="submit" class="px-4 py-2 bg-green-600 hover:bg-green-700 text-white rounded-md transition-colors">
|
||
Speichern
|
||
</button>
|
||
<% if (p.angepasst) { %>
|
||
<button type="submit" formaction="/vorlagen/prompts/<%= p.key %>/reset"
|
||
formnovalidate
|
||
onclick="return confirm('Diesen Prompt auf den Standardtext zurücksetzen? Deine Änderungen gehen verloren.');"
|
||
class="text-sm text-gray-600 dark:text-gray-400 hover:underline">
|
||
Auf Standard zurücksetzen
|
||
</button>
|
||
<% } %>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</details>
|
||
<% }) %>
|
||
</section>
|
||
</main>
|
||
|
||
<%- include('partials/footer') %>
|
||
|
||
<script>
|
||
(function () {
|
||
const cy = document.getElementById('currentYear');
|
||
if (cy) cy.textContent = new Date().getFullYear();
|
||
})();
|
||
|
||
// Design preview: open the sample PDF with the values currently selected
|
||
// in the form (not just the saved ones), so a choice can be judged before
|
||
// it is committed.
|
||
(function () {
|
||
const form = document.getElementById('designForm');
|
||
if (!form) return;
|
||
function oeffne(welches) {
|
||
const q = new URLSearchParams(new FormData(form)).toString();
|
||
window.open('/vorlagen/design/vorschau/' + welches + '.pdf?' + q, '_blank');
|
||
}
|
||
const a = document.getElementById('vorschauAnschreiben');
|
||
const l = document.getElementById('vorschauLebenslauf');
|
||
if (a) a.addEventListener('click', () => oeffne('anschreiben'));
|
||
if (l) l.addEventListener('click', () => oeffne('lebenslauf'));
|
||
|
||
// Hide fields that only apply to one layout (e.g. the sidebar tint has
|
||
// no meaning in the social layout).
|
||
const layoutSel = form.querySelector('[name="layout"]');
|
||
function syncFelder() {
|
||
const aktiv = layoutSel ? layoutSel.value : 'sidebar';
|
||
form.querySelectorAll('[data-nur-layout]').forEach((el) => {
|
||
el.style.display = el.dataset.nurLayout === aktiv ? '' : 'none';
|
||
});
|
||
}
|
||
if (layoutSel) layoutSel.addEventListener('change', syncFelder);
|
||
syncFelder();
|
||
})();
|
||
</script>
|
||
</body>
|
||
</html>
|