Fix mobile/responsive view across all pages and components
Comprehensive mobile pass over every page, form, layout and shared component: eliminate horizontal overflow, switch fixed-vh sections to auto height, collapse multi-column grids to single column, scale down display typography and padding, enforce 44px tap targets, and ensure embedded gold forms render and scroll without clipping on small screens. All changes gated behind isMobile; desktop layout unchanged. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { ChevronRight } from 'lucide-react';
|
||||
import MunichSkylineBg from '@/components/ui/munich-skyline-bg';
|
||||
import { useIsMobile } from '@/hooks/useIsMobile';
|
||||
|
||||
const FF = '"IBM Plex Sans", sans-serif';
|
||||
|
||||
@@ -15,6 +16,7 @@ export const TESTIMONIALS = [
|
||||
export default function TestimonialsSection() {
|
||||
const [active, setActive] = useState(0);
|
||||
const t = TESTIMONIALS[active];
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const prev = () => setActive(i => (i - 1 + TESTIMONIALS.length) % TESTIMONIALS.length);
|
||||
const next = () => setActive(i => (i + 1) % TESTIMONIALS.length);
|
||||
@@ -22,20 +24,20 @@ export default function TestimonialsSection() {
|
||||
return (
|
||||
<section style={{ background: '#fff', overflow: 'hidden', position: 'relative', isolation: 'isolate' }}>
|
||||
<MunichSkylineBg />
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '42% 58%', height: 560, position: 'relative', zIndex: 1 }}>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '42% 58%', height: isMobile ? 'auto' : 560, position: 'relative', zIndex: 1 }}>
|
||||
{/* Left — portrait photo */}
|
||||
<div style={{ position: 'relative', overflow: 'hidden' }}>
|
||||
<div style={{ position: 'relative', overflow: 'hidden', height: isMobile ? 280 : 'auto' }}>
|
||||
<img
|
||||
key={t.img}
|
||||
src={t.img}
|
||||
alt={t.name}
|
||||
style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', objectFit: 'cover', objectPosition: 'top', display: 'block', transition: 'opacity 0.4s' }}
|
||||
/>
|
||||
<div style={{ position: 'absolute', inset: 0, background: 'linear-gradient(to right, transparent 60%, #fff 100%)' }} />
|
||||
<div style={{ position: 'absolute', inset: 0, background: isMobile ? 'linear-gradient(to bottom, transparent 70%, #fff 100%)' : 'linear-gradient(to right, transparent 60%, #fff 100%)' }} />
|
||||
</div>
|
||||
|
||||
{/* Right — quote */}
|
||||
<div style={{ padding: '80px 72px 80px 56px', display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
|
||||
<div style={{ padding: isMobile ? '40px 22px 56px' : '80px 72px 80px 56px', display: 'flex', flexDirection: 'column', justifyContent: 'center' }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: '#4A8FC9', textTransform: 'uppercase', letterSpacing: '0.3em', fontWeight: 700, display: 'block', marginBottom: 32 }}>Stimmen der Preisträger</span>
|
||||
|
||||
<p style={{ fontFamily: FF, fontSize: 'clamp(1.4rem, 2.2vw, 2rem)', fontWeight: 300, color: '#101828', lineHeight: 1.55, margin: '0 0 40px' }}>
|
||||
@@ -50,13 +52,13 @@ export default function TestimonialsSection() {
|
||||
</div>
|
||||
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginTop: 40 }}>
|
||||
<button onClick={prev} style={{ width: 40, height: 40, border: '1.5px solid #D0D5DD', background: 'transparent', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'all 0.15s' }}
|
||||
<button onClick={prev} style={{ width: isMobile ? 44 : 40, height: isMobile ? 44 : 40, border: '1.5px solid #D0D5DD', background: 'transparent', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'all 0.15s' }}
|
||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.borderColor = '#111D55'; (e.currentTarget as HTMLElement).style.background = '#111D55'; (e.currentTarget as HTMLElement).style.color = '#fff'; }}
|
||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.borderColor = '#D0D5DD'; (e.currentTarget as HTMLElement).style.background = 'transparent'; (e.currentTarget as HTMLElement).style.color = 'inherit'; }}
|
||||
>
|
||||
<ChevronRight size={16} style={{ transform: 'rotate(180deg)' }} />
|
||||
</button>
|
||||
<button onClick={next} style={{ width: 40, height: 40, border: '1.5px solid #111D55', background: '#111D55', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'all 0.15s', color: '#fff' }}
|
||||
<button onClick={next} style={{ width: isMobile ? 44 : 40, height: isMobile ? 44 : 40, border: '1.5px solid #111D55', background: '#111D55', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'all 0.15s', color: '#fff' }}
|
||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = '#EFBF04'; (e.currentTarget as HTMLElement).style.borderColor = '#EFBF04'; }}
|
||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = '#111D55'; (e.currentTarget as HTMLElement).style.borderColor = '#111D55'; }}
|
||||
>
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
ProcessCardBody,
|
||||
ProcessCardTitle,
|
||||
} from '@/components/ui/process-timeline';
|
||||
import { useIsMobile } from '@/hooks/useIsMobile';
|
||||
|
||||
const FF = '"IBM Plex Sans", sans-serif';
|
||||
const FB = '"Inter", sans-serif';
|
||||
@@ -21,6 +22,61 @@ const STEPS = [
|
||||
];
|
||||
|
||||
export default function WegZurAuszeichnungSection() {
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
// ── MOBILE: stacked vertical list (no scroll-jack, no horizontal overflow) ──
|
||||
if (isMobile) {
|
||||
return (
|
||||
<section id="schritte" style={{ background: NAVY, padding: '56px 22px' }}>
|
||||
{/* Section header */}
|
||||
<div style={{ marginBottom: 36 }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: '#4A8FC9', textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, display: 'block', marginBottom: 12 }}>Schritt für Schritt</span>
|
||||
<h2 style={{ fontFamily: FF, fontSize: 'clamp(1.9rem, 8vw, 2.6rem)', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.025em', lineHeight: 1.05, margin: '0 0 16px' }}>
|
||||
IHR WEG ZUR<br />AUSZEICHNUNG.
|
||||
</h2>
|
||||
<p style={{ fontFamily: FB, fontSize: 15, color: 'rgba(255,255,255,0.65)', lineHeight: 1.7, margin: 0 }}>
|
||||
Von der Einreichung bis zur Gala — vier klar definierte Schritte auf dem Weg zur höchsten Auszeichnung des bayerischen Mittelstands.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Stacked step cards */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
{STEPS.map((item, index) => {
|
||||
const Icon = item.Icon;
|
||||
return (
|
||||
<div key={item.step} style={{ border: '1px solid rgba(239,191,4,0.25)', background: '#111D55', padding: '24px 22px' }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 20 }}>
|
||||
<div style={{ width: 44, height: 44, border: '1px solid rgba(239,191,4,0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}>
|
||||
<Icon size={18} style={{ color: GOLD }} strokeWidth={1.5} />
|
||||
</div>
|
||||
<div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 4 }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, color: GOLD, letterSpacing: '0.18em', textTransform: 'uppercase' }}>Schritt {item.step}</span>
|
||||
<span style={{ width: 1, height: 11, background: 'rgba(255,255,255,0.2)' }} />
|
||||
<span style={{ fontFamily: FF, fontSize: 10, color: 'rgba(255,255,255,0.62)', letterSpacing: '0.06em' }}>{item.date}</span>
|
||||
</div>
|
||||
<h3 style={{ fontFamily: FF, fontSize: '1.35rem', fontWeight: 900, color: '#fff', textTransform: 'uppercase', letterSpacing: '-0.02em', lineHeight: 1.1, margin: 0 }}>
|
||||
{item.title}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<p style={{ fontFamily: FB, fontSize: 15, color: 'rgba(255,255,255,0.68)', lineHeight: 1.7, margin: 0 }}>
|
||||
{item.desc}
|
||||
</p>
|
||||
{/* Step progress indicator */}
|
||||
<div style={{ display: 'flex', gap: 4, marginTop: 20 }}>
|
||||
{[0, 1, 2, 3].map(i => (
|
||||
<div key={i} style={{ height: 2, flex: i === index ? 2 : 1, background: i === index ? GOLD : 'rgba(255,255,255,0.1)' }} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ContainerScroll id="schritte" className="h-[380vh]" style={{ background: NAVY }}>
|
||||
<ContainerSticky className="top-0 h-screen flex flex-col">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { ArrowRight, ArrowLeft, Check, Trophy, User, Send, Building2, AlertCircle } from 'lucide-react';
|
||||
import { useIsMobile } from '@/hooks/useIsMobile';
|
||||
|
||||
const FF = '"IBM Plex Sans", sans-serif';
|
||||
|
||||
@@ -183,14 +184,15 @@ function Field({ label, error, children, optional, c }: {
|
||||
|
||||
function StyledInput({ c, ...props }: React.InputHTMLAttributes<HTMLInputElement> & { c: C }) {
|
||||
const [focused, setFocused] = useState(false);
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
<input
|
||||
{...props}
|
||||
onFocus={e => { setFocused(true); props.onFocus?.(e); }}
|
||||
onBlur ={e => { setFocused(false); props.onBlur?.(e); }}
|
||||
style={{
|
||||
width: '100%', height: 38, padding: '0 12px',
|
||||
fontFamily: FF, fontSize: 13, color: c.inputText,
|
||||
width: '100%', height: isMobile ? 44 : 38, padding: isMobile ? '0 14px' : '0 12px',
|
||||
fontFamily: FF, fontSize: isMobile ? 16 : 13, color: c.inputText,
|
||||
border: `1px solid ${focused ? c.borderActive : c.inputBorder}`,
|
||||
background: focused ? c.inputBgFocus : c.inputBg,
|
||||
outline: 'none', boxSizing: 'border-box',
|
||||
@@ -221,16 +223,19 @@ function TypeCard({ selected, onClick, title, desc, c }: {
|
||||
);
|
||||
}
|
||||
|
||||
function OptionButton({ selected, onClick, label, c }: {
|
||||
selected: boolean; onClick: () => void; label: string; c: C;
|
||||
function OptionButton({ selected, onClick, label, c, style }: {
|
||||
selected: boolean; onClick: () => void; label: string; c: C; style?: React.CSSProperties;
|
||||
}) {
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
<button type="button" onClick={onClick} style={{
|
||||
padding: '8px 16px', fontFamily: FF, fontSize: 12, fontWeight: 600, cursor: 'pointer',
|
||||
padding: isMobile ? '12px 18px' : '8px 16px', minHeight: isMobile ? 44 : undefined,
|
||||
fontFamily: FF, fontSize: isMobile ? 14 : 12, fontWeight: 600, cursor: 'pointer',
|
||||
border: `1.5px solid ${selected ? c.optBorderSel : c.optBorder}`,
|
||||
background: selected ? c.optBgSel : c.optBg,
|
||||
color: selected ? c.optTextSel : c.optText,
|
||||
transition: 'all 0.15s',
|
||||
...style,
|
||||
}}>
|
||||
{label}
|
||||
</button>
|
||||
@@ -240,10 +245,12 @@ function OptionButton({ selected, onClick, label, c }: {
|
||||
function YesNo({ value, onChange, c }: {
|
||||
value: 'ja' | 'nein' | ''; onChange: (v: 'ja' | 'nein') => void; c: C;
|
||||
}) {
|
||||
const isMobile = useIsMobile();
|
||||
const flexStyle = isMobile ? { flex: 1 } : undefined;
|
||||
return (
|
||||
<div style={{ display: 'flex', gap: 8 }}>
|
||||
<OptionButton selected={value === 'ja'} onClick={() => onChange('ja')} label="Ja" c={c} />
|
||||
<OptionButton selected={value === 'nein'} onClick={() => onChange('nein')} label="Nein" c={c} />
|
||||
<OptionButton selected={value === 'ja'} onClick={() => onChange('ja')} label="Ja" c={c} style={flexStyle} />
|
||||
<OptionButton selected={value === 'nein'} onClick={() => onChange('nein')} label="Nein" c={c} style={flexStyle} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -256,6 +263,7 @@ function isEligible(data: FormData) {
|
||||
|
||||
export default function BewerbungsForm({ theme = 'dark' }: { theme?: 'dark' | 'gold' }) {
|
||||
const c = theme === 'gold' ? GOLD : DARK;
|
||||
const isMobile = useIsMobile();
|
||||
|
||||
const [step, setStep] = useState(0);
|
||||
const [dir, setDir] = useState(1);
|
||||
@@ -362,7 +370,7 @@ export default function BewerbungsForm({ theme = 'dark' }: { theme?: 'dark' | 'g
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div style={{ padding: '20px 28px', flex: 1, minHeight: 0, overflowX: 'hidden', overflowY: 'auto' }}>
|
||||
<div style={{ padding: isMobile ? '18px 20px' : '20px 28px', flex: 1, minHeight: 0, overflowX: 'hidden', overflowY: 'auto' }}>
|
||||
<AnimatePresence mode="wait" custom={dir}>
|
||||
<motion.div key={`${step}-${data.type}`} custom={dir} variants={variants}
|
||||
initial="enter" animate="center" exit="exit"
|
||||
@@ -373,7 +381,7 @@ export default function BewerbungsForm({ theme = 'dark' }: { theme?: 'dark' | 'g
|
||||
<div>
|
||||
<p style={{ fontFamily: FF, fontSize: 10, color: c.accentLabel, textTransform: 'uppercase', letterSpacing: '0.25em', fontWeight: 700, marginBottom: 8 }}>Schritt 1</p>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 16, fontWeight: 900, color: c.heading, textTransform: 'uppercase', letterSpacing: '-0.01em', marginBottom: 14 }}>Art der Einreichung</h3>
|
||||
<div style={{ display: 'flex', gap: 8 }}>
|
||||
<div style={{ display: 'flex', flexDirection: isMobile ? 'column' : 'row', gap: 8 }}>
|
||||
<TypeCard c={c} selected={data.type === 'selbst'} title="Eigenbewerbung"
|
||||
desc="Ich bewerbe mein eigenes Unternehmen für den Bayerischen Mittelstandspreis."
|
||||
onClick={() => { set('type', 'selbst'); setDir(1); setStep(1); }} />
|
||||
@@ -437,7 +445,7 @@ export default function BewerbungsForm({ theme = 'dark' }: { theme?: 'dark' | 'g
|
||||
<p style={{ fontFamily: FF, fontSize: 10, color: c.accentLabel, textTransform: 'uppercase', letterSpacing: '0.25em', fontWeight: 700, marginBottom: 6 }}>Kontakt</p>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 16, fontWeight: 900, color: c.heading, textTransform: 'uppercase', letterSpacing: '-0.01em' }}>Ihre Kontaktdaten</h3>
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14 }}>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: 14 }}>
|
||||
<Field c={c} label="Firmenname" error={errors.firmenname}>
|
||||
<StyledInput c={c} value={data.firmenname} onChange={e => set('firmenname', e.target.value)} placeholder="Muster GmbH" />
|
||||
</Field>
|
||||
@@ -474,7 +482,7 @@ export default function BewerbungsForm({ theme = 'dark' }: { theme?: 'dark' | 'g
|
||||
].map(row => (
|
||||
<div key={row.label} style={{ display: 'grid', gridTemplateColumns: '90px 1fr', gap: 12, padding: '10px 0', borderBottom: `1px solid ${c.border}`, alignItems: 'baseline' }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, color: c.rowLabel, textTransform: 'uppercase', letterSpacing: '0.12em' }}>{row.label}</span>
|
||||
<span style={{ fontFamily: FF, fontSize: 13, color: c.rowValue }}>{row.value}</span>
|
||||
<span style={{ fontFamily: FF, fontSize: 13, color: c.rowValue, overflowWrap: 'anywhere' }}>{row.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -488,7 +496,7 @@ export default function BewerbungsForm({ theme = 'dark' }: { theme?: 'dark' | 'g
|
||||
<p style={{ fontFamily: FF, fontSize: 10, color: c.accentLabel, textTransform: 'uppercase', letterSpacing: '0.25em', fontWeight: 700, marginBottom: 6 }}>Nominierung</p>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 16, fontWeight: 900, color: c.heading, textTransform: 'uppercase', letterSpacing: '-0.01em' }}>Nominiertes Unternehmen</h3>
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14 }}>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: 14 }}>
|
||||
<Field c={c} label="Firmenname" error={errors.nomFirma}>
|
||||
<StyledInput c={c} value={data.nomFirma} onChange={e => set('nomFirma', e.target.value)} placeholder="Muster GmbH" />
|
||||
</Field>
|
||||
@@ -509,7 +517,7 @@ export default function BewerbungsForm({ theme = 'dark' }: { theme?: 'dark' | 'g
|
||||
<p style={{ fontFamily: FF, fontSize: 10, color: c.accentLabel, textTransform: 'uppercase', letterSpacing: '0.25em', fontWeight: 700, marginBottom: 6 }}>Nominierender</p>
|
||||
<h3 style={{ fontFamily: FF, fontSize: 16, fontWeight: 900, color: c.heading, textTransform: 'uppercase', letterSpacing: '-0.01em' }}>Ihre Angaben</h3>
|
||||
</div>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 14 }}>
|
||||
<div style={{ display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap: 14 }}>
|
||||
<Field c={c} label="Ihr Name" error={errors.nomName}>
|
||||
<StyledInput c={c} value={data.nomName} onChange={e => set('nomName', e.target.value)} placeholder="Vor- und Nachname" />
|
||||
</Field>
|
||||
@@ -542,7 +550,7 @@ export default function BewerbungsForm({ theme = 'dark' }: { theme?: 'dark' | 'g
|
||||
].map(row => (
|
||||
<div key={row.label} style={{ display: 'grid', gridTemplateColumns: '100px 1fr', gap: 12, padding: '10px 0', borderBottom: `1px solid ${c.border}`, alignItems: 'baseline' }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, fontWeight: 700, color: c.rowLabel, textTransform: 'uppercase', letterSpacing: '0.12em' }}>{row.label}</span>
|
||||
<span style={{ fontFamily: FF, fontSize: 13, color: c.rowValue }}>{row.value}</span>
|
||||
<span style={{ fontFamily: FF, fontSize: 13, color: c.rowValue, overflowWrap: 'anywhere' }}>{row.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -555,12 +563,13 @@ export default function BewerbungsForm({ theme = 'dark' }: { theme?: 'dark' | 'g
|
||||
|
||||
{/* Navigation */}
|
||||
{step > 0 && (
|
||||
<div style={{ padding: '12px 28px', borderTop: `1px solid ${c.border}`, display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexShrink: 0 }}>
|
||||
<div style={{ padding: isMobile ? '12px 20px' : '12px 28px', borderTop: `1px solid ${c.border}`, display: 'flex', alignItems: 'center', justifyContent: 'space-between', flexShrink: 0 }}>
|
||||
<button type="button" onClick={prev} style={{
|
||||
fontFamily: FF, fontSize: 12, fontWeight: 700, textTransform: 'uppercase',
|
||||
letterSpacing: '0.1em', color: c.backText, background: 'none',
|
||||
border: 'none', cursor: 'pointer', display: 'flex', alignItems: 'center', gap: 5,
|
||||
padding: 0, transition: 'color 0.15s',
|
||||
padding: isMobile ? '12px 4px' : 0, minHeight: isMobile ? 44 : undefined,
|
||||
transition: 'color 0.15s',
|
||||
}}
|
||||
onMouseEnter={e => (e.currentTarget.style.color = c.backTextHover)}
|
||||
onMouseLeave={e => (e.currentTarget.style.color = c.backText)}
|
||||
@@ -573,7 +582,7 @@ export default function BewerbungsForm({ theme = 'dark' }: { theme?: 'dark' | 'g
|
||||
<button type="button" onClick={next} style={{
|
||||
fontFamily: FF, fontSize: 12, fontWeight: 700, textTransform: 'uppercase',
|
||||
letterSpacing: '0.1em', color: c.btnText, background: c.btnBg,
|
||||
border: 'none', cursor: 'pointer', padding: '10px 22px',
|
||||
border: 'none', cursor: 'pointer', padding: isMobile ? '13px 24px' : '10px 22px',
|
||||
display: 'flex', alignItems: 'center', gap: 6, transition: 'background 0.15s',
|
||||
}}
|
||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = c.btnBgHover; }}
|
||||
@@ -585,7 +594,7 @@ export default function BewerbungsForm({ theme = 'dark' }: { theme?: 'dark' | 'g
|
||||
<button type="button" onClick={submit} style={{
|
||||
fontFamily: FF, fontSize: 12, fontWeight: 700, textTransform: 'uppercase',
|
||||
letterSpacing: '0.1em', color: c.btnText, background: c.btnBg,
|
||||
border: 'none', cursor: 'pointer', padding: '10px 22px',
|
||||
border: 'none', cursor: 'pointer', padding: isMobile ? '13px 24px' : '10px 22px',
|
||||
display: 'flex', alignItems: 'center', gap: 6, transition: 'background 0.15s',
|
||||
}}
|
||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = c.btnBgHover; }}
|
||||
|
||||
@@ -115,14 +115,15 @@ const variants = {
|
||||
|
||||
function StyledInput({ c, ...props }: React.InputHTMLAttributes<HTMLInputElement> & { c: C }) {
|
||||
const [focused, setFocused] = useState(false);
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
<input
|
||||
{...props}
|
||||
onFocus={e => { setFocused(true); props.onFocus?.(e); }}
|
||||
onBlur ={e => { setFocused(false); props.onBlur?.(e); }}
|
||||
style={{
|
||||
width: '100%', height: 42, padding: '0 12px',
|
||||
fontSize: 14, color: c.inputText, fontFamily: FF,
|
||||
width: '100%', height: isMobile ? 46 : 42, padding: '0 12px',
|
||||
fontSize: isMobile ? 16 : 14, color: c.inputText, fontFamily: FF,
|
||||
border: `1.5px solid ${focused ? c.inputBorderFoc : c.inputBorder}`,
|
||||
background: focused ? c.inputBgFocus : c.inputBg,
|
||||
outline: 'none', boxSizing: 'border-box',
|
||||
@@ -135,6 +136,7 @@ function StyledInput({ c, ...props }: React.InputHTMLAttributes<HTMLInputElement
|
||||
|
||||
function StyledTextarea({ c, ...props }: React.TextareaHTMLAttributes<HTMLTextAreaElement> & { c: C }) {
|
||||
const [focused, setFocused] = useState(false);
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
<textarea
|
||||
{...props}
|
||||
@@ -142,7 +144,7 @@ function StyledTextarea({ c, ...props }: React.TextareaHTMLAttributes<HTMLTextAr
|
||||
onBlur ={e => { setFocused(false); props.onBlur?.(e); }}
|
||||
style={{
|
||||
width: '100%', padding: '10px 12px',
|
||||
fontSize: 14, color: c.inputText, fontFamily: FF,
|
||||
fontSize: isMobile ? 16 : 14, color: c.inputText, fontFamily: FF,
|
||||
border: `1.5px solid ${focused ? c.inputBorderFoc : c.inputBorder}`,
|
||||
background: focused ? c.inputBgFocus : c.inputBg,
|
||||
outline: 'none', boxSizing: 'border-box',
|
||||
@@ -223,7 +225,7 @@ export default function KontaktForm({ theme = 'dark' }: { theme?: 'dark' | 'gold
|
||||
<h3 style={{ fontSize: 20, fontWeight: 800, color: c.successHeading, marginBottom: 10, letterSpacing: '-0.02em', fontFamily: FF }}>
|
||||
Nachricht gesendet
|
||||
</h3>
|
||||
<p style={{ fontSize: 15, color: c.successBody, lineHeight: 1.7, maxWidth: 280, margin: '0 auto', fontFamily: FF }}>
|
||||
<p style={{ fontSize: 15, color: c.successBody, lineHeight: 1.7, maxWidth: 280, margin: '0 auto', fontFamily: FF, overflowWrap: 'anywhere' }}>
|
||||
Danke, <strong style={{ color: c.successHeading }}>{data.name}</strong>. Wir melden uns zeitnah bei{' '}
|
||||
<strong style={{ color: c.successIcon }}>{data.email}</strong>.
|
||||
</p>
|
||||
@@ -313,7 +315,7 @@ export default function KontaktForm({ theme = 'dark' }: { theme?: 'dark' | 'gold
|
||||
<button type="button" onClick={prev} style={{
|
||||
fontSize: 12, fontWeight: 600, color: c.backText, background: 'none', border: 'none', cursor: 'pointer',
|
||||
display: 'flex', alignItems: 'center', justifyContent: isMobile ? 'center' : 'flex-start', gap: 5,
|
||||
padding: isMobile ? '8px 0' : 0, fontFamily: FF, transition: 'color 0.15s',
|
||||
padding: isMobile ? '12px 0' : 0, minHeight: isMobile ? 44 : undefined, fontFamily: FF, transition: 'color 0.15s',
|
||||
}}
|
||||
onMouseEnter={e => (e.currentTarget.style.color = c.backTextHover)}
|
||||
onMouseLeave={e => (e.currentTarget.style.color = c.backText)}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { ArrowRight, ArrowLeft, Check, Layers, Building2, Mail } from 'lucide-react';
|
||||
import { useIsMobile } from '@/hooks/useIsMobile';
|
||||
|
||||
const FF = '"IBM Plex Sans", sans-serif';
|
||||
|
||||
@@ -122,14 +123,15 @@ function Field({ c, label, error, children }: { c: C; label: string; error?: str
|
||||
|
||||
function StyledInput({ c, ...props }: React.InputHTMLAttributes<HTMLInputElement> & { c: C }) {
|
||||
const [focused, setFocused] = useState(false);
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
<input
|
||||
{...props}
|
||||
onFocus={e => { setFocused(true); props.onFocus?.(e); }}
|
||||
onBlur ={e => { setFocused(false); props.onBlur?.(e); }}
|
||||
style={{
|
||||
width: '100%', height: 44, padding: '0 14px',
|
||||
fontFamily: FF, fontSize: 14, color: c.inputText,
|
||||
width: '100%', height: isMobile ? 46 : 44, padding: '0 14px',
|
||||
fontFamily: FF, fontSize: isMobile ? 16 : 14, color: c.inputText,
|
||||
border: `1px solid ${focused ? c.inputBorderFoc : c.inputBorder}`,
|
||||
background: focused ? c.inputBgFocus : c.inputBg,
|
||||
outline: 'none', boxSizing: 'border-box',
|
||||
@@ -165,6 +167,7 @@ function PaketCard({ c, pkg, selected, onClick }: { c: C; pkg: typeof PAKETE[0];
|
||||
|
||||
export default function SponsoringForm({ theme = 'dark' }: { theme?: 'dark' | 'gold' }) {
|
||||
const c = theme === 'gold' ? GOLD : DARK;
|
||||
const isMobile = useIsMobile();
|
||||
const [step, setStep] = useState(0);
|
||||
const [dir, setDir] = useState(1);
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
@@ -301,7 +304,9 @@ export default function SponsoringForm({ theme = 'dark' }: { theme?: 'dark' | 'g
|
||||
<button type="button" onClick={prev} style={{
|
||||
fontFamily: FF, fontSize: 12, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em',
|
||||
color: c.backText, background: 'none', border: 'none', cursor: 'pointer',
|
||||
display: 'flex', alignItems: 'center', gap: 6, padding: 0, transition: 'color 0.15s',
|
||||
display: 'flex', alignItems: 'center', gap: 6,
|
||||
padding: isMobile ? '12px 4px' : 0, minHeight: isMobile ? 44 : undefined,
|
||||
transition: 'color 0.15s',
|
||||
}}
|
||||
onMouseEnter={e => (e.currentTarget.style.color = c.backTextHover)}
|
||||
onMouseLeave={e => (e.currentTarget.style.color = c.backText)}
|
||||
@@ -314,7 +319,8 @@ export default function SponsoringForm({ theme = 'dark' }: { theme?: 'dark' | 'g
|
||||
<button type="button" onClick={next} style={{
|
||||
fontFamily: FF, fontSize: 12, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em',
|
||||
color: c.btnText, background: c.btnBg, border: 'none', cursor: 'pointer',
|
||||
padding: '12px 24px', display: 'flex', alignItems: 'center', gap: 7, transition: 'background 0.15s',
|
||||
padding: isMobile ? '14px 24px' : '12px 24px', minHeight: isMobile ? 44 : undefined,
|
||||
display: 'flex', alignItems: 'center', gap: 7, transition: 'background 0.15s',
|
||||
}}
|
||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = c.btnBgHover; }}
|
||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = c.btnBg; }}
|
||||
@@ -325,7 +331,8 @@ export default function SponsoringForm({ theme = 'dark' }: { theme?: 'dark' | 'g
|
||||
<button type="button" onClick={submit} style={{
|
||||
fontFamily: FF, fontSize: 12, fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em',
|
||||
color: c.btnText, background: c.btnBg, border: 'none', cursor: 'pointer',
|
||||
padding: '12px 24px', display: 'flex', alignItems: 'center', gap: 7, transition: 'background 0.15s',
|
||||
padding: isMobile ? '14px 24px' : '12px 24px', minHeight: isMobile ? 44 : undefined,
|
||||
display: 'flex', alignItems: 'center', gap: 7, transition: 'background 0.15s',
|
||||
}}
|
||||
onMouseEnter={e => { (e.currentTarget as HTMLElement).style.background = c.btnBgHover; }}
|
||||
onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = c.btnBg; }}
|
||||
|
||||
@@ -256,7 +256,7 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
|
||||
<div style={{ display:'flex', alignItems:'center', justifyContent:'flex-end', padding:'0 12px', height:'100%' }}>
|
||||
{/* Mobile CTA */}
|
||||
<Link to="/teilnahme#bewerben"
|
||||
style={{ fontFamily:FF_DISPLAY, fontSize:9, fontWeight:700, textTransform:'uppercase', letterSpacing:'.1em', color:'#101828', background:GOLD, padding:'7px 12px', textDecoration:'none', marginRight:8, whiteSpace:'nowrap' }}
|
||||
style={{ display:'inline-flex', alignItems:'center', minHeight:36, fontFamily:FF_DISPLAY, fontSize:10, fontWeight:700, textTransform:'uppercase', letterSpacing:'.1em', color:'#101828', background:GOLD, padding:'0 14px', textDecoration:'none', marginRight:8, whiteSpace:'nowrap' }}
|
||||
>BMP</Link>
|
||||
{/* Mobile hamburger */}
|
||||
<button
|
||||
@@ -551,10 +551,10 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
|
||||
</div>
|
||||
|
||||
{/* Mobile secondary links */}
|
||||
<div style={{ padding:'0 24px 24px', display:'flex', flexWrap:'wrap', gap:'10px 24px' }}>
|
||||
<div style={{ padding:'0 24px 24px', display:'flex', flexWrap:'wrap', gap:'4px 8px' }}>
|
||||
{SECONDARY.map(item => (
|
||||
<Link key={item.path} to={item.path} onClick={() => setMobileMenuOpen(false)}
|
||||
style={{ fontSize:12, color:MUTED, textDecoration:'none', fontFamily:FF_BODY }}
|
||||
style={{ display:'inline-flex', alignItems:'center', minHeight:44, padding:'0 8px', fontSize:13, color:MUTED, textDecoration:'none', fontFamily:FF_BODY }}
|
||||
>{item.label}</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import { useIsMobile } from "@/hooks/useIsMobile";
|
||||
|
||||
// ─── CONSTANTS ────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -210,6 +211,7 @@ function Input({
|
||||
maxLength?: number;
|
||||
}) {
|
||||
const [focused, setFocused] = useState(false);
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
@@ -221,6 +223,8 @@ function Input({
|
||||
onBlur={() => { setFocused(false); onBlur?.(); }}
|
||||
style={{
|
||||
...fieldBase,
|
||||
// ≥16px on mobile prevents iOS auto-zoom on focus
|
||||
fontSize: isMobile ? 16 : fieldBase.fontSize,
|
||||
borderColor: error ? GOLD : focused ? "rgba(239,191,4,0.6)" : "rgba(255,255,255,0.15)",
|
||||
background: focused ? "rgba(255,255,255,0.1)" : "rgba(255,255,255,0.07)",
|
||||
}}
|
||||
@@ -235,6 +239,7 @@ function Select({
|
||||
options: { value: string; label: string }[]; placeholder?: string; error?: string;
|
||||
}) {
|
||||
const [focused, setFocused] = useState(false);
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
<select
|
||||
value={value}
|
||||
@@ -243,6 +248,8 @@ function Select({
|
||||
onBlur={() => setFocused(false)}
|
||||
style={{
|
||||
...fieldBase,
|
||||
// ≥16px on mobile prevents iOS auto-zoom on focus
|
||||
fontSize: isMobile ? 16 : fieldBase.fontSize,
|
||||
cursor: "pointer",
|
||||
appearance: "none",
|
||||
borderColor: error ? GOLD : focused ? "rgba(239,191,4,0.6)" : "rgba(255,255,255,0.15)",
|
||||
@@ -271,6 +278,7 @@ function Textarea({
|
||||
maxLength?: number; error?: string;
|
||||
}) {
|
||||
const [focused, setFocused] = useState(false);
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
<div>
|
||||
<textarea
|
||||
@@ -283,6 +291,8 @@ function Textarea({
|
||||
rows={4}
|
||||
style={{
|
||||
...fieldBase,
|
||||
// ≥16px on mobile prevents iOS auto-zoom on focus
|
||||
fontSize: isMobile ? 16 : fieldBase.fontSize,
|
||||
resize: "vertical",
|
||||
borderColor: error ? GOLD : focused ? "rgba(239,191,4,0.6)" : "rgba(255,255,255,0.15)",
|
||||
background: focused ? "rgba(255,255,255,0.1)" : "rgba(255,255,255,0.07)",
|
||||
@@ -419,8 +429,9 @@ function ReviewRow({ label, value }: { label: string; value: string }) {
|
||||
// ─── PROGRESS BAR ─────────────────────────────────────────────────────────────
|
||||
|
||||
function ProgressBar({ current, total }: { current: number; total: number }) {
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
<div style={{ marginBottom: 36 }}>
|
||||
<div style={{ marginBottom: isMobile ? 24 : 36 }}>
|
||||
{/* Step dots + connecting lines */}
|
||||
<div style={{ position: "relative", display: "flex", alignItems: "center", marginBottom: 12 }}>
|
||||
{/* Background track */}
|
||||
@@ -502,28 +513,43 @@ function ProgressBar({ current, total }: { current: number; total: number }) {
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Step labels */}
|
||||
<div style={{ display: "flex" }}>
|
||||
{STEPS.map((step) => {
|
||||
const done = step.id < current;
|
||||
const active = step.id === current;
|
||||
return (
|
||||
<div key={step.id} style={{
|
||||
flex: 1,
|
||||
textAlign: step.id === 1 ? "left" : step.id === total ? "right" : "center",
|
||||
fontSize: 10,
|
||||
fontWeight: active ? 700 : 500,
|
||||
letterSpacing: "0.08em",
|
||||
textTransform: "uppercase",
|
||||
color: active ? GOLD : done ? "rgba(239,191,4,0.5)" : "rgba(255,255,255,0.25)",
|
||||
fontFamily: '"IBM Plex Sans", sans-serif',
|
||||
transition: "color 0.3s",
|
||||
}}>
|
||||
{step.label}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{/* Step labels — full list on desktop; on mobile only the active label
|
||||
(the 5-up text row would overflow / wrap on narrow screens) */}
|
||||
{isMobile ? (
|
||||
<div style={{
|
||||
textAlign: "center",
|
||||
fontSize: 11,
|
||||
fontWeight: 700,
|
||||
letterSpacing: "0.1em",
|
||||
textTransform: "uppercase",
|
||||
color: GOLD,
|
||||
fontFamily: '"IBM Plex Sans", sans-serif',
|
||||
}}>
|
||||
{STEPS.find((s) => s.id === current)?.label} · {current}/{total}
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ display: "flex" }}>
|
||||
{STEPS.map((step) => {
|
||||
const done = step.id < current;
|
||||
const active = step.id === current;
|
||||
return (
|
||||
<div key={step.id} style={{
|
||||
flex: 1,
|
||||
textAlign: step.id === 1 ? "left" : step.id === total ? "right" : "center",
|
||||
fontSize: 10,
|
||||
fontWeight: active ? 700 : 500,
|
||||
letterSpacing: "0.08em",
|
||||
textTransform: "uppercase",
|
||||
color: active ? GOLD : done ? "rgba(239,191,4,0.5)" : "rgba(255,255,255,0.25)",
|
||||
fontFamily: '"IBM Plex Sans", sans-serif',
|
||||
transition: "color 0.3s",
|
||||
}}>
|
||||
{step.label}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -587,6 +613,7 @@ function NavButtons({
|
||||
}: {
|
||||
step: number; onBack: () => void; onNext: () => void; onSubmit: () => void; disabled?: boolean;
|
||||
}) {
|
||||
const isMobile = useIsMobile();
|
||||
const isLast = step === 5;
|
||||
return (
|
||||
<div style={{
|
||||
@@ -602,7 +629,8 @@ function NavButtons({
|
||||
onClick={onBack}
|
||||
style={{
|
||||
flex: "0 0 auto",
|
||||
padding: "12px 20px",
|
||||
padding: isMobile ? "12px 16px" : "12px 20px",
|
||||
minHeight: 44,
|
||||
background: "transparent",
|
||||
border: "1px solid rgba(255,255,255,0.2)",
|
||||
borderRadius: 8,
|
||||
@@ -613,6 +641,7 @@ function NavButtons({
|
||||
cursor: "pointer",
|
||||
letterSpacing: "0.05em",
|
||||
transition: "all 0.2s",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
(e.target as HTMLButtonElement).style.borderColor = "rgba(255,255,255,0.5)";
|
||||
@@ -633,17 +662,21 @@ function NavButtons({
|
||||
style={{
|
||||
flex: 1,
|
||||
padding: "13px 20px",
|
||||
minHeight: 44,
|
||||
background: disabled ? "rgba(239,191,4,0.3)" : GOLD,
|
||||
border: "none",
|
||||
borderRadius: 8,
|
||||
color: disabled ? "rgba(17,29,85,0.5)" : NAVY,
|
||||
fontFamily: '"IBM Plex Sans", sans-serif',
|
||||
fontSize: 13,
|
||||
// long final-step label needs a smaller size + wrapping on mobile
|
||||
fontSize: isMobile && isLast ? 12 : 13,
|
||||
fontWeight: 700,
|
||||
cursor: disabled ? "not-allowed" : "pointer",
|
||||
letterSpacing: "0.1em",
|
||||
letterSpacing: isMobile && isLast ? "0.04em" : "0.1em",
|
||||
textTransform: "uppercase",
|
||||
transition: "all 0.2s",
|
||||
lineHeight: 1.3,
|
||||
overflowWrap: "anywhere",
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (!disabled) (e.target as HTMLButtonElement).style.background = "#f5ca1a";
|
||||
@@ -663,6 +696,7 @@ function NavButtons({
|
||||
function Step1({ data, onChange, errors }: {
|
||||
data: FormData; onChange: (k: keyof FormData, v: string | boolean) => void; errors: Errors;
|
||||
}) {
|
||||
const isMobile = useIsMobile();
|
||||
const rechtsformen = [
|
||||
"GmbH","GmbH & Co. KG","AG","UG","KG","OHG","GbR",
|
||||
"Einzelunternehmen","e.K.","Sonstige",
|
||||
@@ -672,7 +706,7 @@ function Step1({ data, onChange, errors }: {
|
||||
<>
|
||||
<StepHeading step={1} title="Ihr Unternehmen" subtitle="Angaben zum antragstellenden Unternehmen" />
|
||||
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "0 16px" }}>
|
||||
<div style={{ display: "grid", gridTemplateColumns: isMobile ? "1fr" : "1fr 1fr", gap: "0 16px" }}>
|
||||
<div style={{ gridColumn: "1 / -1" }}>
|
||||
<Field label="Firmenname" required error={errors.firmenname}>
|
||||
<Input value={data.firmenname} onChange={(v) => onChange("firmenname", v)} placeholder="Muster GmbH" error={errors.firmenname} />
|
||||
@@ -713,17 +747,18 @@ function Step1({ data, onChange, errors }: {
|
||||
function Step2({ data, onChange, errors }: {
|
||||
data: FormData; onChange: (k: keyof FormData, v: string | boolean) => void; errors: Errors;
|
||||
}) {
|
||||
const isMobile = useIsMobile();
|
||||
return (
|
||||
<>
|
||||
<StepHeading step={2} title="Kontaktperson" subtitle="Ansprechpartner für die Mitgliedschaft" />
|
||||
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "0 16px" }}>
|
||||
<div style={{ display: "grid", gridTemplateColumns: isMobile ? "1fr" : "1fr 1fr", gap: "0 16px" }}>
|
||||
<Field label="Anrede" required error={errors.anrede}>
|
||||
<Select value={data.anrede} onChange={(v) => onChange("anrede", v)}
|
||||
options={[{value:"Herr",label:"Herr"},{value:"Frau",label:"Frau"},{value:"Divers",label:"Divers"}]}
|
||||
placeholder="Bitte wählen" error={errors.anrede} />
|
||||
</Field>
|
||||
<div /> {/* spacer */}
|
||||
{!isMobile && <div />} {/* spacer (desktop only) */}
|
||||
<Field label="Vorname" required error={errors.vorname}>
|
||||
<Input value={data.vorname} onChange={(v) => onChange("vorname", v)} placeholder="Max" error={errors.vorname} />
|
||||
</Field>
|
||||
@@ -755,6 +790,7 @@ function Step2({ data, onChange, errors }: {
|
||||
function Step3({ data, onChange, errors }: {
|
||||
data: FormData; onChange: (k: keyof FormData, v: string | boolean) => void; errors: Errors;
|
||||
}) {
|
||||
const isMobile = useIsMobile();
|
||||
const beitrag = calcBeitrag(data.mitarbeiter);
|
||||
return (
|
||||
<>
|
||||
@@ -800,7 +836,7 @@ function Step3({ data, onChange, errors }: {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "0 16px" }}>
|
||||
<div style={{ display: "grid", gridTemplateColumns: isMobile ? "1fr" : "1fr 1fr", gap: "0 16px" }}>
|
||||
<Field label="Gewünschtes Eintrittsdatum" required error={errors.eintrittsdatum}>
|
||||
<Input value={data.eintrittsdatum} onChange={(v) => onChange("eintrittsdatum", v)} type="date" error={errors.eintrittsdatum} />
|
||||
</Field>
|
||||
@@ -826,6 +862,7 @@ function Step3({ data, onChange, errors }: {
|
||||
function Step4({ data, onChange, errors }: {
|
||||
data: FormData; onChange: (k: keyof FormData, v: string | boolean) => void; errors: Errors;
|
||||
}) {
|
||||
const isMobile = useIsMobile();
|
||||
const ibanIsValid = validateIBAN(data.iban);
|
||||
|
||||
return (
|
||||
@@ -849,7 +886,7 @@ function Step4({ data, onChange, errors }: {
|
||||
Ich ermächtige den BMP e.V., Zahlungen von meinem Konto mittels Lastschrift einzuziehen. Zugleich weise ich mein Kreditinstitut an, die vom BMP e.V. auf mein Konto gezogenen Lastschriften einzulösen. Hinweis: Ich kann innerhalb von acht Wochen, beginnend mit dem Belastungsdatum, die Erstattung des belasteten Betrages verlangen. Es gelten dabei die mit meinem Kreditinstitut vereinbarten Bedingungen. Die Mandatsreferenz wird separat mitgeteilt. Gläubiger-Identifikationsnummer: DE98ZZZ09999999999.
|
||||
</div>
|
||||
|
||||
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "0 16px" }}>
|
||||
<div style={{ display: "grid", gridTemplateColumns: isMobile ? "1fr" : "1fr 1fr", gap: "0 16px" }}>
|
||||
<div style={{ gridColumn: "1 / -1" }}>
|
||||
<Field label="Kontoinhaber" required error={errors.kontoinhaber}>
|
||||
<Input value={data.kontoinhaber} onChange={(v) => onChange("kontoinhaber", v)} placeholder="Max Mustermann" error={errors.kontoinhaber} />
|
||||
@@ -1084,6 +1121,7 @@ function SuccessScreen() {
|
||||
// ─── MAIN COMPONENT ───────────────────────────────────────────────────────────
|
||||
|
||||
function MembershipWizard() {
|
||||
const isMobile = useIsMobile();
|
||||
const [step, setStep] = useState(1);
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
const [errors, setErrors] = useState<Errors>({});
|
||||
@@ -1162,7 +1200,7 @@ function MembershipWizard() {
|
||||
backdropFilter: "blur(8px)",
|
||||
borderRadius: 16,
|
||||
border: "1px solid rgba(255,255,255,0.08)",
|
||||
padding: "32px 28px",
|
||||
padding: isMobile ? "20px 16px" : "32px 28px",
|
||||
color: WHITE,
|
||||
fontFamily: '"IBM Plex Sans", sans-serif',
|
||||
boxShadow: "0 24px 64px rgba(0,0,0,0.4), inset 0 1px 0 rgba(255,255,255,0.06)",
|
||||
@@ -1191,7 +1229,7 @@ function MembershipWizard() {
|
||||
<>
|
||||
<ProgressBar current={step} total={STEPS.length} />
|
||||
|
||||
<div ref={contentRef} style={{ minHeight: 380 }}>
|
||||
<div ref={contentRef} style={{ minHeight: isMobile ? 300 : 380 }}>
|
||||
{step === 1 && <Step1 data={data} onChange={onChange} errors={errors} />}
|
||||
{step === 2 && <Step2 data={data} onChange={onChange} errors={errors} />}
|
||||
{step === 3 && <Step3 data={data} onChange={onChange} errors={errors} />}
|
||||
|
||||
@@ -24,10 +24,10 @@ const buttonVariants = cva(
|
||||
"bg-accent text-accent-foreground shadow-xs hover:bg-accent/90",
|
||||
},
|
||||
size: {
|
||||
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
||||
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
||||
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
||||
icon: "size-9",
|
||||
default: "h-9 px-4 py-2 has-[>svg]:px-3 max-md:min-h-11",
|
||||
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5 max-md:min-h-11",
|
||||
lg: "h-10 rounded-md px-6 has-[>svg]:px-4 max-md:min-h-11",
|
||||
icon: "size-9 max-md:min-h-11 max-md:min-w-11",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
|
||||
@@ -9,7 +9,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
<input
|
||||
type={type}
|
||||
className={cn(
|
||||
"flex h-11 w-full border border-stone-200 bg-white px-4 py-2 text-sm text-[#101828] placeholder:text-stone-400 focus:outline-none focus:ring-2 focus:ring-[#EFBF04] focus:border-transparent disabled:cursor-not-allowed disabled:opacity-50 transition-all",
|
||||
"flex h-11 w-full border border-stone-200 bg-white px-4 py-2 text-base md:text-sm text-[#101828] placeholder:text-stone-400 focus:outline-none focus:ring-2 focus:ring-[#EFBF04] focus:border-transparent disabled:cursor-not-allowed disabled:opacity-50 transition-all",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
|
||||
@@ -8,7 +8,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
|
||||
return (
|
||||
<textarea
|
||||
className={cn(
|
||||
"flex min-h-[100px] w-full border border-stone-200 bg-white px-4 py-3 text-sm text-[#101828] placeholder:text-stone-400 focus:outline-none focus:ring-2 focus:ring-[#EFBF04] focus:border-transparent disabled:cursor-not-allowed disabled:opacity-50 transition-all resize-none",
|
||||
"flex min-h-[100px] w-full border border-stone-200 bg-white px-4 py-3 text-base md:text-sm text-[#101828] placeholder:text-stone-400 focus:outline-none focus:ring-2 focus:ring-[#EFBF04] focus:border-transparent disabled:cursor-not-allowed disabled:opacity-50 transition-all resize-none",
|
||||
className
|
||||
)}
|
||||
ref={ref}
|
||||
|
||||
Reference in New Issue
Block a user