<?php
session_start();
// --- Config ---
define('CONFIG_FILE', __DIR__ . '/data/config.json');
define('SOURCES_FILE', __DIR__ . '/data/sources.json');
define('RESULTS_FILE', __DIR__ . '/data/results.json');
define('APP_PASSWORD', 'changeme123'); // <-- Passwort hier ändern
// --- Bootstrap ---
if (!is_dir(__DIR__ . '/data')) mkdir(__DIR__ . '/data', 0755, true);
if (!file_exists(CONFIG_FILE)) file_put_contents(CONFIG_FILE, json_encode(['api_key' => '', 'keywords' => 'Marketing Manager, Senior Marketing, Head of Marketing', 'location' => 'Karlsruhe, Pforzheim, Heilbronn'], JSON_PRETTY_PRINT));
if (!file_exists(SOURCES_FILE)) file_put_contents(SOURCES_FILE, json_encode([], JSON_PRETTY_PRINT));
if (!file_exists(RESULTS_FILE)) file_put_contents(RESULTS_FILE, json_encode([], JSON_PRETTY_PRINT));
function loadJson($file) { return json_decode(file_get_contents($file), true) ?? []; }
function saveJson($file, $data) { file_put_contents($file, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); }
// --- Auth ---
if (isset($_POST['logout'])) { session_destroy(); header('Location: index.php'); exit; }
if (isset($_POST['password'])) {
if ($_POST['password'] === APP_PASSWORD) { $_SESSION['auth'] = true; }
else { $loginError = 'Falsches Passwort.'; }
}
if (!isset($_SESSION['auth'])) { showLogin($loginError ?? null); exit; }
// --- Actions ---
$config = loadJson(CONFIG_FILE);
$sources = loadJson(SOURCES_FILE);
$results = loadJson(RESULTS_FILE);
$message = '';
$scanLog = [];
// Save config
if (isset($_POST['save_config'])) {
$config['api_key'] = trim($_POST['api_key'] ?? '');
$config['keywords'] = trim($_POST['keywords'] ?? '');
$config['location'] = trim($_POST['location'] ?? '');
saveJson(CONFIG_FILE, $config);
$message = '✓ Einstellungen gespeichert.';
}
// Add source
if (isset($_POST['add_source'])) {
$url = trim($_POST['source_url'] ?? '');
$name = trim($_POST['source_name'] ?? '');
if ($url && $name) {
$sources[] = ['id' => uniqid(), 'name' => $name, 'url' => $url, 'active' => true];
saveJson(SOURCES_FILE, $sources);
$message = '✓ Quelle hinzugefügt.';
}
}
// Toggle source
if (isset($_POST['toggle_source'])) {
foreach ($sources as &$s) {
if ($s['id'] === $_POST['toggle_source']) { $s['active'] = !$s['active']; }
}
saveJson(SOURCES_FILE, $sources);
header('Location: index.php?tab=sources'); exit;
}
// Delete source
if (isset($_POST['delete_source'])) {
$sources = array_values(array_filter($sources, fn($s) => $s['id'] !== $_POST['delete_source']));
saveJson(SOURCES_FILE, $sources);
header('Location: index.php?tab=sources'); exit;
}
// Delete result
if (isset($_POST['delete_result'])) {
$results = array_values(array_filter($results, fn($r) => $r['id'] !== $_POST['delete_result']));
saveJson(RESULTS_FILE, $results);
header('Location: index.php?tab=results'); exit;
}
// Clear all results
if (isset($_POST['clear_results'])) {
saveJson(RESULTS_FILE, []);
$results = [];
$message = '✓ Alle Ergebnisse gelöscht.';
}
// --- SCAN ---
if (isset($_POST['start_scan'])) {
if (empty($config['api_key'])) {
$message = '⚠ Bitte zuerst den Claude API Key in den Einstellungen eintragen.';
} else {
$activeSources = array_filter($sources, fn($s) => $s['active']);
if (empty($activeSources)) {
$message = '⚠ Keine aktiven Quellen vorhanden.';
} else {
$newJobs = [];
$existingUrls = array_column($results, 'url');
foreach ($activeSources as $source) {
$scanLog[] = "Lese: " . $source['name'];
$jobs = fetchRssFeed($source['url'], $source['name']);
$scanLog[] = "→ " . count($jobs) . " Einträge gefunden";
foreach ($jobs as $job) {
if (!in_array($job['url'], $existingUrls)) {
$newJobs[] = $job;
$existingUrls[] = $job['url'];
}
}
}
$scanLog[] = "Neu gesamt: " . count($newJobs) . " Stellen";
if (!empty($newJobs)) {
$keywords = strtolower($config['keywords']);
$kwList = array_map('trim', explode(',', $keywords));
$location = strtolower($config['location']);
$locList = array_map('trim', explode(',', $location));
$filtered = array_filter($newJobs, function($job) use ($kwList, $locList) {
$text = strtolower($job['title'] . ' ' . $job['description']);
$locText = strtolower($job['location'] ?? $job['title'] . ' ' . $job['description']);
$kwMatch = false;
foreach ($kwList as $kw) { if ($kw && strpos($text, $kw) !== false) { $kwMatch = true; break; } }
if (!$kwMatch) return false;
if (empty(array_filter($locList))) return true;
foreach ($locList as $loc) { if ($loc && strpos($locText, $loc) !== false) return true; }
return false;
});
$filtered = array_values($filtered);
$scanLog[] = "Nach Filter: " . count($filtered) . " relevante Stellen";
foreach ($filtered as $job) {
$scanLog[] = "Bewerte: " . $job['title'];
$rating = rateJobWithClaude($job, $config['api_key']);
$results[] = [
'id' => uniqid(),
'title' => $job['title'],
'company' => $job['company'] ?? '',
'location' => $job['location'] ?? '',
'url' => $job['url'],
'source' => $job['source'],
'description' => substr($job['description'], 0, 500),
'rating' => $rating['rating'] ?? 'unbekannt',
'verdict' => $rating['verdict'] ?? '',
'summary' => $rating['summary'] ?? '',
'found_at' => date('d.m.Y H:i'),
];
}
saveJson(RESULTS_FILE, $results);
$message = '✓ Scan abgeschlossen. ' . count($filtered) . ' neue Stellen bewertet.';
} else {
$message = 'ℹ Keine neuen Stellen gefunden.';
}
}
}
}
// --- Functions ---
function fetchRssFeed($url, $sourceName) {
$jobs = [];
$ctx = stream_context_create(['http' => ['timeout' => 15, 'header' => 'User-Agent: Mozilla/5.0 JobScout/1.0']]);
$xml = @file_get_contents($url, false, $ctx);
if (!$xml) return $jobs;
libxml_use_internal_errors(true);
$feed = simplexml_load_string($xml);
if (!$feed) return $jobs;
$items = $feed->channel->item ?? $feed->entry ?? [];
foreach ($items as $item) {
$title = (string)($item->title ?? '');
$link = (string)($item->link ?? $item->id ?? '');
if (is_object($item->link) && isset($item->link['href'])) $link = (string)$item->link['href'];
$desc = strip_tags((string)($item->description ?? $item->summary ?? $item->content ?? ''));
if ($title && $link) {
$jobs[] = ['title' => $title, 'url' => $link, 'description' => $desc, 'company' => '', 'location' => '', 'source' => $sourceName];
}
}
return $jobs;
}
function rateJobWithClaude($job, $apiKey) {
$profile = <<<PROFILE
Frederik ist Senior Marketing Manager mit 15+ Jahren Erfahrung, davon 2,5 Jahre B2B-IT (Microsoft, Security, KI, Cloud).
Kernkompetenz: Komplexe technische Themen verständlich kommunizieren, Marketing als System aufbauen.
Sucht: Teamlead / Head of Marketing oder Senior Marketing Manager.
Branche bevorzugt: IT, SaaS, Mittelstand, Technologieunternehmen, erklärungsbedürftige Produkte.
Region: Karlsruhe, Pforzheim, Heilbronn, BaWü (40km um Eisingen/Pforzheim).
No-Gos: Reine Ausführungsrollen, konzernweit gesteuerte Satelliten-Marketingstellen, kein Gestaltungsspielraum.
Gehalt Senior: ~65k€, Teamlead: 76-80k€.
PROFILE;
$prompt = "Bewerte diese Stellenanzeige für Frederik:\n\nTitel: {$job['title']}\nBeschreibung: {$job['description']}\n\nProfil:\n$profile\n\nAntworte NUR mit gültigem JSON (kein Markdown, keine Backticks):\n{\"rating\": \"gruen|gelb|rot\", \"verdict\": \"Ein Satz Urteil\", \"summary\": \"2-3 Sätze Begründung\"}";
$payload = json_encode(['model' => 'claude-sonnet-4-20250514', 'max_tokens' => 300, 'messages' => [['role' => 'user', 'content' => $prompt]]]);
$ctx = stream_context_create(['http' => ['method' => 'POST', 'timeout' => 30, 'header' => "Content-Type: application/json\r\nx-api-key: $apiKey\r\nanthropid-version: 2023-06-01\r\n", 'content' => $payload]]);
$response = @file_get_contents('https://api.anthropic.com/v1/messages', false, $ctx);
if (!$response) return ['rating' => 'unbekannt', 'verdict' => 'API nicht erreichbar', 'summary' => ''];
$data = json_decode($response, true);
$text = $data['content'][0]['text'] ?? '';
$text = preg_replace('/```json|```/', '', $text);
$parsed = json_decode(trim($text), true);
return $parsed ?: ['rating' => 'unbekannt', 'verdict' => $text, 'summary' => ''];
}
$tab = $_GET['tab'] ?? 'results';
// Sort results
usort($results, function($a, $b) {
$order = ['gruen' => 0, 'gelb' => 1, 'rot' => 2, 'unbekannt' => 3];
return ($order[$a['rating']] ?? 3) <=> ($order[$b['rating']] ?? 3);
});
// --- Output ---
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JobScout</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Syne:wght@400;600;700;800&display=swap" rel="stylesheet">
<style>
:root {
--bg: #0e0f11;
--surface: #16181c;
--border: #2a2d35;
--accent: #c8f135;
--accent2: #4af0c4;
--text: #e8eaf0;
--muted: #6b7280;
--gruen: #4af0c4;
--gelb: #f5c842;
--rot: #f05a5a;
--font-head: 'Syne', sans-serif;
--font-mono: 'DM Mono', monospace;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body { background: var(--bg); color: var(--text); font-family: var(--font-mono); min-height: 100vh; }
/* Header */
.header { display: flex; align-items: center; justify-content: space-between; padding: 1.5rem 2rem; border-bottom: 1px solid var(--border); }
.logo { font-family: var(--font-head); font-weight: 800; font-size: 1.4rem; color: var(--accent); letter-spacing: -0.03em; }
.logo span { color: var(--muted); font-weight: 400; }
.logout-btn { background: none; border: 1px solid var(--border); color: var(--muted); padding: .4rem .9rem; cursor: pointer; font-family: var(--font-mono); font-size: .75rem; border-radius: 4px; transition: all .2s; }
.logout-btn:hover { border-color: var(--accent); color: var(--accent); }
/* Nav */
.nav { display: flex; gap: 0; border-bottom: 1px solid var(--border); padding: 0 2rem; }
.nav a { padding: .85rem 1.2rem; font-size: .8rem; color: var(--muted); text-decoration: none; border-bottom: 2px solid transparent; transition: all .2s; letter-spacing: .05em; text-transform: uppercase; }
.nav a.active { color: var(--accent); border-bottom-color: var(--accent); }
.nav a:hover:not(.active) { color: var(--text); }
/* Main */
.main { max-width: 1100px; margin: 0 auto; padding: 2rem; }
/* Message */
.msg { background: #1a1f2e; border: 1px solid var(--accent2); color: var(--accent2); padding: .75rem 1.1rem; border-radius: 6px; margin-bottom: 1.5rem; font-size: .85rem; }
/* Scan bar */
.scan-bar { display: flex; align-items: center; gap: 1rem; margin-bottom: 2rem; }
.btn-scan { background: var(--accent); color: #0e0f11; border: none; padding: .75rem 2rem; font-family: var(--font-head); font-weight: 700; font-size: .95rem; cursor: pointer; border-radius: 6px; transition: all .2s; letter-spacing: .02em; }
.btn-scan:hover { background: #d9ff50; transform: translateY(-1px); }
.btn-scan:active { transform: translateY(0); }
.scan-meta { color: var(--muted); font-size: .78rem; }
/* Results table */
.results-grid { display: flex; flex-direction: column; gap: .6rem; }
.result-card { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 1.1rem 1.3rem; display: grid; grid-template-columns: 36px 1fr auto; gap: .75rem; align-items: start; transition: border-color .2s; }
.result-card:hover { border-color: #3a3d45; }
.result-card.gruen { border-left: 3px solid var(--gruen); }
.result-card.gelb { border-left: 3px solid var(--gelb); }
.result-card.rot { border-left: 3px solid var(--rot); }
.result-card.unbekannt { border-left: 3px solid var(--muted); }
.ampel { width: 28px; height: 28px; border-radius: 50%; flex-shrink: 0; margin-top: 2px; }
.ampel.gruen { background: var(--gruen); box-shadow: 0 0 12px rgba(74,240,196,.4); }
.ampel.gelb { background: var(--gelb); box-shadow: 0 0 12px rgba(245,200,66,.4); }
.ampel.rot { background: var(--rot); box-shadow: 0 0 12px rgba(240,90,90,.4); }
.ampel.unbekannt { background: var(--muted); }
.result-body {}
.result-title { font-family: var(--font-head); font-weight: 700; font-size: 1rem; color: var(--text); margin-bottom: .25rem; }
.result-title a { color: inherit; text-decoration: none; }
.result-title a:hover { color: var(--accent); }
.result-meta { color: var(--muted); font-size: .75rem; margin-bottom: .4rem; }
.result-verdict { color: var(--text); font-size: .82rem; margin-bottom: .25rem; }
.result-summary { color: var(--muted); font-size: .78rem; line-height: 1.5; }
.result-actions { display: flex; flex-direction: column; gap: .4rem; align-items: flex-end; }
.tag { font-size: .7rem; padding: .2rem .55rem; border-radius: 3px; font-family: var(--font-mono); }
.tag.gruen { background: rgba(74,240,196,.12); color: var(--gruen); }
.tag.gelb { background: rgba(245,200,66,.12); color: var(--gelb); }
.tag.rot { background: rgba(240,90,90,.12); color: var(--rot); }
.tag.muted { background: rgba(107,114,128,.12); color: var(--muted); }
.btn-del { background: none; border: none; color: var(--muted); cursor: pointer; font-size: .75rem; padding: .3rem .5rem; }
.btn-del:hover { color: var(--rot); }
/* Empty state */
.empty { text-align: center; padding: 4rem 2rem; color: var(--muted); }
.empty-icon { font-size: 3rem; margin-bottom: 1rem; opacity: .4; }
.empty h3 { font-family: var(--font-head); color: var(--text); margin-bottom: .5rem; }
/* Sources */
.sources-grid { display: flex; flex-direction: column; gap: .6rem; margin-bottom: 2rem; }
.source-card { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: .9rem 1.2rem; display: flex; align-items: center; justify-content: space-between; gap: 1rem; }
.source-info { flex: 1; min-width: 0; }
.source-name { font-family: var(--font-head); font-weight: 600; font-size: .9rem; }
.source-url { color: var(--muted); font-size: .72rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 500px; }
.source-actions { display: flex; gap: .5rem; align-items: center; flex-shrink: 0; }
.status-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.status-dot.on { background: var(--gruen); }
.status-dot.off { background: var(--muted); }
.btn-sm { background: none; border: 1px solid var(--border); color: var(--muted); padding: .3rem .7rem; cursor: pointer; font-family: var(--font-mono); font-size: .72rem; border-radius: 4px; transition: all .15s; }
.btn-sm:hover { border-color: var(--accent2); color: var(--accent2); }
.btn-sm.danger:hover { border-color: var(--rot); color: var(--rot); }
/* Forms */
.form-section { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 1.5rem; margin-bottom: 1.5rem; }
.form-section h3 { font-family: var(--font-head); font-weight: 700; margin-bottom: 1.2rem; font-size: 1rem; color: var(--accent); }
.form-row { display: flex; gap: 1rem; margin-bottom: 1rem; flex-wrap: wrap; }
.form-group { flex: 1; min-width: 200px; }
label { display: block; font-size: .75rem; color: var(--muted); margin-bottom: .4rem; text-transform: uppercase; letter-spacing: .05em; }
input[type=text], input[type=password] { width: 100%; background: var(--bg); border: 1px solid var(--border); color: var(--text); padding: .6rem .9rem; font-family: var(--font-mono); font-size: .85rem; border-radius: 5px; outline: none; transition: border-color .2s; }
input:focus { border-color: var(--accent2); }
.btn-save { background: var(--accent2); color: #0e0f11; border: none; padding: .6rem 1.5rem; font-family: var(--font-head); font-weight: 700; font-size: .85rem; cursor: pointer; border-radius: 5px; transition: all .2s; }
.btn-save:hover { background: #6df5d5; }
/* Scan log */
.scan-log { background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 1rem; margin-top: 1.5rem; font-size: .78rem; color: var(--muted); line-height: 1.8; }
.scan-log-title { color: var(--accent); font-size: .75rem; text-transform: uppercase; letter-spacing: .05em; margin-bottom: .5rem; }
/* Badge */
.badge { background: var(--accent); color: #0e0f11; font-size: .65rem; padding: .15rem .45rem; border-radius: 3px; font-weight: 700; margin-left: .5rem; }
/* Stats bar */
.stats-bar { display: flex; gap: 1.5rem; margin-bottom: 1.5rem; flex-wrap: wrap; }
.stat { display: flex; align-items: center; gap: .5rem; font-size: .8rem; }
.stat-dot { width: 10px; height: 10px; border-radius: 50%; }
/* Hint box */
.hint { background: #1a1f2e; border: 1px solid #2a3a5e; border-radius: 6px; padding: 1rem 1.2rem; font-size: .8rem; color: #8899cc; line-height: 1.6; margin-bottom: 1.5rem; }
.hint strong { color: var(--accent2); }
</style>
</head>
<body>
<div class="header">
<div class="logo">Job<span>Scout</span></div>
<form method="post"><button class="logout-btn" name="logout" value="1">Abmelden</button></form>
</div>
<nav class="nav">
<a href="?tab=results" class="<?= $tab==='results'?'active':'' ?>">Ergebnisse<?php $c=count($results); if($c>0) echo "<span class='badge'>$c</span>"; ?></a>
<a href="?tab=sources" class="<?= $tab==='sources'?'active':'' ?>">Quellen<?php $sc=count($sources); if($sc>0) echo "<span class='badge'>$sc</span>"; ?></a>
<a href="?tab=settings" class="<?= $tab==='settings'?'active':'' ?>">Einstellungen</a>
</nav>
<div class="main">
<?php if ($message): ?>
<div class="msg"><?= htmlspecialchars($message) ?></div>
<?php endif; ?>
<?php if ($tab === 'results'): ?>
<div class="scan-bar">
<form method="post">
<button class="btn-scan" name="start_scan" value="1">▶ Jetzt suchen</button>
</form>
<div class="scan-meta"><?= count($sources) ?> Quellen · <?= count(array_filter($sources, fn($s)=>$s['active'])) ?> aktiv</div>
</div>
<?php if (!empty($scanLog)): ?>
<div class="scan-log">
<div class="scan-log-title">Scan-Protokoll</div>
<?php foreach ($scanLog as $line) echo htmlspecialchars($line) . "<br>"; ?>
</div>
<?php endif; ?>
<?php if (!empty($results)): ?>
<?php
$g = count(array_filter($results, fn($r)=>$r['rating']==='gruen'));
$y = count(array_filter($results, fn($r)=>$r['rating']==='gelb'));
$r = count(array_filter($results, fn($r)=>$r['rating']==='rot'));
?>
<div class="stats-bar">
<div class="stat"><div class="stat-dot" style="background:var(--gruen)"></div><?= $g ?> Gut passend</div>
<div class="stat"><div class="stat-dot" style="background:var(--gelb)"></div><?= $y ?> Mit Einschränkungen</div>
<div class="stat"><div class="stat-dot" style="background:var(--rot)"></div><?= $r ?> Nicht empfohlen</div>
<div style="margin-left:auto">
<form method="post" onsubmit="return confirm('Wirklich alle löschen?')">
<button class="btn-sm danger" name="clear_results" value="1">Alle löschen</button>
</form>
</div>
</div>
<div class="results-grid">
<?php foreach ($results as $result): ?>
<?php $r = $result['rating'] ?? 'unbekannt'; ?>
<div class="result-card <?= htmlspecialchars($r) ?>">
<div class="ampel <?= htmlspecialchars($r) ?>"></div>
<div class="result-body">
<div class="result-title"><a href="<?= htmlspecialchars($result['url']) ?>" target="_blank"><?= htmlspecialchars($result['title']) ?></a></div>
<div class="result-meta"><?= htmlspecialchars($result['company'] ?: '—') ?> · <?= htmlspecialchars($result['location'] ?: '—') ?> · <?= htmlspecialchars($result['source']) ?> · <?= htmlspecialchars($result['found_at']) ?></div>
<?php if ($result['verdict']): ?><div class="result-verdict"><?= htmlspecialchars($result['verdict']) ?></div><?php endif; ?>
<?php if ($result['summary']): ?><div class="result-summary"><?= htmlspecialchars($result['summary']) ?></div><?php endif; ?>
</div>
<div class="result-actions">
<span class="tag <?= htmlspecialchars($r) ?>"><?= $r==='gruen'?'✓ Gut':($r==='gelb'?'~ Prüfen':($r==='rot'?'✗ Nein':'?')) ?></span>
<form method="post">
<input type="hidden" name="delete_result" value="<?= htmlspecialchars($result['id']) ?>">
<button class="btn-del" title="Löschen">✕</button>
</form>
</div>
</div>
<?php endforeach; ?>
</div>
<?php else: ?>
<div class="empty">
<div class="empty-icon">◎</div>
<h3>Noch keine Ergebnisse</h3>
<p>Quellen einrichten und auf „Jetzt suchen" klicken.</p>
</div>
<?php endif; ?>
<?php elseif ($tab === 'sources'): ?>
<div class="hint">
<strong>RSS-Feed URLs</strong> der Stellenbörsen hier eintragen. Die meisten Jobportale bieten RSS-Feeds für Suchergebnisse an — die URL bekommst du, indem du eine Suche startest und dann auf „RSS" oder „Feed abonnieren" klickst. <strong>Tipps für gute Quellen:</strong> Arbeitsagentur (jobsuche.arbeitsagentur.de → RSS-Symbol), Stepstone, Indeed, sowie Karriereseiten direkt bei Unternehmen.
</div>
<?php if (!empty($sources)): ?>
<div class="sources-grid">
<?php foreach ($sources as $source): ?>
<div class="source-card">
<div class="status-dot <?= $source['active'] ? 'on' : 'off' ?>"></div>
<div class="source-info">
<div class="source-name"><?= htmlspecialchars($source['name']) ?></div>
<div class="source-url"><?= htmlspecialchars($source['url']) ?></div>
</div>
<div class="source-actions">
<form method="post">
<input type="hidden" name="toggle_source" value="<?= htmlspecialchars($source['id']) ?>">
<button class="btn-sm"><?= $source['active'] ? 'Deaktivieren' : 'Aktivieren' ?></button>
</form>
<form method="post" onsubmit="return confirm('Quelle löschen?')">
<input type="hidden" name="delete_source" value="<?= htmlspecialchars($source['id']) ?>">
<button class="btn-sm danger">Löschen</button>
</form>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
<div class="form-section">
<h3>Neue Quelle hinzufügen</h3>
<form method="post">
<div class="form-row">
<div class="form-group">
<label>Name der Quelle</label>
<input type="text" name="source_name" placeholder="z.B. Stepstone Marketing BaWü">
</div>
<div class="form-group">
<label>RSS-Feed URL</label>
<input type="text" name="source_url" placeholder="https://www.stepstone.de/...rss...">
</div>
</div>
<button class="btn-save" name="add_source" value="1">Quelle hinzufügen</button>
</form>
</div>
<?php elseif ($tab === 'settings'): ?>
<div class="form-section">
<h3>Claude API Key</h3>
<form method="post">
<div class="form-row">
<div class="form-group">
<label>API Key</label>
<input type="text" name="api_key" value="<?= htmlspecialchars($config['api_key'] ?? '') ?>" placeholder="sk-ant-...">
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Suchbegriffe (kommagetrennt)</label>
<input type="text" name="keywords" value="<?= htmlspecialchars($config['keywords'] ?? '') ?>" placeholder="Marketing Manager, Head of Marketing">
</div>
<div class="form-group">
<label>Standorte (kommagetrennt)</label>
<input type="text" name="location" value="<?= htmlspecialchars($config['location'] ?? '') ?>" placeholder="Karlsruhe, Pforzheim, Heilbronn">
</div>
</div>
<button class="btn-save" name="save_config" value="1">Einstellungen speichern</button>
</form>
</div>
<div class="hint">
<strong>API Key anlegen:</strong> Gehe auf <strong>console.anthropic.com</strong> → Account anlegen → „API Keys" → „Create Key". Den Key einmalig hier eintragen, er wird lokal auf deinem Server gespeichert. Kosten: ca. 0,01–0,05 € pro bewerteter Stelle (sehr günstig).<br><br>
<strong>Passwort ändern:</strong> Öffne die Datei <code>index.php</code> und ändere in Zeile 6 den Wert <code>changeme123</code> auf ein eigenes Passwort.
</div>
<?php endif; ?>
</div>
</body>
</html>
<?php
function showLogin($error = null) {
?>
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JobScout · Login</title>
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@700;800&family=DM+Mono&display=swap" rel="stylesheet">
<style>
:root { --bg:#0e0f11; --surface:#16181c; --border:#2a2d35; --accent:#c8f135; --text:#e8eaf0; --muted:#6b7280; }
* { box-sizing: border-box; margin: 0; padding: 0; }
body { background: var(--bg); color: var(--text); font-family: 'DM Mono', monospace; min-height: 100vh; display: flex; align-items: center; justify-content: center; }
.box { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 2.5rem; width: 360px; }
.logo { font-family: 'Syne', sans-serif; font-weight: 800; font-size: 1.8rem; color: var(--accent); margin-bottom: .4rem; }
.sub { color: var(--muted); font-size: .8rem; margin-bottom: 2rem; }
label { display: block; font-size: .75rem; color: var(--muted); text-transform: uppercase; letter-spacing: .05em; margin-bottom: .4rem; }
input[type=password] { width: 100%; background: var(--bg); border: 1px solid var(--border); color: var(--text); padding: .7rem 1rem; font-family: 'DM Mono', monospace; font-size: .9rem; border-radius: 6px; outline: none; margin-bottom: 1rem; }
input:focus { border-color: var(--accent); }
button { width: 100%; background: var(--accent); color: #0e0f11; border: none; padding: .75rem; font-family: 'Syne', sans-serif; font-weight: 700; font-size: 1rem; cursor: pointer; border-radius: 6px; }
.error { color: #f05a5a; font-size: .8rem; margin-bottom: 1rem; }
</style>
</head>
<body>
<div class="box">
<div class="logo">JobScout</div>
<div class="sub">Dein persönliches Stellenradar</div>
<form method="post">
<?php if ($error): ?><div class="error"><?= htmlspecialchars($error) ?></div><?php endif; ?>
<label>Passwort</label>
<input type="password" name="password" autofocus>
<button type="submit">Einloggen</button>
</form>
</div>
</body>
</html>
<?php
}
?>