Files
SHK-vorlage/SHK-Scorecard.dc.html
2026-08-17 20:59:53 +02:00

288 lines
17 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="crossorigin" />
<link href="https://fonts.googleapis.com/css2?family=Source+Sans+3:wght@400;600;700&display=swap" rel="stylesheet" />
<style>
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; background: #F4F6F8; color: #1A1D21; font-family: 'Source Sans 3', 'Segoe UI', system-ui, sans-serif; font-size: 17px; line-height: 1.55; -webkit-font-smoothing: antialiased; }
a { color: #0B4F6C; text-decoration: underline; text-underline-offset: 3px; }
a:hover { color: #08384D; }
:focus-visible { outline: 3px solid #C9451C; outline-offset: 2px; }
p { text-wrap: pretty; }
h1, h2, h3 { text-wrap: balance; }
</style>
</helmet>
<div style="background:#F4F6F8;min-height:100vh">
<header style="background:#0B4F6C;color:#FFFFFF;padding:clamp(24px,3.5vw,44px) clamp(16px,3vw,32px)">
<div style="max-width:1280px;margin:0 auto">
<p style="margin:0 0 10px;font-size:14px;letter-spacing:.12em;text-transform:uppercase;color:#9FC0CF;font-weight:700">Akquise-Werkzeug · SHK-Website-Analyse</p>
<h1 style="margin:0 0 12px;font-size:clamp(1.75rem,3.4vw,2.5rem);line-height:1.14;font-weight:700;letter-spacing:-0.02em">Scorecard: 40 Kriterien, 80 Punkte</h1>
<p style="margin:0 0 22px;font-size:18px;color:#C6DAE4;max-width:52em">Für das Erstgespräch: Bestandsseite des Interessenten durchgehen, Punkte vergeben, Ergebnis zeigen. Der Branchenschnitt liegt bei 24 Prozent — die meisten Gespräche verkaufen sich hier von selbst.</p>
<div style="display:flex;gap:12px;flex-wrap:wrap;align-items:flex-end">
<label style="display:flex;flex-direction:column;gap:5px;font-size:14px;color:#9FC0CF;font-weight:600">Geprüfte Website
<input type="text" value="{{ domain }}" onChange="{{ setzeDomain }}" placeholder="beispiel-shk.de" style="padding:10px 13px;border:1px solid rgba(255,255,255,.3);border-radius:3px;background:rgba(255,255,255,.12);color:#FFFFFF;font-size:17px;font-weight:600;min-width:240px" />
</label>
<label style="display:flex;flex-direction:column;gap:5px;font-size:14px;color:#9FC0CF;font-weight:600">Geprüft am
<input type="text" value="{{ datum }}" onChange="{{ setzeDatum }}" placeholder="16.08.2026" style="padding:10px 13px;border:1px solid rgba(255,255,255,.3);border-radius:3px;background:rgba(255,255,255,.12);color:#FFFFFF;font-size:17px;font-weight:600;min-width:150px" />
</label>
<button type="button" onClick="{{ zuruecksetzen }}" style="padding:11px 20px;border-radius:3px;border:1.5px solid rgba(255,255,255,.5);background:none;color:#FFFFFF;font-size:16px;font-weight:600;cursor:pointer" style-hover="background:rgba(255,255,255,.14)">Alles zurücksetzen</button>
</div>
</div>
</header>
<div style="max-width:1280px;margin:0 auto;padding:clamp(24px,3vw,40px) clamp(16px,3vw,32px) clamp(40px,5vw,72px);display:grid;grid-template-columns:repeat(auto-fit,minmax(340px,1fr));gap:clamp(20px,3vw,32px);align-items:start">
<div style="display:flex;flex-direction:column;gap:20px;min-width:0">
<sc-for list="{{ bloecke }}" as="b" hint-placeholder-count="6">
<section style="background:#FFFFFF;border:1px solid #DDE2E7;border-radius:5px;overflow:hidden">
<div style="padding:16px 20px;background:#F4F6F8;border-bottom:1px solid #DDE2E7;display:flex;justify-content:space-between;gap:16px;align-items:baseline;flex-wrap:wrap">
<div>
<p style="margin:0;font-size:19px;font-weight:700">{{ b.titel }}</p>
<p style="margin:0;font-size:15px;color:#5A6169">{{ b.unter }}</p>
</div>
<p style="margin:0;font-size:17px;font-weight:700;color:#0B4F6C;font-variant-numeric:tabular-nums;white-space:nowrap">{{ b.stand }}</p>
</div>
<sc-for list="{{ b.zeilen }}" as="z" hint-placeholder-count="8">
<div style="display:flex;gap:16px;align-items:center;justify-content:space-between;padding:12px 20px;border-bottom:1px solid #EDF1F4;flex-wrap:wrap">
<p style="margin:0;font-size:16px;flex:1 1 260px;min-width:0"><strong style="color:#5A6169;font-variant-numeric:tabular-nums">{{ z.id }}</strong>&nbsp;&nbsp;{{ z.text }}</p>
<div style="display:flex;gap:6px;flex:none">
<sc-for list="{{ z.optionen }}" as="o" hint-placeholder-count="3">
<button type="button" onClick="{{ o.waehlen }}" aria-label="{{ o.aria }}" style="{{ o.stil }}">{{ o.label }}</button>
</sc-for>
</div>
</div>
</sc-for>
</section>
</sc-for>
</div>
<aside style="position:sticky;top:20px;display:flex;flex-direction:column;gap:16px">
<div style="background:#FFFFFF;border:1px solid #DDE2E7;border-radius:5px;padding:24px">
<p style="margin:0 0 4px;font-size:14px;letter-spacing:.1em;text-transform:uppercase;color:#5A6169;font-weight:700">Gesamtergebnis</p>
<p style="margin:0 0 2px;font-size:clamp(2.5rem,6vw,3.5rem);font-weight:700;line-height:1;color:#0B4F6C;font-variant-numeric:tabular-nums">{{ gesamt }}</p>
<p style="margin:0 0 18px;font-size:17px;color:#5A6169">von 80 Punkten · {{ prozent }}</p>
<div style="height:10px;background:#EDF1F4;border-radius:100px;overflow:hidden;margin-bottom:8px">
<div style="height:10px;background:{{ bandFarbe }};width:{{ balken }};transition:width .3s ease"></div>
</div>
<p style="margin:0 0 18px;font-size:18px;font-weight:700;color:{{ bandFarbe }}">{{ bandTitel }}</p>
<p style="margin:0;font-size:16px;color:#3D4650">{{ bandText }}</p>
</div>
<div style="background:#FFFFFF;border:1px solid #DDE2E7;border-radius:5px;padding:24px">
<p style="margin:0 0 16px;font-size:14px;letter-spacing:.1em;text-transform:uppercase;color:#5A6169;font-weight:700">Nach Bereich</p>
<div style="display:flex;flex-direction:column;gap:14px">
<sc-for list="{{ bloecke }}" as="b" hint-placeholder-count="6">
<div>
<div style="display:flex;justify-content:space-between;gap:12px;font-size:16px;margin-bottom:5px">
<span style="font-weight:600">{{ b.kurz }}</span>
<span style="font-variant-numeric:tabular-nums;color:#5A6169">{{ b.stand }}</span>
</div>
<div style="height:7px;background:#EDF1F4;border-radius:100px;overflow:hidden">
<div style="height:7px;background:#0B4F6C;width:{{ b.balken }};transition:width .3s ease"></div>
</div>
</div>
</sc-for>
</div>
</div>
<sc-if value="{{ hatKo }}" hint-placeholder-val="{{ false }}">
<div style="background:#FBF0F0;border:1px solid #E8B9BD;border-left:4px solid #C1121F;border-radius:0 5px 5px 0;padding:22px">
<p style="margin:0 0 8px;font-size:18px;font-weight:700;color:#B0111D">K.-o.-Punkte im Bereich Recht</p>
<p style="margin:0 0 12px;font-size:16px;color:#3D4650">Diese Punkte sind keine Optimierung, sondern Abmahnrisiko. Sie gehören ins Angebot, egal wie der Rest ausfällt.</p>
<ul style="margin:0;padding-left:20px;display:flex;flex-direction:column;gap:7px;font-size:16px">
<sc-for list="{{ koListe }}" as="k" hint-placeholder-count="3">
<li>{{ k.text }}</li>
</sc-for>
</ul>
</div>
</sc-if>
<sc-if value="{{ hatLuecken }}" hint-placeholder-val="{{ false }}">
<div style="background:#FFFFFF;border:1px solid #DDE2E7;border-radius:5px;padding:24px">
<p style="margin:0 0 6px;font-size:14px;letter-spacing:.1em;text-transform:uppercase;color:#5A6169;font-weight:700">Gesprächsstoff</p>
<p style="margin:0 0 14px;font-size:16px;color:#3D4650">{{ lueckenZahl }} Kriterien stehen auf null. Die ersten fünf sind meist die, bei denen der Interessent im Gespräch selbst nickt.</p>
<ol style="margin:0;padding-left:20px;display:flex;flex-direction:column;gap:8px;font-size:16px">
<sc-for list="{{ luecken }}" as="l" hint-placeholder-count="5">
<li><strong style="color:#5A6169;font-variant-numeric:tabular-nums">{{ l.id }}</strong> {{ l.text }}</li>
</sc-for>
</ol>
</div>
</sc-if>
<p style="margin:0;font-size:15px;color:#5A6169">Eingaben bleiben in diesem Browser gespeichert — Sie können das Fenster während des Termins schließen. <a href="SHK-Startseite.dc.html">Zur Muster-Website</a> · <a href="SHK-Pitch.dc.html">Zum Pitch-Deck</a></p>
</aside>
</div>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props="{}">
class Component extends DCLogic {
state = { p: {}, domain: '', datum: '' };
katalog = [
{ kurz: 'A · Trust', titel: 'A · Erster Eindruck & Trust', unter: 'max. 16 Punkte', zeilen: [
['A1', 'Sterne und Bewertungsanzahl oberhalb der Falzkante sichtbar'],
['A2', 'Gesicht: Inhaber- oder Teamfoto im oberen Seitendrittel'],
['A3', 'Headline nennt Nutzen und Ort, nicht nur den Firmennamen'],
['A4', 'Echte eigene Fotos statt Stock — Baustelle, Team, Fahrzeug'],
['A5', 'Mindestens drei namentliche Kundenstimmen mit Ort'],
['A6', 'Siegel und Zertifikate mit Erklärung, was sie bedeuten'],
['A7', 'Konkrete Zahlen statt Adjektive: Projekte, Jahre, Reaktionszeit'],
['A8', 'Externe Validierung: Award, Presse, prüfbare Registriernummer']
] },
{ kurz: 'B · Conversion', titel: 'B · Erreichbarkeit & Conversion', unter: 'max. 16 Punkte', zeilen: [
['B1', 'Telefonnummer als tel:-Link, sticky auf Mobil'],
['B2', 'Öffnungszeiten sichtbar, idealerweise mit Live-Status'],
['B3', 'Notdienst-Kanal getrennt und prominent, mit ehrlichen Zeiten'],
['B4', 'WhatsApp-Kanal vorhanden'],
['B5', 'Rückrufformular mit Feld „Wann sind Sie erreichbar?"'],
['B6', 'Getrennte Anfragestrecken je Gewerk statt Universalformular'],
['B7', 'Mehrstufiger Konfigurator oder Rechner als eigener CTA'],
['B8', 'Echte Online-Terminbuchung — Kalender, nicht Formular']
] },
{ kurz: 'C · Inhalt', titel: 'C · Inhalt & Struktur', unter: 'max. 16 Punkte', zeilen: [
['C1', 'Eigene URL je Leistung, nicht nur Startseiten-Sektionen'],
['C2', 'Navigation in Laiensprache, höchstens sieben Hauptpunkte'],
['C3', 'Wärmepumpe als eigenes Themencluster inklusive Förderung'],
['C4', 'Eigene Förderseite mit Standdatum und Disclaimer'],
['C5', 'Referenzen mit Vorher/Nachher'],
['C6', 'FAQ, das echte Kundenfragen beantwortet'],
['C7', 'Kosten- oder Preisorientierung in irgendeiner Form vorhanden'],
['C8', 'Standortseiten mit ortsspezifischer Substanz']
] },
{ kurz: 'D · Karriere', titel: 'D · Karriere', unter: 'max. 8 Punkte', zeilen: [
['D1', 'Karriere im Hauptmenü'],
['D2', '„Ausbildung" getrennt von „Jobs", in eigener Ansprache'],
['D3', 'Konkrete offene Stellen statt „Initiativbewerbung willkommen"'],
['D4', 'Quick-Bewerbung: höchstens vier Felder, keine Anschreiben-Pflicht']
] },
{ kurz: 'E · Technik', titel: 'E · Technik & Auffindbarkeit', unter: 'max. 12 Punkte', zeilen: [
['E1', 'Core Web Vitals mobil grün: LCP ≤ 2,5 s · INP ≤ 200 ms · CLS ≤ 0,1'],
['E2', 'Alle Assets lokal gehostet — Fonts, Icons, JavaScript'],
['E3', 'Schema Plumber + HVACBusiness, Service je Leistung, BreadcrumbList'],
['E4', 'Google-Unternehmensprofil vollständig, NAP identisch zur Website'],
['E5', 'Title und H1 mit Leistung und Ort auf jeder Leistungsseite'],
['E6', 'Barrierefreiheits-Baseline: Kontrast, Tastatur, Alt-Texte, Fokus']
] },
{ kurz: 'F · Recht', titel: 'F · Recht — jedes Kriterium ist K.o.', unter: 'max. 12 Punkte', ko: true, zeilen: [
['F1', 'OS-Plattform-Link entfernt (Pflicht seit 20.07.2025)'],
['F2', 'Impressum vollständig inkl. HWK, Berufsbezeichnung, HwO-Link, USt-IdNr.'],
['F3', 'Consent-Banner mit gleichwertigem Ablehnen-Button auf erster Ebene'],
['F4', 'Kein einwilligungspflichtiges Skript vor der Einwilligung'],
['F5', 'Bewertungen mit Hinweis zur Echtheitsprüfung (§ 5b Abs. 3 UWG)'],
['F6', 'Preise brutto inkl. MwSt., Förderaussagen mit Disclaimer und Standdatum']
] }
];
stufen = [
{ w: 0, label: '0', aria: 'fehlt', farbe: '#C1121F' },
{ w: 1, label: '1', aria: 'teilweise', farbe: '#A9760F' },
{ w: 2, label: '2', aria: 'gut gelöst', farbe: '#0F6B33' }
];
componentDidMount() {
try {
const roh = window.localStorage.getItem('shk-scorecard-v1');
if (roh) this.setState(JSON.parse(roh));
} catch (e) { /* kein Speicher verfügbar */ }
}
sichern(next) {
try { window.localStorage.setItem('shk-scorecard-v1', JSON.stringify(next)); } catch (e) { /* ignorieren */ }
}
setzen(id, wert) {
this.setState(s => {
const p = Object.assign({}, s.p);
if (p[id] === wert) { delete p[id]; } else { p[id] = wert; }
const next = { p: p, domain: s.domain, datum: s.datum };
this.sichern(next);
return { p: p };
});
}
feld(name, wert) {
this.setState(s => {
const next = { p: s.p, domain: s.domain, datum: s.datum };
next[name] = wert;
this.sichern(next);
return { [name]: wert };
});
}
knopf(aktiv, farbe) {
return aktiv
? 'width:40px;height:38px;border-radius:3px;border:1.5px solid ' + farbe + ';background:' + farbe + ';color:#FFFFFF;font-size:16px;font-weight:700;cursor:pointer'
: 'width:40px;height:38px;border-radius:3px;border:1.5px solid #DDE2E7;background:#FFFFFF;color:#8A939C;font-size:16px;font-weight:700;cursor:pointer';
}
renderVals() {
const p = this.state.p;
let gesamt = 0, maxGesamt = 0;
const ko = [], luecken = [];
const bloecke = this.katalog.map(b => {
let punkte = 0;
const max = b.zeilen.length * 2;
const zeilen = b.zeilen.map(z => {
const id = z[0], text = z[1];
const wert = p[id];
if (typeof wert === 'number') punkte += wert;
if (wert === 0) {
if (b.ko) ko.push({ text: id + ' — ' + text });
else luecken.push({ id: id, text: text });
}
return {
id: id, text: text,
optionen: this.stufen.map(st => ({
label: st.label,
aria: id + ': ' + st.aria,
stil: this.knopf(wert === st.w, st.farbe),
waehlen: () => this.setzen(id, st.w)
}))
};
});
gesamt += punkte; maxGesamt += max;
return { kurz: b.kurz, titel: b.titel, unter: b.unter, zeilen: zeilen,
stand: punkte + ' / ' + max, balken: Math.round((punkte / max) * 100) + '%' };
});
let band = { titel: 'Branchendurchschnitt', farbe: '#C1121F', text: 'Hier liegt der Median der 500 im Webseiten-Check Handwerk 2025 getesteten Seiten. Diese Website leistet aktuell nichts, was ein Wettbewerber nicht auch hätte — der Neubau rechnet sich am schnellsten.' };
if (gesamt > 20) band = { titel: 'Solide, aber ohne Wettbewerbsvorteil', farbe: '#A9760F', text: 'Das Handwerkliche stimmt, es fehlt der Grund, warum jemand hier anfragt statt beim Nachbarbetrieb. Meist reichen drei bis fünf gezielte Ergänzungen statt eines Relaunches.' };
if (gesamt > 40) band = { titel: 'Unter den besten 10 % der Nische', farbe: '#0F6B33', text: 'Diese Seite ist besser als fast alles im Wettbewerbsumfeld. Sinnvoll sind jetzt Conversion-Details: Erreichbarkeitslogik, Rechner, Terminbuchung — und die Rechtspunkte.' };
if (gesamt > 60) band = { titel: 'Zielkorridor der Vorlage', farbe: '#0F6B33', text: 'Sehr gut. Ab hier bringt Wartung mehr als Umbau: Fördersätze, Zertifikatslaufzeiten, Stellenanzeigen und Bewertungsakquise aktuell halten.' };
return {
bloecke: bloecke,
gesamt: gesamt,
prozent: Math.round((gesamt / maxGesamt) * 100) + ' %',
balken: Math.round((gesamt / maxGesamt) * 100) + '%',
bandTitel: band.titel, bandFarbe: band.farbe, bandText: band.text,
hatKo: ko.length > 0, koListe: ko,
hatLuecken: luecken.length > 0,
lueckenZahl: luecken.length + ko.length,
luecken: luecken.slice(0, 5),
domain: this.state.domain,
datum: this.state.datum,
setzeDomain: e => this.feld('domain', e.target.value),
setzeDatum: e => this.feld('datum', e.target.value),
zuruecksetzen: () => { this.sichern({ p: {}, domain: '', datum: '' }); this.setState({ p: {}, domain: '', datum: '' }); }
};
}
}
</script>
</body>
</html>