feat: apply Funktechnik Schubert branding

This commit is contained in:
Schubert Ferenc 2026-07-03 21:08:04 +02:00
parent 9a648e2ec8
commit 99e8201745
17 changed files with 335 additions and 104 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View file

@ -10,10 +10,11 @@ export async function POST(request: Request) {
const email = form.get("email"); const email = form.get("email");
const manufacturer = form.get("manufacturer"); const manufacturer = form.get("manufacturer");
const model = form.get("model"); const model = form.get("model");
const deviceType = form.get("deviceType");
const description = form.get("description"); const description = form.get("description");
const privacy = form.get("privacy"); const privacy = form.get("privacy");
if (!required(name) || !required(email) || !required(manufacturer) || !required(model) || !required(description) || privacy !== "on") { if (!required(name) || !required(email) || !required(manufacturer) || !required(model) || !required(deviceType) || !required(description) || privacy !== "on") {
return NextResponse.json({ message: "Bitte füllen Sie alle Pflichtfelder aus." }, { status: 400 }); return NextResponse.json({ message: "Bitte füllen Sie alle Pflichtfelder aus." }, { status: 400 });
} }
@ -24,8 +25,10 @@ export async function POST(request: Request) {
hasIntakeIntegration: Boolean(intakeUrl && hasIntegrationToken), hasIntakeIntegration: Boolean(intakeUrl && hasIntegrationToken),
manufacturer: String(manufacturer), manufacturer: String(manufacturer),
model: String(model), model: String(model),
deviceType: String(deviceType),
hasPhone: required(form.get("phone")), hasPhone: required(form.get("phone")),
hasSerialNumber: required(form.get("serialNumber")), hasSerialNumber: required(form.get("serialNumber")),
hasAccessories: required(form.get("accessories")),
}); });
return NextResponse.json({ return NextResponse.json({

View file

@ -9,7 +9,7 @@ export default function RadioServicePage() {
return ( return (
<> <>
<PageHero eyebrow="Funkgeräte-Service" title="Prüfung, Diagnose und Abgleich"> <PageHero eyebrow="Funkgeräte-Service" title="Prüfung, Diagnose und Abgleich">
Funkgeräte zeigen Fehler oft erst im Zusammenspiel von Empfang, Sendeteil, Versorgung, Antennenanpassung und Bedienung. Der Service betrachtet diese Zusammenhänge strukturiert. Funkgeräte zeigen Fehler oft erst im Zusammenspiel von Empfang, Sendeteil, Versorgung, Antennenanpassung, Bedienung und Abgleich. Der Service betrachtet diese Zusammenhänge strukturiert.
</PageHero> </PageHero>
<section className="section"> <section className="section">
<div className="container split"> <div className="container split">
@ -19,13 +19,14 @@ export default function RadioServicePage() {
<li>Kein oder schwacher Empfang</li> <li>Kein oder schwacher Empfang</li>
<li>Keine, leise oder verzerrte Modulation</li> <li>Keine, leise oder verzerrte Modulation</li>
<li>Frequenzversatz, PLL-Rasten oder instabile Kanäle</li> <li>Frequenzversatz, PLL-Rasten oder instabile Kanäle</li>
<li>Aussetzer durch Stromversorgung, Buchsen oder Bedienelemente</li> <li>Sendeprobleme, schwankende Leistung oder auffällige Erwärmung</li>
<li>Unklare Vorarbeiten oder verbastelte Geräte</li> <li>Audio-/NF-Probleme, Displayfehler oder Aussetzer durch Bedienelemente</li>
<li>Unklare Vorarbeiten, fehlende Serviceunterlagen oder bereits geöffnete Geräte</li>
</ul> </ul>
</div> </div>
<div className="panel"> <div className="panel">
<h2>Serviceablauf</h2> <h2>Messung und Einordnung</h2>
<p className="lead">Eine gute Reparatur beginnt mit vollständigen Angaben zu Gerät, Fehlerbild und Vorgeschichte. Danach folgt eine technische Einschätzung und die weitere Abstimmung.</p> <p className="lead">Eine gute Reparatur beginnt mit vollständigen Angaben zu Gerät, Fehlerbild und Vorgeschichte. Danach folgen technische Einschätzung, Prüfung mit geeigneter Messtechnik und die weitere Abstimmung.</p>
<Link className="button" href="/reparatur">Reparatur anfragen</Link> <Link className="button" href="/reparatur">Reparatur anfragen</Link>
</div> </div>
</div> </div>

View file

@ -1,15 +1,16 @@
:root { :root {
--bg: #f6f8fb; --bg: #f4f6f8;
--surface: #ffffff; --surface: #ffffff;
--ink: #101827; --ink: #101722;
--muted: #5b677a; --muted: #566270;
--line: #d9e1ec; --line: #d9dee6;
--navy: #0b1728; --navy: #082a60;
--navy-2: #13243a; --navy-2: #0e3a78;
--cyan: #16b6d9; --steel: #666d76;
--cyan: #1e9fb8;
--orange: #f28c28; --orange: #f28c28;
--radius: 8px; --radius: 8px;
--shadow: 0 20px 50px rgba(10, 23, 40, 0.12); --shadow: 0 18px 44px rgba(8, 42, 96, 0.12);
} }
* { * {
@ -49,77 +50,102 @@ textarea {
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 20; z-index: 20;
border-bottom: 1px solid rgba(255, 255, 255, 0.1); border-bottom: 1px solid rgba(8, 42, 96, 0.12);
background: rgba(11, 23, 40, 0.96); background: rgba(255, 255, 255, 0.96);
color: #fff; color: var(--ink);
backdrop-filter: blur(14px); box-shadow: 0 8px 24px rgba(8, 42, 96, 0.06);
backdrop-filter: blur(12px);
} }
.header-inner { .header-inner {
display: flex; display: flex;
min-height: 72px; min-height: 78px;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
gap: 24px; gap: 18px;
} }
.brand { .brand {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 12px; flex: 0 0 auto;
font-weight: 800;
} }
.brand-mark { .brand-logo {
display: grid; width: 286px;
width: 38px; height: 68px;
height: 38px; object-fit: cover;
place-items: center; object-position: center;
border: 1px solid rgba(22, 182, 217, 0.45); display: block;
border-radius: 8px;
background: linear-gradient(135deg, rgba(22, 182, 217, 0.22), rgba(242, 140, 40, 0.18));
}
.brand-mark::before {
width: 16px;
height: 16px;
border: 3px solid var(--cyan);
border-left-color: transparent;
border-radius: 999px;
content: "";
} }
.nav { .nav {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
align-items: center; align-items: center;
justify-content: flex-end; justify-content: center;
flex: 1 1 auto;
gap: 6px; gap: 6px;
} }
.nav a { .nav a {
border-radius: 8px; border-radius: 8px;
padding: 9px 11px; padding: 9px 11px;
color: rgba(255, 255, 255, 0.82); color: var(--muted);
font-size: 14px; font-size: 14px;
font-weight: 700;
} }
.nav a:hover { .nav a:hover {
background: rgba(255, 255, 255, 0.08); background: rgba(8, 42, 96, 0.07);
color: var(--navy);
}
.nav a[aria-current="page"] {
background: rgba(8, 42, 96, 0.1);
color: var(--navy);
}
.header-cta {
display: inline-flex;
min-height: 40px;
align-items: center;
justify-content: center;
border-radius: 8px;
background: var(--navy);
color: #fff; color: #fff;
font-size: 14px;
font-weight: 800;
padding: 0 14px;
white-space: nowrap;
}
.header-cta:hover {
background: var(--navy-2);
}
.menu-toggle {
display: none;
min-height: 40px;
border: 1px solid var(--line);
border-radius: 8px;
background: #fff;
color: var(--navy);
font-weight: 800;
padding: 0 12px;
} }
.hero { .hero {
position: relative; position: relative;
overflow: hidden; overflow: hidden;
min-height: calc(100vh - 72px); min-height: calc(100vh - 78px);
display: grid; display: grid;
align-items: center; align-items: center;
color: #fff; color: #fff;
background: background:
linear-gradient(90deg, rgba(8, 18, 32, 0.94), rgba(8, 18, 32, 0.72), rgba(8, 18, 32, 0.58)), linear-gradient(90deg, rgba(6, 24, 52, 0.96), rgba(8, 42, 96, 0.78), rgba(8, 42, 96, 0.58)),
url("/workbench-signal.svg"), url("/workbench-signal.svg"),
linear-gradient(135deg, #0b1728, #17324e); linear-gradient(135deg, #07172d, #0b3778);
background-position: center; background-position: center;
background-size: cover; background-size: cover;
} }
@ -174,6 +200,30 @@ h3 {
font-size: 21px; font-size: 21px;
} }
.hero-copy {
max-width: 720px;
color: rgba(255, 255, 255, 0.78);
font-size: 17px;
line-height: 1.65;
}
.hero-badges {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 24px;
}
.hero-badges span {
border: 1px solid rgba(255, 255, 255, 0.18);
border-radius: 999px;
background: rgba(255, 255, 255, 0.08);
color: rgba(255, 255, 255, 0.86);
font-size: 13px;
font-weight: 800;
padding: 7px 10px;
}
.hero-actions, .hero-actions,
.actions { .actions {
display: flex; display: flex;
@ -190,21 +240,29 @@ h3 {
border: 1px solid transparent; border: 1px solid transparent;
border-radius: 8px; border-radius: 8px;
padding: 0 18px; padding: 0 18px;
background: var(--cyan); background: var(--navy);
color: #06111d; color: #fff;
font-weight: 800; font-weight: 800;
} }
.button:hover {
background: var(--navy-2);
}
.button.secondary { .button.secondary {
border-color: rgba(255, 255, 255, 0.22); border-color: rgba(255, 255, 255, 0.36);
background: rgba(255, 255, 255, 0.08); background: rgba(255, 255, 255, 0.1);
color: #fff; color: #fff;
} }
.button.light { .button.light {
border-color: var(--line); border-color: var(--navy);
background: #fff; background: #fff;
color: var(--ink); color: var(--navy);
}
.button.light:hover {
background: #eef3f8;
} }
.section { .section {
@ -244,12 +302,26 @@ h3 {
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
} }
.service-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.card { .card {
border: 1px solid var(--line); border: 1px solid var(--line);
border-radius: var(--radius); border-radius: var(--radius);
background: var(--surface); background: var(--surface);
padding: 24px; padding: 24px;
box-shadow: 0 8px 26px rgba(12, 28, 48, 0.05); box-shadow: 0 10px 26px rgba(8, 42, 96, 0.06);
}
.card h3::before {
display: block;
width: 34px;
height: 3px;
margin-bottom: 14px;
border-radius: 999px;
background: var(--orange);
content: "";
} }
.dark .card { .dark .card {
@ -366,6 +438,20 @@ h3 {
color: #fff; color: #fff;
} }
.footer-logo {
display: block;
width: 220px;
height: 52px;
margin-bottom: 14px;
object-fit: cover;
object-position: center;
}
.copyright {
color: rgba(255, 255, 255, 0.58);
font-size: 14px;
}
.legal { .legal {
max-width: 860px; max-width: 860px;
} }
@ -375,16 +461,71 @@ h3 {
font-size: 26px; font-size: 26px;
} }
@media (max-width: 860px) { @media (max-width: 1080px) and (min-width: 861px) {
.header-inner, .brand-logo {
.nav { width: 236px;
justify-content: flex-start; height: 56px;
} }
.header-inner { .header-inner {
gap: 12px;
}
.nav a {
padding: 8px;
font-size: 13px;
}
.header-cta {
padding: 0 12px;
}
}
@media (max-width: 860px) {
.header-inner {
min-height: 68px;
flex-wrap: wrap;
gap: 10px;
padding: 10px 0;
}
.brand {
min-width: 0;
}
.brand-logo {
width: 194px;
height: 46px;
}
.menu-toggle {
display: inline-flex;
align-items: center;
margin-left: auto;
}
.header-cta {
min-height: 38px;
order: 3;
width: 100%;
}
.nav {
display: none;
order: 4;
width: 100%;
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: stretch;
padding: 14px 0; border-top: 1px solid var(--line);
padding-top: 10px;
}
.nav.is-open {
display: flex;
}
.nav a {
padding: 11px 12px;
} }
.hero { .hero {
@ -397,6 +538,7 @@ h3 {
.grid.three, .grid.three,
.grid.two, .grid.two,
.service-grid,
.split, .split,
.footer-grid { .footer-grid {
grid-template-columns: 1fr; grid-template-columns: 1fr;

View file

@ -6,7 +6,7 @@ import { createMetadata } from "./seo";
export const metadata: Metadata = createMetadata( export const metadata: Metadata = createMetadata(
"Funktechnik Schubert", "Funktechnik Schubert",
"Service, Reparatur und technische Unterstützung für Funkgeräte und Kommunikationstechnik.", "Service, Reparatur, Diagnose und Abgleich von Funktechnik, CB-Funk, Amateurfunk und Kommunikationstechnik.",
); );
export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {

View file

@ -6,25 +6,21 @@ import { createMetadata } from "../seo";
export const metadata: Metadata = createMetadata("Leistungen", "Funkgeräte-Service, Diagnose, Abgleich und Dokumentation für Kommunikationstechnik.", "/leistungen"); export const metadata: Metadata = createMetadata("Leistungen", "Funkgeräte-Service, Diagnose, Abgleich und Dokumentation für Kommunikationstechnik.", "/leistungen");
const services = [ const services = [
["Funkgeräte-Service", "Prüfung, Wartung und technische Beurteilung von Funkgeräten mit Fokus auf nachvollziehbare Fehlerbilder."], ["Funkgeräte-Service", "Service für CB-Funk, Amateurfunk, Betriebsfunk sowie ältere und moderne Funkgeräte. Dazu gehören Sichtprüfung, Funktionsprüfung und eine technische Einschätzung des Gerätezustands."],
["CB-Funk", "Service für klassische und moderne CB-Funkgeräte, inklusive Empfangs-, Sende- und Modulationsproblemen."], ["Fehlersuche & Reparatur", "Strukturierte Analyse bei fehlender Modulation, Frequenzabweichung, PLL-Problemen, Empfangs- oder Sendeproblemen, Audio-/NF-Fehlern und Anzeige- oder Displayfehlern."],
["Amateurfunk", "Unterstützung bei ausgewählter Amateurfunktechnik, Abgleichfragen und gerätespezifischen Fehlern."], ["Abgleich & Prüfung", "Frequenzabgleich, Hub- und Modulationsprüfung, Sendeleistung, Empfindlichkeit und Funktionsprüfung nach technischen Unterlagen und sinnvoller Werkstattpraxis."],
["Betriebsfunk", "Technische Einschätzung und Prüfung von betriebsnaher Kommunikationstechnik im Rahmen der Werkstattmöglichkeiten."], ["Messtechnik & Diagnose", "Systematische Fehlersuche mit geeigneter Messtechnik wie Oszilloskop, Frequenzzähler, Signalgenerator, RF-Messung und Modulationsmessung."],
["Fehlersuche", "Strukturierte Diagnose bei Aussetzern, Störungen, instabiler Versorgung oder auffälligem Verhalten."], ["Serviceunterlagen", "Sorgfältige Einordnung von Schaltplänen, Service Manuals, Abgleichanleitungen, Dokumentation und Reparaturhinweisen für nachvollziehbare Arbeitsschritte."],
["Modulationsprobleme", "Analyse von leiser, verzerrter oder fehlender Modulation sowie Prüfung relevanter Baugruppen."],
["PLL-/Frequenzprobleme", "Prüfung von Frequenzstabilität, Rastung, Synthesizer- und Referenzproblemen."],
["Abgleich und Prüfung", "Sorgfältiger Abgleich nach technischen Unterlagen und dokumentierte Funktionsprüfung."],
["Dokumentation", "Einbindung von Serviceunterlagen, Schaltplänen und Reparaturhinweisen in den Prüfprozess."],
] as const; ] as const;
export default function ServicesPage() { export default function ServicesPage() {
return ( return (
<> <>
<PageHero eyebrow="Leistungen" title="Service für Funkgeräte und Kommunikationstechnik"> <PageHero eyebrow="Leistungen" title="Service für Funkgeräte und Kommunikationstechnik">
Technische Unterstützung für Geräte, bei denen Diagnose, Erfahrung und saubere Dokumentation zählen. Technische Unterstützung für Geräte, bei denen Diagnose, Messtechnik, Erfahrung und saubere Dokumentation zählen.
</PageHero> </PageHero>
<section className="section"> <section className="section">
<div className="container grid three"> <div className="container grid service-grid">
{services.map(([title, text]) => <ServiceCard key={title} title={title}>{text}</ServiceCard>)} {services.map(([title, text]) => <ServiceCard key={title} title={title}>{text}</ServiceCard>)}
</div> </div>
</section> </section>

View file

@ -6,9 +6,18 @@ export default function HomePage() {
<> <>
<section className="hero"> <section className="hero">
<div className="container hero-content"> <div className="container hero-content">
<p className="eyebrow">Funktechnik, Elektronikservice, Werkstatt</p> <p className="eyebrow">Funktechnik, Messtechnik, Werkstattservice</p>
<h1>Funktechnik Schubert</h1> <h1>Funktechnik Schubert</h1>
<p className="lead">Service, Reparatur und technische Unterstützung für Funkgeräte und Kommunikationstechnik.</p> <p className="lead">Service, Reparatur und Diagnose für Funkgeräte, Messtechnik und Kommunikationstechnik.</p>
<p className="hero-copy">Von CB-Funk und Amateurfunk bis zur professionellen Funktechnik: Wir unterstützen bei Fehlersuche, Abgleich, Modulationsproblemen, Frequenzproblemen und technischer Dokumentation.</p>
<div className="hero-badges" aria-label="Technische Schwerpunkte">
<span>CB-Funk</span>
<span>Amateurfunk</span>
<span>Messtechnik</span>
<span>Abgleich</span>
<span>Diagnose</span>
<span>Service Manuals</span>
</div>
<div className="hero-actions"> <div className="hero-actions">
<Link className="button" href="/reparatur">Reparatur anfragen</Link> <Link className="button" href="/reparatur">Reparatur anfragen</Link>
<Link className="button secondary" href="/leistungen">Leistungen ansehen</Link> <Link className="button secondary" href="/leistungen">Leistungen ansehen</Link>
@ -20,13 +29,13 @@ export default function HomePage() {
<div className="container"> <div className="container">
<div className="section-head"> <div className="section-head">
<p className="eyebrow">Leistungsbereiche</p> <p className="eyebrow">Leistungsbereiche</p>
<h2>Technischer Service mit sauberer Diagnose</h2> <h2>Werkstattservice für Funktechnik und Messtechnik</h2>
<p className="lead">Von der ersten Fehlerbeschreibung bis zur dokumentierten Prüfung: Funktechnik Schubert verbindet Werkstatterfahrung mit strukturierter Kommunikation.</p> <p className="lead">Von der ersten Fehlerbeschreibung bis zur dokumentierten Prüfung: Funktechnik Schubert verbindet technische Erfahrung, geeignete Messmittel und strukturierte Kommunikation.</p>
</div> </div>
<div className="grid three"> <div className="grid three">
<ServiceCard title="Funkgeräte-Service">Prüfung, Abgleich und Service für CB-Funk, Amateurfunk und ausgewählte Kommunikationstechnik.</ServiceCard> <ServiceCard title="Funkgeräte-Service">Sichtprüfung, Funktionsprüfung und technische Beurteilung für CB-Funk, Amateurfunk, Betriebsfunk sowie ältere und moderne Geräte.</ServiceCard>
<ServiceCard title="Reparatur & Diagnose">Systematische Fehlersuche bei Empfang, Modulation, PLL, Frequenzstabilität und Stromversorgung.</ServiceCard> <ServiceCard title="Fehlersuche & Reparatur">Systematische Diagnose bei Empfangsproblemen, Sendeproblemen, PLL-Fehlern, Frequenzabweichungen und Audio-/NF-Störungen.</ServiceCard>
<ServiceCard title="Serviceunterlagen">Sorgfältiger Umgang mit Schaltplänen, Service Manuals und gerätespezifischen Dokumentationen.</ServiceCard> <ServiceCard title="Abgleich & Messtechnik">Frequenzabgleich, Hub- und Modulationsprüfung, Sendeleistung, Empfindlichkeit und RF-Messung mit geeigneter Messtechnik.</ServiceCard>
</div> </div>
</div> </div>
</section> </section>
@ -36,7 +45,7 @@ export default function HomePage() {
<div> <div>
<p className="eyebrow">Persönlicher Support</p> <p className="eyebrow">Persönlicher Support</p>
<h2>Direkte technische Einschätzung statt anonymer Abwicklung</h2> <h2>Direkte technische Einschätzung statt anonymer Abwicklung</h2>
<p className="lead">Viele Fehlerbilder lassen sich bereits mit einer klaren Beschreibung eingrenzen. Die Reparaturannahme fragt deshalb die wichtigsten technischen Details strukturiert ab.</p> <p className="lead">Viele Fehlerbilder lassen sich bereits mit einer klaren Beschreibung und den richtigen Gerätedaten eingrenzen. Die Reparaturannahme fragt deshalb die wichtigsten technischen Details strukturiert ab.</p>
<div className="actions"> <div className="actions">
<Link className="button" href="/reparatur">Reparaturformular öffnen</Link> <Link className="button" href="/reparatur">Reparaturformular öffnen</Link>
</div> </div>
@ -45,9 +54,9 @@ export default function HomePage() {
<h3>Warum Funktechnik Schubert?</h3> <h3>Warum Funktechnik Schubert?</h3>
<ul className="list"> <ul className="list">
<li>Technischer Fokus auf Funkgeräte und Kommunikationstechnik</li> <li>Technischer Fokus auf Funkgeräte und Kommunikationstechnik</li>
<li>Nachvollziehbare Diagnose statt pauschaler Austauschlogik</li> <li>Nachvollziehbare Diagnose mit Blick auf Messwerte und Fehlerbild</li>
<li>Klare Rückmeldung zu Aufwand, Zustand und nächsten Schritten</li> <li>Sauberer Umgang mit Service Manuals, Schaltplänen und Abgleichanleitungen</li>
<li>Vorbereitung für strukturierte Service- und Dokumentationsprozesse</li> <li>Klare Rückmeldung zu Zustand, Aufwand und sinnvollen nächsten Schritten</li>
</ul> </ul>
</div> </div>
</div> </div>

View file

@ -9,18 +9,19 @@ export default function RepairPage() {
return ( return (
<> <>
<PageHero eyebrow="Reparaturannahme" title="Reparatur anfragen"> <PageHero eyebrow="Reparaturannahme" title="Reparatur anfragen">
Beschreiben Sie Gerät, Fehlerbild und bisherige Vorarbeiten. Die Anfrage wird strukturiert erfasst und für eine spätere technische Übergabe vorbereitet. Beschreiben Sie Gerät, Fehlerbild, Zubehör und bisherige Vorarbeiten. Die Anfrage wird strukturiert erfasst und für eine spätere technische Bearbeitung vorbereitet.
</PageHero> </PageHero>
<section className="section"> <section className="section">
<div className="container split"> <div className="container split">
<div> <div>
<h2>Vor der Einsendung</h2> <h2>Vor der Einsendung</h2>
<p className="lead">Bitte senden Sie Geräte erst nach Rückmeldung ein. Eine klare Fehlerbeschreibung hilft, den Aufwand besser einzuschätzen.</p> <p className="lead">Bitte senden Sie Geräte erst nach Rückmeldung ein. Vollständige Gerätedaten und eine präzise Fehlerbeschreibung helfen, den Aufwand besser einzuschätzen.</p>
<ul className="list"> <ul className="list">
<li>Hersteller, Modell und Seriennummer bereithalten</li> <li>Hersteller, Modell, Geräteart und Seriennummer bereithalten, soweit vorhanden</li>
<li>Fehler möglichst reproduzierbar beschreiben</li> <li>Fehler möglichst konkret beschreiben: Empfang, Sendung, Modulation, Frequenz, Anzeige oder Versorgung</li>
<li>Vorarbeiten und geöffnete Geräte ehrlich angeben</li> <li>Zubehör wie Netzteil, Mikrofon, Antennenadapter oder Kabel angeben</li>
<li>Zubehör nur nach Absprache mitsenden</li> <li>Bereits geöffnete Geräte und durchgeführte Arbeiten ehrlich nennen</li>
<li>Fotos können später nach Rückmeldung ergänzt werden</li>
</ul> </ul>
</div> </div>
<div className="panel"> <div className="panel">

View file

@ -1,15 +1,22 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
const siteName = "Funktechnik Schubert"; const siteName = "Funktechnik Schubert";
const defaultTitle = "Funktechnik Schubert | Funkgeräte-Service & Messtechnik";
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL ?? "http://localhost:3010"; const siteUrl = process.env.NEXT_PUBLIC_SITE_URL ?? "http://localhost:3010";
const keywords = [ const keywords = [
"Funktechnik Schubert", "Funktechnik Schubert",
"Funkgeräte Reparatur", "Funkgeräte Reparatur",
"CB Funk Service", "CB Funk Service",
"Amateurfunk Reparatur", "Amateurfunk Service",
"Funkgeräte Abgleich", "Funkgeräte Abgleich",
"Funkgeräte Diagnose",
"Messtechnik",
"Funktechnik Service", "Funktechnik Service",
"Kommunikationstechnik", "Kommunikationstechnik",
"Service Manual",
"Schaltplan",
"Funkwerkstatt",
"Reparaturannahme",
]; ];
export function createMetadata(title: string, description: string, path = "/"): Metadata { export function createMetadata(title: string, description: string, path = "/"): Metadata {
@ -17,9 +24,19 @@ export function createMetadata(title: string, description: string, path = "/"):
const fullTitle = title === siteName ? title : `${title} | ${siteName}`; const fullTitle = title === siteName ? title : `${title} | ${siteName}`;
return { return {
title: fullTitle, metadataBase: new URL(siteUrl),
title: title === siteName ? { default: defaultTitle, template: `%s | ${siteName}` } : fullTitle,
applicationName: siteName,
description, description,
keywords, keywords,
icons: {
icon: [
{ url: "/favicon.ico", sizes: "any" },
{ url: "/favicon-16x16.png", sizes: "16x16", type: "image/png" },
{ url: "/favicon-32x32.png", sizes: "32x32", type: "image/png" },
],
apple: [{ url: "/apple-touch-icon.png", sizes: "180x180", type: "image/png" }],
},
alternates: { alternates: {
canonical: url, canonical: url,
}, },
@ -30,8 +47,16 @@ export function createMetadata(title: string, description: string, path = "/"):
siteName, siteName,
title: fullTitle, title: fullTitle,
description, description,
images: [
{
url: "/funktechnik_schubert_logo.jpg",
width: 1672,
height: 941,
alt: siteName,
},
],
}, },
}; };
} }
export { siteName, siteUrl }; export { defaultTitle, siteName, siteUrl };

View file

@ -1,3 +1,4 @@
import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
export default function Footer() { export default function Footer() {
@ -5,20 +6,27 @@ export default function Footer() {
<footer className="footer"> <footer className="footer">
<div className="container footer-grid"> <div className="container footer-grid">
<div> <div>
<h3>Funktechnik Schubert</h3> <Image
<p>Service, Reparatur und technische Unterstützung für Funkgeräte und Kommunikationstechnik.</p> src="/funktechnik_schubert_logo.jpg"
alt="Funktechnik Schubert"
width={1672}
height={941}
className="footer-logo"
/>
<p>Funktechnik Schubert Service und technische Unterstützung für Funkgeräte, Messtechnik und Kommunikationstechnik.</p>
<p className="copyright">© Funktechnik Schubert</p>
</div> </div>
<div> <div>
<h3>Service</h3> <h3>Website</h3>
<p><Link href="/leistungen">Leistungen</Link></p> <p><Link href="/leistungen">Leistungen</Link></p>
<p><Link href="/funkgeraete-service">Funkgeräte-Service</Link></p> <p><Link href="/funkgeraete-service">Funkgeräte-Service</Link></p>
<p><Link href="/reparatur">Reparaturannahme</Link></p> <p><Link href="/reparatur">Reparatur</Link></p>
<p><Link href="/kontakt">Kontakt</Link></p>
</div> </div>
<div> <div>
<h3>Rechtliches</h3> <h3>Rechtliches</h3>
<p><Link href="/impressum">Impressum</Link></p> <p><Link href="/impressum">Impressum</Link></p>
<p><Link href="/datenschutz">Datenschutz</Link></p> <p><Link href="/datenschutz">Datenschutz</Link></p>
<p><Link href="/kontakt">Kontakt</Link></p>
</div> </div>
</div> </div>
</footer> </footer>

View file

@ -1,6 +1,12 @@
"use client";
import Image from "next/image";
import Link from "next/link"; import Link from "next/link";
import { usePathname } from "next/navigation";
import { useState } from "react";
const navigation = [ const navigation = [
["Start", "/"],
["Leistungen", "/leistungen"], ["Leistungen", "/leistungen"],
["Funkgeräte-Service", "/funkgeraete-service"], ["Funkgeräte-Service", "/funkgeraete-service"],
["Reparatur", "/reparatur"], ["Reparatur", "/reparatur"],
@ -9,18 +15,39 @@ const navigation = [
] as const; ] as const;
export default function Header() { export default function Header() {
const pathname = usePathname();
const [menuOpen, setMenuOpen] = useState(false);
return ( return (
<header className="site-header"> <header className="site-header">
<div className="container header-inner"> <div className="container header-inner">
<Link href="/" className="brand" aria-label="Funktechnik Schubert Startseite"> <Link href="/" className="brand" aria-label="Funktechnik Schubert Startseite" onClick={() => setMenuOpen(false)}>
<span className="brand-mark" aria-hidden="true" /> <Image
<span>Funktechnik Schubert</span> src="/funktechnik_schubert_logo.jpg"
alt="Funktechnik Schubert"
width={1672}
height={941}
priority
className="brand-logo"
/>
</Link> </Link>
<nav className="nav" aria-label="Hauptnavigation"> <button
className="menu-toggle"
type="button"
aria-expanded={menuOpen}
aria-controls="site-navigation"
onClick={() => setMenuOpen((open) => !open)}
>
Menü
</button>
<nav id="site-navigation" className={menuOpen ? "nav is-open" : "nav"} aria-label="Hauptnavigation">
{navigation.map(([label, href]) => ( {navigation.map(([label, href]) => (
<Link key={href} href={href}>{label}</Link> <Link key={href} href={href} aria-current={pathname === href ? "page" : undefined} onClick={() => setMenuOpen(false)}>
{label}
</Link>
))} ))}
</nav> </nav>
<Link className="header-cta" href="/reparatur">Reparatur anfragen</Link>
</div> </div>
</header> </header>
); );

View file

@ -2,7 +2,8 @@
import { useState, type FormEvent } from "react"; import { useState, type FormEvent } from "react";
type Errors = Partial<Record<"name" | "email" | "manufacturer" | "model" | "description" | "privacy", string>>; type ErrorField = "name" | "email" | "manufacturer" | "model" | "deviceType" | "description" | "privacy";
type Errors = Partial<Record<ErrorField, string>>;
export default function RepairForm() { export default function RepairForm() {
const [errors, setErrors] = useState<Errors>({}); const [errors, setErrors] = useState<Errors>({});
@ -19,6 +20,7 @@ export default function RepairForm() {
if (!String(form.get("email") ?? "").includes("@")) nextErrors.email = "Bitte geben Sie eine gültige E-Mail-Adresse ein."; if (!String(form.get("email") ?? "").includes("@")) nextErrors.email = "Bitte geben Sie eine gültige E-Mail-Adresse ein.";
if (!String(form.get("manufacturer") ?? "").trim()) nextErrors.manufacturer = "Bitte nennen Sie den Hersteller."; if (!String(form.get("manufacturer") ?? "").trim()) nextErrors.manufacturer = "Bitte nennen Sie den Hersteller.";
if (!String(form.get("model") ?? "").trim()) nextErrors.model = "Bitte nennen Sie das Modell."; if (!String(form.get("model") ?? "").trim()) nextErrors.model = "Bitte nennen Sie das Modell.";
if (!String(form.get("deviceType") ?? "").trim()) nextErrors.deviceType = "Bitte wählen Sie die Geräteart aus.";
if (String(form.get("description") ?? "").trim().length < 20) nextErrors.description = "Bitte beschreiben Sie den Fehler mit mindestens 20 Zeichen."; if (String(form.get("description") ?? "").trim().length < 20) nextErrors.description = "Bitte beschreiben Sie den Fehler mit mindestens 20 Zeichen.";
if (form.get("privacy") !== "on") nextErrors.privacy = "Bitte stimmen Sie der Verarbeitung Ihrer Angaben zu."; if (form.get("privacy") !== "on") nextErrors.privacy = "Bitte stimmen Sie der Verarbeitung Ihrer Angaben zu.";
@ -52,6 +54,23 @@ export default function RepairForm() {
<Field label="Modell" name="model" error={errors.model} /> <Field label="Modell" name="model" error={errors.model} />
<Field label="Seriennummer optional" name="serialNumber" /> <Field label="Seriennummer optional" name="serialNumber" />
</div> </div>
<div className="field">
<label htmlFor="deviceType">Geräteart</label>
<select id="deviceType" name="deviceType" defaultValue="">
<option value="">Bitte auswählen</option>
<option value="cb-radio">CB-Funkgerät</option>
<option value="amateur-radio">Amateurfunkgerät</option>
<option value="business-radio">Betriebsfunkgerät</option>
<option value="receiver">Empfänger / Scanner</option>
<option value="measurement">Messtechnik</option>
<option value="other">Sonstige Kommunikationstechnik</option>
</select>
{errors.deviceType && <span className="error">{errors.deviceType}</span>}
</div>
<div className="field">
<label htmlFor="accessories">Zubehör vorhanden</label>
<textarea id="accessories" name="accessories" placeholder="z. B. Netzteil, Mikrofon, Kabel, Adapter, Antenne oder Bediengerät" />
</div>
<div className="field"> <div className="field">
<label htmlFor="opened">Wurde das Gerät bereits geöffnet?</label> <label htmlFor="opened">Wurde das Gerät bereits geöffnet?</label>
<select id="opened" name="opened" defaultValue="unknown"> <select id="opened" name="opened" defaultValue="unknown">
@ -62,14 +81,14 @@ export default function RepairForm() {
</div> </div>
<div className="field"> <div className="field">
<label htmlFor="previousWork">Gibt es Vorarbeiten?</label> <label htmlFor="previousWork">Gibt es Vorarbeiten?</label>
<textarea id="previousWork" name="previousWork" placeholder="z. B. Bauteile getauscht, Abgleich versucht, Fremdwerkstatt" /> <textarea id="previousWork" name="previousWork" placeholder="z. B. Bauteile getauscht, Abgleich versucht, Reinigung, Messung, Fremdwerkstatt" />
</div> </div>
<div className="field"> <div className="field">
<label htmlFor="description">Fehlerbeschreibung</label> <label htmlFor="description">Fehlerbeschreibung</label>
<textarea id="description" name="description" placeholder="Bitte Gerät, Fehlerbild und bisherige Beobachtungen beschreiben." /> <textarea id="description" name="description" placeholder="Bitte Fehlerbild möglichst konkret beschreiben: tritt der Fehler beim Empfang, Senden, bei Modulation, Frequenz, Anzeige oder Stromversorgung auf?" />
{errors.description && <span className="error">{errors.description}</span>} {errors.description && <span className="error">{errors.description}</span>}
</div> </div>
<p className="notice">Datei- und Bild-Upload ist für eine spätere Ausbaustufe vorbereitet. Bitte senden Sie Bilder aktuell erst nach Rückmeldung.</p> <p className="notice">Fotos vom Gerät, Typenschild oder Innenleben können später nach Rückmeldung ergänzt werden. Bitte senden Sie aktuell noch keine Dateien unaufgefordert.</p>
<label className="checkbox"> <label className="checkbox">
<input type="checkbox" name="privacy" /> <input type="checkbox" name="privacy" />
<span>Ich stimme zu, dass meine Angaben zur Bearbeitung der Reparaturanfrage verarbeitet werden.</span> <span>Ich stimme zu, dass meine Angaben zur Bearbeitung der Reparaturanfrage verarbeitet werden.</span>

BIN
public/apple-touch-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

BIN
public/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
public/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB