From 3cf7e025616948734694023011ab42e73fc9d53d Mon Sep 17 00:00:00 2001 From: nmemmert Date: Mon, 12 Oct 2026 16:22:27 -0400 Subject: [PATCH] Contact page redesign: message-type chips with hints, side-by-side names, character count, sticky profile card, form first on phones, compact Ko-fi card; v2.27.0 Co-Authored-By: Claude Sonnet 5.5 --- package-lock.json | 4 +- package.json | 2 +- src/App.css | 244 +++++++++++++++------------------ src/App.tsx | 141 ++++++++++--------- src/components/ContactForm.tsx | 116 ++++++++++------ 5 files changed, 254 insertions(+), 253 deletions(-) diff --git a/package-lock.json b/package-lock.json index c823d1c..fe39f67 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "siteforge", - "version": "2.26.0", + "version": "2.27.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "siteforge", - "version": "2.26.0", + "version": "2.27.0", "dependencies": { "@aws-sdk/client-s3": "^3.1135.0", "@dnd-kit/core": "^6.3.1", diff --git a/package.json b/package.json index 298e32f..d55a00d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "siteforge", "private": true, - "version": "2.26.0", + "version": "2.27.0", "type": "module", "scripts": { "dev": "vite", diff --git a/src/App.css b/src/App.css index cbd85fb..c2911c6 100644 --- a/src/App.css +++ b/src/App.css @@ -3827,138 +3827,41 @@ margin-bottom: 0.75rem; } -.section-contact { - background: #090909; - border-top: 1px solid rgba(201, 168, 76, 0.18); - border-bottom: 1px solid rgba(201, 168, 76, 0.18); - padding: 5rem 0; -} -.contact-inner { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 2.5rem; - align-items: start; -} -.contact-card { - background: #111111; - border: 1px solid rgba(201, 168, 76, 0.2); - border-radius: 16px; - padding: 2.3rem; - max-width: 500px; -} -.contact-card .eyebrow { - margin: 0 0 0.45rem; -} -.contact-card h2 { - margin: 0 0 1.75rem; - font-family: var(--brand-font-heading); - color: var(--brand-warm-white); - font-size: clamp(1.45rem, 2vw, 1.9rem); - line-height: 1.3; - font-weight: 600; -} -.contact-profile { - display: flex; - align-items: flex-start; - gap: 1rem; - margin-bottom: 1.75rem; -} -.contact-profile-photo { - width: 108px; - height: 108px; - border-radius: 999px; - object-fit: cover; - object-position: center 22%; - border: 2px solid var(--brand-gold); - flex-shrink: 0; - box-shadow: - 0 0 0 4px rgba(201, 168, 76, 0.16), - 0 10px 28px rgba(0, 0, 0, 0.42); -} -.contact-profile-name { - margin: 0 0 0.15rem; - font-family: var(--brand-font-body); - font-size: 1.45rem; - font-weight: 600; - color: var(--brand-warm-white); -} -.contact-profile-role { - margin: 0; - font-family: var(--brand-font-body); - font-size: 0.95rem; - color: var(--brand-gold); -} -.contact-quote { - border-left: 2px solid var(--brand-gold); - padding-left: 1rem; - margin: 0 0 1.75rem; -} -.contact-quote p { - margin: 0; - font-family: var(--brand-font-body); - font-size: 1.35rem; - font-style: italic; - font-weight: 500; - color: #d4c4a0; - line-height: 1.6; -} -.contact-intro { - margin: 0 0 1rem; - font-family: var(--brand-font-body); - font-size: 1rem; - line-height: 1.6; - color: #9a9994; -} -.contact-points { - display: flex; - flex-direction: column; - gap: 0.5rem; -} -.contact-point { - display: flex; - align-items: center; - gap: 0.65rem; - color: #8f8e89; - font-family: var(--brand-font-body); - font-size: 0.92rem; -} -.contact-point svg { - color: var(--brand-gold); - flex: 0 0 auto; -} -.contact-scripture { - margin-top: 1.75rem; - padding-top: 1.5rem; - border-top: 1px solid #2c2116; -} -.contact-scripture p { - margin: 0; - font-family: var(--brand-font-body); - font-size: 0.88rem; - font-style: italic; - line-height: 1.6; - color: #a09888; -} -.contact-scripture-ref { - white-space: nowrap; -} + + + + + + + + + + + + + + + + + + .contact-form { background: #121212; @@ -7828,19 +7731,11 @@ a.study-track-card:hover { grid-template-columns: 1fr; } - .contact-inner { - grid-template-columns: 1fr; - gap: 1.5rem; - } + - .contact-card { - max-width: 100%; - padding: 1.45rem; - } + - .contact-quote p { - font-size: 1.2rem; - } + .header-logo-img { height: 40px; @@ -12801,14 +12696,14 @@ a.study-track-card:hover { .em-webhook-add-title { font-size: 0.85rem; font-weight: 600; color: #c0bcd4; margin: 0 0 0.75rem; } /* ── Ko-fi support section ───────────────────────────────────────────────── */ -.section-kofi-support { background: var(--brand-black-2, #0f0f0c); padding: 4.5rem 1.5rem; text-align: center; border-top: 1px solid var(--brand-border, #2a2518); border-bottom: 1px solid var(--brand-border, #2a2518); } -.kofi-support-inner { max-width: 520px; margin: 0 auto; display: flex; flex-direction: column; align-items: center; gap: 1.25rem; } -.kofi-support-inner .eyebrow { color: var(--brand-gold, #c9a84c); } -.kofi-support-inner h2 { font-family: var(--brand-font-heading, 'Cormorant Garamond', serif); font-size: 2.2rem; font-weight: 600; color: var(--brand-warm-white, #f0ead8); margin: 0; } -.kofi-support-desc { font-size: 1rem; line-height: 1.75; color: var(--brand-muted, #b0a48c); max-width: 460px; margin: 0; } -.kofi-qr-wrap { display: flex; flex-direction: column; align-items: center; gap: 0.6rem; margin: 0.5rem 0; } -.kofi-qr-img { width: 180px; height: 180px; border-radius: 8px; border: 1px solid var(--brand-border, #2a2518); } -.kofi-qr-caption { font-size: 0.78rem; color: var(--brand-muted, #b0a48c); letter-spacing: 0.03em; } + + + + + + + + .kofi-btn { display: inline-block; background: transparent; color: var(--brand-gold, #c9a84c); border: 1px solid var(--brand-gold, #c9a84c); font-size: 0.9rem; font-weight: 600; padding: 0.7rem 1.75rem; border-radius: 4px; text-decoration: none; letter-spacing: 0.04em; transition: background 0.2s, color 0.2s; } .kofi-btn:hover { background: var(--brand-gold, #c9a84c); color: var(--brand-black, #0a0a08); } @@ -13774,3 +13669,84 @@ a.study-track-card:hover { @media (max-width: 480px) { .dl-form .study-download-grid { grid-template-columns: minmax(0, 1fr); } } + +/* ── Contact page ──────────────────────────────────────────────────────── */ +.cx-section { padding: 2.5rem 0 2rem; } +.cx-layout { display: grid; grid-template-columns: minmax(0, 340px) minmax(0, 1fr); gap: 2.5rem; align-items: start; } +.cx-side { position: sticky; top: 9rem; display: flex; flex-direction: column; gap: 1.1rem; } +.cx-profile { display: flex; align-items: center; gap: 0.9rem; } +.cx-photo { width: 68px; height: 68px; border-radius: 50%; object-fit: cover; border: 2px solid var(--brand-gold-dark); flex-shrink: 0; } +.cx-name { margin: 0; font-family: var(--brand-font-heading); font-size: 1.5rem; font-weight: 600; color: var(--brand-warm-white); line-height: 1.1; } +.cx-role { margin: 0.2rem 0 0; font-size: 0.88rem; color: var(--brand-muted); } +.cx-quote { margin: 0; padding: 0 0 0 1rem; border-left: 2px solid var(--brand-gold-dark); font-size: 1rem; line-height: 1.6; font-style: italic; color: var(--brand-warm-white); } +.cx-intro { margin: 0; font-size: 0.98rem; line-height: 1.6; color: var(--brand-muted); } +.cx-points { display: flex; flex-direction: column; gap: 0.5rem; } +.cx-point { display: flex; align-items: flex-start; gap: 0.6rem; font-size: 0.9rem; color: var(--brand-muted); } +.cx-point svg { margin-top: 0.2rem; flex-shrink: 0; color: var(--brand-gold); } +.cx-verse { margin: 0; font-size: 0.88rem; line-height: 1.55; color: var(--brand-muted); } +.cx-verse span { color: var(--brand-gold); white-space: nowrap; } +.cx-qa-link { align-self: flex-start; font-size: 0.9rem; color: var(--brand-gold); text-decoration: none; } +.cx-qa-link:hover { text-decoration: underline; } + +.cx-form-card { background: var(--brand-black-3); border: 1px solid var(--brand-border); border-radius: 16px; padding: 1.75rem; } +.cx-form { display: flex; flex-direction: column; gap: 1.1rem; } +.cx-form label { display: flex; flex-direction: column; gap: 0.35rem; font-size: 0.85rem; font-weight: 500; color: var(--brand-muted); } +.cx-form input[type='text'], .cx-form input[type='email'], .cx-form textarea { + font: inherit; font-size: 1rem; font-weight: 400; color: var(--brand-warm-white); background: var(--brand-black); + border: 1px solid var(--brand-border); border-radius: 8px; padding: 0.7rem 0.85rem; width: 100%; min-width: 0; + transition: border-color 150ms, box-shadow 150ms; +} +.cx-form textarea { resize: vertical; min-height: 9rem; line-height: 1.55; } +.cx-form input:focus, .cx-form textarea:focus { outline: none; border-color: var(--brand-gold); box-shadow: 0 0 0 3px rgba(201, 168, 76, 0.16); } +.cx-row { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 1rem; } +.cx-count { align-self: flex-end; font-size: 0.75rem; color: var(--brand-muted); opacity: 0.8; } + +.cx-types { border: 0; margin: 0; padding: 0; min-width: 0; } +.cx-types legend { padding: 0; margin-bottom: 0.55rem; font-size: 0.85rem; font-weight: 500; color: var(--brand-muted); } +.cx-type-row { display: flex; flex-wrap: wrap; gap: 0.5rem; } +.cx-form .cx-type { position: relative; flex-direction: row; gap: 0; cursor: pointer; } +.cx-type input { position: absolute; opacity: 0; width: 1px; height: 1px; } +.cx-type span { + font-size: 0.9rem; padding: 0.4rem 0.95rem; border-radius: 999px; color: var(--brand-muted); + border: 1px solid var(--brand-border); transition: border-color 150ms, background 150ms, color 150ms; +} +.cx-type:hover span { border-color: var(--brand-gold-dark); color: var(--brand-warm-white); } +.cx-type input:checked + span { background: var(--brand-gold); border-color: var(--brand-gold); color: var(--brand-black); font-weight: 600; } +.cx-type input:focus-visible + span { outline: 2px solid var(--brand-gold-light); outline-offset: 2px; } +.cx-hint { margin: 0.6rem 0 0; font-size: 0.88rem; color: var(--brand-muted); } + +.cx-checks { display: flex; flex-direction: column; gap: 0.6rem; } +.cx-form label.cx-check { flex-direction: row; align-items: flex-start; gap: 0.6rem; font-size: 0.9rem; font-weight: 400; cursor: pointer; } +.cx-check input { margin-top: 0.22rem; flex-shrink: 0; accent-color: var(--brand-gold); } +.cx-submit { align-self: flex-start; justify-content: center; min-width: 200px; } + +.cx-support { padding: 1rem 0 3.5rem; } +.cx-support-card { + display: flex; align-items: center; justify-content: space-between; gap: 2rem; padding: 1.5rem 1.75rem; + background: linear-gradient(135deg, rgba(201, 168, 76, 0.1), rgba(201, 168, 76, 0.02)); + border: 1px solid var(--brand-gold-dark); border-radius: 16px; +} +.cx-support-copy { display: flex; flex-direction: column; align-items: flex-start; gap: 0.5rem; min-width: 0; } +.cx-support-copy .eyebrow { margin: 0; } +#root > :not(.admin-shell) .cx-support h2 { font-size: clamp(1.4rem, 2.6vw, 1.75rem); margin: 0; line-height: 1.2; } +.cx-support-copy p:not(.eyebrow) { margin: 0 0 0.4rem; font-size: 0.95rem; line-height: 1.6; color: var(--brand-muted); max-width: 52ch; } +.cx-support-qr { margin: 0; flex-shrink: 0; text-align: center; } +.cx-support-qr img { width: 120px; height: 120px; border-radius: 10px; background: #fff; padding: 6px; display: block; } +.cx-support-qr figcaption { margin-top: 0.4rem; font-size: 0.78rem; color: var(--brand-muted); } + +@media (max-width: 900px) { + .cx-layout { grid-template-columns: minmax(0, 1fr); gap: 1.5rem; } + .cx-side { position: static; order: 2; } + .cx-form-card { order: 1; } +} +@media (max-width: 600px) { + .cx-section { padding: 1.5rem 0 1rem; } + .cx-form-card { padding: 1.1rem; } + .cx-submit { align-self: stretch; } + .cx-support-card { flex-direction: column; align-items: flex-start; gap: 1.1rem; padding: 1.25rem; } + .cx-support-qr { align-self: center; } +} +@media (max-width: 480px) { + .cx-row { grid-template-columns: minmax(0, 1fr); } +} + diff --git a/src/App.tsx b/src/App.tsx index 6a0370d..5c24935 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -637,25 +637,29 @@ function KofiSupportSection({ content }: { content: SiteContent }) { const qrUrl = content.platformKofiQrUrl if (!kofiUrl && !qrUrl) return null return ( -
-
-

