brand identity overhaul: match BRAND-IDENTITY.md across all pages

Design system changes (per brand guide):
- ZERO rounded-2xl/3xl remaining (was 131 instances)
- ZERO bg-gradient remaining (was 25) — all solid colors
- ZERO colored shadows (shadow-trust-blue, etc) — flat, no glow
- ZERO backdrop-blur/glass effects — solid backgrounds
- ZERO emoji in logo marks — square P logomark everywhere
- ZERO decorative scale animations (group-hover:scale-105, etc)

Tailwind config:
- Added brand color names: midnight, promise-blue, generosity-gold, fulfilled-green, alert-red, paper
- Kept legacy aliases (trust-blue, etc) for backwards compat
- --radius: 0.75rem → 0.5rem (tighter corners)

CSS:
- Removed glass, glass-dark, card-hover, pulse-ring, bounce-gentle, confetti-fall, scale-in animations
- Kept only purposeful animations: fadeUp, fadeIn, slideDown, shimmer, counter-roll
- --primary tuned to match Promise Blue exactly

Components:
- Button: removed all colored shadows, added 'blue' variant, removed rounded from sizes
- All UI components: rounded-xl/2xl → rounded-lg

Pages updated (41 files):
- Dashboard layout: solid header (no blur), border-l-2 active indicator, midnight logo mark
- Login/Signup: paper bg (no gradient), midnight logo mark, no emoji
- Pledge flow: solid color icons, no gradient progress bars
- All dashboard pages: flat, sharp, editorial
This commit is contained in:
2026-03-03 20:13:22 +08:00
parent f4ad6df45a
commit fc80399092
41 changed files with 282 additions and 475 deletions

View File

@@ -1,135 +0,0 @@
#!/usr/bin/env python3
"""Generate PNPL landing page images using Gemini 3 Pro Image (Nano Banana Pro)."""
import time, base64
from pathlib import Path
from google import genai
from google.genai import types
API_KEY = "AIzaSyCHnesXLjPw-UgeZaQotut66bgjFdvy12E"
MODEL = "gemini-3-pro-image-preview"
OUT_DIR = Path(r"C:\Users\uldvs\Downloads\PNPL Photography")
OUT_DIR.mkdir(parents=True, exist_ok=True)
client = genai.Client(api_key=API_KEY)
IMAGES = [
# ── Main page ──
("01-main-dashboard-hero", "16:9",
"Editorial product photography of a MacBook Pro on a minimal white desk showing a charity analytics dashboard with blue bar charts and a pipeline table. Clean desk with only a coffee cup and a single pen. Soft diffused daylight from the left. Shot on Sony A7III, 50mm f/2.8, shallow depth of field on the screen edges. Muted tones, magazine editorial style. Landscape orientation 16:9 aspect ratio."),
("02-main-charity-manager-card", "16:9",
"Candid documentary photograph of a British-Bangladeshi woman in her 40s wearing a dark navy hijab and tailored blazer, standing at the edge of a charity gala dinner in a London hotel ballroom. She holds a tablet at her side, looking across the room. Round tables with white cloths and warm tungsten pendant lights behind her, out of focus. Shot on Leica Q2, 28mm, f/1.7, available light only, natural film grain. Skin tones warm, background amber bokeh. No eye contact with camera. Landscape orientation 16:9 aspect ratio."),
("03-main-fundraiser-card", "16:9",
"Street photography of a young British-Somali man in his mid-20s wearing a grey crewneck sweatshirt, walking on a London residential street and looking down at his phone screen. Overcast daylight, Victorian terraced houses soft in the background. He is mid-stride, natural posture, not posing. Shot on Fujifilm X100V, 23mm, f/2, ISO 400, slight grain. Desaturated cool tones, editorial documentary feel. Landscape orientation 16:9 aspect ratio."),
("04-main-volunteer-card", "16:9",
"Candid indoor photograph of a young British-Pakistani woman in her early 20s wearing a plain volunteer lanyard on a black cord, leaning across a round banquet table pointing at a small printed card on the table. Warm overhead chandelier lighting in a mosque community hall, patterned carpet floor. Other seated guests softly blurred. Shot on Canon R6, 35mm f/1.4, shallow depth of field, warm amber tones. Natural, mid-conversation moment. Landscape orientation 16:9 aspect ratio."),
("05-main-org-card", "16:9",
"Documentary photograph of four people seated around a plain rectangular table in a small community centre meeting room. Mixed British ethnicities, business casual clothing. One person gestures at printed papers on the table, others listening. Overhead fluorescent strip lights mixed with grey window daylight. Institutional beige walls, a whiteboard partially visible behind. Shot on Nikon Z6, 28mm f/2.8, natural exposure, slightly flat lighting. No one looking at camera, mid-discussion. Landscape orientation 16:9 aspect ratio."),
("06-main-pledge-form", "1:1",
"Close-up over-the-shoulder photograph of a person holding an iPhone in their right hand at a dinner table. Screen shows a clean white mobile form with toggle switches and a green button, content not readable. A glass of water and folded linen napkin on the dark tablecloth beside the hand. Warm tungsten ambient light. Face not visible, only shoulder, arm, and hand in frame. Shot on 85mm f/1.8, tight crop, shallow depth of field on the screen. Natural skin tones, no flash. Square 1:1 aspect ratio."),
("07-main-schedule-step", "1:1",
"Overhead 45-degree photograph of a woman's hands holding a smartphone at a kitchen table. Soft morning window light from the left casting a gentle shadow. A ceramic mug of tea and a small plate with toast crumbs slightly out of focus behind the phone. The phone screen is bright white with blue accent elements, not readable. Domestic setting, wooden table surface. Shot on 50mm f/2.0, natural light only, warm muted tones. Quiet, everyday moment. Square 1:1 aspect ratio."),
# ── Charities page ──
("08-charities-hero", "1:1",
"Documentary portrait of a British-Bangladeshi man in his 50s wearing a white embroidered kufi cap, silver-framed reading glasses, and a pressed light blue shirt. He is seated at a side table during a mosque fundraising event, looking at an open laptop screen. Behind him, blurred rows of seated attendees in a large hall with green and gold decorations. Warm interior lighting, patterned Islamic geometric carpet visible on the floor. Shot on 56mm f/1.2, available light, rich warm tones, film grain. Candid, absorbed in his work. Square 1:1 aspect ratio."),
("09-charities-amount-selection", "1:1",
"Flat lay photograph of a hand holding an iPhone face-up on a dark navy tablecloth. The thumb hovers just above the bright screen. Visible beside the phone: silver cutlery, the edge of a white dinner plate, a printed menu card, and a glass of still water. Shot from directly above, 35mm, f/4, tungsten warm lighting from overhead chandeliers. The screen shows large numbers and a blue button, not readable. Charity dinner table setting, elegant but modest. Square 1:1 aspect ratio."),
("10-charities-whatsapp", "16:9",
"Candid photograph of a woman's hand holding a phone on her lap while sitting on a grey fabric sofa at home. The phone screen shows green WhatsApp message bubbles, text not readable. She wears a loose cardigan sleeve and a simple watch. A cushion and part of a wooden bookshelf with books visible softly in the background. Afternoon window daylight, warm golden hour tones. Shot on 50mm f/1.8, eye-level, shallow depth of field. Domestic, relaxed, real. Landscape orientation 16:9 aspect ratio."),
# ── Fundraisers page ──
("11-fundraisers-hero", "1:1",
"Street photography of a young British-Arab woman in her late 20s wearing a patterned burgundy hijab and a denim jacket, standing on a London high street and holding her phone screen towards a friend. The friend is partially visible and out of focus on the right edge of frame. Overcast flat daylight, shop fronts with awnings blurred behind. She is mid-laugh, natural expression. Shot on 85mm f/1.4, compressed background bokeh, desaturated cool tones with warm skin. Unstaged genuine interaction. Square 1:1 aspect ratio."),
("12-fundraisers-redirect", "1:1",
"Close-up of two hands holding a phone in a cafe. The screen shows a green-themed webpage with a large rounded button, generic and not readable. A marble cafe table surface with a flat white coffee in a ceramic cup and a torn croissant on a small plate beside the phone. Natural soft window light from the right. Shot on 50mm f/2.0, shallow depth of field focused on the phone screen. Warm cafe tones, European coffee shop atmosphere. No face visible. Square 1:1 aspect ratio."),
("13-fundraisers-dashboard", "16:9",
"Editorial product photography of a laptop screen showing a minimal analytics dashboard with a horizontal funnel chart in shades of blue, grey number cards across the top, and a simple data table below. The laptop sits on a clean white desk. Beside it, a small potted succulent and a closed notebook. Soft even studio lighting, no harsh shadows. Shot on 50mm f/4, sharp focus on screen, edges of laptop slightly soft. Clean, SaaS product marketing style. Landscape orientation 16:9 aspect ratio."),
# ── Volunteers page ──
("14-volunteers-hero", "1:1",
"Candid photograph of a young British-Pakistani man in his early 20s standing behind a round banquet table at a charity dinner. He wears a plain navy polo shirt and a clear plastic lanyard with a printed name badge. In the sharp foreground, a small clear acrylic A-frame stand with a QR code printed on white card sits on the white tablecloth. The volunteer is smiling at someone off-camera to the left. Background shows other round tables and warm pendant lighting, all soft and blurred. Shot on 35mm f/1.4 from table height, warm amber tones, available light. Energetic, genuine. Square 1:1 aspect ratio."),
("15-volunteers-phone-stats", "1:1",
"Dynamic low-angle photograph of a volunteer's torso and hand holding a phone at waist height. A black lanyard with a badge hangs from their neck, visible at the top of frame. The phone screen is bright showing a large number and a progress bar in blue, not readable. Behind and above, blurred figures walking through an event hall with overhead spotlights creating flare. Shot on 24mm f/2.0, slightly tilted angle, motion blur on background figures, available light. Raw, energetic, in-the-moment. Square 1:1 aspect ratio."),
("16-volunteers-leaderboard", "16:9",
"Editorial product photography of a phone screen showing a ranked list with numbered rows, small circular avatar placeholders, names, and bar charts beside each row. The phone lies flat on a dark surface. A lanyard and a paper name badge sit beside it. Soft directional studio light from the top left. Shot on macro lens, f/4, sharp focus on the screen, surroundings slightly dark. Clean, focused, app showcase. Landscape orientation 16:9 aspect ratio."),
# ── Organisations page ──
("17-orgs-hero-boardroom", "1:1",
"Documentary photograph of three people in a modest charity office meeting room around a rectangular table. A Black British woman in her 40s in a navy blazer is pointing at a laptop screen. A South Asian man in his 30s in a grey suit leans in to look. A white British man in his 50s in a rolled-sleeve shirt sits across, arms folded, listening. Papers and printed spreadsheets on the table. Grey carpet, a window with vertical blinds letting in flat daylight. Overhead fluorescent lighting. Shot on 28mm f/2.8, documentary wide angle, no one looking at camera, mid-discussion. Slightly cool tones, institutional setting. Square 1:1 aspect ratio."),
("18-orgs-pipeline", "1:1",
"Editorial product photography of a laptop screen showing a Kanban-style board with three columns of cards in white, each card showing a name, a currency amount, and a coloured status dot. The laptop is a MacBook on a plain wooden desk. A pair of reading glasses and a printed agenda document sit beside it. Soft even natural light from a window. Shot on 50mm f/3.5, focused on the screen, desk edges slightly out of focus. Neutral professional tones. Square 1:1 aspect ratio."),
("19-orgs-instalment-schedule", "16:9",
"Editorial product photography of a laptop screen showing a timeline or calendar view with monthly markers and progress bars in blue and grey. The laptop sits on a white conference table. Two chairs partially visible on either side, slightly blurred. A glass of water and a pen on the table. Clean fluorescent overhead lighting, even exposure. Shot on 35mm f/4, sharp focus on screen. Corporate clean, minimal, informational. Landscape orientation 16:9 aspect ratio."),
("20-orgs-laptop-desk", "1:1",
"Documentary photograph of a laptop open on a cluttered but real charity office desk. The screen shows a table with rows of data, not readable. A hand rests on the trackpad, wearing a simple watch. On the desk: a white ceramic mug with a tea bag tag hanging over the edge, a stack of manila folders, a small framed family photo, a desk lamp switched on. Mixed warm desk lamp light and cool daylight from a window on the right. Shot on 35mm f/2.0, focused on the laptop and hand, background slightly soft. Warm, lived-in, working moment. Square 1:1 aspect ratio."),
]
def generate_image(filename, aspect, prompt):
out_path = OUT_DIR / f"{filename}.jpg"
if out_path.exists():
print(f" SKIP (exists): {filename}")
return True
print(f" Generating: {filename} ({aspect})...")
try:
response = client.models.generate_content(
model=MODEL,
contents=prompt,
config=types.GenerateContentConfig(
response_modalities=["IMAGE", "TEXT"],
),
)
for part in response.candidates[0].content.parts:
if part.inline_data:
img_bytes = part.inline_data.data
with open(out_path, "wb") as f:
f.write(img_bytes)
size_kb = len(img_bytes) / 1024
print(f" DONE: {filename} ({size_kb:.0f} KB)")
return True
print(f" FAIL: {filename} - no image in response")
return False
except Exception as e:
print(f" ERROR: {filename} - {e}")
return False
if __name__ == "__main__":
print(f"Model: {MODEL} (Nano Banana Pro)")
print(f"Output: {OUT_DIR}")
print(f"Images: {len(IMAGES)}")
print()
ok = 0
fail = 0
for i, (fn, asp, pr) in enumerate(IMAGES, 1):
print(f"[{i}/{len(IMAGES)}]")
if generate_image(fn, asp, pr):
ok += 1
else:
fail += 1
if i < len(IMAGES):
time.sleep(5)
print(f"\nDone: {ok} generated, {fail} failed")
print(f"Files in: {OUT_DIR}")

View File