Support the Ministry

-

Help Keep the Word Going

-

- This podcast and study resources are listener-supported. If God has used this ministry in your life, - consider buying me a coffee — it goes directly toward keeping the teaching free for everyone. -

+
+
+
+
+

Support the Ministry

+

Help Keep the Word Going

+

+ This podcast and its study resources are listener-supported. If God has used this ministry in your life, + consider buying me a coffee. It goes straight toward keeping the teaching free for everyone. +

+ {kofiUrl && ( + + ☕ Support on Ko-fi + + )} +
{qrUrl && ( -
- Ko-fi donation QR code -

Scan to give on Ko-fi

-
- )} - {kofiUrl && ( - - ☕ Support on Ko-fi - +
+ Ko-fi donation QR code +
Scan to give on Ko-fi
+
)} +
) @@ -694,64 +698,59 @@ function AboutSection({ content }: { content: SiteContent }) { function ContactSection({ content }: { content: SiteContent }) { + const point = (text: string, icon: ReactElement) => ( +
+ {icon} + {text} +
+ ) return ( -
-
-
-
-
- {content.contactName -
-

{content.contactName || 'Nate'}

-

{content.contactRole || 'Bible teacher · Lynchburg, VA'}

-
+
+
+ + +
+
-
) diff --git a/src/components/ContactForm.tsx b/src/components/ContactForm.tsx index fae6501..cd0a58b 100644 --- a/src/components/ContactForm.tsx +++ b/src/components/ContactForm.tsx @@ -3,6 +3,7 @@ import type { ChangeEvent } from 'react' import { useNavigate, useSearchParams } from 'react-router-dom' const EMAIL_REGEX = /^[a-zA-Z0-9._%+-]{1,64}@[a-zA-Z0-9.-]{1,253}\.[a-zA-Z]{2,}$/ +const MAX_MESSAGE = 3000 interface ContactFields { firstName: string @@ -12,12 +13,20 @@ interface ContactFields { messageType: string } +const TYPES: { value: string; label: string; hint: string; placeholder: string }[] = [ + { value: 'question', label: 'Bible question', hint: 'Ask anything about Scripture. Answers are posted on the Q&A page.', placeholder: 'What would you like to know?' }, + { value: 'community', label: 'Community question', hint: 'A question about a study discussion or the community.', placeholder: 'What’s on your mind?' }, + { value: 'testimony', label: 'Testimony', hint: 'Share what God is doing in your life. Every one is read.', placeholder: 'Tell your story…' }, + { value: 'topic', label: 'Topic request', hint: 'Suggest a passage or topic for a future episode.', placeholder: 'Which passage or topic?' }, + { value: 'general', label: 'General message', hint: 'Anything else you’d like to say.', placeholder: 'Write your message…' }, +] + export default function ContactForm() { const navigate = useNavigate() const [searchParams] = useSearchParams() const source = searchParams.get('source') const studyName = searchParams.get('study') - + const [fields, setFields] = useState({ firstName: '', lastName: '', @@ -42,6 +51,8 @@ export default function ContactForm() { const [errorMsg, setErrorMsg] = useState('') const [fieldErrors, setFieldErrors] = useState<{ email?: string }>({}) + const activeType = TYPES.find(t => t.value === fields.messageType) ?? TYPES[0] + function handleChange(e: ChangeEvent) { const { name, value } = e.target setFields(f => ({ ...f, [name]: value })) @@ -59,10 +70,6 @@ export default function ContactForm() { } } - function handleSelectChange(e: ChangeEvent) { - setFields(f => ({ ...f, [e.target.name]: e.target.value })) - } - async function handleSubmit(e: React.FormEvent) { e.preventDefault() setStatus('submitting') @@ -90,7 +97,7 @@ export default function ContactForm() { } return ( -
+ setHoney(e.target.value)} /> - - + +
+ What is this about? +
+ {TYPES.map(t => ( + + ))} +
+

{activeType.hint}

+
+ +
+ + +
+ - +