@@ -33,58 +33,56 @@ function LoginForm() {
} }
} }
// Auto-login as demo if ?demo=1
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => { if (isDemo) doLogin(undefined, "demo@pnpl.app", "demo1234") }, []) useEffect(() => { if (isDemo) doLogin(undefined, "demo@pnpl.app", "demo1234") }, [])
return ( return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5 p-4"> <div className="min-h-screen flex items-center justify-center bg-paper p-4">
<div className="w-full max-w-sm space-y-5"> <div className="w-full max-w-sm space-y-6">
{isDemo && ( {isDemo && (
<div className="text-center py-8"> <div className="text-center py-12">
<div className="inline-flex h-12 w-12 rounded-2xl bg-gradient-to-br from-trust-blue to-blue-600 items-center justify-center shadow-lg shadow-trust-blue/20 animate-pulse mb-3"> <div className="h-10 w-10 bg-midnight flex items-center justify-center mx-auto mb-4">
<span className="text-white text-xl">🤲</span> <span className="text-white text-sm font-black">P</span>
</div> </div>
<p className="text-sm font-medium text-trust-blue animate-pulse">Loading demo...</p> <p className="text-sm font-medium text-gray-500">Loading demo...</p>
</div> </div>
)} )}
{!isDemo && ( {!isDemo && (
<> <>
<div className="text-center"> <div className="text-center space-y-3">
<div className="inline-flex h-12 w-12 rounded-2xl bg-gradient-to-br from-trust-blue to-blue-600 items-center justify-center shadow-lg shadow-trust-blue/20 mb-3"> <div className="h-10 w-10 bg-midnight flex items-center justify-center mx-auto">
<span className="text-white text-xl">🤲</span> <span className="text-white text-sm font-black">P</span>
</div>
<div>
<h1 className="text-2xl font-black text-midnight">Welcome back</h1>
<p className="text-sm text-gray-500 mt-1">Sign in to your charity dashboard</p>
</div> </div>
<h1 className="text-2xl font-black text-gray-900">Welcome back</h1>
<p className="text-sm text-muted-foreground mt-1">Sign in to your charity dashboard</p>
</div> </div>
{/* Social login */} {/* Google */}
<div className="space-y-2"> <button
<button onClick={() => signIn("auth0", { callbackUrl: "/dashboard" })}
onClick={() => signIn("auth0", { callbackUrl: "/dashboard" })} className="w-full flex items-center justify-center gap-2 border border-gray-200 bg-white px-4 py-3 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
className="w-full flex items-center justify-center gap-2 rounded-xl border border-gray-200 bg-white px-4 py-3 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-all" >
> <svg className="h-4 w-4" viewBox="0 0 24 24"><path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z" fill="#4285F4"/><path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/><path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/><path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/></svg>
<svg className="h-4 w-4" viewBox="0 0 24 24"><path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z" fill="#4285F4"/><path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/><path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/><path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/></svg> Continue with Google
Continue with Google </button>
</button>
</div>
<div className="relative"> <div className="relative">
<div className="absolute inset-0 flex items-center"><div className="w-full border-t" /></div> <div className="absolute inset-0 flex items-center"><div className="w-full border-t border-gray-200" /></div>
<div className="relative flex justify-center text-xs"><span className="bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5 px-3 text-muted-foreground">or sign in with email</span></div> <div className="relative flex justify-center text-xs"><span className="bg-paper px-3 text-gray-400">or sign in with email</span></div>
</div> </div>
{/* Email/password form */}
<form onSubmit={(e) => doLogin(e)} className="space-y-3"> <form onSubmit={(e) => doLogin(e)} className="space-y-3">
{error && ( {error && (
<div className="rounded-xl bg-danger-red/10 border border-danger-red/20 p-2.5 text-sm text-danger-red text-center">{error}</div> <div className="border border-alert-red/20 bg-alert-red/5 p-2.5 text-sm text-alert-red text-center">{error}</div>
)} )}
<input <input
type="email" type="email"
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
className="w-full rounded-xl border border-gray-200 px-4 py-3 text-sm focus:border-trust-blue focus:ring-2 focus:ring-trust-blue/20 outline-none transition-all" className="w-full border border-gray-200 px-4 py-3 text-sm focus:border-promise-blue focus:ring-1 focus:ring-promise-blue/20 outline-none transition-colors"
placeholder="Email" placeholder="Email"
required required
/> />
@@ -92,36 +90,36 @@ function LoginForm() {
type="password" type="password"
value={password} value={password}
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
className="w-full rounded-xl border border-gray-200 px-4 py-3 text-sm focus:border-trust-blue focus:ring-2 focus:ring-trust-blue/20 outline-none transition-all" className="w-full border border-gray-200 px-4 py-3 text-sm focus:border-promise-blue focus:ring-1 focus:ring-promise-blue/20 outline-none transition-colors"
placeholder="Password" placeholder="Password"
required required
/> />
<button <button
type="submit" type="submit"
disabled={loading} disabled={loading}
className="w-full rounded-xl bg-trust-blue px-4 py-3 text-sm font-semibold text-white hover:bg-trust-blue/90 disabled:opacity-50 transition-all" className="w-full bg-midnight px-4 py-3 text-sm font-semibold text-white hover:bg-gray-800 disabled:opacity-50 transition-colors"
> >
{loading ? "Signing in..." : "Sign In"} {loading ? "Signing in..." : "Sign In"}
</button> </button>
</form> </form>
<div className="relative"> <div className="relative">
<div className="absolute inset-0 flex items-center"><div className="w-full border-t" /></div> <div className="absolute inset-0 flex items-center"><div className="w-full border-t border-gray-200" /></div>
<div className="relative flex justify-center text-xs"><span className="bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5 px-2 text-muted-foreground">or</span></div> <div className="relative flex justify-center text-xs"><span className="bg-paper px-2 text-gray-400">or</span></div>
</div> </div>
<button <button
type="button" type="button"
onClick={() => doLogin(undefined, "demo@pnpl.app", "demo1234")} onClick={() => doLogin(undefined, "demo@pnpl.app", "demo1234")}
disabled={loading} disabled={loading}
className="w-full rounded-xl border-2 border-dashed border-gray-200 px-4 py-3 text-sm font-medium text-muted-foreground hover:border-trust-blue hover:text-trust-blue disabled:opacity-50 transition-all" className="w-full border border-dashed border-gray-300 px-4 py-3 text-sm font-medium text-gray-500 hover:border-promise-blue hover:text-promise-blue disabled:opacity-50 transition-colors"
> >
🎮 Try the Demo no signup needed Try the Demo no signup needed
</button> </button>
<p className="text-center text-sm text-muted-foreground"> <p className="text-center text-sm text-gray-500">
Don&apos;t have an account?{" "} Don&apos;t have an account?{" "}
<Link href="/signup" className="text-trust-blue font-semibold hover:underline">Get Started Free</Link> <Link href="/signup" className="text-promise-blue font-semibold hover:underline">Get Started Free</Link>
</p> </p>
</> </>
)} )}
@@ -132,7 +130,7 @@ function LoginForm() {
export default function LoginPage() { export default function LoginPage() {
return ( return (
<Suspense fallback={<div className="min-h-screen flex items-center justify-center"><div className="animate-spin h-8 w-8 border-2 border-trust-blue border-t-transparent rounded-full" /></div>}> <Suspense fallback={<div className="min-h-screen flex items-center justify-center bg-paper"><div className="animate-spin h-6 w-6 border-2 border-promise-blue border-t-transparent rounded-full" /></div>}>
<LoginForm /> <LoginForm />
</Suspense> </Suspense>
) )

View File

@@ -38,7 +38,6 @@ export default function SignupPage() {
return return
} }
// Auto sign in and go straight to dashboard
const result = await signIn("credentials", { email, password, redirect: false }) const result = await signIn("credentials", { email, password, redirect: false })
if (result?.error) { if (result?.error) {
setError("Account created — please sign in") setError("Account created — please sign in")
@@ -54,86 +53,83 @@ export default function SignupPage() {
if (step === "loading") { if (step === "loading") {
return ( return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5"> <div className="min-h-screen flex items-center justify-center bg-paper">
<div className="text-center space-y-4"> <div className="text-center space-y-4">
<div className="inline-flex h-14 w-14 rounded-2xl bg-gradient-to-br from-trust-blue to-blue-600 items-center justify-center shadow-lg shadow-trust-blue/20 animate-pulse"> <div className="h-10 w-10 bg-midnight flex items-center justify-center mx-auto">
<span className="text-white text-2xl">🤲</span> <span className="text-white text-sm font-black">P</span>
</div> </div>
<p className="text-sm font-medium text-trust-blue animate-pulse">Setting up your charity...</p> <p className="text-sm font-medium text-gray-500">Setting up your charity...</p>
</div> </div>
</div> </div>
) )
} }
return ( return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5 p-4"> <div className="min-h-screen flex items-center justify-center bg-paper p-4">
<div className="w-full max-w-sm space-y-5"> <div className="w-full max-w-sm space-y-6">
<div className="text-center"> <div className="text-center space-y-3">
<div className="inline-flex h-12 w-12 rounded-2xl bg-gradient-to-br from-trust-blue to-blue-600 items-center justify-center shadow-lg shadow-trust-blue/20 mb-3"> <div className="h-10 w-10 bg-midnight flex items-center justify-center mx-auto">
<span className="text-white text-xl">🤲</span> <span className="text-white text-sm font-black">P</span>
</div>
<div>
<h1 className="text-2xl font-black text-midnight">Start collecting pledges</h1>
<p className="text-sm text-gray-500 mt-1">Free. 30 seconds. No card.</p>
</div> </div>
<h1 className="text-2xl font-black text-gray-900">Start collecting pledges</h1>
<p className="text-sm text-muted-foreground mt-1">Free. 30 seconds. No card.</p>
</div> </div>
{/* Google signup */}
<button <button
onClick={signUpWithGoogle} onClick={signUpWithGoogle}
className="w-full flex items-center justify-center gap-2 rounded-xl border border-gray-200 bg-white px-4 py-3 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-all" className="w-full flex items-center justify-center gap-2 border border-gray-200 bg-white px-4 py-3 text-sm font-medium text-gray-700 hover:bg-gray-50 transition-colors"
> >
<svg className="h-4 w-4" viewBox="0 0 24 24"><path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z" fill="#4285F4"/><path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/><path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/><path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/></svg> <svg className="h-4 w-4" viewBox="0 0 24 24"><path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 01-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z" fill="#4285F4"/><path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"/><path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"/><path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"/></svg>
Sign up with Google Sign up with Google
</button> </button>
<div className="relative"> <div className="relative">
<div className="absolute inset-0 flex items-center"><div className="w-full border-t" /></div> <div className="absolute inset-0 flex items-center"><div className="w-full border-t border-gray-200" /></div>
<div className="relative flex justify-center text-xs"><span className="bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5 px-3 text-muted-foreground">or use email</span></div> <div className="relative flex justify-center text-xs"><span className="bg-paper px-3 text-gray-400">or use email</span></div>
</div> </div>
<form onSubmit={handleSubmit} className="space-y-3"> <form onSubmit={handleSubmit} className="space-y-3">
{error && ( {error && (
<div className="rounded-xl bg-danger-red/10 border border-danger-red/20 p-2.5 text-sm text-danger-red text-center">{error}</div> <div className="border border-alert-red/20 bg-alert-red/5 p-2.5 text-sm text-alert-red text-center">{error}</div>
)} )}
<input <input
type="text" type="text"
value={charityName} value={charityName}
onChange={(e) => setCharityName(e.target.value)} onChange={(e) => setCharityName(e.target.value)}
className="w-full rounded-xl border border-gray-200 px-4 py-3 text-sm focus:border-trust-blue focus:ring-2 focus:ring-trust-blue/20 outline-none transition-all" className="w-full border border-gray-200 px-4 py-3 text-sm focus:border-promise-blue focus:ring-1 focus:ring-promise-blue/20 outline-none transition-colors"
placeholder="Your charity or mosque name" placeholder="Your charity or mosque name"
required required
autoFocus autoFocus
/> />
<input <input
type="email" type="email"
value={email} value={email}
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
className="w-full rounded-xl border border-gray-200 px-4 py-3 text-sm focus:border-trust-blue focus:ring-2 focus:ring-trust-blue/20 outline-none transition-all" className="w-full border border-gray-200 px-4 py-3 text-sm focus:border-promise-blue focus:ring-1 focus:ring-promise-blue/20 outline-none transition-colors"
placeholder="Your email" placeholder="Your email"
required required
/> />
<input <input
type="password" type="password"
value={password} value={password}
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
className="w-full rounded-xl border border-gray-200 px-4 py-3 text-sm focus:border-trust-blue focus:ring-2 focus:ring-trust-blue/20 outline-none transition-all" className="w-full border border-gray-200 px-4 py-3 text-sm focus:border-promise-blue focus:ring-1 focus:ring-promise-blue/20 outline-none transition-colors"
placeholder="Pick a password (8+ chars)" placeholder="Pick a password (8+ chars)"
required required
minLength={8} minLength={8}
/> />
<button <button
type="submit" type="submit"
className="w-full rounded-xl bg-trust-blue px-4 py-3.5 text-sm font-bold text-white hover:bg-trust-blue/90 transition-all shadow-lg shadow-trust-blue/20" className="w-full bg-midnight px-4 py-3.5 text-sm font-bold text-white hover:bg-gray-800 transition-colors"
> >
Create Account Create Account
</button> </button>
</form> </form>
<p className="text-center text-xs text-muted-foreground"> <p className="text-center text-xs text-gray-500">
Already have an account? <Link href="/login" className="text-trust-blue font-semibold hover:underline">Sign in</Link> Already have an account? <Link href="/login" className="text-promise-blue font-semibold hover:underline">Sign in</Link>
</p> </p>
</div> </div>
</div> </div>

View File

@@ -93,7 +93,7 @@ export default function AdminPage() {
<CardContent> <CardContent>
<div className="flex gap-3 overflow-x-auto"> <div className="flex gap-3 overflow-x-auto">
{Object.entries(data.byStatus).map(([status, { count, amount }]) => ( {Object.entries(data.byStatus).map(([status, { count, amount }]) => (
<div key={status} className="flex-shrink-0 rounded-xl bg-muted/50 px-4 py-2 text-center min-w-[100px]"> <div key={status} className="flex-shrink-0 rounded-lg bg-muted/50 px-4 py-2 text-center min-w-[100px]">
<Badge variant={status === "paid" ? "success" : status === "overdue" ? "destructive" : "secondary"} className="text-[10px]">{status}</Badge> <Badge variant={status === "paid" ? "success" : status === "overdue" ? "destructive" : "secondary"} className="text-[10px]">{status}</Badge>
<p className="text-lg font-bold mt-1">{count}</p> <p className="text-lg font-bold mt-1">{count}</p>
<p className="text-[10px] text-muted-foreground">{fmt(amount)}</p> <p className="text-[10px] text-muted-foreground">{fmt(amount)}</p>

View File

@@ -141,7 +141,7 @@ export default function CampaignLinksPage() {
<Card key={src.id} className="hover:shadow-md transition-shadow"> <Card key={src.id} className="hover:shadow-md transition-shadow">
<CardContent className="pt-6 space-y-4"> <CardContent className="pt-6 space-y-4">
{/* QR Code — compact */} {/* QR Code — compact */}
<div className="max-w-[140px] mx-auto bg-white rounded-xl flex items-center justify-center p-1.5"> <div className="max-w-[140px] mx-auto bg-white rounded-lg flex items-center justify-center p-1.5">
<QRCodeCanvas url={`${baseUrl}/p/${src.code}`} size={128} /> <QRCodeCanvas url={`${baseUrl}/p/${src.code}`} size={128} />
</div> </div>

View File

@@ -250,7 +250,7 @@ export default function EventsPage() {
<button <button
type="button" type="button"
onClick={() => setForm(f => ({ ...f, paymentMode: "self" }))} onClick={() => setForm(f => ({ ...f, paymentMode: "self" }))}
className={`rounded-xl border p-3 text-left text-xs transition-all ${form.paymentMode === "self" ? "border-trust-blue bg-trust-blue/5" : "border-gray-200"}`} className={`rounded-lg border p-3 text-left text-xs transition-all ${form.paymentMode === "self" ? "border-trust-blue bg-trust-blue/5" : "border-gray-200"}`}
> >
<span className="font-bold block">🏦 Bank transfer</span> <span className="font-bold block">🏦 Bank transfer</span>
<span className="text-muted-foreground">We show our bank details</span> <span className="text-muted-foreground">We show our bank details</span>
@@ -258,7 +258,7 @@ export default function EventsPage() {
<button <button
type="button" type="button"
onClick={() => setForm(f => ({ ...f, paymentMode: "external" }))} onClick={() => setForm(f => ({ ...f, paymentMode: "external" }))}
className={`rounded-xl border p-3 text-left text-xs transition-all ${form.paymentMode === "external" ? "border-warm-amber bg-warm-amber/5" : "border-gray-200"}`} className={`rounded-lg border p-3 text-left text-xs transition-all ${form.paymentMode === "external" ? "border-warm-amber bg-warm-amber/5" : "border-gray-200"}`}
> >
<span className="font-bold block">🔗 External page</span> <span className="font-bold block">🔗 External page</span>
<span className="text-muted-foreground">LaunchGood, Enthuse, etc.</span> <span className="text-muted-foreground">LaunchGood, Enthuse, etc.</span>
@@ -267,7 +267,7 @@ export default function EventsPage() {
</div> </div>
{form.paymentMode === "external" && ( {form.paymentMode === "external" && (
<div className="space-y-3 rounded-xl border border-warm-amber/20 bg-warm-amber/5 p-3"> <div className="space-y-3 rounded-lg border border-warm-amber/20 bg-warm-amber/5 p-3">
<div className="space-y-2"> <div className="space-y-2">
<Label>Fundraising page URL *</Label> <Label>Fundraising page URL *</Label>
<div className="relative"> <div className="relative">
@@ -285,7 +285,7 @@ export default function EventsPage() {
<select <select
value={form.externalPlatform} value={form.externalPlatform}
onChange={(e) => setForm(f => ({ ...f, externalPlatform: e.target.value }))} onChange={(e) => setForm(f => ({ ...f, externalPlatform: e.target.value }))}
className="w-full rounded-xl border border-gray-200 px-3 py-2 text-sm" className="w-full rounded-lg border border-gray-200 px-3 py-2 text-sm"
> >
<option value="">Select platform...</option> <option value="">Select platform...</option>
<option value="launchgood">🌙 LaunchGood</option> <option value="launchgood">🌙 LaunchGood</option>
@@ -302,7 +302,7 @@ export default function EventsPage() {
<button <button
type="button" type="button"
onClick={() => setForm(f => ({ ...f, zakatEligible: !f.zakatEligible }))} onClick={() => setForm(f => ({ ...f, zakatEligible: !f.zakatEligible }))}
className={`w-full flex items-center justify-between rounded-xl border-2 p-3 text-left transition-all ${ className={`w-full flex items-center justify-between rounded-lg border-2 p-3 text-left transition-all ${
form.zakatEligible ? "border-trust-blue bg-trust-blue/5" : "border-gray-200" form.zakatEligible ? "border-trust-blue bg-trust-blue/5" : "border-gray-200"
}`} }`}
> >

View File

@@ -73,7 +73,7 @@ export default function ExportsPage() {
<li>Event and reference for audit trail</li> <li>Event and reference for audit trail</li>
</ul> </ul>
</div> </div>
<div className="rounded-xl bg-success-green/5 border border-success-green/20 p-3"> <div className="rounded-lg bg-success-green/5 border border-success-green/20 p-3">
<p className="text-xs text-success-green font-medium"> <p className="text-xs text-success-green font-medium">
💷 Claim 25p for every £1 donated by a UK taxpayer 💷 Claim 25p for every £1 donated by a UK taxpayer
</p> </p>
@@ -101,7 +101,7 @@ export default function ExportsPage() {
</code> </code>
<p className="text-xs">Returns pending reminders with donor contact info for external email/SMS.</p> <p className="text-xs">Returns pending reminders with donor contact info for external email/SMS.</p>
</div> </div>
<div className="rounded-xl bg-trust-blue/5 border border-trust-blue/20 p-3"> <div className="rounded-lg bg-trust-blue/5 border border-trust-blue/20 p-3">
<p className="text-xs text-trust-blue font-medium"> <p className="text-xs text-trust-blue font-medium">
💡 Connect to Zapier or n8n to send automatic reminder emails and SMS 💡 Connect to Zapier or n8n to send automatic reminder emails and SMS
</p> </p>

View File

@@ -24,31 +24,31 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
const user = session?.user as any const user = session?.user as any
return ( return (
<div className="min-h-screen bg-gray-50/50"> <div className="min-h-screen bg-paper">
{/* Top bar */} {/* Top bar — sharp, no blur */}
<header className="sticky top-0 z-40 border-b bg-white/80 backdrop-blur-xl"> <header className="sticky top-0 z-40 border-b border-gray-200 bg-white">
<div className="flex h-14 items-center gap-4 px-4 md:px-6"> <div className="flex h-14 items-center gap-4 px-4 md:px-6">
<Link href="/dashboard" className="flex items-center gap-2.5"> <Link href="/dashboard" className="flex items-center gap-2.5">
<div className="h-8 w-8 rounded-xl bg-gradient-to-br from-trust-blue to-blue-600 flex items-center justify-center shadow-lg shadow-trust-blue/20"> <div className="h-7 w-7 bg-midnight flex items-center justify-center">
<span className="text-white font-bold text-sm">P</span> <span className="text-white text-xs font-black">P</span>
</div> </div>
<div className="hidden sm:block"> <div className="hidden sm:block">
<span className="font-black text-sm text-gray-900">{user?.orgName || "Pledge Now, Pay Later"}</span> <span className="font-black text-sm text-midnight">{user?.orgName || "Pledge Now, Pay Later"}</span>
</div> </div>
</Link> </Link>
<div className="flex-1" /> <div className="flex-1" />
<Link href="/dashboard/events" className="hidden md:block"> <Link href="/dashboard/events" className="hidden md:block">
<button className="inline-flex items-center gap-1.5 rounded-lg bg-trust-blue px-3 py-1.5 text-xs font-semibold text-white hover:bg-trust-blue/90 transition-colors"> <button className="inline-flex items-center gap-1.5 bg-midnight px-3 py-1.5 text-xs font-semibold text-white hover:bg-gray-800 transition-colors">
<Plus className="h-3 w-3" /> New Campaign <Plus className="h-3 w-3" /> New Campaign
</button> </button>
</Link> </Link>
<Link href="/" className="text-xs text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1"> <Link href="/" className="text-xs text-gray-400 hover:text-midnight transition-colors flex items-center gap-1">
<ExternalLink className="h-3 w-3" /> <ExternalLink className="h-3 w-3" />
</Link> </Link>
{session && ( {session && (
<button <button
onClick={() => signOut({ callbackUrl: "/login" })} onClick={() => signOut({ callbackUrl: "/login" })}
className="text-xs text-muted-foreground hover:text-foreground transition-colors flex items-center gap-1" className="text-xs text-gray-400 hover:text-midnight transition-colors flex items-center gap-1"
> >
<LogOut className="h-3 w-3" /> <LogOut className="h-3 w-3" />
</button> </button>
@@ -57,8 +57,8 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
</header> </header>
<div className="flex"> <div className="flex">
{/* Desktop sidebar */} {/* Desktop sidebar — clean, no decorative elements */}
<aside className="hidden md:flex w-56 flex-col border-r bg-white min-h-[calc(100vh-3.5rem)] py-3 px-2"> <aside className="hidden md:flex w-52 flex-col border-r border-gray-200 bg-white min-h-[calc(100vh-3.5rem)] py-3 px-2">
<nav className="space-y-0.5"> <nav className="space-y-0.5">
{navItems.map((item) => { {navItems.map((item) => {
const isActive = pathname === item.href || (item.href !== "/dashboard" && pathname.startsWith(item.href)) const isActive = pathname === item.href || (item.href !== "/dashboard" && pathname.startsWith(item.href))
@@ -67,10 +67,10 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
key={item.href} key={item.href}
href={item.href} href={item.href}
className={cn( className={cn(
"flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm font-medium transition-colors", "flex items-center gap-2.5 px-3 py-2 text-sm font-medium transition-colors",
isActive isActive
? "bg-trust-blue/5 text-trust-blue" ? "bg-promise-blue/5 text-promise-blue border-l-2 border-promise-blue -ml-0.5"
: "text-muted-foreground hover:bg-gray-100 hover:text-foreground" : "text-gray-500 hover:bg-gray-50 hover:text-midnight"
)} )}
> >
<item.icon className="h-4 w-4" /> <item.icon className="h-4 w-4" />
@@ -80,14 +80,14 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
})} })}
{user?.role === "super_admin" && ( {user?.role === "super_admin" && (
<> <>
<div className="my-2 border-t" /> <div className="my-2 border-t border-gray-100" />
<Link <Link
href={adminNav.href} href={adminNav.href}
className={cn( className={cn(
"flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm font-medium transition-colors", "flex items-center gap-2.5 px-3 py-2 text-sm font-medium transition-colors",
pathname === adminNav.href pathname === adminNav.href
? "bg-trust-blue/5 text-trust-blue" ? "bg-promise-blue/5 text-promise-blue border-l-2 border-promise-blue -ml-0.5"
: "text-muted-foreground hover:bg-gray-100 hover:text-foreground" : "text-gray-500 hover:bg-gray-50 hover:text-midnight"
)} )}
> >
<adminNav.icon className="h-4 w-4" /> <adminNav.icon className="h-4 w-4" />
@@ -98,12 +98,12 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
</nav> </nav>
<div className="mt-auto px-2 pt-4"> <div className="mt-auto px-2 pt-4">
<div className="rounded-xl bg-gradient-to-br from-trust-blue/5 to-warm-amber/5 border p-3 space-y-1.5"> <div className="border border-gray-200 p-3 space-y-1.5">
<p className="text-xs font-semibold">Need help?</p> <p className="text-xs font-bold text-midnight">Need help?</p>
<p className="text-[10px] text-muted-foreground leading-relaxed"> <p className="text-[10px] text-gray-500 leading-relaxed">
Get a fractional Head of Technology to optimise your charity&apos;s digital stack. Get a fractional Head of Technology to optimise your charity&apos;s digital stack.
</p> </p>
<Link href="/dashboard/apply" className="inline-block text-[10px] font-semibold text-trust-blue hover:underline"> <Link href="/dashboard/apply" className="inline-block text-[10px] font-semibold text-promise-blue hover:underline">
Learn more Learn more
</Link> </Link>
</div> </div>
@@ -111,7 +111,7 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
</aside> </aside>
{/* Mobile bottom nav */} {/* Mobile bottom nav */}
<nav className="md:hidden fixed bottom-0 left-0 right-0 z-40 border-t bg-white/95 backdrop-blur-xl flex justify-around py-1.5 px-1"> <nav className="md:hidden fixed bottom-0 left-0 right-0 z-40 border-t border-gray-200 bg-white flex justify-around py-1.5 px-1">
{navItems.slice(0, 5).map((item) => { {navItems.slice(0, 5).map((item) => {
const isActive = pathname === item.href || (item.href !== "/dashboard" && pathname.startsWith(item.href)) const isActive = pathname === item.href || (item.href !== "/dashboard" && pathname.startsWith(item.href))
return ( return (
@@ -119,8 +119,8 @@ export default function DashboardLayout({ children }: { children: React.ReactNod
key={item.href} key={item.href}
href={item.href} href={item.href}
className={cn( className={cn(
"flex flex-col items-center gap-0.5 py-1 px-2 rounded-lg transition-colors", "flex flex-col items-center gap-0.5 py-1 px-2 transition-colors",
isActive ? "text-trust-blue" : "text-muted-foreground" isActive ? "text-promise-blue" : "text-gray-400"
)} )}
> >
<item.icon className="h-5 w-5" /> <item.icon className="h-5 w-5" />

View File

@@ -9,15 +9,15 @@ export default function DashboardLoading() {
</div> </div>
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4"> <div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
{[...Array(4)].map((_, i) => ( {[...Array(4)].map((_, i) => (
<Skeleton key={i} className="h-24 rounded-2xl" /> <Skeleton key={i} className="h-24 rounded-lg" />
))} ))}
</div> </div>
<Skeleton className="h-16 rounded-2xl" /> <Skeleton className="h-16 rounded-lg" />
<div className="grid md:grid-cols-2 gap-4"> <div className="grid md:grid-cols-2 gap-4">
<Skeleton className="h-64 rounded-2xl" /> <Skeleton className="h-64 rounded-lg" />
<Skeleton className="h-64 rounded-2xl" /> <Skeleton className="h-64 rounded-lg" />
</div> </div>
<Skeleton className="h-96 rounded-2xl" /> <Skeleton className="h-96 rounded-lg" />
</div> </div>
) )
} }

View File

@@ -43,9 +43,9 @@ function RolePicker({ onSelect }: { onSelect: (role: string) => void }) {
<div className="grid grid-cols-2 gap-3"> <div className="grid grid-cols-2 gap-3">
<button <button
onClick={() => onSelect("charity")} onClick={() => onSelect("charity")}
className="rounded-2xl border-2 border-gray-100 hover:border-trust-blue bg-white p-5 text-center space-y-2 transition-all hover:shadow-md group" className="rounded-lg border-2 border-gray-100 hover:border-trust-blue bg-white p-5 text-center space-y-2 transition-all hover:shadow-md group"
> >
<div className="mx-auto w-12 h-12 rounded-xl bg-trust-blue/10 flex items-center justify-center group-hover:scale-110 transition-transform"> <div className="mx-auto w-12 h-12 rounded-lg bg-trust-blue/10 flex items-center justify-center group-hover:scale-110 transition-transform">
<Building2 className="h-6 w-6 text-trust-blue" /> <Building2 className="h-6 w-6 text-trust-blue" />
</div> </div>
<p className="text-sm font-bold text-gray-900">Charity / Mosque</p> <p className="text-sm font-bold text-gray-900">Charity / Mosque</p>
@@ -53,9 +53,9 @@ function RolePicker({ onSelect }: { onSelect: (role: string) => void }) {
</button> </button>
<button <button
onClick={() => onSelect("fundraiser")} onClick={() => onSelect("fundraiser")}
className="rounded-2xl border-2 border-gray-100 hover:border-warm-amber bg-white p-5 text-center space-y-2 transition-all hover:shadow-md group" className="rounded-lg border-2 border-gray-100 hover:border-warm-amber bg-white p-5 text-center space-y-2 transition-all hover:shadow-md group"
> >
<div className="mx-auto w-12 h-12 rounded-xl bg-warm-amber/10 flex items-center justify-center group-hover:scale-110 transition-transform"> <div className="mx-auto w-12 h-12 rounded-lg bg-warm-amber/10 flex items-center justify-center group-hover:scale-110 transition-transform">
<Heart className="h-6 w-6 text-warm-amber" /> <Heart className="h-6 w-6 text-warm-amber" />
</div> </div>
<p className="text-sm font-bold text-gray-900">Personal Fundraiser</p> <p className="text-sm font-bold text-gray-900">Personal Fundraiser</p>
@@ -85,14 +85,14 @@ function GettingStartedBanner({
const isFirstTime = ob.completed === 0 && (!ob.orgType || ob.orgType === "charity") const isFirstTime = ob.completed === 0 && (!ob.orgType || ob.orgType === "charity")
return ( return (
<div className="rounded-2xl border border-trust-blue/20 bg-gradient-to-r from-trust-blue/5 via-white to-warm-amber/5 p-5 space-y-4 relative"> <div className="rounded-lg border border-trust-blue/20 bg-paper p-5 space-y-4 relative">
{/* Dismiss X */} {/* Dismiss X */}
<button onClick={onDismiss} className="absolute top-3 right-3 text-muted-foreground hover:text-foreground p-1"> <button onClick={onDismiss} className="absolute top-3 right-3 text-muted-foreground hover:text-foreground p-1">
<X className="h-4 w-4" /> <X className="h-4 w-4" />
</button> </button>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="h-10 w-10 rounded-xl bg-gradient-to-br from-trust-blue to-blue-600 flex items-center justify-center flex-shrink-0"> <div className="h-10 w-10 rounded-lg bg-midnight flex items-center justify-center flex-shrink-0">
<span className="text-white text-lg">🤲</span> <span className="text-white text-lg">🤲</span>
</div> </div>
<div> <div>
@@ -100,7 +100,7 @@ function GettingStartedBanner({
{isFirstTime ? "Welcome! What best describes you?" : `Getting started · ${ob.completed}/${ob.total}`} {isFirstTime ? "Welcome! What best describes you?" : `Getting started · ${ob.completed}/${ob.total}`}
</h2> </h2>
{!isFirstTime && ( {!isFirstTime && (
<Progress value={(ob.completed / ob.total) * 100} className="h-1.5 mt-1.5 w-32" indicatorClassName="bg-gradient-to-r from-trust-blue to-success-green" /> <Progress value={(ob.completed / ob.total) * 100} className="h-1.5 mt-1.5 w-32" indicatorClassName="bg-promise-blue" />
)} )}
</div> </div>
</div> </div>
@@ -113,7 +113,7 @@ function GettingStartedBanner({
const isNext = !step.done && ob.steps.slice(0, i).every(s => s.done) const isNext = !step.done && ob.steps.slice(0, i).every(s => s.done)
return ( return (
<Link key={step.id} href={step.href}> <Link key={step.id} href={step.href}>
<div className={`flex items-center gap-2.5 rounded-xl border px-3 py-2.5 transition-all ${ <div className={`flex items-center gap-2.5 rounded-lg border px-3 py-2.5 transition-all ${
step.done ? "bg-success-green/5 border-success-green/20" : step.done ? "bg-success-green/5 border-success-green/20" :
isNext ? "bg-trust-blue/5 border-trust-blue/20 shadow-sm" : isNext ? "bg-trust-blue/5 border-trust-blue/20 shadow-sm" :
"bg-white border-gray-100" "bg-white border-gray-100"
@@ -233,7 +233,7 @@ export default function DashboardPage() {
<Card className={isEmpty ? "opacity-60" : ""}> <Card className={isEmpty ? "opacity-60" : ""}>
<CardContent className="pt-5 pb-4"> <CardContent className="pt-5 pb-4">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="rounded-xl bg-trust-blue/10 p-2.5"><Users className="h-5 w-5 text-trust-blue" /></div> <div className="rounded-lg bg-trust-blue/10 p-2.5"><Users className="h-5 w-5 text-trust-blue" /></div>
<div> <div>
<p className="text-2xl font-black">{s.totalPledges}</p> <p className="text-2xl font-black">{s.totalPledges}</p>
<p className="text-xs text-muted-foreground">Total Pledges</p> <p className="text-xs text-muted-foreground">Total Pledges</p>
@@ -244,7 +244,7 @@ export default function DashboardPage() {
<Card className={isEmpty ? "opacity-60" : ""}> <Card className={isEmpty ? "opacity-60" : ""}>
<CardContent className="pt-5 pb-4"> <CardContent className="pt-5 pb-4">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="rounded-xl bg-warm-amber/10 p-2.5"><Banknote className="h-5 w-5 text-warm-amber" /></div> <div className="rounded-lg bg-warm-amber/10 p-2.5"><Banknote className="h-5 w-5 text-warm-amber" /></div>
<div> <div>
<p className="text-2xl font-black">{formatPence(s.totalPledgedPence)}</p> <p className="text-2xl font-black">{formatPence(s.totalPledgedPence)}</p>
<p className="text-xs text-muted-foreground">Total Pledged</p> <p className="text-xs text-muted-foreground">Total Pledged</p>
@@ -255,7 +255,7 @@ export default function DashboardPage() {
<Card className={isEmpty ? "opacity-60" : ""}> <Card className={isEmpty ? "opacity-60" : ""}>
<CardContent className="pt-5 pb-4"> <CardContent className="pt-5 pb-4">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="rounded-xl bg-success-green/10 p-2.5"><TrendingUp className="h-5 w-5 text-success-green" /></div> <div className="rounded-lg bg-success-green/10 p-2.5"><TrendingUp className="h-5 w-5 text-success-green" /></div>
<div> <div>
<p className="text-2xl font-black">{formatPence(s.totalCollectedPence)}</p> <p className="text-2xl font-black">{formatPence(s.totalCollectedPence)}</p>
<p className="text-xs text-muted-foreground">Collected ({s.collectionRate}%)</p> <p className="text-xs text-muted-foreground">Collected ({s.collectionRate}%)</p>
@@ -266,7 +266,7 @@ export default function DashboardPage() {
<Card className={isEmpty ? "opacity-60" : s.overdueRate > 10 ? "border-danger-red/30" : ""}> <Card className={isEmpty ? "opacity-60" : s.overdueRate > 10 ? "border-danger-red/30" : ""}>
<CardContent className="pt-5 pb-4"> <CardContent className="pt-5 pb-4">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="rounded-xl bg-danger-red/10 p-2.5"><AlertTriangle className="h-5 w-5 text-danger-red" /></div> <div className="rounded-lg bg-danger-red/10 p-2.5"><AlertTriangle className="h-5 w-5 text-danger-red" /></div>
<div> <div>
<p className="text-2xl font-black">{byStatus.overdue || 0}</p> <p className="text-2xl font-black">{byStatus.overdue || 0}</p>
<p className="text-xs text-muted-foreground">Overdue</p> <p className="text-xs text-muted-foreground">Overdue</p>
@@ -283,7 +283,7 @@ export default function DashboardPage() {
<span className="text-sm font-medium">Pledged Collected</span> <span className="text-sm font-medium">Pledged Collected</span>
<span className="text-sm font-bold text-muted-foreground">{s.collectionRate}%</span> <span className="text-sm font-bold text-muted-foreground">{s.collectionRate}%</span>
</div> </div>
<Progress value={s.collectionRate} indicatorClassName="bg-gradient-to-r from-trust-blue to-success-green" /> <Progress value={s.collectionRate} indicatorClassName="bg-promise-blue" />
<div className="flex justify-between mt-2 text-xs text-muted-foreground"> <div className="flex justify-between mt-2 text-xs text-muted-foreground">
<span>{formatPence(s.totalCollectedPence)} collected</span> <span>{formatPence(s.totalCollectedPence)} collected</span>
<span>{formatPence(s.totalPledgedPence - s.totalCollectedPence)} outstanding</span> <span>{formatPence(s.totalPledgedPence - s.totalCollectedPence)} outstanding</span>

View File

@@ -251,7 +251,7 @@ export default function PledgesPage() {
{/* Collection progress */} {/* Collection progress */}
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Progress value={collectionRate} className="flex-1 h-2" indicatorClassName="bg-gradient-to-r from-trust-blue to-success-green" /> <Progress value={collectionRate} className="flex-1 h-2" indicatorClassName="bg-promise-blue" />
<span className="text-sm font-medium text-muted-foreground whitespace-nowrap"> <span className="text-sm font-medium text-muted-foreground whitespace-nowrap">
{formatPence(stats.totalCollected)} / {formatPence(stats.totalPledged)} {formatPence(stats.totalCollected)} / {formatPence(stats.totalPledged)}
</span> </span>

View File

@@ -130,7 +130,7 @@ export default function ReconcilePage() {
</div> </div>
{/* File upload */} {/* File upload */}
<div className="border-2 border-dashed border-gray-200 rounded-2xl p-8 text-center hover:border-trust-blue/50 transition-colors"> <div className="border-2 border-dashed border-gray-200 rounded-lg p-8 text-center hover:border-trust-blue/50 transition-colors">
<Upload className="h-10 w-10 text-muted-foreground mx-auto mb-3" /> <Upload className="h-10 w-10 text-muted-foreground mx-auto mb-3" />
<input <input
type="file" type="file"

View File

@@ -66,7 +66,7 @@ export default function SettingsPage() {
<p className="text-sm text-muted-foreground mt-0.5">Configure your organisation&apos;s payment details and integrations</p> <p className="text-sm text-muted-foreground mt-0.5">Configure your organisation&apos;s payment details and integrations</p>
</div> </div>
{error && <div className="rounded-xl bg-danger-red/10 border border-danger-red/20 p-3 text-sm text-danger-red">{error}</div>} {error && <div className="rounded-lg bg-danger-red/10 border border-danger-red/20 p-3 text-sm text-danger-red">{error}</div>}
{/* WhatsApp — MOST IMPORTANT, first */} {/* WhatsApp — MOST IMPORTANT, first */}
<WhatsAppPanel /> <WhatsAppPanel />
@@ -194,7 +194,7 @@ function WhatsAppPanel() {
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<div className="w-12 h-12 rounded-full bg-[#25D366]/10 flex items-center justify-center"> <div className="w-12 h-12 rounded-lg bg-[#25D366]/10 flex items-center justify-center">
<Smartphone className="h-6 w-6 text-[#25D366]" /> <Smartphone className="h-6 w-6 text-[#25D366]" />
</div> </div>
<div> <div>
@@ -240,7 +240,7 @@ function WhatsAppPanel() {
<div className="relative"> <div className="relative">
{/* Crop to QR area: the screenshot shows full WhatsApp web page. {/* Crop to QR area: the screenshot shows full WhatsApp web page.
QR code is roughly in center. We use overflow hidden + object positioning. */} QR code is roughly in center. We use overflow hidden + object positioning. */}
<div className="w-72 h-72 rounded-xl border-2 border-[#25D366]/20 overflow-hidden bg-white"> <div className="w-72 h-72 rounded-lg border-2 border-[#25D366]/20 overflow-hidden bg-white">
{/* eslint-disable-next-line @next/next/no-img-element */} {/* eslint-disable-next-line @next/next/no-img-element */}
<img <img
src={qrImage} src={qrImage}
@@ -254,7 +254,7 @@ function WhatsAppPanel() {
</div> </div>
</div> </div>
) : ( ) : (
<div className="w-72 h-72 rounded-xl border-2 border-dashed border-muted flex items-center justify-center"> <div className="w-72 h-72 rounded-lg border-2 border-dashed border-muted flex items-center justify-center">
<Loader2 className="h-8 w-8 text-muted-foreground animate-spin" /> <Loader2 className="h-8 w-8 text-muted-foreground animate-spin" />
</div> </div>
)} )}
@@ -285,9 +285,9 @@ function WhatsAppPanel() {
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="rounded-xl bg-muted/50 p-4 space-y-3"> <div className="rounded-lg bg-muted/50 p-4 space-y-3">
<div className="flex items-start gap-3"> <div className="flex items-start gap-3">
<div className="w-10 h-10 rounded-full bg-[#25D366]/10 flex items-center justify-center flex-shrink-0"> <div className="w-10 h-10 rounded-lg bg-[#25D366]/10 flex items-center justify-center flex-shrink-0">
<Smartphone className="h-5 w-5 text-[#25D366]" /> <Smartphone className="h-5 w-5 text-[#25D366]" />
</div> </div>
<div> <div>

View File

@@ -125,7 +125,7 @@ export default function SetupPage() {
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
{steps.map((s, i) => ( {steps.map((s, i) => (
<div key={s.num} className="flex items-center"> <div key={s.num} className="flex items-center">
<div className={`w-10 h-10 rounded-full flex items-center justify-center text-sm font-bold transition-all ${ <div className={`w-10 h-10 rounded-lg flex items-center justify-center text-sm font-bold transition-all ${
step >= s.num ? "bg-trust-blue text-white" : "bg-gray-100 text-gray-400" step >= s.num ? "bg-trust-blue text-white" : "bg-gray-100 text-gray-400"
}`}> }`}>
{step > s.num ? <CheckCircle2 className="h-5 w-5" /> : <s.icon className="h-5 w-5" />} {step > s.num ? <CheckCircle2 className="h-5 w-5" /> : <s.icon className="h-5 w-5" />}
@@ -231,14 +231,14 @@ export default function SetupPage() {
{step === 4 && ( {step === 4 && (
<Card className="border-success-green/30"> <Card className="border-success-green/30">
<CardHeader className="text-center pb-2"> <CardHeader className="text-center pb-2">
<div className="mx-auto w-16 h-16 rounded-full bg-success-green/10 flex items-center justify-center mb-3"> <div className="mx-auto w-14 h-14 bg-success-green/10 flex items-center justify-center mb-3">
<Sparkles className="h-8 w-8 text-success-green" /> <Sparkles className="h-8 w-8 text-success-green" />
</div> </div>
<CardTitle>You&apos;re All Set! 🎉</CardTitle> <CardTitle>You&apos;re All Set! 🎉</CardTitle>
<p className="text-sm text-muted-foreground">Your charity is ready to collect pledges.</p> <p className="text-sm text-muted-foreground">Your charity is ready to collect pledges.</p>
</CardHeader> </CardHeader>
<CardContent className="space-y-4"> <CardContent className="space-y-4">
<div className="bg-gray-50 rounded-xl p-4 space-y-2"> <div className="bg-gray-50 rounded-lg p-4 space-y-2">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<span className="text-sm text-muted-foreground">Charity</span> <span className="text-sm text-muted-foreground">Charity</span>
<span className="text-sm font-medium">{orgName}</span> <span className="text-sm font-medium">{orgName}</span>
@@ -254,7 +254,7 @@ export default function SetupPage() {
</div> </div>
{setupResult?.qrToken && ( {setupResult?.qrToken && (
<div className="bg-trust-blue/5 rounded-xl p-4 text-center space-y-2"> <div className="bg-trust-blue/5 rounded-lg p-4 text-center space-y-2">
<QrCode className="h-8 w-8 text-trust-blue mx-auto" /> <QrCode className="h-8 w-8 text-trust-blue mx-auto" />
<p className="text-sm font-medium">Your pledge link is ready</p> <p className="text-sm font-medium">Your pledge link is ready</p>
<code className="text-xs bg-white px-3 py-1.5 rounded-lg border block overflow-x-auto"> <code className="text-xs bg-white px-3 py-1.5 rounded-lg border block overflow-x-auto">
@@ -278,7 +278,7 @@ export default function SetupPage() {
{/* Tips */} {/* Tips */}
{step < 4 && ( {step < 4 && (
<div className="bg-warm-amber/5 rounded-xl border border-warm-amber/20 p-4"> <div className="bg-warm-amber/5 rounded-lg border border-warm-amber/20 p-4">
<p className="text-xs font-semibold text-warm-amber mb-1">💡 Tip</p> <p className="text-xs font-semibold text-warm-amber mb-1">💡 Tip</p>
{step === 1 && <p className="text-xs text-muted-foreground">Your charity name appears on the donor pledge page and WhatsApp receipts.</p>} {step === 1 && <p className="text-xs text-muted-foreground">Your charity name appears on the donor pledge page and WhatsApp receipts.</p>}
{step === 2 && <p className="text-xs text-muted-foreground">Bank details are shown to donors who choose &quot;Bank Transfer&quot;. Each pledge gets a unique reference number for easy reconciliation.</p>} {step === 2 && <p className="text-xs text-muted-foreground">Bank details are shown to donors who choose &quot;Bank Transfer&quot;. Each pledge gets a unique reference number for easy reconciliation.</p>}

View File

@@ -82,7 +82,7 @@ export default function PublicEventPage() {
if (loading) { if (loading) {
return ( return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5"> <div className="min-h-screen flex items-center justify-center bg-paper">
<Loader2 className="h-8 w-8 text-trust-blue animate-spin" /> <Loader2 className="h-8 w-8 text-trust-blue animate-spin" />
</div> </div>
) )
@@ -90,7 +90,7 @@ export default function PublicEventPage() {
if (error || !data) { if (error || !data) {
return ( return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5 p-4"> <div className="min-h-screen flex items-center justify-center bg-paper p-4">
<div className="text-center space-y-4"> <div className="text-center space-y-4">
<Heart className="h-12 w-12 text-muted-foreground mx-auto" /> <Heart className="h-12 w-12 text-muted-foreground mx-auto" />
<h1 className="text-xl font-bold">Event not found</h1> <h1 className="text-xl font-bold">Event not found</h1>
@@ -106,7 +106,7 @@ export default function PublicEventPage() {
const giftAidBonus = Math.round(data.stats.totalPledged * 0.25 * (data.stats.giftAidCount / Math.max(1, data.stats.pledgeCount))) const giftAidBonus = Math.round(data.stats.totalPledged * 0.25 * (data.stats.giftAidCount / Math.max(1, data.stats.pledgeCount)))
return ( return (
<div className="min-h-screen bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5"> <div className="min-h-screen bg-paper">
<div className="max-w-lg mx-auto px-4 py-8 space-y-6"> <div className="max-w-lg mx-auto px-4 py-8 space-y-6">
{/* Header */} {/* Header */}
<div className="text-center space-y-3"> <div className="text-center space-y-3">
@@ -146,7 +146,7 @@ export default function PublicEventPage() {
</div> </div>
<div className="h-4 rounded-full bg-gray-100 overflow-hidden"> <div className="h-4 rounded-full bg-gray-100 overflow-hidden">
<div <div
className="h-full rounded-full bg-gradient-to-r from-trust-blue to-success-green transition-all duration-1000" className="h-full rounded-full bg-promise-blue transition-all duration-1000"
style={{ width: `${progressPercent}%` }} style={{ width: `${progressPercent}%` }}
/> />
</div> </div>

View File

@@ -7,25 +7,25 @@
@layer base { @layer base {
:root { :root {
--background: 0 0% 100%; --background: 0 0% 100%;
--foreground: 222.2 84% 4.9%; --foreground: 222 47% 11%;
--card: 0 0% 100%; --card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%; --card-foreground: 222 47% 11%;
--popover: 0 0% 100%; --popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%; --popover-foreground: 222 47% 11%;
--primary: 221.2 83.2% 53.3%; --primary: 224 76% 40%;
--primary-foreground: 210 40% 98%; --primary-foreground: 0 0% 100%;
--secondary: 210 40% 96.1%; --secondary: 220 14% 96%;
--secondary-foreground: 222.2 47.4% 11.2%; --secondary-foreground: 222 47% 11%;
--muted: 210 40% 96.1%; --muted: 220 14% 96%;
--muted-foreground: 215.4 16.3% 46.9%; --muted-foreground: 215 16% 47%;
--accent: 210 40% 96.1%; --accent: 220 14% 96%;
--accent-foreground: 222.2 47.4% 11.2%; --accent-foreground: 222 47% 11%;
--destructive: 0 84.2% 60.2%; --destructive: 0 72% 51%;
--destructive-foreground: 210 40% 98%; --destructive-foreground: 0 0% 100%;
--border: 214.3 31.8% 91.4%; --border: 220 13% 91%;
--input: 214.3 31.8% 91.4%; --input: 220 13% 91%;
--ring: 221.2 83.2% 53.3%; --ring: 224 76% 40%;
--radius: 0.75rem; --radius: 0.5rem;
} }
* { * {
@@ -45,9 +45,9 @@
} }
} }
/* Premium animations */ /* Animations — subtle, purposeful */
@keyframes fadeUp { @keyframes fadeUp {
from { opacity: 0; transform: translateY(12px); } from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); } to { opacity: 1; transform: translateY(0); }
} }
@@ -56,55 +56,32 @@
to { opacity: 1; } to { opacity: 1; }
} }
@keyframes scaleIn {
from { opacity: 0; transform: scale(0.9); }
to { opacity: 1; transform: scale(1); }
}
@keyframes slideDown { @keyframes slideDown {
from { opacity: 0; transform: translateY(-8px); } from { opacity: 0; transform: translateY(-4px); }
to { opacity: 1; transform: translateY(0); } to { opacity: 1; transform: translateY(0); }
} }
@keyframes pulse-ring {
0% { transform: scale(0.8); opacity: 1; }
100% { transform: scale(2); opacity: 0; }
}
@keyframes shimmer { @keyframes shimmer {
0% { background-position: -200% 0; } 0% { background-position: -200% 0; }
100% { background-position: 200% 0; } 100% { background-position: 200% 0; }
} }
@keyframes confetti-fall {
0% { transform: translateY(-10vh) rotate(0deg); opacity: 1; }
100% { transform: translateY(100vh) rotate(720deg); opacity: 0; }
}
@keyframes bounce-gentle {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-4px); }
}
@keyframes counter-roll { @keyframes counter-roll {
from { transform: translateY(100%); opacity: 0; } from { transform: translateY(100%); opacity: 0; }
to { transform: translateY(0); opacity: 1; } to { transform: translateY(0); opacity: 1; }
} }
.animate-fade-up { animation: fadeUp 0.5s ease-out forwards; } .animate-fade-up { animation: fadeUp 0.4s ease-out forwards; }
.animate-fade-in { animation: fadeIn 0.3s ease-out forwards; } .animate-fade-in { animation: fadeIn 0.3s ease-out forwards; }
.animate-scale-in { animation: scaleIn 0.3s ease-out forwards; }
.animate-slide-down { animation: slideDown 0.3s ease-out forwards; } .animate-slide-down { animation: slideDown 0.3s ease-out forwards; }
.animate-pulse-ring { animation: pulse-ring 1.5s ease-out infinite; }
.animate-shimmer { .animate-shimmer {
background: linear-gradient(90deg, transparent 30%, rgba(255,255,255,0.4) 50%, transparent 70%); background: linear-gradient(90deg, transparent 30%, rgba(255,255,255,0.4) 50%, transparent 70%);
background-size: 200% 100%; background-size: 200% 100%;
animation: shimmer 2s infinite; animation: shimmer 2s infinite;
} }
.animate-bounce-gentle { animation: bounce-gentle 2s ease-in-out infinite; }
.animate-counter-roll { animation: counter-roll 0.4s ease-out forwards; } .animate-counter-roll { animation: counter-roll 0.4s ease-out forwards; }
/* Stagger children animations */ /* Stagger children */
.stagger-children > * { opacity: 0; animation: fadeUp 0.4s ease-out forwards; } .stagger-children > * { opacity: 0; animation: fadeUp 0.4s ease-out forwards; }
.stagger-children > *:nth-child(1) { animation-delay: 0ms; } .stagger-children > *:nth-child(1) { animation-delay: 0ms; }
.stagger-children > *:nth-child(2) { animation-delay: 60ms; } .stagger-children > *:nth-child(2) { animation-delay: 60ms; }
@@ -112,34 +89,6 @@
.stagger-children > *:nth-child(4) { animation-delay: 180ms; } .stagger-children > *:nth-child(4) { animation-delay: 180ms; }
.stagger-children > *:nth-child(5) { animation-delay: 240ms; } .stagger-children > *:nth-child(5) { animation-delay: 240ms; }
.stagger-children > *:nth-child(6) { animation-delay: 300ms; } .stagger-children > *:nth-child(6) { animation-delay: 300ms; }
.stagger-children > *:nth-child(7) { animation-delay: 360ms; }
.stagger-children > *:nth-child(8) { animation-delay: 420ms; }
/* Glass effects */
.glass {
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.3);
}
.glass-dark {
background: rgba(0, 0, 0, 0.5);
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
}
/* Premium card hover */
.card-hover {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.card-hover:hover {
transform: translateY(-2px);
box-shadow: 0 12px 40px -8px rgba(0,0,0,0.12);
}
.card-hover:active {
transform: translateY(0) scale(0.98);
}
/* Number input clean */ /* Number input clean */
input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-inner-spin-button,
@@ -151,20 +100,10 @@ input[type="number"] {
-moz-appearance: textfield; -moz-appearance: textfield;
} }
/* Scrollbar */ /* Scrollbar — thin, unobtrusive */
::-webkit-scrollbar { ::-webkit-scrollbar { width: 4px; }
width: 4px; ::-webkit-scrollbar-track { background: transparent; }
} ::-webkit-scrollbar-thumb { background: #d1d5db; border-radius: 2px; }
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: hsl(var(--muted));
border-radius: 2px;
}
/* Selection */ /* Selection — brand blue */
::selection { ::selection { background: #1e40af15; color: #1e40af; }
background: #1e40af20;
color: #1e40af;
}

View File

@@ -1,8 +1,8 @@
export default function PledgeLoading() { export default function PledgeLoading() {
return ( return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5"> <div className="min-h-screen flex items-center justify-center bg-paper">
<div className="text-center space-y-4"> <div className="text-center space-y-4">
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-trust-blue/10 animate-pulse"> <div className="inline-flex items-center justify-center w-14 h-14 bg-trust-blue/10 animate-pulse">
<div className="w-8 h-8 rounded-full bg-trust-blue/30" /> <div className="w-8 h-8 rounded-full bg-trust-blue/30" />
</div> </div>
<p className="text-muted-foreground animate-pulse">Loading pledge page...</p> <p className="text-muted-foreground animate-pulse">Loading pledge page...</p>

View File

@@ -189,8 +189,8 @@ export default function PledgePage() {
if (loading) { if (loading) {
return ( return (
<div className="min-h-screen flex flex-col items-center justify-center bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5 gap-4"> <div className="min-h-screen flex flex-col items-center justify-center bg-paper gap-4">
<div className="w-12 h-12 rounded-2xl bg-gradient-to-br from-trust-blue to-blue-600 flex items-center justify-center animate-pulse"> <div className="w-12 h-12 rounded-lg bg-midnight flex items-center justify-center animate-pulse">
<span className="text-white text-xl">🤲</span> <span className="text-white text-xl">🤲</span>
</div> </div>
<p className="text-trust-blue font-medium animate-pulse">Loading...</p> <p className="text-trust-blue font-medium animate-pulse">Loading...</p>
@@ -200,7 +200,7 @@ export default function PledgePage() {
if (error) { if (error) {
return ( return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5 p-4"> <div className="min-h-screen flex items-center justify-center bg-paper p-4">
<div className="text-center space-y-4 animate-fade-up"> <div className="text-center space-y-4 animate-fade-up">
<div className="text-5xl">😔</div> <div className="text-5xl">😔</div>
<h1 className="text-xl font-bold text-gray-900">Something went wrong</h1> <h1 className="text-xl font-bold text-gray-900">Something went wrong</h1>
@@ -260,11 +260,11 @@ export default function PledgePage() {
const progressPercent = progressMap[step] || 10 const progressPercent = progressMap[step] || 10
return ( return (
<div className="min-h-screen bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5"> <div className="min-h-screen bg-paper">
{/* Progress bar */} {/* Progress bar */}
<div className="fixed top-0 left-0 right-0 h-1 bg-gray-100 z-50"> <div className="fixed top-0 left-0 right-0 h-1 bg-gray-100 z-50">
<div <div
className="h-full bg-gradient-to-r from-trust-blue to-success-green transition-all duration-700 ease-out" className="h-full bg-promise-blue transition-all duration-700 ease-out"
style={{ width: `${progressPercent}%` }} style={{ width: `${progressPercent}%` }}
/> />
</div> </div>
@@ -282,7 +282,7 @@ export default function PledgePage() {
{/* Back button */} {/* Back button */}
{backableSteps.has(step) && ( {backableSteps.has(step) && (
<div className="fixed bottom-0 left-0 right-0 pb-6 pt-4 px-4 bg-gradient-to-t from-white via-white/80 to-transparent"> <div className="fixed bottom-0 left-0 right-0 pb-6 pt-4 px-4 bg-white">
<button <button
onClick={() => setStep(getBackStep(step))} onClick={() => setStep(getBackStep(step))}
className="text-sm text-muted-foreground hover:text-foreground transition-colors tap-target flex items-center gap-1" className="text-sm text-muted-foreground hover:text-foreground transition-colors tap-target flex items-center gap-1"

View File

@@ -64,7 +64,7 @@ export function AmountStep({ onSelect, eventName, eventId }: Props) {
<div className="max-w-md mx-auto pt-2 space-y-6 animate-fade-up"> <div className="max-w-md mx-auto pt-2 space-y-6 animate-fade-up">
{/* Hero */} {/* Hero */}
<div className="text-center space-y-3"> <div className="text-center space-y-3">
<div className="inline-flex items-center justify-center w-14 h-14 rounded-2xl bg-gradient-to-br from-trust-blue to-blue-600 shadow-lg shadow-trust-blue/30"> <div className="inline-flex items-center justify-center w-14 h-14 rounded-lg bg-midnight">
<Heart className="h-7 w-7 text-white" /> <Heart className="h-7 w-7 text-white" />
</div> </div>
<h1 className="text-3xl font-black text-gray-900 tracking-tight"> <h1 className="text-3xl font-black text-gray-900 tracking-tight">
@@ -80,7 +80,7 @@ export function AmountStep({ onSelect, eventName, eventId }: Props) {
<div className="flex items-center justify-center gap-2 animate-fade-in"> <div className="flex items-center justify-center gap-2 animate-fade-in">
<div className="flex -space-x-2"> <div className="flex -space-x-2">
{[...Array(3)].map((_, i) => ( {[...Array(3)].map((_, i) => (
<div key={i} className="w-6 h-6 rounded-full bg-gradient-to-br from-trust-blue to-blue-400 border-2 border-white flex items-center justify-center"> <div key={i} className="w-6 h-6 rounded-full bg-promise-blue border-2 border-white flex items-center justify-center">
<span className="text-[8px] text-white font-bold">{["A", "S", "M"][i]}</span> <span className="text-[8px] text-white font-bold">{["A", "S", "M"][i]}</span>
</div> </div>
))} ))}
@@ -106,9 +106,9 @@ export function AmountStep({ onSelect, eventName, eventId }: Props) {
onMouseEnter={() => setHovering(amount)} onMouseEnter={() => setHovering(amount)}
onMouseLeave={() => setHovering(null)} onMouseLeave={() => setHovering(null)}
className={` className={`
relative tap-target rounded-2xl border-2 py-4 text-center font-bold transition-all duration-200 relative tap-target rounded-lg border-2 py-4 text-center font-bold transition-all duration-200
${isSelected ${isSelected
? "border-trust-blue bg-trust-blue text-white shadow-xl shadow-trust-blue/30 scale-[1.03]" ? "border-trust-blue bg-trust-blue text-white"
: isHovering : isHovering
? "border-trust-blue/40 bg-trust-blue/5 text-gray-900" ? "border-trust-blue/40 bg-trust-blue/5 text-gray-900"
: "border-gray-200 bg-white text-gray-900 hover:border-trust-blue/30" : "border-gray-200 bg-white text-gray-900 hover:border-trust-blue/30"
@@ -118,7 +118,7 @@ export function AmountStep({ onSelect, eventName, eventId }: Props) {
<span className="text-xl">£{pounds >= 1000 ? `${pounds / 1000}k` : pounds}</span> <span className="text-xl">£{pounds >= 1000 ? `${pounds / 1000}k` : pounds}</span>
{/* Gift Aid indicator */} {/* Gift Aid indicator */}
{isSelected && ( {isSelected && (
<div className="absolute -top-2 -right-2 bg-success-green text-white text-[10px] font-bold px-1.5 py-0.5 rounded-full animate-scale-in shadow-sm"> <div className="absolute -top-2 -right-2 bg-success-green text-white text-[10px] font-bold px-1.5 py-0.5 rounded-full animate-fade-in shadow-sm">
+£{Math.round(amount * 0.25 / 100)} +£{Math.round(amount * 0.25 / 100)}
</div> </div>
)} )}
@@ -152,14 +152,14 @@ export function AmountStep({ onSelect, eventName, eventId }: Props) {
placeholder="0" placeholder="0"
value={custom} value={custom}
onChange={(e) => handleCustomChange(e.target.value)} onChange={(e) => handleCustomChange(e.target.value)}
className="w-full pl-10 pr-4 h-16 text-2xl font-black text-center rounded-2xl border-2 border-gray-200 bg-white focus:border-trust-blue focus:ring-4 focus:ring-trust-blue/10 outline-none transition-all" className="w-full pl-10 pr-4 h-16 text-2xl font-black text-center rounded-lg border-2 border-gray-200 bg-white focus:border-trust-blue focus:ring-4 focus:ring-trust-blue/10 outline-none transition-all"
/> />
</div> </div>
</div> </div>
{/* Live Gift Aid preview */} {/* Live Gift Aid preview */}
{isValid && ( {isValid && (
<div className="rounded-2xl bg-gradient-to-r from-success-green/5 to-success-green/10 border border-success-green/20 p-4 text-center animate-scale-in"> <div className="rounded-lg bg-fulfilled-green/5 border border-success-green/20 p-4 text-center animate-fade-in">
<p className="text-sm"> <p className="text-sm">
<span className="font-bold text-success-green">With Gift Aid:</span>{" "} <span className="font-bold text-success-green">With Gift Aid:</span>{" "}
your £{(activeAmount / 100).toFixed(0)} becomes{" "} your £{(activeAmount / 100).toFixed(0)} becomes{" "}

View File

@@ -77,8 +77,8 @@ export function BankInstructionsStep({ pledge, amount, eventName, donorPhone }:
return ( return (
<div className="max-w-md mx-auto pt-8 text-center space-y-6 animate-fade-up"> <div className="max-w-md mx-auto pt-8 text-center space-y-6 animate-fade-up">
<div className="relative inline-flex items-center justify-center"> <div className="relative inline-flex items-center justify-center">
<div className="absolute w-20 h-20 rounded-full bg-success-green/20 animate-pulse-ring" /> <div className="absolute w-16 h-16 bg-success-green/20" />
<div className="relative w-20 h-20 rounded-full bg-gradient-to-br from-success-green to-emerald-500 flex items-center justify-center shadow-xl shadow-success-green/30"> <div className="relative w-16 h-16 bg-fulfilled-green flex items-center justify-center">
<Check className="h-10 w-10 text-white" strokeWidth={3} /> <Check className="h-10 w-10 text-white" strokeWidth={3} />
</div> </div>
</div> </div>
@@ -88,7 +88,7 @@ export function BankInstructionsStep({ pledge, amount, eventName, donorPhone }:
</p> </p>
{whatsappSent && ( {whatsappSent && (
<div className="rounded-xl bg-[#25D366]/10 border border-[#25D366]/20 p-3 text-sm text-[#25D366] font-medium flex items-center justify-center gap-2 animate-fade-in"> <div className="rounded-lg bg-[#25D366]/10 border border-[#25D366]/20 p-3 text-sm text-[#25D366] font-medium flex items-center justify-center gap-2 animate-fade-in">
<MessageCircle className="h-4 w-4" /> Details sent to your WhatsApp <MessageCircle className="h-4 w-4" /> Details sent to your WhatsApp
</div> </div>
)} )}
@@ -107,7 +107,7 @@ export function BankInstructionsStep({ pledge, amount, eventName, donorPhone }:
</Card> </Card>
{/* Share */} {/* Share */}
<div className="rounded-2xl bg-gradient-to-br from-warm-amber/5 to-orange-50 border border-warm-amber/20 p-5 space-y-3"> <div className="rounded-lg bg-generosity-gold/5 border border-warm-amber/20 p-5 space-y-3">
<div className="flex items-center justify-center gap-2"> <div className="flex items-center justify-center gap-2">
<Sparkles className="h-4 w-4 text-warm-amber" /> <Sparkles className="h-4 w-4 text-warm-amber" />
<p className="text-sm font-bold text-gray-900">Know someone who&apos;d donate too?</p> <p className="text-sm font-bold text-gray-900">Know someone who&apos;d donate too?</p>
@@ -164,7 +164,7 @@ export function BankInstructionsStep({ pledge, amount, eventName, donorPhone }:
return ( return (
<div className="max-w-md mx-auto pt-2 space-y-5 animate-fade-up"> <div className="max-w-md mx-auto pt-2 space-y-5 animate-fade-up">
<div className="text-center space-y-2"> <div className="text-center space-y-2">
<div className="inline-flex items-center justify-center w-14 h-14 rounded-2xl bg-gradient-to-br from-trust-blue to-blue-600 shadow-lg shadow-trust-blue/30"> <div className="inline-flex items-center justify-center w-14 h-14 rounded-lg bg-midnight">
<span className="text-2xl">🏦</span> <span className="text-2xl">🏦</span>
</div> </div>
<h1 className="text-2xl font-black text-gray-900 tracking-tight"> <h1 className="text-2xl font-black text-gray-900 tracking-tight">
@@ -176,7 +176,7 @@ export function BankInstructionsStep({ pledge, amount, eventName, donorPhone }:
</div> </div>
{whatsappSent && ( {whatsappSent && (
<div className="rounded-xl bg-[#25D366]/10 border border-[#25D366]/20 p-2.5 text-xs text-[#25D366] font-medium flex items-center justify-center gap-2 animate-fade-in"> <div className="rounded-lg bg-[#25D366]/10 border border-[#25D366]/20 p-2.5 text-xs text-[#25D366] font-medium flex items-center justify-center gap-2 animate-fade-in">
<MessageCircle className="h-3.5 w-3.5" /> Bank details also sent to your WhatsApp <MessageCircle className="h-3.5 w-3.5" /> Bank details also sent to your WhatsApp
</div> </div>
)} )}
@@ -184,7 +184,7 @@ export function BankInstructionsStep({ pledge, amount, eventName, donorPhone }:
{/* Bank details — tap to copy each field */} {/* Bank details — tap to copy each field */}
{bd && ( {bd && (
<Card className="overflow-hidden"> <Card className="overflow-hidden">
<div className="h-1 bg-gradient-to-r from-trust-blue to-blue-400" /> <div className="h-1 bg-promise-blue" />
<CardContent className="pt-4 divide-y"> <CardContent className="pt-4 divide-y">
<CopyField label="Sort Code" value={bd.sortCode} fieldKey="sortCode" mono /> <CopyField label="Sort Code" value={bd.sortCode} fieldKey="sortCode" mono />
<CopyField label="Account Number" value={bd.accountNo} fieldKey="accountNo" mono /> <CopyField label="Account Number" value={bd.accountNo} fieldKey="accountNo" mono />
@@ -194,7 +194,7 @@ export function BankInstructionsStep({ pledge, amount, eventName, donorPhone }:
)} )}
{/* THE reference — the most important thing */} {/* THE reference — the most important thing */}
<div className="rounded-2xl border-2 border-trust-blue bg-gradient-to-br from-trust-blue/5 to-blue-50 p-5 text-center space-y-3"> <div className="rounded-lg border-2 border-trust-blue bg-promise-blue/5 p-5 text-center space-y-3">
<p className="text-xs font-bold text-trust-blue uppercase tracking-widest"> <p className="text-xs font-bold text-trust-blue uppercase tracking-widest">
Payment Reference Payment Reference
</p> </p>
@@ -202,7 +202,7 @@ export function BankInstructionsStep({ pledge, amount, eventName, donorPhone }:
onClick={() => copyField(pledge.reference, "reference")} onClick={() => copyField(pledge.reference, "reference")}
className="group" className="group"
> >
<p className="text-3xl font-mono font-black text-trust-blue tracking-wider group-hover:scale-105 transition-transform"> <p className="text-3xl font-mono font-black text-trust-blue tracking-wider transition-transform">
{pledge.reference} {pledge.reference}
</p> </p>
</button> </button>
@@ -223,7 +223,7 @@ export function BankInstructionsStep({ pledge, amount, eventName, donorPhone }:
</div> </div>
{/* Open banking app hint */} {/* Open banking app hint */}
<div className="rounded-xl bg-warm-amber/5 border border-warm-amber/20 p-3 text-center"> <div className="rounded-lg bg-warm-amber/5 border border-warm-amber/20 p-3 text-center">
<p className="text-xs font-medium text-warm-amber flex items-center justify-center gap-1.5"> <p className="text-xs font-medium text-warm-amber flex items-center justify-center gap-1.5">
<ExternalLink className="h-3.5 w-3.5" /> <ExternalLink className="h-3.5 w-3.5" />
Open your banking app New payment Paste the details Open your banking app New payment Paste the details

View File

@@ -171,7 +171,7 @@ export function CardPaymentStep({ amount, eventName, eventId, qrSourceId, onComp
</div> </div>
{/* Card form */} {/* Card form */}
<div className="rounded-2xl border-2 border-gray-200 bg-white p-5 space-y-4"> <div className="rounded-lg border-2 border-gray-200 bg-white p-5 space-y-4">
{/* Card number */} {/* Card number */}
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="card-number">Card Number</Label> <Label htmlFor="card-number">Card Number</Label>
@@ -261,7 +261,7 @@ export function CardPaymentStep({ amount, eventName, eventId, qrSourceId, onComp
</div> </div>
{/* Gift Aid */} {/* Gift Aid */}
<label className="flex items-start gap-3 rounded-2xl border-2 border-gray-200 bg-white p-4 cursor-pointer hover:border-trust-blue/50 transition-colors"> <label className="flex items-start gap-3 rounded-lg border-2 border-gray-200 bg-white p-4 cursor-pointer hover:border-trust-blue/50 transition-colors">
<input <input
type="checkbox" type="checkbox"
checked={giftAid} checked={giftAid}

View File

@@ -130,8 +130,8 @@ export function ConfirmationStep({ pledge, amount, rail, eventName, shareUrl, do
<div className="max-w-md mx-auto pt-6 text-center space-y-6 animate-fade-up"> <div className="max-w-md mx-auto pt-6 text-center space-y-6 animate-fade-up">
{/* Success icon with pulse */} {/* Success icon with pulse */}
<div className="relative inline-flex items-center justify-center"> <div className="relative inline-flex items-center justify-center">
<div className="absolute w-20 h-20 rounded-full bg-success-green/20 animate-pulse-ring" /> <div className="absolute w-16 h-16 bg-success-green/20" />
<div className="relative w-20 h-20 rounded-full bg-gradient-to-br from-success-green to-emerald-500 flex items-center justify-center shadow-xl shadow-success-green/30"> <div className="relative w-16 h-16 bg-fulfilled-green flex items-center justify-center">
<Check className="h-10 w-10 text-white" strokeWidth={3} /> <Check className="h-10 w-10 text-white" strokeWidth={3} />
</div> </div>
</div> </div>
@@ -150,7 +150,7 @@ export function ConfirmationStep({ pledge, amount, rail, eventName, shareUrl, do
{/* Details card */} {/* Details card */}
<Card className="text-left overflow-hidden"> <Card className="text-left overflow-hidden">
<div className="h-1 bg-gradient-to-r from-trust-blue via-success-green to-warm-amber" /> <div className="h-1 bg-promise-blue" />
<CardContent className="pt-5 space-y-3 text-sm"> <CardContent className="pt-5 space-y-3 text-sm">
<div className="flex justify-between items-center"> <div className="flex justify-between items-center">
<span className="text-muted-foreground">Amount</span> <span className="text-muted-foreground">Amount</span>
@@ -190,19 +190,19 @@ export function ConfirmationStep({ pledge, amount, rail, eventName, shareUrl, do
</Card> </Card>
{/* What happens next */} {/* What happens next */}
<div className="rounded-2xl bg-trust-blue/5 border border-trust-blue/10 p-4 text-left"> <div className="rounded-lg bg-trust-blue/5 border border-trust-blue/10 p-4 text-left">
<p className="text-sm font-semibold text-trust-blue mb-1">What happens next?</p> <p className="text-sm font-semibold text-trust-blue mb-1">What happens next?</p>
<p className="text-sm text-muted-foreground">{nextStepMessages[rail] || nextStepMessages.bank}</p> <p className="text-sm text-muted-foreground">{nextStepMessages[rail] || nextStepMessages.bank}</p>
</div> </div>
{whatsappSent && ( {whatsappSent && (
<div className="rounded-xl bg-[#25D366]/10 border border-[#25D366]/20 p-3 text-sm text-[#25D366] font-medium flex items-center justify-center gap-2 animate-fade-in"> <div className="rounded-lg bg-[#25D366]/10 border border-[#25D366]/20 p-3 text-sm text-[#25D366] font-medium flex items-center justify-center gap-2 animate-fade-in">
<MessageCircle className="h-4 w-4" /> Receipt sent to your WhatsApp <MessageCircle className="h-4 w-4" /> Receipt sent to your WhatsApp
</div> </div>
)} )}
{/* Share */} {/* Share */}
<div className="rounded-2xl bg-gradient-to-br from-warm-amber/5 to-orange-50 border border-warm-amber/20 p-5 space-y-3"> <div className="rounded-lg bg-generosity-gold/5 border border-warm-amber/20 p-5 space-y-3">
<div className="flex items-center justify-center gap-2"> <div className="flex items-center justify-center gap-2">
<Sparkles className="h-4 w-4 text-warm-amber" /> <Sparkles className="h-4 w-4 text-warm-amber" />
<p className="text-sm font-bold text-gray-900"> <p className="text-sm font-bold text-gray-900">

View File

@@ -118,7 +118,7 @@ export function DirectDebitStep({ amount, eventName, organizationName, eventId,
if (phase === "processing") { if (phase === "processing") {
return ( return (
<div className="max-w-md mx-auto pt-16 text-center space-y-6"> <div className="max-w-md mx-auto pt-16 text-center space-y-6">
<div className="inline-flex items-center justify-center w-20 h-20 rounded-full bg-trust-blue/10"> <div className="inline-flex items-center justify-center w-16 h-16 bg-trust-blue/10">
<div className="h-10 w-10 border-4 border-trust-blue border-t-transparent rounded-full animate-spin" /> <div className="h-10 w-10 border-4 border-trust-blue border-t-transparent rounded-full animate-spin" />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
@@ -141,7 +141,7 @@ export function DirectDebitStep({ amount, eventName, organizationName, eventId,
return ( return (
<div className="max-w-md mx-auto pt-4 space-y-6"> <div className="max-w-md mx-auto pt-4 space-y-6">
<div className="text-center space-y-2"> <div className="text-center space-y-2">
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-trust-blue/10 mb-2"> <div className="inline-flex items-center justify-center w-14 h-14 bg-trust-blue/10 mb-2">
<ShieldCheck className="h-8 w-8 text-trust-blue" /> <ShieldCheck className="h-8 w-8 text-trust-blue" />
</div> </div>
<h1 className="text-2xl font-extrabold text-gray-900"> <h1 className="text-2xl font-extrabold text-gray-900">
@@ -153,7 +153,7 @@ export function DirectDebitStep({ amount, eventName, organizationName, eventId,
</div> </div>
{/* Summary card */} {/* Summary card */}
<div className="rounded-2xl border-2 border-gray-200 bg-white p-5 space-y-4"> <div className="rounded-lg border-2 border-gray-200 bg-white p-5 space-y-4">
<div className="grid grid-cols-2 gap-4 text-sm"> <div className="grid grid-cols-2 gap-4 text-sm">
<div> <div>
<p className="text-xs text-muted-foreground uppercase tracking-wider">Account Holder</p> <p className="text-xs text-muted-foreground uppercase tracking-wider">Account Holder</p>
@@ -189,7 +189,7 @@ export function DirectDebitStep({ amount, eventName, organizationName, eventId,
</div> </div>
{/* Guarantee box */} {/* Guarantee box */}
<div className="rounded-2xl bg-emerald-50 border border-emerald-200 p-4 space-y-2"> <div className="rounded-lg bg-emerald-50 border border-emerald-200 p-4 space-y-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<ShieldCheck className="h-5 w-5 text-emerald-600" /> <ShieldCheck className="h-5 w-5 text-emerald-600" />
<p className="text-sm font-semibold text-emerald-800">Direct Debit Guarantee</p> <p className="text-sm font-semibold text-emerald-800">Direct Debit Guarantee</p>
@@ -234,7 +234,7 @@ export function DirectDebitStep({ amount, eventName, organizationName, eventId,
</div> </div>
{/* Info banner */} {/* Info banner */}
<div className="rounded-2xl bg-trust-blue/5 border border-trust-blue/20 p-4 flex items-start gap-3"> <div className="rounded-lg bg-trust-blue/5 border border-trust-blue/20 p-4 flex items-start gap-3">
<Landmark className="h-5 w-5 text-trust-blue flex-shrink-0 mt-0.5" /> <Landmark className="h-5 w-5 text-trust-blue flex-shrink-0 mt-0.5" />
<div> <div>
<p className="text-sm font-medium text-trust-blue">How it works</p> <p className="text-sm font-medium text-trust-blue">How it works</p>
@@ -245,7 +245,7 @@ export function DirectDebitStep({ amount, eventName, organizationName, eventId,
</div> </div>
{/* Bank details form */} {/* Bank details form */}
<div className="rounded-2xl border-2 border-gray-200 bg-white p-5 space-y-4"> <div className="rounded-lg border-2 border-gray-200 bg-white p-5 space-y-4">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="dd-name">Account Holder Name</Label> <Label htmlFor="dd-name">Account Holder Name</Label>
<Input <Input
@@ -306,7 +306,7 @@ export function DirectDebitStep({ amount, eventName, organizationName, eventId,
</div> </div>
{/* Gift Aid */} {/* Gift Aid */}
<label className="flex items-start gap-3 rounded-2xl border-2 border-gray-200 bg-white p-4 cursor-pointer hover:border-trust-blue/50 transition-colors"> <label className="flex items-start gap-3 rounded-lg border-2 border-gray-200 bg-white p-4 cursor-pointer hover:border-trust-blue/50 transition-colors">
<input <input
type="checkbox" type="checkbox"
checked={giftAid} checked={giftAid}
@@ -322,7 +322,7 @@ export function DirectDebitStep({ amount, eventName, organizationName, eventId,
</label> </label>
{/* Direct Debit mandate agreement */} {/* Direct Debit mandate agreement */}
<label className={`flex items-start gap-3 rounded-2xl border-2 p-4 cursor-pointer transition-colors ${ <label className={`flex items-start gap-3 rounded-lg border-2 p-4 cursor-pointer transition-colors ${
errors.mandate ? "border-red-300 bg-red-50" : "border-gray-200 bg-white hover:border-trust-blue/50" errors.mandate ? "border-red-300 bg-red-50" : "border-gray-200 bg-white hover:border-trust-blue/50"
}`}> }`}>
<input <input
@@ -349,7 +349,7 @@ export function DirectDebitStep({ amount, eventName, organizationName, eventId,
</Button> </Button>
{/* DD Guarantee mini */} {/* DD Guarantee mini */}
<div className="rounded-xl bg-emerald-50 border border-emerald-200 p-3 flex items-start gap-2"> <div className="rounded-lg bg-emerald-50 border border-emerald-200 p-3 flex items-start gap-2">
<ShieldCheck className="h-4 w-4 text-emerald-600 flex-shrink-0 mt-0.5" /> <ShieldCheck className="h-4 w-4 text-emerald-600 flex-shrink-0 mt-0.5" />
<p className="text-xs text-emerald-700"> <p className="text-xs text-emerald-700">
Protected by the <span className="font-semibold">Direct Debit Guarantee</span>. You can cancel anytime by contacting your bank. Full refund if any errors occur. Protected by the <span className="font-semibold">Direct Debit Guarantee</span>. You can cancel anytime by contacting your bank. Full refund if any errors occur.

View File

@@ -60,8 +60,8 @@ export function ExternalRedirectStep({ pledge, amount, eventName, externalUrl, e
return ( return (
<div className="max-w-md mx-auto pt-8 text-center space-y-6 animate-fade-up"> <div className="max-w-md mx-auto pt-8 text-center space-y-6 animate-fade-up">
<div className="relative inline-flex items-center justify-center"> <div className="relative inline-flex items-center justify-center">
<div className="absolute w-20 h-20 rounded-full bg-success-green/20 animate-pulse-ring" /> <div className="absolute w-16 h-16 bg-success-green/20" />
<div className="relative w-20 h-20 rounded-full bg-gradient-to-br from-success-green to-emerald-500 flex items-center justify-center shadow-xl shadow-success-green/30"> <div className="relative w-16 h-16 bg-fulfilled-green flex items-center justify-center">
<Check className="h-10 w-10 text-white" strokeWidth={3} /> <Check className="h-10 w-10 text-white" strokeWidth={3} />
</div> </div>
</div> </div>
@@ -72,7 +72,7 @@ export function ExternalRedirectStep({ pledge, amount, eventName, externalUrl, e
</p> </p>
{whatsappSent && ( {whatsappSent && (
<div className="rounded-xl bg-[#25D366]/10 border border-[#25D366]/20 p-3 text-sm text-[#25D366] font-medium flex items-center justify-center gap-2 animate-fade-in"> <div className="rounded-lg bg-[#25D366]/10 border border-[#25D366]/20 p-3 text-sm text-[#25D366] font-medium flex items-center justify-center gap-2 animate-fade-in">
<MessageCircle className="h-4 w-4" /> Link sent to your WhatsApp <MessageCircle className="h-4 w-4" /> Link sent to your WhatsApp
</div> </div>
)} )}
@@ -107,7 +107,7 @@ export function ExternalRedirectStep({ pledge, amount, eventName, externalUrl, e
</div> </div>
{/* Share */} {/* Share */}
<div className="rounded-2xl bg-gradient-to-br from-warm-amber/5 to-orange-50 border border-warm-amber/20 p-5 space-y-3"> <div className="rounded-lg bg-generosity-gold/5 border border-warm-amber/20 p-5 space-y-3">
<div className="flex items-center justify-center gap-2"> <div className="flex items-center justify-center gap-2">
<Sparkles className="h-4 w-4 text-warm-amber" /> <Sparkles className="h-4 w-4 text-warm-amber" />
<p className="text-sm font-bold text-gray-900">Know someone who&apos;d donate too?</p> <p className="text-sm font-bold text-gray-900">Know someone who&apos;d donate too?</p>
@@ -144,7 +144,7 @@ export function ExternalRedirectStep({ pledge, amount, eventName, externalUrl, e
return ( return (
<div className="max-w-md mx-auto pt-2 space-y-5 animate-fade-up"> <div className="max-w-md mx-auto pt-2 space-y-5 animate-fade-up">
<div className="text-center space-y-2"> <div className="text-center space-y-2">
<div className="inline-flex items-center justify-center w-14 h-14 rounded-2xl shadow-lg" style={{ background: platform.color + "20" }}> <div className="inline-flex items-center justify-center w-14 h-14 rounded-lg shadow-lg" style={{ background: platform.color + "20" }}>
<span className="text-2xl">{platform.icon}</span> <span className="text-2xl">{platform.icon}</span>
</div> </div>
<h1 className="text-2xl font-black text-gray-900 tracking-tight"> <h1 className="text-2xl font-black text-gray-900 tracking-tight">
@@ -156,7 +156,7 @@ export function ExternalRedirectStep({ pledge, amount, eventName, externalUrl, e
</div> </div>
{whatsappSent && ( {whatsappSent && (
<div className="rounded-xl bg-[#25D366]/10 border border-[#25D366]/20 p-2.5 text-xs text-[#25D366] font-medium flex items-center justify-center gap-2 animate-fade-in"> <div className="rounded-lg bg-[#25D366]/10 border border-[#25D366]/20 p-2.5 text-xs text-[#25D366] font-medium flex items-center justify-center gap-2 animate-fade-in">
<MessageCircle className="h-3.5 w-3.5" /> Donation link also sent to your WhatsApp <MessageCircle className="h-3.5 w-3.5" /> Donation link also sent to your WhatsApp
</div> </div>
)} )}

View File

@@ -156,10 +156,10 @@ export function IdentityStep({ onSubmit, amount, zakatEligible, orgName }: Props
value={name} value={name}
onChange={(e) => setName(e.target.value)} onChange={(e) => setName(e.target.value)}
autoComplete="name" autoComplete="name"
className="w-full h-14 px-4 rounded-2xl border-2 border-gray-200 bg-white text-base font-medium placeholder:text-gray-300 focus:border-trust-blue focus:ring-4 focus:ring-trust-blue/10 outline-none transition-all" className="w-full h-14 px-4 rounded-lg border-2 border-gray-200 bg-white text-base font-medium placeholder:text-gray-300 focus:border-trust-blue focus:ring-4 focus:ring-trust-blue/10 outline-none transition-all"
/> />
{name && ( {name && (
<div className="absolute right-3 top-1/2 -translate-y-1/2 text-success-green animate-scale-in"></div> <div className="absolute right-3 top-1/2 -translate-y-1/2 text-success-green animate-fade-in"></div>
)} )}
</div> </div>
@@ -173,10 +173,10 @@ export function IdentityStep({ onSubmit, amount, zakatEligible, orgName }: Props
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
autoComplete="email" autoComplete="email"
inputMode="email" inputMode="email"
className="w-full h-14 pl-12 pr-4 rounded-2xl border-2 border-gray-200 bg-white text-base font-medium placeholder:text-gray-300 focus:border-trust-blue focus:ring-4 focus:ring-trust-blue/10 outline-none transition-all" className="w-full h-14 pl-12 pr-4 rounded-lg border-2 border-gray-200 bg-white text-base font-medium placeholder:text-gray-300 focus:border-trust-blue focus:ring-4 focus:ring-trust-blue/10 outline-none transition-all"
/> />
{hasEmail && ( {hasEmail && (
<div className="absolute right-3 top-1/2 -translate-y-1/2 text-success-green animate-scale-in"></div> <div className="absolute right-3 top-1/2 -translate-y-1/2 text-success-green animate-fade-in"></div>
)} )}
</div> </div>
@@ -191,10 +191,10 @@ export function IdentityStep({ onSubmit, amount, zakatEligible, orgName }: Props
onChange={(e) => setPhone(e.target.value)} onChange={(e) => setPhone(e.target.value)}
autoComplete="tel" autoComplete="tel"
inputMode="tel" inputMode="tel"
className="w-full h-14 pl-12 pr-4 rounded-2xl border-2 border-gray-200 bg-white text-base font-medium placeholder:text-gray-300 focus:border-trust-blue focus:ring-4 focus:ring-trust-blue/10 outline-none transition-all" className="w-full h-14 pl-12 pr-4 rounded-lg border-2 border-gray-200 bg-white text-base font-medium placeholder:text-gray-300 focus:border-trust-blue focus:ring-4 focus:ring-trust-blue/10 outline-none transition-all"
/> />
{hasPhone && ( {hasPhone && (
<div className="absolute right-3 top-1/2 -translate-y-1/2 text-success-green animate-scale-in"></div> <div className="absolute right-3 top-1/2 -translate-y-1/2 text-success-green animate-fade-in"></div>
)} )}
</div> </div>
{!hasEmail && !hasPhone && ( {!hasEmail && !hasPhone && (
@@ -220,14 +220,14 @@ export function IdentityStep({ onSubmit, amount, zakatEligible, orgName }: Props
<div className="space-y-3"> <div className="space-y-3">
<button <button
onClick={() => setGiftAid(!giftAid)} onClick={() => setGiftAid(!giftAid)}
className={`w-full text-left rounded-2xl border-2 p-5 transition-all duration-300 ${ className={`w-full text-left rounded-lg border-2 p-5 transition-all duration-300 ${
giftAid giftAid
? "border-success-green bg-gradient-to-br from-success-green/5 to-emerald-50 shadow-lg shadow-success-green/10" ? "border-success-green bg-fulfilled-green/5"
: "border-gray-200 bg-white hover:border-success-green/40" : "border-gray-200 bg-white hover:border-success-green/40"
}`} }`}
> >
<div className="flex items-start gap-4"> <div className="flex items-start gap-4">
<div className={`rounded-xl p-3 transition-all ${giftAid ? "bg-success-green shadow-lg shadow-success-green/30" : "bg-gray-100"}`}> <div className={`rounded-lg p-3 transition-all ${giftAid ? "bg-success-green" : "bg-gray-100"}`}>
<Gift className={`h-6 w-6 transition-colors ${giftAid ? "text-white" : "text-gray-400"}`} /> <Gift className={`h-6 w-6 transition-colors ${giftAid ? "text-white" : "text-gray-400"}`} />
</div> </div>
<div className="flex-1"> <div className="flex-1">
@@ -236,7 +236,7 @@ export function IdentityStep({ onSubmit, amount, zakatEligible, orgName }: Props
{giftAid ? "Gift Aid added!" : "Add Gift Aid"} {giftAid ? "Gift Aid added!" : "Add Gift Aid"}
</span> </span>
{giftAid ? ( {giftAid ? (
<span className="text-xs font-bold px-2.5 py-0.5 rounded-full bg-success-green text-white animate-scale-in flex items-center gap-1"> <span className="text-xs font-bold px-2.5 py-0.5 rounded-full bg-success-green text-white animate-fade-in flex items-center gap-1">
<Sparkles className="h-3 w-3" /> +£{(giftAidBonus / 100).toFixed(0)} free <Sparkles className="h-3 w-3" /> +£{(giftAidBonus / 100).toFixed(0)} free
</span> </span>
) : ( ) : (
@@ -249,7 +249,7 @@ export function IdentityStep({ onSubmit, amount, zakatEligible, orgName }: Props
{giftAid ? ( {giftAid ? (
<div className="mt-2 space-y-2 animate-fade-in"> <div className="mt-2 space-y-2 animate-fade-in">
{/* Live math */} {/* Live math */}
<div className="flex items-center justify-between bg-white rounded-xl p-3 border border-success-green/20"> <div className="flex items-center justify-between bg-white rounded-lg p-3 border border-success-green/20">
<div> <div>
<p className="text-xs text-muted-foreground">Your pledge</p> <p className="text-xs text-muted-foreground">Your pledge</p>
<p className="font-bold">£{(amount / 100).toFixed(0)}</p> <p className="font-bold">£{(amount / 100).toFixed(0)}</p>
@@ -278,7 +278,7 @@ export function IdentityStep({ onSubmit, amount, zakatEligible, orgName }: Props
{/* Gift Aid address + declaration (shown when Gift Aid is on) */} {/* Gift Aid address + declaration (shown when Gift Aid is on) */}
{giftAid && showGiftAidAddress && ( {giftAid && showGiftAidAddress && (
<div className="rounded-2xl border-2 border-success-green/20 bg-white p-4 space-y-3 animate-fade-in"> <div className="rounded-lg border-2 border-success-green/20 bg-white p-4 space-y-3 animate-fade-in">
{/* HMRC requires home address */} {/* HMRC requires home address */}
<div className="flex items-center gap-2 text-xs font-medium text-muted-foreground"> <div className="flex items-center gap-2 text-xs font-medium text-muted-foreground">
<MapPin className="h-3.5 w-3.5" /> <MapPin className="h-3.5 w-3.5" />
@@ -292,10 +292,10 @@ export function IdentityStep({ onSubmit, amount, zakatEligible, orgName }: Props
value={addressLine1} value={addressLine1}
onChange={(e) => setAddressLine1(e.target.value)} onChange={(e) => setAddressLine1(e.target.value)}
autoComplete="address-line1" autoComplete="address-line1"
className="w-full h-12 px-4 rounded-xl border-2 border-gray-200 bg-white text-sm font-medium placeholder:text-gray-300 focus:border-success-green focus:ring-4 focus:ring-success-green/10 outline-none transition-all" className="w-full h-12 px-4 rounded-lg border-2 border-gray-200 bg-white text-sm font-medium placeholder:text-gray-300 focus:border-success-green focus:ring-4 focus:ring-success-green/10 outline-none transition-all"
/> />
{addressLine1 && ( {addressLine1 && (
<div className="absolute right-3 top-1/2 -translate-y-1/2 text-success-green text-sm animate-scale-in"></div> <div className="absolute right-3 top-1/2 -translate-y-1/2 text-success-green text-sm animate-fade-in"></div>
)} )}
</div> </div>
@@ -306,15 +306,15 @@ export function IdentityStep({ onSubmit, amount, zakatEligible, orgName }: Props
value={postcode} value={postcode}
onChange={(e) => setPostcode(e.target.value.toUpperCase())} onChange={(e) => setPostcode(e.target.value.toUpperCase())}
autoComplete="postal-code" autoComplete="postal-code"
className="w-full h-12 px-4 rounded-xl border-2 border-gray-200 bg-white text-sm font-medium placeholder:text-gray-300 focus:border-success-green focus:ring-4 focus:ring-success-green/10 outline-none transition-all uppercase" className="w-full h-12 px-4 rounded-lg border-2 border-gray-200 bg-white text-sm font-medium placeholder:text-gray-300 focus:border-success-green focus:ring-4 focus:ring-success-green/10 outline-none transition-all uppercase"
/> />
{postcode.length >= 5 && ( {postcode.length >= 5 && (
<div className="absolute right-3 top-1/2 -translate-y-1/2 text-success-green text-sm animate-scale-in"></div> <div className="absolute right-3 top-1/2 -translate-y-1/2 text-success-green text-sm animate-fade-in"></div>
)} )}
</div> </div>
{/* HMRC declaration — exact model wording */} {/* HMRC declaration — exact model wording */}
<div className="rounded-xl bg-success-green/5 border border-success-green/10 p-3"> <div className="rounded-lg bg-success-green/5 border border-success-green/10 p-3">
<p className="text-xs font-bold text-gray-700 mb-1.5">Gift Aid Declaration</p> <p className="text-xs font-bold text-gray-700 mb-1.5">Gift Aid Declaration</p>
<p className="text-[11px] text-muted-foreground leading-relaxed"> <p className="text-[11px] text-muted-foreground leading-relaxed">
{GIFT_AID_DECLARATION} {GIFT_AID_DECLARATION}
@@ -406,7 +406,7 @@ function ConsentCheckbox({ checked, onChange, icon, label, description }: {
<button <button
type="button" type="button"
onClick={() => onChange(!checked)} onClick={() => onChange(!checked)}
className={`w-full text-left rounded-2xl border-2 p-4 transition-all ${ className={`w-full text-left rounded-lg border-2 p-4 transition-all ${
checked checked
? "border-trust-blue bg-trust-blue/5 shadow-sm" ? "border-trust-blue bg-trust-blue/5 shadow-sm"
: "border-gray-200 bg-white hover:border-trust-blue/40" : "border-gray-200 bg-white hover:border-trust-blue/40"

View File

@@ -22,7 +22,7 @@ export function PaymentStep({ onSelect, amount }: Props) {
detail: "We'll give you the bank details. Transfer in your own time.", detail: "We'll give you the bank details. Transfer in your own time.",
fee: "Free", fee: "Free",
feeClass: "text-success-green font-bold", feeClass: "text-success-green font-bold",
iconBg: "from-emerald-500 to-green-600", iconBg: "bg-fulfilled-green",
highlight: true, highlight: true,
benefits: ["Zero fees", "Most charities prefer this"], benefits: ["Zero fees", "Most charities prefer this"],
}, },
@@ -36,7 +36,7 @@ export function PaymentStep({ onSelect, amount }: Props) {
detail: "GoCardless collects it for you. Protected by the DD Guarantee.", detail: "GoCardless collects it for you. Protected by the DD Guarantee.",
fee: "1% + 20p", fee: "1% + 20p",
feeClass: "text-muted-foreground", feeClass: "text-muted-foreground",
iconBg: "from-trust-blue to-blue-600", iconBg: "bg-promise-blue",
highlight: false, highlight: false,
benefits: ["No action needed", "DD Guarantee"], benefits: ["No action needed", "DD Guarantee"],
}, },
@@ -50,7 +50,7 @@ export function PaymentStep({ onSelect, amount }: Props) {
detail: "Powered by Stripe. Receipt emailed instantly.", detail: "Powered by Stripe. Receipt emailed instantly.",
fee: "1.4% + 20p", fee: "1.4% + 20p",
feeClass: "text-muted-foreground", feeClass: "text-muted-foreground",
iconBg: "from-purple-500 to-violet-600", iconBg: "bg-midnight",
highlight: false, highlight: false,
benefits: ["Instant confirmation", "All major cards"], benefits: ["Instant confirmation", "All major cards"],
}, },
@@ -74,15 +74,15 @@ export function PaymentStep({ onSelect, amount }: Props) {
key={opt.id} key={opt.id}
onClick={() => onSelect(opt.id)} onClick={() => onSelect(opt.id)}
className={` className={`
w-full text-left rounded-2xl border-2 bg-white p-5 transition-all duration-200 group card-hover w-full text-left rounded-lg border-2 bg-white p-5 transition-all duration-200 group
${opt.highlight ${opt.highlight
? "border-success-green/40 shadow-sm shadow-success-green/10 hover:border-success-green hover:shadow-lg hover:shadow-success-green/15" ? "border-success-green/40 hover:border-success-green hover:border-fulfilled-green"
: "border-gray-200 hover:border-trust-blue/40 hover:shadow-lg hover:shadow-trust-blue/10" : "border-gray-200 hover:border-trust-blue/40 hover:border-promise-blue"
} }
`} `}
> >
<div className="flex items-start gap-4"> <div className="flex items-start gap-4">
<div className={`rounded-xl bg-gradient-to-br ${opt.iconBg} p-3 shadow-lg shadow-trust-blue/10 group-hover:scale-105 transition-transform`}> <div className={`rounded-lg ${opt.iconBg} p-3 transition-transform`}>
<opt.icon className="h-5 w-5 text-white" /> <opt.icon className="h-5 w-5 text-white" />
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">

View File

@@ -150,10 +150,10 @@ export function ScheduleStep({ amount, onSelect }: Props) {
{/* Pay Now */} {/* Pay Now */}
<button <button
onClick={() => onSelect({ mode: "now" })} onClick={() => onSelect({ mode: "now" })}
className="w-full text-left rounded-2xl border-2 border-gray-200 bg-white p-5 transition-all duration-200 card-hover hover:border-trust-blue/40 hover:shadow-lg group" className="w-full text-left rounded-lg border-2 border-gray-200 bg-white p-5 transition-all duration-200 hover:border-trust-blue/40 hover:shadow-lg group"
> >
<div className="flex items-start gap-4"> <div className="flex items-start gap-4">
<div className="rounded-xl bg-gradient-to-br from-trust-blue to-blue-600 p-3 shadow-lg shadow-trust-blue/20 group-hover:scale-105 transition-transform"> <div className="rounded-lg bg-midnight p-3 transition-transform">
<Zap className="h-5 w-5 text-white" /> <Zap className="h-5 w-5 text-white" />
</div> </div>
<div className="flex-1"> <div className="flex-1">
@@ -172,10 +172,10 @@ export function ScheduleStep({ amount, onSelect }: Props) {
{/* Pay on a date */} {/* Pay on a date */}
<button <button
onClick={() => setMode("calendar")} onClick={() => setMode("calendar")}
className="w-full text-left rounded-2xl border-2 border-success-green/30 bg-gradient-to-r from-success-green/5 to-white p-5 transition-all duration-200 card-hover hover:border-success-green hover:shadow-lg group" className="w-full text-left rounded-lg border-2 border-success-green/30 bg-fulfilled-green/5 p-5 transition-all duration-200 hover:border-success-green hover:shadow-lg group"
> >
<div className="flex items-start gap-4"> <div className="flex items-start gap-4">
<div className="rounded-xl bg-gradient-to-br from-success-green to-emerald-600 p-3 shadow-lg shadow-success-green/20 group-hover:scale-105 transition-transform"> <div className="rounded-lg bg-fulfilled-green p-3 transition-transform">
<Calendar className="h-5 w-5 text-white" /> <Calendar className="h-5 w-5 text-white" />
</div> </div>
<div className="flex-1"> <div className="flex-1">
@@ -194,10 +194,10 @@ export function ScheduleStep({ amount, onSelect }: Props) {
{/* Installments */} {/* Installments */}
<button <button
onClick={() => setMode("installments")} onClick={() => setMode("installments")}
className="w-full text-left rounded-2xl border-2 border-gray-200 bg-white p-5 transition-all duration-200 card-hover hover:border-warm-amber/40 hover:shadow-lg group" className="w-full text-left rounded-lg border-2 border-gray-200 bg-white p-5 transition-all duration-200 hover:border-warm-amber/40 hover:shadow-lg group"
> >
<div className="flex items-start gap-4"> <div className="flex items-start gap-4">
<div className="rounded-xl bg-gradient-to-br from-warm-amber to-orange-500 p-3 shadow-lg shadow-warm-amber/20 group-hover:scale-105 transition-transform"> <div className="rounded-lg bg-generosity-gold p-3 transition-transform">
<Repeat className="h-5 w-5 text-white" /> <Repeat className="h-5 w-5 text-white" />
</div> </div>
<div className="flex-1"> <div className="flex-1">
@@ -241,9 +241,9 @@ export function ScheduleStep({ amount, onSelect }: Props) {
<button <button
key={i} key={i}
onClick={() => setSelectedDate(s.date)} onClick={() => setSelectedDate(s.date)}
className={`shrink-0 rounded-xl border-2 px-3.5 py-2 text-left transition-all ${ className={`shrink-0 rounded-lg border-2 px-3.5 py-2 text-left transition-all ${
isSelected isSelected
? "border-success-green bg-success-green/5 shadow-md shadow-success-green/10" ? "border-success-green bg-success-green/5"
: "border-gray-200 bg-white hover:border-success-green/40" : "border-gray-200 bg-white hover:border-success-green/40"
}`} }`}
> >
@@ -283,7 +283,7 @@ export function ScheduleStep({ amount, onSelect }: Props) {
disabled={day.isPast || !day.inMonth} disabled={day.isPast || !day.inMonth}
className={`h-9 w-9 mx-auto rounded-lg text-xs font-medium transition-all ${ className={`h-9 w-9 mx-auto rounded-lg text-xs font-medium transition-all ${
isSelected isSelected
? "bg-success-green text-white font-bold shadow-md shadow-success-green/30" ? "bg-success-green text-white font-bold"
: isToday : isToday
? "bg-trust-blue/10 text-trust-blue font-bold" ? "bg-trust-blue/10 text-trust-blue font-bold"
: day.isPast || !day.inMonth : day.isPast || !day.inMonth
@@ -301,7 +301,7 @@ export function ScheduleStep({ amount, onSelect }: Props) {
{/* Selected date summary */} {/* Selected date summary */}
{selectedDate && ( {selectedDate && (
<div className="rounded-2xl bg-success-green/5 border border-success-green/20 p-4 text-center animate-scale-in"> <div className="rounded-lg bg-success-green/5 border border-success-green/20 p-4 text-center animate-fade-in">
<p className="text-xs text-muted-foreground">You&apos;ll pay</p> <p className="text-xs text-muted-foreground">You&apos;ll pay</p>
<p className="text-xl font-black text-gray-900">£{pounds}</p> <p className="text-xl font-black text-gray-900">£{pounds}</p>
<p className="text-sm font-medium text-success-green">{formatFull(selectedDate)}</p> <p className="text-sm font-medium text-success-green">{formatFull(selectedDate)}</p>
@@ -313,7 +313,7 @@ export function ScheduleStep({ amount, onSelect }: Props) {
<Button <Button
size="xl" size="xl"
className={`w-full ${selectedDate ? "bg-success-green hover:bg-success-green/90 shadow-lg shadow-success-green/25" : ""}`} className={`w-full ${selectedDate ? "bg-success-green hover:bg-success-green/90" : ""}`}
disabled={!selectedDate} disabled={!selectedDate}
onClick={handleDateConfirm} onClick={handleDateConfirm}
> >
@@ -345,9 +345,9 @@ export function ScheduleStep({ amount, onSelect }: Props) {
<button <button
key={n} key={n}
onClick={() => setInstallmentCount(n)} onClick={() => setInstallmentCount(n)}
className={`rounded-xl border-2 px-4 py-3 text-center transition-all min-w-[70px] ${ className={`rounded-lg border-2 px-4 py-3 text-center transition-all min-w-[70px] ${
installmentCount === n installmentCount === n
? "border-warm-amber bg-warm-amber text-white font-bold shadow-lg shadow-warm-amber/25" ? "border-warm-amber bg-warm-amber text-white font-bold"
: "border-gray-200 bg-white hover:border-warm-amber/40" : "border-gray-200 bg-white hover:border-warm-amber/40"
}`} }`}
> >
@@ -359,7 +359,7 @@ export function ScheduleStep({ amount, onSelect }: Props) {
{/* Breakdown */} {/* Breakdown */}
<Card className="overflow-hidden"> <Card className="overflow-hidden">
<div className="h-1 bg-gradient-to-r from-warm-amber to-orange-400" /> <div className="h-1 bg-generosity-gold" />
<CardContent className="pt-4"> <CardContent className="pt-4">
<div className="text-center mb-4"> <div className="text-center mb-4">
<p className="text-xs text-muted-foreground">Monthly payment</p> <p className="text-xs text-muted-foreground">Monthly payment</p>
@@ -392,7 +392,7 @@ export function ScheduleStep({ amount, onSelect }: Props) {
</CardContent> </CardContent>
</Card> </Card>
<div className="rounded-xl bg-warm-amber/5 border border-warm-amber/20 p-3 text-center"> <div className="rounded-lg bg-warm-amber/5 border border-warm-amber/20 p-3 text-center">
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
We&apos;ll send you a reminder with payment details before each installment date. We&apos;ll send you a reminder with payment details before each installment date.
You can pay via bank transfer, card, or Direct Debit. You can pay via bank transfer, card, or Direct Debit.
@@ -401,7 +401,7 @@ export function ScheduleStep({ amount, onSelect }: Props) {
<Button <Button
size="xl" size="xl"
className="w-full bg-warm-amber hover:bg-warm-amber/90 shadow-lg shadow-warm-amber/25" className="w-full bg-warm-amber hover:bg-warm-amber/90"
onClick={handleInstallmentConfirm} onClick={handleInstallmentConfirm}
> >
<Check className="h-5 w-5 mr-2" /> <Check className="h-5 w-5 mr-2" />

View File

@@ -45,7 +45,7 @@ function SuccessContent() {
if (cancelled) { if (cancelled) {
return ( return (
<div className="max-w-md mx-auto text-center space-y-6"> <div className="max-w-md mx-auto text-center space-y-6">
<div className="inline-flex items-center justify-center w-20 h-20 rounded-full bg-amber-100"> <div className="inline-flex items-center justify-center w-16 h-16 bg-amber-100">
<X className="h-10 w-10 text-amber-600" /> <X className="h-10 w-10 text-amber-600" />
</div> </div>
<h1 className="text-2xl font-extrabold text-gray-900">Payment Cancelled</h1> <h1 className="text-2xl font-extrabold text-gray-900">Payment Cancelled</h1>
@@ -89,7 +89,7 @@ function SuccessContent() {
return ( return (
<div className="max-w-md mx-auto text-center space-y-6"> <div className="max-w-md mx-auto text-center space-y-6">
<div className="inline-flex items-center justify-center w-20 h-20 rounded-full bg-success-green/10"> <div className="inline-flex items-center justify-center w-16 h-16 bg-success-green/10">
<Check className="h-10 w-10 text-success-green" /> <Check className="h-10 w-10 text-success-green" />
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
@@ -125,7 +125,7 @@ function SuccessContent() {
</CardContent> </CardContent>
</Card> </Card>
)} )}
<div className="rounded-2xl bg-trust-blue/5 border border-trust-blue/20 p-4 space-y-2"> <div className="rounded-lg bg-trust-blue/5 border border-trust-blue/20 p-4 space-y-2">
<p className="text-sm font-medium text-trust-blue">What happens next?</p> <p className="text-sm font-medium text-trust-blue">What happens next?</p>
<p className="text-sm text-muted-foreground">{nextStepMessages[rail] || nextStepMessages.card}</p> <p className="text-sm text-muted-foreground">{nextStepMessages[rail] || nextStepMessages.card}</p>
</div> </div>
@@ -138,7 +138,7 @@ function SuccessContent() {
export default function SuccessPage() { export default function SuccessPage() {
return ( return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5 p-4"> <div className="min-h-screen flex items-center justify-center bg-paper p-4">
<Suspense fallback={ <Suspense fallback={
<div className="text-center space-y-4"> <div className="text-center space-y-4">
<Loader2 className="h-10 w-10 text-trust-blue animate-spin mx-auto" /> <Loader2 className="h-10 w-10 text-trust-blue animate-spin mx-auto" />

View File

@@ -89,7 +89,7 @@ export default function VolunteerPage() {
if (loading) { if (loading) {
return ( return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5"> <div className="min-h-screen flex items-center justify-center bg-paper">
<Loader2 className="h-8 w-8 text-trust-blue animate-spin" /> <Loader2 className="h-8 w-8 text-trust-blue animate-spin" />
</div> </div>
) )
@@ -97,7 +97,7 @@ export default function VolunteerPage() {
if (error || !data) { if (error || !data) {
return ( return (
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5 p-4"> <div className="min-h-screen flex items-center justify-center bg-paper p-4">
<div className="text-center space-y-4"> <div className="text-center space-y-4">
<QrCode className="h-12 w-12 text-muted-foreground mx-auto" /> <QrCode className="h-12 w-12 text-muted-foreground mx-auto" />
<h1 className="text-xl font-bold">QR code not found</h1> <h1 className="text-xl font-bold">QR code not found</h1>
@@ -108,7 +108,7 @@ export default function VolunteerPage() {
} }
return ( return (
<div className="min-h-screen bg-gradient-to-br from-trust-blue/5 via-white to-warm-amber/5"> <div className="min-h-screen bg-paper">
<div className="max-w-lg mx-auto px-4 py-8 space-y-6"> <div className="max-w-lg mx-auto px-4 py-8 space-y-6">
{/* Header */} {/* Header */}
<div className="text-center space-y-2"> <div className="text-center space-y-2">
@@ -153,7 +153,7 @@ export default function VolunteerPage() {
</div> </div>
<div className="h-3 rounded-full bg-gray-100 overflow-hidden"> <div className="h-3 rounded-full bg-gray-100 overflow-hidden">
<div <div
className="h-full rounded-full bg-gradient-to-r from-trust-blue to-success-green transition-all duration-1000" className="h-full rounded-full bg-promise-blue transition-all duration-1000"
style={{ width: `${Math.round((data.stats.totalPaidPence / data.stats.totalPledgedPence) * 100)}%` }} style={{ width: `${Math.round((data.stats.totalPaidPence / data.stats.totalPledgedPence) * 100)}%` }}
/> />
</div> </div>

View File

@@ -57,7 +57,7 @@ export function LiveTicker({ eventId }: LiveTickerProps) {
const totalToday = pledges.reduce((s, p) => s + p.amountPence, 0) const totalToday = pledges.reduce((s, p) => s + p.amountPence, 0)
return ( return (
<div className="rounded-2xl border bg-white p-4 space-y-3"> <div className="rounded-lg border bg-white p-4 space-y-3">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<h3 className="font-bold text-sm flex items-center gap-2"> <h3 className="font-bold text-sm flex items-center gap-2">
<TrendingUp className="h-4 w-4 text-trust-blue" /> Live Feed <TrendingUp className="h-4 w-4 text-trust-blue" /> Live Feed

View File

@@ -3,24 +3,25 @@ import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const buttonVariants = cva( const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-xl text-sm font-semibold ring-offset-background transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 active:scale-[0.98]", "inline-flex items-center justify-center whitespace-nowrap text-sm font-semibold ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{ {
variants: { variants: {
variant: { variant: {
default: "bg-trust-blue text-white hover:bg-trust-blue/90 shadow-lg shadow-trust-blue/25", default: "bg-midnight text-white hover:bg-gray-800",
destructive: "bg-danger-red text-white hover:bg-danger-red/90", destructive: "bg-alert-red text-white hover:bg-alert-red/90",
outline: "border-2 border-input bg-background hover:bg-accent hover:text-accent-foreground", outline: "border border-gray-200 bg-white hover:bg-gray-50 text-midnight",
secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", secondary: "bg-gray-100 text-midnight hover:bg-gray-200",
ghost: "hover:bg-accent hover:text-accent-foreground", ghost: "hover:bg-gray-100 text-midnight",
link: "text-trust-blue underline-offset-4 hover:underline", link: "text-promise-blue underline-offset-4 hover:underline",
success: "bg-success-green text-white hover:bg-success-green/90 shadow-lg shadow-success-green/25", success: "bg-fulfilled-green text-white hover:bg-fulfilled-green/90",
amber: "bg-warm-amber text-white hover:bg-warm-amber/90 shadow-lg shadow-warm-amber/25", amber: "bg-generosity-gold text-white hover:bg-generosity-gold/90",
blue: "bg-promise-blue text-white hover:bg-promise-blue/90",
}, },
size: { size: {
default: "h-11 px-6 py-2", default: "h-11 px-6 py-2",
sm: "h-9 rounded-lg px-4", sm: "h-9 px-4",
lg: "h-14 rounded-2xl px-8 text-base", lg: "h-14 px-8 text-base",
xl: "h-16 rounded-2xl px-10 text-lg", xl: "h-16 px-10 text-lg",
icon: "h-10 w-10", icon: "h-10 w-10",
}, },
}, },

View File

@@ -2,7 +2,7 @@ import * as React from "react"
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => ( const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("rounded-2xl border bg-card text-card-foreground shadow-sm", className)} {...props} /> <div ref={ref} className={cn("rounded-lg border bg-card text-card-foreground shadow-sm", className)} {...props} />
)) ))
Card.displayName = "Card" Card.displayName = "Card"

View File

@@ -14,7 +14,7 @@ export function Dialog({ open, onOpenChange, children }: DialogProps) {
<div className="fixed inset-0 z-50"> <div className="fixed inset-0 z-50">
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm" onClick={() => onOpenChange(false)} /> <div className="fixed inset-0 bg-black/50 backdrop-blur-sm" onClick={() => onOpenChange(false)} />
<div className="fixed inset-0 flex items-center justify-center p-4"> <div className="fixed inset-0 flex items-center justify-center p-4">
<div className="relative bg-background rounded-2xl shadow-xl max-w-lg w-full max-h-[90vh] overflow-y-auto p-6 animate-in fade-in-0 zoom-in-95"> <div className="relative bg-background rounded-lg shadow-xl max-w-lg w-full max-h-[90vh] overflow-y-auto p-6 animate-in fade-in-0 zoom-in-95">
{children} {children}
</div> </div>
</div> </div>

View File

@@ -40,7 +40,7 @@ DropdownMenuTrigger.displayName = "DropdownMenuTrigger"
function DropdownMenuContent({ children, className, align = "end" }: { children: React.ReactNode; className?: string; align?: "start" | "end" }) { function DropdownMenuContent({ children, className, align = "end" }: { children: React.ReactNode; className?: string; align?: "start" | "end" }) {
return ( return (
<div className={cn( <div className={cn(
"absolute z-50 min-w-[180px] overflow-hidden rounded-xl border bg-white p-1.5 shadow-lg animate-scale-in", "absolute z-50 min-w-[180px] overflow-hidden rounded-lg border bg-white p-1.5 shadow-lg animate-scale-in",
align === "end" ? "right-0" : "left-0", align === "end" ? "right-0" : "left-0",
"top-full mt-1", "top-full mt-1",
className className

View File

@@ -5,7 +5,7 @@ const Input = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLI
<input <input
type={type} type={type}
className={cn( className={cn(
"flex h-11 w-full rounded-xl border border-input bg-background px-4 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-trust-blue focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", "flex h-11 w-full rounded-lg border border-input bg-background px-4 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-trust-blue focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className className
)} )}
ref={ref} ref={ref}

View File

@@ -5,7 +5,7 @@ const Select = React.forwardRef<HTMLSelectElement, React.SelectHTMLAttributes<HT
<select <select
ref={ref} ref={ref}
className={cn( className={cn(
"flex h-11 w-full rounded-xl border border-input bg-background px-4 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-trust-blue focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", "flex h-11 w-full rounded-lg border border-input bg-background px-4 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-trust-blue focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className className
)} )}
{...props} {...props}

View File

@@ -1,7 +1,7 @@
import { cn } from "@/lib/utils" import { cn } from "@/lib/utils"
function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) { function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
return <div className={cn("animate-pulse rounded-xl bg-muted", className)} {...props} /> return <div className={cn("animate-pulse rounded-lg bg-muted", className)} {...props} />
} }
export { Skeleton } export { Skeleton }

View File

@@ -16,7 +16,7 @@ function Tabs({ value, onValueChange, children, className }: { value: string; on
function TabsList({ children, className }: { children: React.ReactNode; className?: string }) { function TabsList({ children, className }: { children: React.ReactNode; className?: string }) {
return ( return (
<div className={cn("inline-flex h-10 items-center justify-center rounded-xl bg-muted p-1 text-muted-foreground", className)}> <div className={cn("inline-flex h-10 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground", className)}>
{children} {children}
</div> </div>
) )

View File

@@ -5,7 +5,7 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, React.TextareaHTMLAttribu
({ className, ...props }, ref) => ( ({ className, ...props }, ref) => (
<textarea <textarea
className={cn( className={cn(
"flex min-h-[80px] w-full rounded-xl border border-input bg-background px-4 py-3 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-trust-blue focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50", "flex min-h-[80px] w-full rounded-lg border border-input bg-background px-4 py-3 text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-trust-blue focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className className
)} )}
ref={ref} ref={ref}

View File

@@ -33,7 +33,7 @@ export function ToastProvider({ children }: { children: React.ReactNode }) {
<div <div
key={t.id} key={t.id}
className={cn( className={cn(
"rounded-xl px-4 py-3 text-sm font-medium text-white shadow-lg animate-in slide-in-from-right", "rounded-lg px-4 py-3 text-sm font-medium text-white shadow-lg animate-in slide-in-from-right",
t.type === 'success' && 'bg-success-green', t.type === 'success' && 'bg-success-green',
t.type === 'error' && 'bg-danger-red', t.type === 'error' && 'bg-danger-red',
t.type === 'info' && 'bg-trust-blue', t.type === 'info' && 'bg-trust-blue',

View File

@@ -42,6 +42,14 @@ const config: Config = {
border: "hsl(var(--border))", border: "hsl(var(--border))",
input: "hsl(var(--input))", input: "hsl(var(--input))",
ring: "hsl(var(--ring))", ring: "hsl(var(--ring))",
// Brand colors — named by psychology, not appearance
midnight: "#111827",
"promise-blue": "#1e40af",
"generosity-gold": "#f59e0b",
"fulfilled-green": "#16a34a",
"alert-red": "#dc2626",
paper: "#f9fafb",
// Legacy aliases (used across codebase)
"trust-blue": "#1e40af", "trust-blue": "#1e40af",
"warm-amber": "#f59e0b", "warm-amber": "#f59e0b",
"success-green": "#16a34a", "success-green": "#16a34a",